From 86553f8c19c2c484d0035bc0e7737199602b2523 Mon Sep 17 00:00:00 2001 From: Thibault Vataire Date: Wed, 5 Aug 2026 00:24:48 +0200 Subject: [PATCH 001/176] FE/Qt: Fix: when several machines are selected, the refresh feature only refreshes one of them. Preserves the selection on refresh. --- .../src/manager/chooser/UIChooserModel.cpp | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/src/VBox/Frontends/VirtualBox/src/manager/chooser/UIChooserModel.cpp b/src/VBox/Frontends/VirtualBox/src/manager/chooser/UIChooserModel.cpp index dfd78327897a..b978bfd4f40d 100644 --- a/src/VBox/Frontends/VirtualBox/src/manager/chooser/UIChooserModel.cpp +++ b/src/VBox/Frontends/VirtualBox/src/manager/chooser/UIChooserModel.cpp @@ -916,14 +916,30 @@ void UIChooserModel::startOrShowSelectedItems() void UIChooserModel::refreshSelectedMachineItems() { + /* Remember selected items */ + QStringList selectedItemDefinitions; + foreach (UIChooserItem *pSelectedItem, selectedItems()) + { + AssertPtrReturnVoid(pSelectedItem); + selectedItemDefinitions << pSelectedItem->definition(); + } + + /* Remember current item */ + UIChooserItem *pCurItem = currentItem(); + AssertPtrReturnVoid(pCurItem); + QString currentItemDefinition = pCurItem->definition(); + + /* Remember scrolling location */ + const int iScrollLocation = m_pRoot ? m_pRoot->toGroupItem()->scrollingValue() : 0; + /* Gather list of current unique inaccessible machine-items: */ QList inaccessibleMachineItemList; UIChooserItemMachine::enumerateMachineItems(selectedItems(), inaccessibleMachineItemList, UIChooserItemMachineEnumerationFlag_Unique | UIChooserItemMachineEnumerationFlag_Inaccessible); - /* Prepare item to be selected: */ - UIChooserItem *pSelectedItem = 0; + /* Ids of all local machines to refresh */ + QList localMachineItemIds; /* For each machine-item: */ foreach (UIChooserItemMachine *pItem, inaccessibleMachineItemList) @@ -939,15 +955,10 @@ void UIChooserModel::refreshSelectedMachineItems() /* Became accessible? */ if (pItem->accessible()) { - /* Acquire machine ID: */ - const QUuid uId = pItem->id(); - /* Reload this machine: */ - sltReloadMachine(uId); - /* Select first of reloaded items: */ - if (!pSelectedItem) - pSelectedItem = root()->searchForItem(uId.toString(), - UIChooserItemSearchFlag_Machine | - UIChooserItemSearchFlag_ExactId); + /* Call to the sltReloadMachine method have to be delayed because after the first call to this + * method, cache type of remaining items become UIVirtualMachineItemType_Invalid so related + * machines are not refreshed. */ + localMachineItemIds << pItem->id(); } break; @@ -978,12 +989,33 @@ void UIChooserModel::refreshSelectedMachineItems() } } - /* Some item to be selected? */ - if (pSelectedItem) + foreach (QUuid uId, localMachineItemIds) { + sltReloadMachine(uId); + } + + /* Restore selected items */ + QList itemsToSelect; + foreach (const QString &strSelectedItemDefinition, selectedItemDefinitions) { - pSelectedItem->makeSureItsVisible(); - setSelectedItem(pSelectedItem); + UIChooserItem *pItemToSelect = searchItemByDefinition(strSelectedItemDefinition); + if (pItemToSelect) + { + itemsToSelect << pItemToSelect; + } } + setSelectedItems(itemsToSelect); + makeSureAtLeastOneItemSelected(); + + /* Restore current item */ + pCurItem = searchItemByDefinition(currentItemDefinition); + if (!pCurItem || !selectedItems().contains(pCurItem)) + { + pCurItem = firstSelectedItem(); + } + setCurrentItem(pCurItem); + + /* Restore scrolling location: */ + m_pRoot->toGroupItem()->setScrollingValue(iScrollLocation); } void UIChooserModel::sortSelectedGroupItem() From 65a55db5e16301dde4eb7f32aa2e0a650f732cd7 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 4 Aug 2026 09:49:02 +0000 Subject: [PATCH 002/176] FE/Qt: Fixed loading / handling inaccessible VM configurations more gracefully (and the same way as FE/VBoxManage). Before the fix the UI simply would assert and not come up. svn:sync-xref-src-repo-rev: r174685 --- .../Frontends/VirtualBox/src/manager/UIToolPane.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/VBox/Frontends/VirtualBox/src/manager/UIToolPane.cpp b/src/VBox/Frontends/VirtualBox/src/manager/UIToolPane.cpp index 472de368b909..e21dd18d7405 100644 --- a/src/VBox/Frontends/VirtualBox/src/manager/UIToolPane.cpp +++ b/src/VBox/Frontends/VirtualBox/src/manager/UIToolPane.cpp @@ -1,4 +1,4 @@ -/* $Id: UIToolPane.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: UIToolPane.cpp 114848 2026-08-04 09:49:02Z andreas.loeffler@oracle.com $ */ /** @file * VBox Qt GUI - UIToolPane class implementation. */ @@ -526,8 +526,12 @@ void UIToolPane::setErrorDetails(const QString &strDetails) void UIToolPane::setItems(const QList &items) { - /* Cache passed value: */ - m_items = items; + /* Cache accessible items only. Inaccessible machines expose limited + * information and are handled by the Error pane. */ + m_items.clear(); + foreach (UIVirtualMachineItem *pItem, items) + if (pItem && pItem->accessible()) + m_items << pItem; /* Update details pane if it is open: */ if (isToolOpened(UIToolType_Details)) From c9121270291bdda96c6419b76cb4befb6948f6d2 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Tue, 4 Aug 2026 16:28:13 +0000 Subject: [PATCH 003/176] WDDM: ring of command buffer headers (moved debug assert to the right place). bugref:10934. svn:sync-xref-src-repo-rev: r174687 --- .../win/Graphics/Video/mp/wddm/gallium/SvgaFifo.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/SvgaFifo.cpp b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/SvgaFifo.cpp index cd26f2c6644f..b2b3da74f57f 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/SvgaFifo.cpp +++ b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/SvgaFifo.cpp @@ -1,4 +1,4 @@ -/* $Id: SvgaFifo.cpp 114840 2026-07-31 22:12:25Z vitali.pelenjow@oracle.com $ */ +/* $Id: SvgaFifo.cpp 114850 2026-08-04 16:28:13Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox Windows Guest Mesa3D - VMSVGA FIFO. */ @@ -546,6 +546,11 @@ static NTSTATUS svgaCBSubmitLocked(PVBOXWDDM_EXT_VMSVGA pSvga, PVMSVGACB pCB, PV PVMSVGACBSTATE pCBState = pSvga->pCBState; PVMSVGACBHEADERS pCBHeaders = cbStateHeaders(pCBState); + /* Allocate a header for the buffer. */ + Assert(pCBCtx->cHeaders <= RT_ELEMENTS(pCBHeaders->aContext0CBHeaders)); + if (pCBCtx->cHeaders == 0) + return STATUS_PENDING; + #ifdef DEBUG Assert(!pCB->fSubmitted); if (pCB->fSubmitted) @@ -553,11 +558,6 @@ static NTSTATUS svgaCBSubmitLocked(PVBOXWDDM_EXT_VMSVGA pSvga, PVMSVGACB pCB, PV pCB->fSubmitted = true; #endif - /* Allocate a header for the buffer. */ - Assert(pCBCtx->cHeaders <= RT_ELEMENTS(pCBHeaders->aContext0CBHeaders)); - if (pCBCtx->cHeaders == 0) - return STATUS_PENDING; - SVGACBHeader *pCBHeader = &pCBHeaders->aContext0CBHeaders[pCBCtx->idxNextHeader]; pCBCtx->idxNextHeader = (pCBCtx->idxNextHeader + 1) % RT_ELEMENTS(pCBHeaders->aContext0CBHeaders); --pCBCtx->cHeaders; From f72dc7665c0204d71e36a5635fbf4ef4e377f630 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Tue, 4 Aug 2026 16:33:04 +0000 Subject: [PATCH 004/176] Devices/Graphics: debug logging. bugref:10934 svn:sync-xref-src-repo-rev: r174690 --- .../Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp index d1be0ce7eb88..ffc76118c584 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114814 2026-07-28 15:06:29Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114853 2026-08-04 16:33:04Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -7851,12 +7851,15 @@ static void dxEnsureViews(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) AssertRC(rc); #ifdef LOG_ENABLED - SVGACOTableDXDSViewEntry const *pDSViewEntry = &pDXContext->cot.paDSView[viewId]; - PVMSVGA3DSURFACE pSurface = NULL; - vmsvga3dSurfaceFromSid(pThisCC->svga.p3dState, pDXView->sid, &pSurface); - LogFunc(("dsv sid = %u, dsvid = %u, format = %s(%d), %dx%d\n", - pDXView->sid, viewId, vmsvgaLookupEnum((int)pDSViewEntry->format, &g_SVGA3dSurfaceFormat2String), pDSViewEntry->format, - pSurface->paMipmapLevels[0].cBlocksX * pSurface->cxBlock, pSurface->paMipmapLevels[0].cBlocksY * pSurface->cyBlock)); + if (RT_SUCCESS(rc)) + { + SVGACOTableDXDSViewEntry const *pDSViewEntry = &pDXContext->cot.paDSView[viewId]; + PVMSVGA3DSURFACE pSurface = NULL; + vmsvga3dSurfaceFromSid(pThisCC->svga.p3dState, pDXView->sid, &pSurface); + LogFunc(("dsv sid = %u, dsvid = %u, format = %s(%d), %dx%d\n", + pDXView->sid, viewId, vmsvgaLookupEnum((int)pDSViewEntry->format, &g_SVGA3dSurfaceFormat2String), pDSViewEntry->format, + pSurface->paMipmapLevels[0].cBlocksX * pSurface->cxBlock, pSurface->paMipmapLevels[0].cBlocksY * pSurface->cyBlock)); + } #endif } From 8fb0e16396ac912c164d4451adf501376a71246a Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Tue, 4 Aug 2026 18:26:47 +0000 Subject: [PATCH 005/176] Devices/Graphics: handle cubemaps for depth stencil views. svn:sync-xref-src-repo-rev: r174691 --- src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp index ffc76118c584..0a4774c7b4dd 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114853 2026-08-04 16:33:04Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114854 2026-08-04 18:26:47Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -2519,6 +2519,13 @@ static HRESULT dxDepthStencilViewCreate(PVGASTATECC pThisCC, SVGACOTableDXDSView desc.Texture2DArray.ArraySize = pEntry->arraySize; } break; + case SVGA3D_RESOURCE_TEXTURECUBE: + /* Cube is a 6 elements array. */ + desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY; + desc.Texture2DArray.MipSlice = pEntry->mipSlice; + desc.Texture2DArray.FirstArraySlice = pEntry->firstArraySlice; + desc.Texture2DArray.ArraySize = pEntry->arraySize; + break; default: ASSERT_GUEST_FAILED_RETURN(E_INVALIDARG); } From 94b7d4626772d09c11ec4ac783b341ddf1f6e7dd Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Tue, 4 Aug 2026 19:16:41 +0000 Subject: [PATCH 006/176] WDDM: resource views must reference the resource allocation svn:sync-xref-src-repo-rev: r174692 --- .../Graphics/Video/disp/wddm/dx/VBoxDX.cpp | 184 +++++++++++++----- .../win/Graphics/Video/disp/wddm/dx/VBoxDX.h | 6 +- .../Graphics/Video/disp/wddm/dx/VBoxDXCmd.cpp | 171 ++++++++++++---- .../Graphics/Video/disp/wddm/dx/VBoxDXCmd.h | 59 ++++-- .../Graphics/Video/disp/wddm/dx/VBoxDXDDI.cpp | 70 +------ .../Video/disp/wddm/dx/VBoxDXVideo.cpp | 47 +++-- 6 files changed, 352 insertions(+), 185 deletions(-) diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.cpp b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.cpp index fc6fcdfdfaf4..dbb3388ae92b 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.cpp +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxDX.cpp 114634 2026-07-07 15:34:54Z vitali.pelenjow@oracle.com $ */ +/* $Id: VBoxDX.cpp 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox D3D user mode driver. */ @@ -704,16 +704,20 @@ void vboxDXStorePatchLocation(PVBOXDX_DEVICE pDevice, void *pvPatch, PVBOXDXKMRE pKMResource->LastReferencedFenceValue = pDevice->ContextMonitoring.CurrentFenceValue; } - D3DDDI_PATCHLOCATIONLIST *pPatchLocation = &pDevice->pPatchLocationList[pDevice->cPatchLocations]; - pPatchLocation->AllocationIndex = idxAllocation; - pPatchLocation->Value = 0; - pPatchLocation->DriverId = DriverId == 0 - ? pKMResource->AllocationDesc.enmAllocationType - : DriverId; - pPatchLocation->AllocationOffset = offAllocation; - pPatchLocation->PatchOffset = (uintptr_t)pvPatch - (uintptr_t)pDevice->pCommandBuffer; - pPatchLocation->SplitOffset = pDevice->cbCommandBuffer; - ++pDevice->cPatchLocations; + /* Add an optional patch location. */ + if (pvPatch) + { + D3DDDI_PATCHLOCATIONLIST *pPatchLocation = &pDevice->pPatchLocationList[pDevice->cPatchLocations]; + pPatchLocation->AllocationIndex = idxAllocation; + pPatchLocation->Value = 0; + pPatchLocation->DriverId = DriverId == 0 + ? pKMResource->AllocationDesc.enmAllocationType + : DriverId; + pPatchLocation->AllocationOffset = offAllocation; + pPatchLocation->PatchOffset = (uintptr_t)pvPatch - (uintptr_t)pDevice->pCommandBuffer; + pPatchLocation->SplitOffset = pDevice->cbCommandBuffer; + ++pDevice->cPatchLocations; + } /* Move the KM resource to the head of the resource list. */ RTListNodeRemove(&pKMResource->nodeResource); @@ -3538,7 +3542,8 @@ void vboxDXCreateShaderResourceView(PVBOXDX_DEVICE pDevice, PVBOXDXSHADERRESOURC void vboxDXGenMips(PVBOXDX_DEVICE pDevice, PVBOXDXSHADERRESOURCEVIEW pShaderResourceView) { - vgpu10GenMips(pDevice, pShaderResourceView->uShaderResourceViewId); + vgpu10GenMips(pDevice, pShaderResourceView->uShaderResourceViewId, + vboxDXGetKMResource(pShaderResourceView->pResource)); } @@ -3546,7 +3551,8 @@ void vboxDXDestroyShaderResourceView(PVBOXDX_DEVICE pDevice, PVBOXDXSHADERRESOUR { RTListNodeRemove(&pShaderResourceView->nodeView); - vgpu10DestroyShaderResourceView(pDevice, pShaderResourceView->uShaderResourceViewId); + vgpu10DestroyShaderResourceView(pDevice, pShaderResourceView->uShaderResourceViewId, + vboxDXGetKMResource(pShaderResourceView->pResource)); RTHandleTableFree(pDevice->hHTShaderResourceView, pShaderResourceView->uShaderResourceViewId); } @@ -3603,13 +3609,15 @@ void vboxDXCreateRenderTargetView(PVBOXDX_DEVICE pDevice, PVBOXDXRENDERTARGETVIE void vboxDXClearRenderTargetView(PVBOXDX_DEVICE pDevice, PVBOXDXRENDERTARGETVIEW pRenderTargetView, const FLOAT ColorRGBA[4]) { - vgpu10ClearRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId, ColorRGBA); + vgpu10ClearRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId, + vboxDXGetKMResource(pRenderTargetView->pResource), ColorRGBA); } void vboxDXClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, PVBOXDXRENDERTARGETVIEW pRenderTargetView, const FLOAT Color[4], const D3D10_DDI_RECT *pRect, UINT NumRects) { - vgpu10ClearRenderTargetViewRegion(pDevice, pRenderTargetView->uRenderTargetViewId, Color, pRect, NumRects); + vgpu10ClearRenderTargetViewRegion(pDevice, pRenderTargetView->uRenderTargetViewId, + vboxDXGetKMResource(pRenderTargetView->pResource), Color, pRect, NumRects); } @@ -3625,7 +3633,8 @@ void vboxDXDestroyRenderTargetView(PVBOXDX_DEVICE pDevice, PVBOXDXRENDERTARGETVI RTListNodeRemove(&pRenderTargetView->nodeView); - vgpu10DestroyRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId); + vgpu10DestroyRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId, + vboxDXGetKMResource(pRenderTargetView->pResource)); RTHandleTableFree(pDevice->hHTRenderTargetView, pRenderTargetView->uRenderTargetViewId); } @@ -3679,7 +3688,8 @@ void vboxDXClearDepthStencilView(PVBOXDX_DEVICE pDevice, PVBOXDXDEPTHSTENCILVIEW svgaFlags |= SVGA3D_CLEAR_DEPTH; if (Flags & D3D10_DDI_CLEAR_STENCIL) svgaFlags |= SVGA3D_CLEAR_STENCIL; - vgpu10ClearDepthStencilView(pDevice, svgaFlags, Stencil, pDepthStencilView->uDepthStencilViewId, Depth); + vgpu10ClearDepthStencilView(pDevice, svgaFlags, Stencil, pDepthStencilView->uDepthStencilViewId, + vboxDXGetKMResource(pDepthStencilView->pResource), Depth); } @@ -3692,7 +3702,8 @@ void vboxDXDestroyDepthStencilView(PVBOXDX_DEVICE pDevice, PVBOXDXDEPTHSTENCILVI RTListNodeRemove(&pDepthStencilView->nodeView); - vgpu10DestroyDepthStencilView(pDevice, pDepthStencilView->uDepthStencilViewId); + vgpu10DestroyDepthStencilView(pDevice, pDepthStencilView->uDepthStencilViewId, + vboxDXGetKMResource(pDepthStencilView->pResource)); RTHandleTableFree(pDevice->hHTDepthStencilView, pDepthStencilView->uDepthStencilViewId); } @@ -3724,17 +3735,21 @@ void vboxDXSetRenderTargets(PVBOXDX_DEVICE pDevice, PVBOXDXDEPTHSTENCILVIEW pDep pDevice->pipeline.pDepthStencilView = pDepthStencilView; - /* Fetch view ids.*/ + /* Fetch view ids and kernel mode resources. */ uint32_t aRenderTargetViewIds[SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS]; + PVBOXDXKMRESOURCE aViewKMResources[SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS]; for (unsigned i = 0; i < NumRTVs; ++i) { PVBOXDXRENDERTARGETVIEW pRenderTargetView = papRenderTargetViews[i]; aRenderTargetViewIds[i] = pRenderTargetView ? pRenderTargetView->uRenderTargetViewId : SVGA3D_INVALID_ID; + aViewKMResources[i] = pRenderTargetView ? vboxDXGetKMResource(pRenderTargetView->pResource) : NULL; } uint32_t DepthStencilViewId = pDepthStencilView ? pDepthStencilView->uDepthStencilViewId : SVGA3D_INVALID_ID; - vgpu10SetRenderTargets(pDevice, DepthStencilViewId, NumRTVs, ClearSlots, aRenderTargetViewIds); + vgpu10SetRenderTargets(pDevice, DepthStencilViewId, pDepthStencilView ? vboxDXGetKMResource(pDepthStencilView->pResource) : NULL, + NumRTVs, ClearSlots, aRenderTargetViewIds, + aViewKMResources); } @@ -3759,15 +3774,17 @@ void vboxDXSetShaderResourceViews(PVBOXDX_DEVICE pDevice, SVGA3dShaderType enmSh } pSRVS->cShaderResourceView = cSRV; - /* Fetch View ids. */ + /* Fetch View ids and kernel mode resources. */ uint32_t aViewIds[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; + PVBOXDXKMRESOURCE aViewKMResources[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; for (unsigned i = 0; i < NumViews; ++i) { VBOXDXSHADERRESOURCEVIEW *pView = papViews[i]; aViewIds[i] = pView ? pView->uShaderResourceViewId : SVGA3D_INVALID_ID; + aViewKMResources[i] = pView ? vboxDXGetKMResource(pView->pResource) : NULL; } - vgpu10SetShaderResources(pDevice, enmShaderType, StartSlot, NumViews, aViewIds); + vgpu10SetShaderResources(pDevice, enmShaderType, StartSlot, NumViews, aViewIds, aViewKMResources); } @@ -3878,7 +3895,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pShaderResourceView->fDefined) { - vgpu10DestroyShaderResourceView(pDevice, pShaderResourceView->uShaderResourceViewId); + vgpu10DestroyShaderResourceView(pDevice, pShaderResourceView->uShaderResourceViewId, + vboxDXGetKMResource(pShaderResourceView->pResource)); pShaderResourceView->fDefined = false; } } @@ -3888,7 +3906,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pRenderTargetView->fDefined) { - vgpu10DestroyRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId); + vgpu10DestroyRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId, + vboxDXGetKMResource(pRenderTargetView->pResource)); pRenderTargetView->fDefined = false; } } @@ -3898,7 +3917,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pDepthStencilView->fDefined) { - vgpu10DestroyDepthStencilView(pDevice, pDepthStencilView->uDepthStencilViewId); + vgpu10DestroyDepthStencilView(pDevice, pDepthStencilView->uDepthStencilViewId, + vboxDXGetKMResource(pDepthStencilView->pResource)); pDepthStencilView->fDefined = false; } } @@ -3908,7 +3928,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pUnorderedAccessView->fDefined) { - vgpu10DestroyUAView(pDevice, pUnorderedAccessView->uUnorderedAccessViewId); + vgpu10DestroyUAView(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, + vboxDXGetKMResource(pUnorderedAccessView->pResource)); pUnorderedAccessView->fDefined = false; } } @@ -3918,7 +3939,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pVDOV->fDefined) { - vgpu10DestroyVideoDecoderOutputView(pDevice, pVDOV->uVideoDecoderOutputViewId); + vgpu10DestroyVideoDecoderOutputView(pDevice, pVDOV->uVideoDecoderOutputViewId, + vboxDXGetKMResource(pVDOV->pResource)); pVDOV->fDefined = false; } } @@ -3928,7 +3950,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pVPIV->fDefined) { - vgpu10DestroyVideoProcessorInputView(pDevice, pVPIV->uVideoProcessorInputViewId); + vgpu10DestroyVideoProcessorInputView(pDevice, pVPIV->uVideoProcessorInputViewId, + vboxDXGetKMResource(pVPIV->pResource)); pVPIV->fDefined = false; } } @@ -3939,7 +3962,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pVPOV->fDefined) { - vgpu10DestroyVideoProcessorOutputView(pDevice, pVPOV->uVideoProcessorOutputViewId); + vgpu10DestroyVideoProcessorOutputView(pDevice, pVPOV->uVideoProcessorOutputViewId, + vboxDXGetKMResource(pVPOV->pResource)); pVPOV->fDefined = false; } } @@ -4051,7 +4075,7 @@ static void vboxdxUnbindResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE p if (pSRVS->apShaderResourceView[i] == pShaderResourceView) { uint32_t id = SVGA3D_INVALID_ID; - vgpu10SetShaderResources(pDevice, enmShaderType, i, 1, &id); + vgpu10SetShaderResources(pDevice, enmShaderType, i, 1, &id, NULL); } } } @@ -4146,7 +4170,7 @@ HRESULT vboxDXRotateResourceIdentities(PVBOXDX_DEVICE pDevice, UINT cResources, if (fBound) { - vgpu10SetRenderTargets(pDevice, SVGA3D_INVALID_ID, 0, SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS, NULL); + vgpu10SetRenderTargets(pDevice, SVGA3D_INVALID_ID, NULL, 0, SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS, NULL, NULL); break; } } @@ -4385,44 +4409,68 @@ void vboxDXDestroyUnorderedAccessView(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDAC { RTListNodeRemove(&pUnorderedAccessView->nodeView); - vgpu10DestroyUAView(pDevice, pUnorderedAccessView->uUnorderedAccessViewId); + vgpu10DestroyUAView(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, + vboxDXGetKMResource(pUnorderedAccessView->pResource)); RTHandleTableFree(pDevice->hHTUnorderedAccessView, pUnorderedAccessView->uUnorderedAccessViewId); } void vboxDXClearUnorderedAccessViewUint(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView, const UINT Values[4]) { - vgpu10ClearUAViewUint(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, Values); + vgpu10ClearUAViewUint(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, + vboxDXGetKMResource(pUnorderedAccessView->pResource), Values); } void vboxDXClearUnorderedAccessViewFloat(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView, const FLOAT Values[4]) { - vgpu10ClearUAViewFloat(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, Values); + vgpu10ClearUAViewFloat(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, + vboxDXGetKMResource(pUnorderedAccessView->pResource), Values); } -void vboxDXCsSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const uint32_t *paViewIds, const UINT* pUAVInitialCounts) +void vboxDXCsSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const PVBOXDXUNORDEREDACCESSVIEW *papViews, const UINT* pUAVInitialCounts) { - for (unsigned i = 0; i < NumViews; ++i) + AssertReturnVoidStmt( NumViews <= SVGA3D_DX11_1_MAX_UAVIEWS + && StartSlot < SVGA3D_DX11_1_MAX_UAVIEWS + && NumViews + StartSlot <= SVGA3D_DX11_1_MAX_UAVIEWS, + vboxDXDeviceSetError(pDevice, E_INVALIDARG)); + + /* Fetch View ids and kernel mode resources. */ + uint32_t aViewIds[SVGA3D_DX11_1_MAX_UAVIEWS]; + PVBOXDXKMRESOURCE aViewKMResources[SVGA3D_DX11_1_MAX_UAVIEWS]; + for (UINT i = 0; i < NumViews; ++i) { - if (paViewIds[i] != SVGA3D_INVALID_ID) - vgpu10SetStructureCount(pDevice, paViewIds[i], pUAVInitialCounts[i]); + VBOXDXUNORDEREDACCESSVIEW *pView = papViews[i]; + aViewIds[i] = pView ? pView->uUnorderedAccessViewId : SVGA3D_INVALID_ID; + aViewKMResources[i] = pView ? vboxDXGetKMResource(pView->pResource) : NULL; } - vgpu10SetCSUAViews(pDevice, StartSlot, NumViews, paViewIds); + for (UINT i = 0; i < NumViews; ++i) + { + if (aViewIds[i] != SVGA3D_INVALID_ID) + vgpu10SetStructureCount(pDevice, aViewIds[i], aViewKMResources[i], pUAVInitialCounts[i]); + } + + vgpu10SetCSUAViews(pDevice, StartSlot, NumViews, aViewIds, aViewKMResources); } void vboxDXSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const PVBOXDXUNORDEREDACCESSVIEW *papViews, const UINT *pUAVInitialCounts) { - /* Fetch view ids.*/ + AssertReturnVoidStmt( NumViews <= D3D11_1_UAV_SLOT_COUNT + && StartSlot <= SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS, + vboxDXDeviceSetError(pDevice, E_INVALIDARG)); + + /* Fetch View ids and kernel mode resources. */ uint32_t aViewIds[D3D11_1_UAV_SLOT_COUNT]; - for (unsigned i = 0; i < NumViews; ++i) + PVBOXDXKMRESOURCE aViewKMResources[SVGA3D_DX11_1_MAX_UAVIEWS]; + for (UINT i = 0; i < NumViews; ++i) { - PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView = papViews[i]; - aViewIds[i] = pUnorderedAccessView ? pUnorderedAccessView->uUnorderedAccessViewId : SVGA3D_INVALID_ID; + PVBOXDXUNORDEREDACCESSVIEW pView = papViews[i]; + aViewIds[i] = pView ? pView->uUnorderedAccessViewId : SVGA3D_INVALID_ID; + aViewKMResources[i] = pView ? vboxDXGetKMResource(pView->pResource) : NULL; } UINT NumViewsToSet; @@ -4439,13 +4487,13 @@ void vboxDXSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT pDevice->pipeline.cUnorderedAccessViews = NumViews; - for (unsigned i = 0; i < NumViews; ++i) + for (UINT i = 0; i < NumViews; ++i) { if (aViewIds[i] != SVGA3D_INVALID_ID) - vgpu10SetStructureCount(pDevice, aViewIds[i], pUAVInitialCounts[i]); + vgpu10SetStructureCount(pDevice, aViewIds[i], aViewKMResources[i], pUAVInitialCounts[i]); } - vgpu10SetUAViews(pDevice, StartSlot, NumViewsToSet, aViewIds); + vgpu10SetUAViews(pDevice, StartSlot, NumViewsToSet, aViewIds, aViewKMResources); } @@ -4465,7 +4513,8 @@ void vboxDXDispatchIndirect(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pResource, void vboxDXCopyStructureCount(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pDstBuffer, UINT DstAlignedByteOffset, PVBOXDXUNORDEREDACCESSVIEW pSrcView) { - vgpu10CopyStructureCount(pDevice, pSrcView->uUnorderedAccessViewId, vboxDXGetKMResource(pDstBuffer), DstAlignedByteOffset); + vgpu10CopyStructureCount(pDevice, pSrcView->uUnorderedAccessViewId, vboxDXGetKMResource(pSrcView->pResource), + vboxDXGetKMResource(pDstBuffer), DstAlignedByteOffset); } @@ -4496,30 +4545,65 @@ HRESULT vboxDXBlt(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pDstResource, UINT Ds } -void vboxDXClearView(PVBOXDX_DEVICE pDevice, D3D11DDI_HANDLETYPE ViewType, uint32_t ViewId, FLOAT const Color[4], D3D10_DDI_RECT const *pRect, UINT NumRects) +void vboxDXClearView(PVBOXDX_DEVICE pDevice, D3D11DDI_HANDLETYPE ViewType, void *pView, FLOAT const Color[4], D3D10_DDI_RECT const *pRect, UINT NumRects) { - SVGAFifo3dCmdId enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_RTV; + SVGAFifo3dCmdId enmCmdId; + uint32_t ViewId; + PVBOXDXKMRESOURCE pViewKMResource; + + /* "Possible types are the following. + * D3D10DDI_HT_RENDERTARGETVIEW + * D3D11DDI_HT_UNORDEREDACCESSVIEW + * Any D3D11_1DDI_HT_VIDEOXXX type" + */ + switch (ViewType) { case D3D10DDI_HT_RENDERTARGETVIEW: + { + PVBOXDXRENDERTARGETVIEW pRenderTargetView = (PVBOXDXRENDERTARGETVIEW)pView; + enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_RTV; + ViewId = pRenderTargetView->uRenderTargetViewId; + pViewKMResource = vboxDXGetKMResource(pRenderTargetView->pResource); break; + } case D3D11DDI_HT_UNORDEREDACCESSVIEW: + { + PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView = (PVBOXDXUNORDEREDACCESSVIEW)pView; enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_UAV; + ViewId = pUnorderedAccessView->uUnorderedAccessViewId; + pViewKMResource = vboxDXGetKMResource(pUnorderedAccessView->pResource); break; + } case D3D11_1DDI_HT_VIDEODECODEROUTPUTVIEW: + { + PVBOXDXVIDEODECODEROUTPUTVIEW pVideoDecoderOutputView = (PVBOXDXVIDEODECODEROUTPUTVIEW)pView; enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_VDOV; + ViewId = pVideoDecoderOutputView->uVideoDecoderOutputViewId; + pViewKMResource = vboxDXGetKMResource(pVideoDecoderOutputView->pResource); break; + } case D3D11_1DDI_HT_VIDEOPROCESSORINPUTVIEW: + { + PVBOXDXVIDEOPROCESSORINPUTVIEW pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)pView; enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_VPIV; + ViewId = pVideoProcessorInputView->uVideoProcessorInputViewId; + pViewKMResource = vboxDXGetKMResource(pVideoProcessorInputView->pResource); break; + } case D3D11_1DDI_HT_VIDEOPROCESSOROUTPUTVIEW: + { + PVBOXDXVIDEOPROCESSOROUTPUTVIEW pVideoProcessorOutputView = (PVBOXDXVIDEOPROCESSOROUTPUTVIEW)pView; enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_VPOV; + ViewId = pVideoProcessorOutputView->uVideoProcessorOutputViewId; + pViewKMResource = vboxDXGetKMResource(pVideoProcessorOutputView->pResource); break; + } default: AssertFailedReturnVoid(); } - vgpu10ClearView(pDevice, enmCmdId, ViewId, Color, pRect, NumRects); + vgpu10ClearView(pDevice, enmCmdId, ViewId, pViewKMResource, Color, pRect, NumRects); } diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.h b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.h index dba7246b941e..d670d18adcb3 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.h +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.h @@ -1,4 +1,4 @@ -/* $Id: VBoxDX.h 114508 2026-06-24 14:34:30Z vitali.pelenjow@oracle.com $ */ +/* $Id: VBoxDX.h 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VBoxVideo Display D3D User mode dll */ @@ -885,13 +885,13 @@ void vboxDXDestroyUnorderedAccessView(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDAC void vboxDXClearUnorderedAccessViewUint(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView, const UINT Values[4]); void vboxDXClearUnorderedAccessViewFloat(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView, const FLOAT Values[4]); void vboxDXSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const PVBOXDXUNORDEREDACCESSVIEW *papViews, const UINT *pUAVInitialCounts); -void vboxDXCsSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const uint32_t *paViewIds, const UINT* pUAVInitialCounts); +void vboxDXCsSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const PVBOXDXUNORDEREDACCESSVIEW *papViews, const UINT *pUAVInitialCounts); void vboxDXDispatch(PVBOXDX_DEVICE pDevice, UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ); void vboxDXDispatchIndirect(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pResource, UINT AlignedByteOffsetForArgs); void vboxDXDrawIndexedInstancedIndirect(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pResource, UINT AlignedByteOffsetForArgs); void vboxDXDrawInstancedIndirect(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pResource, UINT AlignedByteOffsetForArgs); void vboxDXCopyStructureCount(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pDstBuffer, UINT DstAlignedByteOffset, PVBOXDXUNORDEREDACCESSVIEW pSrcView); -void vboxDXClearView(PVBOXDX_DEVICE pDevice, D3D11DDI_HANDLETYPE ViewType, uint32_t ViewId, FLOAT const Color[4], D3D10_DDI_RECT const *pRect, UINT NumRects); +void vboxDXClearView(PVBOXDX_DEVICE pDevice, D3D11DDI_HANDLETYPE ViewType, void *pView, FLOAT const Color[4], D3D10_DDI_RECT const *pRect, UINT NumRects); HRESULT vboxDXBlt(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pDstResource, UINT DstSubresource, PVBOXDX_RESOURCE pSrcResource, UINT SrcSubresource, UINT DstLeft, UINT DstTop, UINT DstRight, UINT DstBottom, diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.cpp b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.cpp index a787d9569470..b47174774992 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.cpp +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxDXCmd.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxDXCmd.cpp 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox D3D user mode driver utilities. */ @@ -859,15 +859,18 @@ int vgpu10DefineShaderResourceView(PVBOXDX_DEVICE pDevice, int vgpu10GenMips(PVBOXDX_DEVICE pDevice, - SVGA3dShaderResourceViewId shaderResourceViewId) + SVGA3dShaderResourceViewId shaderResourceViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_GENMIPS, - sizeof(SVGA3dCmdDXGenMips)); + sizeof(SVGA3dCmdDXGenMips), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXGenMips *cmd = (SVGA3dCmdDXGenMips *)pvCmd; SET_CMD_FIELD(shaderResourceViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -875,15 +878,18 @@ int vgpu10GenMips(PVBOXDX_DEVICE pDevice, int vgpu10DestroyShaderResourceView(PVBOXDX_DEVICE pDevice, - SVGA3dShaderResourceViewId shaderResourceViewId) + SVGA3dShaderResourceViewId shaderResourceViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_DESTROY_SHADERRESOURCE_VIEW, - sizeof(SVGA3dCmdDXDestroyShaderResourceView)); + sizeof(SVGA3dCmdDXDestroyShaderResourceView), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXDestroyShaderResourceView *cmd = (SVGA3dCmdDXDestroyShaderResourceView *)pvCmd; SET_CMD_FIELD(shaderResourceViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -918,10 +924,11 @@ int vgpu10DefineRenderTargetView(PVBOXDX_DEVICE pDevice, int vgpu10ClearRenderTargetView(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId renderTargetViewId, + PVBOXDXKMRESOURCE pViewKMResource, const float rgba[4]) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_CLEAR_RENDERTARGET_VIEW, - sizeof(SVGA3dCmdDXClearRenderTargetView)); + sizeof(SVGA3dCmdDXClearRenderTargetView), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -931,6 +938,8 @@ int vgpu10ClearRenderTargetView(PVBOXDX_DEVICE pDevice, cmd->rgba.value[1] = rgba[1]; cmd->rgba.value[2] = rgba[2]; cmd->rgba.value[3] = rgba[3]; + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -939,12 +948,13 @@ int vgpu10ClearRenderTargetView(PVBOXDX_DEVICE pDevice, int vgpu10ClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId viewId, + PVBOXDXKMRESOURCE pViewKMResource, const float color[4], const D3D10_DDI_RECT *paRects, uint32_t cRects) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_VB_DX_CLEAR_RENDERTARGET_VIEW_REGION, - sizeof(SVGA3dCmdVBDXClearRenderTargetViewRegion) + cRects * sizeof(SVGASignedRect)); + sizeof(SVGA3dCmdVBDXClearRenderTargetViewRegion) + cRects * sizeof(SVGASignedRect), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -966,6 +976,8 @@ int vgpu10ClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, d->right = s->right; d->bottom = s->bottom; } + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -975,12 +987,13 @@ int vgpu10ClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, int vgpu10ClearView(PVBOXDX_DEVICE pDevice, SVGAFifo3dCmdId cmdId, uint32_t viewId, + PVBOXDXKMRESOURCE pViewKMResource, const float color[4], const D3D10_DDI_RECT *paRects, uint32_t cRects) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, cmdId, - sizeof(VBSVGA3dCmdDXClearView) + cRects * sizeof(SVGASignedRect)); + sizeof(VBSVGA3dCmdDXClearView) + cRects * sizeof(SVGASignedRect), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -1001,6 +1014,8 @@ int vgpu10ClearView(PVBOXDX_DEVICE pDevice, d->right = s->right; d->bottom = s->bottom; } + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1008,15 +1023,18 @@ int vgpu10ClearView(PVBOXDX_DEVICE pDevice, int vgpu10DestroyRenderTargetView(PVBOXDX_DEVICE pDevice, - SVGA3dRenderTargetViewId renderTargetViewId) + SVGA3dRenderTargetViewId renderTargetViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_DESTROY_RENDERTARGET_VIEW, - sizeof(SVGA3dCmdDXDestroyRenderTargetView)); + sizeof(SVGA3dCmdDXDestroyRenderTargetView), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXDestroyRenderTargetView *cmd = (SVGA3dCmdDXDestroyRenderTargetView *)pvCmd; SET_CMD_FIELD(renderTargetViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1059,10 +1077,11 @@ int vgpu10ClearDepthStencilView(PVBOXDX_DEVICE pDevice, uint16 flags, uint16 stencil, SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource, float depth) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_CLEAR_DEPTHSTENCIL_VIEW, - sizeof(SVGA3dCmdDXClearDepthStencilView)); + sizeof(SVGA3dCmdDXClearDepthStencilView), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -1071,6 +1090,8 @@ int vgpu10ClearDepthStencilView(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(stencil); SET_CMD_FIELD(depthStencilViewId); SET_CMD_FIELD(depth); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1078,15 +1099,18 @@ int vgpu10ClearDepthStencilView(PVBOXDX_DEVICE pDevice, int vgpu10DestroyDepthStencilView(PVBOXDX_DEVICE pDevice, - SVGA3dDepthStencilViewId depthStencilViewId) + SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_DESTROY_DEPTHSTENCIL_VIEW, - sizeof(SVGA3dCmdDXDestroyDepthStencilView)); + sizeof(SVGA3dCmdDXDestroyDepthStencilView), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXDestroyDepthStencilView *cmd = (SVGA3dCmdDXDestroyDepthStencilView *)pvCmd; SET_CMD_FIELD(depthStencilViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1095,13 +1119,15 @@ int vgpu10DestroyDepthStencilView(PVBOXDX_DEVICE pDevice, int vgpu10SetRenderTargets(PVBOXDX_DEVICE pDevice, SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32_t numRTVs, uint32_t numClearSlots, - uint32_t *paRenderTargetViewIds) + uint32_t *paRenderTargetViewIds, + PVBOXDXKMRESOURCE *papViewKMResources) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_RENDERTARGETS, sizeof(SVGA3dCmdDXSetRenderTargets) - + (numRTVs + numClearSlots) * sizeof(SVGA3dRenderTargetViewId)); + + (numRTVs + numClearSlots) * sizeof(SVGA3dRenderTargetViewId), 1 + numRTVs); if (!pvCmd) return VERR_NO_MEMORY; @@ -1115,6 +1141,16 @@ int vgpu10SetRenderTargets(PVBOXDX_DEVICE pDevice, for (unsigned i = 0; i < numClearSlots; ++i) *dst++ = SVGA3D_INVALID_ID; + if (depthStencilViewId != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); + for (uint32_t i = 0; i < numRTVs; ++i) + { + if (paRenderTargetViewIds[i] != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, papViewKMResources[i], + 0, false); + } + vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; } @@ -1124,11 +1160,12 @@ int vgpu10SetShaderResources(PVBOXDX_DEVICE pDevice, SVGA3dShaderType type, uint32 startView, uint32_t numViews, - uint32_t *paViewIds) + uint32_t *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_SHADER_RESOURCES, sizeof(SVGA3dCmdDXSetShaderResources) - + numViews * sizeof(SVGA3dShaderResourceViewId)); + + numViews * sizeof(SVGA3dShaderResourceViewId), numViews); if (!pvCmd) return VERR_NO_MEMORY; @@ -1137,6 +1174,13 @@ int vgpu10SetShaderResources(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(type); memcpy(&cmd[1], paViewIds, numViews * sizeof(SVGA3dShaderResourceViewId)); + for (uint32_t i = 0; i < numViews; ++i) + { + if (paViewIds[i] != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, papViewKMResources[i], + 0, false); + } + vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; } @@ -1529,15 +1573,18 @@ int vgpu10DefineUAView(PVBOXDX_DEVICE pDevice, int vgpu10DestroyUAView(PVBOXDX_DEVICE pDevice, - SVGA3dUAViewId uaViewId) + SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_DESTROY_UA_VIEW, - sizeof(SVGA3dCmdDXDestroyUAView), 0); + sizeof(SVGA3dCmdDXDestroyUAView), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXDestroyUAView *cmd = (SVGA3dCmdDXDestroyUAView *)pvCmd; SET_CMD_FIELD(uaViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1546,10 +1593,11 @@ int vgpu10DestroyUAView(PVBOXDX_DEVICE pDevice, int vgpu10ClearUAViewUint(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, const uint32 value[4]) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_CLEAR_UA_VIEW_UINT, - sizeof(SVGA3dCmdDXClearUAViewUint), 0); + sizeof(SVGA3dCmdDXClearUAViewUint), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -1559,6 +1607,8 @@ int vgpu10ClearUAViewUint(PVBOXDX_DEVICE pDevice, cmd->value.value[1] = value[1]; cmd->value.value[2] = value[2]; cmd->value.value[3] = value[3]; + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1567,10 +1617,11 @@ int vgpu10ClearUAViewUint(PVBOXDX_DEVICE pDevice, int vgpu10ClearUAViewFloat(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, const float value[4]) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_CLEAR_UA_VIEW_FLOAT, - sizeof(SVGA3dCmdDXClearUAViewFloat), 0); + sizeof(SVGA3dCmdDXClearUAViewFloat), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -1580,6 +1631,8 @@ int vgpu10ClearUAViewFloat(PVBOXDX_DEVICE pDevice, cmd->value.value[1] = value[1]; cmd->value.value[2] = value[2]; cmd->value.value[3] = value[3]; + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1589,11 +1642,12 @@ int vgpu10ClearUAViewFloat(PVBOXDX_DEVICE pDevice, int vgpu10SetCSUAViews(PVBOXDX_DEVICE pDevice, uint32 startIndex, uint32 numViews, - const SVGA3dUAViewId *paViewIds) + const SVGA3dUAViewId *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_CS_UA_VIEWS, sizeof(SVGA3dCmdDXSetCSUAViews) - + numViews * sizeof(SVGA3dUAViewId)); + + numViews * sizeof(SVGA3dUAViewId), numViews); if (!pvCmd) return VERR_NO_MEMORY; @@ -1601,6 +1655,13 @@ int vgpu10SetCSUAViews(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(startIndex); memcpy(&cmd[1], paViewIds, numViews * sizeof(SVGA3dUAViewId)); + for (uint32 i = 0; i < numViews; ++ i) + { + if (paViewIds[i] != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, papViewKMResources[i], + 0, false); + } + vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; } @@ -1609,11 +1670,12 @@ int vgpu10SetCSUAViews(PVBOXDX_DEVICE pDevice, int vgpu10SetUAViews(PVBOXDX_DEVICE pDevice, uint32 uavSpliceIndex, uint32 numViews, - const SVGA3dUAViewId *paViewIds) + const SVGA3dUAViewId *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_UA_VIEWS, sizeof(SVGA3dCmdDXSetUAViews) - + numViews * sizeof(SVGA3dUAViewId)); + + numViews * sizeof(SVGA3dUAViewId), numViews); if (!pvCmd) return VERR_NO_MEMORY; @@ -1621,6 +1683,13 @@ int vgpu10SetUAViews(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(uavSpliceIndex); memcpy(&cmd[1], paViewIds, numViews * sizeof(SVGA3dUAViewId)); + for (uint32 i = 0; i < numViews; ++ i) + { + if (paViewIds[i] != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, papViewKMResources[i], + 0, false); + } + vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; } @@ -1628,16 +1697,19 @@ int vgpu10SetUAViews(PVBOXDX_DEVICE pDevice, int vgpu10SetStructureCount(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32 structureCount) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_STRUCTURE_COUNT, - sizeof(SVGA3dCmdDXSetStructureCount), 0); + sizeof(SVGA3dCmdDXSetStructureCount), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXSetStructureCount *cmd = (SVGA3dCmdDXSetStructureCount *)pvCmd; SET_CMD_FIELD(uaViewId); SET_CMD_FIELD(structureCount); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1729,11 +1801,12 @@ int vgpu10DrawInstancedIndirect(PVBOXDX_DEVICE pDevice, int vgpu10CopyStructureCount(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId srcUAViewId, + PVBOXDXKMRESOURCE pViewKMResource, PVBOXDXKMRESOURCE pDstKMResource, uint32 destByteOffset) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_COPY_STRUCTURE_COUNT, - sizeof(SVGA3dCmdDXCopyStructureCount), 1); + sizeof(SVGA3dCmdDXCopyStructureCount), 2); if (!pvCmd) return VERR_NO_MEMORY; @@ -1742,6 +1815,8 @@ int vgpu10CopyStructureCount(PVBOXDX_DEVICE pDevice, cmd->destSid = SVGA3D_INVALID_ID; SET_CMD_FIELD(destByteOffset); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXStorePatchLocation(pDevice, &cmd->destSid, pDstKMResource, 0, true); @@ -1845,16 +1920,19 @@ int vgpu10DefineVideoDecoder(PVBOXDX_DEVICE pDevice, int vgpu10VideoDecoderBeginFrame(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderId videoDecoderId, - VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId) + VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_VIDEO_DECODER_BEGIN_FRAME, - sizeof(VBSVGA3dCmdDXVideoDecoderBeginFrame), 0); + sizeof(VBSVGA3dCmdDXVideoDecoderBeginFrame), 1); if (!pvCmd) return VERR_NO_MEMORY; VBSVGA3dCmdDXVideoDecoderBeginFrame *cmd = (VBSVGA3dCmdDXVideoDecoderBeginFrame *)pvCmd; SET_CMD_FIELD(videoDecoderId); SET_CMD_FIELD(videoDecoderOutputViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1957,13 +2035,16 @@ int vgpu10DefineVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, int vgpu10VideoProcessorBlt(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoProcessorId videoProcessorId, VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32 outputFrame, uint32 streamCount, uint32 cbVideoProcessorStreams, - VBSVGA3dVideoProcessorStream *pVideoProcessorStreams) + VBSVGA3dVideoProcessorStream *pVideoProcessorStreams, + uint32_t cVPIViewKMResource, + PVBOXDXKMRESOURCE *papVPIViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_VIDEO_PROCESSOR_BLT, - sizeof(VBSVGA3dCmdDXVideoProcessorBlt) + cbVideoProcessorStreams, 0); + sizeof(VBSVGA3dCmdDXVideoProcessorBlt) + cbVideoProcessorStreams, 1 + cVPIViewKMResource); if (!pvCmd) return VERR_NO_MEMORY; @@ -1973,6 +2054,13 @@ int vgpu10VideoProcessorBlt(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(outputFrame); SET_CMD_FIELD(streamCount); memcpy(&cmd[1], pVideoProcessorStreams, cbVideoProcessorStreams); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); + for (uint32_t i = 0; i < cVPIViewKMResource; ++i) + { + vboxDXStorePatchLocation(pDevice, NULL, papVPIViewKMResource[i], + 0, false); + } vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1996,15 +2084,18 @@ int vgpu10DestroyVideoDecoder(PVBOXDX_DEVICE pDevice, int vgpu10DestroyVideoDecoderOutputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId) + VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_DESTROY_VIDEO_DECODER_OUTPUT_VIEW, - sizeof(VBSVGA3dCmdDXDestroyVideoDecoderOutputView), 0); + sizeof(VBSVGA3dCmdDXDestroyVideoDecoderOutputView), 1); if (!pvCmd) return VERR_NO_MEMORY; VBSVGA3dCmdDXDestroyVideoDecoderOutputView *cmd = (VBSVGA3dCmdDXDestroyVideoDecoderOutputView *)pvCmd; SET_CMD_FIELD(videoDecoderOutputViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -2028,15 +2119,18 @@ int vgpu10DestroyVideoProcessor(PVBOXDX_DEVICE pDevice, int vgpu10DestroyVideoProcessorInputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId) + VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_DESTROY_VIDEO_PROCESSOR_INPUT_VIEW, - sizeof(VBSVGA3dCmdDXDestroyVideoProcessorInputView), 0); + sizeof(VBSVGA3dCmdDXDestroyVideoProcessorInputView), 1); if (!pvCmd) return VERR_NO_MEMORY; VBSVGA3dCmdDXDestroyVideoProcessorInputView *cmd = (VBSVGA3dCmdDXDestroyVideoProcessorInputView *)pvCmd; SET_CMD_FIELD(videoProcessorInputViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -2044,15 +2138,18 @@ int vgpu10DestroyVideoProcessorInputView(PVBOXDX_DEVICE pDevice, int vgpu10DestroyVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId) + VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_DESTROY_VIDEO_PROCESSOR_OUTPUT_VIEW, - sizeof(VBSVGA3dCmdDXDestroyVideoProcessorOutputView), 0); + sizeof(VBSVGA3dCmdDXDestroyVideoProcessorOutputView), 1); if (!pvCmd) return VERR_NO_MEMORY; VBSVGA3dCmdDXDestroyVideoProcessorOutputView *cmd = (VBSVGA3dCmdDXDestroyVideoProcessorOutputView *)pvCmd; SET_CMD_FIELD(videoProcessorOutputViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.h b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.h index 08ec4006e8b6..da218f6ba5ac 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.h +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.h @@ -1,4 +1,4 @@ -/* $Id: VBoxDXCmd.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxDXCmd.h 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VBoxVideo Display D3D User mode dll */ @@ -206,9 +206,11 @@ int vgpu10DefineShaderResourceView(PVBOXDX_DEVICE pDevice, SVGA3dResourceType resourceDimension, SVGA3dShaderResourceViewDesc const *pDesc); int vgpu10GenMips(PVBOXDX_DEVICE pDevice, - SVGA3dShaderResourceViewId shaderResourceViewId); + SVGA3dShaderResourceViewId shaderResourceViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DestroyShaderResourceView(PVBOXDX_DEVICE pDevice, - SVGA3dShaderResourceViewId shaderResourceViewId); + SVGA3dShaderResourceViewId shaderResourceViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DefineRenderTargetView(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId renderTargetViewId, PVBOXDXKMRESOURCE pKMResource, @@ -217,9 +219,11 @@ int vgpu10DefineRenderTargetView(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewDesc const *pDesc); int vgpu10ClearRenderTargetView(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId renderTargetViewId, + PVBOXDXKMRESOURCE pViewKMResource, const float rgba[4]); int vgpu10DestroyRenderTargetView(PVBOXDX_DEVICE pDevice, - SVGA3dRenderTargetViewId renderTargetViewId); + SVGA3dRenderTargetViewId renderTargetViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DefineDepthStencilView(PVBOXDX_DEVICE pDevice, SVGA3dDepthStencilViewId depthStencilViewId, PVBOXDXKMRESOURCE pKMResource, @@ -233,19 +237,24 @@ int vgpu10ClearDepthStencilView(PVBOXDX_DEVICE pDevice, uint16 flags, uint16 stencil, SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource, float depth); int vgpu10DestroyDepthStencilView(PVBOXDX_DEVICE pDevice, - SVGA3dDepthStencilViewId depthStencilViewId); + SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10SetRenderTargets(PVBOXDX_DEVICE pDevice, SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32_t numRTVs, uint32_t numClearSlots, - uint32_t *paRenderTargetViewIds); + uint32_t *paRenderTargetViewIds, + PVBOXDXKMRESOURCE *papViewKMResources); int vgpu10SetShaderResources(PVBOXDX_DEVICE pDevice, SVGA3dShaderType type, uint32 startView, uint32_t numViews, - uint32_t *paViewIds); + uint32_t *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources); int vgpu10SetSingleConstantBuffer(PVBOXDX_DEVICE pDevice, uint32 slot, SVGA3dShaderType type, @@ -317,23 +326,29 @@ int vgpu10DefineUAView(PVBOXDX_DEVICE pDevice, SVGA3dResourceType resourceDimension, const SVGA3dUAViewDesc &desc); int vgpu10DestroyUAView(PVBOXDX_DEVICE pDevice, - SVGA3dUAViewId uaViewId); + SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10ClearUAViewUint(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, const uint32 value[4]); int vgpu10ClearUAViewFloat(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, const float value[4]); int vgpu10SetCSUAViews(PVBOXDX_DEVICE pDevice, uint32 startIndex, uint32 numViews, - const SVGA3dUAViewId *paViewIds); + const SVGA3dUAViewId *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources); int vgpu10SetUAViews(PVBOXDX_DEVICE pDevice, uint32 uavSpliceIndex, uint32 numViews, - const SVGA3dUAViewId *paViewIds); + const SVGA3dUAViewId *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources); int vgpu10SetStructureCount(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32 structureCount); int vgpu10Dispatch(PVBOXDX_DEVICE pDevice, uint32 threadGroupCountX, @@ -350,10 +365,12 @@ int vgpu10DrawInstancedIndirect(PVBOXDX_DEVICE pDevice, uint32 byteOffsetForArgs); int vgpu10CopyStructureCount(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId srcUAViewId, + PVBOXDXKMRESOURCE pViewKMResource, PVBOXDXKMRESOURCE pDstKMResource, uint32 destByteOffset); int vgpu10ClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId viewId, + PVBOXDXKMRESOURCE pViewKMResource, const float color[4], const D3D10_DDI_RECT *paRects, uint32_t cRects); @@ -379,7 +396,8 @@ int vgpu10DefineVideoDecoder(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderConfig const &config); int vgpu10VideoDecoderBeginFrame(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderId videoDecoderId, - VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId); + VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10VideoDecoderSubmitBuffers(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderId videoDecoderId, uint32 bufferCount, @@ -400,20 +418,26 @@ int vgpu10DefineVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, int vgpu10VideoProcessorBlt(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoProcessorId videoProcessorId, VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, - uint32 OutputFrame, - uint32 StreamCount, + PVBOXDXKMRESOURCE pViewKMResource, + uint32 outputFrame, + uint32 streamCount, uint32 cbVideoProcessorStreams, - VBSVGA3dVideoProcessorStream *paVideoProcessorStreams); + VBSVGA3dVideoProcessorStream *pVideoProcessorStreams, + uint32_t cVPIViewKMResource, + PVBOXDXKMRESOURCE *papVPIViewKMResource); int vgpu10DestroyVideoDecoder(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderId videoDecoderId); int vgpu10DestroyVideoDecoderOutputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId); + VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DestroyVideoProcessor(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoProcessorId videoProcessorId); int vgpu10DestroyVideoProcessorInputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId); + VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DestroyVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId); + VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10VideoProcessorSetOutputTargetRect(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoProcessorId videoProcessorId, BOOL enable, @@ -515,6 +539,7 @@ int vgpu10GetVideoCapability(PVBOXDX_DEVICE pDevice, int vgpu10ClearView(PVBOXDX_DEVICE pDevice, SVGAFifo3dCmdId cmdId, uint32_t viewId, + PVBOXDXKMRESOURCE pViewKMResource, const float color[4], const D3D10_DDI_RECT *paRects, uint32_t cRects); diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXDDI.cpp b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXDDI.cpp index 5666d9008170..9107c678771e 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXDDI.cpp +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXDDI.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxDXDDI.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxDXDDI.cpp 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox D3D11 user mode DDI interface. */ @@ -895,10 +895,6 @@ static void APIENTRY ddi11SetRenderTargets( vboxDXSetRenderTargets(pDevice, pDepthStencilView, NumRTVs, ClearSlots, (PVBOXDXRENDERTARGETVIEW *)phRenderTargetView); - AssertReturnVoidStmt( NumUAVs <= D3D11_1_UAV_SLOT_COUNT - && UAVStartSlot <= SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS, - vboxDXDeviceSetError(pDevice, E_INVALIDARG)); - vboxDXSetUnorderedAccessViews(pDevice, UAVStartSlot, NumUAVs, (PVBOXDXUNORDEREDACCESSVIEW *)phUnorderedAccessView, pUAVInitialCounts); RT_NOREF(UAVRangeStart, UAVRangeSize); /* These are hints and not used by the driver. */ @@ -3870,20 +3866,8 @@ static void APIENTRY ddi11CsSetUnorderedAccessViews( PVBOXDX_DEVICE pDevice = (PVBOXDX_DEVICE)hDevice.pDrvPrivate; LogFlowFunc(("pDevice = %p, StartSlot = %u, NumViews = %u\n", pDevice, StartSlot, NumViews)); - AssertReturnVoidStmt( NumViews <= SVGA3D_DX11_1_MAX_UAVIEWS - && StartSlot < SVGA3D_DX11_1_MAX_UAVIEWS - && NumViews + StartSlot <= SVGA3D_DX11_1_MAX_UAVIEWS, - vboxDXDeviceSetError(pDevice, E_INVALIDARG)); - - /* Fetch View ids. */ - uint32_t aViewIds[SVGA3D_DX11_1_MAX_UAVIEWS]; - for (unsigned i = 0; i < NumViews; ++i) - { - VBOXDXUNORDEREDACCESSVIEW *pView = (PVBOXDXUNORDEREDACCESSVIEW)phUnorderedAccessView[i].pDrvPrivate; - aViewIds[i] = pView ? pView->uUnorderedAccessViewId : SVGA3D_INVALID_ID; - } - - vboxDXCsSetUnorderedAccessViews(pDevice, StartSlot, NumViews, aViewIds, pUAVInitialCounts); + vboxDXCsSetUnorderedAccessViews(pDevice, StartSlot, NumViews, + (PVBOXDXUNORDEREDACCESSVIEW *)phUnorderedAccessView, pUAVInitialCounts); } static void APIENTRY ddi11Dispatch( @@ -4031,53 +4015,7 @@ static void APIENTRY ddi11_1ClearView( if (pDevice->pAdapter->fVBoxCaps & VBSVGA3D_CAP_VIDEO) { - uint32_t ViewId = SVGA3D_INVALID_ID; - - /* "Possible types are the following. - * D3D10DDI_HT_RENDERTARGETVIEW - * D3D11DDI_HT_UNORDEREDACCESSVIEW - * Any D3D11_1DDI_HT_VIDEOXXX type" - */ - switch (ViewType) - { - case D3D10DDI_HT_RENDERTARGETVIEW: - { - PVBOXDXRENDERTARGETVIEW pRenderTargetView = (PVBOXDXRENDERTARGETVIEW)hView; - ViewId = pRenderTargetView->uRenderTargetViewId; - break; - } - case D3D11DDI_HT_UNORDEREDACCESSVIEW: - { - PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView = (PVBOXDXUNORDEREDACCESSVIEW)hView; - ViewId = pUnorderedAccessView->uUnorderedAccessViewId; - break; - } - case D3D11_1DDI_HT_VIDEODECODEROUTPUTVIEW: - { - PVBOXDXVIDEODECODEROUTPUTVIEW pVideoDecoderOutputView = (PVBOXDXVIDEODECODEROUTPUTVIEW)hView; - ViewId = pVideoDecoderOutputView->uVideoDecoderOutputViewId; - break; - } - case D3D11_1DDI_HT_VIDEOPROCESSORINPUTVIEW: - { - PVBOXDXVIDEOPROCESSORINPUTVIEW pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)hView; - ViewId = pVideoProcessorInputView->uVideoProcessorInputViewId; - break; - } - case D3D11_1DDI_HT_VIDEOPROCESSOROUTPUTVIEW: - { - PVBOXDXVIDEOPROCESSOROUTPUTVIEW pVideoProcessorOutputView = (PVBOXDXVIDEOPROCESSOROUTPUTVIEW)hView; - ViewId = pVideoProcessorOutputView->uVideoProcessorOutputViewId; - break; - } - default: - { - DEBUG_BREAKPOINT_TEST(); - break; - } - } - if (ViewId != SVGA3D_INVALID_ID) - vboxDXClearView(pDevice, ViewType, ViewId, Color, pRect, NumRects); + vboxDXClearView(pDevice, ViewType, hView, Color, pRect, NumRects); return; } diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXVideo.cpp b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXVideo.cpp index 9d057f022e37..70408e9d938f 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXVideo.cpp +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXVideo.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxDXVideo.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxDXVideo.cpp 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox D3D user mode driver. */ @@ -513,7 +513,8 @@ HRESULT vboxDXVideoDecoderBeginFrame(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEODECODER RT_NOREF(pContentKey, ContentKeySize); /** @todo vgpu10VideoDecoderBeginFrame2 */ vgpu10VideoDecoderBeginFrame(pDevice, pVideoDecoder->uVideoDecoderId, - pVideoDecoderOutputView->uVideoDecoderOutputViewId); + pVideoDecoderOutputView->uVideoDecoderOutputViewId, + vboxDXGetKMResource(pVideoDecoderOutputView->pResource)); return S_OK; } @@ -648,25 +649,31 @@ HRESULT vboxDXCreateVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, PVBOXDXVIDE HRESULT vboxDXVideoProcessorBlt(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOPROCESSOR pVideoProcessor, PVBOXDXVIDEOPROCESSOROUTPUTVIEW pVideoProcessorOutputView, UINT OutputFrame, UINT StreamCount, D3D11_1DDI_VIDEO_PROCESSOR_STREAM const *paStream) { + uint32_t cVPIVIds = 0; uint32_t cbVideoProcessorStreams = StreamCount * sizeof(VBSVGA3dVideoProcessorStream); for (UINT i = 0; i < StreamCount; ++i) { D3D11_1DDI_VIDEO_PROCESSOR_STREAM const *s = &paStream[i]; - uint32_t cbIds = (s->PastFrames + 1 + s->FutureFrames) * sizeof(VBSVGA3dVideoProcessorInputViewId); + uint32_t cIds = s->PastFrames + 1 + s->FutureFrames; if (pVideoProcessor->aStreams[i].FrameFormat == D3D11_1DDI_VIDEO_PROCESSOR_STEREO_FORMAT_SEPARATE) - cbIds *= 2; - cbVideoProcessorStreams += cbIds; + cIds *= 2; + cVPIVIds += cIds; + cbVideoProcessorStreams += cIds * sizeof(VBSVGA3dVideoProcessorInputViewId); } - void *pvTmpBuffer = RTMemTmpAlloc(cbVideoProcessorStreams); + size_t const cbAlloc = cbVideoProcessorStreams + cVPIVIds * sizeof(PVBOXDXKMRESOURCE); + void *pvTmpBuffer = RTMemTmpAlloc(cbAlloc); if (!pvTmpBuffer) return E_OUTOFMEMORY; + PVBOXDXKMRESOURCE *papVPIViewKMResource = (PVBOXDXKMRESOURCE *)((uint8_t *)pvTmpBuffer + cbVideoProcessorStreams); + uint32_t idxVPIViewKMResource = 0; + VBSVGA3dVideoProcessorStream *paVideoProcessorStreams = (VBSVGA3dVideoProcessorStream *)pvTmpBuffer; + VBSVGA3dVideoProcessorStream *d = &paVideoProcessorStreams[0]; for (UINT i = 0; i < StreamCount; ++i) { D3D11_1DDI_VIDEO_PROCESSOR_STREAM const *s = &paStream[i]; - VBSVGA3dVideoProcessorStream *d = &paVideoProcessorStreams[i]; d->Enable = s->Enable; d->StereoFormatSeparate = pVideoProcessor->aStreams[i].StereoFormat.Format == D3D11_1DDI_VIDEO_PROCESSOR_STEREO_FORMAT_SEPARATE; @@ -683,15 +690,18 @@ HRESULT vboxDXVideoProcessorBlt(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOPROCESSOR pV { pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->pPastSurfaces[j].pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); } pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->hInputSurface.pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); for (UINT j = 0; j < s->FutureFrames; ++j) { pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->pFutureSurfaces[j].pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); } if (d->StereoFormatSeparate) @@ -700,21 +710,31 @@ HRESULT vboxDXVideoProcessorBlt(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOPROCESSOR pV { pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->pPastSurfacesRight[j].pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); } pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->hInputSurfaceRight.pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); for (UINT j = 0; j < s->FutureFrames; ++j) { pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->pFutureSurfacesRight[j].pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); } } + + d = (VBSVGA3dVideoProcessorStream *)pVPIVId; } - vgpu10VideoProcessorBlt(pDevice, pVideoProcessor->uVideoProcessorId, pVideoProcessorOutputView->uVideoProcessorOutputViewId, - OutputFrame, StreamCount, cbVideoProcessorStreams, paVideoProcessorStreams); + Assert(idxVPIViewKMResource == cVPIVIds); + + vgpu10VideoProcessorBlt(pDevice, pVideoProcessor->uVideoProcessorId, + pVideoProcessorOutputView->uVideoProcessorOutputViewId, + vboxDXGetKMResource(pVideoProcessorOutputView->pResource), + OutputFrame, StreamCount, cbVideoProcessorStreams, paVideoProcessorStreams, + idxVPIViewKMResource, papVPIViewKMResource); RTMemTmpFree(pvTmpBuffer); return S_OK; } @@ -731,7 +751,8 @@ void vboxDXDestroyVideoDecoderOutputView(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEODEC { RTListNodeRemove(&pVideoDecoderOutputView->nodeView); - vgpu10DestroyVideoDecoderOutputView(pDevice, pVideoDecoderOutputView->uVideoDecoderOutputViewId); + vgpu10DestroyVideoDecoderOutputView(pDevice, pVideoDecoderOutputView->uVideoDecoderOutputViewId, + vboxDXGetKMResource(pVideoDecoderOutputView->pResource)); RTHandleTableFree(pDevice->hHTVideoDecoderOutputView, pVideoDecoderOutputView->uVideoDecoderOutputViewId); } @@ -747,7 +768,8 @@ void vboxDXDestroyVideoProcessorInputView(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOPR { RTListNodeRemove(&pVideoProcessorInputView->nodeView); - vgpu10DestroyVideoProcessorInputView(pDevice, pVideoProcessorInputView->uVideoProcessorInputViewId); + vgpu10DestroyVideoProcessorInputView(pDevice, pVideoProcessorInputView->uVideoProcessorInputViewId, + vboxDXGetKMResource(pVideoProcessorInputView->pResource)); RTHandleTableFree(pDevice->hHTVideoProcessorInputView, pVideoProcessorInputView->uVideoProcessorInputViewId); } @@ -756,7 +778,8 @@ void vboxDXDestroyVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOP { RTListNodeRemove(&pVideoProcessorOutputView->nodeView); - vgpu10DestroyVideoProcessorOutputView(pDevice, pVideoProcessorOutputView->uVideoProcessorOutputViewId); + vgpu10DestroyVideoProcessorOutputView(pDevice, pVideoProcessorOutputView->uVideoProcessorOutputViewId, + vboxDXGetKMResource(pVideoProcessorOutputView->pResource)); RTHandleTableFree(pDevice->hHTVideoProcessorOutputView, pVideoProcessorOutputView->uVideoProcessorOutputViewId); } From 6e047b2b51672b368c8c04d0a999ce8bcf5c8bd0 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Wed, 5 Aug 2026 09:09:50 +0000 Subject: [PATCH 007/176] Devices/DevVirtioSCSI: Do not wake disabled queues during saved-state restore. Fixes annoying assertions in debug builds. svn:sync-xref-src-repo-rev: r174693 --- src/VBox/Devices/Storage/DevVirtioSCSI.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VBox/Devices/Storage/DevVirtioSCSI.cpp b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp index fed37ca74c58..8411c76f511b 100644 --- a/src/VBox/Devices/Storage/DevVirtioSCSI.cpp +++ b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVirtioSCSI.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DevVirtioSCSI.cpp 114856 2026-08-05 09:09:50Z andreas.loeffler@oracle.com $ */ /** @file * VBox storage devices - Virtio SCSI Driver * @@ -2106,7 +2106,8 @@ static DECLCALLBACK(int) virtioScsiR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSS */ for (int uVirtqNbr = VIRTQ_REQ_BASE; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++) { - if (pThis->afVirtqAttached[uVirtqNbr]) + if ( pThis->afVirtqAttached[uVirtqNbr] + && virtioCoreIsVirtqEnabled(&pThis->Virtio, uVirtqNbr)) { LogFunc(("Waking %s worker.\n", VIRTQNAME(uVirtqNbr))); int rc2 = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[uVirtqNbr].hEvtProcess); From d47dcabb2e5e85b9e4fe54d06c9defe81512af1e Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Wed, 5 Aug 2026 15:08:05 +0000 Subject: [PATCH 008/176] =?UTF-8?q?Shared=20Clipboard/Main:=20More=20plumb?= =?UTF-8?q?ing=20for=20making=20Shared=20Clipboard=20transfers=20available?= =?UTF-8?q?=20via=20public=20API.=20=E2=80=8B=E2=80=8B=E2=80=8Bbugref:4697?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174695 --- doc/iuml/clipboard_api.iuml | 9 +- doc/iuml/clipboard_transfer_sequence.iuml | 19 +- doc/manual/en_US/man_VBoxManage-clipboard.xml | 58 +- doc/manual/ru_RU/man_VBoxManage-clipboard.xml | 58 +- doc/manual/user_ChangeLogImpl.xml | 6 + .../GuestHost/SharedClipboard-transfers.h | 58 +- include/VBox/GuestHost/SharedClipboard.h | 31 + .../Additions/darwin/VBoxClient/Makefile.kmk | 8 +- .../VBoxManage/VBoxManageClipboard.cpp | 725 +++++++----- .../SharedClipboard/clipboard-common.cpp | 60 +- .../SharedClipboard/clipboard-helper.cpp | 33 +- .../SharedClipboard/clipboard-transfers.cpp | 155 ++- .../testcase/tstClipboardMimeConv.cpp | 75 +- .../VBoxSharedClipboardSvc-client.cpp | 18 +- .../VBoxSharedClipboardSvc-transfers.cpp | 8 +- .../testcase/tstClipboardTransfers.cpp | 128 +- src/VBox/Main/idl/VirtualBox.xidl | 243 +++- src/VBox/Main/include/ClipboardImpl.h | 35 +- .../Main/include/ClipboardTransferDataImpl.h | 6 +- .../include/ClipboardTransferDirectoryImpl.h | 18 +- .../Main/include/ClipboardTransferFileImpl.h | 4 +- src/VBox/Main/include/ClipboardTransferImpl.h | 47 +- .../include/ClipboardTransferManagerImpl.h | 109 +- src/VBox/Main/src-client/ClipboardImpl.cpp | 204 +++- .../src-client/ClipboardTransferDataImpl.cpp | 157 ++- .../ClipboardTransferDirectoryImpl.cpp | 168 ++- .../src-client/ClipboardTransferFileImpl.cpp | 112 +- .../ClipboardTransferFsObjInfoImpl.cpp | 23 +- .../Main/src-client/ClipboardTransferImpl.cpp | 291 +++-- .../ClipboardTransferManagerImpl.cpp | 850 ++++++------- src/VBox/Main/src-client/GuestShClPrivate.cpp | 57 +- src/VBox/Main/src-client/GuestShClSvcExt.cpp | 131 +-- .../VBoxSharedClipboardSvc-utils.cpp | 7 +- .../darwin/ClipboardBackendDarwin.cpp | 93 +- .../src-client/darwin/darwin-pasteboard.cpp | 5 +- src/VBox/Main/testcase/tstClipboard.cpp | 1048 +++++++++++++++-- 36 files changed, 3646 insertions(+), 1411 deletions(-) diff --git a/doc/iuml/clipboard_api.iuml b/doc/iuml/clipboard_api.iuml index d0f49ac3c20d..2f59cb463c63 100644 --- a/doc/iuml/clipboard_api.iuml +++ b/doc/iuml/clipboard_api.iuml @@ -1,4 +1,4 @@ -' $Id: clipboard_api.iuml 114611 2026-07-03 15:34:02Z andreas.loeffler@oracle.com $ +' $Id: clipboard_api.iuml 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ '' @file ' UML definition for PlantUML - VirtualBox Clipboard API - Draft ' @@ -289,8 +289,7 @@ package "Clipboard control" { abstract class IClipboardTransferManager { +getTransfers(direction, flags) : SafeArray - +createTransfer(direction, source, action) : IClipboardTransfer - +add(transfer) : HRESULT + +create(direction, source, action) : IClipboardTransfer +remove(transfer) : HRESULT +cancel(transfer) : HRESULT +approve(transfer, flags) : HRESULT @@ -300,6 +299,10 @@ package "Clipboard control" { +resume(transfer) : HRESULT +reset() : HRESULT } + note right of IClipboardTransferManager + Clients obtain manager-owned transfers through + create() or getTransfers(); controls reject foreign objects. + end note abstract class IClipboardSettings { diff --git a/doc/iuml/clipboard_transfer_sequence.iuml b/doc/iuml/clipboard_transfer_sequence.iuml index 3ac70512c15d..19cc4f08b6b8 100644 --- a/doc/iuml/clipboard_transfer_sequence.iuml +++ b/doc/iuml/clipboard_transfer_sequence.iuml @@ -1,4 +1,4 @@ -' $Id: clipboard_transfer_sequence.iuml 114611 2026-07-03 15:34:02Z andreas.loeffler@oracle.com $ +' $Id: clipboard_transfer_sequence.iuml 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ '' @file ' UML definition for PlantUML - VirtualBox Clipboard Transfer Sequence - Draft ' @@ -53,6 +53,8 @@ end box legend right IClipboardTransfer is the high-level filesystem-like transfer tree API. + Clients obtain manager-owned transfers through create() or getTransfers(). + Manager controls reject foreign transfer objects. sourcePaths configure local producer-side filesystem objects. roots() returns receiver-visible transfer-relative nodes. Roots, query, list, openDirectory and openFile are preferred for normal clients. @@ -62,10 +64,14 @@ endlegend == Configure a local host-to-guest transfer == -Client -> Manager: createTransfer(ToGuest, Host, Copy) -Manager -> VBoxC: create unpublished transfer wrapper +Client -> Manager: create(ToGuest, Host, Copy) +Manager -> VBoxC: create and track manager-owned transfer wrapper VBoxC --> Manager: transfer Manager --> Client: transfer +note over Manager, VBoxC + Main ownership and tracking do not publish + the transfer to the service or platform backend. +end note Client -> Transfer: setSourcePaths(["/home/user/report.txt", "/home/user/photos"]) note right of Client @@ -82,15 +88,10 @@ Core --> VBoxC: initialized transfer tree VBoxC --> Transfer: success Transfer --> Client: success -Client -> Manager: add(transfer) -Manager -> VBoxC: publish configured transfer -VBoxC -> Core: make transfer available -Core -> Provider: advertise roots report.txt and photos - == Discover and approve a transfer == Client -> Manager: getTransfers(direction, 0) -Manager -> VBoxC: find matching live transfers +Manager -> VBoxC: find matching tracked transfers VBoxC --> Manager: matching transfers Manager --> Client: transfer[] diff --git a/doc/manual/en_US/man_VBoxManage-clipboard.xml b/doc/manual/en_US/man_VBoxManage-clipboard.xml index 423a1ef30597..6f214c9b2694 100644 --- a/doc/manual/en_US/man_VBoxManage-clipboard.xml +++ b/doc/manual/en_US/man_VBoxManage-clipboard.xml @@ -30,7 +30,7 @@ ]> - $Date: 2026-07-03 17:22:37 +0200 (Fri, 03 Jul 2026) $ + $Date: 2026-08-05 17:08:05 +0200 (Wed, 05 Aug 2026) $ VBoxManage clipboard @@ -576,20 +576,28 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 The VBoxManage clipboard listen command listens for - live clipboard events from the virtual machine console. It prints event - metadata by default, including revision and - clientId fields when they are supplied by the clipboard - API. The revision is a monotonic event revision for the live console - clipboard. A clientId value of 0 - means no IClipboardSession originated the event; a - non-zero value identifies the originating clipboard API session. The - command is observational by default; use VBoxManage clipboard - copy to publish host clipboard data to the guest. With repeated - or options, the command may - read guest data for diagnostic output. Waitable clipboard events are - acknowledged automatically before event output is written. Pressing + live clipboard events from the virtual machine console. Human output + uses compact logfmt-style fields in the order rev, + src when available, event, and + action when available, without a command-name prefix. + The revision is a monotonic event revision for the live console clipboard. + With verbose output, cid is inserted between + src and event. A client ID value of + 0 means no IClipboardSession + originated the event; a non-zero value identifies the originating + clipboard API session. The command is observational by default; use + VBoxManage clipboard copy to publish host clipboard + data to the guest. Waitable clipboard events are acknowledged + automatically before event output is written. Pressing Ctrl+C stops the command. + + If Shared Clipboard is disabled when listening starts, the first output + record is a warning with event=warning and + mode=disabled. This startup record is emitted + regardless of and does not count toward + . + @@ -610,13 +618,14 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 Specifies the event output format. JSON output is written as one - JSON object per line. When event identity metadata is available, - human output includes revision=<n> and - clientId=<id>, machine-readable output - includes revision="<n>" and - clientId="<id>", and JSON output includes - numeric revision and clientId - members. + JSON object per line. Human output labels the revision, source, + and client ID fields rev, src, + and cid, and quotes and escapes MIME values. + Machine-readable and JSON output retain the full field names revision, + source, and clientId; + machine-readable values are quoted, while JSON revision and client + ID values are numeric. The client ID field is included only with + verbose output. @@ -642,9 +651,12 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 - Increases verbosity. Repeating this option includes payload data - in data-change output where available and may read guest data for - diagnostics. Payload data is bounded and escaped where possible. + Increases verbosity. The first occurrence includes the + client ID field. Repeating this option includes + available payload data in data-change output, available host text + in host data-requested output, and may read + guest data for format-change diagnostics. Payload data is bounded + and escaped. Clipboard payloads can contain sensitive data. diff --git a/doc/manual/ru_RU/man_VBoxManage-clipboard.xml b/doc/manual/ru_RU/man_VBoxManage-clipboard.xml index 46fc46e6d2e4..77e0d2aaaed9 100644 --- a/doc/manual/ru_RU/man_VBoxManage-clipboard.xml +++ b/doc/manual/ru_RU/man_VBoxManage-clipboard.xml @@ -30,7 +30,7 @@ ]> - $Date: 2026-07-03 17:22:37 +0200 (Fri, 03 Jul 2026) $ + $Date: 2026-08-05 17:08:05 +0200 (Wed, 05 Aug 2026) $ VBoxManage clipboard @@ -576,20 +576,28 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 The VBoxManage clipboard listen command listens for - live clipboard events from the virtual machine console. It prints event - metadata by default, including revision and - clientId fields when they are supplied by the clipboard - API. The revision is a monotonic event revision for the live console - clipboard. A clientId value of 0 - means no IClipboardSession originated the event; a - non-zero value identifies the originating clipboard API session. The - command is observational by default; use VBoxManage clipboard - copy to publish host clipboard data to the guest. With repeated - or options, the command may - read guest data for diagnostic output. Waitable clipboard events are - acknowledged automatically before event output is written. Pressing + live clipboard events from the virtual machine console. Human output + uses compact logfmt-style fields in the order rev, + src when available, event, and + action when available, without a command-name prefix. + The revision is a monotonic event revision for the live console clipboard. + With verbose output, cid is inserted between + src and event. A client ID value of + 0 means no IClipboardSession + originated the event; a non-zero value identifies the originating + clipboard API session. The command is observational by default; use + VBoxManage clipboard copy to publish host clipboard + data to the guest. Waitable clipboard events are acknowledged + automatically before event output is written. Pressing Ctrl+C stops the command. + + If Shared Clipboard is disabled when listening starts, the first output + record is a warning with event=warning and + mode=disabled. This startup record is emitted + regardless of and does not count toward + . + @@ -610,13 +618,14 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 Specifies the event output format. JSON output is written as one - JSON object per line. When event identity metadata is available, - human output includes revision=<n> and - clientId=<id>, machine-readable output - includes revision="<n>" and - clientId="<id>", and JSON output includes - numeric revision and clientId - members. + JSON object per line. Human output labels the revision, source, + and client ID fields rev, src, + and cid, and quotes and escapes MIME values. + Machine-readable and JSON output retain the full field names revision, + source, and clientId; + machine-readable values are quoted, while JSON revision and client + ID values are numeric. The client ID field is included only with + verbose output. @@ -642,9 +651,12 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 - Increases verbosity. Repeating this option includes payload data - in data-change output where available and may read guest data for - diagnostics. Payload data is bounded and escaped where possible. + Increases verbosity. The first occurrence includes the + client ID field. Repeating this option includes + available payload data in data-change output, available host text + in host data-requested output, and may read + guest data for format-change diagnostics. Payload data is bounded + and escaped. Clipboard payloads can contain sensitive data. diff --git a/doc/manual/user_ChangeLogImpl.xml b/doc/manual/user_ChangeLogImpl.xml index df2c6ce54203..7a7d54120dc4 100644 --- a/doc/manual/user_ChangeLogImpl.xml +++ b/doc/manual/user_ChangeLogImpl.xml @@ -111,6 +111,12 @@ Rules for adding a changelog entry to make them look more uniform: 'modifyvm' clipboard options are now marked as being deprecated. + + Shared Clipboard: Fixed plain-text copy and paste on macOS hosts and improved + VBoxManage clipboard listen event and payload output, including compact logfmt-style + human records and a startup warning when Shared Clipboard is disabled + + placeholder diff --git a/include/VBox/GuestHost/SharedClipboard-transfers.h b/include/VBox/GuestHost/SharedClipboard-transfers.h index c05099553522..f3c846f1a8f7 100644 --- a/include/VBox/GuestHost/SharedClipboard-transfers.h +++ b/include/VBox/GuestHost/SharedClipboard-transfers.h @@ -1,4 +1,4 @@ -/* $Id: SharedClipboard-transfers.h 114830 2026-07-31 10:02:47Z andreas.loeffler@oracle.com $ */ +/* $Id: SharedClipboard-transfers.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Shared transfer functions between host and guest. */ @@ -1146,6 +1146,62 @@ void ShClTransferObjDataChunkFree(PSHCLOBJDATACHUNK pDataChunk); /** @name Shared Clipboard transfer API. * @{ */ +/** + * Checks whether a transfer ID is in the assignable context-local range. + * + * @returns true if the ID can be used by a transfer context, false otherwise. + * @param idTransfer Transfer ID to check before narrowing to SHCLTRANSFERID. + */ +bool ShClTransferIdIsValid(uint32_t idTransfer); + +/** + * Checks whether a transfer key is usable for lifecycle tracking. + * + * @returns true if the key identifies a non-nil service transfer, false otherwise. + * @param idSession Service session ID. + * @param idTransfer Service transfer ID, before narrowing to SHCLTRANSFERID. + * @param uGeneration Service transfer generation. + */ +bool ShClTransferKeyIsValid(SHCLSESSIONID idSession, uint32_t idTransfer, SHCLTRANSFERGEN uGeneration); + +/** + * Checks whether a transfer status is part of the Shared Clipboard protocol. + * + * @returns true if the status is valid, false otherwise. + * @param enmStatus Transfer status to validate. + */ +bool ShClTransferStatusIsValid(SHCLTRANSFERSTATUS enmStatus); + +/** + * Checks whether a transfer status ends the transfer lifecycle. + * + * @returns true if the status is terminal, false otherwise. + * @param enmStatus Transfer status to classify. + */ +bool ShClTransferStatusIsTerminal(SHCLTRANSFERSTATUS enmStatus); + +/** + * Checks whether a transfer status and result form a valid service reply. + * + * @returns true if the status is valid and the result matches it, false otherwise. + * @param enmStatus Transfer status to validate. + * @param rcTransfer Transfer result associated with the status. + */ +bool ShClTransferStatusResultIsValid(SHCLTRANSFERSTATUS enmStatus, int rcTransfer); + +/** + * Checks whether a service-reported transfer status may follow the previous + * service-reported status. + * + * This describes lifecycle records, not the lower-level transfer state + * mutation sequence. + * + * @returns true if both statuses are valid and the transition is monotonic, false otherwise. + * @param enmOldStatus Current transfer status. + * @param enmNewStatus Incoming transfer status. + */ +bool ShClTransferStatusTransitionIsValid(SHCLTRANSFERSTATUS enmOldStatus, SHCLTRANSFERSTATUS enmNewStatus); + int ShClTransferCreateEx(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, uint32_t cbMaxChunkSize, uint32_t cMaxListHandles, uint32_t cMaxObjHandles, PSHCLTRANSFER *ppTransfer); int ShClTransferCreate(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, PSHCLTRANSFER *ppTransfer); int ShClTransferInit(PSHCLTRANSFER pTransfer); diff --git a/include/VBox/GuestHost/SharedClipboard.h b/include/VBox/GuestHost/SharedClipboard.h index eec9c9037743..6331729f9c54 100644 --- a/include/VBox/GuestHost/SharedClipboard.h +++ b/include/VBox/GuestHost/SharedClipboard.h @@ -92,6 +92,22 @@ typedef uint32_t SHCLFORMATS; /** Pointer to a bit map of Shared Clipboard formats (VBOX_SHCL_FMT_XXX). */ typedef SHCLFORMATS *PSHCLFORMATS; +/** + * Checks whether a value names exactly one Shared Clipboard format. + * + * @returns true if @a uFmt is a single valid VBOX_SHCL_FMT_XXX bit, false otherwise. + * @param uFmt Format value to validate. + */ +VBGH_DECL(bool) ShClFormatIsValid(SHCLFORMAT uFmt); + +/** + * Checks whether a Shared Clipboard format mask contains only known format bits. + * + * @returns true if @a fFormats only contains VBOX_SHCL_FMT_XXX bits, false otherwise. + * @param fFormats Format mask to validate. VBOX_SHCL_FMT_NONE is valid. + */ +VBGH_DECL(bool) ShClFormatsAreValid(SHCLFORMATS fFormats); + /** Main API Shared Clipboard client/session identifier. */ typedef uint32_t SHCLMAINCLIENTID; /** Pointer to a Main API Shared Clipboard client/session identifier. */ @@ -162,6 +178,13 @@ typedef enum SHCLTRANSFERDIR /** Pointer to a shared clipboard transfer direction. */ typedef SHCLTRANSFERDIR *PSHCLTRANSFERDIR; +/** + * Checks whether a Shared Clipboard transfer direction is valid. + * + * @returns true if @a enmDir is valid, false otherwise. + * @param enmDir Transfer direction to validate. + */ +VBGH_DECL(bool) ShClTransferDirIsValid(SHCLTRANSFERDIR enmDir); /** * Shared Clipboard data read request. @@ -314,6 +337,14 @@ typedef enum SHCLSOURCE SHCLSOURCE_32BIT_HACK = 0x7fffffff } SHCLSOURCE; +/** + * Checks whether a Shared Clipboard source is valid. + * + * @returns true if @a enmSource is valid, false otherwise. + * @param enmSource Source to validate. + */ +VBGH_DECL(bool) ShClSourceIsValid(SHCLSOURCE enmSource); + /** @name Shared Clipboard caching. * @{ */ diff --git a/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk b/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk index 083749184b28..2d9f1aab5a99 100644 --- a/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk +++ b/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114381 2026-06-16 06:43:15Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the VirtualBox Guest Addition Darwin Client. # @@ -49,6 +49,12 @@ ifdef VBOX_WITH_SHARED_CLIPBOARD VBoxClientClipboardGuestToHost.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp + ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS VBOX_WITH_SHARED_CLIPBOARD_GUEST + VBoxClient_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp + endif endif VBoxClient_LDFLAGS = -framework IOKit -framework ApplicationServices diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageClipboard.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageClipboard.cpp index 64b7802fe256..5c538c1808ed 100644 --- a/src/VBox/Frontends/VBoxManage/VBoxManageClipboard.cpp +++ b/src/VBox/Frontends/VBoxManage/VBoxManageClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxManageClipboard.cpp 114646 2026-07-08 08:18:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxManageClipboard.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VBoxManage - Implementation of the clipboard command. */ @@ -149,6 +149,20 @@ typedef struct SHCLCLIPBOARDEVENTINFO } SHCLCLIPBOARDEVENTINFO; +typedef struct SHCLHANDLELISTENSTATE +{ + SHCLHANDLELISTENSTATE() + : fHaveSource(false) + , enmSource(ClipboardSource_Custom) + { } + + /** Whether enmSource contains the last source reported by an event. */ + bool fHaveSource; + /** Last clipboard source reported by an event. */ + ClipboardSource_T enmSource; +} SHCLHANDLELISTENSTATE; + + /** Current verbosity level for clipboard command diagnostics. */ static unsigned g_uVerbosity = 0; @@ -281,51 +295,100 @@ static bool shclGetClipboardEventInfo(const ComPtr &ptrEvent, SHCLCLIPBO } -/** - * Prints optional clipboard event identity fields in JSON output. - * - * @param pInfo Event identity fields, optional. - */ -static void shclPrintEventInfoJson(const SHCLCLIPBOARDEVENTINFO *pInfo) -{ - if (!pInfo) - return; - if (pInfo->fHaveRevision) - RTStrmPrintf(g_pStdOut, ",\"revision\":%RI64", (int64_t)pInfo->iRevision); - if (pInfo->fHaveClientId) - RTStrmPrintf(g_pStdOut, ",\"clientId\":%RU32", (uint32_t)pInfo->uClientId); -} +static void shclHandleListenJsonString(const char *pszValue); /** - * Prints optional clipboard event identity fields in machine-readable output. + * Starts a uniformly ordered clipboard-listen event record. * - * @param pInfo Event identity fields, optional. - */ -static void shclPrintEventInfoMachineReadable(const SHCLCLIPBOARDEVENTINFO *pInfo) -{ - if (!pInfo) - return; - if (pInfo->fHaveRevision) - RTPrintf(" revision=\"%RI64\"", (int64_t)pInfo->iRevision); - if (pInfo->fHaveClientId) - RTPrintf(" clientId=\"%RU32\"", (uint32_t)pInfo->uClientId); -} - - -/** - * Prints optional clipboard event identity fields in human-readable output. + * The caller appends event-specific fields and terminates the record. * + * @param enmFormat Output format. * @param pInfo Event identity fields, optional. + * @param pszSource Clipboard source, optional. + * @param pszEvent Event name. + * @param pszAction Clipboard action, optional. */ -static void shclPrintEventInfoHuman(const SHCLCLIPBOARDEVENTINFO *pInfo) +static void shclHandleListenPrintPrefix(CLIPBOARDLISTENFMT enmFormat, + const SHCLCLIPBOARDEVENTINFO *pInfo, + const char *pszSource, + const char *pszEvent, + const char *pszAction) { - if (!pInfo) - return; - if (pInfo->fHaveRevision) - RTPrintf(" revision=%RI64", (int64_t)pInfo->iRevision); - if (pInfo->fHaveClientId) - RTPrintf(" clientId=%RU32", (uint32_t)pInfo->uClientId); + AssertPtrReturnVoid(pszEvent); + + if (enmFormat == CLIPBOARDLISTENFMT_JSON) + { + const char *pszSeparator = ""; + RTStrmPutCh(g_pStdOut, '{'); + if (pInfo && pInfo->fHaveRevision) + { + RTStrmPrintf(g_pStdOut, "\"revision\":%RI64", (int64_t)pInfo->iRevision); + pszSeparator = ","; + } + if (pszSource) + { + RTStrmPrintf(g_pStdOut, "%s\"source\":", pszSeparator); + shclHandleListenJsonString(pszSource); + pszSeparator = ","; + } + if (g_uVerbosity && pInfo && pInfo->fHaveClientId) + { + RTStrmPrintf(g_pStdOut, "%s\"clientId\":%RU32", pszSeparator, (uint32_t)pInfo->uClientId); + pszSeparator = ","; + } + RTStrmPrintf(g_pStdOut, "%s\"event\":", pszSeparator); + shclHandleListenJsonString(pszEvent); + if (pszAction) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"action\":")); + shclHandleListenJsonString(pszAction); + } + } + else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) + { + const char *pszSeparator = ""; + if (pInfo && pInfo->fHaveRevision) + { + RTPrintf("revision=\"%RI64\"", (int64_t)pInfo->iRevision); + pszSeparator = " "; + } + if (pszSource) + { + RTPrintf("%ssource=\"%s\"", pszSeparator, pszSource); + pszSeparator = " "; + } + if (g_uVerbosity && pInfo && pInfo->fHaveClientId) + { + RTPrintf("%sclientId=\"%RU32\"", pszSeparator, (uint32_t)pInfo->uClientId); + pszSeparator = " "; + } + RTPrintf("%sevent=\"%s\"", pszSeparator, pszEvent); + if (pszAction) + RTPrintf(" action=\"%s\"", pszAction); + } + else + { + const char *pszSeparator = ""; + if (pInfo && pInfo->fHaveRevision) + { + RTPrintf("rev=%RI64", (int64_t)pInfo->iRevision); + pszSeparator = " "; + } + if (pszSource) + { + RTPrintf("%ssrc=%s", pszSeparator, pszSource); + pszSeparator = " "; + } + if (g_uVerbosity && pInfo && pInfo->fHaveClientId) + { + RTPrintf("%scid=%RU32", pszSeparator, (uint32_t)pInfo->uClientId); + pszSeparator = " "; + } + RTPrintf("%sevent=%s", pszSeparator, pszEvent); + if (pszAction) + RTPrintf(" action=%s", pszAction); + } } @@ -506,6 +569,36 @@ static HRESULT shclGet(HandlerArg *pArg, const char *pszMachine, ComPtr ptrMachine; + HRESULT hrc = pArg->session->COMGETTER(Machine)(ptrMachine.asOutParam()); + if (FAILED(hrc)) + return hrc; + if (ptrMachine.isNull()) + return E_FAIL; + + ComPtr ptrClipboardSettings; + hrc = ptrMachine->COMGETTER(Clipboard)(ptrClipboardSettings.asOutParam()); + if (FAILED(hrc)) + return hrc; + if (ptrClipboardSettings.isNull()) + return E_FAIL; + + return ptrClipboardSettings->COMGETTER(Mode)(penmMode); +} + + /** * Opens the VM clipboard settings object for modification. * @@ -935,24 +1028,39 @@ static void shclHandleListenJsonStringN(const char *pszValue, size_t cchValue) RTStrmPutCh(g_pStdOut, '"'); if (pszValue) { + size_t offPending = 0; for (size_t i = 0; i < cchValue; i++) { unsigned char const ch = (unsigned char)pszValue[i]; + const char *pszEscape = NULL; + size_t cchEscape = 0; switch (ch) { - case '\\': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\\\")); break; - case '"': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\\"")); break; - case '\n': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\n")); break; - case '\r': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\r")); break; - case '\t': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\t")); break; + case '\\': pszEscape = "\\\\"; cchEscape = 2; break; + case '"': pszEscape = "\\\""; cchEscape = 2; break; + case '\n': pszEscape = "\\n"; cchEscape = 2; break; + case '\r': pszEscape = "\\r"; cchEscape = 2; break; + case '\t': pszEscape = "\\t"; cchEscape = 2; break; default: - if (ch >= 0x20) - RTStrmPutCh(g_pStdOut, ch); - else + if (ch < 0x20) + { + if (i > offPending) + RTStrmWrite(g_pStdOut, &pszValue[offPending], i - offPending); RTStrmPrintf(g_pStdOut, "\\u%04x", ch); + offPending = i + 1; + } break; } + if (pszEscape) + { + if (i > offPending) + RTStrmWrite(g_pStdOut, &pszValue[offPending], i - offPending); + RTStrmWrite(g_pStdOut, pszEscape, cchEscape); + offPending = i + 1; + } } + if (offPending < cchValue) + RTStrmWrite(g_pStdOut, &pszValue[offPending], cchValue - offPending); } RTStrmPutCh(g_pStdOut, '"'); } @@ -983,6 +1091,38 @@ static void shclHandleListenQuotedString(const char *pszValue) } +/** + * Prints the startup warning used when Shared Clipboard is disabled. + * + * @param enmFormat Output format. + */ +static void shclHandleListenPrintDisabledWarning(CLIPBOARDLISTENFMT enmFormat) +{ + const char *pszMessage = Clipboard::tr("Shared Clipboard is disabled for this VM."); + + shclHandleListenPrintPrefix(enmFormat, NULL /* pInfo */, NULL /* pszSource */, + "warning", NULL /* pszAction */); + if (enmFormat == CLIPBOARDLISTENFMT_JSON) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"mode\":\"disabled\",\"message\":")); + shclHandleListenJsonString(pszMessage); + RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); + } + else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" mode=\"disabled\" message=")); + shclHandleListenQuotedString(pszMessage); + RTStrmPutCh(g_pStdOut, '\n'); + } + else + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" mode=disabled message=")); + shclHandleListenQuotedString(pszMessage); + RTStrmPutCh(g_pStdOut, '\n'); + } +} + + /** * Converts a bounded UTF-8 or UTF-16 clipboard payload to UTF-8 for verbose event output. * @@ -1130,107 +1270,41 @@ static void shclVerbosePayloadData(const char *pszCommand, const char *pszOperat /** - * Reads and logs current guest data for extra-verbose format-only diagnostics. + * Reads current guest data for an extra-verbose format event. * - * @returns true if data was read and logged, false otherwise. - * @param pszCommand Clipboard subcommand name. - * @param pszOperation Copying operation being logged. - * @param ptrSession Clipboard session to read from. - * @param strExpectedMimeType Expected MIME type selected from the format event. - * @param fLogFailures Whether read misses/errors should be logged. + * @returns true if matching non-empty guest data was returned. + * @param ptrSession Clipboard session to read from. + * @param strExpectedMimeType MIME type selected from the format event. + * @param strReadMimeType Where to return the MIME type read. + * @param aBuffer Where to return the payload read. */ -static bool shclVerboseReadGuestData(const char *pszCommand, const char *pszOperation, - const ComPtr &ptrSession, const Utf8Str &strExpectedMimeType, - bool fLogFailures) +static bool shclHandleListenReadGuestData(const ComPtr &ptrSession, + const Utf8Str &strExpectedMimeType, + Utf8Str &strReadMimeType, + SafeArray &aBuffer) { - if (g_uVerbosity < 2) + if (g_uVerbosity < 2 || ptrSession.isNull()) return false; ClipboardSource_T enmReadSource = ClipboardSource_Custom; Bstr bstrRequestedMimeType(""); Bstr bstrReadMimeType; - SafeArray aBuffer; HRESULT hrc = ptrSession->ReadDataRaw(ClipboardAction_Copy, bstrRequestedMimeType.raw(), &enmReadSource, bstrReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aBuffer)); if (FAILED(hrc)) - { - if (fLogFailures) - shclVerbose("%s: data read failed for format=%s: %Rhrc", pszCommand, - strExpectedMimeType.c_str(), hrc); return false; - } - Utf8Str strReadMimeType(bstrReadMimeType); + strReadMimeType = bstrReadMimeType; if (enmReadSource != ClipboardSource_Guest) - { - if (fLogFailures) - shclVerbose("%s: data read ignored non-guest source=%s format=%s size=%zu", pszCommand, - ShClHlpSourceToString(enmReadSource), strReadMimeType.c_str(), aBuffer.size()); return false; - } if (!shclMimeEquivalent(strExpectedMimeType, strReadMimeType)) - { - if (fLogFailures) - shclVerbose("%s: data read ignored mismatched format=%s expected=%s size=%zu", pszCommand, - strReadMimeType.c_str(), strExpectedMimeType.c_str(), aBuffer.size()); return false; - } if (!aBuffer.size()) - { - if (fLogFailures) - shclVerbose("%s: data read ignored empty guest data format=%s", pszCommand, - strReadMimeType.c_str()); return false; - } - - shclVerbosePayloadData(pszCommand, pszOperation, strReadMimeType, aBuffer.raw(), aBuffer.size()); return true; } -/** - * Reads and logs guest data selected by a format-changed event for extra-verbose diagnostics. - * - * @param pszCommand Clipboard subcommand name. - * @param ptrSession Clipboard session to read from. - * @param ptrFormatEvent Format-changed event to inspect. - */ -static void shclHandleListenVerboseReadFormatEventGuestData(const char *pszCommand, - const ComPtr &ptrSession, - const ComPtr &ptrFormatEvent) -{ - if (g_uVerbosity < 2 || ptrFormatEvent.isNull()) - return; - - ClipboardSource_T enmSource = ClipboardSource_Custom; - SafeIfaceArray aFormats; - HRESULT hrc = ptrFormatEvent->COMGETTER(ClipboardSource)(&enmSource); - if (SUCCEEDED(hrc)) - hrc = ptrFormatEvent->COMGETTER(Formats)(ComSafeArrayAsOutParam(aFormats)); - if (FAILED(hrc)) - { - shclVerbose("%s: format-event inspection failed: %Rhrc", pszCommand, hrc); - return; - } - if (enmSource != ClipboardSource_Guest) - return; - - ComPtr ptrPreferredFormat; - Utf8Str strMimeType; - hrc = shclSelectPreferredFormat(aFormats, ptrPreferredFormat, strMimeType); - if (hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED) - return; - if (FAILED(hrc)) - { - shclVerbose("%s: format selection failed: %Rhrc", pszCommand, hrc); - return; - } - - shclVerboseReadGuestData(pszCommand, "read guest data after format event", ptrSession, strMimeType, - true /* fLogFailures */); -} - - /** * Gets the MIME type of a clipboard format. * @@ -1353,17 +1427,31 @@ static const char *shclEventTypeToString(VBoxEventType_T enmType) * @param enmFormat Output format. * @param pszEvent Event name. * @param ptrItem Clipboard item associated with the event. + * @param pszAction Clipboard action associated with the event, optional. * @param fVerboseData Whether to include payload data. * @param pEventInfo Optional event identity fields. + * @param pListenState Listen state to update and use as a source fallback. */ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const char *pszEvent, - const ComPtr &ptrItem, bool fVerboseData, - const SHCLCLIPBOARDEVENTINFO *pEventInfo) + const ComPtr &ptrItem, const char *pszAction, + bool fVerboseData, const SHCLCLIPBOARDEVENTINFO *pEventInfo, + SHCLHANDLELISTENSTATE *pListenState) { Utf8Str strMimeType; ClipboardSource_T enmSource = ClipboardSource_Host; SafeArray aBuffer; HRESULT hrc = ptrItem.isNotNull() ? shclGetItemData(ptrItem, strMimeType, &enmSource, aBuffer) : E_FAIL; + if (SUCCEEDED(hrc) && pListenState) + { + pListenState->fHaveSource = true; + pListenState->enmSource = enmSource; + } + + const char *pszSource = NULL; + if (SUCCEEDED(hrc)) + pszSource = ShClHlpSourceToString(enmSource); + else if (pListenState && pListenState->fHaveSource) + pszSource = ShClHlpSourceToString(pListenState->enmSource); char *pszVerboseText = NULL; size_t cchVerboseText = 0; @@ -1375,13 +1463,9 @@ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const c if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":")); - shclHandleListenJsonString(pszEvent); - shclPrintEventInfoJson(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, pszEvent, pszAction); if (SUCCEEDED(hrc)) { - RTStrmPrintf(g_pStdOut, ",\"source\":"); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); RTStrmPrintf(g_pStdOut, ",\"format\":"); shclHandleListenJsonString(strMimeType.c_str()); RTStrmPrintf(g_pStdOut, ",\"size\":%zu", aBuffer.size()); @@ -1400,12 +1484,10 @@ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const c } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"%s\"", pszEvent); - shclPrintEventInfoMachineReadable(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, pszEvent, pszAction); if (SUCCEEDED(hrc)) { - RTPrintf(" source=\"%s\" format=\"%s\" size=\"%zu\"", - ShClHlpSourceToString(enmSource), strMimeType.c_str(), aBuffer.size()); + RTPrintf(" format=\"%s\" size=\"%zu\"", strMimeType.c_str(), aBuffer.size()); if (fHaveVerboseData) { RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data=\"")); @@ -1422,11 +1504,12 @@ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const c } else { - RTPrintf("clipboard: %s", pszEvent); - shclPrintEventInfoHuman(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, pszEvent, pszAction); if (SUCCEEDED(hrc)) { - RTPrintf(" source=%s format=%s size=%zu", ShClHlpSourceToString(enmSource), strMimeType.c_str(), aBuffer.size()); + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" format=")); + shclHandleListenQuotedString(strMimeType.c_str()); + RTPrintf(" size=%zu", aBuffer.size()); if (fHaveVerboseData) { RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data=\"")); @@ -1451,14 +1534,24 @@ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const c * * @param enmFormat Output format. * @param ptrFormatEvent Format-changed event object to print. + * @param ptrSession Clipboard session for extra-verbose guest reads. * @param pEventInfo Optional event identity fields. + * @param pListenState Listen state to update. */ static void shclHandleListenPrintFormatChangedEvent(CLIPBOARDLISTENFMT enmFormat, const ComPtr &ptrFormatEvent, - const SHCLCLIPBOARDEVENTINFO *pEventInfo) + const ComPtr &ptrSession, + const SHCLCLIPBOARDEVENTINFO *pEventInfo, + SHCLHANDLELISTENSTATE *pListenState) { ClipboardSource_T enmSource = ClipboardSource_Host; HRESULT hrc = ptrFormatEvent->COMGETTER(ClipboardSource)(&enmSource); + bool const fHaveSource = SUCCEEDED(hrc); + if (SUCCEEDED(hrc) && pListenState) + { + pListenState->fHaveSource = true; + pListenState->enmSource = enmSource; + } SafeIfaceArray aFormats; if (SUCCEEDED(hrc)) @@ -1477,14 +1570,33 @@ static void shclHandleListenPrintFormatChangedEvent(CLIPBOARDLISTENFMT enmFormat } } + Utf8Str strDataMimeType; + SafeArray aDataBuffer; + bool fHaveData = false; + if (SUCCEEDED(hrc) && enmSource == ClipboardSource_Guest && g_uVerbosity > 1) + { + ComPtr ptrPreferredFormat; + Utf8Str strPreferredMimeType; + HRESULT const hrcSelect = shclSelectPreferredFormat(aFormats, ptrPreferredFormat, strPreferredMimeType); + if (SUCCEEDED(hrcSelect)) + fHaveData = shclHandleListenReadGuestData(ptrSession, strPreferredMimeType, + strDataMimeType, aDataBuffer); + } + + char *pszData = NULL; + size_t cchData = 0; + bool fDataTruncated = false; + bool const fHaveText = fHaveData + && shclGetVerboseText(strDataMimeType, aDataBuffer.raw(), aDataBuffer.size(), + &pszData, &cchData, &fDataTruncated); + if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"format-changed\"")); - shclPrintEventInfoJson(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + fHaveSource ? ShClHlpSourceToString(enmSource) : NULL, + "format-changed", NULL /* pszAction */); if (SUCCEEDED(hrc)) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"source\":")); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"formats\":[")); for (size_t i = 0; i < vecMimeTypes.size(); ++i) { @@ -1493,37 +1605,77 @@ static void shclHandleListenPrintFormatChangedEvent(CLIPBOARDLISTENFMT enmFormat shclHandleListenJsonString(vecMimeTypes[i].c_str()); } RTStrmPutCh(g_pStdOut, ']'); + if (fHaveData) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"data-format\":")); + shclHandleListenJsonString(strDataMimeType.c_str()); + RTStrmPrintf(g_pStdOut, ",\"size\":%zu,\"data\":", aDataBuffer.size()); + if (fHaveText) + shclHandleListenJsonStringN(pszData, cchData); + else + shclHandleListenJsonString(""); + if (fHaveText && fDataTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"data-truncated\":true")); + } } RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"format-changed\""); - shclPrintEventInfoMachineReadable(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + fHaveSource ? ShClHlpSourceToString(enmSource) : NULL, + "format-changed", NULL /* pszAction */); if (SUCCEEDED(hrc)) { - RTPrintf(" source=\"%s\"", ShClHlpSourceToString(enmSource)); for (size_t i = 0; i < vecMimeTypes.size(); ++i) RTPrintf(" format=\"%s\"", vecMimeTypes[i].c_str()); + if (fHaveData) + { + RTPrintf(" data-format=\"%s\" size=\"%zu\" data=\"", strDataMimeType.c_str(), aDataBuffer.size()); + if (fHaveText) + ShClHlpPrintEscapedString(g_pStdOut, pszData, cchData); + else + RTStrmWrite(g_pStdOut, RT_STR_TUPLE("")); + RTStrmPutCh(g_pStdOut, '"'); + if (fHaveText && fDataTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-truncated=\"true\"")); + } } RTPrintf("\n"); } else { - RTPrintf("clipboard: format-changed"); - shclPrintEventInfoHuman(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + fHaveSource ? ShClHlpSourceToString(enmSource) : NULL, + "format-changed", NULL /* pszAction */); if (SUCCEEDED(hrc)) { - RTPrintf(" source=%s formats=", ShClHlpSourceToString(enmSource)); + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" formats=\"")); for (size_t i = 0; i < vecMimeTypes.size(); ++i) { if (i) - RTPrintf(","); - RTPrintf("%s", vecMimeTypes[i].c_str()); + RTStrmPutCh(g_pStdOut, ','); + ShClHlpPrintEscapedString(g_pStdOut, vecMimeTypes[i].c_str(), vecMimeTypes[i].length()); + } + RTStrmPutCh(g_pStdOut, '"'); + if (fHaveData) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-format=")); + shclHandleListenQuotedString(strDataMimeType.c_str()); + RTPrintf(" size=%zu data=\"", aDataBuffer.size()); + if (fHaveText) + ShClHlpPrintEscapedString(g_pStdOut, pszData, cchData); + else + RTStrmWrite(g_pStdOut, RT_STR_TUPLE("")); + RTStrmPutCh(g_pStdOut, '"'); + if (fHaveText && fDataTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-truncated=true")); } } RTPrintf("\n"); } + + RTStrFree(pszData); } @@ -1533,10 +1685,12 @@ static void shclHandleListenPrintFormatChangedEvent(CLIPBOARDLISTENFMT enmFormat * @param enmFormat Output format. * @param ptrRequestEvent Data-requested event object to print. * @param pEventInfo Optional event identity fields. + * @param pListenState Listen state to update. */ static void shclHandleListenPrintDataRequestedEvent(CLIPBOARDLISTENFMT enmFormat, const ComPtr &ptrRequestEvent, - const SHCLCLIPBOARDEVENTINFO *pEventInfo) + const SHCLCLIPBOARDEVENTINFO *pEventInfo, + SHCLHANDLELISTENSTATE *pListenState) { ULONG uRequestId = 0; ClipboardAction_T enmAction = ClipboardAction_Copy; @@ -1548,6 +1702,12 @@ static void shclHandleListenPrintDataRequestedEvent(CLIPBOARDLISTENFMT enmFormat hrc = ptrRequestEvent->COMGETTER(Action)(&enmAction); if (SUCCEEDED(hrc)) hrc = ptrRequestEvent->COMGETTER(ClipboardSource)(&enmSource); + bool const fHaveSource = SUCCEEDED(hrc); + if (fHaveSource && pListenState) + { + pListenState->fHaveSource = true; + pListenState->enmSource = enmSource; + } if (SUCCEEDED(hrc)) hrc = ptrRequestEvent->COMGETTER(Format)(ptrFormat.asOutParam()); @@ -1555,41 +1715,83 @@ static void shclHandleListenPrintDataRequestedEvent(CLIPBOARDLISTENFMT enmFormat if (SUCCEEDED(hrc)) hrc = shclGetFormatMimeType(ptrFormat, strMimeType); + ComPtr ptrItem; + HRESULT hrcItem = ptrRequestEvent->COMGETTER(Item)(ptrItem.asOutParam()); + Utf8Str strItemMimeType; + ClipboardSource_T enmItemSource = ClipboardSource_Custom; + SafeArray aItemBuffer; + if (SUCCEEDED(hrcItem) && ptrItem.isNotNull()) + hrcItem = shclGetItemData(ptrItem, strItemMimeType, &enmItemSource, aItemBuffer); + else + hrcItem = E_FAIL; + + char *pszText = NULL; + size_t cchText = 0; + bool fTextTruncated = false; + bool const fHaveText = g_uVerbosity > 1 + && SUCCEEDED(hrc) + && enmSource == ClipboardSource_Host + && SUCCEEDED(hrcItem) + && enmItemSource == ClipboardSource_Host + && shclMimeEquivalent(strMimeType, strItemMimeType) + && shclGetVerboseText(strItemMimeType, aItemBuffer.raw(), aItemBuffer.size(), + &pszText, &cchText, &fTextTruncated); + + const char *pszSource = fHaveSource ? ShClHlpSourceToString(enmSource) : NULL; + const char *pszAction = SUCCEEDED(hrc) ? shclActionToString(enmAction) : NULL; + if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"data-requested\"")); - shclPrintEventInfoJson(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, "data-requested", pszAction); if (SUCCEEDED(hrc)) { - RTStrmPrintf(g_pStdOut, ",\"request-id\":%RU32,\"action\":", (uint32_t)uRequestId); - shclHandleListenJsonString(shclActionToString(enmAction)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"source\":")); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"format\":")); + RTStrmPrintf(g_pStdOut, ",\"request-id\":%RU32,\"format\":", (uint32_t)uRequestId); shclHandleListenJsonString(strMimeType.c_str()); } + if (fHaveText) + { + RTStrmPrintf(g_pStdOut, ",\"size\":%zu,\"data\":", aItemBuffer.size()); + shclHandleListenJsonStringN(pszText, cchText); + if (fTextTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"data-truncated\":true")); + } RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"data-requested\""); - shclPrintEventInfoMachineReadable(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, "data-requested", pszAction); if (SUCCEEDED(hrc)) - RTPrintf(" request-id=\"%RU32\" action=\"%s\" source=\"%s\" format=\"%s\"", - (uint32_t)uRequestId, shclActionToString(enmAction), - ShClHlpSourceToString(enmSource), strMimeType.c_str()); + RTPrintf(" request-id=\"%RU32\" format=\"%s\"", (uint32_t)uRequestId, strMimeType.c_str()); + if (fHaveText) + { + RTPrintf(" size=\"%zu\" data=\"", aItemBuffer.size()); + ShClHlpPrintEscapedString(g_pStdOut, pszText, cchText); + RTStrmPutCh(g_pStdOut, '"'); + if (fTextTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-truncated=\"true\"")); + } RTPrintf("\n"); } else { - RTPrintf("clipboard: data-requested"); - shclPrintEventInfoHuman(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, "data-requested", pszAction); if (SUCCEEDED(hrc)) - RTPrintf(" request-id=%RU32 action=%s source=%s format=%s", - (uint32_t)uRequestId, shclActionToString(enmAction), - ShClHlpSourceToString(enmSource), strMimeType.c_str()); + { + RTPrintf(" request-id=%RU32 format=", (uint32_t)uRequestId); + shclHandleListenQuotedString(strMimeType.c_str()); + } + if (fHaveText) + { + RTPrintf(" size=%zu data=\"", aItemBuffer.size()); + ShClHlpPrintEscapedString(g_pStdOut, pszText, cchText); + RTStrmPutCh(g_pStdOut, '"'); + if (fTextTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-truncated=true")); + } RTPrintf("\n"); } + + RTStrFree(pszText); } @@ -1632,16 +1834,13 @@ static void shclHandleListenPrintTransferEvent(CLIPBOARDLISTENFMT enmFormat, if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"transfer\"")); - shclPrintEventInfoJson(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + SUCCEEDED(hrcTransfer) ? ShClHlpSourceToString(enmSource) : NULL, + "transfer", SUCCEEDED(hrcTransfer) ? shclActionToString(enmAction) : NULL); if (SUCCEEDED(hrcTransfer)) { RTStrmPrintf(g_pStdOut, ",\"id\":%RU32,\"direction\":", (uint32_t)idTransfer); shclHandleListenJsonString(shclTransferDirectionToString(enmDirection)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"source\":")); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"action\":")); - shclHandleListenJsonString(shclActionToString(enmAction)); } if (SUCCEEDED(hrcEvent)) { @@ -1660,12 +1859,12 @@ static void shclHandleListenPrintTransferEvent(CLIPBOARDLISTENFMT enmFormat, } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"transfer\""); - shclPrintEventInfoMachineReadable(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + SUCCEEDED(hrcTransfer) ? ShClHlpSourceToString(enmSource) : NULL, + "transfer", SUCCEEDED(hrcTransfer) ? shclActionToString(enmAction) : NULL); if (SUCCEEDED(hrcTransfer)) - RTPrintf(" id=\"%RU32\" direction=\"%s\" source=\"%s\" action=\"%s\"", - (uint32_t)idTransfer, shclTransferDirectionToString(enmDirection), - ShClHlpSourceToString(enmSource), shclActionToString(enmAction)); + RTPrintf(" id=\"%RU32\" direction=\"%s\"", + (uint32_t)idTransfer, shclTransferDirectionToString(enmDirection)); if (SUCCEEDED(hrcEvent)) { RTPrintf(" state=\"%s\" interaction=\"%s\" path=", @@ -1678,12 +1877,11 @@ static void shclHandleListenPrintTransferEvent(CLIPBOARDLISTENFMT enmFormat, } else { - RTPrintf("clipboard: transfer"); - shclPrintEventInfoHuman(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + SUCCEEDED(hrcTransfer) ? ShClHlpSourceToString(enmSource) : NULL, + "transfer", SUCCEEDED(hrcTransfer) ? shclActionToString(enmAction) : NULL); if (SUCCEEDED(hrcTransfer)) - RTPrintf(" id=%RU32 direction=%s source=%s action=%s", - (uint32_t)idTransfer, shclTransferDirectionToString(enmDirection), - ShClHlpSourceToString(enmSource), shclActionToString(enmAction)); + RTPrintf(" id=%RU32 direction=%s", (uint32_t)idTransfer, shclTransferDirectionToString(enmDirection)); if (SUCCEEDED(hrcEvent)) { RTPrintf(" state=%s interaction=%s path=", @@ -1703,29 +1901,19 @@ static void shclHandleListenPrintTransferEvent(CLIPBOARDLISTENFMT enmFormat, * @param enmFormat Output format. * @param pszEvent Event name. * @param pEventInfo Optional event identity fields. + * @param pListenState Listen state providing the current source, optional. */ static void shclHandleListenPrintSimpleEvent(CLIPBOARDLISTENFMT enmFormat, const char *pszEvent, - const SHCLCLIPBOARDEVENTINFO *pEventInfo) + const SHCLCLIPBOARDEVENTINFO *pEventInfo, + const SHCLHANDLELISTENSTATE *pListenState) { + const char *pszSource = pListenState && pListenState->fHaveSource + ? ShClHlpSourceToString(pListenState->enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, pszEvent, NULL /* pszAction */); if (enmFormat == CLIPBOARDLISTENFMT_JSON) - { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":")); - shclHandleListenJsonString(pszEvent); - shclPrintEventInfoJson(pEventInfo); RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); - } - else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) - { - RTPrintf("event=\"%s\"", pszEvent); - shclPrintEventInfoMachineReadable(pEventInfo); - RTPrintf("\n"); - } else - { - RTPrintf("clipboard: %s", pszEvent); - shclPrintEventInfoHuman(pEventInfo); RTPrintf("\n"); - } } @@ -1735,9 +1923,11 @@ static void shclHandleListenPrintSimpleEvent(CLIPBOARDLISTENFMT enmFormat, const * @param enmFormat Output format. * @param ptrEvent Event object to print. * @param ptrSession Clipboard session for extra-verbose diagnostic reads. + * @param pListenState Listen state to update. */ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPtr &ptrEvent, - const ComPtr &ptrSession) + const ComPtr &ptrSession, + SHCLHANDLELISTENSTATE *pListenState) { VBoxEventType_T enmType; HRESULT hrc = ptrEvent->COMGETTER(Type)(&enmType); @@ -1753,34 +1943,24 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt { ComPtr ptrSourceEvent = ptrEvent; ClipboardSource_T enmSource = ClipboardSource_Host; - ptrSourceEvent->COMGETTER(ClipboardSource)(&enmSource); - if (enmFormat == CLIPBOARDLISTENFMT_JSON) + HRESULT const hrcSource = ptrSourceEvent->COMGETTER(ClipboardSource)(&enmSource); + if (SUCCEEDED(hrcSource) && pListenState) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"source-changed\"")); - shclPrintEventInfoJson(&EventInfo); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"source\":")); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); - } - else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) - { - RTPrintf("event=\"source-changed\""); - shclPrintEventInfoMachineReadable(&EventInfo); - RTPrintf(" source=\"%s\"\n", ShClHlpSourceToString(enmSource)); + pListenState->fHaveSource = true; + pListenState->enmSource = enmSource; } + const char *pszSource = SUCCEEDED(hrcSource) ? ShClHlpSourceToString(enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, &EventInfo, pszSource, "source-changed", NULL /* pszAction */); + if (enmFormat == CLIPBOARDLISTENFMT_JSON) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); else - { - RTPrintf("clipboard: source-changed"); - shclPrintEventInfoHuman(&EventInfo); - RTPrintf(" source=%s\n", ShClHlpSourceToString(enmSource)); - } + RTPrintf("\n"); break; } case VBoxEventType_OnClipboardFormatChanged: { ComPtr ptrFormatEvent = ptrEvent; - shclHandleListenPrintFormatChangedEvent(enmFormat, ptrFormatEvent, &EventInfo); - shclHandleListenVerboseReadFormatEventGuestData("listen", ptrSession, ptrFormatEvent); + shclHandleListenPrintFormatChangedEvent(enmFormat, ptrFormatEvent, ptrSession, &EventInfo, pListenState); break; } case VBoxEventType_OnClipboardDataChanged: @@ -1788,13 +1968,17 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt ComPtr ptrDataEvent = ptrEvent; ComPtr ptrItem; ptrDataEvent->COMGETTER(Item)(ptrItem.asOutParam()); - shclHandleListenPrintEventItem(enmFormat, "data-changed", ptrItem, g_uVerbosity > 1, &EventInfo); + ClipboardAction_T enmAction = ClipboardAction_Copy; + HRESULT const hrcAction = ptrDataEvent->COMGETTER(Action)(&enmAction); + shclHandleListenPrintEventItem(enmFormat, "data-changed", ptrItem, + SUCCEEDED(hrcAction) ? shclActionToString(enmAction) : NULL, + g_uVerbosity > 1, &EventInfo, pListenState); break; } case VBoxEventType_OnClipboardDataRequested: { ComPtr ptrRequestEvent = ptrEvent; - shclHandleListenPrintDataRequestedEvent(enmFormat, ptrRequestEvent, &EventInfo); + shclHandleListenPrintDataRequestedEvent(enmFormat, ptrRequestEvent, &EventInfo, pListenState); break; } case VBoxEventType_OnClipboardTransfer: @@ -1811,25 +1995,26 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt ptrErrorEvent->COMGETTER(Msg)(bstrMsg.asOutParam()); ptrErrorEvent->COMGETTER(RcError)(&rcError); Utf8Str strMsg(bstrMsg); + const char *pszSource = pListenState && pListenState->fHaveSource + ? ShClHlpSourceToString(pListenState->enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, &EventInfo, pszSource, "error", NULL /* pszAction */); if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"error\"")); - shclPrintEventInfoJson(&EventInfo); - RTStrmPrintf(g_pStdOut, ",\"rc\":%ld,\"message\":", rcError); + RTStrmPrintf(g_pStdOut, ",\"rc\":%RI32,\"message\":", (int32_t)rcError); shclHandleListenJsonString(strMsg.c_str()); RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"error\""); - shclPrintEventInfoMachineReadable(&EventInfo); - RTPrintf(" rc=\"%ld\" message=\"%s\"\n", rcError, strMsg.c_str()); + RTPrintf(" rc=\"%RI32\" message=", (int32_t)rcError); + shclHandleListenQuotedString(strMsg.c_str()); + RTPrintf("\n"); } else { - RTPrintf("clipboard: error"); - shclPrintEventInfoHuman(&EventInfo); - RTPrintf(" rc=%ld message=%s\n", rcError, strMsg.c_str()); + RTPrintf(" rc=%RI32 message=", (int32_t)rcError); + shclHandleListenQuotedString(strMsg.c_str()); + RTPrintf("\n"); } break; } @@ -1839,26 +2024,19 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt ClipboardMode_T enmMode = ClipboardMode_Disabled; ptrModeEvent->COMGETTER(ClipboardMode)(&enmMode); const char *pszMode = ShClHlpModeToString(enmMode); + const char *pszSource = pListenState && pListenState->fHaveSource + ? ShClHlpSourceToString(pListenState->enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, &EventInfo, pszSource, "mode-changed", NULL /* pszAction */); if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"mode-changed\"")); - shclPrintEventInfoJson(&EventInfo); RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"mode\":")); shclHandleListenJsonString(pszMode); RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"active\":true}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) - { - RTPrintf("event=\"mode-changed\""); - shclPrintEventInfoMachineReadable(&EventInfo); RTPrintf(" mode=\"%s\" active=\"true\"\n", pszMode); - } else - { - RTPrintf("clipboard: mode-changed"); - shclPrintEventInfoHuman(&EventInfo); - RTPrintf(" mode=%s now-active\n", pszMode); - } + RTPrintf(" mode=%s active=true\n", pszMode); break; } case VBoxEventType_OnClipboardFileTransferModeChanged: @@ -1866,10 +2044,12 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt ComPtr ptrModeEvent = ptrEvent; BOOL fEnabled = FALSE; ptrModeEvent->COMGETTER(Enabled)(&fEnabled); + const char *pszSource = pListenState && pListenState->fHaveSource + ? ShClHlpSourceToString(pListenState->enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, &EventInfo, pszSource, + "file-transfer-mode-changed", NULL /* pszAction */); if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"file-transfer-mode-changed\"")); - shclPrintEventInfoJson(&EventInfo); RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"enabled\":")); if (fEnabled) RTStrmWrite(g_pStdOut, RT_STR_TUPLE("true")); @@ -1878,21 +2058,13 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"active\":true}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) - { - RTPrintf("event=\"file-transfer-mode-changed\""); - shclPrintEventInfoMachineReadable(&EventInfo); RTPrintf(" enabled=\"%s\" active=\"true\"\n", fEnabled ? "true" : "false"); - } else - { - RTPrintf("clipboard: file-transfer-mode-changed"); - shclPrintEventInfoHuman(&EventInfo); - RTPrintf(" enabled=%s now-active\n", fEnabled ? "true" : "false"); - } + RTPrintf(" enabled=%s active=true\n", fEnabled ? "true" : "false"); break; } default: - shclHandleListenPrintSimpleEvent(enmFormat, "unknown", &EventInfo); + shclHandleListenPrintSimpleEvent(enmFormat, "unknown", &EventInfo, pListenState); break; } } @@ -3185,8 +3357,8 @@ static RTEXITCODE shclHandleTransferOffer(HandlerArg *pArg, int argc, char **arg } ComPtr ptrTransfer; - hrc = ptrManager->CreateTransfer(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, - ClipboardAction_Copy, ptrTransfer.asOutParam()); + hrc = ptrManager->Create(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, + ClipboardAction_Copy, ptrTransfer.asOutParam()); if (FAILED(hrc) || ptrTransfer.isNull()) { pArg->session->UnlockMachine(); @@ -3199,22 +3371,19 @@ static RTEXITCODE shclHandleTransferOffer(HandlerArg *pArg, int argc, char **arg hrc = ptrTransfer->SetSourcePaths(ComSafeArrayAsInParam(aSourcePaths)); if (FAILED(hrc)) { + HRESULT const hrcRemove = ptrManager->Remove(ptrTransfer); + if (FAILED(hrcRemove)) + shclVerbose("transfer offer: removing the unconfigured manager transfer failed: %Rhrc", hrcRemove); pArg->session->UnlockMachine(); return RTMsgErrorExit(RTEXITCODE_FAILURE, Clipboard::tr("Configuring clipboard file transfer source paths failed: %Rhrc"), hrc); } - hrc = ptrManager->Add(ptrTransfer); - if (FAILED(hrc)) - { - pArg->session->UnlockMachine(); - return RTMsgErrorExit(RTEXITCODE_FAILURE, Clipboard::tr("Publishing clipboard file transfer failed: %Rhrc"), hrc); - } - ULONG idTransfer = shclGetTransferId(ptrTransfer); if (idTransfer) - shclVerbose("transfer offer: published transfer id=%RU32", (uint32_t)idTransfer); - shclInfo(Clipboard::tr("Published %zu host path(s) as clipboard file transfer."), vecAbsSources.size()); + shclVerbose("transfer offer: configured manager transfer id=%RU32", (uint32_t)idTransfer); + shclInfo(Clipboard::tr("Configured %zu host path(s) on a manager-tracked clipboard file transfer."), + vecAbsSources.size()); RTEXITCODE rcExit = shclWaitForTransferCompletion(ptrClipboardSession, ptrManager, ClipboardTransferDirection_ToGuest, idTransfer, cMsTimeout); @@ -5113,11 +5282,22 @@ static RTEXITCODE shclHandleListen(HandlerArg *pArg, int argc, char **argv) if (rcExit != RTEXITCODE_SUCCESS) return rcExit; + ClipboardMode_T enmMode = ClipboardMode_Disabled; + hrc = shclGetMode(pArg, &enmMode); + if (SUCCEEDED(hrc) && enmMode == ClipboardMode_Disabled) + { + shclHandleListenPrintDisabledWarning(enmOutputFormat); + RTStrmFlush(g_pStdOut); + } + else if (FAILED(hrc)) + shclVerbose("getting the Shared Clipboard mode failed: %Rhrc", hrc); + shclSignalHandlerInstall(); uint64_t const msStart = RTTimeMilliTS(); uint32_t cEvents = 0; bool fTimedOut = false; + SHCLHANDLELISTENSTATE ListenState; while (cEvents < cEventsMax) { if (shclSignalWasCaught()) @@ -5148,7 +5328,7 @@ static RTEXITCODE shclHandleListen(HandlerArg *pArg, int argc, char **argv) continue; shclMarkEventProcessed(ptrEventSource, ptrListener, ptrEvent); - shclHandleListenPrintEvent(enmOutputFormat, ptrEvent, ptrClipboardSession); + shclHandleListenPrintEvent(enmOutputFormat, ptrEvent, ptrClipboardSession, &ListenState); RTStrmFlush(g_pStdOut); cEvents++; } @@ -5156,9 +5336,9 @@ static RTEXITCODE shclHandleListen(HandlerArg *pArg, int argc, char **argv) bool const fInterrupted = shclSignalWasCaught(); shclCleanupListener(ptrEventSource, ptrListener, true /* fSignalHandlerInstalled */); if (fInterrupted) - shclVerbose("listen: interrupted; shutting down"); + shclVerbose("interrupted; shutting down"); else if (fTimedOut) - shclVerbose("listen: timed out after %RU32 ms", cMsTimeout); + shclVerbose("timed out after %RU32 ms", cMsTimeout); return RTEXITCODE_SUCCESS; } @@ -5196,6 +5376,11 @@ RTEXITCODE handleClipboard(HandlerArg *pArg) | HELP_SCOPE_CLIPBOARD_TRANSFER_CANCEL); return shclHandleTransfer(pArg, pArg->argc, pArg->argv); } +#else + if ( !strcmp(pszSubcommand, "set-filetransfers") + || !strcmp(pszSubcommand, "transfer")) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + Clipboard::tr("Clipboard transfer commands are not implemented on this platform.")); #endif if (!strcmp(pszSubcommand, "copy")) { diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp index 3fcb76e1ccef..4287287b1133 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-common.cpp 114830 2026-07-31 10:02:47Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-common.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common helper objects. */ @@ -861,6 +861,62 @@ char *ShClFormatsToStrA(SHCLFORMATS fFormats) } +/********************************************************************************************************************************* +* Shared Clipboard validation * +*********************************************************************************************************************************/ + +/** + * Checks whether a value names exactly one Shared Clipboard format. + * + * @returns true if @a uFmt is a single valid VBOX_SHCL_FMT_XXX bit, false otherwise. + * @param uFmt Format value to validate. + */ +VBGH_DECL(bool) ShClFormatIsValid(SHCLFORMAT uFmt) +{ + return uFmt != VBOX_SHCL_FMT_NONE + && (uFmt & ~VBOX_SHCL_FMT_VALID_MASK) == 0 + && (uFmt & (uFmt - 1)) == 0; +} + + +/** + * Checks whether a Shared Clipboard format mask contains only known format bits. + * + * @returns true if @a fFormats only contains VBOX_SHCL_FMT_XXX bits, false otherwise. + * @param fFormats Format mask to validate. VBOX_SHCL_FMT_NONE is valid. + */ +VBGH_DECL(bool) ShClFormatsAreValid(SHCLFORMATS fFormats) +{ + return (fFormats & ~VBOX_SHCL_FMT_VALID_MASK) == 0; +} + + +/** + * Checks whether a Shared Clipboard transfer direction is valid. + * + * @returns true if @a enmDir is valid, false otherwise. + * @param enmDir Transfer direction to validate. + */ +VBGH_DECL(bool) ShClTransferDirIsValid(SHCLTRANSFERDIR enmDir) +{ + return enmDir == SHCLTRANSFERDIR_FROM_REMOTE + || enmDir == SHCLTRANSFERDIR_TO_REMOTE; +} + + +/** + * Checks whether a Shared Clipboard source is valid. + * + * @returns true if @a enmSource is valid, false otherwise. + * @param enmSource Source to validate. + */ +VBGH_DECL(bool) ShClSourceIsValid(SHCLSOURCE enmSource) +{ + return enmSource == SHCLSOURCE_LOCAL + || enmSource == SHCLSOURCE_REMOTE; +} + + /********************************************************************************************************************************* * Shared Clipboard Cache * *********************************************************************************************************************************/ @@ -1112,7 +1168,7 @@ VBGH_DECL(int) ShClCacheSetMultiple(PSHCLCACHE pCache, SHCLFORMATS uFmts, const AssertPtrReturn(pvData, VERR_INVALID_POINTER); AssertReturn(cbData, VERR_INVALID_PARAMETER); AssertReturn(uFmts != VBOX_SHCL_FMT_NONE, VERR_INVALID_PARAMETER); - AssertReturn(!(uFmts & ~VBOX_SHCL_FMT_VALID_MASK), VERR_INVALID_FLAGS); + AssertReturn(ShClFormatsAreValid(uFmts), VERR_INVALID_FLAGS); int rc = VINF_SUCCESS; SHCLFORMATS uFmtsLeft = uFmts; diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp index 0af574ba3f9b..962f794210ef 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-helper.cpp 114834 2026-07-31 11:53:00Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-helper.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Helper functions. */ @@ -992,22 +992,37 @@ void ShClHlpPrintEscapedString(PRTSTREAM pStrm, const char *pszText, size_t cchT return; AssertPtrReturnVoid(pszText); + size_t offPending = 0; for (size_t i = 0; i < cchText; i++) { unsigned char const ch = (unsigned char)pszText[i]; + const char *pszEscape = NULL; + size_t cchEscape = 0; switch (ch) { - case '\n': RTStrmWrite(pStrm, RT_STR_TUPLE("\\n")); break; - case '\r': RTStrmWrite(pStrm, RT_STR_TUPLE("\\r")); break; - case '\t': RTStrmWrite(pStrm, RT_STR_TUPLE("\\t")); break; - case '\\': RTStrmWrite(pStrm, RT_STR_TUPLE("\\\\")); break; - case '"': RTStrmWrite(pStrm, RT_STR_TUPLE("\\\"")); break; + case '\n': pszEscape = "\\n"; cchEscape = 2; break; + case '\r': pszEscape = "\\r"; cchEscape = 2; break; + case '\t': pszEscape = "\\t"; cchEscape = 2; break; + case '\\': pszEscape = "\\\\"; cchEscape = 2; break; + case '"': pszEscape = "\\\""; cchEscape = 2; break; default: - if (ch >= 0x20) - RTStrmPutCh(pStrm, ch); - else + if (ch < 0x20) + { + if (i > offPending) + RTStrmWrite(pStrm, &pszText[offPending], i - offPending); RTStrmPrintf(pStrm, "\\x%02x", ch); + offPending = i + 1; + } break; } + if (pszEscape) + { + if (i > offPending) + RTStrmWrite(pStrm, &pszText[offPending], i - offPending); + RTStrmWrite(pStrm, pszEscape, cchEscape); + offPending = i + 1; + } } + if (offPending < cchText) + RTStrmWrite(pStrm, &pszText[offPending], cchText - offPending); } diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index e03c377565f8..6fdf50b026ac 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 114770 2026-07-25 11:53:54Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -65,29 +65,43 @@ static PSHCLTRANSFER shClTransferCtxGetTransferByIndexInternal(PSHCLTRANSFERCTX /** - * Returns whether a transfer direction value is valid. + * Checks whether a transfer ID is in the assignable context-local range. + * + * @returns true if the ID can be used by a transfer context, false otherwise. + * @param idTransfer Transfer ID to check before narrowing to SHCLTRANSFERID. */ -static bool shClTransferDirIsValid(SHCLTRANSFERDIR enmDir) +bool ShClTransferIdIsValid(uint32_t idTransfer) { - return enmDir == SHCLTRANSFERDIR_FROM_REMOTE - || enmDir == SHCLTRANSFERDIR_TO_REMOTE; + return idTransfer > 0 + && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1; } /** - * Returns whether a transfer source value is valid. + * Checks whether a transfer key is usable for lifecycle tracking. + * + * @returns true if the key identifies a non-nil service transfer, false otherwise. + * @param idSession Service session ID. + * @param idTransfer Service transfer ID, before narrowing to SHCLTRANSFERID. + * @param uGeneration Service transfer generation. */ -static bool shClTransferSourceIsValid(SHCLSOURCE enmSource) +bool ShClTransferKeyIsValid(SHCLSESSIONID idSession, uint32_t idTransfer, SHCLTRANSFERGEN uGeneration) { - return enmSource == SHCLSOURCE_LOCAL - || enmSource == SHCLSOURCE_REMOTE; + return idSession != 0 + && idSession != NIL_SHCLSESSIONID + && ShClTransferIdIsValid(idTransfer) + && uGeneration != 0 + && uGeneration != NIL_SHCLTRANSFERGEN; } /** - * Returns whether a transfer status value is valid. + * Checks whether a transfer status is part of the Shared Clipboard protocol. + * + * @returns true if the status is valid, false otherwise. + * @param enmStatus Transfer status to validate. */ -static bool shClTransferStatusIsValid(SHCLTRANSFERSTATUS enmStatus) +bool ShClTransferStatusIsValid(SHCLTRANSFERSTATUS enmStatus) { switch (enmStatus) { @@ -108,6 +122,87 @@ static bool shClTransferStatusIsValid(SHCLTRANSFERSTATUS enmStatus) } +/** + * Checks whether a transfer status ends the transfer lifecycle. + * + * @returns true if the status is terminal, false otherwise. + * @param enmStatus Transfer status to classify. + */ +bool ShClTransferStatusIsTerminal(SHCLTRANSFERSTATUS enmStatus) +{ + return enmStatus == SHCLTRANSFERSTATUS_COMPLETED + || enmStatus == SHCLTRANSFERSTATUS_CANCELED + || enmStatus == SHCLTRANSFERSTATUS_KILLED + || enmStatus == SHCLTRANSFERSTATUS_ERROR + || enmStatus == SHCLTRANSFERSTATUS_UNINITIALIZED; +} + + +/** + * Checks whether a transfer status and result form a valid service reply. + * + * @returns true if the status is valid and the result matches it, false otherwise. + * @param enmStatus Transfer status to validate. + * @param rcTransfer Transfer result associated with the status. + */ +bool ShClTransferStatusResultIsValid(SHCLTRANSFERSTATUS enmStatus, int rcTransfer) +{ + if (!ShClTransferStatusIsValid(enmStatus)) + return false; + + switch (enmStatus) + { + case SHCLTRANSFERSTATUS_CANCELED: + return rcTransfer == VERR_CANCELLED; + + case SHCLTRANSFERSTATUS_KILLED: + case SHCLTRANSFERSTATUS_ERROR: + return RT_FAILURE(rcTransfer); + + default: + return RT_SUCCESS(rcTransfer); + } +} + + +/** + * Checks whether a service-reported transfer status may follow the previous + * service-reported status. This describes lifecycle records, not the + * lower-level transfer state mutation sequence. + * + * @returns true if both statuses are valid and the transition is monotonic, false otherwise. + * @param enmOldStatus Current transfer status. + * @param enmNewStatus Incoming transfer status. + */ +bool ShClTransferStatusTransitionIsValid(SHCLTRANSFERSTATUS enmOldStatus, SHCLTRANSFERSTATUS enmNewStatus) +{ + if ( !ShClTransferStatusIsValid(enmOldStatus) + || !ShClTransferStatusIsValid(enmNewStatus)) + return false; + + if (enmOldStatus == enmNewStatus) + return true; + + switch (enmOldStatus) + { + case SHCLTRANSFERSTATUS_REQUESTED: + return enmNewStatus == SHCLTRANSFERSTATUS_INITIALIZED + || ( ShClTransferStatusIsTerminal(enmNewStatus) + && enmNewStatus != SHCLTRANSFERSTATUS_COMPLETED); + + case SHCLTRANSFERSTATUS_INITIALIZED: + return enmNewStatus == SHCLTRANSFERSTATUS_STARTED + || ShClTransferStatusIsTerminal(enmNewStatus); + + case SHCLTRANSFERSTATUS_STARTED: + return ShClTransferStatusIsTerminal(enmNewStatus); + + default: + return false; + } +} + + /** * Returns whether a transfer path is relative in both Unix and DOS path styles. */ @@ -1318,8 +1413,8 @@ static int shClTransferCreateInternal(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSour PSHCLTRANSFER *ppTransfer) { AssertPtrReturn(ppTransfer, VERR_INVALID_POINTER); - AssertReturn(shClTransferDirIsValid(enmDir), VERR_INVALID_PARAMETER); - AssertReturn(shClTransferSourceIsValid(enmSource), VERR_INVALID_PARAMETER); + AssertReturn(ShClTransferDirIsValid(enmDir), VERR_INVALID_PARAMETER); + AssertReturn(ShClSourceIsValid(enmSource), VERR_INVALID_PARAMETER); AssertReturn(cbMaxChunkSize, VERR_INVALID_PARAMETER); AssertReturn(cMaxListHandles, VERR_INVALID_PARAMETER); AssertReturn(cMaxObjHandles, VERR_INVALID_PARAMETER); @@ -1914,7 +2009,7 @@ int ShClTransferSetProvider(PSHCLTRANSFER pTransfer, PSHCLTXPROVIDER pProvider) static int shClTransferSetStatus(PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS enmStatus) { Assert(RTCritSectIsOwner(&pTransfer->CritSect)); - AssertReturn(shClTransferStatusIsValid(enmStatus), VERR_INVALID_PARAMETER); + AssertReturn(ShClTransferStatusIsValid(enmStatus), VERR_INVALID_PARAMETER); #if 0 AssertMsgReturn(pTransfer->State.enmStatus != enmStatus, ("Setting the same status twice in a row (%#x), please report this!\n", enmStatus), VERR_WRONG_ORDER); @@ -3206,9 +3301,7 @@ PSHCLTRANSFER ShClTransferCtxGetTransferByKey(PSHCLTRANSFERCTX pTransferCtx, SHC SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration) { AssertPtrReturn(pTransferCtx, NULL); - AssertReturn(idSession != 0 && idSession != NIL_SHCLSESSIONID, NULL); - AssertReturn(idTransfer != NIL_SHCLTRANSFERID && idTransfer > 0 && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1, NULL); - AssertReturn(uGeneration != 0 && uGeneration != NIL_SHCLTRANSFERGEN, NULL); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), NULL); shClTransferCtxLock(pTransferCtx); @@ -3307,20 +3400,6 @@ uint32_t ShClTransferCtxGetTotalTransfers(PSHCLTRANSFERCTX pTransferCtx) return cTransfers; } -/** - * Checks whether a transfer ID is in the assignable context-local range. - * - * @returns true if the ID can be used by a transfer context, false otherwise. - * @param idTransfer Transfer ID to check. - */ -static bool shClTransferCtxIsValidTransferId(SHCLTRANSFERID idTransfer) -{ - return idTransfer != NIL_SHCLTRANSFERID - && idTransfer > 0 - && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1; -} - - /** * Creates the next non-reserved transfer generation for a locked transfer context. * @@ -3379,7 +3458,7 @@ static int shClTransferCreateIDInternal(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANS } idTransfer++; - if (!shClTransferCtxIsValidTransferId(idTransfer)) + if (!ShClTransferIdIsValid(idTransfer)) idTransfer = 1; } @@ -3423,7 +3502,7 @@ static int shClTransferCtxTransferRegisterExInternal(PSHCLTRANSFERCTX pTransferC AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); Assert(RTCritSectIsOwner(&pTransferCtx->CritSect)); - Assert(shClTransferCtxIsValidTransferId(idTransfer)); + Assert(ShClTransferIdIsValid(idTransfer)); shClTransferLock(pTransfer); pTransfer->State.uID = idTransfer; @@ -3470,7 +3549,7 @@ int ShClTransferCtxRegister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransf SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; /* Shut up MSVC. */ int rc; - if (shClTransferCtxIsValidTransferId(pTransfer->State.uID)) + if (ShClTransferIdIsValid(pTransfer->State.uID)) rc = VERR_ALREADY_EXISTS; else rc = shClTransferCreateIDInternal(pTransferCtx, &idTransfer); @@ -3500,12 +3579,12 @@ int ShClTransferCtxRegisterById(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTr { AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); - AssertReturn(shClTransferCtxIsValidTransferId(idTransfer), VERR_INVALID_PARAMETER); + AssertReturn(ShClTransferIdIsValid(idTransfer), VERR_INVALID_PARAMETER); shClTransferCtxLock(pTransferCtx); int rc; - if (shClTransferCtxIsValidTransferId(pTransfer->State.uID)) + if (ShClTransferIdIsValid(pTransfer->State.uID)) rc = VERR_ALREADY_EXISTS; else if (pTransferCtx->cTransfers < VBOX_SHCL_MAX_TRANSFERS - 2 /* First and last are not used */) { @@ -3555,7 +3634,7 @@ static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransfe Assert(RTCritSectIsOwner(&pTransferCtx->CritSect)); SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); - if (shClTransferCtxIsValidTransferId(idTransfer)) + if (ShClTransferIdIsValid(idTransfer)) ASMBitClear(&pTransferCtx->bmTransferIds[0], idTransfer); RTListNodeRemove(&pTransfer->Node); @@ -3586,7 +3665,7 @@ static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransfe int ShClTransferCtxUnregisterById(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID idTransfer) { AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); - AssertReturn(shClTransferCtxIsValidTransferId(idTransfer), VERR_INVALID_PARAMETER); + AssertReturn(ShClTransferIdIsValid(idTransfer), VERR_INVALID_PARAMETER); shClTransferCtxLock(pTransferCtx); diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardMimeConv.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardMimeConv.cpp index 731fd2446e27..19e147645f58 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardMimeConv.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardMimeConv.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardMimeConv.cpp 114767 2026-07-24 22:06:05Z knut.osmundsen@oracle.com $ */ +/* $Id: tstClipboardMimeConv.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard MIME converter testcase. */ @@ -30,9 +30,13 @@ * Header Files * *********************************************************************************************************************************/ #include +#include #include #include +#include +#include +#include #include #include #include @@ -137,6 +141,74 @@ static void testUriListMapping(void) #endif + +/** + * Tests escaped output containing multibyte UTF-8 on a text-mode stream. + */ +static void testEscapedString(void) +{ + RTTestISub("escaped UTF-8 output"); + + static const char s_szInput[] = + "A\"\\\n\t" + "\xc3\xa9\xe2\x82\xac\xf0\x9f\x98\x80"; + static const char s_szExpected[] = + "A\\\"\\\\\\n\\t" + "\xc3\xa9\xe2\x82\xac\xf0\x9f\x98\x80"; + + char szFilename[RTPATH_MAX]; + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpenTemp(&hFile, szFilename, sizeof(szFilename), + RTFILE_O_CREATE | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTFileOpenTemp failed: %Rrc", rc); + return; + } + rc = RTFileClose(hFile); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTFileClose failed: %Rrc", rc); + RTFileDelete(szFilename); + return; + } + + PRTSTREAM pStrm = NULL; + rc = RTStrmOpen(szFilename, "w", &pStrm); + if (RT_SUCCESS(rc)) + rc = RTStrmSetMode(pStrm, false /* fBinary */, true /* fCurrentCodeSet */); + if (RT_SUCCESS(rc)) + { + ShClHlpPrintEscapedString(pStrm, s_szInput, sizeof(s_szInput) - 1); + rc = RTStrmError(pStrm); + } + if (RT_FAILURE(rc)) + RTTestIFailed("Writing escaped UTF-8 failed: %Rrc", rc); + int const rcClose = RTStrmClose(pStrm); + if (RT_FAILURE(rcClose)) + RTTestIFailed("RTStrmClose failed: %Rrc", rcClose); + + void *pvOutput = NULL; + size_t cbOutput = 0; + rc = RTFileReadAll(szFilename, &pvOutput, &cbOutput); + if (RT_FAILURE(rc)) + RTTestIFailed("RTFileReadAll failed: %Rrc", rc); + else + { + RTTESTI_CHECK_MSG(cbOutput == sizeof(s_szExpected) - 1, + ("cbOutput=%zu expected=%zu\n", cbOutput, sizeof(s_szExpected) - 1)); + if (cbOutput == sizeof(s_szExpected) - 1) + RTTESTI_CHECK_MSG(memcmp(pvOutput, s_szExpected, cbOutput) == 0, + ("output='%.*Rhxs' expected='%.*Rhxs'\n", + cbOutput, pvOutput, sizeof(s_szExpected) - 1, s_szExpected)); + RTFileReadAllFree(pvOutput, cbOutput); + } + + rc = RTFileDelete(szFilename); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +} + + static void testText(RTTEST hTest) { RTTestISub("text"); @@ -336,6 +408,7 @@ int main(int argc, char **argv) return rcExit; RTTestBanner(hTest); + testEscapedString(); testText(hTest); #if 0 /** @todo r=bird: file transfers require a very different approach... */ testUriListMapping(); diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp index c34ce6210b8d..d8d6b86d11e0 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-client.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-client.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Client/session and message queue handling. */ @@ -60,19 +60,6 @@ static int shClSvcClientStateInit(PSHCLCLIENTSTATE pState, uint32_t uClientID); static int shClSvcClientStateTerm(PSHCLCLIENTSTATE pState); static void shClSvcClientStateReset(PSHCLCLIENTSTATE pState); -/** - * Checks whether a value names exactly one Shared Clipboard format. - * - * @returns true if \a uFormat is a single valid VBOX_SHCL_FMT_XXX bit, false otherwise. - * @param uFormat Format value to validate. - */ -static bool shClSvcClientIsValidFormat(SHCLFORMAT uFormat) -{ - return uFormat != VBOX_SHCL_FMT_NONE - && (uFormat & ~VBOX_SHCL_FMT_VALID_MASK) == 0 - && (uFormat & (uFormat - 1)) == 0; -} - #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS static SHCLSESSIONID shClSvcClientAllocSessionId(void) { @@ -1189,7 +1176,7 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Format bit. */ uFormat = paParms[iParm].u.uint32; iParm++; - if (!shClSvcClientIsValidFormat(uFormat)) + if (!ShClFormatIsValid(uFormat)) { LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); return VERR_INVALID_PARAMETER; @@ -1368,4 +1355,3 @@ static void shClSvcClientStateReset(PSHCLCLIENTSTATE pState) pState->Transfers.enmTransferDir = SHCLTRANSFERDIR_UNKNOWN; #endif } - diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp index 16688b2b37d3..701915e1299f 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.cpp 114661 2026-07-08 10:39:13Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal code for transfer (list) handling. */ @@ -71,11 +71,7 @@ static int shClSvcTransferFindByKey(SHCLSESSIONID idSession, SHCLTRANSFERID idTr { AssertPtrReturn(ppClient, VERR_INVALID_POINTER); AssertPtrReturn(ppTransfer, VERR_INVALID_POINTER); - AssertReturn(idSession != 0 && idSession != NIL_SHCLSESSIONID, VERR_INVALID_CONTEXT); - AssertReturn( idTransfer != NIL_SHCLTRANSFERID - && idTransfer > 0 - && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1, VERR_INVALID_CONTEXT); - AssertReturn(uGeneration != 0 && uGeneration != NIL_SHCLTRANSFERGEN, VERR_INVALID_CONTEXT); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), VERR_INVALID_CONTEXT); *ppClient = NULL; *ppTransfer = NULL; diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp index 11eb901e505e..fdee0153ede8 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardTransfers.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardTransfers.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard transfers test case. */ @@ -35,6 +35,26 @@ #include #include #include +#include + + +/** @name TST_SHCL_TRANSFER_STATUS_F_XXX - Testcase transfer status flags. + * + * Each flag uses the numeric SHCLTRANSFERSTATUS value as its bit position. + * This lets expected transition masks name statuses directly and keeps them + * independent of the order of the status array used to exercise the masks. + * @{ */ +#define TST_SHCL_TRANSFER_STATUS_F(a_enmStatus) RT_BIT_32(a_enmStatus) +#define TST_SHCL_TRANSFER_STATUS_F_NONE TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_NONE) +#define TST_SHCL_TRANSFER_STATUS_F_REQUESTED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_REQUESTED) +#define TST_SHCL_TRANSFER_STATUS_F_INITIALIZED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_INITIALIZED) +#define TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_UNINITIALIZED) +#define TST_SHCL_TRANSFER_STATUS_F_STARTED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_STARTED) +#define TST_SHCL_TRANSFER_STATUS_F_COMPLETED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_COMPLETED) +#define TST_SHCL_TRANSFER_STATUS_F_CANCELED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_CANCELED) +#define TST_SHCL_TRANSFER_STATUS_F_KILLED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_KILLED) +#define TST_SHCL_TRANSFER_STATUS_F_ERROR TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_ERROR) +/** @} */ static int testCreateTempDir(RTTEST hTest, const char *pszTestcase, char *pszTempDir, size_t cbTempDir) @@ -57,7 +77,11 @@ static int testCreateTempDir(RTTEST hTest, const char *pszTestcase, char *pszTem rc = RTDirCreateTemp(szTempDir, 0700); RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - rc = RTPathJoin(pszTempDir, cbTempDir, szTempDir, pszTestcase); + char szTempDirReal[RTPATH_MAX]; + rc = RTPathReal(szTempDir, szTempDirReal, sizeof(szTempDirReal)); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + + rc = RTPathJoin(pszTempDir, cbTempDir, szTempDirReal, pszTestcase); RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Created temporary directory: %s\n", pszTempDir); @@ -392,6 +416,105 @@ static void testTransferBasics(void) RTTESTI_CHECK_RC_OK(rc); } +/** + * Tests common Shared Clipboard and transfer validation. + */ +static void testTransferValidation(void) +{ + RTTestISub("Testing Shared Clipboard validation"); + + RTTESTI_CHECK(ShClFormatIsValid(VBOX_SHCL_FMT_UNICODETEXT)); + RTTESTI_CHECK(ShClFormatIsValid(VBOX_SHCL_FMT_BITMAP)); + RTTESTI_CHECK(ShClFormatIsValid(VBOX_SHCL_FMT_HTML)); + RTTESTI_CHECK(ShClFormatIsValid(VBOX_SHCL_FMT_URI_LIST)); + RTTESTI_CHECK(!ShClFormatIsValid(VBOX_SHCL_FMT_NONE)); + RTTESTI_CHECK(!ShClFormatIsValid(VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP)); + RTTESTI_CHECK(!ShClFormatIsValid(VBOX_SHCL_FMT_VALID_MASK + 1)); + + RTTESTI_CHECK(ShClFormatsAreValid(VBOX_SHCL_FMT_NONE)); + RTTESTI_CHECK(ShClFormatsAreValid(VBOX_SHCL_FMT_VALID_MASK)); + RTTESTI_CHECK(!ShClFormatsAreValid(VBOX_SHCL_FMT_VALID_MASK + 1)); + + RTTESTI_CHECK(ShClTransferDirIsValid(SHCLTRANSFERDIR_FROM_REMOTE)); + RTTESTI_CHECK(ShClTransferDirIsValid(SHCLTRANSFERDIR_TO_REMOTE)); + RTTESTI_CHECK(!ShClTransferDirIsValid(SHCLTRANSFERDIR_UNKNOWN)); + RTTESTI_CHECK(!ShClTransferDirIsValid(SHCLTRANSFERDIR_32BIT_HACK)); + + RTTESTI_CHECK(ShClSourceIsValid(SHCLSOURCE_LOCAL)); + RTTESTI_CHECK(ShClSourceIsValid(SHCLSOURCE_REMOTE)); + RTTESTI_CHECK(!ShClSourceIsValid(SHCLSOURCE_INVALID)); + RTTESTI_CHECK(!ShClSourceIsValid(SHCLSOURCE_32BIT_HACK)); + + RTTESTI_CHECK(ShClTransferIdIsValid(1)); + RTTESTI_CHECK(ShClTransferIdIsValid(VBOX_SHCL_MAX_TRANSFERS - 2)); + RTTESTI_CHECK(!ShClTransferIdIsValid(0)); + RTTESTI_CHECK(!ShClTransferIdIsValid(VBOX_SHCL_MAX_TRANSFERS - 1)); + RTTESTI_CHECK(!ShClTransferIdIsValid(NIL_SHCLTRANSFERID)); + RTTESTI_CHECK(!ShClTransferIdIsValid(UINT32_MAX)); + + RTTESTI_CHECK(ShClTransferKeyIsValid(1, 1, 1)); + RTTESTI_CHECK(ShClTransferKeyIsValid(1, VBOX_SHCL_MAX_TRANSFERS - 2, 1)); + RTTESTI_CHECK(!ShClTransferKeyIsValid(0, 1, 1)); + RTTESTI_CHECK(!ShClTransferKeyIsValid(NIL_SHCLSESSIONID, 1, 1)); + RTTESTI_CHECK(!ShClTransferKeyIsValid(1, 0, 1)); + RTTESTI_CHECK(!ShClTransferKeyIsValid(1, VBOX_SHCL_MAX_TRANSFERS - 1, 1)); + RTTESTI_CHECK(!ShClTransferKeyIsValid(1, NIL_SHCLTRANSFERID, 1)); + RTTESTI_CHECK(!ShClTransferKeyIsValid(1, UINT32_MAX, 1)); + RTTESTI_CHECK(!ShClTransferKeyIsValid(1, 1, 0)); + RTTESTI_CHECK(!ShClTransferKeyIsValid(1, 1, NIL_SHCLTRANSFERGEN)); + + /** Expected classification and transitions for each transfer status. */ + static struct + { + /** Transfer status under test. */ + SHCLTRANSFERSTATUS enmStatus; + /** Whether the status is terminal. */ + bool fTerminal; + /** Flags identifying statuses to which the status may transition. */ + uint32_t fTransitions; + } const s_aStatusTests[] = + { + { SHCLTRANSFERSTATUS_NONE, false, TST_SHCL_TRANSFER_STATUS_F_NONE }, + { SHCLTRANSFERSTATUS_REQUESTED, false, TST_SHCL_TRANSFER_STATUS_F_REQUESTED | TST_SHCL_TRANSFER_STATUS_F_INITIALIZED | TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED | TST_SHCL_TRANSFER_STATUS_F_CANCELED | TST_SHCL_TRANSFER_STATUS_F_KILLED | TST_SHCL_TRANSFER_STATUS_F_ERROR }, + { SHCLTRANSFERSTATUS_INITIALIZED, false, TST_SHCL_TRANSFER_STATUS_F_INITIALIZED | TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED | TST_SHCL_TRANSFER_STATUS_F_STARTED | TST_SHCL_TRANSFER_STATUS_F_COMPLETED | TST_SHCL_TRANSFER_STATUS_F_CANCELED | TST_SHCL_TRANSFER_STATUS_F_KILLED | TST_SHCL_TRANSFER_STATUS_F_ERROR }, + { SHCLTRANSFERSTATUS_UNINITIALIZED, true, TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED }, + { SHCLTRANSFERSTATUS_STARTED, false, TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED | TST_SHCL_TRANSFER_STATUS_F_STARTED | TST_SHCL_TRANSFER_STATUS_F_COMPLETED | TST_SHCL_TRANSFER_STATUS_F_CANCELED | TST_SHCL_TRANSFER_STATUS_F_KILLED | TST_SHCL_TRANSFER_STATUS_F_ERROR }, + { SHCLTRANSFERSTATUS_COMPLETED, true, TST_SHCL_TRANSFER_STATUS_F_COMPLETED }, + { SHCLTRANSFERSTATUS_CANCELED, true, TST_SHCL_TRANSFER_STATUS_F_CANCELED }, + { SHCLTRANSFERSTATUS_KILLED, true, TST_SHCL_TRANSFER_STATUS_F_KILLED }, + { SHCLTRANSFERSTATUS_ERROR, true, TST_SHCL_TRANSFER_STATUS_F_ERROR } + }; + + for (size_t i = 0; i < RT_ELEMENTS(s_aStatusTests); ++i) + { + SHCLTRANSFERSTATUS const enmStatus = s_aStatusTests[i].enmStatus; + RTTESTI_CHECK(ShClTransferStatusIsValid(enmStatus)); + RTTESTI_CHECK(ShClTransferStatusIsTerminal(enmStatus) == s_aStatusTests[i].fTerminal); + + bool const fFailureStatus = enmStatus == SHCLTRANSFERSTATUS_KILLED + || enmStatus == SHCLTRANSFERSTATUS_ERROR; + RTTESTI_CHECK(ShClTransferStatusResultIsValid(enmStatus, VINF_SUCCESS) + == ( !fFailureStatus + && enmStatus != SHCLTRANSFERSTATUS_CANCELED)); + RTTESTI_CHECK(ShClTransferStatusResultIsValid(enmStatus, VERR_GENERAL_FAILURE) == fFailureStatus); + RTTESTI_CHECK(ShClTransferStatusResultIsValid(enmStatus, VERR_CANCELLED) + == (fFailureStatus || enmStatus == SHCLTRANSFERSTATUS_CANCELED)); + + for (size_t j = 0; j < RT_ELEMENTS(s_aStatusTests); ++j) + RTTESTI_CHECK(ShClTransferStatusTransitionIsValid(enmStatus, s_aStatusTests[j].enmStatus) + == RT_BOOL(s_aStatusTests[i].fTransitions + & TST_SHCL_TRANSFER_STATUS_F(s_aStatusTests[j].enmStatus))); + } + + SHCLTRANSFERSTATUS const enmInvalid = UINT32_C(0xfeed); + RTTESTI_CHECK(!ShClTransferStatusIsValid(SHCLTRANSFERSTATUS_32BIT_SIZE_HACK)); + RTTESTI_CHECK(!ShClTransferStatusIsValid(enmInvalid)); + RTTESTI_CHECK(!ShClTransferStatusIsTerminal(enmInvalid)); + RTTESTI_CHECK(!ShClTransferStatusResultIsValid(enmInvalid, VINF_SUCCESS)); + RTTESTI_CHECK(!ShClTransferStatusTransitionIsValid(enmInvalid, enmInvalid)); + RTTESTI_CHECK(!ShClTransferStatusTransitionIsValid(SHCLTRANSFERSTATUS_REQUESTED, enmInvalid)); +} + /** * Tests zero-length object data chunk duplication. */ @@ -1013,6 +1136,7 @@ int main(int argc, char *argv[]) testPathSanitize(); testEvents(); testTransferBasics(); + testTransferValidation(); testTransferObjDataChunkDupZeroLength(); testTransferContextIdentity(); testTransferResetClosesObjectHandles(hTest); diff --git a/src/VBox/Main/idl/VirtualBox.xidl b/src/VBox/Main/idl/VirtualBox.xidl index 6fc7c6558893..3b54d2f58e74 100644 --- a/src/VBox/Main/idl/VirtualBox.xidl +++ b/src/VBox/Main/idl/VirtualBox.xidl @@ -1763,7 +1763,10 @@ - Include clipboard payload items in session data-change events when payload data is available. + + Include clipboard payload items in session data-change and data-requested + events when payload data is available. + @@ -15853,14 +15856,14 @@ - Unique identifier used to distinguish clipboard transfers. + Unique identifier used to distinguish clipboard items. - Source clipboard identifier. For X11 based clipboards, values can be used to distinguish PRIMARY, - SECONDARY and CLIPBOARD selections. + Clipboard source which owns or supplied the item, such as the host, + guest, a remote clipboard source, or a custom source. @@ -16261,13 +16264,16 @@ Examples:
    -
  • Calling setSourcePaths(["/home/user/report.txt"]) publishes - the local file as a transfer root such as report.txt.
  • -
  • Calling setSourcePaths(["/home/user/photos"]) publishes the - local directory as a root such as photos; directory contents are - then addressed as transfer-relative paths below that root.
  • -
  • Calling setSourcePaths(["/tmp/a.txt", "/tmp/b.txt"]) creates - one transfer with two roots, not two independent transfers.
  • +
  • Calling setSourcePaths(["/home/user/report.txt"]) configures + the local file as a root such as report.txt in this + manager-owned transfer tree.
  • +
  • Calling setSourcePaths(["/home/user/photos"]) configures the + local directory as a root such as photos in this manager-owned + transfer tree; directory contents are then addressed as + transfer-relative paths below that root.
  • +
  • Calling setSourcePaths(["/tmp/a.txt", "/tmp/b.txt"]) + configures one transfer with two roots, not two independent + transfers.
Empty path entries, entries containing transfer-list separators, and unsupported @@ -16286,7 +16292,7 @@ The returned values are the same kind of local paths accepted by . They are useful for a local producer or management UI that wants to show or verify what local objects - were offered. They are not the portable transfer names used by the receiver; + were configured. They are not the portable transfer names used by the receiver; use to discover those names. For example, a local host-to-guest transfer may return @@ -16304,12 +16310,19 @@ Clipboard transfer manager. + + API clients obtain clipboard transfers from + or + . The manager's + control methods accept only transfers owned by that manager; + IClipboardTransfer is not a caller-implemented extension point. + @@ -16329,48 +16342,97 @@ - + - Adds a clipboard transfer to the manager. + Creates a Main-owned clipboard transfer and immediately tracks it in + this manager. The returned transfer can be configured, for example by + calling , and is visible + through . + + Manager ownership and tracking do not publish the transfer to the + active Shared Clipboard service client or platform backend. This + method does not currently offer a Main-created transfer to the guest. + - - Clipboard transfer to add. + + Transfer direction. Use ToGuest for host-local source paths. + + + Clipboard source owning the new transfer. + + + Clipboard action associated with the new transfer. + + + Created manager-owned transfer. Removes a clipboard transfer from the manager. + + The transfer is not owned by this manager or is no longer tracked. + + + The transfer is backed by an active Shared Clipboard service + operation. Cancel it before removing it. + - Clipboard transfer to remove. + Manager-owned clipboard transfer to remove. Cancels a clipboard transfer. + + The transfer is not owned by this manager or is no longer tracked. + - Clipboard transfer to cancel. + Manager-owned clipboard transfer to cancel. - Approves a transfer waiting for client approval. + Reserved for approving a transfer waiting for client approval. + + Currently not implemented. No production interaction-response + transport exists. For a transfer owned by this manager, this method + returns E_NOTIMPL. + + + Responding to transfer interactions is not implemented. + + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer to approve. - Reserved approval flags. Must currently be zero. + Reserved approval flags for a future implementation. - Denies a transfer waiting for client approval. + Reserved for denying a transfer waiting for client approval. + + Currently not implemented. No production interaction-response + transport exists. For a transfer owned by this manager, this method + returns E_NOTIMPL. + + + Responding to transfer interactions is not implemented. + + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer to deny. @@ -16382,7 +16444,18 @@ - Supplies a response to a transfer interaction request. + Reserved for supplying a response to a transfer interaction request. + + Currently not implemented. No production interaction-response + transport exists. For a transfer owned by this manager, this method + returns E_NOTIMPL. + + + Responding to transfer interactions is not implemented. + + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer waiting for interaction. @@ -16404,7 +16477,7 @@ - Reserved response flags. Must currently be zero. + Reserved response flags for a future implementation. @@ -16417,6 +16490,9 @@ The method is not implemented yet. + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer to pause. @@ -16432,6 +16508,9 @@ The method is not implemented yet. + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer to resume. @@ -16440,32 +16519,19 @@ - Resets all clipboard transfers tracked by the manager. + Resets all Main-created clipboard transfers tracked by the manager only + when no service transfer is active. If a service transfer is active, + this method returns VBOX_E_OBJECT_IN_USE without modifying the + manager because removing Main records without first canceling the + host-service transfer would desynchronize the public and service + lifecycles. + + A service transfer is active. Cancel that transfer before resetting + the manager. + - - - Creates an unpublished clipboard transfer owned by Main. The returned - transfer can be configured, for example by calling - , and then published with - . The transfer is not visible - through until it is - added. - - - Transfer direction. Use ToGuest for host-local source paths. - - - Clipboard source owning the new transfer. - - - Clipboard action associated with the new transfer. - - - Created unpublished transfer. - - for the Main clipboard API overview and the role of this endpoint. When returned by , operations are associated with that session's client identifier. + + Calls to , + , and + are best-effort when the Shared + Clipboard service exists but no active native clipboard client or backend + is connected. In that state, they can return success without changing the + visible operating-system clipboard. @@ -16487,8 +16560,10 @@ Reports clipboard formats from the specified source to the native host clipboard. This also makes those formats the current generic VM clipboard offer for the supplied source, so a later native host data request can be matched - with and completed through - . + with and correlated with + . Because the request event is + non-waitable, a response dispatched later by a passive listener is not + guaranteed to satisfy the original native host read. Clipboard action associated with the reported formats. @@ -16513,6 +16588,9 @@ request; for a session endpoint, the session client identifier must match as well. The supplied action, source and MIME type must match the request and the currently advertised native host clipboard formats. + Because the matching data-request event is non-waitable, a response + dispatched later by a passive listener is not guaranteed to satisfy the + original native host read. Identifier from the corresponding clipboard data request event. @@ -16760,9 +16838,11 @@ , which also updates the generic VM clipboard offer for the guest source. When a native host application later requests one of those formats, Main emits an - and the client completes - the request with . For eager - publication, a client that already has the payload can call + whose request identifier can + be correlated with . The event + is non-waitable, so a later passive response is not guaranteed to + satisfy the original read. For eager publication, a client that + already has the payload can call directly. @@ -17012,7 +17092,7 @@ Identifier of the pending clipboard data request. + + + + Sets a Shared Clipboard service transfer status on the live console + clipboard. This is an internal testcase seam only. + + + Shared Clipboard service session identifier. + + + Shared Clipboard transfer identifier. + + + Host-private transfer generation. + + + + Data source associated with the status. A backing transfer, when + present, must record the same source and remains authoritative for the + transfer direction. + + + + Internal Shared Clipboard transfer status value. + + + IPRT result associated with the status. + + Clipboard item associated with the event, or NULL when no payload item is - included. Session data-change events include this item only when the - session was created with IncludePayload. + included. Session data-change and data-requested events include this item + only when the session was created with IncludePayload. Data-requested + events on the main clipboard event source do not include a payload item. @@ -30725,13 +30835,28 @@ Snapshot 1 (B.vdi) Snapshot 1 (B.vdi) by the native host clipboard backend. An API client may use this event to decide whether and how to supply the requested clipboard data. - The inherited item attribute may be NULL or may contain implementation - specific request context. The request details are provided by the - attributes below. + This event is non-waitable. The requesting backend does not wait for + , and a response dispatched later + by a passive listener is not guaranteed to satisfy the original read. + Native host requests use a one-shot request identifier that can be + correlated with . + + For a host-text request delivered on a session event source created with + IncludePayload, the inherited item attribute contains complete host text + data available to Main when the session event is generated. This can be a + native host snapshot or the current Main clipboard item. It is NULL when + no complete data is available, on the main clipboard event source, and on + session event sources without IncludePayload. An event handler can still + supply replacement data, so this item does not necessarily contain the + bytes ultimately returned to the guest. The request details are provided + by the attributes below. - Unique identifier for this clipboard data request. + Unique identifier for this clipboard data request. For a native host + request, this is a non-zero, one-shot identifier. Supply it through the + host clipboard endpoint that owns the request; it cannot be reused after + the matching request is completed or becomes stale. diff --git a/src/VBox/Main/include/ClipboardImpl.h b/src/VBox/Main/include/ClipboardImpl.h index 3eda1ef6b7d2..aace68f7cc33 100644 --- a/src/VBox/Main/include/ClipboardImpl.h +++ b/src/VBox/Main/include/ClipboardImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Console clipboard API. */ @@ -102,9 +102,22 @@ class ATL_NO_VTABLE Clipboard : #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS HRESULT i_transferCancel(ULONG aTransferId); HRESULT i_transferCancel(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration); + /** + * Handles a Shared Clipboard transfer lifecycle status delivered by the host service. + * + * @returns COM status code. + * @param aServiceSessionId Shared Clipboard service session identifier. + * @param aTransferId Shared Clipboard transfer identifier. + * @param aGeneration Host-private transfer generation. + * @param aTransfer Borrowed service transfer backing the data plane. + * @param enmShClSource Data source recorded by the backing transfer. + * @param enmStatus Transfer lifecycle status. + * @param vrcTransfer Transfer status result code. + */ HRESULT i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration, + PSHCLTRANSFER aTransfer, SHCLSOURCE enmShClSource, SHCLTRANSFERSTATUS enmStatus, int vrcTransfer); @@ -201,6 +214,23 @@ class ATL_NO_VTABLE Clipboard : * @{ */ HRESULT requestData(const com::Utf8Str &aMimeType, ULONG *aRequestId); + /** + * Sets a Shared Clipboard transfer status for Main testcase coverage. + * + * @returns COM status code. + * @param aServiceSessionId Shared Clipboard service session identifier. + * @param aTransferId Shared Clipboard transfer identifier. + * @param aGeneration Host-private transfer generation. + * @param aSource Data source associated with the status. + * @param aStatus Internal Shared Clipboard transfer status value. + * @param aResult IPRT result associated with the status. + */ + HRESULT setTransferStatus(ULONG aServiceSessionId, + ULONG aTransferId, + LONG64 aGeneration, + ClipboardSource_T aSource, + ULONG aStatus, + LONG aResult); /** @} */ HRESULT i_createFormat(const com::Utf8Str &aMimeType, ComPtr &aFormat); @@ -233,7 +263,8 @@ class ATL_NO_VTABLE Clipboard : ULONG i_fireDataRequested(VBOXSHCLMAINCLIENTID aClientId, ClipboardAction_T aAction, ClipboardSource_T aSource, - uint32_t uFormat); + uint32_t uFormat, + const std::vector *pResolvedBuffer = NULL); bool i_isClientIdRegisteredLocked(VBOXSHCLMAINCLIENTID aClientId) const; bool i_isClientFormatOwnerLocked(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFormats, ClipboardSource_T aSource) const; VBOXSHCLMAINCLIENTID i_allocateClientId(); diff --git a/src/VBox/Main/include/ClipboardTransferDataImpl.h b/src/VBox/Main/include/ClipboardTransferDataImpl.h index 1ec2e579f7ef..0bb6873dcc27 100644 --- a/src/VBox/Main/include/ClipboardTransferDataImpl.h +++ b/src/VBox/Main/include/ClipboardTransferDataImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferDataImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferDataImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer data plane object. */ @@ -95,10 +95,10 @@ class ATL_NO_VTABLE ClipboardTransferData : #endif { } - /** Parent transfer object used to keep the backing transfer alive. */ + /** Parent transfer object used to preserve the public object relationship. */ ComPtr mParent; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /** Shared Clipboard transfer backing data-plane operations. */ + /** Parent-owned Shared Clipboard transfer backing data-plane operations. */ PSHCLTRANSFER mTransfer; #endif } mData; diff --git a/src/VBox/Main/include/ClipboardTransferDirectoryImpl.h b/src/VBox/Main/include/ClipboardTransferDirectoryImpl.h index 9347f326c611..dcaeea989d62 100644 --- a/src/VBox/Main/include/ClipboardTransferDirectoryImpl.h +++ b/src/VBox/Main/include/ClipboardTransferDirectoryImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferDirectoryImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferDirectoryImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer directory handle. */ @@ -35,6 +35,8 @@ #include +class ClipboardTransfer; + /** * Clipboard transfer directory handle. */ @@ -49,7 +51,7 @@ class ATL_NO_VTABLE ClipboardTransferDirectory : void FinalRelease(); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - HRESULT init(const ComPtr &aParent, + HRESULT init(const ComObjPtr &aParent, PSHCLTRANSFER aTransfer, const com::Utf8Str &aPath, SHCLLISTHANDLE aHandle); @@ -81,11 +83,13 @@ class ATL_NO_VTABLE ClipboardTransferDirectory : , mStatus(DirectoryStatus_Undefined) { } - ComPtr mParent; - PSHCLTRANSFER mTransfer; - SHCLLISTHANDLE mHandle; - com::Utf8Str mPath; - DirectoryStatus_T mStatus; + /** Concrete parent transfer object keeping the borrowed transfer alive. */ + ComObjPtr mParent; + /** Parent-owned Shared Clipboard transfer owning mHandle. */ + PSHCLTRANSFER mTransfer; + SHCLLISTHANDLE mHandle; + com::Utf8Str mPath; + DirectoryStatus_T mStatus; } mData; }; diff --git a/src/VBox/Main/include/ClipboardTransferFileImpl.h b/src/VBox/Main/include/ClipboardTransferFileImpl.h index 6b2ae1a6e168..f6e3d067fbab 100644 --- a/src/VBox/Main/include/ClipboardTransferFileImpl.h +++ b/src/VBox/Main/include/ClipboardTransferFileImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferFileImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferFileImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer file handle. */ @@ -114,7 +114,9 @@ class ATL_NO_VTABLE ClipboardTransferFile : , mCreationMode(0) { RT_ZERO(mInfo); } + /** Parent transfer object. */ ComPtr mParent; + /** Retained Shared Clipboard transfer owning mHandle. */ PSHCLTRANSFER mTransfer; SHCLOBJHANDLE mHandle; com::Utf8Str mPath; diff --git a/src/VBox/Main/include/ClipboardTransferImpl.h b/src/VBox/Main/include/ClipboardTransferImpl.h index 570574c66322..5147e8b98f4f 100644 --- a/src/VBox/Main/include/ClipboardTransferImpl.h +++ b/src/VBox/Main/include/ClipboardTransferImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer object. */ @@ -66,8 +66,32 @@ class ATL_NO_VTABLE ClipboardTransfer : void uninit(); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /** Returns the backing Shared Clipboard transfer. */ + /** Returns the parent-owned backing Shared Clipboard transfer. */ PSHCLTRANSFER i_getTransfer() const; + /** + * Lists nodes from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aPath Transfer-relative directory path, or empty for roots. + * @param aFlags ClipboardTransferListFlag mask. + * @param aNodes Where to return listed nodes. + */ + HRESULT i_list(PSHCLTRANSFER pTransfer, + const com::Utf8Str &aPath, + ULONG aFlags, + std::vector > &aNodes); + /** + * Queries one node from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aPath Transfer-relative path. + * @param aNode Where to return the node information. + */ + HRESULT i_query(PSHCLTRANSFER pTransfer, + const com::Utf8Str &aPath, + ComPtr &aNode); #endif /** Updates the public transfer state. */ void i_setState(ClipboardTransferState_T aState, @@ -108,6 +132,18 @@ class ATL_NO_VTABLE ClipboardTransfer : HRESULT createDirectory(const com::Utf8Str &aPath); /** @} */ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** + * Returns root nodes from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aNodes Where to return the root nodes. + */ + HRESULT i_roots(PSHCLTRANSFER pTransfer, + std::vector > &aNodes); +#endif + struct Data { /** Unique transfer identifier. */ @@ -131,9 +167,12 @@ class ATL_NO_VTABLE ClipboardTransfer : #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** Source-side local paths explicitly configured for this transfer. */ std::vector mSourcePaths; - /** Shared Clipboard transfer backing data-plane operations. Optional. */ + /** Parent-owned Shared Clipboard transfer backing data-plane operations. Optional. */ PSHCLTRANSFER mTransfer; - /** Whether this object owns and destroys mTransfer. */ + /** + * Whether this object owns and destroys mTransfer. Otherwise the + * object keeps its parent alive while using this borrowed transfer. + */ bool mfOwnTransfer; #endif } mData; diff --git a/src/VBox/Main/include/ClipboardTransferManagerImpl.h b/src/VBox/Main/include/ClipboardTransferManagerImpl.h index 2c0c8d54a8a3..ac23f64b0e55 100644 --- a/src/VBox/Main/include/ClipboardTransferManagerImpl.h +++ b/src/VBox/Main/include/ClipboardTransferManagerImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferManagerImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferManagerImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer manager object. */ @@ -32,6 +32,7 @@ #endif #include "ClipboardTransferManagerWrap.h" +#include "ClipboardTransferImpl.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include @@ -57,12 +58,25 @@ class ATL_NO_VTABLE ClipboardTransferManager : HRESULT init(IEventSource *aEventSource = NULL, Clipboard *aParent = NULL); void uninit(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** Resets the internally tracked transfer list. */ void i_reset(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** + * Handles a Shared Clipboard transfer lifecycle status delivered by the host service. + * + * @returns COM status code. + * @param aServiceSessionId Service session that owns the transfer. + * @param aTransferId Shared Clipboard transfer identifier. + * @param aGeneration Host-private transfer generation. + * @param aTransfer Borrowed service transfer used to validate status metadata. + * @param enmShClSource Data source recorded by the backing transfer. + * @param enmStatus Transfer lifecycle status. + * @param vrcTransfer Transfer result code associated with the status. + */ HRESULT i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration, + PSHCLTRANSFER aTransfer, SHCLSOURCE enmShClSource, SHCLTRANSFERSTATUS enmStatus, int vrcTransfer); @@ -71,23 +85,33 @@ class ATL_NO_VTABLE ClipboardTransferManager : private: - void i_fireTransferEvent(IClipboardTransfer *aTransfer, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + void i_fireTransferEvent(const ComObjPtr &aTransfer, ClipboardTransferState_T aState, ClipboardTransferInteraction_T aInteraction, const com::Utf8Str &aPath, const com::Utf8Str &aMessage, ClipboardError_T aError); +#endif /** @name Wrapped IClipboardTransferManager properties and methods * @{ */ HRESULT getTransfers(ClipboardTransferDirection_T aDirection, ULONG aFlags, std::vector > &aTransfers); - HRESULT createTransfer(ClipboardTransferDirection_T aDirection, - ClipboardSource_T aSource, - ClipboardAction_T aAction, - ComPtr &aTransfer); - HRESULT add(const ComPtr &aTransfer); + /** + * Creates and tracks a Main-owned clipboard transfer. + * + * @returns COM status code. + * @param aDirection Transfer direction. + * @param aSource Clipboard source owning the transfer. + * @param aAction Clipboard transfer action. + * @param aTransfer Where to return the transfer object. + */ + HRESULT create(ClipboardTransferDirection_T aDirection, + ClipboardSource_T aSource, + ClipboardAction_T aAction, + ComPtr &aTransfer); HRESULT remove(const ComPtr &aTransfer); HRESULT cancel(const ComPtr &aTransfer); HRESULT approve(const ComPtr &aTransfer, @@ -121,11 +145,12 @@ class ATL_NO_VTABLE ClipboardTransferManager : : mServiceSessionId(NIL_SHCLSESSIONID) , mTransferId(0) , mGeneration(NIL_SHCLTRANSFERGEN) + , mDirection(SHCLTRANSFERDIR_UNKNOWN) + , mSource(SHCLSOURCE_INVALID) , mStatus(SHCLTRANSFERSTATUS_NONE) , mState(ClipboardTransferState_Added) , mfTerminal(false) , mfCancelRequested(false) - , mfPublished(false) #endif { } @@ -133,27 +158,85 @@ class ATL_NO_VTABLE ClipboardTransferManager : SHCLSESSIONID mServiceSessionId; ULONG mTransferId; SHCLTRANSFERGEN mGeneration; + /** Shared Clipboard data-plane direction. */ + SHCLTRANSFERDIR mDirection; + /** Shared Clipboard data source recorded by the backing transfer. */ + SHCLSOURCE mSource; SHCLTRANSFERSTATUS mStatus; ClipboardTransferState_T mState; bool mfTerminal; bool mfCancelRequested; #endif - ComPtr mTransfer; + /** Concrete transfer owned by this manager. */ + ComObjPtr mTransfer; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /** Whether the transfer is visible through getTransfers(). */ - bool mfPublished; ComPtr mProgress; ComPtr mProgressControl; + + /** Returns whether the public Main interface matches this record. */ + bool matches(IClipboardTransfer *aTransfer) const + { + ClipboardTransfer *pTransfer = mTransfer; + return static_cast(pTransfer) == aTransfer; + } + + /** Returns whether the service identity matches this record. */ + bool matches(SHCLSESSIONID aServiceSessionId, ULONG aTransferId, + SHCLTRANSFERGEN aGeneration) const + { + return mServiceSessionId == aServiceSessionId + && mTransferId == aTransferId + && mGeneration == aGeneration; + } + + /** Returns whether the concrete Main object matches this record. */ + bool matches(ClipboardTransfer *aTransfer) const + { + ClipboardTransfer *pTransfer = mTransfer; + return pTransfer == aTransfer; + } + + /** Returns whether both the Main object and service identity match this record. */ + bool matches(ClipboardTransfer *aTransfer, + SHCLSESSIONID aServiceSessionId, ULONG aTransferId, + SHCLTRANSFERGEN aGeneration) const + { + return matches(aTransfer) + && matches(aServiceSessionId, aTransferId, aGeneration); + } #endif }; + /** Transfer record container type. */ + typedef std::vector TransferRecords; + /** Parent clipboard object. */ Clipboard *mParent; /** Clipboard event source. */ ComPtr mEventSource; /** Current clipboard transfer records. */ - std::vector mTransfers; + TransferRecords mTransfers; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Finds a record by public Main interface while the caller owns the manager lock. */ + TransferRecords::iterator findTransferRecord(IClipboardTransfer *aTransfer) + { + for (TransferRecords::iterator it = mTransfers.begin(); it != mTransfers.end(); ++it) + if (it->matches(aTransfer)) + return it; + return mTransfers.end(); + } + + /** Finds an exact transfer record while the caller owns the manager lock. */ + TransferRecords::iterator findTransferRecord(ClipboardTransfer *aTransfer, + SHCLSESSIONID aServiceSessionId, ULONG aTransferId, + SHCLTRANSFERGEN aGeneration) + { + for (TransferRecords::iterator it = mTransfers.begin(); it != mTransfers.end(); ++it) + if (it->matches(aTransfer, aServiceSessionId, aTransferId, aGeneration)) + return it; + return mTransfers.end(); + } + /** Next Main-created transfer identifier. */ ULONG mNextTransferId; #endif diff --git a/src/VBox/Main/src-client/ClipboardImpl.cpp b/src/VBox/Main/src-client/ClipboardImpl.cpp index de09b1307280..aefe77249009 100644 --- a/src/VBox/Main/src-client/ClipboardImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardImpl.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Console clipboard API. */ @@ -1688,8 +1688,10 @@ HRESULT Clipboard::reset() i_clearPendingDataRequestsLocked(); ptrTransfers = mData->mTransfers; } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if (!ptrTransfers.isNull()) ptrTransfers->i_reset(); +#endif return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD */ } @@ -3223,13 +3225,15 @@ HRESULT Clipboard::i_transferCancel(SHCLSESSIONID aServiceSessionId, SHCLTRANSFE * @param aServiceSessionId Service session that owns the transfer. * @param aTransferId Transfer ID that produced the status. * @param aGeneration Host-private transfer generation. - * @param enmShClSource Shared Clipboard status source. + * @param aTransfer Borrowed service transfer backing the data plane. + * @param enmShClSource Data source recorded by the backing transfer. * @param enmStatus Transfer lifecycle status. * @param vrcTransfer Transfer status result code. */ HRESULT Clipboard::i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration, + PSHCLTRANSFER aTransfer, SHCLSOURCE enmShClSource, SHCLTRANSFERSTATUS enmStatus, int vrcTransfer) @@ -3245,7 +3249,7 @@ HRESULT Clipboard::i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, if (ptrTransfers.isNull()) return E_FAIL; - return ptrTransfers->i_handleTransferStatus(aServiceSessionId, aTransferId, aGeneration, + return ptrTransfers->i_handleTransferStatus(aServiceSessionId, aTransferId, aGeneration, aTransfer, enmShClSource, enmStatus, vrcTransfer); } # endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -3282,6 +3286,10 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c VBOXSHCLMAINCLIENTID idRequestClient; uint32_t fCurrentFormats; uint64_t uLastItemSerial; +#ifdef RT_OS_DARWIN + bool fHadMatchingHostData; + bool fWantRequestPayload; +#endif { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); AssertPtrReturn(mData, E_FAIL); @@ -3289,6 +3297,21 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c idRequestClient = mData->mCurrentClientId; fCurrentFormats = m_fFormats; uLastItemSerial = mData->mLastItemSerial; +#ifdef RT_OS_DARWIN + fHadMatchingHostData = enmRequestSource == ClipboardSource_Host + && mData->mfHaveLastItem + && mData->mLastItemSource == enmRequestSource + && mData->mLastItemAction == enmRequestAction + && consoleClipboardMimeTypeToFormat(mData->mLastItemMimeType) == uFormat; + fWantRequestPayload = false; + for (std::vector::const_iterator it = mData->mSessions.begin(); + it != mData->mSessions.end(); ++it) + if (it->mEventSource.isNotNull() && (it->mfFlags & IClipboardSessionFlag_IncludePayload)) + { + fWantRequestPayload = true; + break; + } +#endif } if ( enmRequestSource != ClipboardSource_Host && enmRequestSource != ClipboardSource_Guest) @@ -3303,7 +3326,43 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c uFormat, fCurrentFormats)); return E_FAIL; } - ULONG const idRequest = i_fireDataRequested(idRequestClient, enmRequestAction, enmRequestSource, uFormat); + GuestShCl *pShCl = NULL; + bool fHostReadAttempted = false; + int vrcHostRead = VERR_NO_DATA; + const std::vector *pResolvedHostData = NULL; +#ifdef RT_OS_DARWIN + std::vector abResolvedHostData; + /* Keep the established provider-first order unless a macOS session explicitly requested the payload. */ + if ( enmRequestSource == ClipboardSource_Host + && uFormat == VBOX_SHCL_FMT_UNICODETEXT + && !fHadMatchingHostData + && fWantRequestPayload + && cbData) + { + pShCl = GuestShCl::TryGetInst(); + if (pShCl) + { + fHostReadAttempted = true; + vrcHostRead = pShCl->ReadDataFromHost((SHCLFORMAT)uFormat, pvData, cbData, pcbActual); + if ( RT_SUCCESS(vrcHostRead) + && *pcbActual > 0 + && *pcbActual <= cbData + && *pcbActual <= s_cbClipboardReadMax) + { + int const vrc2 = clipboardProtocolToMainData((SHCLFORMAT)uFormat, pvData, *pcbActual, + abResolvedHostData); + if (RT_SUCCESS(vrc2) && abResolvedHostData.size() <= s_cbClipboardReadMax) + pResolvedHostData = &abResolvedHostData; + else + Log2Func(("Not attaching native host text to request event: format=%#x, cbActual=%RU32, vrc=%Rrc\n", + uFormat, *pcbActual, vrc2)); + } + } + } +#endif + + ULONG const idRequest = i_fireDataRequested(idRequestClient, enmRequestAction, enmRequestSource, uFormat, + pResolvedHostData); if (idRequest == 0) Log2Func(("No pending data request was registered for service request: format=%#x\n", uFormat)); @@ -3348,7 +3407,8 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c return E_FAIL; } - GuestShCl *pShCl = GuestShCl::TryGetInst(); + if (!pShCl) + pShCl = GuestShCl::TryGetInst(); if (!pShCl) { LogFunc(("Cannot read native host data for guest request without Shared Clipboard service: format=%#x\n", @@ -3356,10 +3416,15 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c i_removePendingDataRequest(idRequest); return E_FAIL; } - int vrc = pShCl->ReadDataFromHost((SHCLFORMAT)uFormat, pvData, cbData, pcbActual); - if (RT_FAILURE(vrc)) + if (!fHostReadAttempted) + { + fHostReadAttempted = true; + vrcHostRead = pShCl->ReadDataFromHost((SHCLFORMAT)uFormat, pvData, cbData, pcbActual); + } + if (RT_FAILURE(vrcHostRead)) { - Log2Func(("Reading native host data for guest request failed: format=%#x, vrc=%Rrc\n", uFormat, vrc)); + Log2Func(("Reading native host data for guest request failed: format=%#x, vrc=%Rrc\n", + uFormat, vrcHostRead)); i_removePendingDataRequest(idRequest); return E_FAIL; } @@ -3415,6 +3480,60 @@ HRESULT Clipboard::requestData(const com::Utf8Str &aMimeType, ULONG *aRequestId) } +/** + * Sets a Shared Clipboard transfer status for Main testcase coverage. + * + * @returns COM status code. + * @param aServiceSessionId Shared Clipboard service session identifier. + * @param aTransferId Shared Clipboard transfer identifier. + * @param aGeneration Host-private transfer generation. + * @param aSource Data source associated with the status. + * @param aStatus Internal Shared Clipboard transfer status value. + * @param aResult IPRT result associated with the status. + */ +HRESULT Clipboard::setTransferStatus(ULONG aServiceSessionId, + ULONG aTransferId, + LONG64 aGeneration, + ClipboardSource_T aSource, + ULONG aStatus, + LONG aResult) +{ +#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RT_NOREF(aServiceSessionId, aTransferId, aGeneration, aSource, aStatus, aResult); + ReturnComNotImplemented(); +#else + if ( aServiceSessionId == 0 + || aServiceSessionId >= NIL_SHCLSESSIONID + || aTransferId == 0 + || aTransferId >= NIL_SHCLTRANSFERID + || aGeneration <= 0) + return E_INVALIDARG; + + SHCLSOURCE enmShClSource; + switch (aSource) + { + case ClipboardSource_Host: + enmShClSource = SHCLSOURCE_LOCAL; + break; + case ClipboardSource_Guest: + enmShClSource = SHCLSOURCE_REMOTE; + break; + default: + return E_INVALIDARG; + } + + HRESULT const hrc = i_handleTransferStatus((SHCLSESSIONID)aServiceSessionId, + (SHCLTRANSFERID)aTransferId, + (SHCLTRANSFERGEN)aGeneration, + NULL /* pTransfer */, + enmShClSource, + (SHCLTRANSFERSTATUS)aStatus, + (int)aResult); + return hrc; +#endif +} + + /** * Requests clipboard data from the current source and registers a pending request. * @@ -3760,11 +3879,13 @@ void Clipboard::i_fireDataChanged(VBOXSHCLMAINCLIENTID aClientId, * @param aAction Clipboard action associated with the request. * @param aSource Clipboard source from which data is requested. * @param uFormat Shared Clipboard format requested. + * @param pResolvedBuffer Optional canonical Main payload already read for the request. */ ULONG Clipboard::i_fireDataRequested(VBOXSHCLMAINCLIENTID aClientId, ClipboardAction_T aAction, ClipboardSource_T aSource, - uint32_t uFormat) + uint32_t uFormat, + const std::vector *pResolvedBuffer) { const char *pszMimeType = consoleClipboardFormatToMimeType((SHCLFORMAT)uFormat); if (!pszMimeType) @@ -3836,10 +3957,9 @@ ULONG Clipboard::i_fireDataRequested(VBOXSHCLMAINCLIENTID aClientId, LONG64 const i64Revision = i_nextEventRevision(); Log2Func(("Firing data requested event: requestId=%RU32, action=%RU32, source=%RU32, mime=%s, revision=%RI64, clientId=%RU32\n", (uint32_t)idRequest, (uint32_t)aAction, (uint32_t)aSource, strMimeType.c_str(), i64Revision, aClientId)); - ComPtr ptrItem; ComPtr ptrEvent; hrc = ::CreateClipboardDataRequestedEvent(ptrEvent.asOutParam(), ptrEventSource, i64Revision, aClientId, - com::Utf8Str(), ptrItem, FALSE /* aVeto */, + com::Utf8Str(), NULL /* aItem */, FALSE /* aVeto */, idRequest, aAction, aSource, ptrFormat); if (FAILED(hrc)) { @@ -3862,9 +3982,69 @@ ULONG Clipboard::i_fireDataRequested(VBOXSHCLMAINCLIENTID aClientId, std::vector vecTargets; i_getSessionEventTargets(vecTargets, aClientId, false /* fPassive */, false /* fCheckReflection */, VBOX_SHCL_FMT_NONE, aSource); + bool fIncludePayload = false; + for (std::vector::const_iterator it = vecTargets.begin(); it != vecTargets.end(); ++it) + if (it->mfFlags & IClipboardSessionFlag_IncludePayload) + { + fIncludePayload = true; + break; + } + + std::vector abRequestBuffer; + const std::vector *pRequestBuffer = NULL; + if (fIncludePayload) + { + if (pResolvedBuffer) + pRequestBuffer = pResolvedBuffer; + else if ( aSource == ClipboardSource_Host + && uFormat == VBOX_SHCL_FMT_UNICODETEXT) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if ( mData + && mData->mfHaveLastItem + && mData->mLastItemSource == aSource + && mData->mLastItemAction == aAction + && consoleClipboardMimeTypeToFormat(mData->mLastItemMimeType) == uFormat) + { + try + { + abRequestBuffer = mData->mLastItemBuffer; + pRequestBuffer = &abRequestBuffer; + } + catch (std::bad_alloc &) + { + LogFunc(("Out of memory copying cached data-requested payload\n")); + } + } + } + } + + ComPtr ptrItem; + if (pRequestBuffer) + { + try + { + hrc = i_createItem(aSource, strMimeType, *pRequestBuffer, ptrItem); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + if (FAILED(hrc)) + { + LogFunc(("Creating data-requested item failed: requestId=%RU32, source=%RU32, mime=%s, cb=%zu, hrc=%#x\n", + (uint32_t)idRequest, (uint32_t)aSource, strMimeType.c_str(), pRequestBuffer->size(), hrc)); + ptrItem.setNull(); + } + } for (std::vector::const_iterator it = vecTargets.begin(); it != vecTargets.end(); ++it) - ::FireClipboardDataRequestedEvent(it->mEventSource, i64Revision, aClientId, com::Utf8Str(), ptrItem, + { + ComPtr ptrSessionItem; + if (it->mfFlags & IClipboardSessionFlag_IncludePayload) + ptrSessionItem = ptrItem; + ::FireClipboardDataRequestedEvent(it->mEventSource, i64Revision, aClientId, com::Utf8Str(), ptrSessionItem, FALSE /* aVeto */, idRequest, aAction, aSource, ptrFormat); + } return idRequest; } diff --git a/src/VBox/Main/src-client/ClipboardTransferDataImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferDataImpl.cpp index 3aa7f0bcba96..724728540b62 100644 --- a/src/VBox/Main/src-client/ClipboardTransferDataImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferDataImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferDataImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferDataImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer data plane object. */ @@ -64,6 +64,9 @@ static HRESULT clipboardTransferDataRcToHrc(int vrc) if ( vrc == VERR_INVALID_PARAMETER || vrc == VERR_INVALID_POINTER || vrc == VERR_INVALID_HANDLE + || vrc == VERR_INVALID_NAME + || vrc == VERR_INVALID_UTF8_ENCODING + || vrc == VERR_PATH_IS_NOT_RELATIVE || vrc == VERR_WRONG_ORDER || vrc == VERR_BUFFER_OVERFLOW || vrc == VERR_TOO_MUCH_DATA) @@ -74,43 +77,6 @@ static HRESULT clipboardTransferDataRcToHrc(int vrc) } -/** - * Validates a transfer-relative path supplied through the low-level data API. - * - * @returns COM status code. - * @param aPath Path to validate. - * @param fAllowEmpty Whether the empty path is accepted. - */ -static HRESULT clipboardTransferDataValidatePath(const com::Utf8Str &aPath, bool fAllowEmpty) -{ - if (aPath.isEmpty()) - return fAllowEmpty ? S_OK : E_INVALIDARG; - - const char *pszPath = aPath.c_str(); - if ( pszPath[0] == '/' - || pszPath[0] == '\\' - || strchr(pszPath, '\\') - || strchr(pszPath, ':')) - return E_INVALIDARG; - - const char *pszCur = pszPath; - while (*pszCur) - { - const char *pszNext = strchr(pszCur, '/'); - size_t const cch = pszNext ? (size_t)(pszNext - pszCur) : strlen(pszCur); - if ( cch == 0 - || (cch == 1 && pszCur[0] == '.') - || (cch == 2 && pszCur[0] == '.' && pszCur[1] == '.')) - return E_INVALIDARG; - if (!pszNext) - break; - pszCur = pszNext + 1; - } - - return S_OK; -} - - /** * Copies a Shared Clipboard list entry to Main API output values. * @@ -127,22 +93,26 @@ static HRESULT clipboardTransferDataListEntryToMain(PCSHCLLISTENTRY pEntry, { AssertPtrReturn(pEntry, E_POINTER); AssertPtrReturn(aInfoFlags, E_POINTER); + if (!ShClTransferListEntryIsValid((PSHCLLISTENTRY)pEntry)) + return E_INVALIDARG; - aName = pEntry->pszName ? pEntry->pszName : ""; - *aInfoFlags = pEntry->fInfo; + com::Utf8Str Name; + std::vector Info; try { - aInfo.resize(pEntry->cbInfo); + Name = pEntry->pszName; + Info.resize(pEntry->cbInfo); if (pEntry->cbInfo) - { - AssertPtrReturn(pEntry->pvInfo, E_POINTER); - memcpy(&aInfo[0], pEntry->pvInfo, pEntry->cbInfo); - } + memcpy(&Info[0], pEntry->pvInfo, pEntry->cbInfo); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + + aName.swap(Name); + aInfo.swap(Info); + *aInfoFlags = pEntry->fInfo; return S_OK; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -174,7 +144,7 @@ void ClipboardTransferData::FinalRelease() * Initializes a clipboard transfer data plane object. * * @returns COM status code. - * @param aParent Parent transfer object used to keep the backing transfer alive. + * @param aParent Parent transfer object. * @param aTransfer Backing Shared Clipboard transfer. */ HRESULT ClipboardTransferData::init(const ComPtr &aParent, PSHCLTRANSFER aTransfer) @@ -234,11 +204,8 @@ HRESULT ClipboardTransferData::open(ClipboardTransferDataType_T aType, return setError(E_POINTER, tr("The clipboard transfer data handle output argument must not be NULL")); *aHandle = 0; - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -256,9 +223,12 @@ HRESULT ClipboardTransferData::open(ClipboardTransferDataType_T aType, case ClipboardTransferDataType_List: { - HRESULT hrc = clipboardTransferDataValidatePath(aPath, true /* fAllowEmpty */); - if (FAILED(hrc)) - return setError(hrc, tr("Invalid clipboard transfer list path")); + vrc = ShClTransferValidatePath(aPath.c_str(), false /* fMustExist */); + if (RT_FAILURE(vrc)) + { + HRESULT const hrc = clipboardTransferDataRcToHrc(vrc); + return setErrorBoth(hrc, vrc, tr("Invalid clipboard transfer list path: %Rrc"), vrc); + } SHCLLISTOPENPARMS OpenParms; vrc = ShClTransferListOpenParmsInit(&OpenParms); @@ -284,9 +254,14 @@ HRESULT ClipboardTransferData::open(ClipboardTransferDataType_T aType, case ClipboardTransferDataType_Object: { - HRESULT hrc = clipboardTransferDataValidatePath(aPath, false /* fAllowEmpty */); - if (FAILED(hrc)) - return setError(hrc, tr("Invalid clipboard transfer object path")); + if (aPath.isEmpty()) + return setError(E_INVALIDARG, tr("Clipboard transfer object paths must not be empty")); + vrc = ShClTransferValidatePath(aPath.c_str(), false /* fMustExist */); + if (RT_FAILURE(vrc)) + { + HRESULT const hrc = clipboardTransferDataRcToHrc(vrc); + return setErrorBoth(hrc, vrc, tr("Invalid clipboard transfer object path: %Rrc"), vrc); + } SHCLOBJOPENCREATEPARMS OpenParms; vrc = ShClTransferObjOpenParmsInit(&OpenParms); @@ -332,11 +307,8 @@ HRESULT ClipboardTransferData::close(ClipboardTransferDataType_T aType, LONG64 a ReturnComNotImplemented(); #else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); if (aHandle < 0) @@ -401,11 +373,8 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, if (aHandle < 0) return setError(E_INVALIDARG, tr("Clipboard transfer data handle must not be negative")); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -416,9 +385,12 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, { if (aSize != 0 || aFlags != 0) return setError(E_INVALIDARG, tr("Root-list clipboard transfer reads require size 0 and flags 0")); - PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTransfer, (uint64_t)aHandle); + + PCSHCLLISTENTRY const pEntry = ShClTransferRootsEntryGet(pTransfer, (uint64_t)aHandle); if (!pEntry) - return setError(VBOX_E_SHCL_NO_DATA, tr("No clipboard transfer root entry exists at index %RI64"), aHandle); + return setErrorBoth(clipboardTransferDataRcToHrc(VERR_NOT_FOUND), VERR_NOT_FOUND, + tr("No clipboard transfer root entry exists at index %RI64"), aHandle); + HRESULT hrc = clipboardTransferDataListEntryToMain(pEntry, aName, aInfoFlags, aInfo); if (FAILED(hrc)) return setError(hrc, tr("Copying clipboard transfer root entry metadata failed")); @@ -435,8 +407,14 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, { vrc = ShClTransferListRead(pTransfer, (SHCLLISTHANDLE)aHandle, &Entry); if (RT_SUCCESS(vrc)) - vrc = SUCCEEDED(clipboardTransferDataListEntryToMain(&Entry, aName, aInfoFlags, aInfo)) - ? VINF_SUCCESS : VERR_NO_MEMORY; + { + HRESULT const hrc = clipboardTransferDataListEntryToMain(&Entry, aName, aInfoFlags, aInfo); + if (FAILED(hrc)) + { + ShClTransferListEntryDestroy(&Entry); + return setError(hrc, tr("Copying clipboard transfer list entry metadata failed")); + } + } ShClTransferListEntryDestroy(&Entry); } break; @@ -446,6 +424,8 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, { if (!aSize || aFlags != 0) return setError(E_INVALIDARG, tr("Clipboard transfer object reads require a non-zero size and flags 0")); + if (aSize > pTransfer->cbMaxChunkSize) + return setError(E_INVALIDARG, tr("Clipboard transfer object read size exceeds the backend chunk limit")); try { aData.resize(aSize); @@ -457,9 +437,15 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, uint32_t cbRead = 0; vrc = ShClTransferObjRead(pTransfer, (SHCLOBJHANDLE)aHandle, aData.empty() ? NULL : &aData[0], aSize, aFlags, &cbRead); - if (RT_SUCCESS(vrc)) + if ( RT_SUCCESS(vrc) + && cbRead <= aSize) aData.resize(cbRead); - else + else if (RT_SUCCESS(vrc)) + { + aData.clear(); + return setError(E_INVALIDARG, tr("Clipboard transfer provider returned an invalid read size")); + } + if (RT_FAILURE(vrc)) aData.clear(); break; } @@ -509,11 +495,8 @@ HRESULT ClipboardTransferData::write(ClipboardTransferDataType_T aType, if (aHandle < 0) return setError(E_INVALIDARG, tr("Clipboard transfer data handle must not be negative")); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -524,6 +507,14 @@ HRESULT ClipboardTransferData::write(ClipboardTransferDataType_T aType, { if (aFlags != 0 || aName.isEmpty()) return setError(E_INVALIDARG, tr("Clipboard transfer list writes require flags 0 and a non-empty entry name")); + if ( aInfo.size() > UINT32_MAX + || ( aInfoFlags == VBOX_SHCL_INFO_F_NONE + && !aInfo.empty()) + || ( aInfoFlags == VBOX_SHCL_INFO_F_FSOBJINFO + && aInfo.size() != sizeof(SHCLFSOBJINFO)) + || ( aInfoFlags != VBOX_SHCL_INFO_F_NONE + && aInfoFlags != VBOX_SHCL_INFO_F_FSOBJINFO)) + return setError(E_INVALIDARG, tr("Clipboard transfer list information flags and payload do not match")); void *pvInfo = NULL; if (!aInfo.empty()) { @@ -540,7 +531,10 @@ HRESULT ClipboardTransferData::write(ClipboardTransferDataType_T aType, if (RT_SUCCESS(vrc)) { pvInfo = NULL; /* Ownership transferred to Entry. */ - vrc = ShClTransferListWrite(pTransfer, (SHCLLISTHANDLE)aHandle, &Entry); + if (!ShClTransferListEntryIsValid(&Entry)) + vrc = VERR_INVALID_PARAMETER; + else + vrc = ShClTransferListWrite(pTransfer, (SHCLLISTHANDLE)aHandle, &Entry); ShClTransferListEntryDestroy(&Entry); } if (pvInfo) @@ -557,8 +551,11 @@ HRESULT ClipboardTransferData::write(ClipboardTransferDataType_T aType, uint32_t cbWritten = 0; vrc = ShClTransferObjWrite(pTransfer, (SHCLOBJHANDLE)aHandle, (void *)&aData[0], (uint32_t)aData.size(), aFlags, &cbWritten); - if (RT_SUCCESS(vrc)) + if ( RT_SUCCESS(vrc) + && cbWritten <= aData.size()) *aWritten = cbWritten; + else if (RT_SUCCESS(vrc)) + return setError(E_INVALIDARG, tr("Clipboard transfer provider returned an invalid write size")); break; } diff --git a/src/VBox/Main/src-client/ClipboardTransferDirectoryImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferDirectoryImpl.cpp index 4e6744527e92..08fb26b99c3e 100644 --- a/src/VBox/Main/src-client/ClipboardTransferDirectoryImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferDirectoryImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferDirectoryImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferDirectoryImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer directory handle. */ @@ -30,6 +30,7 @@ #include "VirtualBoxBase.h" #include "AutoCaller.h" +#include "ClipboardTransferImpl.h" #include "ClipboardTransferDirectoryImpl.h" #include "ClipboardTransferFsObjInfoImpl.h" @@ -38,6 +39,8 @@ #include +#include + DEFINE_EMPTY_CTOR_DTOR(ClipboardTransferDirectory) @@ -117,9 +120,8 @@ static HRESULT clipboardTransferDirectoryEntryToInfo(const com::Utf8Str &aDirect const SHCLLISTENTRY &Entry, ComPtr &aInfo) { - if ( !(Entry.fInfo & VBOX_SHCL_INFO_F_FSOBJINFO) - || !Entry.pvInfo - || Entry.cbInfo != sizeof(SHCLFSOBJINFO)) + if ( Entry.fInfo != VBOX_SHCL_INFO_F_FSOBJINFO + || !ShClTransferListEntryIsValid((PSHCLLISTENTRY)&Entry)) return E_INVALIDARG; ComObjPtr ptrInfo; @@ -127,11 +129,18 @@ static HRESULT clipboardTransferDirectoryEntryToInfo(const com::Utf8Str &aDirect if (FAILED(hrc)) return hrc; - com::Utf8Str const strName(Entry.pszName ? Entry.pszName : ""); - com::Utf8Str const strPath = clipboardTransferDirectoryMakeChildPath(aDirectoryPath, Entry.pszName); - hrc = ptrInfo->init(strPath, strName, (PCSHCLFSOBJINFO)Entry.pvInfo); - if (FAILED(hrc)) - return hrc; + try + { + com::Utf8Str const strName(Entry.pszName); + com::Utf8Str const strPath = clipboardTransferDirectoryMakeChildPath(aDirectoryPath, Entry.pszName); + hrc = ptrInfo->init(strPath, strName, (PCSHCLFSOBJINFO)Entry.pvInfo); + if (FAILED(hrc)) + return hrc; + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } return ptrInfo.queryInterfaceTo(aInfo.asOutParam()); } @@ -164,25 +173,33 @@ void ClipboardTransferDirectory::FinalRelease() * Initializes a clipboard transfer directory handle. * * @returns COM status code. - * @param aParent Parent transfer object used to keep the backing transfer alive. + * @param aParent Concrete parent transfer object. * @param aTransfer Backing Shared Clipboard transfer. * @param aPath Transfer-relative directory path. * @param aHandle Open Shared Clipboard list handle. */ -HRESULT ClipboardTransferDirectory::init(const ComPtr &aParent, +HRESULT ClipboardTransferDirectory::init(const ComObjPtr &aParent, PSHCLTRANSFER aTransfer, const com::Utf8Str &aPath, SHCLLISTHANDLE aHandle) { + AssertReturn(aParent.isNotNull(), E_POINTER); AssertPtrReturn(aTransfer, E_POINTER); AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); + try + { + mData.mPath = aPath; + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } mData.mParent = aParent; - mData.mTransfer = aTransfer; mData.mHandle = aHandle; - mData.mPath = aPath; mData.mStatus = DirectoryStatus_Open; + mData.mTransfer = aTransfer; autoInitSpan.setSucceeded(); return S_OK; @@ -200,10 +217,12 @@ void ClipboardTransferDirectory::uninit() return; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ComObjPtr ptrParent; PSHCLTRANSFER pTransfer = NULL; SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrParent = mData.mParent; pTransfer = mData.mTransfer; hList = mData.mHandle; mData.mTransfer = NULL; @@ -263,19 +282,22 @@ HRESULT ClipboardTransferDirectory::close() #ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS ReturnComNotImplemented(); #else + ComObjPtr ptrParent; PSHCLTRANSFER pTransfer = NULL; SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrParent = mData.mParent; pTransfer = mData.mTransfer; hList = mData.mHandle; mData.mTransfer = NULL; mData.mHandle = NIL_SHCLLISTHANDLE; mData.mStatus = DirectoryStatus_Close; + mData.mParent.setNull(); } - if (!pTransfer || hList == NIL_SHCLLISTHANDLE) + if (!pTransfer) return S_OK; - int vrc = ShClTransferListClose(pTransfer, hList); + int const vrc = hList == NIL_SHCLLISTHANDLE ? VINF_SUCCESS : ShClTransferListClose(pTransfer, hList); HRESULT hrc = clipboardTransferDirectoryRcToHrc(vrc); if (FAILED(hrc)) return setErrorBoth(hrc, vrc, tr("Closing clipboard transfer directory failed with %Rrc"), vrc); @@ -294,12 +316,22 @@ HRESULT ClipboardTransferDirectory::list(ULONG aMaxEntries, std::vector >::const_iterator it = vecEntries.begin(); it != vecEntries.end(); ++it) + std::vector > ObjInfo; + try { - ComPtr ptrInfo(*it); - aObjInfo.push_back(ptrInfo); + ObjInfo.reserve(vecEntries.size()); + for (std::vector >::const_iterator it = vecEntries.begin(); + it != vecEntries.end(); ++it) + { + ComPtr ptrInfo(*it); + ObjInfo.push_back(ptrInfo); + } } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer directory result failed")); + } + aObjInfo.swap(ObjInfo); return S_OK; #endif } @@ -328,15 +360,10 @@ HRESULT ClipboardTransferDirectory::rewind() #ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS ReturnComNotImplemented(); #else - PSHCLTRANSFER pTransfer = NULL; - SHCLLISTHANDLE hOld = NIL_SHCLLISTHANDLE; - com::Utf8Str strPath; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hOld = mData.mHandle; - strPath = mData.mPath; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLLISTHANDLE const hOld = mData.mHandle; + com::Utf8Str const strPath = mData.mPath; if (!pTransfer || hOld == NIL_SHCLLISTHANDLE) return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer directory is closed")); @@ -348,12 +375,8 @@ HRESULT ClipboardTransferDirectory::rewind() return setErrorBoth(hrc, vrc, tr("Rewinding clipboard transfer directory failed with %Rrc"), vrc); } - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - hOld = mData.mHandle; - mData.mHandle = hNew; - mData.mStatus = DirectoryStatus_Open; - } + mData.mHandle = hNew; + mData.mStatus = DirectoryStatus_Open; if (hOld != NIL_SHCLLISTHANDLE) ShClTransferListClose(pTransfer, hOld); return S_OK; @@ -382,47 +405,65 @@ HRESULT ClipboardTransferDirectory::listEx(ULONG aMaxEntries, if (aFlags & ~(ClipboardTransferListFlag_NoRecursion | ClipboardTransferListFlag_IncludeRoot | ClipboardTransferListFlag_NoFollowSymlinks)) return setError(E_INVALIDARG, tr("Invalid clipboard transfer directory-list flags %RU32"), aFlags); - aEntries.clear(); + std::vector > Entries; + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLLISTHANDLE const hList = mData.mHandle; + com::Utf8Str const strPath = mData.mPath; + ComObjPtr const ptrParent = mData.mParent; + if (!pTransfer || hList == NIL_SHCLLISTHANDLE) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer directory is closed")); + if (!(aFlags & ClipboardTransferListFlag_NoRecursion)) { - ComPtr ptrParent; - com::Utf8Str strPath; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - ptrParent = mData.mParent; - strPath = mData.mPath; - } if (ptrParent.isNull()) return setError(E_FAIL, tr("Clipboard transfer directory has no parent transfer for recursive listing")); - SafeIfaceArray aSafeEntries; - HRESULT hrc = ptrParent->List(Bstr(strPath).raw(), aFlags, ComSafeArrayAsOutParam(aSafeEntries)); + + std::vector > vecEntries; + HRESULT hrc = ptrParent->i_list(pTransfer, strPath, aFlags, vecEntries); if (FAILED(hrc)) return setError(hrc, tr("Recursively listing clipboard transfer directory failed")); - for (size_t i = 0; i < aSafeEntries.size(); ++i) + for (std::vector >::const_iterator it = vecEntries.begin(); + it != vecEntries.end(); ++it) { - if (aMaxEntries && aEntries.size() >= aMaxEntries) + if (aMaxEntries && Entries.size() >= aMaxEntries) break; - aEntries.push_back(ComPtr(aSafeEntries[i])); + try + { + Entries.push_back(*it); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the recursive clipboard transfer directory result failed")); + } } + aEntries.swap(Entries); return S_OK; } - PSHCLTRANSFER pTransfer = NULL; - SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; - com::Utf8Str strPath; + if ( (aFlags & ClipboardTransferListFlag_IncludeRoot) + && !strPath.isEmpty()) { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hList = mData.mHandle; - strPath = mData.mPath; + if (ptrParent.isNull()) + return setError(E_FAIL, tr("Clipboard transfer directory has no parent transfer for querying its root")); + + ComPtr ptrRoot; + HRESULT hrc = ptrParent->i_query(pTransfer, strPath, ptrRoot); + if (FAILED(hrc)) + return setError(hrc, tr("Querying the clipboard transfer directory root failed")); + try + { + Entries.push_back(ptrRoot); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer directory root result failed")); + } } - if (!pTransfer || hList == NIL_SHCLLISTHANDLE) - return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer directory is closed")); - ULONG cEntries = 0; for (;;) { - if (aMaxEntries && cEntries >= aMaxEntries) + if (aMaxEntries && Entries.size() >= aMaxEntries) break; SHCLLISTENTRY Entry; @@ -447,9 +488,16 @@ HRESULT ClipboardTransferDirectory::listEx(ULONG aMaxEntries, ShClTransferListEntryDestroy(&Entry); if (FAILED(hrc)) return setError(hrc, tr("Creating clipboard transfer directory entry information failed")); - aEntries.push_back(ptrInfo); - ++cEntries; + try + { + Entries.push_back(ptrInfo); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer directory result failed")); + } } + aEntries.swap(Entries); return S_OK; #endif } diff --git a/src/VBox/Main/src-client/ClipboardTransferFileImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferFileImpl.cpp index 4d1daf9df62f..8feff86d5140 100644 --- a/src/VBox/Main/src-client/ClipboardTransferFileImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferFileImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferFileImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferFileImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer file handle. */ @@ -112,15 +112,21 @@ static int clipboardTransferFileOpenObject(PSHCLTRANSFER pTransfer, */ static int clipboardTransferFileSkip(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj, uint64_t cbSkip) { + uint32_t const cbMaxChunk = pTransfer->cbMaxChunkSize; + if (!cbMaxChunk) + return VERR_INVALID_STATE; + uint8_t abBuf[_64K]; while (cbSkip > 0) { - uint32_t const cbToRead = (uint32_t)RT_MIN(cbSkip, (uint64_t)sizeof(abBuf)); + uint32_t const cbToRead = (uint32_t)RT_MIN(cbSkip, (uint64_t)RT_MIN(sizeof(abBuf), (size_t)cbMaxChunk)); uint32_t cbRead = 0; int vrc = ShClTransferObjRead(pTransfer, hObj, abBuf, cbToRead, 0, &cbRead); if (RT_FAILURE(vrc)) return vrc; - if (!cbRead) + if ( !cbRead + || cbRead > cbToRead + || cbRead > cbSkip) return VERR_EOF; cbSkip -= cbRead; } @@ -157,7 +163,7 @@ void ClipboardTransferFile::FinalRelease() * Initializes a clipboard transfer file handle. * * @returns COM status code. - * @param aParent Parent transfer object used to keep the backing transfer alive. + * @param aParent Parent transfer object. * @param aTransfer Backing Shared Clipboard transfer. * @param aHandle Open Shared Clipboard object handle. * @param aPath Transfer-relative file path. @@ -178,13 +184,21 @@ HRESULT ClipboardTransferFile::init(const ComPtr &aParent, ULONG aCreationMode) { AssertPtrReturn(aTransfer, E_POINTER); + if (aInfo.cbObject < 0) + return E_INVALIDARG; AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); + try + { + mData.mPath = aPath; + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } mData.mParent = aParent; - mData.mTransfer = aTransfer; mData.mHandle = aHandle; - mData.mPath = aPath; mData.mInfo = aInfo; mData.mOffset = 0; mData.mStatus = FileStatus_Open; @@ -192,6 +206,7 @@ HRESULT ClipboardTransferFile::init(const ComPtr &aParent, mData.mOpenAction = aOpenAction; mData.mSharingMode = aSharingMode; mData.mCreationMode = aCreationMode; + mData.mTransfer = aTransfer; autoInitSpan.setSucceeded(); return S_OK; @@ -209,10 +224,12 @@ void ClipboardTransferFile::uninit() return; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ComPtr ptrParent; PSHCLTRANSFER pTransfer = NULL; SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrParent = mData.mParent; pTransfer = mData.mTransfer; hObj = mData.mHandle; mData.mTransfer = NULL; @@ -305,19 +322,22 @@ HRESULT ClipboardTransferFile::close() #ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS ReturnComNotImplemented(); #else + ComPtr ptrParent; PSHCLTRANSFER pTransfer = NULL; SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrParent = mData.mParent; pTransfer = mData.mTransfer; hObj = mData.mHandle; mData.mTransfer = NULL; mData.mHandle = NIL_SHCLOBJHANDLE; mData.mStatus = FileStatus_Closed; + mData.mParent.setNull(); } - if (!pTransfer || hObj == NIL_SHCLOBJHANDLE) + if (!pTransfer) return S_OK; - int vrc = ShClTransferObjClose(pTransfer, hObj); + int const vrc = hObj == NIL_SHCLOBJHANDLE ? VINF_SUCCESS : ShClTransferObjClose(pTransfer, hObj); HRESULT hrc = clipboardTransferFileRcToHrc(vrc); if (FAILED(hrc)) return setErrorBoth(hrc, vrc, tr("Closing clipboard transfer file failed with %Rrc"), vrc); @@ -373,15 +393,13 @@ HRESULT ClipboardTransferFile::read(ULONG aToRead, ULONG aTimeoutMS, std::vector if (!aToRead) return setError(E_INVALIDARG, tr("Clipboard transfer file read size must be non-zero")); - PSHCLTRANSFER pTransfer = NULL; - SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hObj = mData.mHandle; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLOBJHANDLE const hObj = mData.mHandle; if (!pTransfer || hObj == NIL_SHCLOBJHANDLE) return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer file is closed")); + if (aToRead > pTransfer->cbMaxChunkSize) + return setError(E_INVALIDARG, tr("Clipboard transfer file read size exceeds the backend chunk limit")); try { @@ -399,11 +417,14 @@ HRESULT ClipboardTransferFile::read(ULONG aToRead, ULONG aTimeoutMS, std::vector HRESULT hrc = clipboardTransferFileRcToHrc(vrc); return setErrorBoth(hrc, vrc, tr("Reading clipboard transfer file failed with %Rrc"), vrc); } - aData.resize(cbRead); + if ( cbRead > aToRead + || mData.mOffset > INT64_MAX - cbRead) { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - mData.mOffset += cbRead; + aData.clear(); + return setError(E_INVALIDARG, tr("Clipboard transfer provider returned an invalid read size")); } + aData.resize(cbRead); + mData.mOffset += cbRead; return S_OK; #endif } @@ -447,6 +468,10 @@ HRESULT ClipboardTransferFile::seek(LONG64 aOffset, FileSeekOrigin_T aWhence, LO return setError(E_INVALIDARG, tr("Invalid clipboard transfer file seek origin %RU32"), (uint32_t)aWhence); } + if ( (aOffset > 0 && offBase > INT64_MAX - aOffset) + || (aOffset < 0 && offBase < INT64_MIN - aOffset)) + return setError(E_INVALIDARG, tr("Clipboard transfer file seek offset is out of range")); + LONG64 const offNew = offBase + aOffset; if (offNew < 0) return setError(E_INVALIDARG, tr("Clipboard transfer file seek would move before the start of the file")); @@ -468,15 +493,10 @@ HRESULT ClipboardTransferFile::seek(LONG64 aOffset, FileSeekOrigin_T aWhence, LO */ HRESULT ClipboardTransferFile::i_reopenAt(uint64_t offNew) { - PSHCLTRANSFER pTransfer = NULL; - SHCLOBJHANDLE hOld = NIL_SHCLOBJHANDLE; - com::Utf8Str strPath; - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hOld = mData.mHandle; - strPath = mData.mPath; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLOBJHANDLE const hOld = mData.mHandle; + com::Utf8Str const strPath = mData.mPath; if (!pTransfer) return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer file is closed")); @@ -484,6 +504,9 @@ HRESULT ClipboardTransferFile::i_reopenAt(uint64_t offNew) SHCLFSOBJINFO Info; RT_ZERO(Info); int vrc = clipboardTransferFileOpenObject(pTransfer, strPath, &hNew, &Info); + if ( RT_SUCCESS(vrc) + && Info.cbObject < 0) + vrc = VERR_OUT_OF_RANGE; if (RT_SUCCESS(vrc)) vrc = clipboardTransferFileSkip(pTransfer, hNew, offNew); if (RT_FAILURE(vrc)) @@ -494,15 +517,11 @@ HRESULT ClipboardTransferFile::i_reopenAt(uint64_t offNew) return setErrorBoth(hrc, vrc, tr("Seeking clipboard transfer file failed with %Rrc"), vrc); } + mData.mHandle = hNew; + mData.mInfo = Info; + mData.mOffset = (LONG64)offNew; if (hOld != NIL_SHCLOBJHANDLE) ShClTransferObjClose(pTransfer, hOld); - - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - mData.mHandle = hNew; - mData.mInfo = Info; - mData.mOffset = (LONG64)offNew; - } return S_OK; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -535,19 +554,17 @@ HRESULT ClipboardTransferFile::write(const std::vector &aData, ULONG aTime if (aData.empty()) return setError(E_INVALIDARG, tr("Clipboard transfer file write data must not be empty")); - PSHCLTRANSFER pTransfer = NULL; - SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; - FileAccessMode_T enmAccessMode = FileAccessMode_ReadOnly; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hObj = mData.mHandle; - enmAccessMode = mData.mAccessMode; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLOBJHANDLE const hObj = mData.mHandle; + FileAccessMode_T const enmAccessMode = mData.mAccessMode; if (enmAccessMode == FileAccessMode_ReadOnly) return setError(E_NOTIMPL, tr("Writing to read-only clipboard transfer files is not implemented")); if (!pTransfer || hObj == NIL_SHCLOBJHANDLE) return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer file is closed")); + if ( aData.size() > UINT32_MAX + || aData.size() > pTransfer->cbMaxChunkSize) + return setError(E_INVALIDARG, tr("Clipboard transfer file write size exceeds the backend chunk limit")); uint32_t cbWritten = 0; int vrc = ShClTransferObjWrite(pTransfer, hObj, (void *)&aData[0], (uint32_t)aData.size(), 0, &cbWritten); @@ -556,10 +573,10 @@ HRESULT ClipboardTransferFile::write(const std::vector &aData, ULONG aTime HRESULT hrc = clipboardTransferFileRcToHrc(vrc); return setErrorBoth(hrc, vrc, tr("Writing clipboard transfer file failed with %Rrc"), vrc); } - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - mData.mOffset += cbWritten; - } + if ( cbWritten > aData.size() + || mData.mOffset > INT64_MAX - cbWritten) + return setError(E_INVALIDARG, tr("Clipboard transfer provider returned an invalid write size")); + mData.mOffset += cbWritten; *aWritten = cbWritten; return S_OK; #endif @@ -590,4 +607,3 @@ HRESULT ClipboardTransferFile::getPath(com::Utf8Str &aPath) aPath = mData.mPath; return S_OK; } - diff --git a/src/VBox/Main/src-client/ClipboardTransferFsObjInfoImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferFsObjInfoImpl.cpp index c45d3fad270d..90eb03a0fd5d 100644 --- a/src/VBox/Main/src-client/ClipboardTransferFsObjInfoImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferFsObjInfoImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferFsObjInfoImpl.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferFsObjInfoImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer file system object information. */ @@ -34,6 +34,8 @@ #include +#include + DEFINE_EMPTY_CTOR_DTOR(ClipboardTransferFsObjInfo) @@ -125,13 +127,26 @@ HRESULT ClipboardTransferFsObjInfo::init(const com::Utf8Str &aPath, PCSHCLFSOBJINFO aInfo) { AssertPtrReturn(aInfo, E_POINTER); + if ( aInfo->cbObject < 0 + || aInfo->cbAllocated < 0 + || aInfo->Attr.enmAdditional < SHCLFSOBJATTRADD_NOTHING + || aInfo->Attr.enmAdditional > SHCLFSOBJATTRADD_LAST) + return E_INVALIDARG; + AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); - mData.mPath = aPath; - mData.mName = aName; + try + { + mData.mPath = aPath; + mData.mName = aName; + mData.mFileAttributes = clipboardTransferFsObjInfoModeToAttrs(aInfo->Attr.fMode); + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } mData.mType = clipboardTransferFsObjInfoModeToType(aInfo->Attr.fMode); - mData.mFileAttributes = clipboardTransferFsObjInfoModeToAttrs(aInfo->Attr.fMode); mData.mObjectSize = aInfo->cbObject; mData.mAllocatedSize = aInfo->cbAllocated; mData.mAccessTime = aInfo->AccessTime.i64NanosecondsRelativeToUnixEpoch; diff --git a/src/VBox/Main/src-client/ClipboardTransferImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferImpl.cpp index 6818a39b4f36..e5f5b202e879 100644 --- a/src/VBox/Main/src-client/ClipboardTransferImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer object. */ @@ -45,6 +45,15 @@ #include +/** Maximum number of active directory levels, including the initial directory, + * while recursively listing a transfer. This bounds stack consumption for + * externally supplied directory trees. */ +#define VBOX_SHCL_MAIN_MAX_RECURSION_DEPTH 128 +/** Maximum number of result nodes accumulated while recursively listing a + * transfer. This bounds memory consumption and traversal work for externally + * supplied directory trees. */ +#define VBOX_SHCL_MAIN_MAX_RECURSIVE_NODES _64K + // constructor / destructor ///////////////////////////////////////////////////////////////////////////// @@ -146,6 +155,7 @@ static HRESULT clipboardTransferValidatePath(const com::Utf8Str &aPath, bool fAl const char *pszPath = aPath.c_str(); if ( pszPath[0] == '/' || pszPath[0] == '\\' + || pszPath[aPath.length() - 1] == '/' || strchr(pszPath, '\\') || strchr(pszPath, ':')) return E_INVALIDARG; @@ -201,7 +211,6 @@ static HRESULT clipboardTransferCreateLocalProviderBackend(const std::vector &aNode) { AssertPtrReturn(pEntry, E_POINTER); - if ( !(pEntry->fInfo & VBOX_SHCL_INFO_F_FSOBJINFO) - || !pEntry->pvInfo - || pEntry->cbInfo != sizeof(SHCLFSOBJINFO)) + if ( pEntry->fInfo != VBOX_SHCL_INFO_F_FSOBJINFO + || !ShClTransferListEntryIsValid((PSHCLLISTENTRY)pEntry)) return E_INVALIDARG; ComObjPtr ptrInfo; @@ -273,11 +281,18 @@ static HRESULT clipboardTransferCreateFsObjInfoFromEntry(const com::Utf8Str &aPa if (FAILED(hrc)) return hrc; - com::Utf8Str const strName(pEntry->pszName ? pEntry->pszName : ""); - com::Utf8Str const strPath = clipboardTransferMakeChildPath(aParent, pEntry->pszName); - hrc = ptrInfo->init(strPath, strName, (PCSHCLFSOBJINFO)pEntry->pvInfo); - if (FAILED(hrc)) - return hrc; + try + { + com::Utf8Str const strName(pEntry->pszName); + com::Utf8Str const strPath = clipboardTransferMakeChildPath(aParent, pEntry->pszName); + hrc = ptrInfo->init(strPath, strName, (PCSHCLFSOBJINFO)pEntry->pvInfo); + if (FAILED(hrc)) + return hrc; + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } return ptrInfo.queryInterfaceTo(aNode.asOutParam()); } @@ -315,12 +330,17 @@ static HRESULT clipboardTransferOpenList(PSHCLTRANSFER pTransfer, const com::Utf * @param aPath Transfer-relative directory path. * @param aFlags ClipboardTransferListFlag mask. * @param aNodes Where to append listed nodes. + * @param cDepth Current recursion depth. */ static HRESULT clipboardTransferListRecursive(PSHCLTRANSFER pTransfer, const com::Utf8Str &aPath, ULONG aFlags, - std::vector > &aNodes) + std::vector > &aNodes, + uint32_t cDepth) { + if (cDepth >= VBOX_SHCL_MAIN_MAX_RECURSION_DEPTH) + return E_INVALIDARG; + SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; HRESULT hrc = clipboardTransferOpenList(pTransfer, aPath, &hList); if (FAILED(hrc)) @@ -354,9 +374,22 @@ static HRESULT clipboardTransferListRecursive(PSHCLTRANSFER pTransfer, if (SUCCEEDED(hrc)) { Bstr bstrPath; - ptrInfo->COMGETTER(Path)(bstrPath.asOutParam()); - strChild = bstrPath; - aNodes.push_back(ptrInfo); + hrc = ptrInfo->COMGETTER(Path)(bstrPath.asOutParam()); + if (SUCCEEDED(hrc)) + { + try + { + strChild = bstrPath; + if (aNodes.size() >= VBOX_SHCL_MAIN_MAX_RECURSIVE_NODES) + hrc = E_INVALIDARG; + else + aNodes.push_back(ptrInfo); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } } ShClTransferListEntryDestroy(&Entry); if (FAILED(hrc)) @@ -367,7 +400,7 @@ static HRESULT clipboardTransferListRecursive(PSHCLTRANSFER pTransfer, if ( fIsDirectory && !(aFlags & ClipboardTransferListFlag_NoRecursion)) { - hrc = clipboardTransferListRecursive(pTransfer, strChild, aFlags, aNodes); + hrc = clipboardTransferListRecursive(pTransfer, strChild, aFlags, aNodes, cDepth + 1); if (FAILED(hrc)) { ShClTransferListClose(pTransfer, hList); @@ -421,6 +454,11 @@ void ClipboardTransfer::FinalRelease() * @param aAction Clipboard transfer action. * @param aItem Clipboard item being transferred. * @param aProgress Progress object for the transfer. + * @param aTransfer Optional Shared Clipboard transfer backing the data + * plane. If @a fOwnTransfer is false, this method + * borrows the transfer for the lifetime of this object. + * @param fOwnTransfer Whether to take ownership of @a aTransfer and + * destroy it during uninitialization. */ HRESULT ClipboardTransfer::init(ULONG aId, ClipboardTransferDirection_T aDirection, @@ -729,11 +767,7 @@ HRESULT ClipboardTransfer::getData(ComPtr &aData) RT_NOREF(aData); ReturnComNotImplemented(); #else - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -766,8 +800,19 @@ HRESULT ClipboardTransfer::getSourcePaths(std::vector &aSourcePath RT_NOREF(aSourcePaths); ReturnComNotImplemented(); #else - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - aSourcePaths = mData.mSourcePaths; + std::vector SourcePaths; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + try + { + SourcePaths = mData.mSourcePaths; + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer source-path result failed")); + } + } + aSourcePaths.swap(SourcePaths); return S_OK; #endif } @@ -822,13 +867,37 @@ HRESULT ClipboardTransfer::setSourcePaths(const std::vector &aSour PSHCLTRANSFER pOldTransfer = NULL; bool fDestroyOldTransfer = false; + bool fForeignBackend = false; + bool fWrongDirection = false; + bool fWrongState = false; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - pOldTransfer = mData.mTransfer; - fDestroyOldTransfer = mData.mfOwnTransfer && pOldTransfer; - mData.mTransfer = pNewTransfer; - mData.mfOwnTransfer = pNewTransfer != NULL; - mData.mSourcePaths.swap(vecSourcePaths); + fForeignBackend = mData.mTransfer && !mData.mfOwnTransfer; + fWrongDirection = mData.mDirection != ClipboardTransferDirection_ToGuest + || mData.mSource != ClipboardSource_Host; + fWrongState = mData.mState != ClipboardTransferState_Added; + if (!fForeignBackend && !fWrongDirection && !fWrongState) + { + pOldTransfer = mData.mTransfer; + fDestroyOldTransfer = mData.mfOwnTransfer && pOldTransfer; + mData.mTransfer = pNewTransfer; + mData.mfOwnTransfer = pNewTransfer != NULL; + mData.mSourcePaths.swap(vecSourcePaths); + } + } + + if (fForeignBackend || fWrongDirection || fWrongState) + { + if (pNewTransfer) + { + int vrc = ShClTransferDestroy(pNewTransfer); + AssertRC(vrc); + } + if (fForeignBackend) + return setError(E_NOTIMPL, tr("Clipboard transfer source paths cannot replace a foreign data-plane backend")); + if (fWrongDirection) + return setError(E_NOTIMPL, tr("Clipboard transfer source paths currently require a host-to-guest transfer")); + return setError(E_FAIL, tr("Clipboard transfer source paths can only be changed while the transfer is pending")); } if (fDestroyOldTransfer) @@ -853,30 +922,56 @@ HRESULT ClipboardTransfer::roots(std::vector RT_NOREF(aNodes); ReturnComNotImplemented(); #else - aNodes.clear(); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); + std::vector > Nodes; + HRESULT const hrc = i_roots(pTransfer, Nodes); + if (SUCCEEDED(hrc)) + aNodes.swap(Nodes); + return hrc; +#endif +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Returns root nodes from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aNodes Where to return the root nodes. + */ +HRESULT ClipboardTransfer::i_roots(PSHCLTRANSFER pTransfer, + std::vector > &aNodes) +{ + AssertPtrReturn(pTransfer, E_POINTER); + aNodes.clear(); uint64_t const cRoots = ShClTransferRootsCount(pTransfer); for (uint64_t i = 0; i < cRoots; ++i) { - PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTransfer, i); + PCSHCLLISTENTRY const pEntry = ShClTransferRootsEntryGet(pTransfer, i); if (!pEntry) - return setError(VBOX_E_SHCL_NO_DATA, tr("No clipboard transfer root entry exists at index %RU64"), i); + return setErrorBoth(clipboardTransferDataPlaneRcToHrc(VERR_NOT_FOUND), VERR_NOT_FOUND, + tr("No clipboard transfer root entry exists at index %RU64"), i); + ComPtr ptrInfo; HRESULT hrc = clipboardTransferCreateFsObjInfoFromEntry(com::Utf8Str(), pEntry, ptrInfo); if (FAILED(hrc)) return setError(hrc, tr("Creating clipboard transfer root entry information failed")); - aNodes.push_back(ptrInfo); + try + { + aNodes.push_back(ptrInfo); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer root result failed")); + } } return S_OK; -#endif } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /** @@ -897,8 +992,30 @@ HRESULT ClipboardTransfer::query(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Invalid clipboard transfer query path")); + PSHCLTRANSFER const pTransfer = i_getTransfer(); + if (!pTransfer) + return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); + return i_query(pTransfer, aPath, aNode); +#endif +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Queries a node from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aPath Transfer-relative path. + * @param aNode Where to return the node information. + */ +HRESULT ClipboardTransfer::i_query(PSHCLTRANSFER pTransfer, + const com::Utf8Str &aPath, + ComPtr &aNode) +{ + AssertPtrReturn(pTransfer, E_POINTER); std::vector > vecNodes; - hrc = list(com::Utf8Str(), ClipboardTransferListFlag_None, vecNodes); + HRESULT hrc = i_list(pTransfer, com::Utf8Str(), ClipboardTransferListFlag_None, vecNodes); if (FAILED(hrc)) return setError(hrc, tr("Listing clipboard transfer roots for query failed")); for (std::vector >::const_iterator it = vecNodes.begin(); it != vecNodes.end(); ++it) @@ -907,15 +1024,22 @@ HRESULT ClipboardTransfer::query(const com::Utf8Str &aPath, hrc = (*it)->COMGETTER(Path)(bstrPath.asOutParam()); if (FAILED(hrc)) return setError(hrc, tr("Querying clipboard transfer node path failed")); - if (com::Utf8Str(bstrPath) == aPath) + try { - aNode = *it; - return S_OK; + if (com::Utf8Str(bstrPath) == aPath) + { + aNode = *it; + return S_OK; + } + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer query path failed")); } } return setError(VBOX_E_SHCL_NO_DATA, tr("Clipboard transfer path '%s' was not found"), aPath.c_str()); -#endif } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /** @@ -940,24 +1064,53 @@ HRESULT ClipboardTransfer::list(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Invalid clipboard transfer list path")); - aNodes.clear(); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); + std::vector > Nodes; + hrc = i_list(pTransfer, aPath, aFlags, Nodes); + if (SUCCEEDED(hrc)) + aNodes.swap(Nodes); + return hrc; +#endif +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Lists nodes from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aPath Transfer-relative directory path, or empty for roots. + * @param aFlags ClipboardTransferListFlag mask. + * @param aNodes Where to return listed nodes. + */ +HRESULT ClipboardTransfer::i_list(PSHCLTRANSFER pTransfer, + const com::Utf8Str &aPath, + ULONG aFlags, + std::vector > &aNodes) +{ + AssertPtrReturn(pTransfer, E_POINTER); + aNodes.clear(); if (aPath.isEmpty()) { - hrc = roots(aNodes); + HRESULT hrc = i_roots(pTransfer, aNodes); if (FAILED(hrc)) return setError(hrc, tr("Listing clipboard transfer roots failed")); if (aFlags & ClipboardTransferListFlag_NoRecursion) return S_OK; - std::vector > vecRoots = aNodes; + std::vector > vecRoots; + try + { + vecRoots = aNodes; + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer root traversal list failed")); + } for (std::vector >::const_iterator it = vecRoots.begin(); it != vecRoots.end(); ++it) { FsObjType_T enmType = FsObjType_Unknown; @@ -970,7 +1123,14 @@ HRESULT ClipboardTransfer::list(const com::Utf8Str &aPath, hrc2 = (*it)->COMGETTER(Path)(bstrPath.asOutParam()); if (FAILED(hrc2)) return setError(hrc2, tr("Querying clipboard transfer node path failed")); - hrc2 = clipboardTransferListRecursive(pTransfer, com::Utf8Str(bstrPath), aFlags, aNodes); + try + { + hrc2 = clipboardTransferListRecursive(pTransfer, com::Utf8Str(bstrPath), aFlags, aNodes, 0); + } + catch (std::bad_alloc &) + { + hrc2 = E_OUTOFMEMORY; + } if (FAILED(hrc2)) return setError(hrc2, tr("Recursively listing clipboard transfer directory failed")); } @@ -981,17 +1141,24 @@ HRESULT ClipboardTransfer::list(const com::Utf8Str &aPath, if (aFlags & ClipboardTransferListFlag_IncludeRoot) { ComPtr ptrRoot; - hrc = query(aPath, ptrRoot); + HRESULT hrc = i_query(pTransfer, aPath, ptrRoot); if (FAILED(hrc)) return setError(hrc, tr("Querying clipboard transfer list root failed")); - aNodes.push_back(ptrRoot); + try + { + aNodes.push_back(ptrRoot); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer list result failed")); + } } - hrc = clipboardTransferListRecursive(pTransfer, aPath, aFlags, aNodes); + HRESULT hrc = clipboardTransferListRecursive(pTransfer, aPath, aFlags, aNodes, 0); if (FAILED(hrc)) return setError(hrc, tr("Listing clipboard transfer directory failed")); return S_OK; -#endif } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /** @@ -1016,11 +1183,7 @@ HRESULT ClipboardTransfer::openDirectory(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Invalid clipboard transfer directory path")); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -1029,7 +1192,7 @@ HRESULT ClipboardTransfer::openDirectory(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Opening clipboard transfer directory failed")); - ComPtr ptrSelf(this); + ComObjPtr ptrSelf(this); ComObjPtr ptrDirectory; hrc = ptrDirectory.createObject(); @@ -1088,11 +1251,7 @@ HRESULT ClipboardTransfer::openFile(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Invalid clipboard transfer file path")); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); diff --git a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp index 4750bf351cc3..db35f91b25c9 100644 --- a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferManagerImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferManagerImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer manager object. */ @@ -35,6 +35,7 @@ #include "ClipboardTransferManagerImpl.h" #include "GuestShClHelpers.h" #include "ProgressImpl.h" +#include "VirtualBoxErrorInfoImpl.h" #include "VBoxEvents.h" #include @@ -44,6 +45,8 @@ #include #include +#include + // constructor / destructor ///////////////////////////////////////////////////////////////////////////// @@ -73,41 +76,6 @@ void ClipboardTransferManager::FinalRelease() #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** - * Checks whether a transfer key is usable for Main-side lifecycle tracking. - * - * @returns true if the key identifies a non-nil service transfer, false otherwise. - * @param idSession Service session ID. - * @param idTransfer Service transfer ID. - * @param uGeneration Service transfer generation. - */ -static bool clipboardTransferManagerKeyIsValid(SHCLSESSIONID idSession, ULONG idTransfer, SHCLTRANSFERGEN uGeneration) -{ - return idSession != 0 - && idSession != NIL_SHCLSESSIONID - && idTransfer > 0 - && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1 - && uGeneration != 0 - && uGeneration != NIL_SHCLTRANSFERGEN; -} - - -/** - * Checks whether a transfer status ends the Main transfer record lifecycle. - * - * @returns true if the status is terminal, false otherwise. - * @param enmStatus Transfer status to classify. - */ -static bool clipboardTransferManagerStatusIsTerminal(SHCLTRANSFERSTATUS enmStatus) -{ - return enmStatus == SHCLTRANSFERSTATUS_COMPLETED - || enmStatus == SHCLTRANSFERSTATUS_CANCELED - || enmStatus == SHCLTRANSFERSTATUS_KILLED - || enmStatus == SHCLTRANSFERSTATUS_ERROR - || enmStatus == SHCLTRANSFERSTATUS_UNINITIALIZED; -} - - /** * Converts a transfer status to the corresponding public Main transfer state. * @@ -199,46 +167,41 @@ static void clipboardTransferManagerCompleteProgress(const ComPtrNotifyComplete((LONG)clipboardTransferManagerStatusToProgressHrc(enmStatus, vrcTransfer), - NULL /* aErrorInfo */); - AssertComRC(hrc); -} - - -/** - * Validates an optional transfer-relative interaction path. - * - * @returns COM status code. - * @param aPath Path to validate. - * @param fAllowEmpty Whether the empty path is accepted. - */ -static HRESULT clipboardTransferManagerValidatePath(const com::Utf8Str &aPath, bool fAllowEmpty) -{ - if (aPath.isEmpty()) - return fAllowEmpty ? S_OK : E_INVALIDARG; - - const char *pszPath = aPath.c_str(); - if ( pszPath[0] == '/' - || pszPath[0] == '\\' - || strchr(pszPath, '\\') - || strchr(pszPath, ':')) - return E_INVALIDARG; - - const char *psz = pszPath; - while (*psz) + HRESULT const hrcProgress = clipboardTransferManagerStatusToProgressHrc(enmStatus, vrcTransfer); + ComPtr ptrErrorInfo; + if (FAILED(hrcProgress)) { - const char *pszSlash = strchr(psz, '/'); - size_t const cchComponent = pszSlash ? (size_t)(pszSlash - psz) : strlen(psz); - if ( cchComponent == 0 - || (cchComponent == 1 && psz[0] == '.') - || (cchComponent == 2 && psz[0] == '.' && psz[1] == '.')) - return E_INVALIDARG; - if (!pszSlash) - break; - psz = pszSlash + 1; + ComObjPtr ptrErrorInfoImpl; + HRESULT hrc = ptrErrorInfoImpl.createObject(); + if (SUCCEEDED(hrc)) + { + const char *pszText; + if (enmStatus == SHCLTRANSFERSTATUS_CANCELED) + pszText = ClipboardTransferManager::tr("Shared Clipboard transfer was canceled"); + else if (enmStatus == SHCLTRANSFERSTATUS_UNINITIALIZED) + pszText = ClipboardTransferManager::tr("Shared Clipboard transfer was removed before completion"); + else + pszText = ClipboardTransferManager::tr("Shared Clipboard transfer failed"); + try + { + hrc = ptrErrorInfoImpl->initEx(hrcProgress, (LONG)vrcTransfer, COM_IIDOF(IClipboardTransferManager), + "ClipboardTransferManager", com::Utf8Str(pszText)); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + if (SUCCEEDED(hrc)) + ptrErrorInfo = ptrErrorInfoImpl; + } + if (FAILED(hrc)) + AssertComRC(hrc); } - return S_OK; + + HRESULT hrc = ptrProgressControl->NotifyComplete((LONG)hrcProgress, ptrErrorInfo); + AssertComRC(hrc); } + #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -279,109 +242,19 @@ void ClipboardTransferManager::uninit() if (autoUninitSpan.uninitDone()) return; - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - mData.mTransfers.clear(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - mData.mNextTransferId = 1; -#endif - mData.mEventSource.setNull(); - mData.mParent = NULL; -} - - -/** - * Resets the internally tracked transfer list. - */ -void ClipboardTransferManager::i_reset() -{ - LogFunc(("Resetting transfer manager\n")); + std::vector DetachedTransfers; ComPtr ptrEventSource; - std::vector > aTransfers; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - std::vector > aProgressControls; -#endif { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - ptrEventSource = mData.mEventSource; - for (std::vector::const_iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - { - aTransfers.push_back(it->mTransfer); + DetachedTransfers.swap(mData.mTransfers); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - if (it->mProgressControl.isNotNull()) - aProgressControls.push_back(it->mProgressControl); + mData.mNextTransferId = 1; #endif - } - mData.mTransfers.clear(); - Log2Func(("Detached %zu transfers during reset\n", aTransfers.size())); + ptrEventSource = mData.mEventSource; + mData.mEventSource.setNull(); + mData.mParent = NULL; } - RT_NOREF(ptrEventSource); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - for (std::vector >::const_iterator it = aProgressControls.begin(); - it != aProgressControls.end(); ++it) - clipboardTransferManagerCompleteProgress(*it, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); -#endif - for (std::vector >::const_iterator it = aTransfers.begin(); - it != aTransfers.end(); ++it) - { - Log2Func(("Firing transfer removed event during reset: transfer=%p\n", (void *)*it)); - i_fireTransferEvent(*it, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, - com::Utf8Str(), com::Utf8Str(), ClipboardError_None); - } -} - - -/** - * Fires a clipboard transfer event through the parent clipboard object when available. - * If there is no parent clipboard object, emits an anonymous event directly on - * the stored event source. - * - * @param aTransfer Transfer associated with the event. - * @param aState Transfer state. - * @param aInteraction Transfer interaction type. - * @param aPath Transfer-relative path associated with the event, if any. - * @param aMessage Optional event message. - * @param aError Clipboard transfer error code. - */ -void ClipboardTransferManager::i_fireTransferEvent(IClipboardTransfer *aTransfer, - ClipboardTransferState_T aState, - ClipboardTransferInteraction_T aInteraction, - const com::Utf8Str &aPath, - const com::Utf8Str &aMessage, - ClipboardError_T aError) -{ - Clipboard *pParent = NULL; - ComPtr ptrEventSource; - AutoCaller autoCaller; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pParent = mData.mParent; - if (pParent) - autoCaller.attach(pParent); - else - ptrEventSource = mData.mEventSource; - } - - if (pParent) - { - if (SUCCEEDED(autoCaller.hrc())) - pParent->i_fireClipboardTransferEvent(VBOX_SHCL_MAIN_CLIENT_NONE, aTransfer, aState, aInteraction, - aPath, aMessage, aError); - else - LogFunc(("Cannot fire clipboard transfer event through parent: hrc=%#x\n", autoCaller.hrc())); - return; - } - - if (ptrEventSource.isNotNull()) - { - /* - * No parent Clipboard is available to supply a live revision or session - * fan-out context. Keep this legacy event anonymous. - */ - ::FireClipboardTransferEvent(ptrEventSource, 0 /* anonymous revision */, VBOX_SHCL_MAIN_CLIENT_NONE, - aTransfer, aState, aInteraction, Bstr(aPath).raw(), aMessage, aError); - } } @@ -409,24 +282,35 @@ HRESULT ClipboardTransferManager::getTransfers(ClipboardTransferDirection_T aDir && aDirection != ClipboardTransferDirection_ToHost) return setError(E_INVALIDARG, tr("Invalid clipboard transfer direction %RU32"), (uint32_t)aDirection); - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - aTransfers.clear(); - for (std::vector::const_iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) + std::vector > Transfers; { - if (!it->mfPublished) - continue; - if (aDirection != ClipboardTransferDirection_Any) + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + try { - ClipboardTransferDirection_T enmDirection = ClipboardTransferDirection_Any; - HRESULT hrc = it->mTransfer->COMGETTER(Direction)(&enmDirection); - if (FAILED(hrc)) - return setError(hrc, tr("Querying clipboard transfer direction failed")); - if (enmDirection != aDirection) - continue; + Transfers.reserve(mData.mTransfers.size()); + for (std::vector::const_iterator it = mData.mTransfers.begin(); + it != mData.mTransfers.end(); ++it) + { + if (aDirection != ClipboardTransferDirection_Any) + { + ClipboardTransferDirection_T const enmDirection + = it->mDirection == SHCLTRANSFERDIR_FROM_REMOTE + ? ClipboardTransferDirection_ToHost : ClipboardTransferDirection_ToGuest; + if (enmDirection != aDirection) + continue; + } + ComPtr ptrTransfer; + HRESULT const hrc = it->mTransfer.queryInterfaceTo(ptrTransfer.asOutParam()); + AssertComRCReturn(hrc, hrc); + Transfers.push_back(ptrTransfer); + } + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer result list failed")); } - aTransfers.push_back(it->mTransfer); } + aTransfers.swap(Transfers); Log3Func(("cTransfers=%zu\n", aTransfers.size())); return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -434,7 +318,13 @@ HRESULT ClipboardTransferManager::getTransfers(ClipboardTransferDirection_T aDir /** - * Creates an unpublished Main-owned clipboard transfer. + * Creates and tracks a Main-owned clipboard transfer. + * + * @todo Defer the owner bridge until the producer has configured the transfer + * source. Then register the backing transfer with the active Shared + * Clipboard service context and platform backend, record its assigned + * session/transfer/generation key, and define rollback, cancellation, + * unregistration and lifetime handling. * * @returns COM status code. * @param aDirection Transfer direction. @@ -442,10 +332,10 @@ HRESULT ClipboardTransferManager::getTransfers(ClipboardTransferDirection_T aDir * @param aAction Clipboard transfer action. * @param aTransfer Where to return the transfer object. */ -HRESULT ClipboardTransferManager::createTransfer(ClipboardTransferDirection_T aDirection, - ClipboardSource_T aSource, - ClipboardAction_T aAction, - ComPtr &aTransfer) +HRESULT ClipboardTransferManager::create(ClipboardTransferDirection_T aDirection, + ClipboardSource_T aSource, + ClipboardAction_T aAction, + ComPtr &aTransfer) { #ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS RT_NOREF(aDirection, aSource, aAction, aTransfer); @@ -500,76 +390,27 @@ HRESULT ClipboardTransferManager::createTransfer(ClipboardTransferDirection_T aD AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); Data::TransferRecord Record; Record.mTransferId = idTransfer; - Record.mTransfer = ptrTransfer; - Record.mfPublished = false; - mData.mTransfers.push_back(Record); - } - - aTransfer = ptrTransfer; - return S_OK; -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ -} - - -/** - * Adds a clipboard transfer. - * - * @returns COM status code. - * @param aTransfer Transfer to add. - */ -HRESULT ClipboardTransferManager::add(const ComPtr &aTransfer) -{ -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer); - ReturnComNotImplemented(); -#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - - Log2Func(("aTransfer=%p\n", (void *)aTransfer)); - if (aTransfer.isNull()) - { - LogFunc(("Rejecting NULL transfer add\n")); - return setError(E_INVALIDARG, tr("Clipboard transfer to add must not be NULL")); - } - - ULONG idTransfer = 0; - HRESULT hrc = aTransfer->COMGETTER(Id)(&idTransfer); - if (FAILED(hrc)) - return setError(hrc, tr("Querying clipboard transfer ID failed")); - - bool fFireEvent = false; - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - bool fKnown = false; - for (std::vector::iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) - { - if (it->mfPublished) - return S_OK; - it->mfPublished = true; - fKnown = true; - Log2Func(("Published existing transfer: cTransfers=%zu\n", mData.mTransfers.size())); - break; - } - - if (!fKnown) + Record.mDirection = aDirection == ClipboardTransferDirection_ToHost + ? SHCLTRANSFERDIR_FROM_REMOTE : SHCLTRANSFERDIR_TO_REMOTE; + Record.mSource = aSource == ClipboardSource_Host + ? SHCLSOURCE_LOCAL + : aSource == ClipboardSource_Guest ? SHCLSOURCE_REMOTE : SHCLSOURCE_INVALID; + Record.mTransfer = ptrTransferObj; + try { - Data::TransferRecord Record; - Record.mTransferId = idTransfer; - Record.mTransfer = aTransfer; - Record.mfPublished = true; mData.mTransfers.push_back(Record); - Log2Func(("Added transfer: cTransfers=%zu\n", mData.mTransfers.size())); } - fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } } - if (fFireEvent) - { - Log2Func(("Firing transfer added event: transfer=%p\n", (void *)aTransfer)); - i_fireTransferEvent(aTransfer, ClipboardTransferState_Added, ClipboardTransferInteraction_None, - com::Utf8Str(), com::Utf8Str(), ClipboardError_None); - } + aTransfer = ptrTransfer; + ClipboardTransfer *pTransfer = ptrTransferObj; + Log2Func(("Firing transfer added event: transfer=%p\n", (void *)pTransfer)); + i_fireTransferEvent(ptrTransferObj, ClipboardTransferState_Added, ClipboardTransferInteraction_None, + com::Utf8Str(), com::Utf8Str(), ClipboardError_None); return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ } @@ -583,11 +424,6 @@ HRESULT ClipboardTransferManager::add(const ComPtr &aTransfe */ HRESULT ClipboardTransferManager::remove(const ComPtr &aTransfer) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer); - ReturnComNotImplemented(); -#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - Log2Func(("aTransfer=%p\n", (void *)aTransfer)); if (aTransfer.isNull()) { @@ -595,34 +431,50 @@ HRESULT ClipboardTransferManager::remove(const ComPtr &aTran return setError(E_INVALIDARG, tr("Clipboard transfer to remove must not be NULL")); } +#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RT_NOREF(aTransfer); + ReturnComNotImplemented(); +#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + ComObjPtr ptrTransfer; bool fRemoved = false; bool fFireEvent = false; + bool fServiceTransfer = false; ComPtr ptrProgressControl; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - for (std::vector::iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) + Data::TransferRecords::iterator it = mData.findTransferRecord(aTransfer); + if (it != mData.mTransfers.end()) + { + ptrTransfer = it->mTransfer; + if (ShClTransferKeyIsValid(it->mServiceSessionId, it->mTransferId, it->mGeneration)) + fServiceTransfer = true; + else { ptrProgressControl = it->mProgressControl; mData.mTransfers.erase(it); Log2Func(("Removed transfer: cTransfers=%zu\n", mData.mTransfers.size())); fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); fRemoved = true; - break; } + } } - if (fRemoved) - clipboardTransferManagerCompleteProgress(ptrProgressControl, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); - if (fRemoved && fFireEvent) + if (fServiceTransfer) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot remove an active service transfer; cancel it first")); + if (!fRemoved) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is no longer owned by this manager")); + + ClipboardTransfer *pTransfer = ptrTransfer; + ptrTransfer->i_setState(ClipboardTransferState_Removed, com::Utf8Str(), ClipboardError_None); + clipboardTransferManagerCompleteProgress(ptrProgressControl, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); + if (fFireEvent) { - Log2Func(("Firing transfer removed event: transfer=%p\n", (void *)aTransfer)); - i_fireTransferEvent(aTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, + Log2Func(("Firing transfer removed event: transfer=%p\n", (void *)pTransfer)); + i_fireTransferEvent(ptrTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); } - else if (!fRemoved) - Log2Func(("Transfer not found for remove: transfer=%p\n", (void *)aTransfer)); return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ } @@ -636,11 +488,6 @@ HRESULT ClipboardTransferManager::remove(const ComPtr &aTran */ HRESULT ClipboardTransferManager::cancel(const ComPtr &aTransfer) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer); - ReturnComNotImplemented(); -#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - Log2Func(("aTransfer=%p\n", (void *)aTransfer)); if (aTransfer.isNull()) { @@ -648,37 +495,54 @@ HRESULT ClipboardTransferManager::cancel(const ComPtr &aTran return setError(E_INVALIDARG, tr("Clipboard transfer to cancel must not be NULL")); } +#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RT_NOREF(aTransfer); + ReturnComNotImplemented(); +#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + ComObjPtr ptrTransfer; bool fCanceled = false; bool fFireEvent = false; bool fNeedsHostCancel = false; + bool fCancelAlreadyRequested = false; SHCLSESSIONID idSession = NIL_SHCLSESSIONID; ULONG idTransfer = 0; SHCLTRANSFERGEN uGeneration = NIL_SHCLTRANSFERGEN; ComPtr ptrProgressControl; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - for (std::vector::iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) + Data::TransferRecords::iterator it = mData.findTransferRecord(aTransfer); + if (it != mData.mTransfers.end()) + { + ptrTransfer = it->mTransfer; + idSession = it->mServiceSessionId; + idTransfer = it->mTransferId; + uGeneration = it->mGeneration; + fNeedsHostCancel = ShClTransferKeyIsValid(idSession, idTransfer, uGeneration); + if (!fNeedsHostCancel) { - idSession = it->mServiceSessionId; - idTransfer = it->mTransferId; - uGeneration = it->mGeneration; - fNeedsHostCancel = clipboardTransferManagerKeyIsValid(idSession, idTransfer, uGeneration); - if (!fNeedsHostCancel) - { - ptrProgressControl = it->mProgressControl; - mData.mTransfers.erase(it); - Log2Func(("Canceled transfer: cTransfers=%zu\n", mData.mTransfers.size())); - fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); - fCanceled = true; - } + ptrProgressControl = it->mProgressControl; + mData.mTransfers.erase(it); + Log2Func(("Canceled transfer: cTransfers=%zu\n", mData.mTransfers.size())); + fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); + fCanceled = true; + } + else + { + if (it->mfCancelRequested) + fCancelAlreadyRequested = true; else it->mfCancelRequested = true; - break; } + } } + if (fCancelAlreadyRequested) + return setError(VBOX_E_OBJECT_IN_USE, tr("Clipboard transfer cancellation is already in progress")); + if (!fCanceled && !fNeedsHostCancel) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is no longer owned by this manager")); + + ClipboardTransfer *pTransfer = ptrTransfer; if (fNeedsHostCancel) { Clipboard *pParent = NULL; @@ -690,40 +554,63 @@ HRESULT ClipboardTransferManager::cancel(const ComPtr &aTran autoCaller.attach(pParent); } if (!pParent) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Data::TransferRecords::iterator it = mData.findTransferRecord(pTransfer, idSession, + idTransfer, uGeneration); + if (it != mData.mTransfers.end()) + it->mfCancelRequested = false; return setError(E_FAIL, tr("Clipboard transfer cannot be canceled because no clipboard backend is available")); + } if (FAILED(autoCaller.hrc())) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Data::TransferRecords::iterator it = mData.findTransferRecord(pTransfer, idSession, + idTransfer, uGeneration); + if (it != mData.mTransfers.end()) + it->mfCancelRequested = false; return setError(autoCaller.hrc(), tr("Clipboard backend is not ready for canceling clipboard transfers")); + } - HRESULT hrc = pParent->i_transferCancel(idSession, (SHCLTRANSFERID)idTransfer, uGeneration); + HRESULT const hrc = pParent->i_transferCancel(idSession, (SHCLTRANSFERID)idTransfer, uGeneration); if (FAILED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Data::TransferRecords::iterator it = mData.findTransferRecord(pTransfer, idSession, + idTransfer, uGeneration); + if (it != mData.mTransfers.end()) + it->mfCancelRequested = false; return setError(hrc, tr("Canceling clipboard transfer through the backend failed")); + } { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - for (std::vector::iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) - { - ptrProgressControl = it->mProgressControl; - mData.mTransfers.erase(it); - Log2Func(("Canceled transfer after host request: cTransfers=%zu\n", mData.mTransfers.size())); - fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); - fCanceled = true; - break; - } + Data::TransferRecords::iterator it = mData.findTransferRecord(pTransfer, idSession, + idTransfer, uGeneration); + if (it != mData.mTransfers.end()) + { + ptrProgressControl = it->mProgressControl; + mData.mTransfers.erase(it); + Log2Func(("Canceled transfer after host request: cTransfers=%zu\n", mData.mTransfers.size())); + fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); + fCanceled = true; + } } } if (fCanceled) + { + ptrTransfer->i_setState(ClipboardTransferState_Canceled, com::Utf8Str(), ClipboardError_None); clipboardTransferManagerCompleteProgress(ptrProgressControl, SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + } if (fCanceled && fFireEvent) { - Log2Func(("Firing transfer canceled event: transfer=%p\n", (void *)aTransfer)); - i_fireTransferEvent(aTransfer, ClipboardTransferState_Canceled, ClipboardTransferInteraction_None, + Log2Func(("Firing transfer canceled event: transfer=%p\n", (void *)pTransfer)); + i_fireTransferEvent(ptrTransfer, ClipboardTransferState_Canceled, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); } else if (!fCanceled) - Log2Func(("Transfer not found for cancel: transfer=%p\n", (void *)aTransfer)); + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is no longer owned by this manager")); return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ } @@ -738,15 +625,19 @@ HRESULT ClipboardTransferManager::cancel(const ComPtr &aTran */ HRESULT ClipboardTransferManager::approve(const ComPtr &aTransfer, ULONG aFlags) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer, aFlags); - ReturnComNotImplemented(); -#else - if (aFlags != 0) - return setError(E_INVALIDARG, tr("Invalid clipboard transfer approval flags %RU32"), aFlags); - return respond(aTransfer, ClipboardTransferInteraction_Approval, com::Utf8Str(), ClipboardTransferResponse_Accept, - com::Utf8Str(), 0); + if (aTransfer.isNull()) + return setError(E_INVALIDARG, tr("Clipboard transfer to approve must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); + } + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); #endif + RT_NOREF(aFlags); + ReturnComNotImplemented(); } @@ -759,17 +650,19 @@ HRESULT ClipboardTransferManager::approve(const ComPtr &aTra */ HRESULT ClipboardTransferManager::deny(const ComPtr &aTransfer, const com::Utf8Str &aReason) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer, aReason); - ReturnComNotImplemented(); -#else - HRESULT hrc = respond(aTransfer, ClipboardTransferInteraction_Approval, com::Utf8Str(), ClipboardTransferResponse_Reject, - com::Utf8Str(), 0); - if (SUCCEEDED(hrc) && aReason.isNotEmpty()) - i_fireTransferEvent(aTransfer, ClipboardTransferState_Canceled, ClipboardTransferInteraction_Approval, - com::Utf8Str(), aReason, ClipboardError_AccessDenied); - return hrc; + if (aTransfer.isNull()) + return setError(E_INVALIDARG, tr("Clipboard transfer to deny must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); + } + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); #endif + RT_NOREF(aReason); + ReturnComNotImplemented(); } @@ -791,51 +684,19 @@ HRESULT ClipboardTransferManager::respond(const ComPtr &aTra const com::Utf8Str &aResponsePath, ULONG aFlags) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer, aInteraction, aPath, aResponse, aResponsePath, aFlags); - ReturnComNotImplemented(); -#else if (aTransfer.isNull()) - return setError(E_INVALIDARG, tr("Clipboard transfer response requires a transfer object")); - if (aInteraction == ClipboardTransferInteraction_None) - return setError(E_INVALIDARG, tr("Invalid clipboard transfer interaction %RU32"), (uint32_t)aInteraction); - if (aResponse == ClipboardTransferResponse_None) - return setError(E_INVALIDARG, tr("Invalid clipboard transfer response %RU32"), (uint32_t)aResponse); - if (aFlags != 0) - return setError(E_INVALIDARG, tr("Invalid clipboard transfer response flags %RU32"), aFlags); - if ( aResponsePath.isNotEmpty() - && aInteraction != ClipboardTransferInteraction_Destination - && aInteraction != ClipboardTransferInteraction_Rename) - return setError(E_INVALIDARG, tr("Clipboard transfer response path is only valid for destination or rename interactions")); - HRESULT hrc = clipboardTransferManagerValidatePath(aPath, true /* fAllowEmpty */); - if (FAILED(hrc)) - return setError(hrc, tr("Invalid clipboard transfer interaction path")); - hrc = clipboardTransferManagerValidatePath(aResponsePath, true /* fAllowEmpty */); - if (FAILED(hrc)) - return setError(hrc, tr("Invalid clipboard transfer response path")); - - bool fKnown = false; + return setError(E_INVALIDARG, tr("Clipboard transfer awaiting a response must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - for (std::vector::const_iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) - { - fKnown = true; - break; - } + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); } - if (!fKnown) - return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer object is not known to this manager")); - - ClipboardTransferState_T const enmState = aResponse == ClipboardTransferResponse_Reject - || aResponse == ClipboardTransferResponse_Cancel - ? ClipboardTransferState_Canceled : ClipboardTransferState_InProgress; - ClipboardError_T const enmError = enmState == ClipboardTransferState_Canceled - ? ClipboardError_AccessDenied : ClipboardError_None; - i_fireTransferEvent(aTransfer, enmState, aInteraction, aPath, com::Utf8Str(), enmError); - return S_OK; + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); #endif + RT_NOREF(aInteraction, aPath, aResponse, aResponsePath, aFlags); + ReturnComNotImplemented(); } @@ -847,7 +708,17 @@ HRESULT ClipboardTransferManager::respond(const ComPtr &aTra */ HRESULT ClipboardTransferManager::pause(const ComPtr &aTransfer) { - RT_NOREF(aTransfer); + if (aTransfer.isNull()) + return setError(E_INVALIDARG, tr("Clipboard transfer to pause must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); + } + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); +#endif ReturnComNotImplemented(); } @@ -860,7 +731,17 @@ HRESULT ClipboardTransferManager::pause(const ComPtr &aTrans */ HRESULT ClipboardTransferManager::resume(const ComPtr &aTransfer) { - RT_NOREF(aTransfer); + if (aTransfer.isNull()) + return setError(E_INVALIDARG, tr("Clipboard transfer to resume must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); + } + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); +#endif ReturnComNotImplemented(); } @@ -878,31 +759,40 @@ HRESULT ClipboardTransferManager::reset() LogFunc(("Resetting transfer manager via public API\n")); ComPtr ptrEventSource; - std::vector > aTransfers; - std::vector > aProgressControls; + std::vector DetachedTransfers; + bool fHasServiceTransfers = false; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - ptrEventSource = mData.mEventSource; for (std::vector::const_iterator it = mData.mTransfers.begin(); it != mData.mTransfers.end(); ++it) + if (ShClTransferKeyIsValid(it->mServiceSessionId, it->mTransferId, it->mGeneration)) + { + fHasServiceTransfers = true; + break; + } + + if (!fHasServiceTransfers) { - aTransfers.push_back(it->mTransfer); - if (it->mProgressControl.isNotNull()) - aProgressControls.push_back(it->mProgressControl); + ptrEventSource = mData.mEventSource; + DetachedTransfers.swap(mData.mTransfers); + Log2Func(("Detached %zu transfers during public reset\n", DetachedTransfers.size())); } - mData.mTransfers.clear(); - Log2Func(("Detached %zu transfers during public reset\n", aTransfers.size())); } + if (fHasServiceTransfers) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot reset clipboard transfers while a service transfer is active; cancel it first")); + RT_NOREF(ptrEventSource); - for (std::vector >::const_iterator it = aProgressControls.begin(); - it != aProgressControls.end(); ++it) - clipboardTransferManagerCompleteProgress(*it, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); - for (std::vector >::const_iterator it = aTransfers.begin(); - it != aTransfers.end(); ++it) + for (std::vector::const_iterator it = DetachedTransfers.begin(); + it != DetachedTransfers.end(); ++it) { - Log2Func(("Firing transfer removed event during public reset: transfer=%p\n", (void *)*it)); - i_fireTransferEvent(*it, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, + ClipboardTransfer *pTransfer = it->mTransfer; + it->mTransfer->i_setState(ClipboardTransferState_Removed, com::Utf8Str(), ClipboardError_None); + clipboardTransferManagerCompleteProgress(it->mProgressControl, + SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); + Log2Func(("Firing transfer removed event during public reset: transfer=%p\n", (void *)pTransfer)); + i_fireTransferEvent(it->mTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); } return S_OK; @@ -911,6 +801,101 @@ HRESULT ClipboardTransferManager::reset() #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Resets the internally tracked transfer list. + */ +void ClipboardTransferManager::i_reset() +{ + LogFunc(("Resetting transfer manager\n")); + ComPtr ptrEventSource; + std::vector DetachedTransfers; + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrEventSource = mData.mEventSource; + DetachedTransfers.swap(mData.mTransfers); + Log2Func(("Detached %zu transfers during reset\n", DetachedTransfers.size())); + } + + RT_NOREF(ptrEventSource); + for (std::vector::const_iterator it = DetachedTransfers.begin(); + it != DetachedTransfers.end(); ++it) + { + it->mTransfer->i_setState(ClipboardTransferState_Removed, com::Utf8Str(), ClipboardError_None); + clipboardTransferManagerCompleteProgress(it->mProgressControl, + SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); + } + for (std::vector::const_iterator it = DetachedTransfers.begin(); + it != DetachedTransfers.end(); ++it) + { + ClipboardTransfer *pTransfer = it->mTransfer; + Log2Func(("Firing transfer removed event during reset: transfer=%p\n", (void *)pTransfer)); + i_fireTransferEvent(it->mTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, + com::Utf8Str(), com::Utf8Str(), ClipboardError_None); + } +} + + +/** + * Fires a clipboard transfer event through the parent clipboard object when available. + * If there is no parent clipboard object, emits an anonymous event directly on + * the stored event source. + * + * @param aTransfer Transfer associated with the event. + * @param aState Transfer state. + * @param aInteraction Transfer interaction type. + * @param aPath Transfer-relative path associated with the event, if any. + * @param aMessage Optional event message. + * @param aError Clipboard transfer error code. + */ +void ClipboardTransferManager::i_fireTransferEvent(const ComObjPtr &aTransfer, + ClipboardTransferState_T aState, + ClipboardTransferInteraction_T aInteraction, + const com::Utf8Str &aPath, + const com::Utf8Str &aMessage, + ClipboardError_T aError) +{ + ComPtr ptrTransfer; + HRESULT const hrc = aTransfer.queryInterfaceTo(ptrTransfer.asOutParam()); + if (FAILED(hrc)) + { + AssertComRC(hrc); + return; + } + + Clipboard *pParent = NULL; + ComPtr ptrEventSource; + AutoCaller autoCaller; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + pParent = mData.mParent; + if (pParent) + autoCaller.attach(pParent); + else + ptrEventSource = mData.mEventSource; + } + + if (pParent) + { + if (SUCCEEDED(autoCaller.hrc())) + pParent->i_fireClipboardTransferEvent(VBOX_SHCL_MAIN_CLIENT_NONE, ptrTransfer, aState, aInteraction, + aPath, aMessage, aError); + else + LogFunc(("Cannot fire clipboard transfer event through parent: hrc=%#x\n", autoCaller.hrc())); + return; + } + + if (ptrEventSource.isNotNull()) + { + /* + * No parent Clipboard is available to supply a live revision or session + * fan-out context. Keep this legacy event anonymous. + */ + ::FireClipboardTransferEvent(ptrEventSource, 0 /* anonymous revision */, VBOX_SHCL_MAIN_CLIENT_NONE, + ptrTransfer, aState, aInteraction, Bstr(aPath).raw(), aMessage, aError); + } +} + + /** * Handles a Shared Clipboard transfer status from the host service. * @@ -918,13 +903,15 @@ HRESULT ClipboardTransferManager::reset() * @param aServiceSessionId Service session that owns the transfer. * @param aTransferId Shared Clipboard transfer ID. * @param aGeneration Host-private transfer generation. - * @param enmShClSource Shared Clipboard status source. + * @param aTransfer Borrowed service transfer used to validate status metadata. + * @param enmShClSource Data source recorded by the backing transfer. * @param enmStatus Transfer lifecycle status. * @param vrcTransfer Transfer result code associated with the status. */ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration, + PSHCLTRANSFER aTransfer, SHCLSOURCE enmShClSource, SHCLTRANSFERSTATUS enmStatus, int vrcTransfer) @@ -932,16 +919,48 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS SHCLSESSIONID const idSession = aServiceSessionId; SHCLTRANSFERID const idTransfer = aTransferId; SHCLTRANSFERGEN const uGeneration = aGeneration; - if (!clipboardTransferManagerKeyIsValid(idSession, idTransfer, uGeneration)) + if (!ShClTransferKeyIsValid(idSession, idTransfer, uGeneration)) + return E_INVALIDARG; + if ( enmShClSource != SHCLSOURCE_LOCAL + && enmShClSource != SHCLSOURCE_REMOTE) + return E_INVALIDARG; + if (!ShClTransferStatusIsValid(enmStatus)) + return E_INVALIDARG; + if (!ShClTransferStatusResultIsValid(enmStatus, vrcTransfer)) + return E_INVALIDARG; + + SHCLTRANSFERDIR enmTransferDirection; + SHCLSOURCE enmTransferSource; + if (aTransfer) + { + if (ShClTransferGetSource(aTransfer) != enmShClSource) + return E_INVALIDARG; + enmTransferDirection = ShClTransferGetDir(aTransfer); + enmTransferSource = ShClTransferGetSource(aTransfer); + } + else + { + enmTransferDirection = enmShClSource == SHCLSOURCE_REMOTE + ? SHCLTRANSFERDIR_FROM_REMOTE : SHCLTRANSFERDIR_TO_REMOTE; + enmTransferSource = enmShClSource; + } + if ( ( enmTransferDirection != SHCLTRANSFERDIR_FROM_REMOTE + && enmTransferDirection != SHCLTRANSFERDIR_TO_REMOTE) + || ( enmTransferSource != SHCLSOURCE_LOCAL + && enmTransferSource != SHCLSOURCE_REMOTE) + || ( enmTransferSource == SHCLSOURCE_LOCAL + && enmTransferDirection != SHCLTRANSFERDIR_TO_REMOTE) + || ( enmTransferSource == SHCLSOURCE_REMOTE + && enmTransferDirection != SHCLTRANSFERDIR_FROM_REMOTE)) return E_INVALIDARG; if (enmStatus == SHCLTRANSFERSTATUS_NONE) return S_OK; ClipboardTransferState_T const enmState = clipboardTransferManagerStatusToState(enmStatus); ClipboardError_T const enmError = clipboardTransferManagerStatusToError(enmStatus, vrcTransfer); - bool const fTerminal = clipboardTransferManagerStatusIsTerminal(enmStatus); + bool const fTerminal = ShClTransferStatusIsTerminal(enmStatus); - ComPtr ptrTransfer; + ComObjPtr ptrTransfer; ComPtr ptrProgress; ComPtr ptrProgressControl; bool fFireAdded = false; @@ -952,9 +971,7 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS size_t idxRecord = mData.mTransfers.size(); for (size_t i = 0; i < mData.mTransfers.size(); ++i) - if ( mData.mTransfers[i].mServiceSessionId == idSession - && mData.mTransfers[i].mTransferId == idTransfer - && mData.mTransfers[i].mGeneration == uGeneration) + if (mData.mTransfers[i].matches(idSession, idTransfer, uGeneration)) { idxRecord = i; break; @@ -968,13 +985,19 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS idSession, idTransfer, uGeneration, (uint32_t)enmStatus)); return S_OK; } + if ( enmStatus != SHCLTRANSFERSTATUS_REQUESTED + && enmStatus != SHCLTRANSFERSTATUS_INITIALIZED) + return E_INVALIDARG; ComObjPtr ptrNewProgress; HRESULT hrc = ptrNewProgress.createObject(); if (FAILED(hrc)) return hrc; - hrc = ptrNewProgress->init(FALSE /* aCancelable */, 1 /* aOperationCount */, - com::Utf8Str("Shared Clipboard transfer")); + ComPtr ptrProgressInitiator = mData.mEventSource; + if (ptrProgressInitiator.isNull()) + return E_FAIL; + hrc = ptrNewProgress->init(ptrProgressInitiator, + com::Utf8Str("Shared Clipboard transfer"), FALSE /* aCancelable */); if (FAILED(hrc)) return hrc; @@ -992,19 +1015,15 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS if (FAILED(hrc)) return hrc; - ClipboardTransferDirection_T const enmDirection = enmShClSource == SHCLSOURCE_REMOTE + ClipboardTransferDirection_T const enmDirection = enmTransferDirection == SHCLTRANSFERDIR_FROM_REMOTE ? ClipboardTransferDirection_ToHost : ClipboardTransferDirection_ToGuest; - ClipboardSource_T const enmSource = enmShClSource == SHCLSOURCE_REMOTE + ClipboardSource_T const enmSource = enmTransferSource == SHCLSOURCE_REMOTE ? ClipboardSource_Guest : ClipboardSource_Host; ComPtr ptrItem; - hrc = ptrNewTransfer->init(idTransfer, enmDirection, enmSource, ClipboardAction_Copy, ptrItem, ptrIProgress); - if (FAILED(hrc)) - return hrc; - - ComPtr ptrITransfer; - hrc = ptrNewTransfer.queryInterfaceTo(ptrITransfer.asOutParam()); + hrc = ptrNewTransfer->init(idTransfer, enmDirection, enmSource, ClipboardAction_Copy, ptrItem, ptrIProgress, + NULL /* aTransfer */, false /* fOwnTransfer */); if (FAILED(hrc)) return hrc; @@ -1012,18 +1031,31 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS Record.mServiceSessionId = idSession; Record.mTransferId = idTransfer; Record.mGeneration = uGeneration; + Record.mDirection = enmTransferDirection; + Record.mSource = enmTransferSource; Record.mStatus = enmStatus; Record.mState = ClipboardTransferState_Added; - Record.mTransfer = ptrITransfer; - Record.mfPublished = true; + Record.mTransfer = ptrNewTransfer; Record.mProgress = ptrIProgress; Record.mProgressControl = ptrIProgressControl; - mData.mTransfers.push_back(Record); + try + { + mData.mTransfers.push_back(Record); + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } idxRecord = mData.mTransfers.size() - 1; fFireAdded = true; } Data::TransferRecord &Record = mData.mTransfers[idxRecord]; + if ( Record.mDirection != enmTransferDirection + || Record.mSource != enmTransferSource + || !ShClTransferStatusTransitionIsValid(Record.mStatus, enmStatus)) + return E_INVALIDARG; + ptrTransfer = Record.mTransfer; ptrProgress = Record.mProgress; ptrProgressControl = Record.mProgressControl; @@ -1047,15 +1079,20 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS RT_NOREF(ptrProgress); - if (fTerminal) - clipboardTransferManagerCompleteProgress(ptrProgressControl, enmStatus, vrcTransfer); - if (fFireAdded) + { + ptrTransfer->i_setState(ClipboardTransferState_Added, com::Utf8Str(), ClipboardError_None); i_fireTransferEvent(ptrTransfer, ClipboardTransferState_Added, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); + } if (fFireState) + { + ptrTransfer->i_setState(enmState, com::Utf8Str(), enmError); + if (fTerminal) + clipboardTransferManagerCompleteProgress(ptrProgressControl, enmStatus, vrcTransfer); i_fireTransferEvent(ptrTransfer, enmState, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), enmError); + } return S_OK; } @@ -1069,7 +1106,7 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS */ HRESULT ClipboardTransferManager::i_cancelTransferById(ULONG aTransferId) { - ComPtr ptrTransfer; + ComObjPtr ptrTransfer; { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); for (std::vector::const_iterator it = mData.mTransfers.begin(); @@ -1085,6 +1122,9 @@ HRESULT ClipboardTransferManager::i_cancelTransferById(ULONG aTransferId) if (ptrTransfer.isNull()) return E_INVALIDARG; - return cancel(ptrTransfer); + ComPtr ptrITransfer; + HRESULT const hrc = ptrTransfer.queryInterfaceTo(ptrITransfer.asOutParam()); + AssertComRCReturn(hrc, hrc); + return cancel(ptrITransfer); } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ diff --git a/src/VBox/Main/src-client/GuestShClPrivate.cpp b/src/VBox/Main/src-client/GuestShClPrivate.cpp index e868c2fe8866..1641d5181696 100644 --- a/src/VBox/Main/src-client/GuestShClPrivate.cpp +++ b/src/VBox/Main/src-client/GuestShClPrivate.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClPrivate.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClPrivate.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Private Shared Clipboard code. */ @@ -501,18 +501,17 @@ int GuestShCl::ReadDataFromHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbDat SHCLCLIENTCMDCTX cmdCtx; RT_ZERO(cmdCtx); - int vrc = lock(); + PSHCLCLIENT pClient = NULL; + int vrc = i_beginGuestRead(&pClient); if (RT_FAILURE(vrc)) return vrc; - PSHCLCLIENT pClient = m_pClient; - if ( pClient - && pClient->pBackend) + if (pClient->pBackend) vrc = ShClBackendReadData(pClient->pBackend, pClient, &cmdCtx, uFormat, pvData, cbData, pcbActual); else vrc = VERR_SHCLPB_NO_DATA; - unlock(); + i_endGuestRead(); return vrc; } @@ -529,10 +528,14 @@ int GuestShCl::ReportFormatsToHost(SHCLFORMATS fFormats) return vrc; ++m_uGuestDataSeq; + unlock(); - PSHCLCLIENT pClient = m_pClient; - if ( pClient - && pClient->pBackend) + PSHCLCLIENT pClient = NULL; + vrc = i_beginGuestRead(&pClient); + if (RT_FAILURE(vrc)) + return vrc == VERR_SHCLPB_NO_DATA ? VINF_SUCCESS : vrc; + + if (pClient->pBackend) { #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS fFormats = shClSvcHandleFormats(false /* fHostToGuest */, pClient, fFormats); @@ -542,7 +545,7 @@ int GuestShCl::ReportFormatsToHost(SHCLFORMATS fFormats) else vrc = VINF_SUCCESS; - unlock(); + i_endGuestRead(); return vrc; } @@ -563,18 +566,17 @@ int GuestShCl::WriteDataToHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbData SHCLCLIENTCMDCTX cmdCtx; RT_ZERO(cmdCtx); - int vrc = lock(); + PSHCLCLIENT pClient = NULL; + int vrc = i_beginGuestRead(&pClient); if (RT_FAILURE(vrc)) - return vrc; + return vrc == VERR_SHCLPB_NO_DATA ? VINF_SUCCESS : vrc; - PSHCLCLIENT pClient = m_pClient; - if ( pClient - && pClient->pBackend) + if (pClient->pBackend) vrc = ShClBackendWriteData(pClient->pBackend, pClient, &cmdCtx, uFormat, pvData, cbData); else vrc = VINF_SUCCESS; - unlock(); + i_endGuestRead(); return vrc; } @@ -625,12 +627,10 @@ int GuestShCl::ReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, S switch (enmSource) { case SHCLSOURCE_LOCAL: - i_incHostDataSeq(); enmClipboardSource = ClipboardSource_Host; break; case SHCLSOURCE_REMOTE: - i_incGuestDataSeq(); enmClipboardSource = ClipboardSource_Guest; break; @@ -638,15 +638,34 @@ int GuestShCl::ReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, S AssertFailedReturn(VERR_INVALID_PARAMETER); } + /* Reuse the guest-read lifetime guard to keep the weak service client valid + * while the platform backend reports the formats. */ + PSHCLCLIENT pActiveClient = NULL; + int vrc = i_beginGuestRead(&pActiveClient); + if (RT_FAILURE(vrc)) + return vrc; + if (pClient != pActiveClient) + { + i_endGuestRead(); + return VERR_SHCLPB_NO_DATA; + } + + if (enmSource == SHCLSOURCE_LOCAL) + i_incHostDataSeq(); + else + i_incGuestDataSeq(); + #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); #endif - int vrc; if (pClient->pBackend) vrc = ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); else vrc = VINF_SUCCESS; + + i_endGuestRead(); + if (RT_SUCCESS(vrc)) { AssertPtr(m_pConsole->i_getClipboard()); diff --git a/src/VBox/Main/src-client/GuestShClSvcExt.cpp b/src/VBox/Main/src-client/GuestShClSvcExt.cpp index a6f4ab150a03..2cecba8cab98 100644 --- a/src/VBox/Main/src-client/GuestShClSvcExt.cpp +++ b/src/VBox/Main/src-client/GuestShClSvcExt.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClSvcExt.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClSvcExt.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard service extension handling for Main. */ @@ -31,6 +31,7 @@ #include "ConsoleImpl.h" #include "ClipboardImpl.h" #include "GuestShClPrivate.h" +#include "Global.h" #include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS @@ -47,34 +48,6 @@ static size_t const s_cchShClSvcExtStringMax = _64K; -/** - * Checks whether a Shared Clipboard format mask contains only known format bits. - * - * @returns true if \a fFormats only contains VBOX_SHCL_FMT_XXX bits, false otherwise. - * @param fFormats Format mask to validate. - * @param fAllowNone Whether VBOX_SHCL_FMT_NONE is accepted. - */ -static bool shClSvcExtIsValidFormats(SHCLFORMATS fFormats, bool fAllowNone) -{ - if (fFormats == VBOX_SHCL_FMT_NONE) - return fAllowNone; - return (fFormats & ~VBOX_SHCL_FMT_VALID_MASK) == 0; -} - -/** - * Checks whether a value names exactly one Shared Clipboard format. - * - * @returns true if \a uFormat is a single valid VBOX_SHCL_FMT_XXX bit, false otherwise. - * @param uFormat Format value to validate. - */ -static bool shClSvcExtIsValidFormat(SHCLFORMAT uFormat) -{ - return uFormat != VBOX_SHCL_FMT_NONE - && (uFormat & ~VBOX_SHCL_FMT_VALID_MASK) == 0 - && (uFormat & (uFormat - 1)) == 0; -} - - /** * Validates a single Shared Clipboard format from service-extension parameters. * @@ -86,53 +59,13 @@ static bool shClSvcExtIsValidFormat(SHCLFORMAT uFormat) */ static int shClSvcExtValidateFormat(SHCLFORMAT uFormat, uint32_t u32Function) { - if (shClSvcExtIsValidFormat(uFormat)) + if (ShClFormatIsValid(uFormat)) return VINF_SUCCESS; LogRelMax2(16, ("Shared Clipboard: Rejecting service-extension function %RU32 with invalid format %#x\n", u32Function, uFormat)); return VERR_INVALID_PARAMETER; } -/** - * Checks whether a Shared Clipboard source value is valid for Main callbacks. - * - * @returns true if \a enmSource is a valid non-invalid SHCLSOURCE value, false otherwise. - * @param enmSource Source value to validate. - */ -static bool shClSvcExtIsValidSource(SHCLSOURCE enmSource) -{ - return enmSource == SHCLSOURCE_LOCAL - || enmSource == SHCLSOURCE_REMOTE; -} - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** - * Checks whether a Shared Clipboard transfer status value is valid for a transfer status reply. - * - * @returns true if \a uStatus is a known SHCLTRANSFERSTATUS value accepted from the guest, - * false otherwise. - * @param uStatus Transfer status value to validate. - */ -static bool shClSvcExtIsValidTransferStatus(SHCLTRANSFERSTATUS uStatus) -{ - switch (uStatus) - { - case SHCLTRANSFERSTATUS_REQUESTED: - case SHCLTRANSFERSTATUS_INITIALIZED: - case SHCLTRANSFERSTATUS_UNINITIALIZED: - case SHCLTRANSFERSTATUS_STARTED: - case SHCLTRANSFERSTATUS_COMPLETED: - case SHCLTRANSFERSTATUS_CANCELED: - case SHCLTRANSFERSTATUS_KILLED: - case SHCLTRANSFERSTATUS_ERROR: - return true; - - default: - return false; - } -} -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - /** * Validates a string coming from the HGCM dispatcher. * @@ -259,7 +192,7 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - AssertReturn(shClSvcExtIsValidFormats(pParms->u.ReportFormats.uFormats, true /* fAllowNone */), + AssertReturn(ShClFormatsAreValid(pParms->u.ReportFormats.uFormats), VERR_INVALID_PARAMETER); AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); AssertReturn(pParms->u.ReportFormats.pClient == pActiveClient, VERR_INVALID_PARAMETER); @@ -268,12 +201,12 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - AssertReturn(shClSvcExtIsValidFormats(pParms->u.ReportFormats.uFormats, true /* fAllowNone */), + AssertReturn(ShClFormatsAreValid(pParms->u.ReportFormats.uFormats), VERR_INVALID_PARAMETER); AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); AssertReturn(pParms->u.ReportFormats.pClient == pActiveClient, VERR_INVALID_PARAMETER); AssertPtrReturn(pParms->u.ReportFormats.pClient->pBackend, VERR_INVALID_POINTER); - AssertReturn(shClSvcExtIsValidSource(pParms->u.ReportFormats.enmSource), VERR_INVALID_PARAMETER); + AssertReturn(ShClSourceIsValid(pParms->u.ReportFormats.enmSource), VERR_INVALID_PARAMETER); return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_DATA_READ: @@ -351,22 +284,23 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 PSHCLTRANSFER const pTransfer = pParms->u.FileTransferData.pTransfer; AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); AssertPtrReturn(pParms->u.FileTransferData.pReply, VERR_INVALID_POINTER); - AssertReturn(pParms->u.FileTransferData.enmShClSource == SHCLSOURCE_REMOTE, VERR_INVALID_PARAMETER); - AssertReturn(ShClTransferCtxGetTransferByKey(&pClient->Transfers.Ctx, - ShClTransferGetSessionId(pTransfer), - ShClTransferGetID(pTransfer), - ShClTransferGetGeneration(pTransfer)) == pTransfer, - VERR_INVALID_CONTEXT); + AssertReturn(ShClSourceIsValid(pParms->u.FileTransferData.enmShClSource), VERR_INVALID_PARAMETER); + PSHCLTRANSFER const pRegisteredTransfer + = ShClTransferCtxGetTransferByKey(&pClient->Transfers.Ctx, + ShClTransferGetSessionId(pTransfer), + ShClTransferGetID(pTransfer), + ShClTransferGetGeneration(pTransfer)); + if (pRegisteredTransfer != pTransfer) + return VERR_INVALID_CONTEXT; PSHCLREPLY const pReply = pParms->u.FileTransferData.pReply; AssertReturn(pReply->uType == VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS, VERR_INVALID_PARAMETER); AssertReturn(pReply->pvPayload == NULL, VERR_INVALID_PARAMETER); AssertReturn(pReply->cbPayload == 0, VERR_INVALID_PARAMETER); - AssertReturn(shClSvcExtIsValidTransferStatus(pReply->u.TransferStatus.uStatus), VERR_INVALID_PARAMETER); - AssertReturn( pReply->u.TransferStatus.uStatus == SHCLTRANSFERSTATUS_ERROR - || RT_SUCCESS((int)pReply->rc), VERR_INVALID_PARAMETER); - AssertReturn( pReply->u.TransferStatus.uStatus != SHCLTRANSFERSTATUS_ERROR - || RT_FAILURE((int)pReply->rc), VERR_INVALID_PARAMETER); + SHCLTRANSFERSTATUS const enmStatus = pReply->u.TransferStatus.uStatus; + AssertReturn( enmStatus != SHCLTRANSFERSTATUS_NONE + && ShClTransferStatusResultIsValid(enmStatus, (int)pReply->rc), + VERR_INVALID_PARAMETER); return VINF_SUCCESS; } #endif @@ -659,8 +593,9 @@ int GuestShCl::i_handleSvcExtBackendDisconnect(PSHCLEXTPARMS pParms, void *pvPar i_waitForGuestReads(); - lock(); vrc = ShClBackendDisconnect(pClient->pBackend, pClient); + + lock(); if (m_pClient == pClient) { m_pClient = NULL; @@ -739,13 +674,27 @@ int GuestShCl::i_handleSvcExtFileTransfer(PSHCLEXTPARMS pParms, void *pvParms, u vrc = ShClBackendTransferHandleStatusReply(pClient->pBackend, pClient, pTransfer, enmShClSource, pReply->u.TransferStatus.uStatus, (int)pReply->rc); - Clipboard *pClipboard = m_pConsole->i_getClipboard(); - if (pClipboard) + if (RT_SUCCESS(vrc)) { - HRESULT hrc = pClipboard->i_handleTransferStatus(idSession, idTransfer, uGeneration, - enmShClSource, enmStatus, vrcTransfer); - if (FAILED(hrc)) - LogFunc(("Main transfer status handling failed: hrc=%Rhrc\n", hrc)); + Clipboard *pClipboard = m_pConsole->i_getClipboard(); + if (pClipboard) + { + /* + * enmShClSource identifies the endpoint which issued this reply + * and is therefore the right value for the platform backend. + * Main's persistent transfer object records the data source + * instead, which is an invariant of the backing transfer even + * when the opposite endpoint reports a lifecycle transition. + */ + SHCLSOURCE const enmTransferSource = ShClTransferGetSource(pTransfer); + HRESULT const hrc = pClipboard->i_handleTransferStatus(idSession, idTransfer, uGeneration, pTransfer, + enmTransferSource, enmStatus, vrcTransfer); + if (FAILED(hrc)) + { + LogFunc(("Main transfer status handling failed: hrc=%Rhrc\n", hrc)); + vrc = Global::vboxStatusCodeFromCOM(hrc); + } + } } return vrc; diff --git a/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp b/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp index b037db71d340..3069bc5b5649 100644 --- a/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp +++ b/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-utils.cpp 114526 2026-06-25 10:37:10Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-utils.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host service utility functions. */ @@ -60,9 +60,7 @@ int ShClSvcGuestDataRetainValidatedEvent(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX AssertPtrReturn(ppEvent, VERR_INVALID_POINTER); *ppEvent = NULL; - if ( uFormat == VBOX_SHCL_FMT_NONE - || (uFormat & ~VBOX_SHCL_FMT_VALID_MASK) - || (uFormat & (uFormat - 1)) != 0) + if (!ShClFormatIsValid(uFormat)) { LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); return VERR_INVALID_PARAMETER; @@ -412,4 +410,3 @@ int ShClSvcReadDataFromGuest(PSHCLCLIENT pClient, SHCLFORMAT fFormats, void **pp LogRel(("Shared Clipboard: Reading data from guest failed with %Rrc\n", vrc)); return vrc; } - diff --git a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp index 05e35a3460cc..e1c6d957e3ba 100644 --- a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp +++ b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendDarwin.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendDarwin.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host. */ @@ -61,6 +61,8 @@ typedef struct SHCLCONTEXT PasteboardRef hPasteboard; /** Shared clipboard client. */ PSHCLCLIENT pClient; + /** Whether @a pClient may be used by the pasteboard poller. */ + bool fClientReady; /** Random 64-bit number embedded into szGuestOwnershipFlavor. */ uint64_t idGuestOwnership; /** Ownership flavor CFStringRef returned by takePasteboardOwnership(). @@ -94,31 +96,33 @@ static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMA * @returns IPRT status code (ignored). * @param pCtx The context. * - * @note Call must own lock. */ static int vboxClipboardChanged(SHCLCONTEXT *pCtx) { - if (pCtx->pClient == NULL) - return VINF_SUCCESS; - - /* Retrieve the formats currently in the clipboard and supported by vbox */ + int vrc = VINF_SUCCESS; uint32_t fFormats = 0; - bool fChanged = false; - int vrc = queryNewPasteboardFormats(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->hStrOwnershipFlavor, - &fFormats, &fChanged); - if ( RT_SUCCESS(vrc) - && fChanged) + + RTCritSectEnter(&pCtx->CritSect); + + if ( pCtx->pClient + && pCtx->fClientReady) { - uint32_t uMode = pCtx->pClient->State.uMode; - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) + /* Retrieve the formats currently in the clipboard and supported by VBox. */ + bool fChanged = false; + vrc = queryNewPasteboardFormats(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->hStrOwnershipFlavor, + &fFormats, &fChanged); + if ( RT_SUCCESS(vrc) + && fChanged) { - vrc = shClBackendReportFormatsToGuestAndMain(pCtx->pClient, fFormats); + uint32_t const uMode = pCtx->pClient->State.uMode; + if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL + || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) + vrc = shClBackendReportFormatsToGuestAndMain(pCtx->pClient, fFormats); } - else - vrc = VINF_SUCCESS; } + RTCritSectLeave(&pCtx->CritSect); + LogFlowFuncLeaveRC(vrc); return vrc; } @@ -137,11 +141,7 @@ static DECLCALLBACK(int) vboxClipboardThread(RTTHREAD ThreadSelf, void *pvUser) while (!ASMAtomicReadBool(&pCtx->fTerminate)) { - /* call this behind the lock because we don't know if the api is - thread safe and in any case we're calling several methods. */ - RTCritSectEnter(&g_ctx.CritSect); vboxClipboardChanged(pCtx); - RTCritSectLeave(&g_ctx.CritSect); /* Sleep for 200 msecs before next poll */ vrc = RTThreadUserWait(ThreadSelf, 200); @@ -157,6 +157,7 @@ static DECLCALLBACK(int) vboxClipboardThread(RTTHREAD ThreadSelf, void *pvUser) int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) { g_ctx.fTerminate = false; + g_ctx.fClientReady = false; int vrc; @@ -206,6 +207,7 @@ void ShClBackendDestroy(PSHCLBACKEND pBackend) */ destroyPasteboard(&g_ctx.hPasteboard); g_ctx.pClient = NULL; + g_ctx.fClientReady = false; if (RTCritSectIsInitialized(&g_ctx.CritSect)) RTCritSectDelete(&g_ctx.CritSect); @@ -215,31 +217,41 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadles { RT_NOREF(pBackend, fHeadless); - if (g_ctx.pClient != NULL) - { - /* One client only. */ - return VERR_NOT_SUPPORTED; - } - RTCritSectEnter(&g_ctx.CritSect); - pClient->State.pCtx = &g_ctx; - pClient->State.pCtx->pClient = pClient; + int vrc; + if (g_ctx.pClient == NULL) + { + pClient->State.pCtx = &g_ctx; + g_ctx.pClient = pClient; + g_ctx.fClientReady = false; + vrc = VINF_SUCCESS; + } + else + vrc = VERR_NOT_SUPPORTED; /* One client only. */ RTCritSectLeave(&g_ctx.CritSect); - return VINF_SUCCESS; + return vrc; } int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) { RT_NOREF(pBackend); + /* GuestShCl records the active client after ShClBackendConnect returns. Do + * not expose it to the poller before that lifetime guard is in place. */ RTCritSectEnter(&g_ctx.CritSect); - /* Sync the host clipboard content with the client. */ - int vrc = vboxClipboardChanged(pClient->State.pCtx); + int vrc = VINF_SUCCESS; + if (pClient->State.pCtx->pClient == pClient) + pClient->State.pCtx->fClientReady = true; + else + vrc = VERR_NOT_SUPPORTED; RTCritSectLeave(&g_ctx.CritSect); + /* Sync the host clipboard content with the client. */ + if (RT_SUCCESS(vrc)) + vrc = vboxClipboardChanged(pClient->State.pCtx); return vrc; } @@ -249,7 +261,11 @@ int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) RTCritSectEnter(&g_ctx.CritSect); - pClient->State.pCtx->pClient = NULL; + if (pClient->State.pCtx->pClient == pClient) + { + pClient->State.pCtx->fClientReady = false; + pClient->State.pCtx->pClient = NULL; + } RTCritSectLeave(&g_ctx.CritSect); @@ -376,12 +392,16 @@ int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENT RTCritSectEnter(&g_ctx.CritSect); - writeToPasteboard(pClient->State.pCtx->hPasteboard, pClient->State.pCtx->idGuestOwnership, pvData, cbData, fFormat); + int vrc = writeToPasteboard(pClient->State.pCtx->hPasteboard, pClient->State.pCtx->idGuestOwnership, + pvData, cbData, fFormat); RTCritSectLeave(&g_ctx.CritSect); - LogFlowFuncLeaveRC(VINF_SUCCESS); - return VINF_SUCCESS; + if (RT_FAILURE(vrc)) + LogRel(("Shared Clipboard: Writing guest data to the macOS pasteboard failed, vrc=%Rrc\n", vrc)); + + LogFlowFuncLeaveRC(vrc); + return vrc; } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS @@ -406,4 +426,3 @@ int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pCli } # endif /* !UNIT_TEST */ #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - diff --git a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp index c424f64b1cd6..cf841e883135 100644 --- a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp +++ b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp @@ -1,4 +1,4 @@ -/* $Id: darwin-pasteboard.cpp 114770 2026-07-25 11:53:54Z knut.osmundsen@oracle.com $ */ +/* $Id: darwin-pasteboard.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host implementation. */ @@ -637,8 +637,9 @@ DECLHIDDEN(int) writeToPasteboard(PasteboardRef hPasteboard, uint64_t idOwnershi /* * Now for the UTF-8 version. */ + PCRTUTF16 const pwszUtf8 = pwszDst[0] == VBOX_SHCL_UTF16_BOM ? pwszDst + 1 : pwszDst; char *pszDst; - int vrc2 = RTUtf16ToUtf8(pwszDst, &pszDst); + int vrc2 = RTUtf16ToUtf8(pwszUtf8, &pszDst); if (RT_SUCCESS(vrc2)) { hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pszDst, strlen(pszDst)); diff --git a/src/VBox/Main/testcase/tstClipboard.cpp b/src/VBox/Main/testcase/tstClipboard.cpp index de196c477a92..954a9bc47d52 100644 --- a/src/VBox/Main/testcase/tstClipboard.cpp +++ b/src/VBox/Main/testcase/tstClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboard.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboard.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Main API Testcase - Clipboard. */ @@ -411,6 +411,139 @@ static bool tstClipboardCheckEventMetadata(const ComPtr &ptrEvent, const } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Verifies a service-originated clipboard transfer event. + */ +static bool tstClipboardCheckTransferEvent(IEvent *pEvent, const char *pszWhat, + ClipboardTransferState_T enmExpectedState, + ClipboardTransferDirection_T enmExpectedDirection, + ClipboardSource_T enmExpectedSource, + ULONG idExpectedTransfer, + ComPtr &ptrTransfer, + ClipboardError_T enmExpectedError = ClipboardError_None) +{ + AssertPtrReturn(pEvent, false); + AssertPtrReturn(pszWhat, false); + + ptrTransfer.setNull(); + ComPtr ptrTransferEvent(pEvent); + if (ptrTransferEvent.isNull()) + { + RTTestIFailed("%s: event does not implement IClipboardTransferEvent\n", pszWhat); + return false; + } + + bool fRc = tstClipboardCheckEventMetadata(ptrTransferEvent, pszWhat, VBOX_SHCL_MAIN_CLIENT_NONE); + + ClipboardTransferState_T enmState = ClipboardTransferState_Removed; + HRESULT hrc = ptrTransferEvent->COMGETTER(State)(&enmState); + if (FAILED(hrc)) + { + RTTestIFailed("%s: COMGETTER(State) failed, hrc=%Rhrc\n", pszWhat, hrc); + fRc = false; + } + else if (enmState != enmExpectedState) + { + RTTestIFailed("%s: state %d, expected %d\n", pszWhat, enmState, enmExpectedState); + fRc = false; + } + + ClipboardError_T enmError = ClipboardError_OperationFailed; + hrc = ptrTransferEvent->COMGETTER(Error)(&enmError); + if (FAILED(hrc)) + { + RTTestIFailed("%s: COMGETTER(Error) failed, hrc=%Rhrc\n", pszWhat, hrc); + fRc = false; + } + else if (enmError != enmExpectedError) + { + RTTestIFailed("%s: error %d, expected %d\n", pszWhat, enmError, enmExpectedError); + fRc = false; + } + + hrc = ptrTransferEvent->COMGETTER(Transfer)(ptrTransfer.asOutParam()); + if (FAILED(hrc)) + { + RTTestIFailed("%s: COMGETTER(Transfer) failed, hrc=%Rhrc\n", pszWhat, hrc); + return false; + } + if (ptrTransfer.isNull()) + { + RTTestIFailed("%s: transfer event has no transfer\n", pszWhat); + return false; + } + + ULONG idTransfer = 0; + hrc = ptrTransfer->COMGETTER(Id)(&idTransfer); + if (FAILED(hrc)) + { + RTTestIFailed("%s: IClipboardTransfer::COMGETTER(Id) failed, hrc=%Rhrc\n", pszWhat, hrc); + fRc = false; + } + else if (idTransfer != idExpectedTransfer) + { + RTTestIFailed("%s: transfer ID %RU32, expected %RU32\n", pszWhat, idTransfer, idExpectedTransfer); + fRc = false; + } + + ClipboardTransferDirection_T enmDirection = ClipboardTransferDirection_Any; + hrc = ptrTransfer->COMGETTER(Direction)(&enmDirection); + if (FAILED(hrc)) + { + RTTestIFailed("%s: IClipboardTransfer::COMGETTER(Direction) failed, hrc=%Rhrc\n", pszWhat, hrc); + fRc = false; + } + else if (enmDirection != enmExpectedDirection) + { + RTTestIFailed("%s: transfer direction %d, expected %d\n", pszWhat, enmDirection, enmExpectedDirection); + fRc = false; + } + + ClipboardSource_T enmSource = ClipboardSource_Custom; + hrc = ptrTransfer->COMGETTER(Source)(&enmSource); + if (FAILED(hrc)) + { + RTTestIFailed("%s: IClipboardTransfer::COMGETTER(Source) failed, hrc=%Rhrc\n", pszWhat, hrc); + fRc = false; + } + else if (enmSource != enmExpectedSource) + { + RTTestIFailed("%s: transfer source %d, expected %d\n", pszWhat, enmSource, enmExpectedSource); + fRc = false; + } + + ClipboardTransferState_T enmTransferState = ClipboardTransferState_Removed; + hrc = ptrTransfer->COMGETTER(State)(&enmTransferState); + if (FAILED(hrc)) + { + RTTestIFailed("%s: IClipboardTransfer::COMGETTER(State) failed, hrc=%Rhrc\n", pszWhat, hrc); + fRc = false; + } + else if (enmTransferState != enmExpectedState) + { + RTTestIFailed("%s: transfer state %d, expected %d\n", pszWhat, enmTransferState, enmExpectedState); + fRc = false; + } + + ClipboardError_T enmTransferError = ClipboardError_OperationFailed; + hrc = ptrTransfer->COMGETTER(Error)(&enmTransferError); + if (FAILED(hrc)) + { + RTTestIFailed("%s: IClipboardTransfer::COMGETTER(Error) failed, hrc=%Rhrc\n", pszWhat, hrc); + fRc = false; + } + else if (enmTransferError != enmExpectedError) + { + RTTestIFailed("%s: transfer error %d, expected %d\n", pszWhat, enmTransferError, enmExpectedError); + fRc = false; + } + + return fRc; +} +#endif + + /** * Checks whether a format array contains exactly the expected MIME type. */ @@ -1114,8 +1247,9 @@ static void tstHostClipboard(RTTEST hTest, IClipboard *pClipboard, IClipboardSet std::vector abSetData = tstBytesFromString(s_aHostClipboardSetData[i].pszData); char szWhat[128]; RTStrPrintf(szWhat, sizeof(szWhat), "IHostClipboard SetData %s", s_aHostClipboardSetData[i].pszWhat); - if (tstHostClipboardSetDataAndKeepReadBack(pClipboard, ptrHostClipboard, s_aHostClipboardSetData[i].pszMimeType, - abSetData, ClipboardSource_Host, strHostStateMimeType.c_str(), + if (tstHostClipboardSetDataAndKeepReadBack(pClipboard, ptrHostClipboard, + s_aHostClipboardSetData[i].pszMimeType, abSetData, + ClipboardSource_Host, strHostStateMimeType.c_str(), abHostStateData, 3 /* cReads */, szWhat)) { fHaveLastHostClipboardSetData = true; @@ -1507,18 +1641,21 @@ static void tstClipboardPublicSessionApi(RTTEST hTest, IClipboard *pClipboard, I RTTESTI_CHECK(tstByteArrayEquals(aWrittenBuffer, abSessionRawData)); LONG64 i64DataRevision = 0; - ComPtr ptrDataEvent; - VBoxEventType_T enmDataEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aBasicEventTypes, - RT_ELEMENTS(s_aBasicEventTypes), 1000 /* cMsTimeout */, - "session observer data", ptrDataEvent, &enmDataEventType); - RTTESTI_CHECK(fRc); - if (fRc) { - RTTESTI_CHECK(enmDataEventType == VBoxEventType_OnClipboardDataChanged); - if (enmDataEventType == VBoxEventType_OnClipboardDataChanged) - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrDataEvent, "session observer data event", idSessionA, - ClipboardAction_Copy, NULL /* pptrItem */, &i64DataRevision)); + ComPtr ptrDataEvent; + VBoxEventType_T enmDataEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aBasicEventTypes, + RT_ELEMENTS(s_aBasicEventTypes), 1000 /* cMsTimeout */, + "session observer data", ptrDataEvent, &enmDataEventType); + RTTESTI_CHECK(fRc); + if (fRc) + { + RTTESTI_CHECK(enmDataEventType == VBoxEventType_OnClipboardDataChanged); + if (enmDataEventType == VBoxEventType_OnClipboardDataChanged) + RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrDataEvent, "session observer data event", + idSessionA, ClipboardAction_Copy, + NULL /* pptrItem */, &i64DataRevision)); + } } if (i64FormatRevision > 0 && i64DataRevision > 0) RTTESTI_CHECK_MSG(i64DataRevision > i64FormatRevision, @@ -1824,16 +1961,23 @@ static void tstClipboardPublicSessionApi(RTTEST hTest, IClipboard *pClipboard, I ComSafeArrayAsInParam(aSessionAData), &enmWrittenSource, bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Session A WriteDataRaw failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstReadSessionDataRawEquals(ptrSessionA, "ExcludeOwnChanges committed state", + ClipboardSource_Host, "text/plain;charset=utf-8", abSessionAData)); ComPtr ptrObservedEvent; VBoxEventType_T enmObservedEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrEventSourceB, ptrListenerB, s_aDataEventTypes, - RT_ELEMENTS(s_aDataEventTypes), 1000 /* cMsTimeout */, - "ExcludeOwnChanges observer", ptrObservedEvent, &enmObservedEventType); + bool const fRc = tstClipboardWaitForAnyEvent(ptrEventSourceB, ptrListenerB, s_aDataEventTypes, + RT_ELEMENTS(s_aDataEventTypes), 1000 /* cMsTimeout */, + "ExcludeOwnChanges observer", ptrObservedEvent, + &enmObservedEventType); RTTESTI_CHECK(fRc); if (fRc) - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrObservedEvent, "ExcludeOwnChanges observer data event", - idSessionA, ClipboardAction_Copy, NULL /* pptrItem */)); + { + RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrObservedEvent, + "ExcludeOwnChanges observer data event", + idSessionA, ClipboardAction_Copy, + NULL /* pptrItem */)); + } RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSourceA, ptrListenerA, 250 /* cMsTimeout */, "ExcludeOwnChanges writer")); } while (0); @@ -1885,15 +2029,18 @@ static void tstClipboardPublicSessionApi(RTTEST hTest, IClipboard *pClipboard, I hrc = ptrNoPayloadSession->COMGETTER(EventSource)(ptrNoPayloadEventSource.asOutParam()); RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(no payload) failed, hrc=%Rhrc\n", hrc)); - static VBoxEventType_T const s_aDataEventTypes[] = + static VBoxEventType_T const s_aPayloadEventTypes[] = { - VBoxEventType_OnClipboardDataChanged + VBoxEventType_OnClipboardDataChanged, + VBoxEventType_OnClipboardDataRequested }; - hrc = tstRegisterClipboardListener(ptrPayloadEventSource, s_aDataEventTypes, RT_ELEMENTS(s_aDataEventTypes), + hrc = tstRegisterClipboardListener(ptrPayloadEventSource, s_aPayloadEventTypes, + RT_ELEMENTS(s_aPayloadEventTypes), ptrPayloadListener); RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(IncludePayload) failed, hrc=%Rhrc\n", hrc)); fPayloadListenerRegistered = true; - hrc = tstRegisterClipboardListener(ptrNoPayloadEventSource, s_aDataEventTypes, RT_ELEMENTS(s_aDataEventTypes), + hrc = tstRegisterClipboardListener(ptrNoPayloadEventSource, s_aPayloadEventTypes, + RT_ELEMENTS(s_aPayloadEventTypes), ptrNoPayloadListener); RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(no payload) failed, hrc=%Rhrc\n", hrc)); fNoPayloadListenerRegistered = true; @@ -1912,38 +2059,99 @@ static void tstClipboardPublicSessionApi(RTTEST hTest, IClipboard *pClipboard, I ComSafeArrayAsInParam(aPayloadData), &enmWrittenSource, bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Payload writer WriteDataRaw failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstReadSessionDataRawEquals(ptrWriterSession, "IncludePayload committed state", + ClipboardSource_Host, "text/plain;charset=utf-8", abPayloadData)); ComPtr ptrPayloadEvent; VBoxEventType_T enmPayloadEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrPayloadEventSource, ptrPayloadListener, s_aDataEventTypes, - RT_ELEMENTS(s_aDataEventTypes), 1000 /* cMsTimeout */, + bool fRc = tstClipboardWaitForAnyEvent(ptrPayloadEventSource, ptrPayloadListener, s_aPayloadEventTypes, + RT_ELEMENTS(s_aPayloadEventTypes), 1000 /* cMsTimeout */, "IncludePayload listener", ptrPayloadEvent, &enmPayloadEventType); RTTESTI_CHECK(fRc); if (fRc) { ComPtr ptrPayloadItem; - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrPayloadEvent, "IncludePayload data event", idWriterSession, - ClipboardAction_Copy, &ptrPayloadItem)); + RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrPayloadEvent, "IncludePayload data event", + idWriterSession, ClipboardAction_Copy, + &ptrPayloadItem)); RTTESTI_CHECK_MSG(!ptrPayloadItem.isNull(), ("IncludePayload data event did not include an item\n")); if (ptrPayloadItem.isNotNull()) RTTESTI_CHECK(tstClipboardCheckItemPayload(ptrPayloadItem, "IncludePayload event item", - ClipboardSource_Host, "text/plain;charset=utf-8", abPayloadData)); + ClipboardSource_Host, "text/plain;charset=utf-8", + abPayloadData)); } ComPtr ptrNoPayloadEvent; VBoxEventType_T enmNoPayloadEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrNoPayloadEventSource, ptrNoPayloadListener, s_aDataEventTypes, - RT_ELEMENTS(s_aDataEventTypes), 1000 /* cMsTimeout */, - "No IncludePayload listener", ptrNoPayloadEvent, &enmNoPayloadEventType); + fRc = tstClipboardWaitForAnyEvent(ptrNoPayloadEventSource, ptrNoPayloadListener, s_aPayloadEventTypes, + RT_ELEMENTS(s_aPayloadEventTypes), 1000 /* cMsTimeout */, + "No IncludePayload listener", ptrNoPayloadEvent, + &enmNoPayloadEventType); RTTESTI_CHECK(fRc); if (fRc) { ComPtr ptrNoPayloadItem; - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrNoPayloadEvent, "No IncludePayload data event", - idWriterSession, ClipboardAction_Copy, &ptrNoPayloadItem)); + RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrNoPayloadEvent, + "No IncludePayload data event", + idWriterSession, ClipboardAction_Copy, + &ptrNoPayloadItem)); RTTESTI_CHECK_MSG(ptrNoPayloadItem.isNull(), ("No IncludePayload data event unexpectedly included an item\n")); } + + ComPtr ptrInternalClipboardControl(pClipboard); + RTTESTI_CHECK_MSG_BREAK(!ptrInternalClipboardControl.isNull(), + ("Query IInternalClipboardControl(IncludePayload) returned NULL\n")); + ULONG idRequest = 0; + hrc = ptrInternalClipboardControl->RequestData(Bstr("text/plain;charset=utf-8").raw(), &idRequest); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), + ("IInternalClipboardControl::RequestData(IncludePayload) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG(idRequest != 0, ("IncludePayload request returned zero request ID\n")); + + ComPtr ptrPayloadRequestEvent; + fRc = tstClipboardWaitForAnyEvent(ptrPayloadEventSource, ptrPayloadListener, s_aPayloadEventTypes, + RT_ELEMENTS(s_aPayloadEventTypes), 1000 /* cMsTimeout */, + "IncludePayload request listener", ptrPayloadRequestEvent, + &enmPayloadEventType); + RTTESTI_CHECK(fRc); + RTTESTI_CHECK(enmPayloadEventType == VBoxEventType_OnClipboardDataRequested); + if (fRc && enmPayloadEventType == VBoxEventType_OnClipboardDataRequested) + { + ComPtr ptrPayloadRequest = ptrPayloadRequestEvent; + RTTESTI_CHECK(!ptrPayloadRequest.isNull()); + ComPtr ptrPayloadRequestItem; + if (ptrPayloadRequest.isNotNull()) + hrc = ptrPayloadRequest->COMGETTER(Item)(ptrPayloadRequestItem.asOutParam()); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("COMGETTER(Item)(IncludePayload request) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG(!ptrPayloadRequestItem.isNull(), + ("IncludePayload request event did not include an item\n")); + if (ptrPayloadRequestItem.isNotNull()) + RTTESTI_CHECK(tstClipboardCheckItemPayload(ptrPayloadRequestItem, + "IncludePayload request item", + ClipboardSource_Host, + "text/plain;charset=utf-8", abPayloadData)); + } + + ComPtr ptrNoPayloadRequestEvent; + fRc = tstClipboardWaitForAnyEvent(ptrNoPayloadEventSource, ptrNoPayloadListener, s_aPayloadEventTypes, + RT_ELEMENTS(s_aPayloadEventTypes), 1000 /* cMsTimeout */, + "No IncludePayload request listener", ptrNoPayloadRequestEvent, + &enmNoPayloadEventType); + RTTESTI_CHECK(fRc); + RTTESTI_CHECK(enmNoPayloadEventType == VBoxEventType_OnClipboardDataRequested); + if (fRc && enmNoPayloadEventType == VBoxEventType_OnClipboardDataRequested) + { + ComPtr ptrNoPayloadRequest = ptrNoPayloadRequestEvent; + RTTESTI_CHECK(!ptrNoPayloadRequest.isNull()); + ComPtr ptrNoPayloadRequestItem; + if (ptrNoPayloadRequest.isNotNull()) + hrc = ptrNoPayloadRequest->COMGETTER(Item)(ptrNoPayloadRequestItem.asOutParam()); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("COMGETTER(Item)(no payload request) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG(ptrNoPayloadRequestItem.isNull(), + ("No IncludePayload request event unexpectedly included an item\n")); + } } while (0); if (fPayloadListenerRegistered && ptrPayloadEventSource.isNotNull() && ptrPayloadListener.isNotNull()) @@ -2083,6 +2291,7 @@ static void tstClipboardPublicApi(RTTEST hTest) HRESULT hrc = S_OK; bool fMachineRegistered = false; bool fMachineLocked = false; + bool fMachinePoweredOn = false; bool fListenerRegistered = false; ComPtr ptrVirtualBoxClient; ComPtr ptrVirtualBox; @@ -2094,6 +2303,14 @@ static void tstClipboardPublicApi(RTTEST hTest) ComPtr ptrListener; ComPtr ptrSurvivingSession; ULONG idSurvivingSession = VBOX_SHCL_MAIN_CLIENT_NONE; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + char szFile1[RTPATH_MAX] = ""; + char szDir1[RTPATH_MAX] = ""; + char szDirFile1[RTPATH_MAX] = ""; + bool fFile1Created = false; + bool fDir1Created = false; + bool fDirFile1Created = false; +#endif do { @@ -2181,6 +2398,7 @@ static void tstClipboardPublicApi(RTTEST hTest) break; } fMachineLocked = true; + fMachinePoweredOn = true; /* Resolve the live console and clipboard objects under test. */ hrc = ptrSession->COMGETTER(Console)(ptrConsole.asOutParam()); @@ -2204,8 +2422,8 @@ static void tstClipboardPublicApi(RTTEST hTest) #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS ComPtr ptrTransfers; hrc = ptrClipboard->COMGETTER(Transfers)(ptrTransfers.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Transfers) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrTransfers.isNull()); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Transfers) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG_BREAK(!ptrTransfers.isNull(), ("COMGETTER(Transfers) returned NULL\n")); SafeIfaceArray aTransfers; hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); @@ -2216,25 +2434,81 @@ static void tstClipboardPublicApi(RTTEST hTest) RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("GetTransfers(invalid direction) returned hrc=%Rhrc, expected E_INVALIDARG\n", hrc)); char szTmpDir[RTPATH_MAX]; - char szFile1[RTPATH_MAX]; vrc = RTPathTemp(szTmpDir, sizeof(szTmpDir)); RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTPathTemp failed, vrc=%Rrc\n", vrc)); - RTStrPrintf(szFile1, sizeof(szFile1), "%s/tstClipboard-%RU64-1.txt", szTmpDir, RTTimeNanoTS()); + char szTmpName[64]; + RTStrPrintf(szTmpName, sizeof(szTmpName), "tstClipboard-%RU64-1.txt", RTTimeNanoTS()); + vrc = RTPathJoin(szFile1, sizeof(szFile1), szTmpDir, szTmpName); + RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTPathJoin(%s, %s) failed, vrc=%Rrc\n", szTmpDir, szTmpName, vrc)); static const char s_szFile1Data[] = "clipboard transfer data one"; RTFILE hFile = NIL_RTFILE; vrc = RTFileOpen(&hFile, szFile1, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTFileOpen(%s) failed, vrc=%Rrc\n", szFile1, vrc)); + fFile1Created = true; vrc = RTFileWrite(hFile, s_szFile1Data, sizeof(s_szFile1Data) - 1, NULL /* pcbWritten */); RTTESTI_CHECK_MSG(RT_SUCCESS(vrc), ("RTFileWrite(%s) failed, vrc=%Rrc\n", szFile1, vrc)); RTFileClose(hFile); + ComPtr ptrMainTransferEventSource; + hrc = ptrClipboard->COMGETTER(EventSource)(ptrMainTransferEventSource.asOutParam()); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc) && !ptrMainTransferEventSource.isNull(), + ("COMGETTER(EventSource for Create) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrMainTransferListener; + static VBoxEventType_T const s_aMainTransferEventTypes[] = + { + VBoxEventType_OnClipboardTransfer + }; + hrc = tstRegisterClipboardListener(ptrMainTransferEventSource, s_aMainTransferEventTypes, + RT_ELEMENTS(s_aMainTransferEventTypes), ptrMainTransferListener); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc) && !ptrMainTransferListener.isNull(), + ("RegisterListener(Create) failed, hrc=%Rhrc\n", hrc)); ComPtr ptrTransfer; - hrc = ptrTransfers->CreateTransfer(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, - ClipboardAction_Copy, ptrTransfer.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateTransfer(ToGuest, Host, Copy) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrTransfer.isNull(), ("CreateTransfer(ToGuest, Host, Copy) returned NULL transfer\n")); + hrc = ptrTransfers->Create(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, + ClipboardAction_Copy, ptrTransfer.asOutParam()); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Create(ToGuest, Host, Copy) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG_BREAK(!ptrTransfer.isNull(), ("Create(ToGuest, Host, Copy) returned NULL transfer\n")); + + ULONG idMainTransfer = 0; + hrc = ptrTransfer->COMGETTER(Id)(&idMainTransfer); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id Main-created transfer) failed, hrc=%Rhrc\n", hrc)); + + ComPtr ptrMainAddedEvent; + VBoxEventType_T enmMainAddedEventType = VBoxEventType_Invalid; + bool fMainTransferEvent = tstClipboardWaitForAnyEvent(ptrMainTransferEventSource, ptrMainTransferListener, + s_aMainTransferEventTypes, + RT_ELEMENTS(s_aMainTransferEventTypes), + 1000 /* cMsTimeout */, "Main-created transfer added", + ptrMainAddedEvent, &enmMainAddedEventType); + RTTESTI_CHECK(fMainTransferEvent); + if (fMainTransferEvent) + { + ComPtr ptrAddedTransfer; + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrMainAddedEvent, "Main-created transfer added", + ClipboardTransferState_Added, + ClipboardTransferDirection_ToGuest, + ClipboardSource_Host, idMainTransfer, ptrAddedTransfer)); + RTTESTI_CHECK(ptrAddedTransfer == ptrTransfer); + } + + aTransfers.setNull(); + hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after Create) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aTransfers.size() == 1); + if (aTransfers.size() == 1) + RTTESTI_CHECK(aTransfers[0] == ptrTransfer); + SafeIfaceArray aGuestTransfers; + hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToGuest, 0, + ComSafeArrayAsOutParam(aGuestTransfers)); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToGuest after Create) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aGuestTransfers.size() == 1); + SafeIfaceArray aHostTransfers; + hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToHost, 0, + ComSafeArrayAsOutParam(aHostTransfers)); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToHost after Create) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aHostTransfers.size() == 0); + SafeArray aSourcePaths; hrc = ptrTransfer->GetSourcePaths(ComSafeArrayAsOutParam(aSourcePaths)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSourcePaths initial failed, hrc=%Rhrc\n", hrc)); @@ -2243,7 +2517,7 @@ static void tstClipboardPublicApi(RTTEST hTest) SafeArray aNewSourcePaths; RTTESTI_CHECK(aNewSourcePaths.push_back(Bstr(szFile1).raw())); hrc = ptrTransfer->SetSourcePaths(ComSafeArrayAsInParam(aNewSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetSourcePaths failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SetSourcePaths failed, hrc=%Rhrc\n", hrc)); aSourcePaths.setNull(); hrc = ptrTransfer->GetSourcePaths(ComSafeArrayAsOutParam(aSourcePaths)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSourcePaths after set failed, hrc=%Rhrc\n", hrc)); @@ -2251,30 +2525,38 @@ static void tstClipboardPublicApi(RTTEST hTest) if (aSourcePaths.size() == 1) RTTESTI_CHECK(!RTUtf16Cmp(aSourcePaths[0], Bstr(szFile1).raw())); - hrc = ptrTransfers->Add(ptrTransfer); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferManager::Add(source-path transfer) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrTransfers->Pause(ptrTransfer); RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Pause returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); hrc = ptrTransfers->Resume(ptrTransfer); RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Resume returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); + hrc = ptrTransfers->Approve(ptrTransfer, 0 /* aFlags */); + RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Approve returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); + hrc = ptrTransfers->Deny(ptrTransfer, Bstr("").raw()); + RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Deny returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); + hrc = ptrTransfers->Respond(ptrTransfer, ClipboardTransferInteraction_Approval, Bstr("").raw(), + ClipboardTransferResponse_Accept, Bstr("").raw(), 0 /* aFlags */); + RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Respond returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); aTransfers.setNull(); hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after SourcePaths) failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(aTransfers.size() == 1); - SafeIfaceArray aGuestTransfers; + if (aTransfers.size() == 1) + RTTESTI_CHECK(aTransfers[0] == ptrTransfer); + aGuestTransfers.setNull(); hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToGuest, 0, ComSafeArrayAsOutParam(aGuestTransfers)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToGuest after SourcePaths) failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(aGuestTransfers.size() == 1); - SafeIfaceArray aHostTransfers; + aHostTransfers.setNull(); hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToHost, 0, ComSafeArrayAsOutParam(aHostTransfers)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToHost after SourcePaths) failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(aHostTransfers.size() == 0); - if (aTransfers.size() == 1) + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrMainTransferEventSource, ptrMainTransferListener, + 100 /* cMsTimeout */, "Main-created transfer source-path update")); + if (ptrTransfer.isNotNull()) { SafeIfaceArray aRootNodes; - hrc = aTransfers[0]->Roots(ComSafeArrayAsOutParam(aRootNodes)); + hrc = ptrTransfer->Roots(ComSafeArrayAsOutParam(aRootNodes)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::Roots failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(aRootNodes.size() == 1); if (aRootNodes.size() == 1) @@ -2286,26 +2568,29 @@ static void tstClipboardPublicApi(RTTEST hTest) } ComPtr ptrInvalidNode; - hrc = aTransfers[0]->Query(Bstr("../host-file").raw(), ptrInvalidNode.asOutParam()); + hrc = ptrTransfer->Query(Bstr("../host-file").raw(), ptrInvalidNode.asOutParam()); RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::Query('../host-file') unexpectedly succeeded\n")); + hrc = ptrTransfer->Query(Bstr("host-file/").raw(), ptrInvalidNode.asOutParam()); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("IClipboardTransfer::Query('host-file/') returned hrc=%Rhrc\n", hrc)); SafeIfaceArray aInvalidNodes; - hrc = aTransfers[0]->List(Bstr("/absolute").raw(), ClipboardTransferListFlag_None, ComSafeArrayAsOutParam(aInvalidNodes)); + hrc = ptrTransfer->List(Bstr("/absolute").raw(), ClipboardTransferListFlag_None, ComSafeArrayAsOutParam(aInvalidNodes)); RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::List('/absolute') unexpectedly succeeded\n")); ComPtr ptrUnsupportedFile; - hrc = aTransfers[0]->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadWrite, - FileOpenAction_OpenExisting, FileSharingMode_Read, 0 /* creationMode */, - ptrUnsupportedFile.asOutParam()); + hrc = ptrTransfer->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadWrite, + FileOpenAction_OpenExisting, FileSharingMode_Read, 0 /* creationMode */, + ptrUnsupportedFile.asOutParam()); RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::OpenFile(ReadWrite) unexpectedly succeeded\n")); - hrc = aTransfers[0]->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadOnly, - FileOpenAction_CreateOrReplace, FileSharingMode_Read, 0 /* creationMode */, - ptrUnsupportedFile.asOutParam()); + hrc = ptrTransfer->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadOnly, + FileOpenAction_CreateOrReplace, FileSharingMode_Read, 0 /* creationMode */, + ptrUnsupportedFile.asOutParam()); RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::OpenFile(CreateOrReplace) unexpectedly succeeded\n")); ComPtr ptrFile; - hrc = aTransfers[0]->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadOnly, - FileOpenAction_OpenExisting, FileSharingMode_Read, 0 /* creationMode */, - ptrFile.asOutParam()); + hrc = ptrTransfer->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadOnly, + FileOpenAction_OpenExisting, FileSharingMode_Read, 0 /* creationMode */, + ptrFile.asOutParam()); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::OpenFile failed, hrc=%Rhrc\n", hrc)); if (SUCCEEDED(hrc) && ptrFile.isNotNull()) { @@ -2327,20 +2612,32 @@ static void tstClipboardPublicApi(RTTEST hTest) hrc = ptrFile->Write(ComSafeArrayAsInParam(aWriteData), 0 /* timeoutMS */, &cbWritten); RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferFile::Write unexpectedly succeeded\n")); + SafeArray aOversizedFileData; + hrc = ptrFile->Read(SHCL_TRANSFER_DEFAULT_MAX_CHUNK_SIZE + 1, 0 /* timeoutMS */, + ComSafeArrayAsOutParam(aOversizedFileData)); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("IClipboardTransferFile::Read(oversized) returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aOversizedFileData.size() == 0); + SafeArray aFileData; hrc = ptrFile->Read(sizeof(s_szFile1Data) - 1, 0 /* timeoutMS */, ComSafeArrayAsOutParam(aFileData)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::Read failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(aFileData.size() == sizeof(s_szFile1Data) - 1); if (aFileData.size() == sizeof(s_szFile1Data) - 1) RTTESTI_CHECK(!memcmp(aFileData.raw(), s_szFile1Data, sizeof(s_szFile1Data) - 1)); + LONG64 offNew = -1; + hrc = ptrFile->Seek(INT64_MAX, FileSeekOrigin_Current, &offNew); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("IClipboardTransferFile::Seek(overflow) returned hrc=%Rhrc\n", hrc)); hrc = ptrFile->Close(); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::Close failed, hrc=%Rhrc\n", hrc)); } ComPtr ptrTransferData; - hrc = aTransfers[0]->COMGETTER(Data)(ptrTransferData.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::COMGETTER(Data) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrTransferData.isNull()); + hrc = ptrTransfer->COMGETTER(Data)(ptrTransferData.asOutParam()); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IClipboardTransfer::COMGETTER(Data) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG_BREAK(!ptrTransferData.isNull(), + ("IClipboardTransfer::COMGETTER(Data) returned NULL\n")); LONG64 cRoots = 0; hrc = ptrTransferData->Open(ClipboardTransferDataType_RootList, Bstr("").raw(), Bstr("").raw(), @@ -2368,15 +2665,27 @@ static void tstClipboardPublicApi(RTTEST hTest) RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("IClipboardTransferData::Open(List '/absolute') returned hrc=%Rhrc\n", hrc)); hrc = ptrTransferData->Open(ClipboardTransferDataType_Object, Bstr("dir\\file").raw(), Bstr("").raw(), SHCL_OBJ_CF_ACCESS_READ | SHCL_OBJ_CF_ACCESS_DENYWRITE, &hInvalid); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("IClipboardTransferData::Open(Object 'dir\\file') returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferData::Open(Object 'dir\\file') unexpectedly succeeded\n")); hrc = ptrTransferData->Open(ClipboardTransferDataType_Object, Bstr("C:file").raw(), Bstr("").raw(), SHCL_OBJ_CF_ACCESS_READ | SHCL_OBJ_CF_ACCESS_DENYWRITE, &hInvalid); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("IClipboardTransferData::Open(Object 'C:file') returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferData::Open(Object 'C:file') unexpectedly succeeded\n")); LONG64 hObj = 0; hrc = ptrTransferData->Open(ClipboardTransferDataType_Object, bstrRootName.raw(), Bstr("").raw(), SHCL_OBJ_CF_ACCESS_READ | SHCL_OBJ_CF_ACCESS_DENYWRITE, &hObj); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Open(Object) failed, hrc=%Rhrc\n", hrc)); + SafeArray aOversizedObjData; + Bstr bstrOversizedObjName; + ULONG fOversizedObjInfo = 0; + SafeArray aOversizedObjInfo; + hrc = ptrTransferData->Read(ClipboardTransferDataType_Object, hObj, + SHCL_TRANSFER_DEFAULT_MAX_CHUNK_SIZE + 1, 0 /* aFlags */, + bstrOversizedObjName.asOutParam(), &fOversizedObjInfo, + ComSafeArrayAsOutParam(aOversizedObjInfo), + ComSafeArrayAsOutParam(aOversizedObjData)); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("IClipboardTransferData::Read(Object oversized) returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aOversizedObjData.size() == 0); SafeArray aObjData; Bstr bstrObjName; ULONG fObjInfo = 0; @@ -2398,15 +2707,19 @@ static void tstClipboardPublicApi(RTTEST hTest) RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Close(Object) failed, hrc=%Rhrc\n", hrc)); } - char szDir1[RTPATH_MAX]; - char szDirFile1[RTPATH_MAX]; - RTStrPrintf(szDir1, sizeof(szDir1), "%s/tstClipboard-%RU64-dir", szTmpDir, RTTimeNanoTS()); + RTStrPrintf(szTmpName, sizeof(szTmpName), "tstClipboard-%RU64-dir", RTTimeNanoTS()); + vrc = RTPathJoin(szDir1, sizeof(szDir1), szTmpDir, szTmpName); + RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTPathJoin(%s, %s) failed, vrc=%Rrc\n", szTmpDir, szTmpName, vrc)); vrc = RTDirCreate(szDir1, 0700 /* fMode */, 0 /* fCreate */); RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTDirCreate(%s) failed, vrc=%Rrc\n", szDir1, vrc)); - RTStrPrintf(szDirFile1, sizeof(szDirFile1), "%s/tstClipboard-list-entry.txt", szDir1); + fDir1Created = true; + vrc = RTPathJoin(szDirFile1, sizeof(szDirFile1), szDir1, "tstClipboard-list-entry.txt"); + RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTPathJoin(%s, tstClipboard-list-entry.txt) failed, vrc=%Rrc\n", + szDir1, vrc)); hFile = NIL_RTFILE; vrc = RTFileOpen(&hFile, szDirFile1, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTFileOpen(%s) failed, vrc=%Rrc\n", szDirFile1, vrc)); + fDirFile1Created = true; static const char s_szDirFile1Data[] = "clipboard transfer list data"; vrc = RTFileWrite(hFile, s_szDirFile1Data, sizeof(s_szDirFile1Data) - 1, NULL /* pcbWritten */); RTTESTI_CHECK_MSG(RT_SUCCESS(vrc), ("RTFileWrite(%s) failed, vrc=%Rrc\n", szDirFile1, vrc)); @@ -2415,7 +2728,7 @@ static void tstClipboardPublicApi(RTTEST hTest) SafeArray aDirSourcePaths; RTTESTI_CHECK(aDirSourcePaths.push_back(Bstr(szDir1).raw())); hrc = ptrTransfer->SetSourcePaths(ComSafeArrayAsInParam(aDirSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetSourcePaths directory failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SetSourcePaths directory failed, hrc=%Rhrc\n", hrc)); aSourcePaths.setNull(); hrc = ptrTransfer->GetSourcePaths(ComSafeArrayAsOutParam(aSourcePaths)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSourcePaths directory failed, hrc=%Rhrc\n", hrc)); @@ -2427,24 +2740,28 @@ static void tstClipboardPublicApi(RTTEST hTest) RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after directory SourcePaths) failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(aTransfers.size() == 1); if (aTransfers.size() == 1) + RTTESTI_CHECK(aTransfers[0] == ptrTransfer); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrMainTransferEventSource, ptrMainTransferListener, + 100 /* cMsTimeout */, "Main-created transfer directory update")); + if (ptrTransfer.isNotNull()) { SafeIfaceArray aDirNodes; - hrc = aTransfers[0]->List(Bstr("").raw(), ClipboardTransferListFlag_None, ComSafeArrayAsOutParam(aDirNodes)); + hrc = ptrTransfer->List(Bstr("").raw(), ClipboardTransferListFlag_None, ComSafeArrayAsOutParam(aDirNodes)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::List(recursive) failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(aDirNodes.size() == 2); SafeIfaceArray aDirRootOnly; - hrc = aTransfers[0]->List(Bstr("").raw(), ClipboardTransferListFlag_NoRecursion, ComSafeArrayAsOutParam(aDirRootOnly)); + hrc = ptrTransfer->List(Bstr("").raw(), ClipboardTransferListFlag_NoRecursion, ComSafeArrayAsOutParam(aDirRootOnly)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::List(NoRecursion) failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(aDirRootOnly.size() == 1); ComPtr ptrInvalidDirectory; - hrc = aTransfers[0]->OpenDirectory(Bstr("").raw(), ClipboardTransferListFlag_NoRecursion, - ptrInvalidDirectory.asOutParam()); + hrc = ptrTransfer->OpenDirectory(Bstr("").raw(), ClipboardTransferListFlag_NoRecursion, + ptrInvalidDirectory.asOutParam()); RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::OpenDirectory(empty path) unexpectedly succeeded\n")); ComPtr ptrDirectory; - hrc = aTransfers[0]->OpenDirectory(Bstr(RTPathFilename(szDir1)).raw(), ClipboardTransferListFlag_NoRecursion, - ptrDirectory.asOutParam()); + hrc = ptrTransfer->OpenDirectory(Bstr(RTPathFilename(szDir1)).raw(), ClipboardTransferListFlag_NoRecursion, + ptrDirectory.asOutParam()); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::OpenDirectory failed, hrc=%Rhrc\n", hrc)); if (SUCCEEDED(hrc) && ptrDirectory.isNotNull()) { @@ -2454,6 +2771,18 @@ static void tstClipboardPublicApi(RTTEST hTest) RTTESTI_CHECK(aChildren.size() == 1); hrc = ptrDirectory->Rewind(); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::Rewind failed, hrc=%Rhrc\n", hrc)); + SafeIfaceArray aRootAndChildren; + hrc = ptrDirectory->ListEx(16, + ClipboardTransferListFlag_NoRecursion + | ClipboardTransferListFlag_IncludeRoot, + ComSafeArrayAsOutParam(aRootAndChildren)); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IClipboardTransferDirectory::ListEx(IncludeRoot, NoRecursion) failed, hrc=%Rhrc\n", + hrc)); + RTTESTI_CHECK(aRootAndChildren.size() == 2); + hrc = ptrDirectory->Rewind(); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IClipboardTransferDirectory::Rewind after IncludeRoot failed, hrc=%Rhrc\n", hrc)); ComPtr ptrChild; hrc = ptrDirectory->Read(ptrChild.asOutParam()); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::Read after rewind failed, hrc=%Rhrc\n", hrc)); @@ -2463,9 +2792,11 @@ static void tstClipboardPublicApi(RTTEST hTest) } ComPtr ptrTransferData; - hrc = aTransfers[0]->COMGETTER(Data)(ptrTransferData.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::COMGETTER(Data directory) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrTransferData.isNull()); + hrc = ptrTransfer->COMGETTER(Data)(ptrTransferData.asOutParam()); + RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), + ("IClipboardTransfer::COMGETTER(Data directory) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG_BREAK(!ptrTransferData.isNull(), + ("IClipboardTransfer::COMGETTER(Data directory) returned NULL\n")); LONG64 cRoots = 0; hrc = ptrTransferData->Open(ClipboardTransferDataType_RootList, Bstr("").raw(), Bstr("").raw(), @@ -2502,6 +2833,17 @@ static void tstClipboardPublicApi(RTTEST hTest) RTTESTI_CHECK(aListInfo.size() == sizeof(SHCLFSOBJINFO)); ULONG cbListWritten = 0; + hrc = ptrTransferData->Write(ClipboardTransferDataType_List, hList, Bstr("invalid-none").raw(), + VBOX_SHCL_INFO_F_NONE, ComSafeArrayAsInParam(aListInfo), + ComSafeArrayAsInParam(aListData), 0 /* aFlags */, &cbListWritten); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("IClipboardTransferData::Write(List mismatched NONE info) returned hrc=%Rhrc\n", hrc)); + SafeArray aEmptyListInfo; + hrc = ptrTransferData->Write(ClipboardTransferDataType_List, hList, Bstr("invalid-fs-info").raw(), + VBOX_SHCL_INFO_F_FSOBJINFO, ComSafeArrayAsInParam(aEmptyListInfo), + ComSafeArrayAsInParam(aListData), 0 /* aFlags */, &cbListWritten); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("IClipboardTransferData::Write(List missing FS info) returned hrc=%Rhrc\n", hrc)); hrc = ptrTransferData->Write(ClipboardTransferDataType_List, hList, Bstr("new-entry").raw(), VBOX_SHCL_INFO_F_FSOBJINFO, ComSafeArrayAsInParam(aListInfo), ComSafeArrayAsInParam(aListData), 0 /* aFlags */, &cbListWritten); @@ -2514,24 +2856,540 @@ static void tstClipboardPublicApi(RTTEST hTest) hrc = ptrTransfers->Remove(ptrTransfer); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferManager::Remove(source-path transfer) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrMainRemovedEvent; + VBoxEventType_T enmMainRemovedEventType = VBoxEventType_Invalid; + fMainTransferEvent = tstClipboardWaitForAnyEvent(ptrMainTransferEventSource, ptrMainTransferListener, + s_aMainTransferEventTypes, + RT_ELEMENTS(s_aMainTransferEventTypes), + 1000 /* cMsTimeout */, "Main-created transfer removed", + ptrMainRemovedEvent, &enmMainRemovedEventType); + RTTESTI_CHECK(fMainTransferEvent); + if (fMainTransferEvent) + { + ComPtr ptrRemovedTransfer; + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrMainRemovedEvent, "Main-created transfer removed", + ClipboardTransferState_Removed, + ClipboardTransferDirection_ToGuest, + ClipboardSource_Host, idMainTransfer, ptrRemovedTransfer)); + RTTESTI_CHECK(ptrRemovedTransfer == ptrTransfer); + } aTransfers.setNull(); hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after source-path transfer remove) failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(aTransfers.size() == 0); - RTFileDelete(szDirFile1); - RTDirRemove(szDir1); - RTFileDelete(szFile1); -#else - ComPtr ptrTransfers; - hrc = ptrClipboard->COMGETTER(Transfers)(ptrTransfers.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("COMGETTER(Transfers) unexpectedly succeeded without transfer support\n")); - RTTESTI_CHECK(ptrTransfers.isNull()); + hrc = ptrTransfers->Remove(ptrTransfer); + RTTESTI_CHECK_MSG(hrc == VBOX_E_OBJECT_NOT_FOUND, + ("Repeated IClipboardTransferManager::Remove returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrMainTransferEventSource, ptrMainTransferListener, + 100 /* cMsTimeout */, "stale Main-created transfer remove")); + hrc = ptrMainTransferEventSource->UnregisterListener(ptrMainTransferListener); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("UnregisterListener(Main transfer) failed, hrc=%Rhrc\n", hrc)); #endif hrc = ptrClipboard->COMGETTER(EventSource)(ptrEventSource.asOutParam()); RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(EventSource) failed, hrc=%Rhrc\n", hrc)); RTTESTI_CHECK(!ptrEventSource.isNull()); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RTTestSub(hTest, "Clipboard service transfer lifecycle"); + ComPtr ptrTransferListener; + static VBoxEventType_T const s_aTransferEventTypes[] = + { + VBoxEventType_OnClipboardTransfer + }; + hrc = tstRegisterClipboardListener(ptrEventSource, s_aTransferEventTypes, RT_ELEMENTS(s_aTransferEventTypes), + ptrTransferListener); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("RegisterListener(service transfer) failed, hrc=%Rhrc\n", hrc)); + if (SUCCEEDED(hrc)) + { + ComPtr ptrInternalClipboardControl(ptrClipboard); + RTTESTI_CHECK_MSG(!ptrInternalClipboardControl.isNull(), + ("Query IInternalClipboardControl(service transfer) returned NULL\n")); + if (ptrInternalClipboardControl.isNotNull()) + { + hrc = ptrInternalClipboardControl->SetTransferStatus(0 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus accepted a zero service session, hrc=%Rhrc\n", hrc)); + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + 0xfeed /* aStatus */, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus accepted an invalid status, hrc=%Rhrc\n", hrc)); + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_INITIALIZED, VERR_ACCESS_DENIED); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus accepted a non-error status with a failing result, hrc=%Rhrc\n", + hrc)); + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_ERROR, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus accepted ERROR with a successful result, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "status/result-inconsistent service transfer")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(REQUESTED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrRequestedEvent; + VBoxEventType_T enmRequestedEventType = VBoxEventType_Invalid; + bool fRequestedRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "requested service transfer", ptrRequestedEvent, + &enmRequestedEventType); + RTTESTI_CHECK(fRequestedRc); + if (fRequestedRc) + { + ComPtr ptrRequestedTransfer; + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrRequestedEvent, "requested service transfer", + ClipboardTransferState_Added, + ClipboardTransferDirection_ToHost, + ClipboardSource_Guest, 76, ptrRequestedTransfer)); + } + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus accepted REQUESTED to COMPLETED, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "requested-to-completed service transfer")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_ERROR, VERR_ACCESS_DENIED); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(requested ERROR) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrRequestedErrorEvent; + VBoxEventType_T enmRequestedErrorEventType = VBoxEventType_Invalid; + fRequestedRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "requested service transfer error", ptrRequestedErrorEvent, + &enmRequestedErrorEventType); + RTTESTI_CHECK(fRequestedRc); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(INITIALIZED) failed, hrc=%Rhrc\n", hrc)); + + ComPtr ptrAddedEvent; + VBoxEventType_T enmAddedEventType = VBoxEventType_Invalid; + bool fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "service transfer added", ptrAddedEvent, &enmAddedEventType); + RTTESTI_CHECK(fRc); + ComPtr ptrStatusTransfer; + if (fRc) + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrAddedEvent, "service transfer added", + ClipboardTransferState_Added, + ClipboardTransferDirection_ToHost, + ClipboardSource_Guest, 77, ptrStatusTransfer)); + + ComPtr ptrStatusProgress; + if (ptrStatusTransfer.isNotNull()) + { + hrc = ptrStatusTransfer->COMGETTER(Progress)(ptrStatusProgress.asOutParam()); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IClipboardTransfer::COMGETTER(Progress) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(!ptrStatusProgress.isNull()); + } + if (ptrStatusProgress.isNotNull()) + { + BOOL fCompleted = TRUE; + hrc = ptrStatusProgress->COMGETTER(Completed)(&fCompleted); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IProgress::COMGETTER(Completed) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(!fCompleted); + } + + SafeIfaceArray aStatusTransfers; + hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToHost, 0, + ComSafeArrayAsOutParam(aStatusTransfers)); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToHost service) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aStatusTransfers.size() == 1); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus(backward REQUESTED) returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "backward service transfer status")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Host, + SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus(source-mismatched STARTED) returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "source-mismatched service transfer status")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(STARTED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrStartedEvent; + VBoxEventType_T enmStartedEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "service transfer started", ptrStartedEvent, &enmStartedEventType); + RTTESTI_CHECK(fRc); + ComPtr ptrStartedTransfer; + if (fRc) + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrStartedEvent, "service transfer started", + ClipboardTransferState_InProgress, + ClipboardTransferDirection_ToHost, + ClipboardSource_Guest, 77, ptrStartedTransfer)); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus(backward INITIALIZED) returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "backward initialized service transfer status")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(duplicate STARTED) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "duplicate service transfer status")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(COMPLETED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrCompletedEvent; + VBoxEventType_T enmCompletedEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "service transfer completed", ptrCompletedEvent, &enmCompletedEventType); + RTTESTI_CHECK(fRc); + ComPtr ptrCompletedTransfer; + if (fRc) + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrCompletedEvent, "service transfer completed", + ClipboardTransferState_Completed, + ClipboardTransferDirection_ToHost, + ClipboardSource_Guest, 77, ptrCompletedTransfer)); + if (ptrStatusProgress.isNotNull()) + { + BOOL fCompleted = FALSE; + hrc = ptrStatusProgress->COMGETTER(Completed)(&fCompleted); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IProgress::COMGETTER(Completed after completion) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(fCompleted); + LONG hrcResult = E_FAIL; + hrc = ptrStatusProgress->COMGETTER(ResultCode)(&hrcResult); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IProgress::COMGETTER(ResultCode after completion) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(hrcResult == S_OK); + } + aStatusTransfers.setNull(); + hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, + ComSafeArrayAsOutParam(aStatusTransfers)); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers after service completion failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aStatusTransfers.size() == 0); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* stale generation */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus(stale STARTED) returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "stale service transfer generation")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 2 /* aGeneration */, ClipboardSource_Host, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("SetTransferStatus(second INITIALIZED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrSecondAddedEvent; + VBoxEventType_T enmSecondAddedEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "second service transfer added", ptrSecondAddedEvent, + &enmSecondAddedEventType); + RTTESTI_CHECK(fRc); + ComPtr ptrFailedTransfer; + if (fRc) + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrSecondAddedEvent, "second service transfer added", + ClipboardTransferState_Added, + ClipboardTransferDirection_ToGuest, + ClipboardSource_Host, 77, ptrFailedTransfer)); + ComPtr ptrFailedProgress; + if (ptrFailedTransfer.isNotNull()) + { + hrc = ptrFailedTransfer->COMGETTER(Progress)(ptrFailedProgress.asOutParam()); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IClipboardTransfer::COMGETTER(Progress failed transfer) failed, hrc=%Rhrc\n", hrc)); + } + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, + 2 /* aGeneration */, ClipboardSource_Host, + SHCLTRANSFERSTATUS_ERROR, VERR_ACCESS_DENIED); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(ERROR) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrFailedEvent; + VBoxEventType_T enmFailedEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "service transfer failed", ptrFailedEvent, &enmFailedEventType); + RTTESTI_CHECK(fRc); + ComPtr ptrFailedEventTransfer; + if (fRc) + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrFailedEvent, "service transfer failed", + ClipboardTransferState_Failed, + ClipboardTransferDirection_ToGuest, + ClipboardSource_Host, 77, ptrFailedEventTransfer, + ClipboardError_AccessDenied)); + if (ptrFailedProgress.isNotNull()) + { + BOOL fCompleted = FALSE; + hrc = ptrFailedProgress->COMGETTER(Completed)(&fCompleted); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IProgress::COMGETTER(Completed after failure) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(fCompleted); + LONG hrcResult = S_OK; + hrc = ptrFailedProgress->COMGETTER(ResultCode)(&hrcResult); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IProgress::COMGETTER(ResultCode after failure) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(hrcResult == (LONG)VBOX_E_SHCL_ACCESS_DENIED); + ComPtr ptrProgressErrorInfo; + hrc = ptrFailedProgress->COMGETTER(ErrorInfo)(ptrProgressErrorInfo.asOutParam()); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IProgress::COMGETTER(ErrorInfo after failure) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(!ptrProgressErrorInfo.isNull()); + if (ptrProgressErrorInfo.isNotNull()) + { + LONG hrcErrorInfo = S_OK; + hrc = ptrProgressErrorInfo->COMGETTER(ResultCode)(&hrcErrorInfo); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IVirtualBoxErrorInfo::COMGETTER(ResultCode) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(hrcErrorInfo == (LONG)VBOX_E_SHCL_ACCESS_DENIED); + LONG vrcErrorInfo = VINF_SUCCESS; + hrc = ptrProgressErrorInfo->COMGETTER(ResultDetail)(&vrcErrorInfo); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("IVirtualBoxErrorInfo::COMGETTER(ResultDetail) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(vrcErrorInfo == VERR_ACCESS_DENIED); + } + } + + /* A replacement service client owns a new, independently numbered generation sequence. */ + hrc = ptrInternalClipboardControl->SetTransferStatus(10 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Host, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("SetTransferStatus(new-session INITIALIZED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrNewSessionAddedEvent; + VBoxEventType_T enmNewSessionAddedEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "new-session service transfer added", ptrNewSessionAddedEvent, + &enmNewSessionAddedEventType); + RTTESTI_CHECK(fRc); + if (fRc) + { + ComPtr ptrNewSessionTransfer; + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrNewSessionAddedEvent, + "new-session service transfer added", + ClipboardTransferState_Added, + ClipboardTransferDirection_ToGuest, + ClipboardSource_Host, 77, ptrNewSessionTransfer)); + } + + hrc = ptrInternalClipboardControl->SetTransferStatus(10 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Host, + SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("SetTransferStatus(new-session COMPLETED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrNewSessionCompletedEvent; + VBoxEventType_T enmNewSessionCompletedEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "new-session service transfer completed", + ptrNewSessionCompletedEvent, &enmNewSessionCompletedEventType); + RTTESTI_CHECK(fRc); + if (fRc) + { + ComPtr ptrNewSessionCompletedTransfer; + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrNewSessionCompletedEvent, + "new-session service transfer completed", + ClipboardTransferState_Completed, + ClipboardTransferDirection_ToGuest, + ClipboardSource_Host, 77, + ptrNewSessionCompletedTransfer)); + } + hrc = ptrInternalClipboardControl->SetTransferStatus(10 /* aServiceSessionId */, 77 /* aTransferId */, + 1 /* stale generation */, ClipboardSource_Host, + SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus(new-session stale STARTED) returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "new-session stale transfer generation")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, + 1 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("SetTransferStatus(unknown COMPLETED) failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "unknown terminal service transfer")); + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, + 1 /* stale generation */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus(STARTED after unknown terminal) returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "status after unknown terminal generation")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, + 2 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("SetTransferStatus(pre-reset INITIALIZED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrPreResetEvent; + VBoxEventType_T enmPreResetEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "pre-reset service transfer added", ptrPreResetEvent, + &enmPreResetEventType); + RTTESTI_CHECK(fRc); + ComPtr ptrPreResetTransfer; + if (fRc) + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrPreResetEvent, "pre-reset service transfer added", + ClipboardTransferState_Added, + ClipboardTransferDirection_ToHost, + ClipboardSource_Guest, 78, ptrPreResetTransfer)); + + hrc = ptrTransfers->Reset(); + RTTESTI_CHECK_MSG(hrc == VBOX_E_OBJECT_IN_USE, + ("IClipboardTransferManager::Reset with an active service transfer returned hrc=%Rhrc\n", + hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "rejected service transfer reset")); + aStatusTransfers.setNull(); + hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, + ComSafeArrayAsOutParam(aStatusTransfers)); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers after rejected reset failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aStatusTransfers.size() == 1); + + if (ptrPreResetTransfer.isNotNull()) + { + hrc = ptrTransfers->Remove(ptrPreResetTransfer); + RTTESTI_CHECK_MSG(hrc == VBOX_E_OBJECT_IN_USE, + ("IClipboardTransferManager::Remove(active service transfer) returned hrc=%Rhrc\n", hrc)); + } + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "rejected service transfer remove")); + aStatusTransfers.setNull(); + hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, + ComSafeArrayAsOutParam(aStatusTransfers)); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers after rejected remove failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aStatusTransfers.size() == 1); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, + 2 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_CANCELED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus accepted CANCELED with a successful result, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "status/result-inconsistent cancellation")); + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, + 2 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("SetTransferStatus(pre-reset CANCELED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrResetEvent; + VBoxEventType_T enmResetEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "service transfer canceled", ptrResetEvent, &enmResetEventType); + RTTESTI_CHECK(fRc); + if (fRc) + { + ComPtr ptrResetTransfer; + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrResetEvent, "service transfer canceled", + ClipboardTransferState_Canceled, + ClipboardTransferDirection_ToHost, + ClipboardSource_Guest, 78, ptrResetTransfer)); + } + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, + 2 /* reset generation */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, + ("SetTransferStatus(STARTED after terminal status) returned hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "status after terminal service transfer")); + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, + 3 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("SetTransferStatus(next-generation INITIALIZED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrPostResetEvent; + VBoxEventType_T enmPostResetEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "next-generation service transfer added", ptrPostResetEvent, + &enmPostResetEventType); + RTTESTI_CHECK(fRc); + ComPtr ptrPostResetTransfer; + if (fRc) + RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrPostResetEvent, "next-generation service transfer added", + ClipboardTransferState_Added, + ClipboardTransferDirection_ToHost, + ClipboardSource_Guest, 78, ptrPostResetTransfer)); + + if (ptrPostResetTransfer.isNotNull()) + { + hrc = ptrTransfers->Cancel(ptrPostResetTransfer); + RTTESTI_CHECK_MSG(FAILED(hrc), + ("IClipboardTransferManager::Cancel(synthetic service transfer) unexpectedly succeeded\n")); + RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, + "failed service transfer cancel")); + aStatusTransfers.setNull(); + hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, + ComSafeArrayAsOutParam(aStatusTransfers)); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("GetTransfers after service cancel failed, hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK(aStatusTransfers.size() == 1); + } + + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, + 3 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("SetTransferStatus(STARTED after failed service cancel) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrPostResetStartedEvent; + VBoxEventType_T enmPostResetStartedEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "service transfer started after failed cancel", + ptrPostResetStartedEvent, &enmPostResetStartedEventType); + RTTESTI_CHECK(fRc); + hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, + 3 /* aGeneration */, ClipboardSource_Guest, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), + ("SetTransferStatus(next-generation CANCELED) failed, hrc=%Rhrc\n", hrc)); + ComPtr ptrPostResetCanceledEvent; + VBoxEventType_T enmPostResetCanceledEventType = VBoxEventType_Invalid; + fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, + RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, + "next-generation service transfer canceled", + ptrPostResetCanceledEvent, &enmPostResetCanceledEventType); + RTTESTI_CHECK(fRc); + } + + hrc = ptrEventSource->UnregisterListener(ptrTransferListener); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("UnregisterListener(service transfer) failed, hrc=%Rhrc\n", hrc)); + } +#endif + tstClipboardPublicSessionApi(hTest, ptrClipboard, ptrClipboardSettings, ptrEventSource); RTTestSub(hTest, "Clipboard public API operations"); @@ -2699,7 +3557,6 @@ static void tstClipboardPublicApi(RTTEST hTest) VBOX_SHCL_MAIN_CLIENT_NONE, ClipboardAction_Copy, NULL /* pptrItem */)); } - ComPtr ptrUnexpectedAfterWrite; hrc = ptrEventSource->GetEvent(ptrListener, 0 /* aTimeout */, ptrUnexpectedAfterWrite.asOutParam()); RTTESTI_CHECK_MSG( hrc == VBOX_E_OBJECT_NOT_FOUND @@ -2901,13 +3758,21 @@ static void tstClipboardPublicApi(RTTEST hTest) } while (0); /* Clean up listeners and VM state regardless of which subtest exited early. */ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fDirFile1Created) + RTFileDelete(szDirFile1); + if (fDir1Created) + RTDirRemove(szDir1); + if (fFile1Created) + RTFileDelete(szFile1); +#endif if (fListenerRegistered && ptrEventSource.isNotNull() && ptrListener.isNotNull()) ptrEventSource->UnregisterListener(ptrListener); ptrListener.setNull(); ptrEventSource.setNull(); ptrClipboard.setNull(); - if (fMachineLocked && !ptrConsole.isNull()) + if (fMachinePoweredOn && !ptrConsole.isNull()) { ComPtr ptrPowerDownProgress; HRESULT hrcPowerDown = ptrConsole->PowerDown(ptrPowerDownProgress.asOutParam()); @@ -2984,6 +3849,11 @@ int main() RTTestBanner(hTest); +#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RTTestSkipped(hTest, "Shared Clipboard transfers are not available on this platform"); + return RTTestSummaryAndDestroy(hTest); +#endif + HRESULT hrc = Initialize(); if (FAILED(hrc)) { From a3aa6128f53b0c27bf6f609da44f266bcc5e0219 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Wed, 5 Aug 2026 15:28:37 +0000 Subject: [PATCH 009/176] =?UTF-8?q?Shared=20Clipboard/Main:=20More=20plumb?= =?UTF-8?q?ing=20for=20making=20Shared=20Clipboard=20transfers=20available?= =?UTF-8?q?=20via=20public=20API=20[build=20fix].=20=E2=80=8B=E2=80=8B?= =?UTF-8?q?=E2=80=8Bbugref:4697?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174696 --- .../src-client/ClipboardTransferManagerImpl.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp index db35f91b25c9..e212f0fc7351 100644 --- a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferManagerImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferManagerImpl.cpp 114859 2026-08-05 15:28:37Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer manager object. */ @@ -407,8 +407,7 @@ HRESULT ClipboardTransferManager::create(ClipboardTransferDirection_T aDirection } aTransfer = ptrTransfer; - ClipboardTransfer *pTransfer = ptrTransferObj; - Log2Func(("Firing transfer added event: transfer=%p\n", (void *)pTransfer)); + Log2Func(("Firing transfer added event: transfer=%p\n", (void *)(ClipboardTransfer *)ptrTransferObj)); i_fireTransferEvent(ptrTransferObj, ClipboardTransferState_Added, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); return S_OK; @@ -466,12 +465,11 @@ HRESULT ClipboardTransferManager::remove(const ComPtr &aTran if (!fRemoved) return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is no longer owned by this manager")); - ClipboardTransfer *pTransfer = ptrTransfer; ptrTransfer->i_setState(ClipboardTransferState_Removed, com::Utf8Str(), ClipboardError_None); clipboardTransferManagerCompleteProgress(ptrProgressControl, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); if (fFireEvent) { - Log2Func(("Firing transfer removed event: transfer=%p\n", (void *)pTransfer)); + Log2Func(("Firing transfer removed event: transfer=%p\n", (void *)(ClipboardTransfer *)ptrTransfer)); i_fireTransferEvent(ptrTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); } @@ -787,11 +785,11 @@ HRESULT ClipboardTransferManager::reset() for (std::vector::const_iterator it = DetachedTransfers.begin(); it != DetachedTransfers.end(); ++it) { - ClipboardTransfer *pTransfer = it->mTransfer; it->mTransfer->i_setState(ClipboardTransferState_Removed, com::Utf8Str(), ClipboardError_None); clipboardTransferManagerCompleteProgress(it->mProgressControl, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); - Log2Func(("Firing transfer removed event during public reset: transfer=%p\n", (void *)pTransfer)); + Log2Func(("Firing transfer removed event during public reset: transfer=%p\n", + (void *)(ClipboardTransfer *)it->mTransfer)); i_fireTransferEvent(it->mTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); } @@ -827,8 +825,8 @@ void ClipboardTransferManager::i_reset() for (std::vector::const_iterator it = DetachedTransfers.begin(); it != DetachedTransfers.end(); ++it) { - ClipboardTransfer *pTransfer = it->mTransfer; - Log2Func(("Firing transfer removed event during reset: transfer=%p\n", (void *)pTransfer)); + Log2Func(("Firing transfer removed event during reset: transfer=%p\n", + (void *)(ClipboardTransfer *)it->mTransfer)); i_fireTransferEvent(it->mTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); } From 062fb6c8e45a3458dc47c3f90a351450d9824a8b Mon Sep 17 00:00:00 2001 From: Klaus Espenlaub Date: Wed, 5 Aug 2026 15:59:20 +0000 Subject: [PATCH 010/176] Runtime/common/crypto: Code cleanup regarding const, fix build failure with OpenSSL 4.0. Inspired by github:gh-794. svn:sync-xref-src-repo-rev: r174697 --- src/VBox/Runtime/common/crypto/ssl-openssl.cpp | 2 +- src/VBox/Runtime/common/crypto/x509-create-sign.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/VBox/Runtime/common/crypto/ssl-openssl.cpp b/src/VBox/Runtime/common/crypto/ssl-openssl.cpp index 67f41ebfa03d..286730c12cb8 100644 --- a/src/VBox/Runtime/common/crypto/ssl-openssl.cpp +++ b/src/VBox/Runtime/common/crypto/ssl-openssl.cpp @@ -422,7 +422,7 @@ RTDECL(int) RTCrSslSessionGetCertIssuerNameAsString(RTCRSSLSESSION hSslSession, X509 *pCert = SSL_get_certificate(pThis->pSsl); if (pCert) { - X509_NAME *pIssuer = X509_get_issuer_name(pCert); + const X509_NAME *pIssuer = X509_get_issuer_name(pCert); if (pIssuer) { char *pszSrc = X509_NAME_oneline(pIssuer, NULL, 0); diff --git a/src/VBox/Runtime/common/crypto/x509-create-sign.cpp b/src/VBox/Runtime/common/crypto/x509-create-sign.cpp index dad632a7ac1a..739a3c4913af 100644 --- a/src/VBox/Runtime/common/crypto/x509-create-sign.cpp +++ b/src/VBox/Runtime/common/crypto/x509-create-sign.cpp @@ -1,4 +1,4 @@ -/* $Id: x509-create-sign.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: x509-create-sign.cpp 114860 2026-08-05 15:59:20Z klaus.espenlaub@oracle.com $ */ /** @file * IPRT - Crypto - X.509, Certificate Creation. */ @@ -156,8 +156,8 @@ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType /** @todo check what the subject name is... Offer way to specify it? */ /* Make it self signed: */ - X509_NAME *pX509Name = X509_get_subject_name(pNewCert); - rcOssl = X509_NAME_add_entry_by_txt(pX509Name, "CN", MBSTRING_ASC, (unsigned char *) pvSubject, -1, -1, 0); + X509_NAME *pX509Name = (X509_NAME *)X509_get_subject_name(pNewCert); + rcOssl = X509_NAME_add_entry_by_txt(pX509Name, "CN", MBSTRING_ASC, (const unsigned char *)pvSubject, -1, -1, 0); AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_NAME_add_entry_by_txt failed")); rcOssl = X509_set_issuer_name(pNewCert, pX509Name); AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_set_issuer_name failed")); From 439c1d33b10a67d65ebfc9d38bef043c35511983 Mon Sep 17 00:00:00 2001 From: Klaus Espenlaub Date: Wed, 5 Aug 2026 16:23:07 +0000 Subject: [PATCH 011/176] Runtime/common/crypto/x509-create-sign.cpp: Cleanup, fix parameter to meet naming conventions. Runtime/tools/RTSignTool.cpp: Fix code for creating self-signed certificate. svn:sync-xref-src-repo-rev: r174698 --- include/iprt/crypto/x509.h | 4 +-- .../common/crypto/x509-create-sign.cpp | 9 +++---- src/VBox/Runtime/tools/RTSignTool.cpp | 26 ++++++++++++++----- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/include/iprt/crypto/x509.h b/include/iprt/crypto/x509.h index 2673885d4ec5..93d8224fa9bd 100644 --- a/include/iprt/crypto/x509.h +++ b/include/iprt/crypto/x509.h @@ -1223,7 +1223,7 @@ RTDECL(PCRTCRX509CERTIFICATE) RTCrX509CertPathsGetPathNodeCert(RTCRX509CERTPATHS * valid for (starting now). * @param fKeyUsage Key usage mask: RTCRX509CERT_KEY_USAGE_F_XXX. * @param fExtKeyUsage Extended key usage mask: RTCRX509CERT_EKU_F_XXX. - * @param pvSubject Subject name. + * @param pszSubject Subject name. * @param pszCertFile Where to store the certificate (PEM formatting). * @param pszPrivateKeyFile Where to store the unencrypted private key (PEM * formatting). @@ -1231,7 +1231,7 @@ RTDECL(PCRTCRX509CERTIFICATE) RTCrX509CertPathsGetPathNodeCert(RTCRX509CERTPATHS * Optional. */ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType, uint32_t cBits, uint32_t cSecsValidFor, - uint32_t fKeyUsage, uint64_t fExtKeyUsage, const char *pvSubject, + uint32_t fKeyUsage, uint64_t fExtKeyUsage, const char *pszSubject, const char *pszCertFile, const char *pszPrivateKeyFile, PRTERRINFO pErrInfo); RT_C_DECLS_END diff --git a/src/VBox/Runtime/common/crypto/x509-create-sign.cpp b/src/VBox/Runtime/common/crypto/x509-create-sign.cpp index 739a3c4913af..aa0fe547540f 100644 --- a/src/VBox/Runtime/common/crypto/x509-create-sign.cpp +++ b/src/VBox/Runtime/common/crypto/x509-create-sign.cpp @@ -1,4 +1,4 @@ -/* $Id: x509-create-sign.cpp 114860 2026-08-05 15:59:20Z klaus.espenlaub@oracle.com $ */ +/* $Id: x509-create-sign.cpp 114861 2026-08-05 16:23:07Z klaus.espenlaub@oracle.com $ */ /** @file * IPRT - Crypto - X.509, Certificate Creation. */ @@ -61,7 +61,7 @@ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType, uint32_t cBits, uint32_t cSecsValidFor, - uint32_t fKeyUsage, uint64_t fExtKeyUsage, const char *pvSubject, + uint32_t fKeyUsage, uint64_t fExtKeyUsage, const char *pszSubject, const char *pszCertFile, const char *pszPrivateKeyFile, PRTERRINFO pErrInfo) { AssertReturn(cSecsValidFor <= (uint32_t)INT32_MAX, VERR_OUT_OF_RANGE); /* larger values are not portable (win) */ @@ -152,12 +152,9 @@ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType # endif /** @todo set other certificate attributes? */ - - /** @todo check what the subject name is... Offer way to specify it? */ - /* Make it self signed: */ X509_NAME *pX509Name = (X509_NAME *)X509_get_subject_name(pNewCert); - rcOssl = X509_NAME_add_entry_by_txt(pX509Name, "CN", MBSTRING_ASC, (const unsigned char *)pvSubject, -1, -1, 0); + rcOssl = X509_NAME_add_entry_by_txt(pX509Name, "CN", MBSTRING_ASC, (const unsigned char *)pszSubject, -1, -1, 0); AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_NAME_add_entry_by_txt failed")); rcOssl = X509_set_issuer_name(pNewCert, pX509Name); AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_set_issuer_name failed")); diff --git a/src/VBox/Runtime/tools/RTSignTool.cpp b/src/VBox/Runtime/tools/RTSignTool.cpp index f2085c2b7673..3920cfcbc09d 100644 --- a/src/VBox/Runtime/tools/RTSignTool.cpp +++ b/src/VBox/Runtime/tools/RTSignTool.cpp @@ -1,4 +1,4 @@ -/* $Id: RTSignTool.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: RTSignTool.cpp 114861 2026-08-05 16:23:07Z klaus.espenlaub@oracle.com $ */ /** @file * IPRT - Signing Tool. */ @@ -6440,7 +6440,7 @@ static RTEXITCODE HelpCreateSelfSignedRsaCert(PRTSTREAM pStrm, RTSIGNTOOLHELP en { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, - "create-self-signed-rsa-cert [--verbose|--quiet] [--key-bits ] [--digest ] [--out-cert=] [--out-pkey=]\n"); + "create-self-signed-rsa-cert --subject= [--key-bits=] [--days= | --secs=] [--digest=] [--out-cert=] [--out-pkey=]\n"); return RTEXITCODE_SUCCESS; } @@ -6482,15 +6482,16 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) */ static const RTGETOPTDEF s_aOptions[] = { + { "--subject", 'S', RTGETOPT_REQ_STRING }, { "--digest", 'd', RTGETOPT_REQ_STRING }, { "--bits", 'b', RTGETOPT_REQ_UINT32 }, { "--key-bits", 'b', RTGETOPT_REQ_UINT32 }, { "--days", 'D', RTGETOPT_REQ_UINT32 }, { "--days", 'D', RTGETOPT_REQ_UINT32 }, - { "--out-cert", 'c', RTGETOPT_REQ_UINT32 }, - { "--out-certificate", 'c', RTGETOPT_REQ_UINT32 }, - { "--out-pkey", 'p', RTGETOPT_REQ_UINT32 }, - { "--out-private-key", 'p', RTGETOPT_REQ_UINT32 }, + { "--out-cert", 'c', RTGETOPT_REQ_STRING }, + { "--out-certificate", 'c', RTGETOPT_REQ_STRING }, + { "--out-pkey", 'p', RTGETOPT_REQ_STRING }, + { "--out-private-key", 'p', RTGETOPT_REQ_STRING }, { "--secs", 's', RTGETOPT_REQ_UINT32 }, { "--seconds", 's', RTGETOPT_REQ_UINT32 }, }; @@ -6500,6 +6501,7 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) uint32_t cSecsValidFor = 365 * RT_SEC_1DAY; uint32_t fKeyUsage = 0; uint32_t fExtKeyUsage = 0; + const char *pszSubject = NULL; const char *pszOutCert = NULL; const char *pszOutPrivKey = NULL; @@ -6544,6 +6546,14 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) cSecsValidFor = ValueUnion.u32; break; + case 'S': + if (pszSubject) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "The --subject option can only be used once."); + if (!ValueUnion.psz || !*ValueUnion.psz) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "The --subject option must be non-empty."); + pszSubject = ValueUnion.psz; + break; + case VINF_GETOPT_NOT_OPTION: if (!pszOutCert) pszOutCert = ValueUnion.psz; @@ -6558,6 +6568,8 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) default: return RTGetOptPrintError(ch, &ValueUnion); } } + if (!pszSubject) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "No subject name specified."); if (!pszOutCert) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output certificate file name specified."); if (!pszOutPrivKey) @@ -6568,7 +6580,7 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) */ RTERRINFOSTATIC StaticErrInfo; rc = RTCrX509Certificate_GenerateSelfSignedRsa(enmDigestType, cKeyBits, cSecsValidFor, - fKeyUsage, fExtKeyUsage, NULL /*pvSubjectTodo*/, + fKeyUsage, fExtKeyUsage, pszSubject, pszOutCert, pszOutPrivKey, RTErrInfoInitStatic(&StaticErrInfo)); if (RT_SUCCESS(rc)) { From f3f1e0f1736584515a0eb2d4a0a94ca202fc661c Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 10:19:52 +0000 Subject: [PATCH 012/176] Shared Clipboard/darwin: Add host-to-guest file transfer support on macOS hosts. bugref:4697 svn:sync-xref-src-repo-rev: r174700 --- Config.kmk | 8 +- src/VBox/Main/include/darwin-pasteboard.h | 5 +- .../darwin/ClipboardBackendDarwin.cpp | 128 +++++- .../src-client/darwin/darwin-pasteboard.cpp | 364 +++++++++++++++--- 4 files changed, 421 insertions(+), 84 deletions(-) diff --git a/Config.kmk b/Config.kmk index 84f392fb43e2..a6399094b20f 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114504 2026-06-23 16:56:50Z aleksey.ilyushin@oracle.com $ +# $Id: Config.kmk 114863 2026-08-06 10:19:52Z andreas.loeffler@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -966,7 +966,7 @@ VBOX_WITH_SHARED_FOLDERS = 1 # Enable shared clipboard VBOX_WITH_SHARED_CLIPBOARD = 1 # Enable shared clipboard (file) transfers -if1of ($(KBUILD_TARGET), win linux) +if1of ($(KBUILD_TARGET), win linux darwin) VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS = 1 if1of ($(KBUILD_TARGET), linux) VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP = 1 @@ -9591,7 +9591,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114504 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114863 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9605,7 +9605,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114504 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114863 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif diff --git a/src/VBox/Main/include/darwin-pasteboard.h b/src/VBox/Main/include/darwin-pasteboard.h index a41caa897879..2094ca63ea68 100644 --- a/src/VBox/Main/include/darwin-pasteboard.h +++ b/src/VBox/Main/include/darwin-pasteboard.h @@ -1,4 +1,4 @@ -/* $Id: darwin-pasteboard.h 114509 2026-06-24 14:35:45Z andreas.loeffler@oracle.com $ */ +/* $Id: darwin-pasteboard.h 114863 2026-08-06 10:19:52Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host implementation. */ @@ -40,6 +40,9 @@ DECLHIDDEN(void) destroyPasteboard(PasteboardRef *pPasteboardRef); DECLHIDDEN(int) queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, uint32_t *pfFormats, bool *pfChanged); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +DECLHIDDEN(int) readFileURLsFromPasteboard(PasteboardRef hPasteboard, char **ppszRoots, size_t *pcbRoots); +#endif DECLHIDDEN(int) readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcbActual); DECLHIDDEN(int) takePasteboardOwnership(PasteboardRef pPasteboard, uint64_t idOwnership, const char *pszOwnershipFlavor, const char *pszOwnershipValue, void **phStrOwnershipFlavor); diff --git a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp index e1c6d957e3ba..e9dd1cd7e4ce 100644 --- a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp +++ b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendDarwin.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendDarwin.cpp 114863 2026-08-06 10:19:52Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host. */ @@ -71,6 +71,8 @@ typedef struct SHCLCONTEXT /** The guest ownership flavor (type) string. */ char szGuestOwnershipFlavor[64]; /** Serialize access to the current pasteboard. */ + RTCRITSECT CritSectPasteboard; + /** Serialize the client pointer and its readiness state. */ RTCRITSECT CritSect; } SHCLCONTEXT; @@ -82,6 +84,75 @@ typedef struct SHCLCONTEXT static SHCLCONTEXT g_ctx; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @copydoc SHCLTXPROVIDERIFACE::pfnRootListRead */ +static DECLCALLBACK(int) shClSvcDarwinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pProviderCtx) +{ + PSHCLCLIENT pClient = (PSHCLCLIENT)pProviderCtx->pvUser; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + SHCLCONTEXT *pCtx = pClient->State.pCtx; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + char *pszRoots = NULL; + size_t cbRoots = 0; + int vrc = RTCritSectEnter(&pCtx->CritSectPasteboard); + if (RT_SUCCESS(vrc)) + { + vrc = readFileURLsFromPasteboard(pCtx->hPasteboard, &pszRoots, &cbRoots); + + int const vrc2 = RTCritSectLeave(&pCtx->CritSectPasteboard); + AssertRC(vrc2); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } + + if (RT_SUCCESS(vrc)) + vrc = ShClTransferRootsSetFromStringList(pProviderCtx->pTransfer, pszRoots, cbRoots); + RTStrFree(pszRoots); + return vrc; +} + + +/** @copydoc SHCLTRANSFERCALLBACKS::pfnOnCreated */ +static DECLCALLBACK(void) shClSvcDarwinTransferOnCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + PSHCLCLIENT pClient = (PSHCLCLIENT)pCbCtx->pvUser; + AssertPtrReturnVoid(pClient); + + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtrReturnVoid(pTransfer); + + RT_ZERO(pClient->Transfers.Provider); + if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_TO_REMOTE + && ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL) + { + ShClTransferProviderLocalQueryInterface(&pClient->Transfers.Provider); + pClient->Transfers.Provider.Interface.pfnRootListRead = shClSvcDarwinTransferIfaceHGRootListRead; + pClient->Transfers.Provider.enmSource = SHCLSOURCE_LOCAL; + pClient->Transfers.Provider.pvUser = pClient; + pClient->Transfers.Provider.cbUser = sizeof(*pClient); + + int const vrc = ShClTransferSetProvider(pTransfer, &pClient->Transfers.Provider); + AssertRC(vrc); + } +} + + +/** @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialize */ +static DECLCALLBACK(int) shClSvcDarwinTransferOnInitializeCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + + if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_TO_REMOTE + && ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL) + return ShClTransferRootListRead(pTransfer); + return VERR_NOT_SUPPORTED; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMATS fFormats) { #ifdef VBOX_COM_INPROC @@ -108,9 +179,18 @@ static int vboxClipboardChanged(SHCLCONTEXT *pCtx) && pCtx->fClientReady) { /* Retrieve the formats currently in the clipboard and supported by VBox. */ - bool fChanged = false; - vrc = queryNewPasteboardFormats(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->hStrOwnershipFlavor, - &fFormats, &fChanged); + bool fChanged = false; + vrc = RTCritSectEnter(&pCtx->CritSectPasteboard); + if (RT_SUCCESS(vrc)) + { + vrc = queryNewPasteboardFormats(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->hStrOwnershipFlavor, + &fFormats, &fChanged); + + int const vrc2 = RTCritSectLeave(&pCtx->CritSectPasteboard); + AssertRC(vrc2); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } if ( RT_SUCCESS(vrc) && fChanged) { @@ -164,9 +244,17 @@ int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) vrc = RTCritSectInit(&g_ctx.CritSect); AssertRCReturn(vrc, vrc); + vrc = RTCritSectInit(&g_ctx.CritSectPasteboard); + if (RT_FAILURE(vrc)) + { + RTCritSectDelete(&g_ctx.CritSect); + return vrc; + } + vrc = initPasteboard(&g_ctx.hPasteboard); if (RT_FAILURE(vrc)) { + RTCritSectDelete(&g_ctx.CritSectPasteboard); RTCritSectDelete(&g_ctx.CritSect); return vrc; } @@ -179,6 +267,7 @@ int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) { g_ctx.hThread = NIL_RTTHREAD; destroyPasteboard(&g_ctx.hPasteboard); + RTCritSectDelete(&g_ctx.CritSectPasteboard); RTCritSectDelete(&g_ctx.CritSect); } @@ -209,6 +298,8 @@ void ShClBackendDestroy(PSHCLBACKEND pBackend) g_ctx.pClient = NULL; g_ctx.fClientReady = false; + if (RTCritSectIsInitialized(&g_ctx.CritSectPasteboard)) + RTCritSectDelete(&g_ctx.CritSectPasteboard); if (RTCritSectIsInitialized(&g_ctx.CritSect)) RTCritSectDelete(&g_ctx.CritSect); } @@ -225,6 +316,13 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadles pClient->State.pCtx = &g_ctx; g_ctx.pClient = pClient; g_ctx.fClientReady = false; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RT_ZERO(pClient->Transfers.Callbacks); + pClient->Transfers.Callbacks.pvUser = pClient; + pClient->Transfers.Callbacks.cbUser = sizeof(*pClient); + pClient->Transfers.Callbacks.pfnOnCreated = shClSvcDarwinTransferOnCreatedCallback; + pClient->Transfers.Callbacks.pfnOnInitialize = shClSvcDarwinTransferOnInitializeCallback; +#endif vrc = VINF_SUCCESS; } else @@ -281,30 +379,30 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR if (fFormats == VBOX_SHCL_FMT_NONE) { SHCLCONTEXT *pCtx = pClient->State.pCtx; - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pCtx->CritSectPasteboard); int vrcClear = clearPasteboard(pCtx->hPasteboard, &pCtx->hStrOwnershipFlavor); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pCtx->CritSectPasteboard); return vrcClear; } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - LogRel2(("Shared Clipboard: Darwin backend does not support file-transfer clipboard offers yet\n")); + LogRel2(("Shared Clipboard: Darwin backend does not support guest-to-host file-transfer offers yet\n")); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; if (fFormats == VBOX_SHCL_FMT_NONE) { SHCLCONTEXT *pCtx = pClient->State.pCtx; - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pCtx->CritSectPasteboard); int vrcClear = clearPasteboard(pCtx->hPasteboard, &pCtx->hStrOwnershipFlavor); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pCtx->CritSectPasteboard); return vrcClear; } } #endif SHCLCONTEXT *pCtx = pClient->State.pCtx; - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pCtx->CritSectPasteboard); /* * Generate a unique flavor string for this format announcement. @@ -324,7 +422,7 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR takePasteboardOwnership(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->szGuestOwnershipFlavor, szValue, &pCtx->hStrOwnershipFlavor); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pCtx->CritSectPasteboard); /* * Now, request the data from the guest. @@ -370,7 +468,7 @@ int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTC RT_NOREF(pBackend, pCmdCtx); - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pClient->State.pCtx->CritSectPasteboard); /* Default to no data available. */ *pcbActual = 0; @@ -379,7 +477,7 @@ int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTC if (RT_FAILURE(vrc)) LogRel(("Shared Clipboard: Error reading host clipboard data from macOS, vrc=%Rrc\n", vrc)); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pClient->State.pCtx->CritSectPasteboard); return vrc; } @@ -390,12 +488,12 @@ int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENT LogFlowFuncEnter(); - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pClient->State.pCtx->CritSectPasteboard); int vrc = writeToPasteboard(pClient->State.pCtx->hPasteboard, pClient->State.pCtx->idGuestOwnership, pvData, cbData, fFormat); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pClient->State.pCtx->CritSectPasteboard); if (RT_FAILURE(vrc)) LogRel(("Shared Clipboard: Writing guest data to the macOS pasteboard failed, vrc=%Rrc\n", vrc)); diff --git a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp index cf841e883135..9813d517d16b 100644 --- a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp +++ b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp @@ -1,4 +1,4 @@ -/* $Id: darwin-pasteboard.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: darwin-pasteboard.cpp 114863 2026-08-06 10:19:52Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host implementation. */ @@ -35,14 +35,22 @@ #include #include -#include #include +#include +#include #include +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include +# include +#endif #include #include #include #include +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include +#endif #include "darwin-pasteboard.h" @@ -112,14 +120,15 @@ DECLHIDDEN(void) destroyPasteboard(PasteboardRef *pPasteboardRef) DECLHIDDEN(int) queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, uint32_t *pfFormats, bool *pfChanged) { - OSStatus orc; + AssertPtrReturn(hPasteboard, VERR_INVALID_POINTER); + AssertPtrReturn(pfFormats, VERR_INVALID_POINTER); + AssertPtrReturn(pfChanged, VERR_INVALID_POINTER); *pfFormats = 0; *pfChanged = true; - PasteboardSyncFlags syncFlags; /* Make sure all is in sync */ - syncFlags = PasteboardSynchronize(hPasteboard); + PasteboardSyncFlags const syncFlags = PasteboardSynchronize(hPasteboard); /* If nothing changed return */ if (!(syncFlags & kPasteboardModified)) { @@ -130,90 +139,317 @@ DECLHIDDEN(int) queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t id /* Are some items in the pasteboard? */ ItemCount cItems = 0; - orc = PasteboardGetItemCount(hPasteboard, &cItems); - if (orc == 0) + OSStatus const orcItems = PasteboardGetItemCount(hPasteboard, &cItems); + if (orcItems == noErr) { if (cItems < 1) Log(("queryNewPasteboardFormats: changed: No items on the pasteboard\n")); else { - /* The id of the first element in the pasteboard */ - PasteboardItemID idItem = 0; - orc = PasteboardGetItemIdentifier(hPasteboard, 1, &idItem); - if (orc == 0) + bool fOwnClipboard = false; + for (ItemCount idxItem = 0; idxItem < cItems; idxItem++) { - /* - * Retrieve all flavors on the pasteboard, maybe there - * is something we can use. Or maybe we're the owner. - */ - CFArrayRef hFlavors = 0; - orc = PasteboardCopyItemFlavors(hPasteboard, idItem, &hFlavors); - if (orc == 0) + PasteboardItemID idItem = 0; + OSStatus const orcItem = PasteboardGetItemIdentifier(hPasteboard, idxItem + 1, &idItem); + if (orcItem == noErr) { - CFIndex cFlavors = CFArrayGetCount(hFlavors); - for (CFIndex idxFlavor = 0; idxFlavor < cFlavors; idxFlavor++) + /* + * Retrieve all flavors on the pasteboard, maybe there + * is something we can use. Or maybe we're the owner. + */ + CFArrayRef hFlavors = NULL; + OSStatus const orcFlavors = PasteboardCopyItemFlavors(hPasteboard, idItem, &hFlavors); + if ( orcFlavors == noErr + && hFlavors) { - CFStringRef hStrFlavor = (CFStringRef)CFArrayGetValueAtIndex(hFlavors, idxFlavor); - if ( idItem == (PasteboardItemID)idOwnership - && hStrOwnershipFlavor - && CFStringCompare(hStrFlavor, (CFStringRef)hStrOwnershipFlavor, 0) == kCFCompareEqualTo) + CFIndex const cFlavors = CFArrayGetCount(hFlavors); + for (CFIndex idxFlavor = 0; idxFlavor < cFlavors; idxFlavor++) { - /* We made the changes ourselves. */ - Log2(("queryNewPasteboardFormats: no-changed: our clipboard!\n")); - *pfChanged = false; - *pfFormats = 0; - break; - } - - if (UTTypeConformsTo(hStrFlavor, kUTTypeBMP)) - { - Log(("queryNewPasteboardFormats: BMP flavor detected.\n")); - *pfFormats |= VBOX_SHCL_FMT_BITMAP; - } - else if ( UTTypeConformsTo(hStrFlavor, kUTTypeUTF8PlainText) - || UTTypeConformsTo(hStrFlavor, kUTTypeUTF16PlainText)) - { - Log(("queryNewPasteboardFormats: Unicode flavor detected.\n")); - *pfFormats |= VBOX_SHCL_FMT_UNICODETEXT; - } + CFStringRef hStrFlavor = (CFStringRef)CFArrayGetValueAtIndex(hFlavors, idxFlavor); + if ( idItem == (PasteboardItemID)idOwnership + && hStrOwnershipFlavor + && CFStringCompare(hStrFlavor, (CFStringRef)hStrOwnershipFlavor, 0) == kCFCompareEqualTo) + { + /* We made the changes ourselves. */ + Log2(("queryNewPasteboardFormats: no-changed: our clipboard!\n")); + fOwnClipboard = true; + break; + } + + if (UTTypeConformsTo(hStrFlavor, kUTTypeBMP)) + { + Log(("queryNewPasteboardFormats: BMP flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_BITMAP; + } + else if ( UTTypeConformsTo(hStrFlavor, kUTTypeUTF8PlainText) + || UTTypeConformsTo(hStrFlavor, kUTTypeUTF16PlainText)) + { + Log(("queryNewPasteboardFormats: Unicode flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_UNICODETEXT; + } #ifdef WITH_HTML_H2G - else if (UTTypeConformsTo(hStrFlavor, kUTTypeHTML)) - { - Log(("queryNewPasteboardFormats: HTML flavor detected.\n")); - *pfFormats |= VBOX_SHCL_FMT_HTML; - } + else if (UTTypeConformsTo(hStrFlavor, kUTTypeHTML)) + { + Log(("queryNewPasteboardFormats: HTML flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_HTML; + } +#endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (UTTypeConformsTo(hStrFlavor, kUTTypeFileURL)) + { + Log(("queryNewPasteboardFormats: File URL flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_URI_LIST; + } #endif #ifdef LOG_ENABLED - else if (LogIs2Enabled()) - { - if (CFStringGetCharactersPtr(hStrFlavor)) - Log2(("queryNewPasteboardFormats: Unknown flavor: %ls.\n", CFStringGetCharactersPtr(hStrFlavor))); - else if (CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8)) - Log2(("queryNewPasteboardFormats: Unknown flavor: %s.\n", - CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8))); - else - Log2(("queryNewPasteboardFormats: Unknown flavor: ???\n")); - } + else if (LogIs2Enabled()) + { + if (CFStringGetCharactersPtr(hStrFlavor)) + Log2(("queryNewPasteboardFormats: Unknown flavor: %ls.\n", + CFStringGetCharactersPtr(hStrFlavor))); + else if (CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8)) + Log2(("queryNewPasteboardFormats: Unknown flavor: %s.\n", + CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8))); + else + Log2(("queryNewPasteboardFormats: Unknown flavor: ???\n")); + } #endif + } } - - CFRelease(hFlavors); + else + Log(("queryNewPasteboardFormats: PasteboardCopyItemFlavors failed - %d (%#x)\n", + orcFlavors, orcFlavors)); + if (hFlavors) + CFRelease(hFlavors); + if (fOwnClipboard) + break; } else - Log(("queryNewPasteboardFormats: PasteboardCopyItemFlavors failed - %d (%#x)\n", orc, orc)); + Log(("queryNewPasteboardFormats: PasteboardGetItemIdentifier failed - %d (%#x)\n", orcItem, orcItem)); } - else - Log(("queryNewPasteboardFormats: PasteboardGetItemIdentifier failed - %d (%#x)\n", orc, orc)); - if (*pfChanged) + if (fOwnClipboard) + { + *pfChanged = false; + *pfFormats = 0; + } + else Log(("queryNewPasteboardFormats: changed: *pfFormats=%#x\n", *pfFormats)); } } else - Log(("queryNewPasteboardFormats: PasteboardGetItemCount failed - %d (%#x)\n", orc, orc)); + Log(("queryNewPasteboardFormats: PasteboardGetItemCount failed - %d (%#x)\n", orcItems, orcItems)); return VINF_SUCCESS; } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Converts one macOS file URL pasteboard value to a canonical file URI. + * + * @returns VBox status code. + * @param hData File URL pasteboard data. + * @param ppszURI Where to return the allocated URI. Must be + * freed with RTStrFree(). + * @param pcchURI Where to return the URI length without the + * terminator. + */ +static int darwinPasteboardFileURLToURI(CFDataRef hData, char **ppszURI, size_t *pcchURI) +{ + AssertPtrReturn(hData, VERR_INVALID_POINTER); + AssertPtrReturn(ppszURI, VERR_INVALID_POINTER); + AssertPtrReturn(pcchURI, VERR_INVALID_POINTER); + + *ppszURI = NULL; + *pcchURI = 0; + + CFIndex const cbData = CFDataGetLength(hData); + if (cbData <= 0) + return VERR_INVALID_PARAMETER; + if ((uint64_t)cbData > (uint64_t)RTPATH_MAX * 3 + 32) + return VERR_TOO_MUCH_DATA; + + const UInt8 *pbData = CFDataGetBytePtr(hData); + AssertPtrReturn(pbData, VERR_INVALID_POINTER); + + size_t cchData = 0; + int vrc = ShClHlpUtf8ValidateExact((const char *)pbData, (size_t)cbData, &cchData); + if (RT_FAILURE(vrc)) + return vrc; + if (!cchData) + return VERR_INVALID_PARAMETER; + for (size_t off = 0; off + 2 < cchData; off++) + if ( pbData[off] == '%' + && pbData[off + 1] == '0' + && pbData[off + 2] == '0') + return VERR_INVALID_PARAMETER; + + CFURLRef hURL = CFURLCreateWithBytes(kCFAllocatorDefault, pbData, (CFIndex)cchData, + kCFStringEncodingUTF8, NULL /* baseURL */); + if (!hURL) + return VERR_INVALID_PARAMETER; + + CFStringRef hScheme = CFURLCopyScheme(hURL); + CFStringRef hLocation = CFURLCopyNetLocation(hURL); + CFStringRef hQuery = CFURLCopyQueryString(hURL, NULL /* charactersToLeaveEscaped */); + CFStringRef hFragment = CFURLCopyFragment(hURL, NULL /* charactersToLeaveEscaped */); + if ( !hScheme + || CFStringCompare(hScheme, CFSTR("file"), kCFCompareCaseInsensitive) != kCFCompareEqualTo + || ( hLocation + && CFStringGetLength(hLocation) + && CFStringCompare(hLocation, CFSTR("localhost"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) + || hQuery + || hFragment) + vrc = VERR_INVALID_PARAMETER; + + char szPath[RTPATH_MAX]; + if (RT_SUCCESS(vrc)) + { + if (!CFURLGetFileSystemRepresentation(hURL, true /* resolveAgainstBase */, (UInt8 *)szPath, sizeof(szPath))) + vrc = VERR_INVALID_PARAMETER; + else if (!RTPathStartsWithRoot(szPath)) + vrc = VERR_PATH_IS_RELATIVE; + else + vrc = RTStrValidateEncoding(szPath); + } + + if (RT_SUCCESS(vrc)) + { + vrc = RTUriFileCreateEx(szPath, RTPATH_STR_F_STYLE_UNIX, ppszURI, 0 /* cbUri */, NULL /* pcchUri */); + if (RT_SUCCESS(vrc)) + *pcchURI = strlen(*ppszURI); + } + + if (hFragment) + CFRelease(hFragment); + if (hQuery) + CFRelease(hQuery); + if (hLocation) + CFRelease(hLocation); + if (hScheme) + CFRelease(hScheme); + CFRelease(hURL); + return vrc; +} + + +/** + * Reads local file URLs from the macOS pasteboard as a transfer root list. + * + * @returns VBox status code. + * @param hPasteboard Reference to the pasteboard to read. + * @param ppszRoots Where to return the allocated CRLF-separated + * file URI list. Must be freed with RTStrFree(). + * @param pcbRoots Where to return the list size, including the + * terminator. + */ +DECLHIDDEN(int) readFileURLsFromPasteboard(PasteboardRef hPasteboard, char **ppszRoots, size_t *pcbRoots) +{ + AssertPtrReturn(hPasteboard, VERR_INVALID_POINTER); + AssertPtrReturn(ppszRoots, VERR_INVALID_POINTER); + AssertPtrReturn(pcbRoots, VERR_INVALID_POINTER); + + *ppszRoots = NULL; + *pcbRoots = 0; + + PasteboardSynchronize(hPasteboard); + + ItemCount cItems = 0; + OSStatus const orcItems = PasteboardGetItemCount(hPasteboard, &cItems); + if (orcItems != noErr) + return VERR_GENERAL_FAILURE; + + char *pszRoots = NULL; + size_t cbRoots = 0; + size_t cRoots = 0; + int vrc = VINF_SUCCESS; + for (ItemCount idxItem = 0; idxItem < cItems; idxItem++) + { + PasteboardItemID idItem = 0; + OSStatus const orcItem = PasteboardGetItemIdentifier(hPasteboard, idxItem + 1, &idItem); + if (orcItem != noErr) + { + vrc = VERR_GENERAL_FAILURE; + break; + } + + CFArrayRef hFlavors = NULL; + OSStatus const orcFlavors = PasteboardCopyItemFlavors(hPasteboard, idItem, &hFlavors); + if ( orcFlavors != noErr + || !hFlavors) + { + if (hFlavors) + CFRelease(hFlavors); + vrc = VERR_GENERAL_FAILURE; + break; + } + + CFStringRef hFileURLFlavor = NULL; + CFIndex const cFlavors = CFArrayGetCount(hFlavors); + for (CFIndex idxFlavor = 0; idxFlavor < cFlavors; idxFlavor++) + { + CFStringRef hFlavor = (CFStringRef)CFArrayGetValueAtIndex(hFlavors, idxFlavor); + if (UTTypeConformsTo(hFlavor, kUTTypeFileURL)) + { + hFileURLFlavor = hFlavor; + break; + } + } + + if (hFileURLFlavor) + { + CFDataRef hData = NULL; + OSStatus const orcData = PasteboardCopyItemFlavorData(hPasteboard, idItem, hFileURLFlavor, &hData); + if ( orcData == noErr + && hData) + { + char *pszURI = NULL; + size_t cchURI = 0; + vrc = darwinPasteboardFileURLToURI(hData, &pszURI, &cchURI); + if (RT_SUCCESS(vrc)) + { + size_t const cchSep = sizeof(SHCL_TRANSFER_URI_LIST_SEP_STR) - 1; + if ( cbRoots > ~(size_t)0 - cchSep - 1 + || cchURI > ~(size_t)0 - cbRoots - cchSep - 1) + vrc = VERR_TOO_MUCH_DATA; + else + { + vrc = RTStrAAppendExN(&pszRoots, 2 /* cPairs */, pszURI, cchURI, + SHCL_TRANSFER_URI_LIST_SEP_STR, cchSep); + if (RT_SUCCESS(vrc)) + { + cbRoots += cchURI + cchSep; + cRoots++; + } + } + } + RTStrFree(pszURI); + } + else + vrc = VERR_GENERAL_FAILURE; + if (hData) + CFRelease(hData); + } + + CFRelease(hFlavors); + if (RT_FAILURE(vrc)) + break; + } + + if ( RT_SUCCESS(vrc) + && !cRoots) + vrc = VERR_NOT_FOUND; + if (RT_SUCCESS(vrc)) + { + *ppszRoots = pszRoots; + *pcbRoots = cbRoots + 1; + LogRel2(("Shared Clipboard: macOS reported %zu root entries for transfer to guest\n", cRoots)); + } + else + RTStrFree(pszRoots); + return vrc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + /** * Read content from the host clipboard and write it to the internal clipboard * structure for further processing. From e74eb4a95874bbe72bbd5e9ed9278930e6586629 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 10:23:36 +0000 Subject: [PATCH 013/176] Shared Clipboard/X11: Fixed parsing of CRLF-separated transfer roots. This does not change the HGCM protocol or compatibility with older Guest Additions. bugref:4697 svn:sync-xref-src-repo-rev: r174701 --- src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index dcf649447bfb..1dded21cb966 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 114744 2026-07-21 18:37:21Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard-x11.cpp 114864 2026-08-06 10:23:36Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -84,8 +84,7 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA &pvData, &cbData); if (RT_SUCCESS(rc)) { - rc = ShClTransferRootsSetFromStringListEx(pTransfer, (const char *)pvData, cbData, - "\n" /* X11-based Desktop environments separate entries with "\n" */); + rc = ShClTransferRootsSetFromStringList(pTransfer, (const char *)pvData, cbData); RTMemFree(pvData); } break; From d2ca89ca0624ac907483a9ae82d4f85c14fc0641 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 10:27:20 +0000 Subject: [PATCH 014/176] Shared Clipboard/Transfers: Percent-encoded file names in HTTP transfer URLs. Added a new testcase. svn:sync-xref-src-repo-rev: r174702 --- .../clipboard-transfers-http.cpp | 5 ++-- .../testcase/tstClipboardHttpServer.cpp | 28 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp index 31c8796b2eaa..0a29676beafd 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-http.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-http.cpp 114865 2026-08-06 10:27:20Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts. */ @@ -1325,7 +1325,8 @@ char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTrans if (pEntry) { AssertReturn(RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX), NULL); - pszUrl = RTStrAPrintf2("%s:%RU16%s/%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual, pEntry->pszName); + pszUrl = RTStrAPrintf2("%s:%RU16%s/%RMpp", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, + pSrvTx->szPathVirtual, pEntry->pszName); } } else /* Only return the base. */ diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp index bc00b698a40e..0ebf106e8494 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardHttpServer.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardHttpServer.cpp 114865 2026-08-06 10:27:20Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard HTTP server test case. */ @@ -69,7 +69,7 @@ static struct RTFMODE fMode; /** Local path to serve via HTTP server. */ const char *pszPath; - /** URL to use for downloading the file via RTHttp APIs. Has to be fully escaped. */ + /** Expected URL path component. Has to be fully percent-encoded. */ const char *pszUrl; /** File allocation size. * Specify UINT64_MAX for random size. */ @@ -84,6 +84,7 @@ static struct { RTFS_TYPE_FILE, "file1.txt", "file1.txt", _64K, VINF_SUCCESS }, /* Note: For RTHttpGetFile() the URL needs to be percent-encoded. */ { RTFS_TYPE_FILE, "file2 with spaces.txt", "file2%20with%20spaces.txt", _64K, VINF_SUCCESS }, + { RTFS_TYPE_FILE, "file #%20?.txt", "file%20%23%2520%3F.txt", 42, VINF_SUCCESS }, { RTFS_TYPE_FILE, "bigfile.bin", "bigfile.bin", _512M, VINF_SUCCESS }, { RTFS_TYPE_FILE, "zerobytes", "zerobytes", 0, VINF_SUCCESS }, { RTFS_TYPE_FILE, "file\\with\\slashes", "file%5Cwith%5Cslashes", 42, VINF_SUCCESS }, @@ -616,7 +617,7 @@ int main(int argc, char *argv[]) RTTEST_CHECK_RC_OK(hTest, RTHttpSetProxy(hClient, NULL /*pszProxyUrl*/, 0 /*uPort*/, NULL /*pszProxyUser*/, NULL /*pszProxyPwd*/)); - char szURL[RTPATH_MAX]; + char szExpectedUrl[RTPATH_MAX]; if (ShClTransferCtxGetTotalTransfers(&TxCtx) > 0) { PSHCLTRANSFER pTx = ShClTransferCtxGetTransferByIndex(&TxCtx, 0); @@ -631,7 +632,19 @@ int main(int argc, char *argv[]) { PSHCLTRANSFER pTx = ShClTransferCtxGetTransferByIndex(&TxCtx, i); char *pszUrlBase = ShClTransferHttpServerGetUrlA(&HttpSrv, ShClTransferGetID(pTx), UINT64_MAX); - RTTEST_CHECK(hTest, RTStrPrintf2(szURL, sizeof(szURL), "%s/%s", pszUrlBase, g_aTests[i].pszUrl)); + char *pszUrl = ShClTransferHttpServerGetUrlA(&HttpSrv, ShClTransferGetID(pTx), 0 /* idxEntry */); + RTTEST_CHECK(hTest, pszUrlBase != NULL); + RTTEST_CHECK(hTest, pszUrl != NULL); + if (!pszUrlBase || !pszUrl) + { + RTStrFree(pszUrlBase); + RTStrFree(pszUrl); + continue; + } + RTTEST_CHECK(hTest, RTStrPrintf2(szExpectedUrl, sizeof(szExpectedUrl), "%s/%s", + pszUrlBase, g_aTests[i].pszUrl)); + RTTEST_CHECK_MSG(hTest, RTStrCmp(pszUrl, szExpectedUrl) == 0, + (hTest, "Expected URL '%s', got '%s'\n", szExpectedUrl, pszUrl)); RTStrFree(pszUrlBase); switch (g_aTests[i].fMode & RTFS_TYPE_MASK) @@ -643,8 +656,8 @@ int main(int argc, char *argv[]) RTTEST_CHECK_RC_OK(hTest, RTPathTemp(szDstFile, sizeof(szDstFile))); RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szDstFile, sizeof(szDstFile), "tstClipboardHttpServer-XXXXXX")); RTTEST_CHECK_RC_OK(hTest, RTFileCreateTemp(szDstFile, 0600)); - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Downloading file '%s' -> '%s'\n", szURL, szDstFile); - RTTEST_CHECK_RC_OK(hTest, RTHttpGetFile(hClient, szURL, szDstFile)); + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Downloading file '%s' -> '%s'\n", pszUrl, szDstFile); + RTTEST_CHECK_RC_OK(hTest, RTHttpGetFile(hClient, pszUrl, szDstFile)); /* Compare files. */ char szSrcFile[RTPATH_MAX]; @@ -665,7 +678,7 @@ int main(int argc, char *argv[]) RTTEST_CHECK_RC_OK(hTest, RTPathTemp(szDstFile, sizeof(szDstFile))); RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szDstFile, sizeof(szDstFile), "tstClipboardHttpServer-XXXXXX")); RTTEST_CHECK_RC_OK(hTest, RTFileCreateTemp(szDstFile, 0600)); - RTTEST_CHECK_RC (hTest, RTHttpGetFile(hClient, szURL, szDstFile), g_aTests[i].rc); + RTTEST_CHECK_RC (hTest, RTHttpGetFile(hClient, pszUrl, szDstFile), g_aTests[i].rc); RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szDstFile)); break; } @@ -673,6 +686,7 @@ int main(int argc, char *argv[]) default: break; } + RTStrFree(pszUrl); } RTTEST_CHECK_RC_OK(hTest, RTHttpDestroy(hClient)); From 010f11c4fdd85ec8acb5e93cadf065fb3bfe7e92 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 11:24:19 +0000 Subject: [PATCH 015/176] GuestHost/SharedClipboard: Made X11 file-transfer target selection use explicit direction and priority rules, and prevented KDE cut-selection metadata from being parsed as a URI list. svn:sync-xref-src-repo-rev: r174703 --- .../SharedClipboard/clipboard-x11.cpp | 187 +++++++++++++----- .../testcase/tstClipboardGH-X11.cpp | 78 +++++++- 2 files changed, 216 insertions(+), 49 deletions(-) diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp index bcf38199f302..65d9d10ee2e7 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -142,14 +142,61 @@ SHCL_X11_DECL(SHCLX11FMTTABLE) g_aFormats[] = { "x-special/gnome-copied-files", SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES, VBOX_SHCL_FMT_URI_LIST }, { "x-special/mate-copied-files", SHCLX11FMT_URI_LIST_MATE_COPIED_FILES, VBOX_SHCL_FMT_URI_LIST }, { "x-special/nautilus-clipboard", SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD, VBOX_SHCL_FMT_URI_LIST }, - /* KDE uses this as cut/copy metadata; the actual file list is in text/uri-list. */ - { "application/x-kde-cutselection", SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, VBOX_SHCL_FMT_NONE }, + /* Associate KDE cut-selection with the VBox URI format so that we advertise + * it when exporting files. It is metadata only, so s_aTransferTargets + * explicitly prevents selecting or parsing it in the other direction. */ + { "application/x-kde-cutselection", SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, VBOX_SHCL_FMT_URI_LIST }, /** @todo Anything else we need to add here? */ /** @todo Add Wayland / Weston support. */ #endif }; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** X11 transfer target can be read from the X11 clipboard. */ +# define SHCLX11TRANSFERDIR_F_FROM_X11 RT_BIT_32(0) +/** X11 transfer target can be offered to the X11 clipboard. */ +# define SHCLX11TRANSFERDIR_F_TO_X11 RT_BIT_32(1) +/** X11 transfer target supports both directions. */ +# define SHCLX11TRANSFERDIR_F_BIDIRECTIONAL (SHCLX11TRANSFERDIR_F_FROM_X11 | SHCLX11TRANSFERDIR_F_TO_X11) + +/** + * Direction and selection policy for an X11 file-transfer target. + * + * The X11 format enum identifies the representation; it does not define + * preference or whether the representation actually contains file names. + */ +typedef struct SHCLX11TRANSFERTARGET +{ + /** The X11 transfer format. */ + SHCLX11FMT enmFmt; + /** SHCLX11TRANSFERDIR_F_XXX direction flags. */ + uint32_t fDirections; + /** Incoming selection priority; higher values have higher priority, while + * zero means the target cannot be read as a file list. */ + uint8_t uPriorityFromX11; +} SHCLX11TRANSFERTARGET; + +/** + * Transfer target capabilities and incoming selection order. + * + * Keeping this policy separate from SHCLX11FMT makes adding or reordering enum + * values harmless. Prefer the standard URI-list target when several file-list + * representations are offered, followed by the file-manager-specific formats. + * KDE cut-selection carries only cut/copy state, not file names, and therefore + * is offered when exporting but never accepted as an incoming file list. + */ +static const SHCLX11TRANSFERTARGET s_aTransferTargets[] = +{ + { SHCLX11FMT_URI_LIST, SHCLX11TRANSFERDIR_F_BIDIRECTIONAL, 100 }, + { SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES, SHCLX11TRANSFERDIR_F_BIDIRECTIONAL, 90 }, + { SHCLX11FMT_URI_LIST_MATE_COPIED_FILES, SHCLX11TRANSFERDIR_F_BIDIRECTIONAL, 80 }, + { SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD, SHCLX11TRANSFERDIR_F_BIDIRECTIONAL, 70 }, + { SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, SHCLX11TRANSFERDIR_F_TO_X11, 0 } +}; +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + #ifdef TESTCASE # ifdef RT_OS_SOLARIS_10 char XtStrings [] = ""; @@ -243,6 +290,39 @@ static SHCLFORMAT clipVBoxFormatForX11Format(SHCLX11FMTIDX uFmtIdx) return g_aFormats[uFmtIdx].uFmtVBox; } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Looks up the direction and priority policy of an X11 transfer target. + * + * @returns Transfer target capabilities, or NULL if the format is not a transfer target. + * @param enmFmt X11 format to look up. + */ +static const SHCLX11TRANSFERTARGET *shClX11TransferTargetLookup(SHCLX11FMT enmFmt) +{ + for (size_t i = 0; i < RT_ELEMENTS(s_aTransferTargets); i++) + if (s_aTransferTargets[i].enmFmt == enmFmt) + return &s_aTransferTargets[i]; + return NULL; +} + + +/** + * Checks whether an X11 transfer target supports a direction. + * + * Unknown transfer targets are deliberately rejected so they cannot silently + * become importable or exportable merely by being added to g_aFormats. + * + * @returns true if supported, false if not. + * @param enmFmt X11 format to check. + * @param fDirection SHCLX11TRANSFERDIR_F_XXX direction to check. + */ +static bool shClX11TransferTargetSupportsDirection(SHCLX11FMT enmFmt, uint32_t fDirection) +{ + const SHCLX11TRANSFERTARGET *pTarget = shClX11TransferTargetLookup(enmFmt); + return pTarget && (pTarget->fDirections & fDirection) != 0; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + /** * Looks up the X11 format matching a given X11 atom. * @@ -274,8 +354,19 @@ static SHCLX11FMTIDX clipEnumX11Formats(SHCLFORMATS uFormatsVBox, { for (unsigned i = lastFmtIdx + 1; i < RT_ELEMENTS(g_aFormats); ++i) { - if (uFormatsVBox & clipVBoxFormatForX11Format(i)) - return i; + SHCLFORMAT const uFmtVBox = clipVBoxFormatForX11Format(i); + if (uFormatsVBox & uFmtVBox) + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* URI targets are filtered by the explicit export policy. This + * includes KDE metadata, while excluding any future import-only + * representations. */ + if ( uFmtVBox != VBOX_SHCL_FMT_URI_LIST + || shClX11TransferTargetSupportsDirection(clipRealFormatForX11Format(i), + SHCLX11TRANSFERDIR_F_TO_X11)) +#endif + return i; + } } return NIL_CLIPX11FORMAT; @@ -594,8 +685,13 @@ static SHCLX11FMTIDX clipGetHtmlFormatFromTargets(PSHCLX11CTX pCtx, # ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** - * Goes through an array of X11 clipboard targets to see if they contain an URI list - * format we can support, and if so choose the ones we prefer. + * Goes through an array of X11 clipboard targets to find the preferred file-list + * representation that is safe to import. + * + * Selection uses s_aTransferTargets rather than SHCLX11FMT enum values or the + * order in which the clipboard owner returned its targets. Targets which only + * carry transfer metadata are ignored even when they map to VBOX_SHCL_FMT_URI_LIST + * for export purposes. * * @return Supported X clipboard format. * @param pCtx The X11 clipboard context to use. @@ -610,27 +706,29 @@ SHCL_X11_DECL(SHCLX11FMTIDX) clipGetURIListFormatFromTargets(PSHCLX11CTX pCtx, AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT); SHCLX11FMTIDX idxFmtURI = NIL_CLIPX11FORMAT; - SHCLX11FMT fmtURIX11 = SHCLX11FMT_INVALID; + uint8_t uPriority = 0; bool fSawUnsupportedTransferMetadata = false; for (unsigned i = 0; i < cTargets; ++i) { SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i]; if (idxFmt != NIL_CLIPX11FORMAT) { - SHCLX11FMT const fmtReal = clipRealFormatForX11Format(idxFmt); - if ( fmtReal == SHCLX11FMT_URI_LIST_KDE_CUTSELECTION - && clipVBoxFormatForX11Format(idxFmt) != VBOX_SHCL_FMT_URI_LIST) + SHCLX11FMT const enmFmtX11 = clipRealFormatForX11Format(idxFmt); + const SHCLX11TRANSFERTARGET *pTarget = shClX11TransferTargetLookup(enmFmtX11); + if ( pTarget + && !(pTarget->fDirections & SHCLX11TRANSFERDIR_F_FROM_X11)) { fSawUnsupportedTransferMetadata = true; - LogRelMax2(16, ("Shared Clipboard: Ignoring X11 clipboard target '%s'; it only describes cut/copy state, not file names\n", + LogRelMax2(16, ("Shared Clipboard: Ignoring X11 clipboard target '%s'; it only describes cut/copy " + "state, not file names\n", g_aFormats[idxFmt].pcszAtom)); } - - if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_URI_LIST) - && fmtURIX11 < fmtReal) + else if ( pTarget + && clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_URI_LIST + && uPriority < pTarget->uPriorityFromX11) { - fmtURIX11 = fmtReal; - idxFmtURI = idxFmt; + uPriority = pTarget->uPriorityFromX11; + idxFmtURI = idxFmt; } } } @@ -639,7 +737,8 @@ SHCL_X11_DECL(SHCLX11FMTIDX) clipGetURIListFormatFromTargets(PSHCLX11CTX pCtx, LogRelMax2(16, ("Shared Clipboard: Selected X11 URI-list target '%s' for host file transfer\n", g_aFormats[idxFmtURI].pcszAtom)); else if (fSawUnsupportedTransferMetadata) - LogRelMax2(16, ("Shared Clipboard: X11 clipboard had file-transfer metadata but no supported file list target (for example text/uri-list); host file transfer will not be announced\n")); + LogRelMax2(16, ("Shared Clipboard: X11 clipboard had file-transfer metadata but no supported file list target " + "(for example text/uri-list); host file transfer will not be announced\n")); return idxFmtURI; } @@ -1853,12 +1952,7 @@ static int clipConvertToX11Data(PSHCLX11CTX pCtx, Atom *atomTarget, } } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - else if ( fmtX11 == SHCLX11FMT_URI_LIST - || fmtX11 == SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES - /** @todo BUGBUG Not sure about the following ones; test those. */ - || fmtX11 == SHCLX11FMT_URI_LIST_MATE_COPIED_FILES - || fmtX11 == SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD - || fmtX11 == SHCLX11FMT_URI_LIST_KDE_CUTSELECTION) + else if (shClX11TransferTargetSupportsDirection(fmtX11, SHCLX11TRANSFERDIR_F_TO_X11)) { if (pCtx->vboxFormats & VBOX_SHCL_FMT_URI_LIST) { @@ -2103,13 +2197,22 @@ int ShClX11TransferConvertToX11(const char *pszSrc, size_t cbSrc, SHCLX11FMT en switch (enmFmtX11) { + case SHCLX11FMT_URI_LIST_KDE_CUTSELECTION: + { + /* KDE stores only cut/copy state in this target: "0" means copy + * and "1" means cut. Shared Clipboard exports copies, while the + * file names themselves are provided through text/uri-list. */ + pszDst = RTStrDup("0"); /* Copy. */ + if (!pszDst) + rc = VERR_NO_MEMORY; + break; + } + case SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_MATE_COPIED_FILES: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_KDE_CUTSELECTION: { const char chSep = '\n'; /* Currently (?) all entries need to be separated by '\n'. */ @@ -2515,28 +2618,20 @@ SHCL_X11_DECL(void) clipConvertDataFromX11Worker(void *pClient, void *pvSrc, uns # ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS else if (pReq->Read.uFmtVBox == VBOX_SHCL_FMT_URI_LIST) { - /* In which format is the clipboard data? */ - switch (clipRealFormatForX11Format(pReq->Read.idxFmtX11)) + SHCLX11FMT const enmFmtX11 = clipRealFormatForX11Format(pReq->Read.idxFmtX11); + /* Recheck the direction at the conversion boundary. Normally target + * selection already guarantees this, but the check also protects + * forced, stale or otherwise inconsistent format indices from feeding + * metadata such as KDE's "0"/"1" value to the URI-list parser. */ + if (shClX11TransferTargetSupportsDirection(enmFmtX11, SHCLX11TRANSFERDIR_F_FROM_X11)) + rc = ShClX11TransferConvertFromX11((const char *)pvSrc, cbSrc, (char **)&pvDst, &cbDst); + else { - case SHCLX11FMT_URI_LIST: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_MATE_COPIED_FILES: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_KDE_CUTSELECTION: - { - rc = ShClX11TransferConvertFromX11((const char *)pvSrc, cbSrc, (char **)&pvDst, &cbDst); - break; - } - - default: - { - AssertFailedStmt(rc = VERR_NOT_SUPPORTED); /* Missing code? */ - break; - } + const char *pszTarget = pReq->Read.idxFmtX11 < RT_ELEMENTS(g_aFormats) + ? g_aFormats[pReq->Read.idxFmtX11].pcszAtom : ""; + LogRelMax2(16, ("Shared Clipboard: Refusing to parse X11 clipboard target '%s' as a file list\n", + pszTarget)); + rc = VERR_NOT_SUPPORTED; } } # endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp index e763d17facdc..0dc05c161fad 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardGH-X11.cpp 114767 2026-07-24 22:06:05Z knut.osmundsen@oracle.com $ */ +/* $Id: tstClipboardGH-X11.cpp 114866 2026-08-06 11:24:19Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard guest/host X11 code test cases. */ @@ -485,17 +485,90 @@ static bool tstClipURIListFormatConversion(PSHCLX11CTX pCtx) SHCLX11FMTIDX aTargets[2]; SHCLX11FMTIDX idxFmtX11; - aTargets[0] = tstClipFindX11FormatByAtomText("application/x-kde-cutselection"); + /* Prefer the standard target over a higher-valued format enum. */ + aTargets[0] = tstClipFindX11FormatByAtomText("x-special/gnome-copied-files"); aTargets[1] = tstClipFindX11FormatByAtomText("text/uri-list"); idxFmtX11 = clipGetURIListFormatFromTargets(pCtx, aTargets, 2); if (clipRealFormatForX11Format(idxFmtX11) != SHCLX11FMT_URI_LIST) fSuccess = false; + /* Target enumeration order must not affect the result. */ + aTargets[0] = tstClipFindX11FormatByAtomText("text/uri-list"); + aTargets[1] = tstClipFindX11FormatByAtomText("x-special/gnome-copied-files"); + idxFmtX11 = clipGetURIListFormatFromTargets(pCtx, aTargets, 2); + if (clipRealFormatForX11Format(idxFmtX11) != SHCLX11FMT_URI_LIST) + fSuccess = false; + + /* KDE cut-selection is metadata, but another target can provide the file list. */ + aTargets[0] = tstClipFindX11FormatByAtomText("application/x-kde-cutselection"); + aTargets[1] = tstClipFindX11FormatByAtomText("x-special/gnome-copied-files"); + idxFmtX11 = clipGetURIListFormatFromTargets(pCtx, aTargets, 2); + if (clipRealFormatForX11Format(idxFmtX11) != SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES) + fSuccess = false; + aTargets[0] = tstClipFindX11FormatByAtomText("application/x-kde-cutselection"); idxFmtX11 = clipGetURIListFormatFromTargets(pCtx, aTargets, 1); if (idxFmtX11 != NIL_CLIPX11FORMAT) fSuccess = false; + /* Even a forced read must not treat KDE cut/copy metadata as a file list. */ + static const char s_szKdeCopy[] = "0"; + tstClipSetSelectionValues("application/x-kde-cutselection", XA_STRING, + s_szKdeCopy, sizeof(s_szKdeCopy) - 1, 8); + pCtx->idxFmtURI = aTargets[0]; + uint8_t abBuf[TESTCASE_MAX_BUF_SIZE]; + uint32_t cbRead = 0; + int rc = ShClX11ReadDataFromX11(pCtx, &g_EventSource, g_msTimeout, VBOX_SHCL_FMT_URI_LIST, + abBuf, sizeof(abBuf), &cbRead); + if (rc != VERR_SHCLPB_NO_DATA) + fSuccess = false; + pCtx->idxFmtURI = NIL_CLIPX11FORMAT; + + /* When exporting files, KDE receives copy metadata separately from text/uri-list. */ + static const char s_szURI[] = "file:///tmp/a"; + void *pvKde = NULL; + size_t cbKde = 0; + rc = ShClX11TransferConvertToX11(s_szURI, sizeof(s_szURI) - 1, SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, + &pvKde, &cbKde); + if ( RT_FAILURE(rc) + || cbKde != sizeof(s_szKdeCopy) - 1 + || memcmp(pvKde, s_szKdeCopy, sizeof(s_szKdeCopy) - 1)) + fSuccess = false; + XtFree((char *)pvKde); + + /* KDE metadata and the standard URI list must both be offered to X11 consumers. */ + rc = ShClX11ReportFormatsToX11Async(pCtx, VBOX_SHCL_FMT_URI_LIST); + if (RT_FAILURE(rc)) + fSuccess = false; + else + { + Atom atomType; + XtPointer pvTargets = NULL; + unsigned long cTargets; + int iFormat; + if (!tstClipConvertSelection("TARGETS", &atomType, &pvTargets, &cTargets, &iFormat)) + fSuccess = false; + else + { + bool fFoundURI = false; + bool fFoundKDE = false; + Atom const *paTargets = (Atom const *)pvTargets; + for (size_t i = 0; i < cTargets; i++) + { + if (paTargets[i] == XInternAtom(NULL, "text/uri-list", 0)) + fFoundURI = true; + else if (paTargets[i] == XInternAtom(NULL, "application/x-kde-cutselection", 0)) + fFoundKDE = true; + } + if ( atomType != XA_ATOM + || iFormat != 32 + || !fFoundURI + || !fFoundKDE) + fSuccess = false; + } + XtFree((char *)pvTargets); + } + return fSuccess; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -969,4 +1042,3 @@ int main() return RTTestSummaryAndDestroy(hTest); } - From ddae7b45e68044f7aac128159e52efbfd6370cc1 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 15:19:51 +0000 Subject: [PATCH 016/176] Shared Clipboard/X11: Prepare file-transfer URI lists asynchronously. This moves HTTP transfer initialization and URI-list generation out of the X11 selection conversion callback on both host and guest. URI targets are now advertised only after their data has been prepared and cached, making selection conversion cache-only and preventing the X11 event thread from blocking. No Shared Clipboard protocol changes. svn:sync-xref-src-repo-rev: r174704 --- include/VBox/GuestHost/SharedClipboard-x11.h | 48 +- .../x11/VBoxClient/clipboard-x11.cpp | 351 +++++++++-- src/VBox/Additions/x11/VBoxClient/clipboard.h | 6 +- .../SharedClipboard/clipboard-x11.cpp | 103 +++- .../testcase/tstClipboardGH-X11.cpp | 18 +- .../src-client/linux/ClipboardBackendX11.cpp | 560 +++++++++++++++--- 6 files changed, 935 insertions(+), 151 deletions(-) diff --git a/include/VBox/GuestHost/SharedClipboard-x11.h b/include/VBox/GuestHost/SharedClipboard-x11.h index caa72366bf45..3b1826db4edb 100644 --- a/include/VBox/GuestHost/SharedClipboard-x11.h +++ b/include/VBox/GuestHost/SharedClipboard-x11.h @@ -106,6 +106,39 @@ typedef struct SHCLX11FMTTABLE /** Defines an index of the X11 clipboard format table. */ typedef unsigned SHCLX11FMTIDX; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +/** + * Backend-neutral state for asynchronously preparing and publishing an X11 + * HTTP-backed file-transfer offer. + * + * The backend which embeds this structure supplies the synchronization. The + * offer generation rejects work completed for an old clipboard offer, while + * the transfer ID and generation identify the exact transfer associated with + * the preparation or currently advertised URI-list data. + */ +typedef struct SHCLX11TRANSFERSTATE +{ + /** Most recently reported source formats. */ + SHCLFORMATS fFormats; + /** Generation of the most recently reported source clipboard offer. */ + uint64_t uOfferGeneration; + /** Offer generation for which a transfer is currently being prepared. */ + uint64_t uPreparingOfferGeneration; + /** ID of the transfer bound to the current preparation request. */ + SHCLTRANSFERID idTransfer; + /** Generation of the transfer bound to the current request. */ + SHCLTRANSFERGEN uTransferGeneration; + /** ID of the transfer backing the currently advertised URI-list data. */ + SHCLTRANSFERID idPublishedTransfer; + /** Generation of the transfer backing the advertised URI-list data. */ + SHCLTRANSFERGEN uPublishedTransferGeneration; + /** Whether a transfer preparation request is outstanding. */ + bool fPreparing; +} SHCLX11TRANSFERSTATE; +/** Pointer to X11 transfer state. */ +typedef SHCLX11TRANSFERSTATE *PSHCLX11TRANSFERSTATE; +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + /** * Structure for maintaining a Shared Clipboard context on X11 platforms. */ @@ -149,6 +182,8 @@ typedef struct SHCLX11CTX #endif /** What kind of formats does VBox have to offer? */ SHCLFORMATS vboxFormats; + /** Formats which must be served exclusively from the cache. */ + SHCLFORMATS fCacheOnlyFormats; /** Internal cache of VBox clipboard formats. */ SHCLCACHE Cache; /** When we wish the clipboard to exit, we have to wake up the event @@ -207,6 +242,13 @@ typedef struct SHCLX11REQUEST { /** VBox formats to announce. */ SHCLFORMATS fFormats; + /** Optional format whose data should be cached before announcing + * the formats. VBOX_SHCL_FMT_NONE if no data was supplied. */ + SHCLFORMAT uFmtCache; + /** Optional cache data owned by the request. */ + void *pvCache; + /** Size of the optional cache data in bytes. */ + uint32_t cbCache; } Formats; /** Read request. */ struct @@ -269,7 +311,10 @@ int ShClX11Term(PSHCLX11CTX pCtx); int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool grab); int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab); int ShClX11ThreadStop(PSHCLX11CTX pCtx); -int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS vboxFormats); +int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS fFormats); +/** Reports formats after atomically seeding one X11 clipboard cache entry. */ +int ShClX11ReportFormatsToX11AsyncEx(PSHCLX11CTX pCtx, SHCLFORMATS fFormats, SHCLFORMAT uFmtCache, + const void *pvCache, uint32_t cbCache); int ShClX11ReadDataFromX11Async(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, uint32_t cbMax, PSHCLEVENT pEvent); int ShClX11ReadDataFromX11Ex(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, void **ppvBuf, uint32_t *pcbBuf); int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, void *pvBuf, uint32_t cbBuf, uint32_t *pcbBuf); @@ -283,4 +328,3 @@ int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppsz /** @} */ #endif /* !VBOX_INCLUDED_GuestHost_SharedClipboard_x11_h */ - diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index 1dded21cb966..bce8fa65f1ec 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 114864 2026-08-06 10:23:36Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-x11.cpp 114867 2026-08-06 15:19:51Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -55,6 +55,220 @@ #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +static void vbclX11TransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer); + +/** Resets the transfer key bound to the current asynchronous preparation. */ +static void vbclX11TransferStateResetKey(PSHCLX11TRANSFERSTATE pX11TransferState) +{ + pX11TransferState->idTransfer = NIL_SHCLTRANSFERID; + pX11TransferState->uTransferGeneration = NIL_SHCLTRANSFERGEN; +} + +/** Resets the key of the transfer backing the advertised URI-list data. */ +static void vbclX11TransferPublishedResetKey(PSHCLX11TRANSFERSTATE pX11TransferState) +{ + pX11TransferState->idPublishedTransfer = NIL_SHCLTRANSFERID; + pX11TransferState->uPublishedTransferGeneration = NIL_SHCLTRANSFERGEN; +} + +/** + * Checks whether a transfer is the exact transfer bound to the current + * asynchronous preparation. + */ +static bool vbclX11TransferStateMatches(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + return pX11TransferState->fPreparing + && pX11TransferState->idTransfer != NIL_SHCLTRANSFERID + && pX11TransferState->uTransferGeneration != NIL_SHCLTRANSFERGEN + && pX11TransferState->idTransfer == ShClTransferGetID(pTransfer) + && pX11TransferState->uTransferGeneration == ShClTransferGetGeneration(pTransfer); +} + +/** Checks whether a transfer backs the currently advertised URI-list data. */ +static bool vbclX11TransferPublishedMatches(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + return pX11TransferState->idPublishedTransfer != NIL_SHCLTRANSFERID + && pX11TransferState->uPublishedTransferGeneration != NIL_SHCLTRANSFERGEN + && pX11TransferState->idPublishedTransfer == ShClTransferGetID(pTransfer) + && pX11TransferState->uPublishedTransferGeneration == ShClTransferGetGeneration(pTransfer); +} + +/** Cancels the transfer backing URI-list data superseded by a new clipboard offer. */ +static void vbclX11TransferPublishedCancel(PSHCLCONTEXT pCtx) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + if (pX11TransferState->idPublishedTransfer == NIL_SHCLTRANSFERID) + return; + + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(&pCtx->TransferCtx, + pX11TransferState->idPublishedTransfer); + if ( pTransfer + && vbclX11TransferPublishedMatches(pCtx, pTransfer)) + { + vbclX11TransferUnregister(pCtx, pTransfer); + if (ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_INITIALIZED) + { + int rc = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pTransfer, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Canceling superseded transfer %RU16/%RU64 failed with %Rrc\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer), rc)); + } + } + else + LogRel2(("Shared Clipboard: Published transfer %RU16/%RU64 was already gone or replaced\n", + pX11TransferState->idPublishedTransfer, pX11TransferState->uPublishedTransferGeneration)); + + vbclX11TransferPublishedResetKey(pX11TransferState); +} + +/** + * Starts preparing HTTP-backed URI-list data for the current host clipboard offer. + * + * The request is issued by the clipboard service worker. Its transfer is + * created and initialized later while that worker processes transfer-status + * messages, so the X11 event thread never waits for this operation. + */ +static int vbclX11TransferStateStart(PSHCLCONTEXT pCtx) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + AssertReturn(!pX11TransferState->fPreparing, VERR_WRONG_ORDER); + AssertReturn(pX11TransferState->fFormats & VBOX_SHCL_FMT_URI_LIST, VERR_INVALID_PARAMETER); + + pX11TransferState->fPreparing = true; + pX11TransferState->uPreparingOfferGeneration = pX11TransferState->uOfferGeneration; + vbclX11TransferStateResetKey(pX11TransferState); + + /* Preserve the existing protocol sequence: consume the URI-list clipboard + * data response before requesting the HTTP transfer. The actual X11 data + * will be the URI list of HTTP URLs produced after transfer initialization. */ + void *pvData = NULL; + uint32_t cbData = 0; + int rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, VBOX_SHCL_FMT_URI_LIST, &pvData, &cbData); + RTMemFree(pvData); + if (RT_SUCCESS(rc)) + rc = VbglR3ClipboardTransferRequest(&pCtx->CmdCtx); + + if (RT_FAILURE(rc)) + { + pX11TransferState->fPreparing = false; + vbclX11TransferStateResetKey(pX11TransferState); + LogRel(("Shared Clipboard: Starting asynchronous X11 transfer preparation failed with %Rrc\n", rc)); + } + else + LogRel2(("Shared Clipboard: Preparing X11 URI list for clipboard offer generation %RU64\n", + pX11TransferState->uPreparingOfferGeneration)); + + return rc; +} + +/** + * Completes asynchronous preparation for an exact transfer ID and generation. + * + * A result which no longer matches the current clipboard offer is discarded. + * If the host clipboard changed while a transfer was being initialized, a new + * serialized request is started for the latest offer after the old transfer + * has been canceled. + */ +static void vbclX11TransferStateComplete(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, + const char *pszUriList, size_t cbUriList, int rcPreparation) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + if (!vbclX11TransferStateMatches(pCtx, pTransfer)) + { + LogRel2(("Shared Clipboard: Ignoring URI-list preparation completion for unbound transfer %RU16/%RU64\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); + return; + } + + uint64_t const uPreparingOfferGeneration = pX11TransferState->uPreparingOfferGeneration; + bool const fCurrentOffer = uPreparingOfferGeneration == pX11TransferState->uOfferGeneration + && (pX11TransferState->fFormats & VBOX_SHCL_FMT_URI_LIST); + + pX11TransferState->fPreparing = false; + vbclX11TransferStateResetKey(pX11TransferState); + + bool fPublished = false; + if (RT_SUCCESS(rcPreparation) && fCurrentOffer) + { + if (!pszUriList || !cbUriList) + rcPreparation = VERR_SHCLPB_NO_DATA; + else if (cbUriList > UINT32_MAX) + rcPreparation = VERR_BUFFER_OVERFLOW; + else + { + rcPreparation = ShClX11ReportFormatsToX11AsyncEx(&pCtx->X11, pX11TransferState->fFormats, + VBOX_SHCL_FMT_URI_LIST, pszUriList, + (uint32_t)cbUriList); + if (RT_SUCCESS(rcPreparation)) + { + fPublished = true; + pX11TransferState->idPublishedTransfer = ShClTransferGetID(pTransfer); + pX11TransferState->uPublishedTransferGeneration = ShClTransferGetGeneration(pTransfer); + LogRel2(("Shared Clipboard: Advertised cached X11 URI list for transfer %RU16/%RU64, " + "clipboard offer generation %RU64\n", ShClTransferGetID(pTransfer), + ShClTransferGetGeneration(pTransfer), uPreparingOfferGeneration)); + } + } + } + + if (!fPublished) + { + if (RT_FAILURE(rcPreparation)) + LogRel(("Shared Clipboard: Preparing X11 URI list for transfer %RU16/%RU64 failed with %Rrc\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer), rcPreparation)); + else + LogRel2(("Shared Clipboard: Discarding stale X11 URI list for transfer %RU16/%RU64\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); + + vbclX11TransferUnregister(pCtx, pTransfer); + if (ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_INITIALIZED) + { + int rc2 = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pTransfer, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + if (RT_FAILURE(rc2)) + LogRel(("Shared Clipboard: Canceling unused transfer %RU16/%RU64 failed with %Rrc\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer), rc2)); + } + } + + if ( uPreparingOfferGeneration != pX11TransferState->uOfferGeneration + && (pX11TransferState->fFormats & VBOX_SHCL_FMT_URI_LIST)) + { + int rc2 = vbclX11TransferStateStart(pCtx); + if (RT_FAILURE(rc2)) + LogRel(("Shared Clipboard: Restarting X11 transfer preparation failed with %Rrc\n", rc2)); + } +} + +/** + * Handles a new host clipboard offer without exposing an unprepared URI target. + */ +static int vbclX11ReportHostFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + vbclX11TransferPublishedCancel(pCtx); + pX11TransferState->fFormats = fFormats; + pX11TransferState->uOfferGeneration++; + if (!pX11TransferState->uOfferGeneration) + pX11TransferState->uOfferGeneration = 1; + + /* Non-transfer formats can be advertised immediately. The URI-list bit is + * added by the completion callback together with its pre-seeded cache. */ + int rc = ShClX11ReportFormatsToX11Async(&pCtx->X11, fFormats & ~VBOX_SHCL_FMT_URI_LIST); + if ( (fFormats & VBOX_SHCL_FMT_URI_LIST) + && !pX11TransferState->fPreparing) + { + int rc2 = vbclX11TransferStateStart(pCtx); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialize * @@ -110,14 +324,50 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA break; } + if ( RT_FAILURE(rc) + && ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE + && vbclX11TransferStateMatches(pCtx, pTransfer)) + vbclX11TransferStateComplete(pCtx, pTransfer, NULL, 0, rc); + LogFlowFuncLeaveRC(rc); return rc; } +/** + * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialized + * + * Builds and publishes URI-list data only for the transfer ID and generation + * captured when the current asynchronous request was registered. + * + * @thread Clipboard main thread. + */ +static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; + AssertPtr(pCtx); + + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtr(pTransfer); + + if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE + && vbclX11TransferStateMatches(pCtx, pTransfer)) + { + char *pszUriList = NULL; + size_t cbUriList = 0; + int rc = ShClTransferHttpConvertToStringList(&pCtx->X11.HttpCtx.HttpServer, pTransfer, + &pszUriList, &cbUriList); + vbclX11TransferStateComplete(pCtx, pTransfer, pszUriList, cbUriList, rc); + RTStrFree(pszUriList); + } +} + /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnRegistered * - * This starts the HTTP server if not done yet and registers the transfer with it. + * This binds pending transfer preparation to the newly registered transfer's + * exact ID and generation, and starts the HTTP server if necessary. The + * transfer itself is added to the HTTP server after its roots have been read by + * the initialization callback. * * @thread Clipboard main thread. */ @@ -136,6 +386,25 @@ static DECLCALLBACK(void) vbclX11OnTransferRegisteredCallback(PSHCLTRANSFERCALLB /* We only need to start the HTTP server when we actually receive data from the remote (host). */ if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */ { + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + /* H->G requests are serialized by fPreparing. The first H->G + * registration is therefore the protocol response to the outstanding + * request. Capture its immutable key once; later registration + * callbacks must not redirect this preparation to another transfer. */ + if ( pX11TransferState->fPreparing + && pX11TransferState->idTransfer == NIL_SHCLTRANSFERID) + { + pX11TransferState->idTransfer = ShClTransferGetID(pTransfer); + pX11TransferState->uTransferGeneration = ShClTransferGetGeneration(pTransfer); + LogRel2(("Shared Clipboard: Bound X11 transfer preparation to transfer %RU16/%RU64\n", + pX11TransferState->idTransfer, pX11TransferState->uTransferGeneration)); + } + else if (pX11TransferState->fPreparing) + LogRel2(("Shared Clipboard: Keeping X11 transfer preparation bound to transfer %RU16/%RU64; " + "ignoring registration of %RU16/%RU64\n", pX11TransferState->idTransfer, + pX11TransferState->uTransferGeneration, ShClTransferGetID(pTransfer), + ShClTransferGetGeneration(pTransfer))); + int rc2 = ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx); if (RT_FAILURE(rc2)) LogRel(("Shared Clipboard: Registering HTTP transfer failed: %Rrc\n", rc2)); @@ -176,7 +445,13 @@ static void vbclX11TransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer static DECLCALLBACK(void) vbclX11OnTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx) { RT_NOREF(pTransferCtx); - vbclX11TransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer); + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; + if (vbclX11TransferStateMatches(pCtx, pCbCtx->pTransfer)) + vbclX11TransferStateComplete(pCtx, pCbCtx->pTransfer, NULL, 0, VERR_CANCELLED); + if (vbclX11TransferPublishedMatches(pCtx, pCbCtx->pTransfer)) + vbclX11TransferPublishedResetKey(&pCtx->X11TransferState); + vbclX11TransferUnregister(pCtx, pCbCtx->pTransfer); } /** @@ -188,8 +463,13 @@ static DECLCALLBACK(void) vbclX11OnTransferUnregisteredCallback(PSHCLTRANSFERCAL */ static DECLCALLBACK(void) vbclX11OnTransferCompletedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rc) { - RT_NOREF(rc); - vbclX11TransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer); + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; + if (vbclX11TransferStateMatches(pCtx, pCbCtx->pTransfer)) + vbclX11TransferStateComplete(pCtx, pCbCtx->pTransfer, NULL, 0, + RT_SUCCESS(rc) ? VERR_SHCLPB_NO_DATA : rc); + if (vbclX11TransferPublishedMatches(pCtx, pCbCtx->pTransfer)) + vbclX11TransferPublishedResetKey(&pCtx->X11TransferState); + vbclX11TransferUnregister(pCtx, pCbCtx->pTransfer); } /** @copydoc SHCLTRANSFERCALLBACKS::pfnOnError @@ -244,48 +524,12 @@ static DECLCALLBACK(int) vbclX11OnRequestDataFromSourceCallback(PSHCLCONTEXT pCt #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP if (uFmt == VBOX_SHCL_FMT_URI_LIST) { - rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser); - if (RT_SUCCESS(rc)) - { - /* Request a new H->G transfer from the host. - * This is needed in order to get a transfer ID from the host we can initialize our own local transfer with. - * Transfer creation and set up will be done in VbglR3. */ - rc = VbglR3ClipboardTransferRequest(&pCtx->CmdCtx); - if (RT_SUCCESS(rc)) - { - PSHCLHTTPSERVER pSrv = &pCtx->X11.HttpCtx.HttpServer; - - /* Wait until the HTTP server got the transfer registered, so that we have something to work with. */ - rc = ShClTransferHttpServerWaitForStatusChange(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED, SHCL_TIMEOUT_DEFAULT_MS); - if (RT_SUCCESS(rc)) - { - PSHCLTRANSFER pTransfer = ShClTransferHttpServerGetTransferLast(pSrv); - if (pTransfer) - { - rc = ShClTransferWaitForStatus(pTransfer, SHCL_TIMEOUT_DEFAULT_MS, SHCLTRANSFERSTATUS_INITIALIZED); - if (RT_SUCCESS(rc)) - { - char *pszData; - size_t cbData; - rc = ShClTransferHttpConvertToStringList(pSrv, pTransfer, &pszData, &cbData); - if (RT_SUCCESS(rc)) - { - *ppv = pszData; - *pcb = cbData; - /* ppv has ownership of pszData now. */ - } - } - } - else - { - AssertMsgFailed(("No registered transfer found for HTTP server\n")); - rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; - } - } - else - LogRel(("Shared Clipboard: Could not start transfer, as no new HTTP transfer was registered in time\n")); - } - } + /* URI targets are marked cache-only when advertised, so the common + * X11 code normally handles a miss without invoking this callback. + * Refuse it here as well: never start or wait for a transfer from the + * X11 event thread. */ + LogRel2(("Shared Clipboard: X11 URI-list conversion missed its prepared URI-list data cache\n")); + rc = VERR_SHCLPB_NO_DATA; } else /* Anything else */ #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ @@ -336,6 +580,12 @@ int VBClX11ClipboardInit(void) { LogFlowFuncEnter(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + RT_ZERO(g_Ctx.X11TransferState); + vbclX11TransferStateResetKey(&g_Ctx.X11TransferState); + vbclX11TransferPublishedResetKey(&g_Ctx.X11TransferState); +#endif + int rc = ShClEventSourceInit(&g_Ctx.EventSrc, 0 /* uID */); AssertRCReturn(rc, rc); @@ -405,6 +655,7 @@ int VBClX11ClipboardMain(void) pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialize = vbclX11OnTransferInitializeCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbclX11OnTransferInitializedCallback; pCtx->CmdCtx.Transfers.Callbacks.pfnOnRegistered = vbclX11OnTransferRegisteredCallback; pCtx->CmdCtx.Transfers.Callbacks.pfnOnUnregistered = vbclX11OnTransferUnregisteredCallback; pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbclX11OnTransferCompletedCallback; @@ -458,7 +709,11 @@ int VBClX11ClipboardMain(void) { case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS: { - ShClX11ReportFormatsToX11Async(&g_Ctx.X11, pEvent->u.fReportedFormats); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + rc = vbclX11ReportHostFormats(pCtx, pEvent->u.fReportedFormats); +#else + rc = ShClX11ReportFormatsToX11Async(&g_Ctx.X11, pEvent->u.fReportedFormats); +#endif break; } diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.h b/src/VBox/Additions/x11/VBoxClient/clipboard.h index 39c79769aca7..1d94f19de1f2 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard.h +++ b/src/VBox/Additions/x11/VBoxClient/clipboard.h @@ -1,4 +1,4 @@ -/* $Id: clipboard.h 114748 2026-07-21 20:16:49Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard.h 114867 2026-08-06 15:19:51Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard - Main header. */ @@ -72,6 +72,10 @@ struct SHCLCONTEXT #endif /** Event source for waiting for X11 request responses in the VbglR3 clipboard event loop. */ SHCLEVENTSOURCE EventSrc; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + /** Guest-side asynchronous X11 HTTP file-transfer state. */ + SHCLX11TRANSFERSTATE X11TransferState; +#endif union { /** X11 clipboard context. */ diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp index 65d9d10ee2e7..0c0cd3f6bff1 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -1646,13 +1646,18 @@ static int shClX11RequestDataForX11CallbackHelper(PSHCLX11CTX pCtx, SHCLFORMAT u PSHCLCACHEENTRY pCacheEntry = ShClCacheGet(&pCtx->Cache, uFmt); if (!pCacheEntry) /* Cache miss */ { - AssertPtrReturn(pCtx->Callbacks.pfnOnRequestDataFromSource, VERR_INVALID_POINTER); - rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pv, &cb, - NULL /* pvUser */); - if (RT_SUCCESS(rc)) + if (pCtx->fCacheOnlyFormats & uFmt) + rc = VERR_SHCLPB_NO_DATA; + else { - rc = ShClCacheSet(&pCtx->Cache, uFmt, pv, cb); - /** @todo r=bird: Leaks pv/cb on ShClCacheSet error? */ + AssertPtrReturn(pCtx->Callbacks.pfnOnRequestDataFromSource, VERR_INVALID_POINTER); + rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pv, &cb, + NULL /* pvUser */); + if (RT_SUCCESS(rc)) + { + rc = ShClCacheSet(&pCtx->Cache, uFmt, pv, cb); + /** @todo r=bird: Leaks pv/cb on ShClCacheSet error? */ + } } } else /* Cache hit */ @@ -2114,10 +2119,11 @@ static void shClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pvUserData; AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_REPORT_FORMATS); - PSHCLX11CTX pCtx = pReq->pCtx; - SHCLFORMATS fFormats = pReq->Formats.fFormats; - - RTMemFree(pReq); + PSHCLX11CTX pCtx = pReq->pCtx; + SHCLFORMATS fFormats = pReq->Formats.fFormats; + SHCLFORMAT uFmtCache = pReq->Formats.uFmtCache; + void *pvCache = pReq->Formats.pvCache; + uint32_t cbCache = pReq->Formats.cbCache; if (LogRelIs2Enabled()) { @@ -2128,6 +2134,23 @@ static void shClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval } clipInvalidateClipboardCache(pCtx); + pCtx->fCacheOnlyFormats = uFmtCache; + + if (uFmtCache != VBOX_SHCL_FMT_NONE) + { + int rc = ShClCacheSet(&pCtx->Cache, uFmtCache, pvCache, cbCache); + if (RT_FAILURE(rc)) + { + LogRel(("Shared Clipboard: Caching format %#x before advertising it to X11 failed with %Rrc; " + "suppressing the format\n", uFmtCache, rc)); + fFormats &= ~uFmtCache; + } + } + + RTMemFree(pvCache); + pReq->Formats.pvCache = NULL; + RTMemFree(pReq); + clipGrabX11Clipboard(pCtx, fFormats); clipResetX11Formats(pCtx); @@ -2139,13 +2162,48 @@ static void shClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval * * @returns VBox status code. * @param pCtx Context data for the clipboard backend. - * @param uFormats Clipboard formats offered. + * @param fFormats Clipboard formats offered. * * @note When calling this function, data for the clipboard already has to be available, * as we grab the clipboard, which in turn then calls the X11 data conversion callback. */ -int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS uFormats) +int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS fFormats) { + return ShClX11ReportFormatsToX11AsyncEx(pCtx, fFormats, VBOX_SHCL_FMT_NONE, NULL, 0); +} + +/** + * Announces new clipboard formats and atomically seeds one cache entry on the + * X11 event thread before taking ownership of the selections. + * + * This is used for data which must be prepared asynchronously. Once the + * formats become visible to X11 clients, conversion of @a uFmtCache is + * guaranteed to be a cache hit and cannot block on the data source. + * + * @returns VBox status code. + * @param pCtx Context data for the clipboard backend. + * @param fFormats Clipboard formats offered. + * @param uFmtCache Format of @a pvCache, or VBOX_SHCL_FMT_NONE. + * @param pvCache Data to cache before advertising @a fFormats. + * @param cbCache Size of @a pvCache in bytes. + * + * @thread Any thread. Cache installation and selection ownership happen on + * the X11 event thread. + */ +int ShClX11ReportFormatsToX11AsyncEx(PSHCLX11CTX pCtx, SHCLFORMATS fFormats, SHCLFORMAT uFmtCache, + const void *pvCache, uint32_t cbCache) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + if (uFmtCache != VBOX_SHCL_FMT_NONE) + { + AssertReturn(ShClFormatIsValid(uFmtCache), VERR_INVALID_PARAMETER); + AssertReturn((fFormats & uFmtCache) == uFmtCache, VERR_INVALID_PARAMETER); + AssertPtrReturn(pvCache, VERR_INVALID_POINTER); + AssertReturn(cbCache, VERR_INVALID_PARAMETER); + } + else + AssertReturn(!pvCache && !cbCache, VERR_INVALID_PARAMETER); + if (shClX11HeadlessIsEnabled(pCtx)) return VINF_SUCCESS; @@ -2154,13 +2212,28 @@ int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS uFormats) PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)RTMemAllocZ(sizeof(SHCLX11REQUEST)); if (pReq) { - pReq->enmType = SHCLX11EVENTTYPE_REPORT_FORMATS; - pReq->pCtx = pCtx; - pReq->Formats.fFormats = uFormats; + pReq->enmType = SHCLX11EVENTTYPE_REPORT_FORMATS; + pReq->pCtx = pCtx; + pReq->Formats.fFormats = fFormats; + pReq->Formats.uFmtCache = uFmtCache; + + if (uFmtCache != VBOX_SHCL_FMT_NONE) + { + pReq->Formats.pvCache = RTMemDup(pvCache, cbCache); + if (!pReq->Formats.pvCache) + { + RTMemFree(pReq); + return VERR_NO_MEMORY; + } + pReq->Formats.cbCache = cbCache; + } rc = clipThreadScheduleCall(pCtx, shClX11ReportFormatsToX11Worker, (XtPointer)pReq); if (RT_FAILURE(rc)) + { + RTMemFree(pReq->Formats.pvCache); RTMemFree(pReq); + } } else rc = VERR_NO_MEMORY; diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp index 0dc05c161fad..6bccf94ad528 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardGH-X11.cpp 114866 2026-08-06 11:24:19Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardGH-X11.cpp 114867 2026-08-06 15:19:51Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard guest/host X11 code test cases. */ @@ -97,6 +97,7 @@ void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data) static int g_tst_rcDataVBox = VINF_SUCCESS; static void *g_tst_pvDataVBox = NULL; static uint32_t g_tst_cbDataVBox = 0; +static uint32_t g_tst_cDataRequests = 0; static SHCLEVENTSOURCE g_EventSource; /* Set empty data in the simulated VBox clipboard. */ @@ -401,6 +402,7 @@ static DECLCALLBACK(int) tstShClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_ static DECLCALLBACK(int) tstShClOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser) { RT_NOREF(pCtx, uFmt, pvUser); + g_tst_cDataRequests++; *pcb = g_tst_cbDataVBox; if (g_tst_pvDataVBox != NULL) { @@ -909,6 +911,20 @@ int main() #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS RTTEST_CHECK_MSG(hTest, tstClipURIListFormatConversion(&X11Ctx), (hTest, "failed to select the right X11 URI-list formats\n")); + + RTTestSub(hTest, "cache-only X11 URI-list offer"); + static const char s_szUriList[] = "http://localhost/a\r\nhttp://localhost/b\r\n"; + uint32_t const cDataRequestsBefore = g_tst_cDataRequests; + RTTEST_CHECK_RC_OK(hTest, ShClX11ReportFormatsToX11AsyncEx(&X11Ctx, VBOX_SHCL_FMT_URI_LIST, + VBOX_SHCL_FMT_URI_LIST, s_szUriList, + sizeof(s_szUriList))); + tstStringFromVBox(hTest, &X11Ctx, "text/uri-list", clipGetAtom(&X11Ctx, "text/uri-list"), s_szUriList); + RTTEST_CHECK_MSG(hTest, g_tst_cDataRequests == cDataRequestsBefore, + (hTest, "Cached URI-list conversion unexpectedly requested source data\n")); + ShClCacheInvalidate(&X11Ctx.Cache); + tstStringFromVBoxFailed(hTest, &X11Ctx, "text/uri-list"); + RTTEST_CHECK_MSG(hTest, g_tst_cDataRequests == cDataRequestsBefore, + (hTest, "Cache-only URI-list miss unexpectedly requested source data\n")); #endif /* * UTF-8 from VBox diff --git a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp index bb37378abdf9..2ad32d642030 100644 --- a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp +++ b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendX11.cpp 114661 2026-07-08 10:39:13Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendX11.cpp 114867 2026-08-06 15:19:51Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - X11 backend. */ @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,14 @@ struct SHCLCONTEXT /** We set this when we start shutting down as a hint not to post any new * requests. */ bool fShuttingDown; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + /** Host-side asynchronous X11 HTTP file-transfer state. */ + SHCLX11TRANSFERSTATE X11TransferState; + /** Event notifying the preparation worker about a new clipboard offer. */ + RTSEMEVENT hX11TransferPreparationEvent; + /** Persistent worker which prepares guest file-transfer URI-list data. */ + RTTHREAD hX11TransferPreparationThread; +#endif }; @@ -97,9 +106,163 @@ static DECLCALLBACK(void) shClSvcX11TransferOnDestroyCallback(PSHCLTRANSFERCALLB static DECLCALLBACK(void) shClSvcX11TransferOnUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx); static DECLCALLBACK(int) shClSvcX11TransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pCtx); +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +static DECLCALLBACK(int) shClSvcX11TransferPreparationThread(RTTHREAD hThreadSelf, void *pvUser); +# endif #endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +/** Resets the exact transfer key bound to an in-progress preparation. */ +static void shClSvcX11TransferPreparationResetKey(PSHCLX11TRANSFERSTATE pX11TransferState) +{ + pX11TransferState->idTransfer = NIL_SHCLTRANSFERID; + pX11TransferState->uTransferGeneration = NIL_SHCLTRANSFERGEN; +} + +/** Resets the exact transfer key backing the advertised URI-list data. */ +static void shClSvcX11TransferPublishedResetKey(PSHCLX11TRANSFERSTATE pX11TransferState) +{ + pX11TransferState->idPublishedTransfer = NIL_SHCLTRANSFERID; + pX11TransferState->uPublishedTransferGeneration = NIL_SHCLTRANSFERGEN; +} + +/** Checks an exact transfer ID and generation against a transfer. */ +static bool shClSvcX11TransferKeyMatches(SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration, + PSHCLTRANSFER pTransfer) +{ + return idTransfer != NIL_SHCLTRANSFERID + && uGeneration != NIL_SHCLTRANSFERGEN + && idTransfer == ShClTransferGetID(pTransfer) + && uGeneration == ShClTransferGetGeneration(pTransfer); +} + +/** Checks whether a worker result still belongs to the current URI offer. */ +static bool shClSvcX11TransferOfferIsCurrent(PSHCLCONTEXT pCtx, uint64_t uOfferGeneration) +{ + int vrc = RTCritSectEnter(&pCtx->CritSect); + AssertRCReturn(vrc, false); + + bool const fCurrent = !pCtx->fShuttingDown + && pCtx->X11TransferState.uOfferGeneration == uOfferGeneration + && (pCtx->X11TransferState.fFormats & VBOX_SHCL_FMT_URI_LIST); + + vrc = RTCritSectLeave(&pCtx->CritSect); + AssertRC(vrc); + return fCurrent; +} + +/** + * Discards the unused transfer backing a URI-list offer hidden by a newer offer. + * + * A transfer which has already started serving an HTTP request must remain + * alive until that request finishes. Otherwise it is safe to destroy the + * transfer after the URI targets have been removed from X11. + * + * @thread X11 transfer preparation worker. + */ +static void shClSvcX11TransferPublishedCancel(PSHCLCONTEXT pCtx) +{ + SHCLTRANSFERID idTransfer; + SHCLTRANSFERGEN uGeneration; + + int vrc = RTCritSectEnter(&pCtx->CritSect); + AssertRCReturnVoid(vrc); + + idTransfer = pCtx->X11TransferState.idPublishedTransfer; + uGeneration = pCtx->X11TransferState.uPublishedTransferGeneration; + shClSvcX11TransferPublishedResetKey(&pCtx->X11TransferState); + + vrc = RTCritSectLeave(&pCtx->CritSect); + AssertRC(vrc); + + if (idTransfer == NIL_SHCLTRANSFERID) + return; + + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(&pCtx->pClient->Transfers.Ctx, idTransfer); + if ( pTransfer + && shClSvcX11TransferKeyMatches(idTransfer, uGeneration, pTransfer)) + { + SHCLTRANSFERSTATUS const enmStatus = ShClTransferGetStatus(pTransfer); + if (enmStatus != SHCLTRANSFERSTATUS_STARTED) + ShClSvcTransferDestroy(pCtx->pClient, pTransfer); + else + LogRel2(("Shared Clipboard: Keeping superseded X11 transfer %RU16/%RU64 alive while it is in use\n", + idTransfer, uGeneration)); + } + else + LogRel2(("Shared Clipboard: Published X11 transfer %RU16/%RU64 was already gone or replaced\n", + idTransfer, uGeneration)); +} + +/** Starts the persistent host-side X11 transfer preparation worker. */ +static int shClSvcX11TransferPreparationStart(PSHCLCONTEXT pCtx) +{ + RT_ZERO(pCtx->X11TransferState); + shClSvcX11TransferPreparationResetKey(&pCtx->X11TransferState); + shClSvcX11TransferPublishedResetKey(&pCtx->X11TransferState); + pCtx->hX11TransferPreparationEvent = NIL_RTSEMEVENT; + pCtx->hX11TransferPreparationThread = NIL_RTTHREAD; + + int vrc = RTSemEventCreate(&pCtx->hX11TransferPreparationEvent); + if (RT_SUCCESS(vrc)) + { + vrc = RTThreadCreate(&pCtx->hX11TransferPreparationThread, shClSvcX11TransferPreparationThread, + pCtx, 0 /* cbStack */, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ShClX11Tx"); + if (RT_SUCCESS(vrc)) + { + vrc = RTThreadUserWait(pCtx->hX11TransferPreparationThread, RT_MS_30SEC); + if (RT_SUCCESS(vrc)) + return VINF_SUCCESS; + + pCtx->fShuttingDown = true; + RTSemEventSignal(pCtx->hX11TransferPreparationEvent); + RTThreadWait(pCtx->hX11TransferPreparationThread, RT_INDEFINITE_WAIT, NULL); + pCtx->hX11TransferPreparationThread = NIL_RTTHREAD; + } + + RTSemEventDestroy(pCtx->hX11TransferPreparationEvent); + pCtx->hX11TransferPreparationEvent = NIL_RTSEMEVENT; + } + + return vrc; +} + +/** Stops the transfer preparation worker before its X11 and client contexts are released. */ +static int shClSvcX11TransferPreparationStop(PSHCLCONTEXT pCtx) +{ + if (pCtx->hX11TransferPreparationThread == NIL_RTTHREAD) + { + pCtx->fShuttingDown = true; + return VINF_SUCCESS; + } + + int vrc = RTCritSectEnter(&pCtx->CritSect); + AssertRCReturn(vrc, vrc); + pCtx->fShuttingDown = true; + pCtx->X11TransferState.uOfferGeneration++; + vrc = RTCritSectLeave(&pCtx->CritSect); + AssertRCReturn(vrc, vrc); + + int vrc2 = RTSemEventSignal(pCtx->hX11TransferPreparationEvent); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + vrc2 = RTThreadWait(pCtx->hX11TransferPreparationThread, RT_INDEFINITE_WAIT, NULL); + if (RT_FAILURE(vrc2)) + return vrc2; + pCtx->hX11TransferPreparationThread = NIL_RTTHREAD; + + vrc2 = RTSemEventDestroy(pCtx->hX11TransferPreparationEvent); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + pCtx->hX11TransferPreparationEvent = NIL_RTSEMEVENT; + + return vrc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + + /********************************************************************************************************************************* * Backend implementation * *********************************************************************************************************************************/ @@ -191,9 +354,19 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadles pClient->Transfers.Callbacks.pfnOnUnregistered = shClSvcX11TransferOnUnregisteredCallback; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - vrc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + if (!fHeadless) + vrc = shClSvcX11TransferPreparationStart(pCtx); +#endif + if (RT_SUCCESS(vrc)) + vrc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */); if (RT_FAILURE(vrc)) + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + shClSvcX11TransferPreparationStop(pCtx); +#endif ShClX11Term(&pCtx->X11); + } } if (RT_FAILURE(vrc)) @@ -254,17 +427,35 @@ int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) PSHCLCONTEXT pCtx = pClient->State.pCtx; AssertPtr(pCtx); - /* Drop the reference to the client, in case it is still there. This - * will cause any outstanding clipboard data requests from X11 to fail - * immediately. */ + /* Stop transfer preparation before releasing either the client or X11 + * context it uses. This also makes later X11 data requests fail. */ + int vrc; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + vrc = shClSvcX11TransferPreparationStop(pCtx); + if (pCtx->hX11TransferPreparationThread != NIL_RTTHREAD) + { + LogRel(("Shared Clipboard: Host X11 transfer preparation worker did not terminate: %Rrc\n", vrc)); + return vrc; + } +#else pCtx->fShuttingDown = true; + vrc = VINF_SUCCESS; +#endif - int vrc = ShClX11ThreadStop(&pCtx->X11); + int vrc2 = ShClX11ThreadStop(&pCtx->X11); + if (RT_SUCCESS(vrc)) + vrc = vrc2; /** @todo handle this slightly more reasonably, or be really sure * it won't go wrong. */ - AssertRC(vrc); + AssertRC(vrc2); ShClX11Term(&pCtx->X11); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* Transfer callback tables retain pCtx as their user argument. Destroy + * all transfers before deleting that context; the service-side client + * teardown which follows treats an already empty context as a no-op. */ + shClSvcTransferDestroyAll(pClient); +#endif RTCritSectDelete(&pCtx->CritSect); RTMemFree(pCtx); @@ -292,7 +483,42 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR } #endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + PSHCLCONTEXT pCtx = pClient->State.pCtx; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + if (pCtx->X11.fHeadless) + return ShClX11ReportFormatsToX11Async(&pCtx->X11, fFormats); + + int vrc = RTCritSectEnter(&pCtx->CritSect); + if (RT_SUCCESS(vrc)) + { + if (!pCtx->fShuttingDown) + { + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + pX11TransferState->fFormats = fFormats; + pX11TransferState->uOfferGeneration++; + if (!pX11TransferState->uOfferGeneration) + pX11TransferState->uOfferGeneration = 1; + + /* Remove an old URI target immediately. The worker adds it back + * only after the new URI-list data has been prepared and cached. */ + vrc = ShClX11ReportFormatsToX11Async(&pCtx->X11, fFormats & ~VBOX_SHCL_FMT_URI_LIST); + + int vrc2 = RTSemEventSignal(pCtx->hX11TransferPreparationEvent); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } + else + vrc = VERR_WRONG_ORDER; + + int const vrc2 = RTCritSectLeave(&pCtx->CritSect); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } +#else int vrc = ShClX11ReportFormatsToX11Async(&pClient->State.pCtx->X11, fFormats); +#endif LogFlowFuncLeaveRC(vrc); return vrc; @@ -422,11 +648,222 @@ static DECLCALLBACK(int) shClSvcX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint return vrc; } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +/** + * Prepares and publishes one exact guest-to-host HTTP-backed URI-list offer. + * + * All guest communication and transfer waits happen on the preparation + * worker. The X11 event thread only observes the final cache-seeding format + * report and therefore never waits on the guest. + * + * @thread X11 transfer preparation worker. + */ +static int shClSvcX11TransferPrepare(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats, uint64_t uOfferGeneration) +{ + AssertReturn(fFormats & VBOX_SHCL_FMT_URI_LIST, VERR_INVALID_PARAMETER); + + PSHCLCLIENT const pClient = pCtx->pClient; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + /* Preserve the established protocol sequence by consuming the URI-list + * data reply before creating and initializing the file transfer. */ + void *pvData = NULL; + uint32_t cbData = 0; + int vrc = ShClSvcReadDataFromGuest(pClient, VBOX_SHCL_FMT_URI_LIST, &pvData, &cbData); + RTMemFree(pvData); + if ( RT_SUCCESS(vrc) + && !shClSvcX11TransferOfferIsCurrent(pCtx, uOfferGeneration)) + vrc = VERR_CANCELLED; + + PSHCLTRANSFER pTransfer = NULL; + if (RT_SUCCESS(vrc)) + vrc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, + NIL_SHCLTRANSFERID, &pTransfer); + + SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; + SHCLTRANSFERGEN uGeneration = NIL_SHCLTRANSFERGEN; + if (RT_SUCCESS(vrc)) + { + idTransfer = ShClTransferGetID(pTransfer); + uGeneration = ShClTransferGetGeneration(pTransfer); + + int vrc2 = RTCritSectEnter(&pCtx->CritSect); + if (RT_SUCCESS(vrc2)) + { + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + if ( pX11TransferState->fPreparing + && pX11TransferState->uPreparingOfferGeneration == uOfferGeneration) + { + pX11TransferState->idTransfer = idTransfer; + pX11TransferState->uTransferGeneration = uGeneration; + } + vrc2 = RTCritSectLeave(&pCtx->CritSect); + } + if (RT_FAILURE(vrc2)) + vrc = vrc2; + } + + if (RT_SUCCESS(vrc)) + vrc = ShClSvcTransferInit(pClient, pTransfer); + if (RT_SUCCESS(vrc)) + { + /* Wait on this transfer object, never on global HTTP-server state. */ + vrc = ShClTransferWaitForStatus(pTransfer, SHCL_TIMEOUT_DEFAULT_MS, + SHCLTRANSFERSTATUS_INITIALIZED); + } + if ( RT_SUCCESS(vrc) + && !shClSvcX11TransferOfferIsCurrent(pCtx, uOfferGeneration)) + vrc = VERR_CANCELLED; + if (RT_SUCCESS(vrc)) + vrc = ShClTransferRootListRead(pTransfer); + if ( RT_SUCCESS(vrc) + && !ShClTransferRootsCount(pTransfer)) + vrc = VERR_SHCLPB_NO_DATA; + if (RT_SUCCESS(vrc)) + vrc = ShClTransferHttpServerRegisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer); + + char *pszUriList = NULL; + size_t cbUriList = 0; + if (RT_SUCCESS(vrc)) + vrc = ShClTransferHttpConvertToStringList(&pCtx->X11.HttpCtx.HttpServer, pTransfer, + &pszUriList, &cbUriList); + if (RT_SUCCESS(vrc)) + { + if (!pszUriList || !cbUriList) + vrc = VERR_SHCLPB_NO_DATA; + else if (cbUriList > UINT32_MAX) + vrc = VERR_BUFFER_OVERFLOW; + } + + bool fPublished = false; + int vrc2 = RTCritSectEnter(&pCtx->CritSect); + if (RT_SUCCESS(vrc2)) + { + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + bool const fBoundTransfer = pTransfer + && pX11TransferState->fPreparing + && pX11TransferState->uPreparingOfferGeneration == uOfferGeneration + && shClSvcX11TransferKeyMatches(pX11TransferState->idTransfer, + pX11TransferState->uTransferGeneration, + pTransfer); + bool const fCurrentOffer = !pCtx->fShuttingDown + && pX11TransferState->uOfferGeneration == uOfferGeneration + && (pX11TransferState->fFormats & VBOX_SHCL_FMT_URI_LIST); + + if ( RT_SUCCESS(vrc) + && fBoundTransfer + && fCurrentOffer) + { + vrc = ShClX11ReportFormatsToX11AsyncEx(&pCtx->X11, pX11TransferState->fFormats, + VBOX_SHCL_FMT_URI_LIST, pszUriList, + (uint32_t)cbUriList); + if (RT_SUCCESS(vrc)) + { + pX11TransferState->idPublishedTransfer = idTransfer; + pX11TransferState->uPublishedTransferGeneration = uGeneration; + fPublished = true; + } + } + else if (RT_SUCCESS(vrc)) + vrc = VERR_CANCELLED; + + pX11TransferState->fPreparing = false; + shClSvcX11TransferPreparationResetKey(pX11TransferState); + + vrc2 = RTCritSectLeave(&pCtx->CritSect); + } + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + RTStrFree(pszUriList); + + if (!fPublished && pTransfer) + ShClSvcTransferDestroy(pClient, pTransfer); + + if (fPublished) + LogRel2(("Shared Clipboard: Advertised cached host X11 URI list for transfer %RU16/%RU64, offer generation %RU64\n", + idTransfer, uGeneration, uOfferGeneration)); + else if (vrc != VERR_CANCELLED) + LogRel(("Shared Clipboard: Preparing host X11 URI list for offer generation %RU64 failed with %Rrc\n", + uOfferGeneration, vrc)); + + return vrc; +} + +/** + * Persistent worker which serializes transfer preparation by clipboard offer + * generation. Multiple reports are coalesced and stale results are discarded. + */ +static DECLCALLBACK(int) shClSvcX11TransferPreparationThread(RTTHREAD hThreadSelf, void *pvUser) +{ + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + int vrc = RTThreadUserSignal(hThreadSelf); + AssertRCReturn(vrc, vrc); + + uint64_t uProcessedOfferGeneration = 0; + for (;;) + { + vrc = RTSemEventWait(pCtx->hX11TransferPreparationEvent, RT_INDEFINITE_WAIT); + if (RT_FAILURE(vrc)) + break; + + for (;;) + { + SHCLFORMATS fFormats; + uint64_t uOfferGeneration; + + vrc = RTCritSectEnter(&pCtx->CritSect); + if (RT_FAILURE(vrc)) + break; + + if (pCtx->fShuttingDown) + { + RTCritSectLeave(&pCtx->CritSect); + shClSvcX11TransferPublishedCancel(pCtx); + return VINF_SUCCESS; + } + + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + uOfferGeneration = pX11TransferState->uOfferGeneration; + if (uOfferGeneration == uProcessedOfferGeneration) + { + RTCritSectLeave(&pCtx->CritSect); + break; + } + + uProcessedOfferGeneration = uOfferGeneration; + fFormats = pX11TransferState->fFormats; + pX11TransferState->fPreparing = RT_BOOL(fFormats & VBOX_SHCL_FMT_URI_LIST); + pX11TransferState->uPreparingOfferGeneration = uOfferGeneration; + shClSvcX11TransferPreparationResetKey(pX11TransferState); + + vrc = RTCritSectLeave(&pCtx->CritSect); + if (RT_FAILURE(vrc)) + break; + + shClSvcX11TransferPublishedCancel(pCtx); + + if (fFormats & VBOX_SHCL_FMT_URI_LIST) + shClSvcX11TransferPrepare(pCtx, fFormats, uOfferGeneration); + } + + if (RT_FAILURE(vrc)) + break; + } + + shClSvcX11TransferPublishedCancel(pCtx); + LogRel(("Shared Clipboard: Host X11 transfer preparation worker failed with %Rrc\n", vrc)); + return vrc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnCreated * - * @thread Service main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static DECLCALLBACK(void) shClSvcX11TransferOnCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { @@ -493,7 +930,7 @@ static DECLCALLBACK(void) shClSvcX11TransferOnCreatedCallback(PSHCLTRANSFERCALLB * For G->H: Starts the HTTP server if not done yet and registers the transfer with it. * For H->G: Called on transfer intialization to populate the transfer's root list. * - * @thread Service main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static DECLCALLBACK(int) shClSvcX11TransferOnInitCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { @@ -540,7 +977,7 @@ static DECLCALLBACK(int) shClSvcX11TransferOnInitCallback(PSHCLTRANSFERCALLBACKC * * This stops the HTTP server if not done yet. * - * @thread Service main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static DECLCALLBACK(void) shClSvcX11TransferOnDestroyCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { @@ -570,7 +1007,7 @@ static DECLCALLBACK(void) shClSvcX11TransferOnDestroyCallback(PSHCLTRANSFERCALLB * @param pCtx Shared clipboard context to unregister transfer for. * @param pTransfer Transfer to unregister. * - * @thread Clipboard main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static void shClSvcX11HttpTransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer) { @@ -595,12 +1032,30 @@ static void shClSvcX11HttpTransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pT * * Unregisters a (now) unregistered transfer from the HTTP server. * - * @thread Clipboard main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static DECLCALLBACK(void) shClSvcX11TransferOnUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx) { RT_NOREF(pTransferCtx); - shClSvcX11HttpTransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer); + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + int vrc = RTCritSectEnter(&pCtx->CritSect); + if (RT_SUCCESS(vrc)) + { + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + if (shClSvcX11TransferKeyMatches(pX11TransferState->idTransfer, + pX11TransferState->uTransferGeneration, pCbCtx->pTransfer)) + shClSvcX11TransferPreparationResetKey(pX11TransferState); + if (shClSvcX11TransferKeyMatches(pX11TransferState->idPublishedTransfer, + pX11TransferState->uPublishedTransferGeneration, pCbCtx->pTransfer)) + shClSvcX11TransferPublishedResetKey(pX11TransferState); + + vrc = RTCritSectLeave(&pCtx->CritSect); + AssertRC(vrc); + } +# endif + shClSvcX11HttpTransferUnregister(pCtx, pCbCtx->pTransfer); } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -628,83 +1083,21 @@ static DECLCALLBACK(int) shClSvcX11RequestDataFromSourceCallback(PSHCLCONTEXT pC return VERR_WRONG_ORDER; } -#if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP if (uFmt == VBOX_SHCL_FMT_URI_LIST) { - *ppv = NULL; - *pcb = 0; - return VERR_NOT_SUPPORTED; + /* URI targets are advertised cache-only after the worker prepared the + * exact transfer. Never fall back to guest I/O on the X11 thread. */ + LogRel2(("Shared Clipboard: Host X11 URI-list conversion missed its prepared URI-list data cache\n")); + return VERR_SHCLPB_NO_DATA; } +#elif defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) + if (uFmt == VBOX_SHCL_FMT_URI_LIST) + return VERR_NOT_SUPPORTED; #endif PSHCLCLIENT const pClient = pCtx->pClient; int vrc = ShClSvcReadDataFromGuest(pClient, uFmt, ppv, pcb); - if (RT_FAILURE(vrc)) - return vrc; - - /* - * Note: We always return a generic URI list (as HTTP links) here. - * As we don't know which Atom target format was requested by the caller, the X11 clipboard codes needs - * to decide & transform the list into the actual clipboard Atom target format the caller wanted. - */ -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - if (uFmt == VBOX_SHCL_FMT_URI_LIST) - { - PSHCLTRANSFER pTransfer = NULL; - vrc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, - NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); - if (RT_SUCCESS(vrc)) - { - /* Initialize the transfer on the host side. */ - vrc = ShClSvcTransferInit(pClient, pTransfer); - } - - if (RT_SUCCESS(vrc)) - { - /* We have to wait for the guest reporting the transfer as being initialized. - * Only then we can start reading stuff. */ - vrc = ShClTransferWaitForStatus(pTransfer, SHCL_TIMEOUT_DEFAULT_MS, SHCLTRANSFERSTATUS_INITIALIZED); - if (RT_SUCCESS(vrc)) - { - vrc = ShClTransferRootListRead(pTransfer); - if (RT_SUCCESS(vrc)) - { -# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - /* As soon as we register the transfer with the HTTP server, the transfer needs to have its roots set. */ - PSHCLHTTPSERVER const pHttpSrv = &pCtx->X11.HttpCtx.HttpServer; - vrc = ShClTransferHttpServerRegisterTransfer(pHttpSrv, pTransfer); - if (RT_SUCCESS(vrc)) - { - char *pszData; - size_t cbData; - vrc = ShClTransferHttpConvertToStringList(pHttpSrv, pTransfer, &pszData, &cbData); - if (RT_SUCCESS(vrc)) - { - RTMemFree(*ppv); - *ppv = pszData; - *pcb = cbData; - /* ppv has ownership of pszData now. */ - } - } -# else - vrc = VERR_NOT_SUPPORTED; -# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ - } - } - } - - if ( RT_FAILURE(vrc) - && pTransfer) - ShClSvcTransferDestroy(pClient, pTransfer); - - if (RT_FAILURE(vrc)) - { - RTMemFree(*ppv); - *ppv = NULL; - *pcb = 0; - } - } -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ if (RT_FAILURE(vrc)) LogRel(("Shared Clipboard: Requesting X11 data in format %#x from guest failed with %Rrc\n", uFmt, vrc)); @@ -792,4 +1185,3 @@ static DECLCALLBACK(int) shClSvcX11TransferIfaceHGRootListRead(PSHCLTXPROVIDERCT return vrc; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - From 942af0430b5ab790e70fb55ea4015f1528ca8371 Mon Sep 17 00:00:00 2001 From: Klaus Espenlaub Date: Thu, 6 Aug 2026 15:53:09 +0000 Subject: [PATCH 017/176] Runtime/common/crypto/x509-create-sign.cpp: Change handling of subject name. No longer adjust it "by reference" and set the issuer to the same, instead use the clean approach of creating a name object first and setting both subject and issuer name. github:gh-794 svn:sync-xref-src-repo-rev: r174705 --- src/VBox/Runtime/common/crypto/x509-create-sign.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/VBox/Runtime/common/crypto/x509-create-sign.cpp b/src/VBox/Runtime/common/crypto/x509-create-sign.cpp index aa0fe547540f..3705a326600e 100644 --- a/src/VBox/Runtime/common/crypto/x509-create-sign.cpp +++ b/src/VBox/Runtime/common/crypto/x509-create-sign.cpp @@ -1,4 +1,4 @@ -/* $Id: x509-create-sign.cpp 114861 2026-08-05 16:23:07Z klaus.espenlaub@oracle.com $ */ +/* $Id: x509-create-sign.cpp 114868 2026-08-06 15:53:09Z klaus.espenlaub@oracle.com $ */ /** @file * IPRT - Crypto - X.509, Certificate Creation. */ @@ -153,9 +153,11 @@ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType /** @todo set other certificate attributes? */ /* Make it self signed: */ - X509_NAME *pX509Name = (X509_NAME *)X509_get_subject_name(pNewCert); + X509_NAME *pX509Name = X509_NAME_new(); rcOssl = X509_NAME_add_entry_by_txt(pX509Name, "CN", MBSTRING_ASC, (const unsigned char *)pszSubject, -1, -1, 0); AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_NAME_add_entry_by_txt failed")); + rcOssl = X509_set_subject_name(pNewCert, pX509Name); + AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_set_subject_name failed")); rcOssl = X509_set_issuer_name(pNewCert, pX509Name); AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_set_issuer_name failed")); @@ -208,6 +210,7 @@ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType rc = RTErrInfoSet(pErrInfo, VERR_CR_KEY_GEN_FAILED_RSA, "X509_sign failed"); } + X509_NAME_free(pX509Name); X509_free(pNewCert); } else From 7ea965b725dac4575508521ebe9b5ce1bf2614d5 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Thu, 6 Aug 2026 18:20:51 +0000 Subject: [PATCH 018/176] Main: legacy VBVA update (update). bugref:11120 svn:sync-xref-src-repo-rev: r174707 --- src/VBox/Main/src-client/DisplayImplLegacy.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/VBox/Main/src-client/DisplayImplLegacy.cpp b/src/VBox/Main/src-client/DisplayImplLegacy.cpp index 402f7a6369e9..337561834158 100644 --- a/src/VBox/Main/src-client/DisplayImplLegacy.cpp +++ b/src/VBox/Main/src-client/DisplayImplLegacy.cpp @@ -1,4 +1,4 @@ -/* $Id: DisplayImplLegacy.cpp 114707 2026-07-14 13:40:18Z vitali.pelenjow@oracle.com $ */ +/* $Id: DisplayImplLegacy.cpp 114870 2026-08-06 18:20:51Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox IDisplay implementation, helpers for legacy GAs. * @@ -390,7 +390,8 @@ static bool i_vbvaFetchBytes(uint8_t RT_UNTRUSTED_VOLATILE_GUEST const *pu8RingB static bool i_vbvaPartialRead(uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, - uint8_t RT_UNTRUSTED_VOLATILE_GUEST const *pu8RingBuffer, uint32_t off32Data) + uint8_t RT_UNTRUSTED_VOLATILE_GUEST const *pu8RingBuffer, uint32_t off32Data, + uint32_t RT_UNTRUSTED_VOLATILE_GUEST *poff32Data) { uint8_t *pu8New; @@ -426,7 +427,8 @@ static bool i_vbvaPartialRead(uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, } /* Fetch data from the ring buffer. */ - if (!i_vbvaFetchBytes(pu8RingBuffer, off32Data, pu8New + *pcb, cbRecord - *pcb)) + uint32_t const cbFetch = cbRecord - *pcb; + if (!i_vbvaFetchBytes(pu8RingBuffer, off32Data, pu8New + *pcb, cbFetch)) { RTMemFree(pu8New); @@ -436,6 +438,9 @@ static bool i_vbvaPartialRead(uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, return false; } + /* Advance data offset. */ + *poff32Data = (off32Data + cbFetch) % VMMDEV_VBVA_RING_BUFFER_SIZE; + *ppu8 = pu8New; *pcb = cbRecord; @@ -490,7 +495,8 @@ static bool i_vbvaFetchCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR **ppHdr, uint32_t if (cbRecord > pVideoAccel->cbVbvaPartial) { /* New data has been added to the record. */ - if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, &pVbvaMemory->au8RingBuffer[0], off32Data)) + if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, + &pVbvaMemory->au8RingBuffer[0], off32Data, &pVbvaMemory->off32Data)) { return false; } @@ -524,7 +530,8 @@ static bool i_vbvaFetchCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR **ppHdr, uint32_t if (cbRecord >= VMMDEV_VBVA_RING_BUFFER_SIZE - VMMDEV_VBVA_RING_BUFFER_THRESHOLD) { /* Partial read must be started. */ - if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, &pVbvaMemory->au8RingBuffer[0], off32Data)) + if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, + &pVbvaMemory->au8RingBuffer[0], off32Data, &pVbvaMemory->off32Data)) { return false; } From cac50df27e6e30684b1ef065f779df0ee54d2604 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 20:59:25 +0000 Subject: [PATCH 019/176] IPRT/Shared Memory: Fixes for named shared-memory creation and unmapping. bugref:11149 svn:sync-xref-src-repo-rev: r174710 --- src/VBox/Runtime/r3/posix/shmem-posix.cpp | 9 +++++---- src/VBox/Runtime/r3/win/shmem-win.cpp | 16 +++++++++++++--- src/VBox/Runtime/testcase/tstRTShMem.cpp | 11 +++++++++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/VBox/Runtime/r3/posix/shmem-posix.cpp b/src/VBox/Runtime/r3/posix/shmem-posix.cpp index 485c96859fe2..41b04441318c 100644 --- a/src/VBox/Runtime/r3/posix/shmem-posix.cpp +++ b/src/VBox/Runtime/r3/posix/shmem-posix.cpp @@ -1,4 +1,4 @@ -/* $Id: shmem-posix.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: shmem-posix.cpp 114873 2026-08-06 20:59:25Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Named shared memory object, POSIX Implementation. */ @@ -177,7 +177,7 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, if (fFlags & RTSHMEM_O_F_TRUNCATE) fShmFlags |= O_TRUNC; pThis->iFdShm = shm_open(pThis->pszName, fShmFlags , 0600); - if (pThis->iFdShm > 0) + if (pThis->iFdShm >= 0) { if (cbMax) rc = RTShMemSetSize(pThis, cbMax); @@ -187,6 +187,8 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, return VINF_SUCCESS; } + if (fShmFlags & O_EXCL) + shm_unlink(pThis->pszName); close(pThis->iFdShm); } else @@ -398,7 +400,7 @@ RTDECL(int) RTShMemUnmapRegion(RTSHMEM hShMem, void *pv) AssertPtrReturn(pMappingDesc, VERR_INVALID_PARAMETER); int rc = VINF_SUCCESS; - size_t cbRegion = pMappingDesc->cMappings; + size_t const cbRegion = pMappingDesc->cbRegion; if (!ASMAtomicDecU32(&pMappingDesc->cMappings)) { /* Last mapping of this region was unmapped, so do the real unmapping now. */ @@ -416,4 +418,3 @@ RTDECL(int) RTShMemUnmapRegion(RTSHMEM hShMem, void *pv) return rc; } - diff --git a/src/VBox/Runtime/r3/win/shmem-win.cpp b/src/VBox/Runtime/r3/win/shmem-win.cpp index 7f027fe76a38..f29163f280d6 100644 --- a/src/VBox/Runtime/r3/win/shmem-win.cpp +++ b/src/VBox/Runtime/r3/win/shmem-win.cpp @@ -1,4 +1,4 @@ -/* $Id: shmem-win.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: shmem-win.cpp 114873 2026-08-06 20:59:25Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Named shared memory object, Windows Implementation. */ @@ -189,6 +189,7 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, rc = RTStrToUtf16Ex(&szName[0], RTSTR_MAX, &pwszName, 0, NULL); if (RT_SUCCESS(rc)) { + DWORD dwErr; if (fFlags & RTSHMEM_O_F_CREATE) { #if HC_ARCH_BITS == 64 @@ -215,8 +216,17 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, else fProt |= PAGE_READWRITE; } + SetLastError(ERROR_SUCCESS); pThis->hShmObj = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, fProt, dwSzMaxHigh, dwSzMaxLow, pwszName); + dwErr = GetLastError(); + if ( pThis->hShmObj != NULL + && (fFlags & RTSHMEM_O_F_CREATE_EXCL) == RTSHMEM_O_F_CREATE_EXCL + && dwErr == ERROR_ALREADY_EXISTS) + { + CloseHandle(pThis->hShmObj); + pThis->hShmObj = NULL; + } } else { @@ -229,6 +239,7 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, fProt |= FILE_MAP_WRITE; pThis->hShmObj = OpenFileMappingW(fProt, FALSE, pwszName); + dwErr = GetLastError(); } RTUtf16Free(pwszName); if (pThis->hShmObj != NULL) @@ -237,7 +248,7 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, return VINF_SUCCESS; } else - rc = RTErrConvertFromWin32(GetLastError()); + rc = RTErrConvertFromWin32(dwErr); } } else @@ -470,4 +481,3 @@ RTDECL(int) RTShMemUnmapRegion(RTSHMEM hShMem, void *pv) return rc; } - diff --git a/src/VBox/Runtime/testcase/tstRTShMem.cpp b/src/VBox/Runtime/testcase/tstRTShMem.cpp index 27c2314df0b3..43de46d515e1 100644 --- a/src/VBox/Runtime/testcase/tstRTShMem.cpp +++ b/src/VBox/Runtime/testcase/tstRTShMem.cpp @@ -1,4 +1,4 @@ -/* $Id: tstRTShMem.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: tstRTShMem.cpp 114873 2026-08-06 20:59:25Z andreas.loeffler@oracle.com $ */ /** @file * IPRT Testcase - RTShMem. */ @@ -84,6 +84,14 @@ static void tstRTShMem1(void) RTTESTI_CHECK_RETV(g_hShMem != NIL_RTSHMEM); + /* Creating the same named object exclusively must fail. */ + RTSHMEM hShMemDuplicate = NIL_RTSHMEM; + RTTESTI_CHECK_RC(RTShMemOpen(&hShMemDuplicate, "tstRTShMem-Share", + RTSHMEM_O_F_CREATE_EXCL | RTSHMEM_O_F_READWRITE | RTSHMEM_O_F_MAYBE_EXEC, _512K, 0), + VERR_ALREADY_EXISTS); + if (hShMemDuplicate != NIL_RTSHMEM) + RTTESTI_CHECK_RC(RTShMemClose(hShMemDuplicate), VINF_SUCCESS); + /* Query the size. */ size_t cbShMem = 0; RTTESTI_CHECK_RC(RTShMemQuerySize(g_hShMem, &cbShMem), VINF_SUCCESS); @@ -155,4 +163,3 @@ int main() */ return RTTestSummaryAndDestroy(hTest); } - From 860a3ba6b9f9dfe12294a2afd4a8d5e6252fe68c Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 21:28:04 +0000 Subject: [PATCH 020/176] =?UTF-8?q?IPRT:=20Secure=20restricted=20local=20I?= =?UTF-8?q?PC=20namespaces=20and=20peer=20identity.=20=E2=80=8Bbugref:1114?= =?UTF-8?q?9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174711 --- include/iprt/localipc.h | 52 +- include/iprt/mangling.h | 3 +- src/VBox/Runtime/r3/posix/localipc-posix.cpp | 242 +++++- src/VBox/Runtime/r3/win/localipc-win.cpp | 790 ++++++++++++++++--- src/VBox/Runtime/testcase/tstRTLocalIpc.cpp | 554 ++++++++++++- 5 files changed, 1481 insertions(+), 160 deletions(-) diff --git a/include/iprt/localipc.h b/include/iprt/localipc.h index 16583484c6b0..f58baaea6e50 100644 --- a/include/iprt/localipc.h +++ b/include/iprt/localipc.h @@ -82,6 +82,15 @@ typedef RTLOCALIPCSESSION *PRTLOCALIPCSESSION; * any special chars or slashes. It will be morphed into a * unique platform specific identifier. * @param fFlags Flags, see RTLOCALIPC_FLAGS_*. + * + * @remarks For portable names, RTLOCALIPC_FLAGS_RESTRICT_TO_USER places the + * endpoint in a protected per-user or per-login-session namespace. + * The client must use RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER as well. + * On Windows, the portable pipe name includes the token session ID + * and logon LUID, while pipe access is limited to LocalSystem and the + * creating token's logon SID. Restricted connections also verify + * the peer session and account. Without the flag, the legacy global + * namespace and security behavior are used. */ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszName, uint32_t fFlags); @@ -89,8 +98,13 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa * @{ */ /** Native name, as apposed to a portable one. */ #define RTLOCALIPC_FLAGS_NATIVE_NAME RT_BIT_32(0) +/** Restrict the portable namespace and access to the login session creating the server. + * + * On Windows, the portable name is tagged with the token session ID and logon + * LUID, and the pipe DACL grants access to the token logon SID and LocalSystem. */ +#define RTLOCALIPC_FLAGS_RESTRICT_TO_USER RT_BIT_32(1) /** The mask of valid flags. */ -#define RTLOCALIPC_FLAGS_VALID_MASK UINT32_C(0x00000001) +#define RTLOCALIPC_FLAGS_VALID_MASK UINT32_C(0x00000003) /** @} */ /** @@ -108,6 +122,8 @@ RTDECL(int) RTLocalIpcServerDestroy(RTLOCALIPCSERVER hServer); * Grant the specified group access to the local IPC server socket. * * @returns IPRT status code. + * @retval VERR_NOT_SUPPORTED if this is not implemented on the host platform, + * including Windows. * @param hServer The server handle. * @param gid Group ID. */ @@ -128,6 +144,8 @@ RTDECL(int) RTLocalIpcServerSetAccessMode(RTLOCALIPCSERVER hServer, RTFMODE fMod * @returns IPRT status code. * @retval VINF_SUCCESS on success and *phClientSession containing the session handle. * @retval VERR_CANCELLED if the listening was interrupted by RTLocalIpcServerCancel(). + * @retval VERR_TRY_AGAIN if a restricted Windows peer was rejected before a + * session could be returned. The server remains usable. * * @param hServer The server handle. * @param phClientSession Where to store the client session handle on success. @@ -162,8 +180,18 @@ RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *p * @{ */ /** Native name, as apposed to a portable one. */ #define RTLOCALIPC_C_FLAGS_NATIVE_NAME RT_BIT_32(0) +/** Allow the server to identify the client. + * + * On Windows this selects the SECURITY_IDENTIFICATION quality-of-service + * level instead of the default SECURITY_ANONYMOUS level. */ +#define RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION RT_BIT_32(1) +/** Resolve a portable name in the protected per-user or per-login-session + * namespace. On Windows, this uses the current token session ID and logon + * LUID and verifies the server's session and account. The server must use + * RTLOCALIPC_FLAGS_RESTRICT_TO_USER. */ +#define RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER RT_BIT_32(2) /** The mask of valid flags. */ -#define RTLOCALIPC_C_FLAGS_VALID_MASK UINT32_C(0x00000001) +#define RTLOCALIPC_C_FLAGS_VALID_MASK UINT32_C(0x00000007) /** @} */ /** @@ -315,6 +343,25 @@ RTDECL(int) RTLocalIpcSessionCancel(RTLOCALIPCSESSION hSession); */ RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS pProcess); +/** + * Verifies that the other party belongs to the user running this process. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if the peer belongs to the same user. + * @retval VERR_ACCESS_DENIED if the peer belongs to another user or cannot + * present an identity accepted by this session. + * @retval VERR_CANCELLED if the operation was cancelled by RTLocalIpcSessionCancel. + * @retval VERR_NOT_SUPPORTED if this is not implemented on the host platform. + * + * @param hSession The session handle. + * + * @remarks On Windows, the server must first read data sent by the client on + * the session, and the client must connect with + * RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION, for the server-side check + * to succeed. The client-side check verifies the named-pipe owner. + */ +RTDECL(int) RTLocalIpcSessionVerifySameUser(RTLOCALIPCSESSION hSession); + /** * Query the user ID of the other party. * @@ -351,4 +398,3 @@ RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGi RT_C_DECLS_END #endif /* !IPRT_INCLUDED_localipc_h */ - diff --git a/include/iprt/mangling.h b/include/iprt/mangling.h index b6e6d6dddab8..bd0ecec4bd78 100644 --- a/include/iprt/mangling.h +++ b/include/iprt/mangling.h @@ -1487,7 +1487,7 @@ # define RTLocalIpcServerCreate RT_MANGLER(RTLocalIpcServerCreate) # define RTLocalIpcServerDestroy RT_MANGLER(RTLocalIpcServerDestroy) # define RTLocalIpcServerGrantGroupAccess RT_MANGLER(RTLocalIpcServerGrantGroupAccess) -# define RTLocalIpcServerSetAccessMode RT_MANGLER(RTLocalIpcServerSetAccessMode); +# define RTLocalIpcServerSetAccessMode RT_MANGLER(RTLocalIpcServerSetAccessMode) # define RTLocalIpcServerCancel RT_MANGLER(RTLocalIpcServerCancel) # define RTLocalIpcServerListen RT_MANGLER(RTLocalIpcServerListen) # define RTLocalIpcSessionConnect RT_MANGLER(RTLocalIpcSessionConnect) @@ -1501,6 +1501,7 @@ # define RTLocalIpcSessionFlush RT_MANGLER(RTLocalIpcSessionFlush) # define RTLocalIpcSessionWaitForData RT_MANGLER(RTLocalIpcSessionWaitForData) # define RTLocalIpcSessionQueryProcess RT_MANGLER(RTLocalIpcSessionQueryProcess) +# define RTLocalIpcSessionVerifySameUser RT_MANGLER(RTLocalIpcSessionVerifySameUser) # define RTLocalIpcSessionQueryUserId RT_MANGLER(RTLocalIpcSessionQueryUserId) # define RTLocalIpcSessionQueryGroupId RT_MANGLER(RTLocalIpcSessionQueryGroupId) # define RTLocaleQueryLocaleName RT_MANGLER(RTLocaleQueryLocaleName) diff --git a/src/VBox/Runtime/r3/posix/localipc-posix.cpp b/src/VBox/Runtime/r3/posix/localipc-posix.cpp index bcf840f95099..26312aa4b0b4 100644 --- a/src/VBox/Runtime/r3/posix/localipc-posix.cpp +++ b/src/VBox/Runtime/r3/posix/localipc-posix.cpp @@ -1,4 +1,4 @@ -/* $Id: localipc-posix.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: localipc-posix.cpp 114874 2026-08-06 21:28:04Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Local IPC Server & Client, Posix. */ @@ -39,6 +39,10 @@ * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_LOCALIPC +#include +#if defined(RT_OS_LINUX) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +#endif #include "internal/iprt.h" #include @@ -46,7 +50,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -58,6 +65,9 @@ #include #include #include +#ifdef RT_OS_SOLARIS +# include +#endif #ifndef RT_OS_OS2 # include #endif @@ -131,7 +141,109 @@ typedef RTLOCALIPCSESSIONINT *PRTLOCALIPCSESSIONINT; /** Local IPC name prefix for portable names. */ -#define RTLOCALIPC_POSIX_NAME_PREFIX "/tmp/.iprt-localipc-" +#define RTLOCALIPC_POSIX_NAME_PREFIX "/tmp/.iprt-localipc-" +/** Local IPC name prefix inside a protected user namespace. */ +#define RTLOCALIPC_POSIX_USER_NAME_PREFIX ".iprt-" + + +/** + * Validates a candidate protected user namespace directory. + * + * @returns IPRT status code. + * @param pszPath The directory to validate. + * @param fNamespace Whether this is the endpoint namespace itself, + * for which no group or other access is allowed. + */ +static int rtLocalIpcPosixValidateUserDir(const char *pszPath, bool fNamespace) +{ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(RTPathStartsWithRoot(pszPath), VERR_INVALID_NAME); + + RTFSOBJINFO ObjInfo; + int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + rc = VERR_NOT_A_DIRECTORY; + else if ( ObjInfo.Attr.u.Unix.uid == NIL_RTUID + || ObjInfo.Attr.u.Unix.uid != (RTUID)geteuid() + || (ObjInfo.Attr.fMode & (RTFS_UNIX_IWUSR | RTFS_UNIX_IXUSR)) + != (RTFS_UNIX_IWUSR | RTFS_UNIX_IXUSR) + || (fNamespace + ? RT_BOOL(ObjInfo.Attr.fMode & (RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO)) + : RT_BOOL(ObjInfo.Attr.fMode & (RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH)))) + rc = VERR_ACCESS_DENIED; + } + return rc; +} + + +/** Returns whether a restricted portable name fits in a Unix socket path. */ +static bool rtLocalIpcPosixUserNameFits(const char *pszPath, const char *pszName) +{ + size_t const cchPath = strlen(pszPath); + return cchPath > 0 + && cchPath + (RTPATH_IS_SLASH(pszPath[cchPath - 1]) ? 0 : 1) + + sizeof(RTLOCALIPC_POSIX_USER_NAME_PREFIX) - 1 + strlen(pszName) + 1 + <= sizeof(((struct sockaddr_un *)0)->sun_path); +} + + +/** + * Locates or creates the protected namespace used for restricted portable + * names. + * + * @returns IPRT status code. + * @param pszPath Where to return the directory. + * @param cbPath Size of the output buffer. + * @param pszName The restricted portable endpoint name. + */ +static int rtLocalIpcPosixGetUserDir(char *pszPath, size_t cbPath, const char *pszName) +{ + const char *pszCandidate = RTEnvGet("XDG_RUNTIME_DIR"); + if (pszCandidate && *pszCandidate && RTPathStartsWithRoot(pszCandidate)) + { + int rc = RTStrCopy(pszPath, cbPath, pszCandidate); + if (RT_SUCCESS(rc)) + rc = rtLocalIpcPosixValidateUserDir(pszPath, true /*fNamespace*/); + if (RT_SUCCESS(rc) && rtLocalIpcPosixUserNameFits(pszPath, pszName)) + return VINF_SUCCESS; + } + +#ifdef RT_OS_LINUX + ssize_t const cchRunUser = RTStrPrintf2(pszPath, cbPath, "/run/user/%RTuid", (RTUID)geteuid()); + if (cchRunUser > 0 && (size_t)cchRunUser < cbPath) + { + int const rc = rtLocalIpcPosixValidateUserDir(pszPath, true /*fNamespace*/); + if (RT_SUCCESS(rc) && rtLocalIpcPosixUserNameFits(pszPath, pszName)) + return VINF_SUCCESS; + } +#endif + + char szHome[RTPATH_MAX]; + int rc = RTPathUserHome(szHome, sizeof(szHome)); + if (RT_SUCCESS(rc) && !RTPathStartsWithRoot(szHome)) + rc = VERR_INVALID_NAME; + if (RT_SUCCESS(rc)) + rc = RTPathReal(szHome, pszPath, cbPath); + if (RT_SUCCESS(rc)) + rc = rtLocalIpcPosixValidateUserDir(pszPath, false /*fNamespace*/); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pszPath, cbPath, ".iprt-localipc"); + if (RT_SUCCESS(rc)) + { + rc = RTDirCreate(pszPath, RTFS_UNIX_IRWXU, 0 /*fCreate*/); + if (rc == VERR_ALREADY_EXISTS) + rc = VINF_SUCCESS; + else if (RT_SUCCESS(rc)) + rc = RTPathSetMode(pszPath, RTFS_UNIX_IRWXU); + } + if (RT_SUCCESS(rc)) + rc = rtLocalIpcPosixValidateUserDir(pszPath, true /*fNamespace*/); + if (RT_SUCCESS(rc) && !rtLocalIpcPosixUserNameFits(pszPath, pszName)) + rc = VERR_FILENAME_TOO_LONG; + return rc; +} /** @@ -177,9 +289,28 @@ static int rtLocalIpcPosixValidateName(const char *pszName, bool fNative) * @param pcbAddr Where to return the address size. * @param pszName The user specified name (valid). * @param fNative Whether it's a native name or a portable name. + * @param fRestrictToUser Whether portable names use the protected user + * namespace. */ -static int rtLocalIpcPosixConstructName(struct sockaddr_un *pAddr, uint8_t *pcbAddr, const char *pszName, bool fNative) +static int rtLocalIpcPosixConstructName(struct sockaddr_un *pAddr, uint8_t *pcbAddr, const char *pszName, + bool fNative, bool fRestrictToUser) { + char szUserName[RTPATH_MAX]; + if (!fNative && fRestrictToUser) + { + int rc = rtLocalIpcPosixGetUserDir(szUserName, sizeof(szUserName), pszName); + if (RT_FAILURE(rc)) + return rc; + rc = RTPathAppend(szUserName, sizeof(szUserName), RTLOCALIPC_POSIX_USER_NAME_PREFIX); + if (RT_FAILURE(rc)) + return rc; + rc = RTStrCat(szUserName, sizeof(szUserName), pszName); + if (RT_FAILURE(rc)) + return rc; + pszName = szUserName; + fNative = true; + } + const char *pszNativeName; int rc = rtPathToNative(&pszNativeName, pszName, NULL /*pszBasePath not support*/); if (RT_SUCCESS(rc)) @@ -251,7 +382,8 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa uint8_t cbAddr; rc = rtLocalIpcPosixConstructName(&pThis->Name, &cbAddr, pszName, - RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { rc = rtSocketBindRawAddr(pThis->hSocket, &pThis->Name, cbAddr); @@ -262,7 +394,11 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa } if (RT_SUCCESS(rc)) { - rc = rtSocketListen(pThis->hSocket, 16); + if ( !(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER) + || chmod(pThis->Name.sun_path, S_IRUSR | S_IWUSR) == 0) + rc = rtSocketListen(pThis->hSocket, 16); + else + rc = RTErrConvertFromErrno(errno); if (RT_SUCCESS(rc)) { LogFlow(("RTLocalIpcServerCreate: Created %p (%s)\n", pThis, pThis->Name.sun_path)); @@ -572,7 +708,9 @@ RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *p struct sockaddr_un Addr; uint8_t cbAddr; - rc = rtLocalIpcPosixConstructName(&Addr, &cbAddr, pszName, RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + rc = rtLocalIpcPosixConstructName(&Addr, &cbAddr, pszName, + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { rc = rtSocketConnectRaw(pThis->hSocket, &Addr, cbAddr); @@ -1118,27 +1256,77 @@ static int rtLocalIpcSessionQueryUcred(RTLOCALIPCSESSION hSession, PRTPROCESS pP AssertPtrReturn(pThis, VERR_INVALID_HANDLE); AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); -#if defined(RT_OS_LINUX) - struct ucred PeerCred = { (pid_t)NIL_RTPROCESS, (uid_t)NIL_RTUID, (gid_t)NIL_RTGID }; - socklen_t cbPeerCred = sizeof(PeerCred); - rtLocalIpcSessionRetain(pThis); - int rc = RTCritSectEnter(&pThis->CritSect);; + int rc = RTCritSectEnter(&pThis->CritSect); if (RT_SUCCESS(rc)) { - if (getsockopt(RTSocketToNative(pThis->hSocket), SOL_SOCKET, SO_PEERCRED, &PeerCred, &cbPeerCred) >= 0) + if (pThis->fCancelled) + rc = VERR_CANCELLED; + else { +#if defined(RT_OS_LINUX) + struct ucred PeerCred = { (pid_t)NIL_RTPROCESS, (uid_t)NIL_RTUID, (gid_t)NIL_RTGID }; + socklen_t cbPeerCred = sizeof(PeerCred); + if (getsockopt(RTSocketToNative(pThis->hSocket), SOL_SOCKET, SO_PEERCRED, &PeerCred, &cbPeerCred) >= 0) + { + if (pProcess) + *pProcess = PeerCred.pid; + if (pUid) + *pUid = PeerCred.uid; + if (pGid) + *pGid = PeerCred.gid; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromErrno(errno); +#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) \ + || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) + if (!pProcess) + { + uid_t uidPeer = (uid_t)NIL_RTUID; + gid_t gidPeer = (gid_t)NIL_RTGID; + if (getpeereid(RTSocketToNative(pThis->hSocket), &uidPeer, &gidPeer) == 0) + { + if (pUid) + *pUid = uidPeer; + if (pGid) + *pGid = gidPeer; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromErrno(errno); + } + else + { + *pProcess = NIL_RTPROCESS; + rc = VERR_NOT_SUPPORTED; + } +#elif defined(RT_OS_SOLARIS) + ucred_t *pCred = NULL; + if (getpeerucred(RTSocketToNative(pThis->hSocket), &pCred) == 0) + { + if (pProcess) + *pProcess = ucred_getpid(pCred); + if (pUid) + *pUid = ucred_geteuid(pCred); + if (pGid) + *pGid = ucred_getegid(pCred); + ucred_free(pCred); + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromErrno(errno); +#else if (pProcess) - *pProcess = PeerCred.pid; + *pProcess = NIL_RTPROCESS; if (pUid) - *pUid = PeerCred.uid; + *pUid = NIL_RTUID; if (pGid) - *pGid = PeerCred.gid; - rc = VINF_SUCCESS; + *pGid = NIL_RTGID; + rc = VERR_NOT_SUPPORTED; +#endif } - else - rc = RTErrConvertFromErrno(errno); int rc2 = RTCritSectLeave(&pThis->CritSect); AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); @@ -1147,13 +1335,6 @@ static int rtLocalIpcSessionQueryUcred(RTLOCALIPCSESSION hSession, PRTPROCESS pP rtLocalIpcSessionRelease(pThis); return rc; - -#else - /** @todo Implement on other platforms too (mostly platform specific this). - * Solaris: getpeerucred? Darwin: LOCALPEERCRED or getpeereid? */ - RT_NOREF(pProcess, pUid, pGid); - return VERR_NOT_SUPPORTED; -#endif } @@ -1163,6 +1344,16 @@ RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS } +RTDECL(int) RTLocalIpcSessionVerifySameUser(RTLOCALIPCSESSION hSession) +{ + RTUID uidPeer = NIL_RTUID; + int rc = rtLocalIpcSessionQueryUcred(hSession, NULL, &uidPeer, NULL); + if (RT_SUCCESS(rc) && uidPeer != (RTUID)geteuid()) + rc = VERR_ACCESS_DENIED; + return rc; +} + + RTDECL(int) RTLocalIpcSessionQueryUserId(RTLOCALIPCSESSION hSession, PRTUID pUid) { return rtLocalIpcSessionQueryUcred(hSession, NULL, pUid, NULL); @@ -1172,4 +1363,3 @@ RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGi { return rtLocalIpcSessionQueryUcred(hSession, NULL, NULL, pGid); } - diff --git a/src/VBox/Runtime/r3/win/localipc-win.cpp b/src/VBox/Runtime/r3/win/localipc-win.cpp index 1ca26717109f..d94affff3dc4 100644 --- a/src/VBox/Runtime/r3/win/localipc-win.cpp +++ b/src/VBox/Runtime/r3/win/localipc-win.cpp @@ -1,4 +1,4 @@ -/* $Id: localipc-win.cpp 113928 2026-04-16 23:39:20Z knut.osmundsen@oracle.com $ */ +/* $Id: localipc-win.cpp 114874 2026-08-06 21:28:04Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Local IPC, Windows Implementation Using Named Pipes. * @@ -43,7 +43,6 @@ *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_LOCALIPC #include /* Need NtCancelIoFile and a few Rtl functions. */ -#include #include #include "internal/iprt.h" @@ -54,9 +53,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -74,6 +73,12 @@ *********************************************************************************************************************************/ /** Pipe prefix string. */ #define RTLOCALIPC_WIN_PREFIX L"\\\\.\\pipe\\IPRT-" +/** Pipe prefix string in the caller's protected login-session namespace. */ +#define RTLOCALIPC_WIN_USER_PREFIX L"\\\\.\\pipe\\LOCAL\\IPRT-" +/** Number of UTF-16 units in the hexadecimal session ID and trailing dash. */ +#define RTLOCALIPC_WIN_SESSION_ID_CWC 9 +/** Number of UTF-16 units in the hexadecimal logon LUID and trailing dash. */ +#define RTLOCALIPC_WIN_LOGON_ID_CWC 17 /********************************************************************************************************************************* @@ -129,6 +134,8 @@ typedef struct RTLOCALIPCSESSIONINT bool volatile fCancelled; /** Set if this is the server side, clear if the client. */ bool fServerSide; + /** Set if the session must remain in the current Windows logon session. */ + bool fRestricted; /** The named pipe handle. */ HANDLE hNmPipe; struct @@ -159,18 +166,341 @@ typedef struct RTLOCALIPCSESSIONINT typedef RTLOCALIPCSESSIONINT *PRTLOCALIPCSESSIONINT; +/** Pointer to a GetNamedPipeClientProcessId or GetNamedPipeServerProcessId function. */ +typedef BOOL (WINAPI *PFNRTLOCALIPCWINQUERYPIPEPROCESS)(HANDLE hPipe, PULONG pidProcess); +/** Pointer to a GetNamedPipeClientSessionId or GetNamedPipeServerSessionId function. */ +typedef BOOL (WINAPI *PFNRTLOCALIPCWINQUERYPIPESESSION)(HANDLE hPipe, PULONG pidSession); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Init once structure for resolving the named pipe process query APIs. */ +static RTONCE g_rtLocalIpcWinQueryProcessResolveOnce = RTONCE_INITIALIZER; +/** GetNamedPipeClientProcessId, introduced with Windows Vista. */ +static PFNRTLOCALIPCWINQUERYPIPEPROCESS g_pfnGetNamedPipeClientProcessId = NULL; +/** GetNamedPipeServerProcessId, introduced with Windows Vista. */ +static PFNRTLOCALIPCWINQUERYPIPEPROCESS g_pfnGetNamedPipeServerProcessId = NULL; +/** Init once structure for resolving the named pipe session query APIs. */ +static RTONCE g_rtLocalIpcWinQuerySessionResolveOnce = RTONCE_INITIALIZER; +/** GetNamedPipeClientSessionId, introduced with Windows Vista. */ +static PFNRTLOCALIPCWINQUERYPIPESESSION g_pfnGetNamedPipeClientSessionId = NULL; +/** GetNamedPipeServerSessionId, introduced with Windows Vista. */ +static PFNRTLOCALIPCWINQUERYPIPESESSION g_pfnGetNamedPipeServerSessionId = NULL; + + /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ -static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession); +static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession, bool fRestricted); +static int rtLocalIpcWinVerifyUserSid(PSID pSid); + + +/** Queries the current process token's Windows session ID. */ +static int rtLocalIpcWinQuerySelfSessionId(uint32_t *pidSession) +{ + AssertPtrReturn(pidSession, VERR_INVALID_POINTER); + *pidSession = 0; + + HANDLE hToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + return RTErrConvertFromWin32(GetLastError()); + + DWORD idSession = 0; + DWORD cbSession = 0; + BOOL const fRc = GetTokenInformation(hToken, TokenSessionId, &idSession, sizeof(idSession), &cbSession); + DWORD const dwErr = fRc ? ERROR_SUCCESS : GetLastError(); + CloseHandle(hToken); + if (!fRc) + return RTErrConvertFromWin32(dwErr); + AssertReturn(cbSession == sizeof(idSession), VERR_INVALID_PARAMETER); + + *pidSession = idSession; + return VINF_SUCCESS; +} + + +/** Queries the current process token's Windows logon LUID. */ +static int rtLocalIpcWinQuerySelfLogonId(uint32_t *pidLogonHigh, uint32_t *pidLogonLow) +{ + AssertPtrReturn(pidLogonHigh, VERR_INVALID_POINTER); + AssertPtrReturn(pidLogonLow, VERR_INVALID_POINTER); + *pidLogonHigh = 0; + *pidLogonLow = 0; + + HANDLE hToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + return RTErrConvertFromWin32(GetLastError()); + + TOKEN_STATISTICS TokenStats; + DWORD cbTokenStats = 0; + BOOL const fRc = GetTokenInformation(hToken, TokenStatistics, &TokenStats, sizeof(TokenStats), &cbTokenStats); + DWORD const dwErr = fRc ? ERROR_SUCCESS : GetLastError(); + CloseHandle(hToken); + if (!fRc) + return RTErrConvertFromWin32(dwErr); + AssertReturn(cbTokenStats == sizeof(TokenStats), VERR_INVALID_PARAMETER); + + *pidLogonHigh = (uint32_t)TokenStats.AuthenticationId.HighPart; + *pidLogonLow = TokenStats.AuthenticationId.LowPart; + return VINF_SUCCESS; +} + + +/** + * Resolves the optional named pipe process query APIs. + * + * @returns IPRT status code. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int) rtLocalIpcWinQueryProcessResolveOnce(void *pvUser) +{ + RT_NOREF(pvUser); + + g_pfnGetNamedPipeClientProcessId = (PFNRTLOCALIPCWINQUERYPIPEPROCESS)GetProcAddress(g_hModKernel32, + "GetNamedPipeClientProcessId"); + g_pfnGetNamedPipeServerProcessId = (PFNRTLOCALIPCWINQUERYPIPEPROCESS)GetProcAddress(g_hModKernel32, + "GetNamedPipeServerProcessId"); + if ( g_pfnGetNamedPipeClientProcessId + && g_pfnGetNamedPipeServerProcessId) + return VINF_SUCCESS; + return VERR_NOT_SUPPORTED; +} + + +/** + * Resolves the optional named pipe session query APIs. + * + * @returns IPRT status code. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int) rtLocalIpcWinQuerySessionResolveOnce(void *pvUser) +{ + RT_NOREF(pvUser); + + g_pfnGetNamedPipeClientSessionId = (PFNRTLOCALIPCWINQUERYPIPESESSION)GetProcAddress(g_hModKernel32, + "GetNamedPipeClientSessionId"); + g_pfnGetNamedPipeServerSessionId = (PFNRTLOCALIPCWINQUERYPIPESESSION)GetProcAddress(g_hModKernel32, + "GetNamedPipeServerSessionId"); + if ( g_pfnGetNamedPipeClientSessionId + && g_pfnGetNamedPipeServerSessionId) + return VINF_SUCCESS; + return VERR_NOT_SUPPORTED; +} + + +/** + * Queries the user information carried by a Windows access token. + * + * @returns IPRT status code. + * @param hToken The token to query. + * @param ppTokenUser Where to return the allocated token user + * information. Free with RTMemTmpFree(). + */ +static int rtLocalIpcWinQueryTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser) +{ + AssertReturn(hToken != NULL && hToken != INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + AssertPtrReturn(ppTokenUser, VERR_INVALID_POINTER); + *ppTokenUser = NULL; + + DWORD cbTokenUser = 0; + if (GetTokenInformation(hToken, TokenUser, NULL, 0, &cbTokenUser)) + return VERR_INTERNAL_ERROR; + DWORD const dwErr = GetLastError(); + if (dwErr != ERROR_INSUFFICIENT_BUFFER) + return RTErrConvertFromWin32(dwErr); + AssertReturn(cbTokenUser >= sizeof(TOKEN_USER), VERR_INVALID_PARAMETER); + + PTOKEN_USER pTokenUser = (PTOKEN_USER)RTMemTmpAlloc(cbTokenUser); + if (!pTokenUser) + return VERR_NO_TMP_MEMORY; + if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbTokenUser, &cbTokenUser)) + { + int const rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pTokenUser); + return rc; + } + if (!IsValidSid(pTokenUser->User.Sid)) + { + RTMemTmpFree(pTokenUser); + return VERR_INVALID_PARAMETER; + } + + *ppTokenUser = pTokenUser; + return VINF_SUCCESS; +} + + +/** + * Queries the logon SID carried by a Windows access token. + * + * @returns IPRT status code. + * @param hToken The token to query. + * @param ppLogonSid Where to return the allocated logon SID. Free + * with RTMemTmpFree(). + */ +static int rtLocalIpcWinQueryTokenLogonSid(HANDLE hToken, PSID *ppLogonSid) +{ + AssertReturn(hToken != NULL && hToken != INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + AssertPtrReturn(ppLogonSid, VERR_INVALID_POINTER); + *ppLogonSid = NULL; + + DWORD cbTokenGroups = 0; + if (GetTokenInformation(hToken, TokenGroups, NULL, 0, &cbTokenGroups)) + return VERR_INTERNAL_ERROR; + DWORD const dwErr = GetLastError(); + if (dwErr != ERROR_INSUFFICIENT_BUFFER) + return RTErrConvertFromWin32(dwErr); + AssertReturn(cbTokenGroups >= sizeof(TOKEN_GROUPS), VERR_INVALID_PARAMETER); + + PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemTmpAlloc(cbTokenGroups); + if (!pTokenGroups) + return VERR_NO_TMP_MEMORY; + if (!GetTokenInformation(hToken, TokenGroups, pTokenGroups, cbTokenGroups, &cbTokenGroups)) + { + int const rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pTokenGroups); + return rc; + } + + int rc = VERR_ACCESS_DENIED; + for (DWORD i = 0; i < pTokenGroups->GroupCount; i++) + if ((pTokenGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) + { + PSID const pTokenLogonSid = pTokenGroups->Groups[i].Sid; + if (IsValidSid(pTokenLogonSid)) + { + DWORD const cbLogonSid = GetLengthSid(pTokenLogonSid); + PSID const pLogonSid = (PSID)RTMemTmpAlloc(cbLogonSid); + if (pLogonSid) + { + if (CopySid(cbLogonSid, pLogonSid, pTokenLogonSid)) + { + *ppLogonSid = pLogonSid; + rc = VINF_SUCCESS; + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pLogonSid); + } + } + else + rc = VERR_NO_TMP_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + break; + } + + RTMemTmpFree(pTokenGroups); + return rc; +} + + +/** Returns whether @a pSid is the LocalSystem SID. */ +static bool rtLocalIpcWinIsLocalSystemSid(PSID pSid) +{ + AssertReturn(pSid && IsValidSid(pSid), false); + + static SID_IDENTIFIER_AUTHORITY s_NtAuth = SECURITY_NT_AUTHORITY; + union + { + SID Sid; + uint8_t abPadding[SECURITY_MAX_SID_SIZE]; + } LocalSystem; + + NTSTATUS const rcNt = RtlInitializeSid(&LocalSystem.Sid, &s_NtAuth, 1); + AssertReturn(NT_SUCCESS(rcNt), false); + *RtlSubAuthoritySid(&LocalSystem.Sid, 0) = SECURITY_LOCAL_SYSTEM_RID; + return EqualSid(pSid, &LocalSystem.Sid) != FALSE; +} + + +/** Returns whether @a dwErr means that a named pipe peer disconnected. */ +static bool rtLocalIpcWinIsPeerGoneError(DWORD dwErr) +{ + return dwErr == ERROR_BROKEN_PIPE + || dwErr == ERROR_NO_DATA + || dwErr == ERROR_PIPE_NOT_CONNECTED; +} + + +/** Verifies that the named pipe peer belongs to the current Windows session. */ +static int rtLocalIpcWinVerifyPeerSession(HANDLE hPipe, bool fServerSide) +{ + AssertReturn(hPipe != NULL && hPipe != INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + + uint32_t idSelfSession = 0; + int rc = rtLocalIpcWinQuerySelfSessionId(&idSelfSession); + if (RT_SUCCESS(rc)) + { + rc = RTOnce(&g_rtLocalIpcWinQuerySessionResolveOnce, rtLocalIpcWinQuerySessionResolveOnce, NULL); + if (RT_SUCCESS(rc)) + { + ULONG idPeerSession = 0; + BOOL const fRc = fServerSide + ? g_pfnGetNamedPipeClientSessionId(hPipe, &idPeerSession) + : g_pfnGetNamedPipeServerSessionId(hPipe, &idPeerSession); + if (fRc) + rc = idPeerSession == idSelfSession ? VINF_SUCCESS : VERR_ACCESS_DENIED; + else + { + DWORD const dwErr = GetLastError(); + rc = fServerSide && rtLocalIpcWinIsPeerGoneError(dwErr) ? VERR_ACCESS_DENIED + : RTErrConvertFromWin32(dwErr); + } + } + } + return rc; +} + + +/** Verifies that the named pipe owner is the current process token's user. */ +static int rtLocalIpcWinVerifyPipeOwnerUser(HANDLE hPipe) +{ + AssertReturn(hPipe != NULL && hPipe != INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + + PSECURITY_DESCRIPTOR pSecDesc = NULL; + PSID pOwner = NULL; + DWORD const dwErr = GetSecurityInfo(hPipe, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, + &pOwner, NULL, NULL, NULL, &pSecDesc); + int const rc = dwErr == ERROR_SUCCESS ? rtLocalIpcWinVerifyUserSid(pOwner) + : RTErrConvertFromWin32(dwErr); + if (pSecDesc) + LocalFree(pSecDesc); + return rc; +} + + +/** Verifies that @a pSid is the current process token's user SID. */ +static int rtLocalIpcWinVerifyUserSid(PSID pSid) +{ + if (!pSid || !IsValidSid(pSid)) + return VERR_INVALID_PARAMETER; + + HANDLE hSelfToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hSelfToken)) + return RTErrConvertFromWin32(GetLastError()); + + PTOKEN_USER pSelfTokenUser = NULL; + int rc = rtLocalIpcWinQueryTokenUser(hSelfToken, &pSelfTokenUser); + if (RT_SUCCESS(rc) && !EqualSid(pSid, pSelfTokenUser->User.Sid)) + rc = VERR_ACCESS_DENIED; + + RTMemTmpFree(pSelfTokenUser); + CloseHandle(hSelfToken); + return rc; +} /** - * DACL for block all network access and local users other than the creator/owner. + * DACL blocking network access while permitting the creating logon session + * and LocalSystem. * * ACE format: (ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid) * - * Note! FILE_GENERIC_WRITE (SDDL_FILE_WRITE) is evil here because it includes + * Note! FILE_GENERIC_WRITE is evil here because it includes * the FILE_CREATE_PIPE_INSTANCE(=FILE_APPEND_DATA) flag. Thus the hardcoded * value 0x0012019b in the client ACE. The server-side still needs * setting FILE_CREATE_PIPE_INSTANCE although. @@ -188,36 +518,24 @@ static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE h * 0x00000004 - FILE_CREATE_PIPE_INSTANCE * = 0x0012019f * - * @todo Triple check this! - * @todo EVERYONE -> AUTHENTICATED USERS or something more appropriate? - * @todo Have trouble allowing the owner FILE_CREATE_PIPE_INSTANCE access, so for now I'm hacking - * it just to get progress - the service runs as local system. - * The CREATOR OWNER and PERSONAL SELF works (the former is only involved in inheriting - * it seems, which is why it won't work. The latter I've no idea about. Perhaps the solution - * is to go the annoying route of OpenProcessToken, QueryTokenInformation, - * ConvertSidToStringSid and then use the result... Suggestions are very welcome + * @returns NT status code. + * @param pDacl The initialized ACL to populate. + * @param pAccessSid The creating process token's logon SID. + * @param fServer Whether the ACL is for a server pipe end. */ -#define RTLOCALIPC_WIN_SDDL_BASE \ - SDDL_DACL SDDL_DELIMINATOR \ - SDDL_ACE_BEGIN SDDL_ACCESS_DENIED L";;" SDDL_GENERIC_ALL L";;;" SDDL_NETWORK SDDL_ACE_END \ - SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL L";;;" SDDL_LOCAL_SYSTEM SDDL_ACE_END -#define RTLOCALIPC_WIN_SDDL_SERVER \ - RTLOCALIPC_WIN_SDDL_BASE \ - SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019f" L";;;" SDDL_EVERYONE SDDL_ACE_END -#define RTLOCALIPC_WIN_SDDL_CLIENT \ - RTLOCALIPC_WIN_SDDL_BASE \ - SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019b" L";;;" SDDL_EVERYONE SDDL_ACE_END -static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, bool fServer) +static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, PSID pAccessSid, bool fServer) { + AssertReturn(pAccessSid && IsValidSid(pAccessSid), STATUS_INVALID_PARAMETER); + static SID_IDENTIFIER_AUTHORITY s_NtAuth = SECURITY_NT_AUTHORITY; union { SID Sid; uint8_t abPadding[SECURITY_MAX_SID_SIZE]; - } Network, LocalSystem, Everyone; + } Network, LocalSystem, OwnerRights; - /* 1. SDDL_ACCESS_DENIED L";;" SDDL_GENERIC_ALL L";;;" SDDL_NETWORK */ + /* 1. Deny all access from network logons. */ NTSTATUS rcNt = RtlInitializeSid(&Network.Sid, &s_NtAuth, 1); AssertReturn(NT_SUCCESS(rcNt), rcNt); *RtlSubAuthoritySid(&Network.Sid, 0) = SECURITY_NETWORK_RID; @@ -225,21 +543,24 @@ static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, bool fServer) rcNt = RtlAddAccessDeniedAce(pDacl, ACL_REVISION, GENERIC_ALL, &Network.Sid); AssertReturn(NT_SUCCESS(rcNt), rcNt); - /* 2. SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL L";;;" SDDL_LOCAL_SYSTEM */ - rcNt = RtlInitializeSid(&LocalSystem.Sid, &s_NtAuth, 1); + /* 2. Suppress the account owner's implicit WRITE_DAC access. */ + static SID_IDENTIFIER_AUTHORITY s_CreatorAuth = SECURITY_CREATOR_SID_AUTHORITY; + rcNt = RtlInitializeSid(&OwnerRights.Sid, &s_CreatorAuth, 1); AssertReturn(NT_SUCCESS(rcNt), rcNt); - *RtlSubAuthoritySid(&LocalSystem.Sid, 0) = SECURITY_LOCAL_SYSTEM_RID; + *RtlSubAuthoritySid(&OwnerRights.Sid, 0) = SECURITY_CREATOR_OWNER_RIGHTS_RID; - rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, FILE_ALL_ACCESS, &Network.Sid); + rcNt = RtlAddAccessDeniedAce(pDacl, ACL_REVISION, WRITE_DAC | WRITE_OWNER, &OwnerRights.Sid); AssertReturn(NT_SUCCESS(rcNt), rcNt); + /* 3. Grant LocalSystem full access. */ + rcNt = RtlInitializeSid(&LocalSystem.Sid, &s_NtAuth, 1); + AssertReturn(NT_SUCCESS(rcNt), rcNt); + *RtlSubAuthoritySid(&LocalSystem.Sid, 0) = SECURITY_LOCAL_SYSTEM_RID; - /* 3. server: SDDL_ACCESS_ALLOWED L";;" L"0x0012019f" L";;;" SDDL_EVERYONE - client: SDDL_ACCESS_ALLOWED L";;" L"0x0012019b" L";;;" SDDL_EVERYONE */ - rcNt = RtlInitializeSid(&Everyone.Sid, &s_NtAuth, 1); + rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, FILE_ALL_ACCESS, &LocalSystem.Sid); AssertReturn(NT_SUCCESS(rcNt), rcNt); - *RtlSubAuthoritySid(&Everyone.Sid, 0) = SECURITY_WORLD_RID; + /* 4. Grant the creating logon session the access required by this pipe end. */ DWORD const fAccess = FILE_READ_DATA /* 0x00000001 */ | FILE_WRITE_DATA /* 0x00000002 */ | FILE_CREATE_PIPE_INSTANCE * fServer /* 0x00000004 */ @@ -251,10 +572,10 @@ static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, bool fServer) | SYNCHRONIZE; /* 0x00100000*/ Assert(fAccess == (fServer ? 0x0012019fU : 0x0012019bU)); - rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, fAccess, &Network.Sid); + rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, fAccess, pAccessSid); AssertReturn(NT_SUCCESS(rcNt), rcNt); - return true; + return STATUS_SUCCESS; } @@ -265,78 +586,135 @@ static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, bool fServer) * @param ppDesc Where to store the allocated security descriptor on success. * Must be free'd using LocalFree(). * @param fServer Whether it's for a server or client instance. + * @param fRestrictToUser Whether to restrict access to the creating logon + * session and LocalSystem. */ -static int rtLocalIpcServerWinAllocSecurityDescriptor(PSECURITY_DESCRIPTOR *ppDesc, bool fServer) +static int rtLocalIpcServerWinAllocSecurityDescriptor(PSECURITY_DESCRIPTOR *ppDesc, bool fServer, + bool fRestrictToUser) { int rc; PSECURITY_DESCRIPTOR pSecDesc = NULL; -#if 0 - /* - * Resolve the API the first time around. - */ - static bool volatile s_fResolvedApis = false; - /** advapi32.dll API ConvertStringSecurityDescriptorToSecurityDescriptorW. */ - static decltype(ConvertStringSecurityDescriptorToSecurityDescriptorW) *s_pfnSSDLToSecDescW = NULL; - - if (!s_fResolvedApis) - { - s_pfnSSDLToSecDescW - = (decltype(s_pfnSSDLToSecDescW))RTLdrGetSystemSymbol("advapi32.dll", - "ConvertStringSecurityDescriptorToSecurityDescriptorW"); - ASMCompilerBarrier(); - s_fResolvedApis = true; - } - if (s_pfnSSDLToSecDescW) + if (!fRestrictToUser) { /* - * We'll create a security descriptor from a SDDL that denies - * access to network clients (this is local IPC after all), it - * makes some further restrictions to prevent non-authenticated - * users from screwing around. + * Preserve the legacy descriptor behavior. The initialized ACL was + * historically not attached to the descriptor, so Windows selected + * the creating token's default DACL. */ - PCRTUTF16 pwszSDDL = fServer ? RTLOCALIPC_WIN_SDDL_SERVER : RTLOCALIPC_WIN_SDDL_CLIENT; - ULONG cbSecDesc = 0; - SetLastError(0); - if (s_pfnSSDLToSecDescW(pwszSDDL, SDDL_REVISION_1, &pSecDesc, &cbSecDesc)) + uint32_t const cbAlloc = SECURITY_DESCRIPTOR_MIN_LENGTH * 2 + _8K; + pSecDesc = LocalAlloc(LMEM_FIXED, cbAlloc); + if (!pSecDesc) + return VERR_NO_MEMORY; + RT_BZERO(pSecDesc, cbAlloc); + + uint32_t const cbDacl = cbAlloc - SECURITY_DESCRIPTOR_MIN_LENGTH * 2; + PACL const pDacl = (PACL)((uint8_t *)pSecDesc + SECURITY_DESCRIPTOR_MIN_LENGTH * 2); + + if ( InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION) + && InitializeAcl(pDacl, cbDacl, ACL_REVISION)) { - DWORD dwErr = GetLastError(); RT_NOREF(dwErr); - AssertPtr(pSecDesc); *ppDesc = pSecDesc; return VINF_SUCCESS; } rc = RTErrConvertFromWin32(GetLastError()); + LocalFree(pSecDesc); + return rc; } - else -#endif + { /* * Manually construct the descriptor. * * This is a bit crude. The 8KB is probably 50+ times more than what we need. */ - uint32_t const cbAlloc = SECURITY_DESCRIPTOR_MIN_LENGTH * 2 + _8K; + HANDLE hSelfToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hSelfToken)) + return RTErrConvertFromWin32(GetLastError()); + + PTOKEN_USER pSelfTokenUser = NULL; + rc = rtLocalIpcWinQueryTokenUser(hSelfToken, &pSelfTokenUser); + PSID pSelfLogonSid = NULL; + if (RT_SUCCESS(rc)) + { + rc = rtLocalIpcWinQueryTokenLogonSid(hSelfToken, &pSelfLogonSid); + if ( rc == VERR_ACCESS_DENIED + && rtLocalIpcWinIsLocalSystemSid(pSelfTokenUser->User.Sid)) + { + DWORD const cbSystemSid = GetLengthSid(pSelfTokenUser->User.Sid); + pSelfLogonSid = (PSID)RTMemTmpAlloc(cbSystemSid); + if (!pSelfLogonSid) + rc = VERR_NO_TMP_MEMORY; + else if (CopySid(cbSystemSid, pSelfLogonSid, pSelfTokenUser->User.Sid)) + rc = VINF_SUCCESS; + else + { + rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pSelfLogonSid); + pSelfLogonSid = NULL; + } + } + } + CloseHandle(hSelfToken); + if (RT_FAILURE(rc)) + { + RTMemTmpFree(pSelfLogonSid); + RTMemTmpFree(pSelfTokenUser); + return rc; + } + + DWORD const cbOwnerSid = GetLengthSid(pSelfTokenUser->User.Sid); + AssertReturnStmt(cbOwnerSid > 0 && cbOwnerSid <= SECURITY_MAX_SID_SIZE, + RTMemTmpFree(pSelfLogonSid); RTMemTmpFree(pSelfTokenUser), VERR_INVALID_PARAMETER); + DWORD const cbLogonSid = GetLengthSid(pSelfLogonSid); + AssertReturnStmt(cbLogonSid > 0 && cbLogonSid <= SECURITY_MAX_SID_SIZE, + RTMemTmpFree(pSelfLogonSid); RTMemTmpFree(pSelfTokenUser), VERR_INVALID_PARAMETER); + + uint32_t const cbDacl = _8K; + uint32_t const cbAlloc = SECURITY_DESCRIPTOR_MIN_LENGTH * 2 + cbDacl + SECURITY_MAX_SID_SIZE * 2; pSecDesc = LocalAlloc(LMEM_FIXED, cbAlloc); if (!pSecDesc) + { + RTMemTmpFree(pSelfLogonSid); + RTMemTmpFree(pSelfTokenUser); return VERR_NO_MEMORY; + } RT_BZERO(pSecDesc, cbAlloc); - uint32_t const cbDacl = cbAlloc - SECURITY_DESCRIPTOR_MIN_LENGTH * 2; PACL const pDacl = (PACL)((uint8_t *)pSecDesc + SECURITY_DESCRIPTOR_MIN_LENGTH * 2); + PSID const pOwner = (PSID)((uint8_t *)pDacl + cbDacl); + PSID const pAccessSid = (PSID)((uint8_t *)pOwner + SECURITY_MAX_SID_SIZE); - if ( InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION) + if ( CopySid(SECURITY_MAX_SID_SIZE, pOwner, pSelfTokenUser->User.Sid) + && CopySid(SECURITY_MAX_SID_SIZE, pAccessSid, pSelfLogonSid) + && InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION) && InitializeAcl(pDacl, cbDacl, ACL_REVISION)) { - if (rtLocalIpcBuildDacl(pDacl, fServer)) + NTSTATUS rcNt = rtLocalIpcBuildDacl(pDacl, pAccessSid, fServer); + if (NT_SUCCESS(rcNt)) { - *ppDesc = pSecDesc; - return VINF_SUCCESS; + rcNt = RtlSetDaclSecurityDescriptor(pSecDesc, TRUE /*fDaclPresent*/, pDacl, FALSE /*fDaclDefaulted*/); + if ( NT_SUCCESS(rcNt) + && SetSecurityDescriptorOwner(pSecDesc, pOwner, FALSE /*bOwnerDefaulted*/)) + { + *ppDesc = pSecDesc; + RTMemTmpFree(pSelfLogonSid); + RTMemTmpFree(pSelfTokenUser); + return VINF_SUCCESS; + } + if (NT_SUCCESS(rcNt)) + rc = RTErrConvertFromWin32(GetLastError()); + else + rc = RTErrConvertFromNtStatus(rcNt); } - rc = VERR_GENERAL_FAILURE; + else + rc = RTErrConvertFromNtStatus(rcNt); } else rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pSelfLogonSid); + RTMemTmpFree(pSelfTokenUser); LocalFree(pSecDesc); } return rc; @@ -352,19 +730,22 @@ static int rtLocalIpcServerWinAllocSecurityDescriptor(PSECURITY_DESCRIPTOR *ppDe * @param phNmPipe Where to store the named pipe handle on success. * This will be set to INVALID_HANDLE_VALUE on failure. * @param pwszPipeName The named pipe name, full, UTF-16 encoded. + * @param fFlags The RTLOCALIPC_FLAGS_* used to create the server. * @param fFirst Set on the first call (from RTLocalIpcServerCreate), * otherwise clear. Governs the * FILE_FLAG_FIRST_PIPE_INSTANCE flag. */ -static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, PCRTUTF16 pwszPipeName, bool fFirst) +static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, PCRTUTF16 pwszPipeName, uint32_t fFlags, bool fFirst) { *phNmPipe = INVALID_HANDLE_VALUE; /* - * Create a security descriptor blocking access to the pipe via network. + * Create the legacy descriptor or, when requested, one blocking network + * and other-user access. */ PSECURITY_DESCRIPTOR pSecDesc; - int rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, fFirst /* Server? */); + int rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, fFirst /* Server? */, + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { #if 0 @@ -446,15 +827,20 @@ static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, PCRTUTF16 pws * @param pszName The name to validate. * @param pcwcFullName Where to return the UTF-16 length of the full name. * @param fNative Whether it's a native name or a portable name. + * @param fRestrictToUser Whether portable names use the login-session + * namespace. */ -static int rtLocalIpcWinValidateName(const char *pszName, size_t *pcwcFullName, bool fNative) +static int rtLocalIpcWinValidateName(const char *pszName, size_t *pcwcFullName, bool fNative, bool fRestrictToUser) { AssertPtrReturn(pszName, VERR_INVALID_POINTER); AssertReturn(*pszName, VERR_INVALID_NAME); if (!fNative) { - size_t cwcName = RT_ELEMENTS(RTLOCALIPC_WIN_PREFIX) - 1; + size_t cwcName = fRestrictToUser ? RT_ELEMENTS(RTLOCALIPC_WIN_USER_PREFIX) - 1 + : RT_ELEMENTS(RTLOCALIPC_WIN_PREFIX) - 1; + if (fRestrictToUser) + cwcName += RTLOCALIPC_WIN_SESSION_ID_CWC + RTLOCALIPC_WIN_LOGON_ID_CWC; for (;;) { char ch = *pszName++; @@ -488,16 +874,49 @@ static int rtLocalIpcWinValidateName(const char *pszName, size_t *pcwcFullName, * @param cwcFullName The output buffer size excluding the terminator. * @param fNative Whether the user supplied name is a native or * portable one. + * @param fRestrictToUser Whether portable names use the login-session + * namespace. */ -static int rtLocalIpcWinConstructName(const char *pszName, PRTUTF16 pwszFullName, size_t cwcFullName, bool fNative) +static int rtLocalIpcWinConstructName(const char *pszName, PRTUTF16 pwszFullName, size_t cwcFullName, + bool fNative, bool fRestrictToUser) { if (!fNative) { - static RTUTF16 const s_wszPrefix[] = RTLOCALIPC_WIN_PREFIX; - Assert(cwcFullName * sizeof(RTUTF16) > sizeof(s_wszPrefix)); - memcpy(pwszFullName, s_wszPrefix, sizeof(s_wszPrefix)); - cwcFullName -= RT_ELEMENTS(s_wszPrefix) - 1; - pwszFullName += RT_ELEMENTS(s_wszPrefix) - 1; + static RTUTF16 const s_wszPrefix[] = RTLOCALIPC_WIN_PREFIX; + static RTUTF16 const s_wszUserPrefix[] = RTLOCALIPC_WIN_USER_PREFIX; + PCRTUTF16 const pwszPrefix = fRestrictToUser ? s_wszUserPrefix : s_wszPrefix; + size_t const cwcPrefix = fRestrictToUser ? RT_ELEMENTS(s_wszUserPrefix) - 1 + : RT_ELEMENTS(s_wszPrefix) - 1; + Assert(cwcFullName > cwcPrefix); + memcpy(pwszFullName, pwszPrefix, cwcPrefix * sizeof(RTUTF16)); + cwcFullName -= cwcPrefix; + pwszFullName += cwcPrefix; + + if (fRestrictToUser) + { + uint32_t idSession = 0; + int rc = rtLocalIpcWinQuerySelfSessionId(&idSession); + if (RT_FAILURE(rc)) + return rc; + uint32_t idLogonHigh = 0; + uint32_t idLogonLow = 0; + rc = rtLocalIpcWinQuerySelfLogonId(&idLogonHigh, &idLogonLow); + if (RT_FAILURE(rc)) + return rc; + AssertReturn(cwcFullName >= RTLOCALIPC_WIN_SESSION_ID_CWC, VERR_BUFFER_OVERFLOW); + ssize_t const cwcSession = RTUtf16Printf(pwszFullName, RTLOCALIPC_WIN_SESSION_ID_CWC + 1, + "%08RX32-", idSession); + AssertReturn(cwcSession == RTLOCALIPC_WIN_SESSION_ID_CWC, VERR_INTERNAL_ERROR); + cwcFullName -= RTLOCALIPC_WIN_SESSION_ID_CWC; + pwszFullName += RTLOCALIPC_WIN_SESSION_ID_CWC; + + AssertReturn(cwcFullName >= RTLOCALIPC_WIN_LOGON_ID_CWC, VERR_BUFFER_OVERFLOW); + ssize_t const cwcLogon = RTUtf16Printf(pwszFullName, RTLOCALIPC_WIN_LOGON_ID_CWC + 1, + "%08RX32%08RX32-", idLogonHigh, idLogonLow); + AssertReturn(cwcLogon == RTLOCALIPC_WIN_LOGON_ID_CWC, VERR_INTERNAL_ERROR); + cwcFullName -= RTLOCALIPC_WIN_LOGON_ID_CWC; + pwszFullName += RTLOCALIPC_WIN_LOGON_ID_CWC; + } } return RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszFullName, cwcFullName + 1, NULL); } @@ -512,7 +931,9 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa *phServer = NIL_RTLOCALIPCSERVER; AssertReturn(!(fFlags & ~RTLOCALIPC_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); size_t cwcFullName; - int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { /* @@ -523,10 +944,13 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa AssertReturn(pThis, VERR_NO_MEMORY); pThis->u32Magic = RTLOCALIPCSERVER_MAGIC; + pThis->fFlags = fFlags; pThis->cRefs = 1; /* the one we return */ pThis->fCancelled = false; - rc = rtLocalIpcWinConstructName(pszName, pThis->wszName, cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + rc = rtLocalIpcWinConstructName(pszName, pThis->wszName, cwcFullName, + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { rc = RTCritSectInit(&pThis->CritSect); @@ -540,7 +964,8 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa pThis->OverlappedIO.Internal = STATUS_PENDING; pThis->OverlappedIO.hEvent = pThis->hEvent; - rc = rtLocalIpcServerWinCreatePipeInstance(&pThis->hNmPipe, pThis->wszName, true /* fFirst */); + rc = rtLocalIpcServerWinCreatePipeInstance(&pThis->hNmPipe, pThis->wszName, pThis->fFlags, + true /* fFirst */); if (RT_SUCCESS(rc)) { *phServer = pThis; @@ -743,12 +1168,13 @@ RTDECL(int) RTLocalIpcServerListen(RTLOCALIPCSERVER hServer, PRTLOCALIPCSESSION || dwErr == ERROR_PIPE_CONNECTED) { HANDLE hNmPipe; - rc = rtLocalIpcServerWinCreatePipeInstance(&hNmPipe, pThis->wszName, false /* fFirst */); + rc = rtLocalIpcServerWinCreatePipeInstance(&hNmPipe, pThis->wszName, pThis->fFlags, false /* fFirst */); if (RT_SUCCESS(rc)) { HANDLE hNmPipeSession = pThis->hNmPipe; /* consumed */ pThis->hNmPipe = hNmPipe; - rc = rtLocalIpcWinCreateSession(phClientSession, hNmPipeSession); + rc = rtLocalIpcWinCreateSession(phClientSession, hNmPipeSession, + RT_BOOL(pThis->fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); } else { @@ -760,6 +1186,16 @@ RTDECL(int) RTLocalIpcServerListen(RTLOCALIPCSERVER hServer, PRTLOCALIPCSESSION AssertMsg(fRc, ("%d\n", GetLastError())); } } + else if ( (pThis->fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER) + && rtLocalIpcWinIsPeerGoneError(dwErr)) + { + fRc = DisconnectNamedPipe(pThis->hNmPipe); + DWORD const dwDisconnectErr = fRc ? ERROR_SUCCESS : GetLastError(); + if (fRc || rtLocalIpcWinIsPeerGoneError(dwDisconnectErr)) + rc = VERR_TRY_AGAIN; + else + rc = RTErrConvertFromWin32(dwDisconnectErr); + } else rc = RTErrConvertFromWin32(dwErr); } @@ -844,15 +1280,29 @@ RTDECL(int) RTLocalIpcServerCancel(RTLOCALIPCSERVER hServer) * INVALID_HANDLE_VALUE if client connect. This will * be consumed by this session, meaning on failure to * create the session it will be closed. + * @param fRestricted Whether to enforce matching Windows session IDs. */ -static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession) +static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession, bool fRestricted) { AssertPtr(ppSession); + int rc; + if (fRestricted && hNmPipeSession != INVALID_HANDLE_VALUE) + { + rc = rtLocalIpcWinVerifyPeerSession(hNmPipeSession, true /*fServerSide*/); + if (RT_FAILURE(rc)) + { + BOOL const fRc = CloseHandle(hNmPipeSession); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + if (rc == VERR_ACCESS_DENIED) + rc = VERR_TRY_AGAIN; + return rc; + } + } + /* * Allocate and initialize the session instance data. */ - int rc; PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)RTMemAllocZ(sizeof(*pThis)); if (pThis) { @@ -861,6 +1311,7 @@ static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE h pThis->fCancelled = false; pThis->fZeroByteRead = false; pThis->fServerSide = hNmPipeSession != INVALID_HANDLE_VALUE; + pThis->fRestricted = fRestricted; pThis->hNmPipe = hNmPipeSession; #if 0 /* Non-blocking writes are not yet supported. */ pThis->pbBounceBuf = NULL; @@ -920,27 +1371,31 @@ RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *p AssertReturn(!(fFlags & ~RTLOCALIPC_C_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); size_t cwcFullName; - int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { /* * Create a session (shared with server client session creation). */ PRTLOCALIPCSESSIONINT pThis; - rc = rtLocalIpcWinCreateSession(&pThis, INVALID_HANDLE_VALUE); + rc = rtLocalIpcWinCreateSession(&pThis, INVALID_HANDLE_VALUE, + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { /* * Try open the pipe. */ PSECURITY_DESCRIPTOR pSecDesc; - rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, false /*fServer*/); + rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, false /*fServer*/, false /*fRestrictToUser*/); if (RT_SUCCESS(rc)) { PRTUTF16 pwszFullName = RTUtf16Alloc((cwcFullName + 1) * sizeof(RTUTF16)); if (pwszFullName) rc = rtLocalIpcWinConstructName(pszName, pwszFullName, cwcFullName, - RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)); else rc = VERR_NO_UTF16_MEMORY; if (RT_SUCCESS(rc)) @@ -950,30 +1405,45 @@ RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *p SecAttrs.lpSecurityDescriptor = pSecDesc; SecAttrs.bInheritHandle = FALSE; - /* The SECURITY_XXX flags are needed in order to prevent the server from impersonating with - this thread's security context (supported at least back to NT 3.51). See @bugref{9773}. */ + /* Default to an anonymous security context. Callers that explicitly need same-user + verification may permit the server to identify, but not impersonate, the client. */ + DWORD const fSecurityQos = fFlags & RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + ? SECURITY_IDENTIFICATION : SECURITY_ANONYMOUS; HANDLE hPipe = CreateFileW(pwszFullName, GENERIC_READ | GENERIC_WRITE, 0 /*no sharing*/, &SecAttrs, OPEN_EXISTING, - FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, + FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | fSecurityQos, NULL /*no template handle*/); if (hPipe != INVALID_HANDLE_VALUE) { - pThis->hNmPipe = hPipe; + if (!(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)) + rc = VINF_SUCCESS; + else + rc = rtLocalIpcWinVerifyPeerSession(hPipe, false /*fServerSide*/); + if ( RT_SUCCESS(rc) + && (fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)) + rc = rtLocalIpcWinVerifyPipeOwnerUser(hPipe); + if (RT_SUCCESS(rc)) + { + pThis->hNmPipe = hPipe; - LocalFree(pSecDesc); - RTUtf16Free(pwszFullName); + LocalFree(pSecDesc); + RTUtf16Free(pwszFullName); - /* - * We're done! - */ - *phSession = pThis; - return VINF_SUCCESS; - } + /* + * We're done! + */ + *phSession = pThis; + return VINF_SUCCESS; + } - rc = RTErrConvertFromWin32(GetLastError()); + BOOL const fRc = CloseHandle(hPipe); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + } + else + rc = RTErrConvertFromWin32(GetLastError()); } RTUtf16Free(pwszFullName); @@ -1765,8 +2235,105 @@ RTDECL(int) RTLocalIpcSessionCancel(RTLOCALIPCSESSION hSession) RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS pProcess) { - RT_NOREF_PV(hSession); RT_NOREF_PV(pProcess); - return VERR_NOT_SUPPORTED; + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + *pProcess = NIL_RTPROCESS; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (!pThis->fCancelled) + { + rc = RTOnce(&g_rtLocalIpcWinQueryProcessResolveOnce, rtLocalIpcWinQueryProcessResolveOnce, NULL); + if (RT_SUCCESS(rc)) + { + ULONG idProcess = 0; + BOOL const fRc = pThis->fServerSide + ? g_pfnGetNamedPipeClientProcessId(pThis->hNmPipe, &idProcess) + : g_pfnGetNamedPipeServerProcessId(pThis->hNmPipe, &idProcess); + if (fRc) + { + *pProcess = (RTPROCESS)idProcess; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else + rc = VERR_CANCELLED; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionVerifySameUser(RTLOCALIPCSESSION hSession) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (!pThis->fCancelled) + { + if (pThis->fRestricted) + { + rc = rtLocalIpcWinVerifyPeerSession(pThis->hNmPipe, pThis->fServerSide); + if (RT_FAILURE(rc)) + { + rtLocalIpcSessionReleaseAndUnlock(pThis); + return rc; + } + } + + if (pThis->fServerSide) + { + HANDLE hPeerToken = NULL; + if (ImpersonateNamedPipeClient(pThis->hNmPipe)) + { + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE /*OpenAsSelf*/, &hPeerToken)) + rc = VERR_ACCESS_DENIED; + + if (!RevertToSelf()) + { + DWORD const dwErr = GetLastError(); + BOOL const fCleared = SetThreadToken(NULL, NULL); + AssertMsg(fCleared, ("SetThreadToken failed: %u (RevertToSelf: %u)\n", GetLastError(), dwErr)); + RT_NOREF(fCleared); + rc = RTErrConvertFromWin32(dwErr); + } + + if (RT_SUCCESS(rc)) + { + PTOKEN_USER pPeerTokenUser = NULL; + rc = rtLocalIpcWinQueryTokenUser(hPeerToken, &pPeerTokenUser); + if (RT_SUCCESS(rc)) + rc = rtLocalIpcWinVerifyUserSid(pPeerTokenUser->User.Sid); + RTMemTmpFree(pPeerTokenUser); + } + } + else + rc = VERR_ACCESS_DENIED; + + if (hPeerToken != NULL) + CloseHandle(hPeerToken); + } + else + rc = rtLocalIpcWinVerifyPipeOwnerUser(pThis->hNmPipe); + } + else + rc = VERR_CANCELLED; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + return rc; } @@ -1782,4 +2349,3 @@ RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGi RT_NOREF_PV(hSession); RT_NOREF_PV(pGid); return VERR_NOT_SUPPORTED; } - diff --git a/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp index 66dbb8487d62..224523f8de4e 100644 --- a/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp +++ b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp @@ -1,4 +1,4 @@ -/* $Id: tstRTLocalIpc.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: tstRTLocalIpc.cpp 114874 2026-08-06 21:28:04Z andreas.loeffler@oracle.com $ */ /** @file * IPRT Testcase - RTLocalIpc API. */ @@ -41,8 +41,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -53,6 +55,13 @@ #include #include #include +#include + +#ifdef RT_OS_WINDOWS +# include +#else +# include +#endif /********************************************************************************************************************************* @@ -98,6 +107,9 @@ static void testBasics(void) RTTESTI_CHECK_RC(RTLocalIpcSessionCancel(NULL), VERR_INVALID_HANDLE); RTTESTI_CHECK_RC(RTLocalIpcSessionClose(NULL), VINF_SUCCESS); + RTPROCESS Process = NIL_RTPROCESS; + RTTESTI_CHECK_RC(RTLocalIpcSessionQueryProcess(NULL, &Process), VERR_INVALID_HANDLE); + RTTESTI_CHECK_RC(RTLocalIpcSessionVerifySameUser(NULL), VERR_INVALID_HANDLE); /* Basic client creation / destruction. */ RTTESTI_CHECK_RC_RETV(rc = RTLocalIpcSessionConnect(&hIpcSession, "BasicTest", 0), VERR_FILE_NOT_FOUND); @@ -107,6 +119,262 @@ static void testBasics(void) } +#ifndef RT_OS_WINDOWS +static void testRestrictedNamespaceProperties(void) +{ + RTTestISub("Restricted namespace properties"); + + char szUserDir[] = "/tmp/tstRTLocalIpc-XXXXXX"; + int rc = RTDirCreateTemp(szUserDir, 0700); + RTTESTI_CHECK_RC_RETV(rc, VINF_SUCCESS); + + char *pszSavedRuntimeDir = RTEnvDupEx(RTENV_DEFAULT, "XDG_RUNTIME_DIR"); + rc = RTEnvSet("XDG_RUNTIME_DIR", szUserDir); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + RTLOCALIPCSERVER hIpcServer = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcNamespace", + RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + char szSocket[RTPATH_MAX]; + int rcPath = RTStrCopy(szSocket, sizeof(szSocket), szUserDir); + if (RT_SUCCESS(rcPath)) + rcPath = RTPathAppend(szSocket, sizeof(szSocket), ".iprt-tstRTLocalIpcNamespace"); + RTTESTI_CHECK_RC(rcPath, VINF_SUCCESS); + if (RT_SUCCESS(rc) && RT_SUCCESS(rcPath)) + { + RTFSOBJINFO ObjInfo; + RTTESTI_CHECK_RC(rcPath = RTPathQueryInfoEx(szSocket, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), + VINF_SUCCESS); + if (RT_SUCCESS(rcPath)) + { + RTTESTI_CHECK(RTFS_IS_SOCKET(ObjInfo.Attr.fMode)); + RTTESTI_CHECK((ObjInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS) == (RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR)); + RTTESTI_CHECK(ObjInfo.Attr.u.Unix.uid == (RTUID)geteuid()); + } + } + + if (hIpcServer != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED); + if (RT_SUCCESS(rcPath)) + { + RTFSOBJINFO ObjInfo; + rcPath = RTPathQueryInfoEx(szSocket, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + RTTESTI_CHECK(rcPath == VERR_FILE_NOT_FOUND || rcPath == VERR_PATH_NOT_FOUND); + } + + /* An unsafe runtime directory must be ignored in favor of a protected + fallback namespace. */ + char szFallbackHome[] = "/tmp/tstRTLocalIpc-home-XXXXXX"; + RTTESTI_CHECK_RC(rc = RTDirCreateTemp(szFallbackHome, 0700), VINF_SUCCESS); + char *pszSavedHome = RTEnvDupEx(RTENV_DEFAULT, "HOME"); + if (RT_SUCCESS(rc)) + RTTESTI_CHECK_RC(rc = RTEnvSet("HOME", szFallbackHome), VINF_SUCCESS); + RTTESTI_CHECK_RC(rc = RTPathSetMode(szUserDir, RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO), + VINF_SUCCESS); + if (RT_SUCCESS(rc)) + RTTESTI_CHECK_RC(rc = RTEnvSet("XDG_RUNTIME_DIR", szUserDir), VINF_SUCCESS); + hIpcServer = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcUnsafeNamespace", + RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + char szUnsafeSocket[RTPATH_MAX]; + int rcUnsafe = RTStrCopy(szUnsafeSocket, sizeof(szUnsafeSocket), szUserDir); + if (RT_SUCCESS(rcUnsafe)) + rcUnsafe = RTPathAppend(szUnsafeSocket, sizeof(szUnsafeSocket), ".iprt-tstRTLocalIpcUnsafeNamespace"); + RTTESTI_CHECK_RC(rcUnsafe, VINF_SUCCESS); + if (RT_SUCCESS(rcUnsafe)) + { + RTFSOBJINFO ObjInfo; + rcUnsafe = RTPathQueryInfoEx(szUnsafeSocket, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + RTTESTI_CHECK(rcUnsafe == VERR_FILE_NOT_FOUND || rcUnsafe == VERR_PATH_NOT_FOUND); + } + if (hIpcServer != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED); + RTTESTI_CHECK_RC(RTPathSetMode(szUserDir, RTFS_UNIX_IRWXU), VINF_SUCCESS); + if (pszSavedHome) + RTTESTI_CHECK_RC(RTEnvSet("HOME", pszSavedHome), VINF_SUCCESS); + else + RTTESTI_CHECK_RC(RTEnvUnset("HOME"), VINF_SUCCESS); + RTStrFree(pszSavedHome); + char szFallbackNamespace[RTPATH_MAX]; + RTTESTI_CHECK_RC(rcUnsafe = RTStrCopy(szFallbackNamespace, sizeof(szFallbackNamespace), szFallbackHome), VINF_SUCCESS); + if (RT_SUCCESS(rcUnsafe)) + RTTESTI_CHECK_RC(rcUnsafe = RTPathAppend(szFallbackNamespace, sizeof(szFallbackNamespace), ".iprt-localipc"), + VINF_SUCCESS); + if (RT_SUCCESS(rcUnsafe)) + RTTESTI_CHECK_RC(RTDirRemove(szFallbackNamespace), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRemove(szFallbackHome), VINF_SUCCESS); + + if (pszSavedRuntimeDir) + RTTESTI_CHECK_RC(RTEnvSet("XDG_RUNTIME_DIR", pszSavedRuntimeDir), VINF_SUCCESS); + else + RTTESTI_CHECK_RC(RTEnvUnset("XDG_RUNTIME_DIR"), VINF_SUCCESS); + RTStrFree(pszSavedRuntimeDir); + RTTESTI_CHECK_RC(RTDirRemove(szUserDir), VINF_SUCCESS); +} +#endif /* !RT_OS_WINDOWS */ + + +#ifdef RT_OS_WINDOWS +static void testRestrictedNamespaceProperties(void) +{ + RTTestISub("Restricted namespace properties"); + + HANDLE hToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + { + RTTestIFailed("OpenProcessToken failed: %u", GetLastError()); + return; + } + + DWORD idSession = 0; + DWORD cbTokenInfo = 0; + if (!GetTokenInformation(hToken, TokenSessionId, &idSession, sizeof(idSession), &cbTokenInfo)) + { + DWORD const dwErr = GetLastError(); + CloseHandle(hToken); + RTTestIFailed("Querying TokenSessionId failed: %u", dwErr); + return; + } + + TOKEN_STATISTICS TokenStats; + cbTokenInfo = 0; + if (!GetTokenInformation(hToken, TokenStatistics, &TokenStats, sizeof(TokenStats), &cbTokenInfo)) + { + DWORD const dwErr = GetLastError(); + CloseHandle(hToken); + RTTestIFailed("Querying TokenStatistics failed: %u", dwErr); + return; + } + CloseHandle(hToken); + uint32_t const idLogonHigh = (uint32_t)TokenStats.AuthenticationId.HighPart; + uint32_t const idLogonLow = TokenStats.AuthenticationId.LowPart; + + char szName[128]; + ssize_t cch = RTStrPrintf2(szName, sizeof(szName), "tstRTLocalIpcNamespace-%RU32", (uint32_t)RTProcSelf()); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szName)); + + char szOldName[RTPATH_MAX]; + cch = RTStrPrintf2(szOldName, sizeof(szOldName), "\\\\.\\pipe\\LOCAL\\IPRT-%s", szName); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szOldName)); + + char szOtherSessionName[RTPATH_MAX]; + cch = RTStrPrintf2(szOtherSessionName, sizeof(szOtherSessionName), + "\\\\.\\pipe\\LOCAL\\IPRT-%08RX32-%08RX32%08RX32-%s", + (uint32_t)idSession ^ RT_BIT_32(31), idLogonHigh, idLogonLow, szName); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szOtherSessionName)); + + char szOtherLogonName[RTPATH_MAX]; + cch = RTStrPrintf2(szOtherLogonName, sizeof(szOtherLogonName), + "\\\\.\\pipe\\LOCAL\\IPRT-%08RX32-%08RX32%08RX32-%s", + (uint32_t)idSession, idLogonHigh ^ RT_BIT_32(31), idLogonLow, szName); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szOtherLogonName)); + + char szExpectedName[RTPATH_MAX]; + cch = RTStrPrintf2(szExpectedName, sizeof(szExpectedName), + "\\\\.\\pipe\\LOCAL\\IPRT-%08RX32-%08RX32%08RX32-%s", + (uint32_t)idSession, idLogonHigh, idLogonLow, szName); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szExpectedName)); + + RTLOCALIPCSERVER hOldName = NIL_RTLOCALIPCSERVER; + int rc = RTLocalIpcServerCreate(&hOldName, szOldName, RTLOCALIPC_FLAGS_NATIVE_NAME); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + RTLOCALIPCSERVER hOtherSession = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hOtherSession, szOtherSessionName, RTLOCALIPC_FLAGS_NATIVE_NAME); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + RTLOCALIPCSERVER hOtherLogon = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hOtherLogon, szOtherLogonName, RTLOCALIPC_FLAGS_NATIVE_NAME); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + RTLOCALIPCSERVER hRestricted = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hRestricted, szName, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + RTLOCALIPCSERVER hExpectedCollision = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + int const rcCollision = RTLocalIpcServerCreate(&hExpectedCollision, szExpectedName, + RTLOCALIPC_FLAGS_NATIVE_NAME); + RTTESTI_CHECK_RC(rcCollision, VERR_ACCESS_DENIED); + } + + if (hRestricted != NIL_RTLOCALIPCSERVER) + { + PRTUTF16 pwszExpectedName = NULL; + int rcRaw = RTStrToUtf16(szExpectedName, &pwszExpectedName); + RTTESTI_CHECK_RC(rcRaw, VINF_SUCCESS); + if (RT_SUCCESS(rcRaw)) + { + HANDLE hVanishedClient = CreateFileW((LPCWSTR)pwszExpectedName, + GENERIC_READ | GENERIC_WRITE, + 0 /*dwShareMode*/, NULL /*pSecurityAttributes*/, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, + NULL /*hTemplateFile*/); + if (hVanishedClient == INVALID_HANDLE_VALUE) + RTTestIFailed("Opening the raw restricted pipe failed: %u", GetLastError()); + else + { + RTTESTI_CHECK(CloseHandle(hVanishedClient)); + + RTLOCALIPCSESSION hGoneSession = NIL_RTLOCALIPCSESSION; + int const rcGone = RTLocalIpcServerListen(hRestricted, &hGoneSession); + RTTESTI_CHECK(rcGone == VERR_TRY_AGAIN || rcGone == VINF_SUCCESS); + if (hGoneSession != NIL_RTLOCALIPCSESSION) + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hGoneSession), VINF_OBJECT_DESTROYED); + + RTLOCALIPCSESSION hClientSession = NIL_RTLOCALIPCSESSION; + int const rcClient = RTLocalIpcSessionConnect(&hClientSession, szName, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + RTTESTI_CHECK_RC(rcClient, VINF_SUCCESS); + if (RT_SUCCESS(rcClient)) + { + RTLOCALIPCSESSION hServerSession = NIL_RTLOCALIPCSESSION; + int const rcServer = RTLocalIpcServerListen(hRestricted, &hServerSession); + RTTESTI_CHECK_RC(rcServer, VINF_SUCCESS); + if (hServerSession != NIL_RTLOCALIPCSESSION) + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hServerSession), VINF_OBJECT_DESTROYED); + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED); + } + } + } + RTUtf16Free(pwszExpectedName); + } + + if (hExpectedCollision != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hExpectedCollision), VINF_OBJECT_DESTROYED); + if (hRestricted != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hRestricted), VINF_OBJECT_DESTROYED); + if (hOtherLogon != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hOtherLogon), VINF_OBJECT_DESTROYED); + if (hOtherSession != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hOtherSession), VINF_OBJECT_DESTROYED); + if (hOldName != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hOldName), VINF_OBJECT_DESTROYED); +} +#endif /* RT_OS_WINDOWS */ + + /********************************************************************************************************************************* * * @@ -114,9 +382,25 @@ static void testBasics(void) * * *********************************************************************************************************************************/ +typedef struct TESTSERVERLISTENARGS +{ + /** The server handle. */ + RTLOCALIPCSERVER hIpcServer; + /** Whether the client is expected to be a different process. */ + bool fExpectOtherProcess; +} TESTSERVERLISTENARGS; +/** Pointer to server listener thread arguments. */ +typedef TESTSERVERLISTENARGS *PTESTSERVERLISTENARGS; + +/** The connection worker runs in a different process. */ +#define TEST_CONNECTION_F_OTHER_PROCESS RT_BIT_32(0) +/** The connection worker must use the protected user namespace. */ +#define TEST_CONNECTION_F_RESTRICTED RT_BIT_32(1) + + static DECLCALLBACK(int) testServerListenThread(RTTHREAD hSelf, void *pvUser) { - RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser; + PTESTSERVERLISTENARGS pArgs = (PTESTSERVERLISTENARGS)pvUser; RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck); RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf)); @@ -125,11 +409,38 @@ static DECLCALLBACK(int) testServerListenThread(RTTHREAD hSelf, void *pvUser) for (;;) { RTLOCALIPCSESSION hIpcSession; - rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession); + rc = RTLocalIpcServerListen(pArgs->hIpcServer, &hIpcSession); if (RT_SUCCESS(rc)) { RTThreadSleep(8); /* windows output fudge (purely esthetical) */ RTTestIPrintf(RTTESTLVL_INFO, "testServerListenThread: Got new client connection.\n"); + uint8_t bIdentity = 0; + RTTESTI_CHECK_RC(rc = RTLocalIpcSessionRead(hIpcSession, &bIdentity, sizeof(bIdentity), NULL), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK(bIdentity == UINT8_C(0x42)); +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) \ + || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) || defined(RT_OS_SOLARIS) + RTTESTI_CHECK_RC(RTLocalIpcSessionVerifySameUser(hIpcSession), VINF_SUCCESS); +#else + RTTESTI_CHECK_RC(RTLocalIpcSessionVerifySameUser(hIpcSession), VERR_NOT_SUPPORTED); +#endif + } +#ifdef RT_OS_WINDOWS + RTPROCESS Process = NIL_RTPROCESS; + RTTESTI_CHECK_RC(rc = RTLocalIpcSessionQueryProcess(hIpcSession, &Process), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + if (pArgs->fExpectOtherProcess) + RTTESTI_CHECK(Process != NIL_RTPROCESS && Process != RTProcSelf()); + else + RTTESTI_CHECK(Process == RTProcSelf()); + + RTPROCESS ProcessAgain = NIL_RTPROCESS; + RTTESTI_CHECK_RC(RTLocalIpcSessionQueryProcess(hIpcSession, &ProcessAgain), VINF_SUCCESS); + RTTESTI_CHECK(ProcessAgain == Process); + } +#endif RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED); } else @@ -148,12 +459,46 @@ static DECLCALLBACK(int) testServerListenThread(RTTHREAD hSelf, void *pvUser) static DECLCALLBACK(int) tstRTLocalIpcSessionConnectionChild(RTTHREAD hSelf, void *pvUser) { RTLOCALIPCSESSION hClientSession; - RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser); + RT_NOREF_PV(hSelf); + + uintptr_t const fWorker = (uintptr_t)pvUser; + uint32_t fConnect = RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION; + if (fWorker & TEST_CONNECTION_F_RESTRICTED) + fConnect |= RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER; RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck); - RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionConnection",0 /* Flags */), + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionConnection", + fConnect), VINF_SUCCESS, rcCheck); + uint8_t const bIdentity = UINT8_C(0x42); + RTTEST_CHECK_RC_OK_RET(g_hTest, RTLocalIpcSessionWrite(hClientSession, &bIdentity, sizeof(bIdentity)), rcCheck); + RTTEST_CHECK_RC_OK_RET(g_hTest, RTLocalIpcSessionFlush(hClientSession), rcCheck); +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) \ + || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) || defined(RT_OS_SOLARIS) + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionVerifySameUser(hClientSession), VINF_SUCCESS, rcCheck); +#else + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionVerifySameUser(hClientSession), VERR_NOT_SUPPORTED, rcCheck); +#endif +#ifdef RT_OS_WINDOWS + RTPROCESS ProcessExpected = RTProcSelf(); + if (fWorker & TEST_CONNECTION_F_OTHER_PROCESS) + RTTEST_CHECK_RC_OK_RET(g_hTest, RTProcQueryParent(RTProcSelf(), &ProcessExpected), rcCheck); + + RTPROCESS Process = NIL_RTPROCESS; + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionQueryProcess(hClientSession, &Process), VINF_SUCCESS, rcCheck); + RTTEST_CHECK_RET(g_hTest, Process == ProcessExpected, VERR_GENERAL_FAILURE); + RTPROCESS ProcessAgain = NIL_RTPROCESS; + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionQueryProcess(hClientSession, &ProcessAgain), VINF_SUCCESS, rcCheck); + RTTEST_CHECK_RET(g_hTest, ProcessAgain == Process, VERR_GENERAL_FAILURE); +#else + RT_NOREF_PV(pvUser); +#endif +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) \ + || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) || defined(RT_OS_SOLARIS) + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionCancel(hClientSession), VINF_SUCCESS, rcCheck); + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionVerifySameUser(hClientSession), VERR_CANCELLED, rcCheck); +#endif RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED, rcCheck); @@ -161,23 +506,93 @@ static DECLCALLBACK(int) tstRTLocalIpcSessionConnectionChild(RTTHREAD hSelf, voi } -static void testSessionConnection(const char *pszExecPath) +static void testSessionConnection(const char *pszExecPath, uint32_t fServerFlags) { - RTTestISub(!pszExecPath ? "Connect from thread" : "Connect from child"); + if (fServerFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER) + RTTestISub(!pszExecPath ? "Restricted connect from thread" : "Restricted connect from child"); + else + RTTestISub(!pszExecPath ? "Connect from thread" : "Connect from child"); + + /* Occupy the legacy global endpoint while testing the protected namespace. */ + bool const fRestricted = RT_BOOL(fServerFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + int rc; +#ifdef RT_OS_WINDOWS + RTLOCALIPCSERVER hSquatter = NIL_RTLOCALIPCSERVER; + if (fRestricted) + RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hSquatter, "tstRTLocalIpcSessionConnection", 0 /*fFlags*/), + VINF_SUCCESS); +#else + char szSquatter[RTPATH_MAX]; + szSquatter[0] = '\0'; + if (fRestricted) + { + RTTESTI_CHECK(RTStrPrintf(szSquatter, sizeof(szSquatter), "/tmp/.iprt-localipc-%s", + "tstRTLocalIpcSessionConnection") > 0); + int const rcUnlink = RTPathUnlink(szSquatter, 0 /*fUnlink*/); + RTTESTI_CHECK(rcUnlink == VINF_SUCCESS || rcUnlink == VERR_FILE_NOT_FOUND || rcUnlink == VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC_RETV(RTDirCreate(szSquatter, 0700, 0 /*fCreate*/), VINF_SUCCESS); + } +#endif + +#ifndef RT_OS_WINDOWS + char szRestrictedDir[] = "/tmp/tstRTLocalIpc-restricted-XXXXXX"; + char *pszSavedRuntimeDir = NULL; + if (fRestricted) + { + rc = RTDirCreateTemp(szRestrictedDir, 0700); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { + RTDirRemove(szSquatter); + return; + } + pszSavedRuntimeDir = RTEnvDupEx(RTENV_DEFAULT, "XDG_RUNTIME_DIR"); + rc = RTEnvSet("XDG_RUNTIME_DIR", szRestrictedDir); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { + RTStrFree(pszSavedRuntimeDir); + RTDirRemove(szRestrictedDir); + RTDirRemove(szSquatter); + return; + } + } +#endif /* * Create the test server. */ - RTLOCALIPCSERVER hIpcServer; - RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionConnection", 0), VINF_SUCCESS); + RTLOCALIPCSERVER hIpcServer = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionConnection", fServerFlags); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { +#ifdef RT_OS_WINDOWS + RTLocalIpcServerDestroy(hSquatter); +#else + if (fRestricted) + { + if (pszSavedRuntimeDir) + RTEnvSet("XDG_RUNTIME_DIR", pszSavedRuntimeDir); + else + RTEnvUnset("XDG_RUNTIME_DIR"); + RTStrFree(pszSavedRuntimeDir); + RTDirRemove(szRestrictedDir); + RTDirRemove(szSquatter); + } +#endif + return; + } /* * Create worker thread that listens and closes incoming connections until * cancelled. */ - int rc; RTTHREAD hListenThread; - RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testServerListenThread, hIpcServer, 0 /* Stack */, + TESTSERVERLISTENARGS Args; + Args.hIpcServer = hIpcServer; + Args.fExpectOtherProcess = pszExecPath != NULL; + RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testServerListenThread, &Args, 0 /* Stack */, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-1")); if (RT_SUCCESS(rc)) { @@ -189,7 +604,14 @@ static void testSessionConnection(const char *pszExecPath) if (pszExecPath) { RTPROCESS hClientProc; - const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionConnectionChild", NULL }; + const char *apszArgs[5] = + { + pszExecPath, + "child", + "tstRTLocalIpcSessionConnectionChild", + fRestricted ? "restricted" : NULL, + NULL + }; RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc)); if (RT_SUCCESS(rc)) { @@ -202,7 +624,8 @@ static void testSessionConnection(const char *pszExecPath) else { RTTHREAD hClientThread; - RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionConnectionChild, NULL, + void *pvWorker = (void *)(uintptr_t)(fRestricted ? TEST_CONNECTION_F_RESTRICTED : 0); + RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionConnectionChild, pvWorker, 0 /* Stack */, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-1")); if (RT_SUCCESS(rc)) { @@ -226,9 +649,89 @@ static void testSessionConnection(const char *pszExecPath) } RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED); +#ifdef RT_OS_WINDOWS + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hSquatter), fRestricted ? VINF_OBJECT_DESTROYED : VINF_SUCCESS); +#else + if (fRestricted) + { + if (pszSavedRuntimeDir) + RTTESTI_CHECK_RC(RTEnvSet("XDG_RUNTIME_DIR", pszSavedRuntimeDir), VINF_SUCCESS); + else + RTTESTI_CHECK_RC(RTEnvUnset("XDG_RUNTIME_DIR"), VINF_SUCCESS); + RTStrFree(pszSavedRuntimeDir); + RTTESTI_CHECK_RC(RTDirRemove(szRestrictedDir), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRemove(szSquatter), VINF_SUCCESS); + } +#endif } +#ifdef RT_OS_WINDOWS +static DECLCALLBACK(int) testServerListenAnonymousThread(RTTHREAD hSelf, void *pvUser) +{ + RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser; + RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck); + + RTTEST_CHECK_RC_OK_RET(g_hTest, RTThreadUserSignal(hSelf), rcCheck); + + RTLOCALIPCSESSION hIpcSession; + RTTEST_CHECK_RC_OK_RET(g_hTest, RTLocalIpcServerListen(hIpcServer, &hIpcSession), rcCheck); + uint8_t bIdentity = 0; + RTTEST_CHECK_RC_OK_RET(g_hTest, RTLocalIpcSessionRead(hIpcSession, &bIdentity, sizeof(bIdentity), NULL), rcCheck); + RTTEST_CHECK_RET(g_hTest, bIdentity == UINT8_C(0x24), VERR_GENERAL_FAILURE); + RTTESTI_CHECK_RC(RTLocalIpcSessionVerifySameUser(hIpcSession), VERR_ACCESS_DENIED); + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED); + return VINF_SUCCESS; +} + + +static void testSessionAnonymousClient(void) +{ + RTTestISub("Anonymous client identification"); + + RTLOCALIPCSERVER hIpcServer; + RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionAnonymous", 0), VINF_SUCCESS); + + RTTHREAD hListenThread; + int rc; + RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testServerListenAnonymousThread, hIpcServer, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-anon")); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC_OK(RTThreadUserWait(hListenThread, RT_MS_1MIN / 2)); + + RTLOCALIPCSESSION hClientSession = NIL_RTLOCALIPCSESSION; + RTTESTI_CHECK_RC_OK(rc = RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionAnonymous", 0 /*fFlags*/)); + if (RT_SUCCESS(rc)) + { + uint8_t const bIdentity = UINT8_C(0x24); + RTTESTI_CHECK_RC_OK(rc = RTLocalIpcSessionWrite(hClientSession, &bIdentity, sizeof(bIdentity))); + if (RT_SUCCESS(rc)) + RTTESTI_CHECK_RC_OK(rc = RTLocalIpcSessionFlush(hClientSession)); + if (RT_FAILURE(rc)) + { + RTLocalIpcSessionClose(hClientSession); + hClientSession = NIL_RTLOCALIPCSESSION; + } + int rcThread; + RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hListenThread, RT_MS_1MIN / 2, &rcThread)); + if (RT_SUCCESS(rc)) + RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS); + if (hClientSession != NIL_RTLOCALIPCSESSION) + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED); + } + else + { + RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS); + RTTESTI_CHECK_RC_OK(RTThreadWait(hListenThread, RT_MS_1MIN / 2, NULL)); + } + } + + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED); +} +#endif /* RT_OS_WINDOWS */ + + /********************************************************************************************************************************* * * @@ -909,13 +1412,24 @@ int main(int argc, char **argv) RTAssertSetQuiet(fQuiet); /* Do real tests if the basics are fine. */ + if (RTTestErrorCount(g_hTest) == 0) + testRestrictedNamespaceProperties(); char szExecPath[RTPATH_MAX]; if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath))) { if (RTTestErrorCount(g_hTest) == 0) - testSessionConnection(NULL); + testSessionConnection(NULL, 0 /*fServerFlags*/); + if (RTTestErrorCount(g_hTest) == 0) + testSessionConnection(szExecPath, 0 /*fServerFlags*/); if (RTTestErrorCount(g_hTest) == 0) - testSessionConnection(szExecPath); + testSessionConnection(NULL, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + if (RTTestErrorCount(g_hTest) == 0) + testSessionConnection(szExecPath, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + +#ifdef RT_OS_WINDOWS + if (RTTestErrorCount(g_hTest) == 0) + testSessionAnonymousClient(); +#endif if (RTTestErrorCount(g_hTest) == 0) testSessionWait(NULL); @@ -938,7 +1452,7 @@ int main(int argc, char **argv) /* * Child process. */ - else if ( argc == 3 + else if ( (argc == 3 || argc == 4) && !strcmp(argv[1], "child")) { rc = RTTestCreateChild(argv[2], &g_hTest); @@ -946,7 +1460,12 @@ int main(int argc, char **argv) return RTEXITCODE_FAILURE; if (!strcmp(argv[2], "tstRTLocalIpcSessionConnectionChild")) - tstRTLocalIpcSessionConnectionChild(RTThreadSelf(), g_hTest); + { + uintptr_t fWorker = TEST_CONNECTION_F_OTHER_PROCESS; + if (argc == 4 && !strcmp(argv[3], "restricted")) + fWorker |= TEST_CONNECTION_F_RESTRICTED; + tstRTLocalIpcSessionConnectionChild(RTThreadSelf(), (void *)fWorker); + } else if (!strcmp(argv[2], "tstRTLocalIpcSessionWaitChild")) tstRTLocalIpcSessionWaitChild(RTThreadSelf(), g_hTest); else if (!strcmp(argv[2], "tstRTLocalIpcSessionDataChild")) @@ -967,4 +1486,3 @@ int main(int argc, char **argv) */ return RTTestSummaryAndDestroy(g_hTest); } - From c6462917b925c8a012cf154216763bc4f23e02d4 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 21:35:39 +0000 Subject: [PATCH 021/176] =?UTF-8?q?IPRT:=20Secure=20restricted=20local=20I?= =?UTF-8?q?PC=20namespaces=20and=20peer=20identity=20[GetSecurityInfo=20bu?= =?UTF-8?q?ild=20fix,=20not=20available=20on=20older=20OSes].=20=E2=80=8Bb?= =?UTF-8?q?ugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174712 --- src/VBox/Runtime/r3/win/localipc-win.cpp | 39 ++++++++++++++++++------ 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/VBox/Runtime/r3/win/localipc-win.cpp b/src/VBox/Runtime/r3/win/localipc-win.cpp index d94affff3dc4..ddfbeed08f98 100644 --- a/src/VBox/Runtime/r3/win/localipc-win.cpp +++ b/src/VBox/Runtime/r3/win/localipc-win.cpp @@ -1,4 +1,4 @@ -/* $Id: localipc-win.cpp 114874 2026-08-06 21:28:04Z andreas.loeffler@oracle.com $ */ +/* $Id: localipc-win.cpp 114875 2026-08-06 21:35:39Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Local IPC, Windows Implementation Using Named Pipes. * @@ -43,7 +43,6 @@ *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_LOCALIPC #include /* Need NtCancelIoFile and a few Rtl functions. */ -#include #include "internal/iprt.h" #include @@ -461,14 +460,34 @@ static int rtLocalIpcWinVerifyPipeOwnerUser(HANDLE hPipe) { AssertReturn(hPipe != NULL && hPipe != INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); - PSECURITY_DESCRIPTOR pSecDesc = NULL; - PSID pOwner = NULL; - DWORD const dwErr = GetSecurityInfo(hPipe, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, - &pOwner, NULL, NULL, NULL, &pSecDesc); - int const rc = dwErr == ERROR_SUCCESS ? rtLocalIpcWinVerifyUserSid(pOwner) - : RTErrConvertFromWin32(dwErr); - if (pSecDesc) - LocalFree(pSecDesc); + /* GetSecurityInfo is unavailable on the NT 3.1 import baseline. */ + DWORD cbSecDesc = 0; + if (GetKernelObjectSecurity(hPipe, OWNER_SECURITY_INFORMATION, NULL, 0, &cbSecDesc)) + return VERR_INTERNAL_ERROR; + DWORD const dwErr = GetLastError(); + if (dwErr != ERROR_INSUFFICIENT_BUFFER) + return RTErrConvertFromWin32(dwErr); + + PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); + if (!pSecDesc) + return VERR_NO_TMP_MEMORY; + + int rc; + if (GetKernelObjectSecurity(hPipe, OWNER_SECURITY_INFORMATION, pSecDesc, cbSecDesc, &cbSecDesc)) + { + PSID pOwner = NULL; + BOOL fOwnerDefaulted = FALSE; + if (GetSecurityDescriptorOwner(pSecDesc, &pOwner, &fOwnerDefaulted)) + { + RT_NOREF(fOwnerDefaulted); + rc = rtLocalIpcWinVerifyUserSid(pOwner); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pSecDesc); return rc; } From d3e90e0197097e2bf0161eddf6a628ef8c654c59 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 21:41:36 +0000 Subject: [PATCH 022/176] IntNet/R3: Added the IntNet R3 IPC protocol header. bugref:11149 svn:sync-xref-src-repo-rev: r174713 --- include/VBox/intnetr3ipc.h | 109 +++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 include/VBox/intnetr3ipc.h diff --git a/include/VBox/intnetr3ipc.h b/include/VBox/intnetr3ipc.h new file mode 100644 index 000000000000..afa2585dcea0 --- /dev/null +++ b/include/VBox/intnetr3ipc.h @@ -0,0 +1,109 @@ +/* $Id: intnetr3ipc.h 114876 2026-08-06 21:41:36Z andreas.loeffler@oracle.com $ */ +/** @file + * Internal networking Ring-3 service IPC protocol. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_intnetr3ipc_h +#define VBOX_INCLUDED_intnetr3ipc_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +RT_C_DECLS_BEGIN + +/** The local IPC service identifier. */ +#ifndef INTNET_R3_SVC_NAME +# define INTNET_R3_SVC_NAME "org.virtualbox.intnet" +#endif +/** Maximum generated per-user Local IPC service name length, including the terminator. */ +#define INTNET_R3_IPC_MAX_SERVICE_NAME 64 +/** Protocol version. */ +#define INTNET_R3_IPC_VERSION UINT16_C(1) +/** Maximum size of an IntNet request payload. */ +#define INTNET_R3_IPC_MAX_REQ UINT32_C(65536) +/** Maximum shared-memory object name length, including the terminator. */ +#define INTNET_R3_IPC_MAX_SHMEM_NAME 256 +/** Maximum time allowed to complete a started Local IPC frame. */ +#define INTNET_R3_IPC_FRAME_TIMEOUT_MS UINT32_C(30000) + +/** Local IPC request header magic. */ +#define INTNET_R3_IPC_REQ_MAGIC RT_MAKE_U32_FROM_U8('I', 'N', 'R', 'Q') +/** Local IPC synchronous reply header magic. */ +#define INTNET_R3_IPC_REPLY_MAGIC RT_MAKE_U32_FROM_U8('I', 'N', 'R', 'P') +/** Local IPC receive-available notification header magic. */ +#define INTNET_R3_IPC_POKE_MAGIC RT_MAKE_U32_FROM_U8('I', 'N', 'R', 'K') + +/** Header preceding an IntNet service request payload. */ +typedef struct INTNETR3IPCREQHDR +{ + /** INTNET_R3_IPC_REQ_MAGIC. */ + uint32_t u32Magic; + /** INTNET_R3_IPC_VERSION. */ + uint16_t u16Version; + /** Size of this header in bytes. */ + uint16_t cbHdr; + /** Size of the request payload following this header. */ + uint32_t cbReq; + /** VMMR0_DO_INTNET_* operation to perform. */ + uint32_t uOperation; +} INTNETR3IPCREQHDR; +/** Pointer to an IntNet service request header. */ +typedef INTNETR3IPCREQHDR *PINTNETR3IPCREQHDR; + +/** Header preceding an IntNet service reply or receive-available notification. */ +typedef struct INTNETR3IPCREPLYHDR +{ + /** INTNET_R3_IPC_REPLY_MAGIC or INTNET_R3_IPC_POKE_MAGIC. */ + uint32_t u32Magic; + /** INTNET_R3_IPC_VERSION. */ + uint16_t u16Version; + /** Size of this header in bytes. */ + uint16_t cbHdr; + /** VBox status code for the request or notification. */ + int32_t rc; + /** Size of the reply payload following this header. */ + uint32_t cbReply; + /** Size of the optional terminated shared-memory name following the payload. */ + uint32_t cbShMemName; + /** Size of the shared-memory object in bytes. */ + uint64_t cbShMem; +} INTNETR3IPCREPLYHDR; +/** Pointer to a const IntNet service reply header. */ +typedef INTNETR3IPCREPLYHDR const *PCINTNETR3IPCREPLYHDR; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_intnetr3ipc_h */ From c2c84a7710a8d513c9a65d3dfa2982dadf8f73cd Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 21:53:20 +0000 Subject: [PATCH 023/176] NetworkServices/IntNet: Implemented R3 IntNet switch transport using local IPC / shared memory for non-Darwin hosts. bugref:11149 svn:sync-xref-src-repo-rev: r174714 --- .../NetworkServices/IntNetSwitch/Makefile.kmk | 10 +- .../IntNetSwitch/VBoxIntNetSwitch.cpp | 1477 ++++++++++++++--- 2 files changed, 1277 insertions(+), 210 deletions(-) diff --git a/src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk b/src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk index d7718d3369ca..2e68157f32ff 100644 --- a/src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk +++ b/src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114877 2026-08-06 21:53:20Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for the Ring-3 based network switch process. # @@ -31,7 +31,9 @@ include $(KBUILD_PATH)/subheader.kmk # # The internal network switch module. # -PROGRAMS += VBoxIntNetSwitch +if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) && ("$(KBUILD_TARGET)" == "darwin" || defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC)) + PROGRAMS += VBoxIntNetSwitch +endif VBoxIntNetSwitch_TEMPLATE := VBoxR3Exe ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING @@ -39,7 +41,9 @@ ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING else VBoxIntNetSwitch_DEFS = KBUILD_TYPE=\"$(KBUILD_TYPE)\" endif -VBoxIntNetSwitch_DEFS += VBOX_WITH_INTNET_SERVICE_IN_R3 +VBoxIntNetSwitch_DEFS += \ + VBOX_WITH_INTNET_SERVICE_IN_R3 \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) VBoxIntNetSwitch_INST.darwin = $(VBoxIntNetSwitch.xpc_INST)/MacOS/ VBoxIntNetSwitch_SOURCES = \ VBoxIntNetSwitch.cpp \ diff --git a/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp b/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp index e8adafc59290..e71121153f78 100644 --- a/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp +++ b/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxIntNetSwitch.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxIntNetSwitch.cpp 114877 2026-08-06 21:53:20Z andreas.loeffler@oracle.com $ */ /** @file * Internal networking - Wrapper for the R0 network service. * @@ -34,26 +34,72 @@ *********************************************************************************************************************************/ #define IN_INTNET_TESTCASE #define IN_INTNET_R3 +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# if !defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) +# error "The Local IPC R3 IntNet service implementation is not enabled!" +# endif +#endif #include "IntNetSwitchInternal.h" #include +#include #include #include #include +#include +#include +#include #include #include #include +#include #include #include #include #include -#include +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# include +# include +#else +# include +# include +# include +# include +# include +# include +#endif + +#ifndef RT_OS_WINDOWS +# include +#endif + +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +static int intnetR3LocalIpcSendPoke(struct SUPDRVSESSION *pSession); +#endif /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ +/** A client has registered an asynchronous receive wait. */ +#define INTNETR3RECVSTATE_F_WAITING RT_BIT_32(0) +/** Receive data arrived before a client registered a wait. */ +#define INTNETR3RECVSTATE_F_AVAILABLE RT_BIT_32(1) +/** The client aborted receive waits permanently for this interface session. */ +#define INTNETR3RECVSTATE_F_NO_MORE_WAITS RT_BIT_32(2) +/** Maximum combined IntNet shared-buffer allocation accepted over an R3 transport. */ +#define INTNETR3_MAX_BUFFER_SIZE UINT64_C(134217728) +/** Maximum number of active transport connections. */ +#define INTNETR3_MAX_CONNECTIONS UINT32_C(128) +/** Maximum number of Local IPC request and notification worker threads. */ +#define INTNETR3_MAX_THREADS UINT32_C(256) +/** Number of worker threads reserved by each Local IPC session. */ +#define INTNETR3_THREADS_PER_LOCALIPC_SESSION UINT32_C(2) +/** Maximum aggregate shared memory allocated by the service. */ +#define INTNETR3_MAX_AGGREGATE_SHMEM_SIZE UINT64_C(1073741824) +/** Number of fresh UUID names tried if a shared-memory object already exists. */ +#define INTNETR3_SHMEM_CREATE_RETRIES 16 /********************************************************************************************************************************* @@ -99,8 +145,20 @@ typedef struct SUPDRVUSAGE */ typedef struct SUPDRVDEVEXT { - /** Number of references to this service. */ + /** Number of active transport connections. */ uint32_t volatile cRefs; + /** Number of active Local IPC worker threads. */ + uint32_t volatile cThreads; + /** Aggregate size of all shared-memory allocations. */ + uint64_t cbShMem; + /** Maximum number of active transport connections. */ + uint32_t cMaxConnections; + /** Maximum number of active Local IPC worker threads. */ + uint32_t cMaxThreads; + /** Maximum aggregate size of all shared-memory allocations. */ + uint64_t cbMaxShMem; + /** Maximum time allowed to receive the first frame or complete a started frame. */ + uint32_t cMsReadTimeout; /** Critical section to serialize the initialization, usage counting and objects. */ RTCRITSECT CritSect; /** List of registered objects. Protected by the spinlock. */ @@ -109,6 +167,22 @@ typedef struct SUPDRVDEVEXT typedef SUPDRVDEVEXT *PSUPDRVDEVEXT; +typedef struct INTNETR3SHMEM +{ + struct INTNETR3SHMEM *pNext; + /** Original mapping address used as the allocation lookup key. */ + void *pvKey; + /** Mapping address while it still needs to be unmapped. */ + void *pv; + size_t cb; +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + RTSHMEM hShMem; + char szName[INTNET_R3_IPC_MAX_SHMEM_NAME]; +#endif +} INTNETR3SHMEM; +typedef INTNETR3SHMEM *PINTNETR3SHMEM; + + /** * Per session data. * This is mainly for memory tracking. @@ -118,21 +192,366 @@ typedef struct SUPDRVSESSION PSUPDRVDEVEXT pDevExt; /** List of generic usage records. (protected by SUPDRVDEVEXT::CritSect) */ PSUPDRVUSAGE volatile pUsage; +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) /** The XPC connection handle for this session. */ xpc_connection_t hXpcCon; +#else + /** Local IPC session handle for this session. */ + RTLOCALIPCSESSION hIpcSession; + /** Request worker thread serving this session. */ + RTTHREAD hThread; + /** Notification worker thread serving this session. */ + RTTHREAD hIpcPokeThread; + /** Wakes the notification worker without blocking an IntNet delivery callback. */ + RTSEMEVENT hIpcPokeEvt; + /** Whether the notification worker must stop. */ + bool volatile fIpcPokeStopping; + /** Serializes complete outbound IPC frames. */ + RTSEMMUTEX hIpcIoMtx; +#endif + /** Shared memory objects created for this session. */ + PINTNETR3SHMEM pShMemHead; /** The intnet interface handle to wait on. */ INTNETIFHANDLE hIfWait; - /** Flag whether a receive wait was initiated. */ - bool volatile fRecvWait; - /** Flag whether there is something to receive. */ - bool volatile fRecvAvail; + /** INTNETR3RECVSTATE_F_XXX state, updated as one atomic value to avoid lost wakeups. */ + uint32_t volatile fRecvState; } SUPDRVSESSION; +/** Request/reply scratch buffer large enough for all supported IntNet service requests. */ +typedef union INTNETR3REQREPLY +{ + INTNETOPENREQ OpenReq; + INTNETIFCLOSEREQ IfCloseReq; + INTNETIFGETBUFFERPTRSREQ IfGetBufferPtrsReq; + INTNETIFSETPROMISCUOUSMODEREQ IfSetPromiscuousModeReq; + INTNETIFSETMACADDRESSREQ IfSetMacAddressReq; + INTNETIFSETACTIVEREQ IfSetActiveReq; + INTNETIFSENDREQ IfSendReq; + INTNETIFWAITREQ IfWaitReq; + INTNETIFABORTWAITREQ IfAbortWaitReq; +} INTNETR3REQREPLY; +typedef INTNETR3REQREPLY *PINTNETR3REQREPLY; + + +/** Transport-independent result of processing an IntNet service request. */ +typedef struct INTNETR3REQRESULT +{ + /** Ring-3 buffer pointer for VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, NULL otherwise. */ + PINTNETBUF pRing3Buf; + /** Size of the request/reply payload to return for synchronous replies. */ + size_t cbReply; + /** Whether the transport should send a synchronous reply. */ + bool fSendReply; + /** Whether the transport should immediately emit a receive-available poke. */ + bool fSendPokeNow; +} INTNETR3REQRESULT; +typedef INTNETR3REQRESULT *PINTNETR3REQRESULT; + + +/********************************************************************************************************************************* +* Internal Helpers * +*********************************************************************************************************************************/ +static DECLCALLBACK(void) intnetR3RecvAvail(INTNETIFHANDLE hIf, void *pvUser); + + +/** Atomically reserves @a cSlots slots without exceeding @a cMax. */ +static bool intnetR3TryReserveSlots(uint32_t volatile *pcSlots, uint32_t cMax, uint32_t cSlots) +{ + AssertReturn(cSlots > 0, false); + + uint32_t cSlotsOld; + do + { + cSlotsOld = ASMAtomicReadU32(pcSlots); + if ( cSlotsOld > cMax + || cSlots > cMax - cSlotsOld) + return false; + } while (!ASMAtomicCmpXchgU32(pcSlots, cSlotsOld + cSlots, cSlotsOld)); + return true; +} + + +/** Atomically reserves one slot without exceeding @a cMax. */ +static bool intnetR3TryReserveSlot(uint32_t volatile *pcSlots, uint32_t cMax) +{ + return intnetR3TryReserveSlots(pcSlots, cMax, 1); +} + + +#ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH +/** Reads a positive testcase-only 32-bit limit override. */ +static uint32_t intnetR3TestGetLimitU32(const char *pszVar, uint32_t uDefault) +{ + const char *pszValue = RTEnvGet(pszVar); + if (pszValue && *pszValue) + { + uint32_t uValue = 0; + if ( RT_SUCCESS(RTStrToUInt32Full(pszValue, 10, &uValue)) + && uValue > 0) + return uValue; + } + return uDefault; +} + + +/** Reads a positive testcase-only 64-bit limit override. */ +static uint64_t intnetR3TestGetLimitU64(const char *pszVar, uint64_t uDefault) +{ + const char *pszValue = RTEnvGet(pszVar); + if (pszValue && *pszValue) + { + uint64_t uValue = 0; + if ( RT_SUCCESS(RTStrToUInt64Full(pszValue, 10, &uValue)) + && uValue > 0) + return uValue; + } + return uDefault; +} +#endif + + +/** Initializes service-wide resource accounting and limits. */ +static void intnetR3InitLimits(PSUPDRVDEVEXT pDevExt) +{ + pDevExt->cRefs = 0; + pDevExt->cThreads = 0; + pDevExt->cbShMem = 0; + pDevExt->cMaxConnections = INTNETR3_MAX_CONNECTIONS; + pDevExt->cMaxThreads = INTNETR3_MAX_THREADS; + pDevExt->cbMaxShMem = INTNETR3_MAX_AGGREGATE_SHMEM_SIZE; + pDevExt->cMsReadTimeout = INTNET_R3_IPC_FRAME_TIMEOUT_MS; +#ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + pDevExt->cMaxConnections = intnetR3TestGetLimitU32("VBOX_INTNET_R3_TEST_MAX_CONNECTIONS", + pDevExt->cMaxConnections); + pDevExt->cMaxThreads = intnetR3TestGetLimitU32("VBOX_INTNET_R3_TEST_MAX_THREADS", pDevExt->cMaxThreads); + pDevExt->cbMaxShMem = intnetR3TestGetLimitU64("VBOX_INTNET_R3_TEST_MAX_SHMEM", pDevExt->cbMaxShMem); + pDevExt->cMsReadTimeout = intnetR3TestGetLimitU32("VBOX_INTNET_R3_TEST_READ_TIMEOUT_MS", + pDevExt->cMsReadTimeout); +#endif +} + + +/** Reserves service-wide shared-memory quota. */ +static int intnetR3ReserveShMem(PSUPDRVDEVEXT pDevExt, size_t cb) +{ + AssertReturn(cb > 0, VERR_INVALID_PARAMETER); + + int rc = RTCritSectEnter(&pDevExt->CritSect); + if (RT_SUCCESS(rc)) + { + if ( pDevExt->cbShMem <= pDevExt->cbMaxShMem + && (uint64_t)cb <= pDevExt->cbMaxShMem - pDevExt->cbShMem) + pDevExt->cbShMem += cb; + else + rc = VERR_OUT_OF_RESOURCES; + int const rc2 = RTCritSectLeave(&pDevExt->CritSect); + AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); + } + return rc; +} + + +/** Releases service-wide shared-memory quota. */ +static void intnetR3ReleaseShMem(PSUPDRVDEVEXT pDevExt, size_t cb) +{ + int const rc = RTCritSectEnter(&pDevExt->CritSect); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + Assert(pDevExt->cbShMem >= cb); + if (pDevExt->cbShMem >= cb) + pDevExt->cbShMem -= cb; + else + pDevExt->cbShMem = pDevExt->cbMaxShMem; + int const rc2 = RTCritSectLeave(&pDevExt->CritSect); + AssertRC(rc2); + } +} + + +/** Validates and bounds the allocation implied by an IntNet open request. */ +static int intnetR3ValidateOpenBufferSizes(uint32_t cbSend, uint32_t cbRecv) +{ + uint64_t const cbMinRing = sizeof(INTNETHDR) * UINT64_C(4); + uint64_t const cbSendAligned = RT_ALIGN_64(RT_MAX((uint64_t)cbSend, cbMinRing), INTNETRINGBUF_ALIGNMENT); + uint64_t const cbRecvAligned = RT_ALIGN_64(RT_MAX((uint64_t)cbRecv, cbMinRing), INTNETRINGBUF_ALIGNMENT); + uint64_t const cbHdrAligned = RT_ALIGN_64(sizeof(INTNETBUF), INTNETRINGBUF_ALIGNMENT); + uint64_t const cbTotal = cbHdrAligned + cbSendAligned + cbRecvAligned; + return cbTotal <= INTNETR3_MAX_BUFFER_SIZE ? VINF_SUCCESS : VERR_OUT_OF_RANGE; +} + + +/** + * Transport-agnostic IntNet request processor used by both XPC (Darwin) and Local IPC (non-Darwin). + * + * This helper validates the header, dispatches the request to the existing IntNetR3/R0 handlers, + * updates the in/out request buffer, and indicates whether a reply should be sent and/or a + * transport-specific poke should be emitted immediately. + * + * @returns VBox status code for the request processing. + * @param pSession The session context. + * @param uOperation The VMMR0_DO_INTNET_* operation. + * @param pReqReply In/out pointer to the request/reply union buffer. + * @param cbReqReply Size of the request buffer. + * @param pResult Transport-independent processing result. + */ +static int intnetR3ProcessRequestCore(PSUPDRVSESSION pSession, uint32_t uOperation, + PINTNETR3REQREPLY pReqReply, size_t cbReqReply, + PINTNETR3REQRESULT pResult) +{ + pResult->pRing3Buf = NULL; + pResult->cbReply = 0; + pResult->fSendReply = true; + pResult->fSendPokeNow = false; + + PSUPVMMR0REQHDR pReqHdr = (PSUPVMMR0REQHDR)pReqReply; + AssertReturn(pReqHdr->u32Magic == SUPVMMR0REQHDR_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pReqHdr->cbReq == cbReqReply, VERR_INVALID_PARAMETER); + + int rc = VERR_INVALID_PARAMETER; + + switch (uOperation) + { + case VMMR0_DO_INTNET_OPEN: + if (cbReqReply == sizeof(INTNETOPENREQ)) + { + PINTNETOPENREQ p = &pReqReply->OpenReq; + rc = intnetR3ValidateOpenBufferSizes(p->cbSend, p->cbRecv); + if (RT_SUCCESS(rc)) + rc = IntNetR3Open(pSession, &p->szNetwork[0], p->enmTrunkType, p->szTrunk, + p->fFlags, p->cbSend, p->cbRecv, intnetR3RecvAvail, pSession, &p->hIf); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_CLOSE: + if (cbReqReply == sizeof(INTNETIFCLOSEREQ)) + { + PINTNETIFCLOSEREQ p = &pReqReply->IfCloseReq; + rc = IntNetR0IfCloseReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS: + if (cbReqReply == sizeof(INTNETIFGETBUFFERPTRSREQ)) + { + PINTNETIFGETBUFFERPTRSREQ p = &pReqReply->IfGetBufferPtrsReq; + rc = IntNetR0IfGetBufferPtrsReq(pSession, p); + if (RT_SUCCESS(rc)) + pResult->pRing3Buf = p->pRing3Buf; + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE: + if (cbReqReply == sizeof(INTNETIFSETPROMISCUOUSMODEREQ)) + { + PINTNETIFSETPROMISCUOUSMODEREQ p = &pReqReply->IfSetPromiscuousModeReq; + rc = IntNetR0IfSetPromiscuousModeReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS: + if (cbReqReply == sizeof(INTNETIFSETMACADDRESSREQ)) + { + PINTNETIFSETMACADDRESSREQ p = &pReqReply->IfSetMacAddressReq; + rc = IntNetR0IfSetMacAddressReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_SET_ACTIVE: + if (cbReqReply == sizeof(INTNETIFSETACTIVEREQ)) + { + PINTNETIFSETACTIVEREQ p = &pReqReply->IfSetActiveReq; + rc = IntNetR0IfSetActiveReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_SEND: + if (cbReqReply == sizeof(INTNETIFSENDREQ)) + { + PINTNETIFSENDREQ p = &pReqReply->IfSendReq; + rc = IntNetR0IfSendReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_WAIT: + if (cbReqReply == sizeof(INTNETIFWAITREQ)) + { + uint32_t fOld; + uint32_t fNew; + do + { + fOld = ASMAtomicReadU32(&pSession->fRecvState); + if (fOld & INTNETR3RECVSTATE_F_NO_MORE_WAITS) + fNew = fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE); + else if (fOld & INTNETR3RECVSTATE_F_AVAILABLE) + fNew = fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE); + else + fNew = fOld | INTNETR3RECVSTATE_F_WAITING; + } while (!ASMAtomicCmpXchgU32(&pSession->fRecvState, fNew, fOld)); + if (fOld & (INTNETR3RECVSTATE_F_AVAILABLE | INTNETR3RECVSTATE_F_NO_MORE_WAITS)) + pResult->fSendPokeNow = true; + pResult->fSendReply = false; /* async */ + rc = VINF_SUCCESS; + } + break; + + case VMMR0_DO_INTNET_IF_ABORT_WAIT: + if (cbReqReply == sizeof(INTNETIFABORTWAITREQ)) + { + PINTNETIFABORTWAITREQ p = &pReqReply->IfAbortWaitReq; + RT_NOREF(p); + uint32_t fOld; + uint32_t fNew; + do + { + fOld = ASMAtomicReadU32(&pSession->fRecvState); + fNew = (fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE)) + | INTNETR3RECVSTATE_F_NO_MORE_WAITS; + } while (!ASMAtomicCmpXchgU32(&pSession->fRecvState, fNew, fOld)); + if (fOld & (INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE)) + pResult->fSendPokeNow = true; + rc = VINF_SUCCESS; + pResult->cbReply = sizeof(*p); + } + break; + + default: + rc = VERR_INVALID_PARAMETER; + break; + } + + return rc; +} + + /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ static SUPDRVDEVEXT g_DevExt; +#ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH +/** Local IPC listener cancelled when the testcase helper loses its last client. */ +static RTLOCALIPCSERVER g_hTestIpcServer = NIL_RTLOCALIPCSERVER; +#endif + + +/** Releases one connection slot and wakes the one-shot listener after the last client. */ +static uint32_t intnetR3ReleaseConnectionSlot(PSUPDRVDEVEXT pDevExt) +{ + uint32_t const cRefs = ASMAtomicDecU32(&pDevExt->cRefs); +#ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + if (!cRefs && g_hTestIpcServer != NIL_RTLOCALIPCSERVER) + RTLocalIpcServerCancel(g_hTestIpcServer); +#endif + return cRefs; +} INTNETR3DECL(void *) SUPR0ObjRegister(PSUPDRVSESSION pSession, SUPDRVOBJTYPE enmType, @@ -320,51 +739,220 @@ INTNETR3DECL(int) SUPR0ObjVerifyAccess(void *pvObj, PSUPDRVSESSION pSession, con } +/** Releases the backing resources of one tracked shared-memory allocation. */ +static int intnetR3DestroyShMem(PINTNETR3SHMEM pShMem) +{ + int rc = VINF_SUCCESS; +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + if (pShMem->pv) + { + if (munmap(pShMem->pv, pShMem->cb) == 0) + pShMem->pv = NULL; + else + rc = RTErrConvertFromErrno(errno); + } +#else + if (pShMem->pv) + { + int const rc2 = RTShMemUnmapRegion(pShMem->hShMem, pShMem->pv); + if (RT_SUCCESS(rc2)) + pShMem->pv = NULL; + else + rc = rc2; + } + if (!pShMem->pv && pShMem->hShMem != NIL_RTSHMEM) + { + int const rc2 = RTShMemClose(pShMem->hShMem); + if (RT_SUCCESS(rc2)) + pShMem->hShMem = NIL_RTSHMEM; + else if (RT_SUCCESS(rc)) + rc = rc2; + } + if (!pShMem->pv && pShMem->hShMem == NIL_RTSHMEM && pShMem->szName[0]) + { + int const rc2 = RTShMemDelete(pShMem->szName); + if ( RT_SUCCESS(rc2) + || rc2 == VERR_FILE_NOT_FOUND + || rc2 == VERR_PATH_NOT_FOUND + || rc2 == VERR_NOT_SUPPORTED) + pShMem->szName[0] = '\0'; + else if (RT_SUCCESS(rc)) + rc = rc2; + } +#endif + return rc; +} + + INTNETR3DECL(int) SUPR0MemAlloc(PSUPDRVSESSION pSession, uint32_t cb, PRTR0PTR ppvR0, PRTR3PTR ppvR3) { - RT_NOREF(pSession); + AssertPtr(pSession); - /* - * This is used to allocate and map the send/receive buffers into the callers process space, meaning - * we have to mmap it with the shareable attribute. - */ - void *pv = mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); - if (pv == MAP_FAILED) + PINTNETR3SHMEM pShMem = (PINTNETR3SHMEM)RTMemAllocZ(sizeof(*pShMem)); + if (!pShMem) return VERR_NO_MEMORY; + pShMem->cb = cb; + + int rc = intnetR3ReserveShMem(pSession->pDevExt, cb); + if (RT_FAILURE(rc)) + { + RTMemFree(pShMem); + return rc; + } + +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + /* The XPC transport requires a shareable mapping for the send/receive buffer. */ + pShMem->pv = mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); + if (pShMem->pv == MAP_FAILED) + { + pShMem->pv = NULL; + rc = VERR_NO_MEMORY; + } +#else + pShMem->hShMem = NIL_RTSHMEM; - *ppvR0 = (RTR0PTR)pv; + rc = VERR_ALREADY_EXISTS; + for (uint32_t iTry = 0; iTry < INTNETR3_SHMEM_CREATE_RETRIES && rc == VERR_ALREADY_EXISTS; iTry++) + { + RTUUID Uuid; + char szUuid[32]; + rc = RTUuidCreate(&Uuid); + if (RT_SUCCESS(rc)) + { + size_t cchUuid = 0; + rc = RTBase64EncodeEx(&Uuid, sizeof(Uuid), RTBASE64_FLAGS_NO_LINE_BREAKS, + szUuid, sizeof(szUuid), &cchUuid); + if (RT_SUCCESS(rc)) + { + while (cchUuid > 0 && szUuid[cchUuid - 1] == '=') + szUuid[--cchUuid] = '\0'; + for (size_t off = 0; off < cchUuid; off++) + if (szUuid[off] == '+') + szUuid[off] = '-'; + else if (szUuid[off] == '/') + szUuid[off] = '_'; + } + } + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pShMem->szName, sizeof(pShMem->szName), "vbi-%s", szUuid); + if (cch <= 0 || (size_t)cch >= sizeof(pShMem->szName)) + rc = VERR_BUFFER_OVERFLOW; + } + if (RT_SUCCESS(rc)) + rc = RTShMemOpen(&pShMem->hShMem, pShMem->szName, + RTSHMEM_O_F_CREATE_EXCL | RTSHMEM_O_F_READWRITE, cb, 2 /*cMappingsHint*/); + } + if (RT_SUCCESS(rc)) + rc = RTShMemMapRegion(pShMem->hShMem, 0 /*off*/, cb, RTSHMEM_MAP_F_READ | RTSHMEM_MAP_F_WRITE, &pShMem->pv); +#endif + if (RT_FAILURE(rc)) + { +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + if (pShMem->hShMem != NIL_RTSHMEM) + intnetR3DestroyShMem(pShMem); + else + pShMem->szName[0] = '\0'; +#endif + if ( !pShMem->pv +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + && pShMem->hShMem == NIL_RTSHMEM + && !pShMem->szName[0] +#endif + ) + { + intnetR3ReleaseShMem(pSession->pDevExt, cb); + RTMemFree(pShMem); + } + else + { + pShMem->pNext = pSession->pShMemHead; + pSession->pShMemHead = pShMem; + } + return rc; + } + + pShMem->pvKey = pShMem->pv; + pShMem->pNext = pSession->pShMemHead; + pSession->pShMemHead = pShMem; + *ppvR0 = (RTR0PTR)pShMem->pv; if (ppvR3) - *ppvR3 = pv; + *ppvR3 = pShMem->pv; return VINF_SUCCESS; } INTNETR3DECL(int) SUPR0MemFree(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr) { - RT_NOREF(pSession); + AssertPtr(pSession); + PINTNETR3SHMEM pPrev = NULL; + PINTNETR3SHMEM pCur = pSession->pShMemHead; + while (pCur) + { + if (pCur->pvKey == (void *)(uintptr_t)uPtr) + { + int const rc = intnetR3DestroyShMem(pCur); + if (RT_FAILURE(rc)) + return rc; + if (pPrev) + pPrev->pNext = pCur->pNext; + else + pSession->pShMemHead = pCur->pNext; + intnetR3ReleaseShMem(pSession->pDevExt, pCur->cb); + RTMemFree(pCur); + return VINF_SUCCESS; + } + pPrev = pCur; + pCur = pCur->pNext; + } + return VERR_NOT_FOUND; +} - PINTNETBUF pBuf = (PINTNETBUF)uPtr; /// @todo Hack hack hack! - munmap((void *)uPtr, pBuf->cbBuf); - return VINF_SUCCESS; + +static PINTNETR3SHMEM intnetR3FindShMemByPtr(PSUPDRVSESSION pSession, void *pv) +{ + for (PINTNETR3SHMEM pCur = pSession->pShMemHead; pCur; pCur = pCur->pNext) + if (pCur->pvKey == pv) + return pCur; + return NULL; +} + + +static void intnetR3FreeShMems(PSUPDRVSESSION pSession) +{ + PINTNETR3SHMEM pCur = pSession->pShMemHead; + pSession->pShMemHead = NULL; + while (pCur) + { + PINTNETR3SHMEM pNext = pCur->pNext; + int const rc = intnetR3DestroyShMem(pCur); + AssertRC(rc); + if (RT_SUCCESS(rc)) + intnetR3ReleaseShMem(pSession->pDevExt, pCur->cb); + RTMemFree(pCur); + pCur = pNext; + } } /** - * Destroys the given internal network XPC connection session freeing all allocated resources. + * Destroys the given internal network service session, freeing all allocated resources. * - * @returns Reference count of the device extension.. - * @param pSession The ession to destroy. + * @returns Reference count of the device extension. + * @param pSession The session to destroy. */ static uint32_t intnetR3SessionDestroy(PSUPDRVSESSION pSession) { PSUPDRVDEVEXT pDevExt = pSession->pDevExt; - uint32_t cRefs = ASMAtomicDecU32(&pDevExt->cRefs); - xpc_transaction_end(); - xpc_connection_set_context(pSession->hXpcCon, NULL); - xpc_connection_cancel(pSession->hXpcCon); - pSession->hXpcCon = NULL; - ASMAtomicXchgBool(&pSession->fRecvAvail, true); + /* Prevent new notification work, but keep its synchronization objects and + transport alive until interface destruction has quiesced callbacks. */ + ASMAtomicWriteU32(&pSession->fRecvState, INTNETR3RECVSTATE_F_NO_MORE_WAITS); +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + ASMAtomicWriteBool(&pSession->fIpcPokeStopping, true); + if (pSession->hIpcSession != NIL_RTLOCALIPCSESSION) + RTLocalIpcSessionCancel(pSession->hIpcSession); +#endif if (pSession->pUsage) { @@ -415,6 +1003,36 @@ static uint32_t intnetR3SessionDestroy(PSUPDRVSESSION pSession) AssertMsg(!pSession->pUsage, ("Some buster reregistered an object during destruction!\n")); } + /* Interface destructors release their shared buffers via SUPR0MemFree. */ + intnetR3FreeShMems(pSession); +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + xpc_transaction_end(); + xpc_connection_set_context(pSession->hXpcCon, NULL); + xpc_connection_cancel(pSession->hXpcCon); + pSession->hXpcCon = NULL; +#else + if (pSession->hIpcPokeEvt != NIL_RTSEMEVENT) + RTSemEventSignal(pSession->hIpcPokeEvt); + if (pSession->hIpcPokeThread != NIL_RTTHREAD) + { + int const rcThread = RTThreadWait(pSession->hIpcPokeThread, RT_INDEFINITE_WAIT, NULL /*prc*/); + AssertRC(rcThread); + pSession->hIpcPokeThread = NIL_RTTHREAD; + } + if (pSession->hIpcSession != NIL_RTLOCALIPCSESSION) + { + RTLocalIpcSessionClose(pSession->hIpcSession); + pSession->hIpcSession = NIL_RTLOCALIPCSESSION; + } + if (pSession->hIpcPokeEvt != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pSession->hIpcPokeEvt); + pSession->hIpcPokeEvt = NIL_RTSEMEVENT; + } + if (pSession->hIpcIoMtx != NIL_RTSEMMUTEX) + RTSemMutexDestroy(pSession->hIpcIoMtx); +#endif + uint32_t const cRefs = intnetR3ReleaseConnectionSlot(pDevExt); RTMemFree(pSession); return cRefs; } @@ -428,186 +1046,112 @@ static DECLCALLBACK(void) intnetR3RecvAvail(INTNETIFHANDLE hIf, void *pvUser) RT_NOREF(hIf); PSUPDRVSESSION pSession = (PSUPDRVSESSION)pvUser; - if (ASMAtomicXchgBool(&pSession->fRecvWait, false)) + uint32_t fOld; + uint32_t fNew; + do { + fOld = ASMAtomicReadU32(&pSession->fRecvState); + if (fOld & INTNETR3RECVSTATE_F_NO_MORE_WAITS) + fNew = fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE); + else if (fOld & INTNETR3RECVSTATE_F_WAITING) + fNew = fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE); + else + fNew = fOld | INTNETR3RECVSTATE_F_AVAILABLE; + } while (!ASMAtomicCmpXchgU32(&pSession->fRecvState, fNew, fOld)); + + if (fOld & INTNETR3RECVSTATE_F_WAITING) + { +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) /* Send an empty message. */ xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0); xpc_connection_send_message(pSession->hXpcCon, hObjPoke); xpc_release(hObjPoke); +#else + int const rc = RTSemEventSignal(pSession->hIpcPokeEvt); + AssertRC(rc); +#endif } - else - ASMAtomicXchgBool(&pSession->fRecvAvail, true); } +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) static void intnetR3RequestProcess(xpc_connection_t hCon, xpc_object_t hObj, PSUPDRVSESSION pSession) { - int rc = VINF_SUCCESS; - uint64_t iReq = xpc_dictionary_get_uint64(hObj, "req-id"); + uint64_t const uOperation = xpc_dictionary_get_uint64(hObj, "req-id"); size_t cbReq = 0; const void *pvReq = xpc_dictionary_get_data(hObj, "req", &cbReq); - union - { - INTNETOPENREQ OpenReq; - INTNETIFCLOSEREQ IfCloseReq; - INTNETIFGETBUFFERPTRSREQ IfGetBufferPtrsReq; - INTNETIFSETPROMISCUOUSMODEREQ IfSetPromiscuousModeReq; - INTNETIFSETMACADDRESSREQ IfSetMacAddressReq; - INTNETIFSETACTIVEREQ IfSetActiveReq; - INTNETIFSENDREQ IfSendReq; - INTNETIFWAITREQ IfWaitReq; - INTNETIFABORTWAITREQ IfAbortWaitReq; - } ReqReply; + if (cbReq > INTNET_R3_IPC_MAX_REQ) + { + xpc_connection_cancel(hCon); + return; + } - memcpy(&ReqReply, pvReq, RT_MIN(sizeof(ReqReply), cbReq)); - size_t cbReply = 0; + xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj); + if (!hObjReply && uOperation != VMMR0_DO_INTNET_IF_WAIT) + { + xpc_connection_cancel(hCon); + return; + } - if (pvReq) + INTNETR3REQREPLY ReqReply; + INTNETR3REQRESULT Result; + + RT_ZERO(ReqReply); + int rc = VERR_INVALID_PARAMETER; + RT_ZERO(Result); + Result.fSendReply = true; + + if ( pvReq + && cbReq >= sizeof(SUPVMMR0REQHDR) + && uOperation == (uint32_t)uOperation) { - switch (iReq) - { - case VMMR0_DO_INTNET_OPEN: - { - if (cbReq == sizeof(INTNETOPENREQ)) - { - rc = IntNetR3Open(pSession, &ReqReply.OpenReq.szNetwork[0], ReqReply.OpenReq.enmTrunkType, ReqReply.OpenReq.szTrunk, - ReqReply.OpenReq.fFlags, ReqReply.OpenReq.cbSend, ReqReply.OpenReq.cbRecv, - intnetR3RecvAvail, pSession, &ReqReply.OpenReq.hIf); - cbReply = sizeof(INTNETOPENREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_CLOSE: - { - if (cbReq == sizeof(INTNETIFCLOSEREQ)) - { - rc = IntNetR0IfCloseReq(pSession, &ReqReply.IfCloseReq); - cbReply = sizeof(INTNETIFCLOSEREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS: - { - if (cbReq == sizeof(INTNETIFGETBUFFERPTRSREQ)) - { - rc = IntNetR0IfGetBufferPtrsReq(pSession, &ReqReply.IfGetBufferPtrsReq); - /* This is special as we need to return a shared memory segment. */ - xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj); - if (RT_SUCCESS(rc)) - { - xpc_object_t hObjShMem = xpc_shmem_create(ReqReply.IfGetBufferPtrsReq.pRing3Buf, ReqReply.IfGetBufferPtrsReq.pRing3Buf->cbBuf); - if (hObjShMem) - { - xpc_dictionary_set_value(hObjReply, "buf-ptr", hObjShMem); - xpc_release(hObjShMem); - } - else - rc = VERR_NO_MEMORY; - } + memcpy(&ReqReply, pvReq, RT_MIN(sizeof(ReqReply), cbReq)); - xpc_dictionary_set_uint64(hObjReply, "rc", INTNET_R3_SVC_SET_RC(rc)); - xpc_connection_send_message(hCon, hObjReply); - xpc_release(hObjReply); - return; - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE: - { - if (cbReq == sizeof(INTNETIFSETPROMISCUOUSMODEREQ)) - { - rc = IntNetR0IfSetPromiscuousModeReq(pSession, &ReqReply.IfSetPromiscuousModeReq); - cbReply = sizeof(INTNETIFSETPROMISCUOUSMODEREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS: - { - if (cbReq == sizeof(INTNETIFSETMACADDRESSREQ)) - { - rc = IntNetR0IfSetMacAddressReq(pSession, &ReqReply.IfSetMacAddressReq); - cbReply = sizeof(INTNETIFSETMACADDRESSREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_SET_ACTIVE: - { - if (cbReq == sizeof(INTNETIFSETACTIVEREQ)) - { - rc = IntNetR0IfSetActiveReq(pSession, &ReqReply.IfSetActiveReq); - cbReply = sizeof(INTNETIFSETACTIVEREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_SEND: - { - if (cbReq == sizeof(INTNETIFSENDREQ)) - { - rc = IntNetR0IfSendReq(pSession, &ReqReply.IfSendReq); - cbReply = sizeof(INTNETIFSENDREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_WAIT: - { - if (cbReq == sizeof(INTNETIFWAITREQ)) - { - ASMAtomicXchgBool(&pSession->fRecvWait, true); - if (ASMAtomicXchgBool(&pSession->fRecvAvail, false)) - { - ASMAtomicXchgBool(&pSession->fRecvWait, false); + rc = intnetR3ProcessRequestCore(pSession, (uint32_t)uOperation, &ReqReply, cbReq, &Result); + } - /* Send an empty message. */ - xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0); - xpc_connection_send_message(pSession->hXpcCon, hObjPoke); - xpc_release(hObjPoke); - } - return; - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_ABORT_WAIT: + if (Result.fSendPokeNow) + { + /* Send an empty message. */ + xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0); + xpc_connection_send_message(pSession->hXpcCon, hObjPoke); + xpc_release(hObjPoke); + } + + if (!Result.fSendReply) + { + if (hObjReply) + xpc_release(hObjReply); + return; + } + + if (!hObjReply) + { + xpc_connection_cancel(hCon); + return; + } + if (RT_SUCCESS(rc) && Result.pRing3Buf) + { + /* This is special as we need to return a shared memory segment. */ + PINTNETR3SHMEM pShMem = intnetR3FindShMemByPtr(pSession, Result.pRing3Buf); + if (pShMem) + { + xpc_object_t hObjShMem = xpc_shmem_create(pShMem->pv, pShMem->cb); + if (hObjShMem) { - if (cbReq == sizeof(INTNETIFABORTWAITREQ)) - { - ASMAtomicXchgBool(&pSession->fRecvWait, false); - if (ASMAtomicXchgBool(&pSession->fRecvAvail, false)) - { - /* Send an empty message. */ - xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0); - xpc_connection_send_message(pSession->hXpcCon, hObjPoke); - xpc_release(hObjPoke); - } - cbReply = sizeof(INTNETIFABORTWAITREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; + xpc_dictionary_set_value(hObjReply, "buf-ptr", hObjShMem); + xpc_release(hObjShMem); } - default: - rc = VERR_INVALID_PARAMETER; + else + rc = VERR_NO_MEMORY; } + else + rc = VERR_INTERNAL_ERROR; } - xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj); xpc_dictionary_set_uint64(hObjReply, "rc", INTNET_R3_SVC_SET_RC(rc)); - xpc_dictionary_set_data(hObjReply, "reply", &ReqReply, cbReply); + size_t const cbXpcReply = uOperation == VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS ? 0 : Result.cbReply; + xpc_dictionary_set_data(hObjReply, "reply", &ReqReply, cbXpcReply); xpc_connection_send_message(hCon, hObjReply); xpc_release(hObjReply); } @@ -615,18 +1159,37 @@ static void intnetR3RequestProcess(xpc_connection_t hCon, xpc_object_t hObj, PSU static DECLCALLBACK(void) xpcConnHandler(xpc_connection_t hXpcCon) { + if (!intnetR3TryReserveSlot(&g_DevExt.cRefs, g_DevExt.cMaxConnections)) + { + xpc_connection_cancel(hXpcCon); + return; + } + + PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession)); + if (!pSession) + { + intnetR3ReleaseConnectionSlot(&g_DevExt); + xpc_connection_cancel(hXpcCon); + return; + } + pSession->pDevExt = &g_DevExt; + pSession->hXpcCon = hXpcCon; + xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) { - PSUPDRVSESSION pSession = (PSUPDRVSESSION)xpc_connection_get_context(hXpcCon); + PSUPDRVSESSION pSessionCtx = (PSUPDRVSESSION)xpc_connection_get_context(hXpcCon); + if (!pSessionCtx) + return; - if (xpc_get_type(hObj) == XPC_TYPE_ERROR) + xpc_type_t const hType = xpc_get_type(hObj); + if (hType == XPC_TYPE_ERROR) { if (hObj == XPC_ERROR_CONNECTION_INVALID) - intnetR3SessionDestroy(pSession); + intnetR3SessionDestroy(pSessionCtx); else if (hObj == XPC_ERROR_TERMINATION_IMMINENT) { - PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + PSUPDRVDEVEXT pDevExt = pSessionCtx->pDevExt; - uint32_t cRefs = intnetR3SessionDestroy(pSession); + uint32_t cRefs = intnetR3SessionDestroy(pSessionCtx); if (!cRefs) { /* Last one cleans up the global data. */ @@ -634,39 +1197,539 @@ static DECLCALLBACK(void) xpcConnHandler(xpc_connection_t hXpcCon) } } } + else if (hType == XPC_TYPE_DICTIONARY) + intnetR3RequestProcess(hXpcCon, hObj, pSessionCtx); else - intnetR3RequestProcess(hXpcCon, hObj, pSession); + xpc_connection_cancel(hXpcCon); }); - PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession)); - if (pSession) + xpc_connection_set_context(hXpcCon, pSession); + xpc_transaction_begin(); + xpc_connection_resume(hXpcCon); +} + +#else /* !RT_OS_DARWIN */ + +# if !defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) +static int intnetR3LocalIpcGetServiceName(char *pszService, size_t cbService) +{ +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestService = RTEnvGet("VBOX_INTNET_R3_SVC_NAME"); + if (pszTestService && *pszTestService) + return RTStrCopy(pszService, cbService, pszTestService); +# endif + + char szUser[256]; + int rc = RTProcQueryUsername(RTProcSelf(), szUser, sizeof(szUser), NULL /*pcbUser*/); + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pszService, cbService, "%s-%08RX32", INTNET_R3_SVC_NAME, RTStrHash1(szUser)); + if (cch < 0 || (size_t)cch >= cbService) + rc = VERR_BUFFER_OVERFLOW; + } + return rc; +} +# endif + + +/** Verifies that a Local IPC client belongs to the user running the switch. */ +static int intnetR3LocalIpcVerifyPeer(RTLOCALIPCSESSION hSession) +{ + int rc = RTLocalIpcSessionVerifySameUser(hSession); +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; +# endif + return rc; +} + + +# if !defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) +static int intnetR3LocalIpcAcquireLock(PRTFILE phLock) +{ + *phLock = NIL_RTFILE; +# ifdef RT_OS_WINDOWS + /* The pipe namespace is scoped to the login session and the first pipe + instance is created atomically, so a user-global file lock is wrong. */ + return VINF_SUCCESS; +# else + char szLock[RTPATH_MAX]; +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestLock = RTEnvGet("VBOX_INTNET_R3_SWITCH_LOCK_FILE"); + int rc; + if (pszTestLock && *pszTestLock) + rc = RTStrCopy(szLock, sizeof(szLock), pszTestLock); + else + { + char szHome[RTPATH_MAX]; + rc = RTPathUserHome(szHome, sizeof(szHome)); + if (RT_SUCCESS(rc) && !RTPathStartsWithRoot(szHome)) + rc = VERR_INVALID_NAME; + if (RT_SUCCESS(rc)) + rc = RTPathReal(szHome, szLock, sizeof(szLock)); + } +# else + char szHome[RTPATH_MAX]; + int rc = RTPathUserHome(szHome, sizeof(szHome)); + if (RT_SUCCESS(rc) && !RTPathStartsWithRoot(szHome)) + rc = VERR_INVALID_NAME; + if (RT_SUCCESS(rc)) + rc = RTPathReal(szHome, szLock, sizeof(szLock)); +# endif +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (RT_SUCCESS(rc) && (!pszTestLock || !*pszTestLock)) +# else + if (RT_SUCCESS(rc)) +# endif + rc = RTPathAppend(szLock, sizeof(szLock), ".VirtualBox"); +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (RT_SUCCESS(rc) && (!pszTestLock || !*pszTestLock)) +# else + if (RT_SUCCESS(rc)) +# endif + rc = RTDirCreateFullPath(szLock, 0700); +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (RT_SUCCESS(rc) && (!pszTestLock || !*pszTestLock)) +# else + if (RT_SUCCESS(rc)) +# endif { - pSession->pDevExt = &g_DevExt; - pSession->hXpcCon = hXpcCon; + RTFSOBJINFO ObjInfo; + rc = RTPathQueryInfoEx(szLock, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + if ( !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode) + || ObjInfo.Attr.u.Unix.uid != (RTUID)geteuid() + || (ObjInfo.Attr.fMode & (RTFS_UNIX_IWUSR | RTFS_UNIX_IXUSR)) + != (RTFS_UNIX_IWUSR | RTFS_UNIX_IXUSR) + || (ObjInfo.Attr.fMode & (RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH))) + rc = VERR_ACCESS_DENIED; + } + } +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (RT_SUCCESS(rc) && (!pszTestLock || !*pszTestLock)) +# else + if (RT_SUCCESS(rc)) +# endif + rc = RTPathAppend(szLock, sizeof(szLock), "VBoxIntNetSwitch.lock"); + if (RT_SUCCESS(rc)) + rc = RTFileOpen(phLock, szLock, RTFILE_O_READWRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_NONE + | RTFILE_O_NO_SYMLINKS + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + rc = RTFileLock(*phLock, RTFILE_LOCK_WRITE | RTFILE_LOCK_IMMEDIATELY, 0 /*offLock*/, 1 /*cbLock*/); + if (RT_FAILURE(rc)) + { + RTFileClose(*phLock); + *phLock = NIL_RTFILE; + } + } + return rc; +# endif +} +# endif - xpc_connection_set_context(hXpcCon, pSession); - xpc_connection_resume(hXpcCon); - xpc_transaction_begin(); - ASMAtomicIncU32(&g_DevExt.cRefs); +static int intnetR3LocalIpcSendHdrAndPayload(PSUPDRVSESSION pSession, PCINTNETR3IPCREPLYHDR pHdr, + const void *pvReply, size_t cbReply, + const char *pszShMemName, size_t cbShMemName) +{ + int rc = RTSemMutexRequest(pSession->hIpcIoMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcSessionWrite(pSession->hIpcSession, pHdr, sizeof(*pHdr)); + if (RT_SUCCESS(rc) && cbReply) + rc = RTLocalIpcSessionWrite(pSession->hIpcSession, pvReply, cbReply); + if (RT_SUCCESS(rc) && cbShMemName) + rc = RTLocalIpcSessionWrite(pSession->hIpcSession, pszShMemName, cbShMemName); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(pSession->hIpcSession); + RTSemMutexRelease(pSession->hIpcIoMtx); } + return rc; } -int main(int argc, char **argv) +static int intnetR3LocalIpcSendReply(PSUPDRVSESSION pSession, int rcReq, const void *pvReply, size_t cbReply, + const char *pszShMemName, size_t cbShMem) { - int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); + size_t const cbShMemName = pszShMemName ? strlen(pszShMemName) + 1 : 0; + AssertReturn(cbShMemName <= INTNET_R3_IPC_MAX_SHMEM_NAME, VERR_BUFFER_OVERFLOW); + AssertReturn(cbReply <= INTNET_R3_IPC_MAX_REQ, VERR_BUFFER_OVERFLOW); + INTNETR3IPCREPLYHDR Hdr; + Hdr.u32Magic = INTNET_R3_IPC_REPLY_MAGIC; + Hdr.u16Version = INTNET_R3_IPC_VERSION; + Hdr.cbHdr = sizeof(Hdr); + Hdr.rc = rcReq; + Hdr.cbReply = (uint32_t)cbReply; + Hdr.cbShMemName = (uint32_t)cbShMemName; + Hdr.cbShMem = cbShMem; + return intnetR3LocalIpcSendHdrAndPayload(pSession, &Hdr, pvReply, cbReply, pszShMemName, cbShMemName); +} + + +static int intnetR3LocalIpcSendPoke(PSUPDRVSESSION pSession) +{ +# ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + /* Deterministically models a notification write blocked by a non-reading peer. */ + const char *pszTestBlockFile = RTEnvGet("VBOX_INTNET_R3_TEST_POKE_BLOCK_FILE"); + while ( pszTestBlockFile + && *pszTestBlockFile + && RTFileExists(pszTestBlockFile)) + { + if (ASMAtomicReadBool(&pSession->fIpcPokeStopping)) + return VERR_CANCELLED; + RTThreadSleep(1); + } +# endif + + INTNETR3IPCREPLYHDR Hdr; + Hdr.u32Magic = INTNET_R3_IPC_POKE_MAGIC; + Hdr.u16Version = INTNET_R3_IPC_VERSION; + Hdr.cbHdr = sizeof(Hdr); + Hdr.rc = VINF_SUCCESS; + Hdr.cbReply = 0; + Hdr.cbShMemName = 0; + Hdr.cbShMem = 0; + return intnetR3LocalIpcSendHdrAndPayload(pSession, &Hdr, NULL, 0, NULL, 0); +} + + +/** Writes queued receive notifications outside the IntNet delivery callback. */ +static DECLCALLBACK(int) intnetR3LocalIpcPokeThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PSUPDRVSESSION pSession = (PSUPDRVSESSION)pvUser; + PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + uint32_t const cMaxThreads = pDevExt->cMaxThreads; + + int rc = VINF_SUCCESS; + for (;;) + { + rc = RTSemEventWait(pSession->hIpcPokeEvt, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + { + if (!ASMAtomicReadBool(&pSession->fIpcPokeStopping)) + RTLocalIpcSessionCancel(pSession->hIpcSession); + break; + } + if (ASMAtomicReadBool(&pSession->fIpcPokeStopping)) + { + rc = VINF_SUCCESS; + break; + } + + rc = intnetR3LocalIpcSendPoke(pSession); + if (RT_FAILURE(rc)) + { + RTLocalIpcSessionCancel(pSession->hIpcSession); + break; + } + } + + if (ASMAtomicReadBool(&pSession->fIpcPokeStopping)) + rc = VINF_SUCCESS; + uint32_t const cThreads = ASMAtomicDecU32(&pDevExt->cThreads); + Assert(cThreads < cMaxThreads); + RT_NOREF(cThreads); + return rc; +} + + +/** Reads exactly @a cbToRead bytes without letting partial progress reset the deadline. */ +static int intnetR3LocalIpcReadExact(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, uint64_t msDeadline) +{ + uint8_t *pbDst = (uint8_t *)pvBuf; + while (cbToRead > 0) + { + size_t cbRead = 0; + int rc = RTLocalIpcSessionReadNB(hSession, pbDst, cbToRead, &cbRead); + if (rc == VINF_SUCCESS && cbRead > 0) + { + AssertReturn(cbRead <= cbToRead, VERR_INTERNAL_ERROR); + pbDst += cbRead; + cbToRead -= cbRead; + continue; + } + if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN) + return rc; + + uint64_t const msNow = RTTimeMilliTS(); + if (msNow >= msDeadline) + return VERR_TIMEOUT; + uint64_t const cMsLeft = msDeadline - msNow; + rc = RTLocalIpcSessionWaitForData(hSession, (uint32_t)RT_MIN(cMsLeft, (uint64_t)UINT32_MAX)); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +static int intnetR3LocalIpcProcessRequest(PSUPDRVSESSION pSession, uint32_t uOperation, const void *pvReq, size_t cbReq, + uint32_t *pcOpenIfs) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pvReq, VERR_INVALID_POINTER); + AssertPtrReturn(pcOpenIfs, VERR_INVALID_POINTER); + AssertReturn(cbReq >= sizeof(SUPVMMR0REQHDR), VERR_INVALID_PARAMETER); + + INTNETR3REQREPLY ReqReply; + INTNETR3REQRESULT Result; + + RT_ZERO(ReqReply); + RT_ZERO(Result); + Result.fSendReply = true; + memcpy(&ReqReply, pvReq, RT_MIN(sizeof(ReqReply), cbReq)); + + PSUPVMMR0REQHDR pReqHdr = (PSUPVMMR0REQHDR)&ReqReply; + AssertReturn(pReqHdr->u32Magic == SUPVMMR0REQHDR_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pReqHdr->cbReq == cbReq, VERR_INVALID_PARAMETER); + + const char *pszShMemName = NULL; + size_t cbShMem = 0; + + int rcReq = intnetR3ProcessRequestCore(pSession, uOperation, &ReqReply, cbReq, &Result); + if (RT_SUCCESS(rcReq) && Result.pRing3Buf) + { + PINTNETR3SHMEM pShMem = intnetR3FindShMemByPtr(pSession, Result.pRing3Buf); + if (pShMem) + { + pszShMemName = pShMem->szName; + cbShMem = pShMem->cb; + } + else + rcReq = VERR_INTERNAL_ERROR; + } + + if (Result.fSendPokeNow) + { + int const rcPoke = intnetR3LocalIpcSendPoke(pSession); + if (!Result.fSendReply) + return rcPoke; + } + + if (Result.fSendReply) + { + int const rc = intnetR3LocalIpcSendReply(pSession, rcReq, &ReqReply, Result.cbReply, pszShMemName, cbShMem); + if (RT_SUCCESS(rc) && RT_SUCCESS(rcReq)) + { + if (uOperation == VMMR0_DO_INTNET_OPEN) + (*pcOpenIfs)++; + else if (uOperation == VMMR0_DO_INTNET_IF_CLOSE && *pcOpenIfs > 0) + (*pcOpenIfs)--; + } + return rc; + } + return rcReq; +} + + +static DECLCALLBACK(int) intnetR3LocalIpcSessionThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PSUPDRVSESSION pSession = (PSUPDRVSESSION)pvUser; + bool fPeerVerified = false; + uint32_t cOpenIfs = 0; + for (;;) + { + uint32_t const cMsFirstByte = cOpenIfs > 0 ? RT_INDEFINITE_WAIT : pSession->pDevExt->cMsReadTimeout; + int rc = RTLocalIpcSessionWaitForData(pSession->hIpcSession, cMsFirstByte); + if (RT_FAILURE(rc)) + break; + + uint64_t const msDeadline = RTTimeMilliTS() + pSession->pDevExt->cMsReadTimeout; + INTNETR3IPCREQHDR Hdr; + rc = intnetR3LocalIpcReadExact(pSession->hIpcSession, &Hdr, sizeof(Hdr), msDeadline); + if (RT_FAILURE(rc)) + break; + if (!fPeerVerified) + { + rc = intnetR3LocalIpcVerifyPeer(pSession->hIpcSession); + if (RT_FAILURE(rc)) + break; + fPeerVerified = true; + } + if ( Hdr.u32Magic != INTNET_R3_IPC_REQ_MAGIC + || Hdr.u16Version != INTNET_R3_IPC_VERSION + || Hdr.cbHdr != sizeof(Hdr) + || Hdr.cbReq < sizeof(SUPVMMR0REQHDR) + || Hdr.cbReq > INTNET_R3_IPC_MAX_REQ) + break; + + void *pvReq = RTMemTmpAlloc(Hdr.cbReq); + if (!pvReq) + break; + rc = intnetR3LocalIpcReadExact(pSession->hIpcSession, pvReq, Hdr.cbReq, msDeadline); + if (RT_SUCCESS(rc)) + rc = intnetR3LocalIpcProcessRequest(pSession, Hdr.uOperation, pvReq, Hdr.cbReq, &cOpenIfs); + RTMemTmpFree(pvReq); + if (RT_FAILURE(rc)) + break; + } + PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + uint32_t const cMaxThreads = pDevExt->cMaxThreads; + intnetR3SessionDestroy(pSession); + uint32_t const cThreads = ASMAtomicDecU32(&pDevExt->cThreads); + Assert(cThreads < cMaxThreads); + RT_NOREF(cThreads); + return VINF_SUCCESS; +} + + +# if !defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) +static int intnetR3LocalIpcRun(const char *pszService) +{ + RTFILE hLock = NIL_RTFILE; + int rc = intnetR3LocalIpcAcquireLock(&hLock); + if (RT_FAILURE(rc)) + return rc; + + RTLOCALIPCSERVER hServer = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hServer, pszService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); +# ifndef RT_OS_WINDOWS if (RT_SUCCESS(rc)) + rc = RTLocalIpcServerSetAccessMode(hServer, RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR); +# endif + if (RT_FAILURE(rc)) { - IntNetR0Init(); + if (hServer != NIL_RTLOCALIPCSERVER) + RTLocalIpcServerDestroy(hServer); + RTFileClose(hLock); + return rc; + } +# ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + g_hTestIpcServer = hServer; +# endif - g_DevExt.pObjs = NULL; - rc = RTCritSectInit(&g_DevExt.CritSect); + for (;;) + { + RTLOCALIPCSESSION hClient = NIL_RTLOCALIPCSESSION; + rc = RTLocalIpcServerListen(hServer, &hClient); +# ifdef RT_OS_WINDOWS + if (rc == VERR_TRY_AGAIN) + { + RTThreadSleep(10); + continue; + } +# endif + if (RT_FAILURE(rc)) + break; + if (!intnetR3TryReserveSlot(&g_DevExt.cRefs, g_DevExt.cMaxConnections)) + { + RTLocalIpcSessionClose(hClient); + RTThreadSleep(10); + continue; + } + PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession)); + if (!pSession) + { + RTLocalIpcSessionClose(hClient); + intnetR3ReleaseConnectionSlot(&g_DevExt); + continue; + } + pSession->pDevExt = &g_DevExt; + pSession->hIpcSession = hClient; + pSession->hThread = NIL_RTTHREAD; + pSession->hIpcPokeThread = NIL_RTTHREAD; + pSession->hIpcPokeEvt = NIL_RTSEMEVENT; + pSession->fIpcPokeStopping = false; + pSession->hIpcIoMtx = NIL_RTSEMMUTEX; + rc = RTSemMutexCreate(&pSession->hIpcIoMtx); if (RT_SUCCESS(rc)) - xpc_main(xpcConnHandler); /* Never returns. */ + rc = RTSemEventCreate(&pSession->hIpcPokeEvt); + if (RT_FAILURE(rc)) + { + intnetR3SessionDestroy(pSession); + continue; + } - exit(EXIT_FAILURE); + if (!intnetR3TryReserveSlots(&g_DevExt.cThreads, g_DevExt.cMaxThreads, + INTNETR3_THREADS_PER_LOCALIPC_SESSION)) + { + intnetR3SessionDestroy(pSession); + RTThreadSleep(10); + continue; + } + + rc = RTThreadCreate(&pSession->hIpcPokeThread, intnetR3LocalIpcPokeThread, pSession, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetPoke"); + if (RT_FAILURE(rc)) + { + intnetR3SessionDestroy(pSession); + ASMAtomicSubU32(&g_DevExt.cThreads, INTNETR3_THREADS_PER_LOCALIPC_SESSION); + continue; + } + rc = RTThreadCreate(&pSession->hThread, intnetR3LocalIpcSessionThread, pSession, 0 /*cbStack*/, + RTTHREADTYPE_IO, 0 /*fFlags*/, "IntNetIpc"); + if (RT_FAILURE(rc)) + { + intnetR3SessionDestroy(pSession); + ASMAtomicDecU32(&g_DevExt.cThreads); /* Reserved request worker was never started. */ + } } +# ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + g_hTestIpcServer = NIL_RTLOCALIPCSERVER; +# endif + RTLocalIpcServerDestroy(hServer); - return RTMsgInitFailure(rc); + /* + * The session workers are detached. A fatal listener failure must not + * let main tear down the IntNet globals while any worker still uses them. + * Closing the listener prevents new sessions; existing clients can then + * finish normally, however long that takes. + */ + while ( ASMAtomicReadU32(&g_DevExt.cRefs) != 0 + || ASMAtomicReadU32(&g_DevExt.cThreads) != 0) + RTThreadSleep(1); + + RTFileClose(hLock); + return rc; } +# endif /* !VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH */ +#endif /* !RT_OS_DARWIN || VBOX_INTNET_TESTCASE_LOCALIPC */ + +#if !defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) +int main(int argc, char **argv) +{ +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); +#else + int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/); +#endif + if (RT_SUCCESS(rc)) + { + rc = IntNetR0Init(); + if (RT_SUCCESS(rc)) + { + g_DevExt.pObjs = NULL; + rc = RTCritSectInit(&g_DevExt.CritSect); + if (RT_SUCCESS(rc)) + { + intnetR3InitLimits(&g_DevExt); +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + xpc_main(xpcConnHandler); /* Never returns. */ + rc = VERR_INTERNAL_ERROR; +#else + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; + rc = intnetR3LocalIpcGetServiceName(szService, sizeof(szService)); + if (RT_SUCCESS(rc)) + rc = intnetR3LocalIpcRun(szService); /* Normally never returns. */ +# ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + if (rc == VERR_CANCELLED) + rc = VINF_SUCCESS; +# endif +#endif + int const rc2 = RTCritSectDelete(&g_DevExt.CritSect); + AssertRC(rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + IntNetR0Term(); + } + } + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTMsgInitFailure(rc); +} +#endif /* !VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH */ From 3e99204bdbdfe6e688f609d2fbc13d4ae6b15e93 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 21:59:49 +0000 Subject: [PATCH 024/176] =?UTF-8?q?NetworkServices/IntNet:=20More=20code?= =?UTF-8?q?=20for=20the=20common=20R3=20IntNet=20client=20interface.=20?= =?UTF-8?q?=E2=80=8Bbugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174715 --- src/VBox/NetworkServices/NetLib/IntNetIf.cpp | 1062 +++++++++++++++++- src/VBox/NetworkServices/NetLib/IntNetIf.h | 11 +- 2 files changed, 1014 insertions(+), 59 deletions(-) diff --git a/src/VBox/NetworkServices/NetLib/IntNetIf.cpp b/src/VBox/NetworkServices/NetLib/IntNetIf.cpp index 867de809d828..98a280455f26 100644 --- a/src/VBox/NetworkServices/NetLib/IntNetIf.cpp +++ b/src/VBox/NetworkServices/NetLib/IntNetIf.cpp @@ -1,4 +1,4 @@ -/* $Id: IntNetIf.cpp 114205 2026-05-29 13:56:57Z andreas.loeffler@oracle.com $ */ +/* $Id: IntNetIf.cpp 114878 2026-08-06 21:59:49Z andreas.loeffler@oracle.com $ */ /** @file * IntNetIfCtx - Abstract API implementing an IntNet connection using the R0 support driver or some R3 IPC variant. */ @@ -30,16 +30,32 @@ * Header Files * *********************************************************************************************************************************/ #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) -# if defined(RT_OS_DARWIN) +# if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# define INTNETIF_WITH_R3_SVC_XPC # include /* This needs to be here because it drags PVM in and cdefs.h needs to undefine it... */ +# elif defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) \ + && (defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(VBOX_INTNET_TESTCASE_LOCALIPC)) +# define INTNETIF_WITH_R3_SVC_LOCALIPC # else -# error "R3 internal networking not implemented for this platform yet!" +# error "No enabled R3 internal networking transport for this platform!" # endif #endif #include +#include +#include #include +#include #include +#include +#include +#include +#if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) +# include +# include +# include +# include +#endif #include #include @@ -70,16 +86,49 @@ typedef struct INTNETIFCTXINT INTNETIFHANDLE hIf; /** The internal network buffer. */ PINTNETBUF pBuf; + /** Whether this context owns one SUPR3Init reference. */ + bool fSupInited; #if defined (VBOX_WITH_INTNET_SERVICE_IN_R3) /** Flag whether this interface is using the internal network switch in userspace path. */ bool fIntNetR3Svc; /** Receive event semaphore. */ RTSEMEVENT hEvtRecv; -# if defined(RT_OS_DARWIN) + /** Whether the context created and owns hEvtRecv. */ + bool fOwnEvtRecv; + /** Set by IntNetR3IfWaitAbort to prevent any subsequent waits. */ + bool volatile fNoMoreWaits; +# if defined(INTNETIF_WITH_R3_SVC_XPC) /** XPC connection handle to the R3 internal network switch service. */ xpc_connection_t hXpcCon; + /** Signalled by XPC's final invalid event after connection cancellation has quiesced callbacks. */ + RTSEMEVENT hEvtXpcCancelled; + /** Size of the communication buffer in bytes. */ + size_t cbBuf; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + /** Local IPC session to the R3 internal network switch service. */ + RTLOCALIPCSESSION hIpcSession; + /** Thread receiving and demultiplexing Local IPC replies and notifications. */ + RTTHREAD hIpcRecvThread; + /** Serializes Local IPC request/reply calls. */ + RTSEMMUTEX hIpcCallMtx; + /** Serializes short socket read and write operations; data waits happen outside it. */ + RTSEMMUTEX hIpcIoMtx; + /** Signalled by the receiver thread when a synchronous reply arrives. */ + RTSEMEVENT hEvtReply; + /** Shared memory handle backing the communication buffer. */ + RTSHMEM hShMemBuf; /** Size of the communication buffer in bytes. */ size_t cbBuf; + /** Most recently received synchronous reply header. */ + INTNETR3IPCREPLYHDR ReplyHdr; + /** Most recently received synchronous reply payload. */ + uint8_t abReply[INTNET_R3_IPC_MAX_REQ]; + /** Shared memory name accompanying the most recent reply. */ + char szReplyShMemName[INTNET_R3_IPC_MAX_SHMEM_NAME]; + /** Status carried by the most recently received notification. */ + int32_t volatile rcRecvPoke; + /** Receiver thread status, set to a failure when the stream terminates. */ + int32_t volatile rcIpcRecv; # endif #endif } INTNETIFCTXINT; @@ -87,10 +136,606 @@ typedef struct INTNETIFCTXINT typedef INTNETIFCTXINT *PINTNETIFCTXINT; +#ifdef VBOX_INTNET_TESTCASE_LOCALIPC +/** Interface paused by the deterministic Wait/Abort race testcase. */ +static PINTNETIFCTXINT g_pTestWaitRaceIf = NULL; +/** Signalled after the test wait has performed its initial no-more-waits check. */ +static RTSEMEVENT g_hTestWaitRaceReached = NIL_RTSEMEVENT; +/** Releases the paused test wait so it performs the serialized check and send. */ +static RTSEMEVENT g_hTestWaitRaceContinue = NIL_RTSEMEVENT; + + +/** Configures the deterministic Wait/Abort race hook used by tstVBoxIntNetR3Switch. */ +DECLHIDDEN(void) intnetR3IfTestSetWaitRace(INTNETIFCTX hIfCtx, RTSEMEVENT hReached, RTSEMEVENT hContinue) +{ + g_hTestWaitRaceReached = hReached; + g_hTestWaitRaceContinue = hContinue; + g_pTestWaitRaceIf = (PINTNETIFCTXINT)hIfCtx; +} +#endif + + /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ +#if defined(INTNETIF_WITH_R3_SVC_XPC) +/** Cancels the XPC connection and waits until its event handler cannot run again. */ +static void intnetR3IfXpcDisconnect(PINTNETIFCTXINT pThis) +{ + if (pThis->hXpcCon) + { + Assert(pThis->hEvtXpcCancelled != NIL_RTSEMEVENT); + xpc_connection_cancel(pThis->hXpcCon); + int const rc = RTSemEventWait(pThis->hEvtXpcCancelled, RT_INDEFINITE_WAIT); + AssertReleaseRC(rc); + xpc_release(pThis->hXpcCon); + pThis->hXpcCon = NULL; + } + if (pThis->hEvtXpcCancelled != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pThis->hEvtXpcCancelled); + pThis->hEvtXpcCancelled = NIL_RTSEMEVENT; + } +} + + +/** Validates an XPC reply and decodes the VBox status code. */ +static int intnetR3IfXpcGetReplyStatus(xpc_object_t hObjReply, int *prcReq) +{ + AssertPtrReturn(prcReq, VERR_INVALID_POINTER); + *prcReq = VERR_INVALID_STATE; + AssertReturn(hObjReply != NULL, VERR_INVALID_STATE); + + xpc_type_t hType = xpc_get_type(hObjReply); + if (hType == XPC_TYPE_ERROR) + return hObjReply == XPC_ERROR_CONNECTION_INTERRUPTED ? VERR_INTERRUPTED : VERR_NET_CONNECTION_REFUSED; + AssertReturn(hType == XPC_TYPE_DICTIONARY, VERR_INVALID_STATE); + + uint64_t const u64Rc = xpc_dictionary_get_uint64(hObjReply, "rc"); + AssertReturn(INTNET_R3_SVC_IS_VALID_RC(u64Rc), VERR_INVALID_STATE); + *prcReq = INTNET_R3_SVC_GET_RC(u64Rc); + return VINF_SUCCESS; +} +#endif + + +#if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) +# ifndef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH +static int intnetR3IfLocalIpcGetServiceName(char *pszService, size_t cbService) +{ +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestService = RTEnvGet("VBOX_INTNET_R3_SVC_NAME"); + if (pszTestService && *pszTestService) + return RTStrCopy(pszService, cbService, pszTestService); +# endif + + char szUser[256]; + int rc = RTProcQueryUsername(RTProcSelf(), szUser, sizeof(szUser), NULL /*pcbUser*/); + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pszService, cbService, "%s-%08RX32", INTNET_R3_SVC_NAME, RTStrHash1(szUser)); + if (cch < 0 || (size_t)cch >= cbService) + rc = VERR_BUFFER_OVERFLOW; + } + return rc; +} +# endif + + +# ifndef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH +static bool intnetR3IfLocalIpcIsServiceAbsent(int rc) +{ + return rc == VERR_FILE_NOT_FOUND + || rc == VERR_PATH_NOT_FOUND + || rc == VERR_NET_CONNECTION_REFUSED + || rc == VERR_PIPE_NOT_CONNECTED; +} + + +static int intnetR3IfLocalIpcStartService(void) +{ + char szExec[RTPATH_MAX]; +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestExec = RTEnvGet("VBOX_INTNET_R3_SWITCH_EXE"); + int rc = pszTestExec && *pszTestExec ? RTStrCopy(szExec, sizeof(szExec), pszTestExec) + : RTPathExecDir(szExec, sizeof(szExec)); +# else + int rc = RTPathExecDir(szExec, sizeof(szExec)); +# endif + if (RT_FAILURE(rc)) + return rc; +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (!pszTestExec || !*pszTestExec) +# endif + { +# ifdef RT_OS_WINDOWS + rc = RTPathAppend(szExec, sizeof(szExec), "VBoxIntNetSwitch.exe"); +# else + rc = RTPathAppend(szExec, sizeof(szExec), "VBoxIntNetSwitch"); +# endif + if (RT_FAILURE(rc)) + return rc; + } + + const char *apszArgs[] = { szExec, NULL }; + RTHANDLE hStdNil; + hStdNil.enmType = RTHANDLETYPE_FILE; + hStdNil.u.hFile = NIL_RTFILE; + uint32_t fFlags = RTPROC_FLAGS_DETACHED; +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestPidFile = RTEnvGet("VBOX_INTNET_R3_SWITCH_PID_FILE"); + bool const fTestWaitable = pszTestPidFile && *pszTestPidFile; + RTPROCESS hTestProcess = NIL_RTPROCESS; + if (fTestWaitable) + fFlags &= ~RTPROC_FLAGS_DETACHED; +# endif +# ifdef RT_OS_WINDOWS + fFlags |= RTPROC_FLAGS_NO_WINDOW; +# endif + rc = RTProcCreateEx(szExec, apszArgs, RTENV_DEFAULT, fFlags, + &hStdNil, &hStdNil, &hStdNil, NULL /*pszAsUser*/, NULL /*pszPassword*/, + NULL /*pvExtraData*/, +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + fTestWaitable ? &hTestProcess : NULL /*phProcess*/); + if (RT_SUCCESS(rc) && fTestWaitable) + { + RTFILE hPidFile = NIL_RTFILE; + rc = RTFileOpen(&hPidFile, pszTestPidFile, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_ALL + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + char szPid[32]; + ssize_t const cchPid = RTStrPrintf2(szPid, sizeof(szPid), "%RU32", hTestProcess); + if (cchPid > 0 && (size_t)cchPid < sizeof(szPid)) + rc = RTFileWrite(hPidFile, szPid, (size_t)cchPid, NULL /*pcbWritten*/); + else + rc = VERR_BUFFER_OVERFLOW; + int const rc2 = RTFileClose(hPidFile); + if (RT_SUCCESS(rc)) + rc = rc2; + } + if (RT_FAILURE(rc)) + { + RTProcTerminate(hTestProcess); + RTProcWait(hTestProcess, RTPROCWAIT_FLAGS_BLOCK, NULL /*pProcStatus*/); + } + } +# else + NULL /*phProcess*/); +# endif + return rc; +} +# endif + + +/** Returns the absolute frame-read timeout, shortened only by Local IPC testcases. */ +static uint32_t intnetR3IfLocalIpcGetFrameTimeout(void) +{ +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestTimeout = RTEnvGet("VBOX_INTNET_R3_TEST_READ_TIMEOUT_MS"); + if (pszTestTimeout && *pszTestTimeout) + { + uint32_t cMsTimeout = 0; + if ( RT_SUCCESS(RTStrToUInt32Full(pszTestTimeout, 10, &cMsTimeout)) + && cMsTimeout > 0) + return cMsTimeout; + } +# endif + return INTNET_R3_IPC_FRAME_TIMEOUT_MS; +} + + +/** Returns the synchronous reply wait timeout, overridden independently only by Local IPC testcases. */ +static uint32_t intnetR3IfLocalIpcGetReplyTimeout(void) +{ +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestTimeout = RTEnvGet("VBOX_INTNET_R3_TEST_REPLY_TIMEOUT_MS"); + if (pszTestTimeout && *pszTestTimeout) + { + uint32_t cMsTimeout = 0; + if ( RT_SUCCESS(RTStrToUInt32Full(pszTestTimeout, 10, &cMsTimeout)) + && cMsTimeout > 0) + return cMsTimeout; + } +# endif + return intnetR3IfLocalIpcGetFrameTimeout(); +} + + +/** Reads exactly @a cbToRead bytes without letting partial progress reset the deadline. */ +static int intnetR3IfLocalIpcReadExact(PINTNETIFCTXINT pThis, void *pvBuf, size_t cbToRead, uint64_t msDeadline) +{ + uint8_t *pbDst = (uint8_t *)pvBuf; + while (cbToRead > 0) + { + size_t cbRead = 0; + int rc = RTLocalIpcSessionReadNB(pThis->hIpcSession, pbDst, cbToRead, &cbRead); + if (rc == VINF_SUCCESS && cbRead > 0) + { + AssertReturn(cbRead <= cbToRead, VERR_INTERNAL_ERROR); + pbDst += cbRead; + cbToRead -= cbRead; + continue; + } + if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN) + return rc; + + uint64_t const msNow = RTTimeMilliTS(); + if (msNow >= msDeadline) + return VERR_TIMEOUT; + uint64_t const cMsLeft = msDeadline - msNow; + rc = RTLocalIpcSessionWaitForData(pThis->hIpcSession, + (uint32_t)RT_MIN(cMsLeft, (uint64_t)UINT32_MAX)); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +/** Local IPC receiver thread. */ +static DECLCALLBACK(int) intnetR3IfLocalIpcRecvThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PINTNETIFCTXINT pThis = (PINTNETIFCTXINT)pvUser; + uint32_t const cMsFrameTimeout = intnetR3IfLocalIpcGetFrameTimeout(); + int rc = VINF_SUCCESS; + + for (;;) + { + rc = RTLocalIpcSessionWaitForData(pThis->hIpcSession, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + rc = RTSemMutexRequest(pThis->hIpcIoMtx, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + uint64_t const msDeadline = RTTimeMilliTS() + cMsFrameTimeout; + INTNETR3IPCREPLYHDR Hdr; + rc = intnetR3IfLocalIpcReadExact(pThis, &Hdr, sizeof(Hdr), msDeadline); + if (RT_FAILURE(rc)) + { + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + if ( Hdr.u16Version != INTNET_R3_IPC_VERSION + || Hdr.cbHdr != sizeof(Hdr)) + { + rc = VERR_VERSION_MISMATCH; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + + if (Hdr.u32Magic == INTNET_R3_IPC_POKE_MAGIC) + { + if (Hdr.cbReply != 0 || Hdr.cbShMemName != 0 || Hdr.cbShMem != 0) + { + rc = VERR_INVALID_PARAMETER; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + ASMAtomicWriteS32(&pThis->rcRecvPoke, Hdr.rc); + RTSemEventSignal(pThis->hEvtRecv); + } + else if (Hdr.u32Magic == INTNET_R3_IPC_REPLY_MAGIC) + { + if ( Hdr.cbReply > sizeof(pThis->abReply) + || Hdr.cbShMemName > sizeof(pThis->szReplyShMemName)) + { + rc = VERR_BUFFER_OVERFLOW; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + + if (Hdr.cbReply) + { + rc = intnetR3IfLocalIpcReadExact(pThis, pThis->abReply, Hdr.cbReply, msDeadline); + if (RT_FAILURE(rc)) + { + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + } + pThis->szReplyShMemName[0] = '\0'; + if (Hdr.cbShMemName) + { + rc = intnetR3IfLocalIpcReadExact(pThis, pThis->szReplyShMemName, Hdr.cbShMemName, msDeadline); + if (RT_FAILURE(rc)) + { + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + if (pThis->szReplyShMemName[Hdr.cbShMemName - 1] != '\0') + { + rc = VERR_INVALID_PARAMETER; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + } + pThis->ReplyHdr = Hdr; + RTSemEventSignal(pThis->hEvtReply); + } + else + { + rc = VERR_INVALID_MAGIC; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + RTSemMutexRelease(pThis->hIpcIoMtx); + } + + ASMAtomicWriteS32(&pThis->rcIpcRecv, rc); + RTSemEventSignal(pThis->hEvtRecv); + RTSemEventSignal(pThis->hEvtReply); + return rc; +} + + +/** Stops the receiver and closes a failed or no-longer-needed Local IPC transport. */ +static void intnetR3IfLocalIpcRetireSession(PINTNETIFCTXINT pThis) +{ + if ( pThis->hIpcSession != NIL_RTLOCALIPCSESSION + && pThis->hIpcRecvThread != NIL_RTTHREAD) + RTLocalIpcSessionCancel(pThis->hIpcSession); + if (pThis->hIpcRecvThread != NIL_RTTHREAD) + { + int const rcThread = RTThreadWait(pThis->hIpcRecvThread, RT_INDEFINITE_WAIT, NULL); + AssertRC(rcThread); + pThis->hIpcRecvThread = NIL_RTTHREAD; + } + if (pThis->hIpcSession != NIL_RTLOCALIPCSESSION) + { + RTLocalIpcSessionClose(pThis->hIpcSession); + pThis->hIpcSession = NIL_RTLOCALIPCSESSION; + } +} + + +static int intnetR3IfLocalIpcSendReq(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMMR0REQHDR pReqHdr) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pReqHdr, VERR_INVALID_POINTER); + int const rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + { + intnetR3IfLocalIpcRetireSession(pThis); + return rcRecv; + } + AssertReturn(pThis->hIpcSession != NIL_RTLOCALIPCSESSION, VERR_INVALID_HANDLE); + AssertReturn(pReqHdr->cbReq >= sizeof(*pReqHdr), VERR_INVALID_PARAMETER); + AssertReturn(pReqHdr->cbReq <= INTNET_R3_IPC_MAX_REQ, VERR_OUT_OF_RANGE); + + size_t const cbMsg = sizeof(INTNETR3IPCREQHDR) + pReqHdr->cbReq; + uint8_t *pbMsg = (uint8_t *)RTMemTmpAlloc(cbMsg); + AssertReturn(pbMsg, VERR_NO_TMP_MEMORY); + + PINTNETR3IPCREQHDR pHdr = (PINTNETR3IPCREQHDR)pbMsg; + pHdr->u32Magic = INTNET_R3_IPC_REQ_MAGIC; + pHdr->u16Version = INTNET_R3_IPC_VERSION; + pHdr->cbHdr = sizeof(*pHdr); + pHdr->cbReq = pReqHdr->cbReq; + pHdr->uOperation = uOperation; + memcpy(pbMsg + sizeof(*pHdr), pReqHdr, pReqHdr->cbReq); + + int rc = RTSemMutexRequest(pThis->hIpcIoMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + int const rcRecvNow = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecvNow)) + rc = rcRecvNow; + else + { + rc = RTLocalIpcSessionWrite(pThis->hIpcSession, pbMsg, cbMsg); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(pThis->hIpcSession); + } + RTSemMutexRelease(pThis->hIpcIoMtx); + if (RT_FAILURE(rcRecvNow)) + intnetR3IfLocalIpcRetireSession(pThis); + } + RTMemTmpFree(pbMsg); + return rc; +} + +static int intnetR3IfLocalIpcReadReply(PINTNETIFCTXINT pThis, void *pvReply, size_t cbReplyMax, size_t *pcbReply, + char *pszShMemName, size_t cbShMemName, size_t *pcbShMem) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + if (pcbReply) + *pcbReply = 0; + if (pszShMemName && cbShMemName) + *pszShMemName = '\0'; + if (pcbShMem) + *pcbShMem = 0; + + int rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + { + intnetR3IfLocalIpcRetireSession(pThis); + return rcRecv; + } + + int rc = RTSemEventWait(pThis->hEvtReply, intnetR3IfLocalIpcGetReplyTimeout()); + if (rc == VERR_TIMEOUT) + { + ASMAtomicCmpXchgS32(&pThis->rcIpcRecv, VERR_TIMEOUT, VINF_SUCCESS); + intnetR3IfLocalIpcRetireSession(pThis); + return rc; + } + if (RT_FAILURE(rc)) + return rc; + + rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + { + intnetR3IfLocalIpcRetireSession(pThis); + return rcRecv; + } + + PCINTNETR3IPCREPLYHDR pHdr = &pThis->ReplyHdr; + AssertReturn(pHdr->cbReply <= cbReplyMax, VERR_BUFFER_OVERFLOW); + AssertReturn(!pHdr->cbShMemName || (pszShMemName && pHdr->cbShMemName <= cbShMemName), VERR_BUFFER_OVERFLOW); + AssertReturn(pHdr->cbShMem == (size_t)pHdr->cbShMem, VERR_OUT_OF_RANGE); + if (pHdr->cbReply) + { + AssertPtrReturn(pvReply, VERR_INVALID_POINTER); + memcpy(pvReply, pThis->abReply, pHdr->cbReply); + } + if (pcbReply) + *pcbReply = pHdr->cbReply; + if (pHdr->cbShMemName) + memcpy(pszShMemName, pThis->szReplyShMemName, pHdr->cbShMemName); + if (pcbShMem) + *pcbShMem = (size_t)pHdr->cbShMem; + return pHdr->rc; +} + +static int intnetR3IfLocalIpcReadPoke(PINTNETIFCTXINT pThis, uint32_t cMillies) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + int rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + return rcRecv; + int rc = RTSemEventWait(pThis->hEvtRecv, cMillies); + if (RT_SUCCESS(rc)) + { + rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + return rcRecv; + rc = ASMAtomicReadS32(&pThis->rcRecvPoke); + } + return rc; +} + + +/** Executes a synchronous Local IPC call while the caller owns hIpcCallMtx. */ +static int intnetR3IfLocalIpcCallLocked(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMMR0REQHDR pReqHdr) +{ + size_t const cbReq = pReqHdr->cbReq; + int rc = intnetR3IfLocalIpcSendReq(pThis, uOperation, pReqHdr); + if (RT_SUCCESS(rc)) + { + size_t cbReply = 0; + rc = intnetR3IfLocalIpcReadReply(pThis, pReqHdr, cbReq, &cbReply, NULL, 0, NULL); + AssertStmt(RT_FAILURE(rc) || cbReply == cbReq, rc = VERR_INVALID_PARAMETER); + } + return rc; +} + + +static bool intnetR3IfLocalIpcIsRingValid(PCINTNETBUF pBuf, INTNETRINGBUF const *pRing, + uint32_t offRing, uint32_t cbRing, uint32_t offExpected) +{ + uint64_t const offStart = (uint64_t)offRing + pRing->offStart; + uint64_t const offEnd = (uint64_t)offRing + pRing->offEnd; + return offStart == offExpected + && offEnd == offStart + cbRing + && offEnd <= pBuf->cbBuf + && pRing->offReadX >= pRing->offStart + && pRing->offReadX < pRing->offEnd + && pRing->offWriteCom >= pRing->offStart + && pRing->offWriteCom < pRing->offEnd + && pRing->offWriteInt >= pRing->offStart + && pRing->offWriteInt < pRing->offEnd + && RT_ALIGN_32(pRing->offReadX, INTNETHDR_ALIGNMENT) == pRing->offReadX + && RT_ALIGN_32(pRing->offWriteCom, INTNETHDR_ALIGNMENT) == pRing->offWriteCom + && RT_ALIGN_32(pRing->offWriteInt, INTNETHDR_ALIGNMENT) == pRing->offWriteInt; +} + + +static bool intnetR3IfLocalIpcIsBufferValid(PCINTNETBUF pBuf, size_t cbMapped) +{ + if ( pBuf->u32Magic != INTNETBUF_MAGIC + || pBuf->cbBuf != cbMapped + || pBuf->cbBuf < sizeof(*pBuf) + || pBuf->cbRecv < INTNETHDR_ALIGNMENT + || pBuf->cbSend < INTNETHDR_ALIGNMENT + || RT_ALIGN_32(pBuf->cbRecv, INTNETRINGBUF_ALIGNMENT) != pBuf->cbRecv + || RT_ALIGN_32(pBuf->cbSend, INTNETRINGBUF_ALIGNMENT) != pBuf->cbSend) + return false; + + uint32_t const offRecv = RT_UOFFSETOF(INTNETBUF, Recv); + uint32_t const offSend = RT_UOFFSETOF(INTNETBUF, Send); + uint32_t const offRecvStart = RT_ALIGN_32(sizeof(*pBuf), INTNETRINGBUF_ALIGNMENT); + uint64_t const offSendStart = (uint64_t)offRecvStart + pBuf->cbRecv; + if (offSendStart > UINT32_MAX) + return false; + return intnetR3IfLocalIpcIsRingValid(pBuf, &pBuf->Recv, offRecv, pBuf->cbRecv, offRecvStart) + && intnetR3IfLocalIpcIsRingValid(pBuf, &pBuf->Send, offSend, pBuf->cbSend, (uint32_t)offSendStart); +} + + +static void intnetR3IfLocalIpcDisconnect(PINTNETIFCTXINT pThis) +{ + intnetR3IfLocalIpcRetireSession(pThis); + if (pThis->hEvtReply != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pThis->hEvtReply); + pThis->hEvtReply = NIL_RTSEMEVENT; + } + if (pThis->hIpcCallMtx != NIL_RTSEMMUTEX) + { + RTSemMutexDestroy(pThis->hIpcCallMtx); + pThis->hIpcCallMtx = NIL_RTSEMMUTEX; + } + if (pThis->hIpcIoMtx != NIL_RTSEMMUTEX) + { + RTSemMutexDestroy(pThis->hIpcIoMtx); + pThis->hIpcIoMtx = NIL_RTSEMMUTEX; + } +} + + +static int intnetR3IfLocalIpcConnect(PINTNETIFCTXINT pThis, const char *pszService) +{ + int rc = RTSemEventCreate(&pThis->hEvtReply); + if (RT_SUCCESS(rc)) + rc = RTSemMutexCreate(&pThis->hIpcCallMtx); + if (RT_SUCCESS(rc)) + rc = RTSemMutexCreate(&pThis->hIpcIoMtx); + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcSessionConnect(&pThis->hIpcSession, pszService, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); +# ifndef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH + if (intnetR3IfLocalIpcIsServiceAbsent(rc)) + { + int const rcStart = intnetR3IfLocalIpcStartService(); + if (RT_SUCCESS(rcStart)) + { + uint64_t const msStart = RTTimeMilliTS(); + do + { + RTThreadSleep(10); + rc = RTLocalIpcSessionConnect(&pThis->hIpcSession, pszService, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + } while ( ( intnetR3IfLocalIpcIsServiceAbsent(rc) + || rc == VERR_ACCESS_DENIED /* Endpoint security setup may still be completing. */) + && RTTimeMilliTS() - msStart < RT_MS_5SEC); + } + else + rc = rcStart; + } +# endif + } + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionVerifySameUser(pThis->hIpcSession); +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; +# endif + if (RT_SUCCESS(rc)) + rc = RTThreadCreate(&pThis->hIpcRecvThread, intnetR3IfLocalIpcRecvThread, pThis, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetRx"); + if (RT_FAILURE(rc)) + intnetR3IfLocalIpcDisconnect(pThis); + return rc; +} +#endif /* INTNETIF_WITH_R3_SVC_LOCALIPC */ + /** * Calls the internal networking switch service living in either R0 or in another R3 process. * @@ -104,23 +749,42 @@ static int intnetR3IfCallSvc(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMM #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) if (pThis->fIntNetR3Svc) { -# if defined(RT_OS_DARWIN) - size_t cbReq = pReqHdr->cbReq; +# if defined(INTNETIF_WITH_R3_SVC_XPC) + size_t const cbReq = pReqHdr->cbReq; xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); + AssertReturn(hObj != NULL, VERR_NO_MEMORY); xpc_dictionary_set_uint64(hObj, "req-id", uOperation); xpc_dictionary_set_data(hObj, "req", pReqHdr, pReqHdr->cbReq); xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj); xpc_release(hObj); - int rc = (int)xpc_dictionary_get_int64(hObjReply, "rc"); - - size_t cbReply = 0; - const void *pvData = xpc_dictionary_get_data(hObjReply, "reply", &cbReply); - AssertRelease(cbReply == cbReq); - memcpy(pReqHdr, pvData, cbReq); - xpc_release(hObjReply); - + int rcReq = VERR_INVALID_STATE; + int rc = intnetR3IfXpcGetReplyStatus(hObjReply, &rcReq); + if (RT_SUCCESS(rc)) + { + size_t cbReply = 0; + const void *pvData = xpc_dictionary_get_data(hObjReply, "reply", &cbReply); + if (pvData && cbReply == cbReq) + { + memcpy(pReqHdr, pvData, cbReq); + rc = rcReq; + } + else + rc = VERR_INVALID_PARAMETER; + } + if (hObjReply) + xpc_release(hObjReply); return rc; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + int rc = RTSemMutexRequest(pThis->hIpcCallMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + rc = intnetR3IfLocalIpcCallLocked(pThis, uOperation, pReqHdr); + RTSemMutexRelease(pThis->hIpcCallMtx); + } + return rc; +# else + return VERR_SUP_DRIVERLESS; # endif } else @@ -131,7 +795,7 @@ static int intnetR3IfCallSvc(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMM } -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) +#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) /** * Calls the internal networking switch service living in either R0 or in another R3 process. * @@ -144,12 +808,28 @@ static int intnetR3IfCallSvcAsync(PINTNETIFCTXINT pThis, uint32_t uOperation, PS { if (pThis->fIntNetR3Svc) { +# if defined(INTNETIF_WITH_R3_SVC_XPC) xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_uint64(hObj, "req-id", uOperation); xpc_dictionary_set_data(hObj, "req", pReqHdr, pReqHdr->cbReq); xpc_connection_send_message(pThis->hXpcCon, hObj); xpc_release(hObj); return VINF_SUCCESS; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + int rc = RTSemMutexRequest(pThis->hIpcCallMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + if ( uOperation == VMMR0_DO_INTNET_IF_WAIT + && ASMAtomicReadBool(&pThis->fNoMoreWaits)) + rc = VERR_SEM_DESTROYED; + else + rc = intnetR3IfLocalIpcSendReq(pThis, uOperation, pReqHdr); + RTSemMutexRelease(pThis->hIpcCallMtx); + } + return rc; +# else + return VERR_SUP_DRIVERLESS; +# endif } else return SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, uOperation, 0, pReqHdr); @@ -178,26 +858,94 @@ static int intnetR3IfMapBufferPointers(PINTNETIFCTXINT pThis) #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) if (pThis->fIntNetR3Svc) { -#if defined(RT_OS_DARWIN) +# if defined(INTNETIF_WITH_R3_SVC_XPC) xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); + AssertReturn(hObj != NULL, VERR_NO_MEMORY); xpc_dictionary_set_uint64(hObj, "req-id", VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS); xpc_dictionary_set_data(hObj, "req", &GetBufferPtrsReq, sizeof(GetBufferPtrsReq)); xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj); xpc_release(hObj); - rc = (int)xpc_dictionary_get_int64(hObjReply, "rc"); + int rcReq = VERR_INVALID_STATE; + rc = intnetR3IfXpcGetReplyStatus(hObjReply, &rcReq); if (RT_SUCCESS(rc)) { - /* Get the shared memory object. */ - xpc_object_t hObjShMem = xpc_dictionary_get_value(hObjReply, "buf-ptr"); - size_t cbMem = xpc_shmem_map(hObjShMem, (void **)&pThis->pBuf); - if (!cbMem) - rc = VERR_NO_MEMORY; - else - pThis->cbBuf = cbMem; + rc = rcReq; + if (RT_SUCCESS(rc)) + { + /* Get the shared memory object. */ + xpc_object_t hObjShMem = xpc_dictionary_get_value(hObjReply, "buf-ptr"); + if (hObjShMem && xpc_get_type(hObjShMem) == XPC_TYPE_SHMEM) + { + size_t const cbMem = xpc_shmem_map(hObjShMem, (void **)&pThis->pBuf); + if (cbMem) + pThis->cbBuf = cbMem; + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + } } - xpc_release(hObjReply); -#endif + if (hObjReply) + xpc_release(hObjReply); +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + char szShMemName[INTNET_R3_IPC_MAX_SHMEM_NAME]; + size_t cbReply = 0; + size_t cbShMem = 0; + rc = RTSemMutexRequest(pThis->hIpcCallMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + rc = intnetR3IfLocalIpcSendReq(pThis, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, &GetBufferPtrsReq.Hdr); + if (RT_SUCCESS(rc)) + rc = intnetR3IfLocalIpcReadReply(pThis, &GetBufferPtrsReq, sizeof(GetBufferPtrsReq), &cbReply, + szShMemName, sizeof(szShMemName), &cbShMem); + RTSemMutexRelease(pThis->hIpcCallMtx); + } + if (RT_FAILURE(rc)) + return rc; + AssertReturn(cbReply == sizeof(GetBufferPtrsReq), VERR_INVALID_PARAMETER); + AssertReturn(cbShMem >= sizeof(INTNETBUF), VERR_INVALID_PARAMETER); + AssertReturn(cbShMem <= UINT32_MAX, VERR_OUT_OF_RANGE); + AssertReturn(szShMemName[0] != '\0', VERR_INVALID_PARAMETER); + + RTSHMEM hShMem = NIL_RTSHMEM; + rc = RTShMemOpen(&hShMem, szShMemName, RTSHMEM_O_F_READWRITE, 0 /*cbMax*/, 1 /*cMappingsHint*/); + if (RT_FAILURE(rc)) + return rc; + + size_t cbShMemActual = 0; + rc = RTShMemQuerySize(hShMem, &cbShMemActual); + if (RT_SUCCESS(rc) && cbShMemActual < cbShMem) + rc = VERR_INVALID_PARAMETER; + if (RT_FAILURE(rc)) + { + RTShMemClose(hShMem); + return rc; + } + + void *pvBuf = NULL; + rc = RTShMemMapRegion(hShMem, 0 /*off*/, cbShMem, RTSHMEM_MAP_F_READ | RTSHMEM_MAP_F_WRITE, &pvBuf); + if (RT_FAILURE(rc)) + { + RTShMemClose(hShMem); + return rc; + } + + PINTNETBUF pBuf = (PINTNETBUF)pvBuf; + if (!intnetR3IfLocalIpcIsBufferValid(pBuf, cbShMem)) + { + RTShMemUnmapRegion(hShMem, pvBuf); + RTShMemClose(hShMem); + return VERR_INVALID_PARAMETER; + } + + pThis->hShMemBuf = hShMem; + pThis->pBuf = pBuf; + pThis->cbBuf = cbShMem; +# else + rc = VERR_SUP_DRIVERLESS; +# endif } else #endif @@ -226,7 +974,11 @@ static void intnetR3IfClose(PINTNETIFCTXINT pThis) pThis->hIf = INTNET_HANDLE_INVALID; int rc = intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_CLOSE, &CloseReq.Hdr); +#if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + AssertMsg(RT_SUCCESS(rc) || RT_FAILURE(ASMAtomicReadS32(&pThis->rcIpcRecv)), ("%Rrc\n", rc)); +#else AssertRC(rc); +#endif } } @@ -238,48 +990,123 @@ DECLHIDDEN(int) IntNetR3IfCreate(PINTNETIFCTX phIfCtx, const char *pszNetwork) } -DECLHIDDEN(int) IntNetR3IfCreateEx(PINTNETIFCTX phIfCtx, const char *pszNetwork, INTNETTRUNKTYPE enmTrunkType, - const char *pszTrunk, uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags) +/** Worker for IntNetR3IfCreateEx and IntNetR3IfCreateExWithRecvEvent. */ +static int intnetR3IfCreateExWorker(PINTNETIFCTX phIfCtx, const char *pszNetwork, INTNETTRUNKTYPE enmTrunkType, + const char *pszTrunk, uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags, + RTSEMEVENT hEvtRecv) { AssertPtrReturn(phIfCtx, VERR_INVALID_POINTER); AssertPtrReturn(pszNetwork, VERR_INVALID_POINTER); AssertPtrReturn(pszTrunk, VERR_INVALID_POINTER); + *phIfCtx = NULL; + +#if !defined(VBOX_WITH_INTNET_SERVICE_IN_R3) + RT_NOREF(hEvtRecv); +#endif PSUPDRVSESSION pSession = NIL_RTR0PTR; + bool fSupInited = false; +#if defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) || defined(VBOX_INTNET_TESTCASE_FORCE_R3) + /* For R3-only testcases, avoid SUPR3Init to prevent SCM operations + and force the driverless R3 service path. */ + int rc = VINF_SUCCESS; +#else int rc = SUPR3Init(&pSession); + if (RT_SUCCESS(rc)) + fSupInited = true; +#endif if (RT_SUCCESS(rc)) { PINTNETIFCTXINT pThis = (PINTNETIFCTXINT)RTMemAllocZ(sizeof(*pThis)); if (RT_LIKELY(pThis)) { pThis->pSupDrvSession = pSession; + pThis->hIf = INTNET_HANDLE_INVALID; + pThis->fSupInited = fSupInited; #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - pThis->hEvtRecv = NIL_RTSEMEVENT; + pThis->hEvtRecv = hEvtRecv; +# if defined(INTNETIF_WITH_R3_SVC_XPC) + pThis->hEvtXpcCancelled = NIL_RTSEMEVENT; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + pThis->hIpcSession = NIL_RTLOCALIPCSESSION; + pThis->hIpcRecvThread = NIL_RTTHREAD; + pThis->hIpcCallMtx = NIL_RTSEMMUTEX; + pThis->hIpcIoMtx = NIL_RTSEMMUTEX; + pThis->hEvtReply = NIL_RTSEMEVENT; + pThis->hShMemBuf = NIL_RTSHMEM; + pThis->rcRecvPoke = VINF_SUCCESS; + pThis->rcIpcRecv = VINF_SUCCESS; +# endif #endif /* Driverless operation needs support for running the internal network switch using IPC. */ - if (SUPR3IsDriverless()) +#if defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) || defined(VBOX_INTNET_TESTCASE_FORCE_R3) + bool const fDriverless = true; +#else + bool const fDriverless = SUPR3IsDriverless(); +#endif + if (fDriverless) { #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) -# if defined(RT_OS_DARWIN) - xpc_connection_t hXpcCon = xpc_connection_create(INTNET_R3_SVC_NAME, NULL); - xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) { - if (xpc_get_type(hObj) == XPC_TYPE_ERROR) + if (pThis->hEvtRecv == NIL_RTSEMEVENT) + { + rc = RTSemEventCreate(&pThis->hEvtRecv); + if (RT_SUCCESS(rc)) + pThis->fOwnEvtRecv = true; + } + if (RT_SUCCESS(rc)) + { +# if defined(INTNETIF_WITH_R3_SVC_XPC) + rc = RTSemEventCreate(&pThis->hEvtXpcCancelled); + if (RT_SUCCESS(rc)) { - /** @todo Error handling - reconnecting. */ + xpc_connection_t hXpcCon = xpc_connection_create(INTNET_R3_SVC_NAME, NULL); + if (hXpcCon) + { + pThis->hXpcCon = hXpcCon; + xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) { + if (xpc_get_type(hObj) == XPC_TYPE_ERROR) + { + if (hObj == XPC_ERROR_CONNECTION_INVALID) + { + int const rc2 = RTSemEventSignal(pThis->hEvtXpcCancelled); + AssertRC(rc2); + } + /** @todo Error handling - reconnecting. */ + } + else + RTSemEventSignal(pThis->hEvtRecv); + }); + xpc_connection_resume(hXpcCon); + } + else + rc = VERR_NO_MEMORY; } +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) +# ifdef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH + const char *pszService = RTEnvGet("VBOX_INTNET_R3_SVC_NAME"); + if (!pszService || !*pszService) + pszService = INTNET_R3_SVC_NAME; + rc = intnetR3IfLocalIpcConnect(pThis, pszService); +# else + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; + rc = intnetR3IfLocalIpcGetServiceName(szService, sizeof(szService)); + if (RT_SUCCESS(rc)) + rc = intnetR3IfLocalIpcConnect(pThis, szService); +# endif +# else + rc = VERR_SUP_DRIVERLESS; +# endif + if (RT_SUCCESS(rc)) + pThis->fIntNetR3Svc = true; else { - /* Out of band messages should only come when there is something to receive. */ - RTSemEventSignal(pThis->hEvtRecv); + if (pThis->fOwnEvtRecv) + RTSemEventDestroy(pThis->hEvtRecv); + pThis->hEvtRecv = NIL_RTSEMEVENT; + pThis->fOwnEvtRecv = false; } - }); - - xpc_connection_resume(hXpcCon); - pThis->hXpcCon = hXpcCon; -# endif - pThis->fIntNetR3Svc = true; - rc = RTSemEventCreate(&pThis->hEvtRecv); + } #else rc = VERR_SUP_DRIVERLESS; #endif @@ -338,27 +1165,58 @@ DECLHIDDEN(int) IntNetR3IfCreateEx(PINTNETIFCTX phIfCtx, const char *pszNetwork, #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) if (pThis->fIntNetR3Svc) { -# if defined(RT_OS_DARWIN) - if (pThis->hXpcCon) - xpc_connection_cancel(pThis->hXpcCon); - pThis->hXpcCon = NULL; +# if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + if (pThis->pBuf && pThis->hShMemBuf != NIL_RTSHMEM) + { + RTShMemUnmapRegion(pThis->hShMemBuf, pThis->pBuf); + pThis->pBuf = NULL; + pThis->cbBuf = 0; + } + if (pThis->hShMemBuf != NIL_RTSHMEM) + { + RTShMemClose(pThis->hShMemBuf); + pThis->hShMemBuf = NIL_RTSHMEM; + } + intnetR3IfLocalIpcDisconnect(pThis); # endif - - if (pThis->hEvtRecv != NIL_RTSEMEVENT) - RTSemEventDestroy(pThis->hEvtRecv); } +# if defined(INTNETIF_WITH_R3_SVC_XPC) + intnetR3IfXpcDisconnect(pThis); +# endif + if (pThis->fOwnEvtRecv && pThis->hEvtRecv != NIL_RTSEMEVENT) + RTSemEventDestroy(pThis->hEvtRecv); #endif - RTMemFree(pThis); } + else + rc = VERR_NO_MEMORY; + } + if (fSupInited) SUPR3Term(); - } return rc; } +DECLHIDDEN(int) IntNetR3IfCreateEx(PINTNETIFCTX phIfCtx, const char *pszNetwork, INTNETTRUNKTYPE enmTrunkType, + const char *pszTrunk, uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags) +{ + return intnetR3IfCreateExWorker(phIfCtx, pszNetwork, enmTrunkType, pszTrunk, cbSend, cbRecv, fFlags, + NIL_RTSEMEVENT); +} + + +DECLHIDDEN(int) IntNetR3IfCreateExWithRecvEvent(PINTNETIFCTX phIfCtx, const char *pszNetwork, + INTNETTRUNKTYPE enmTrunkType, const char *pszTrunk, + uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags, + RTSEMEVENT hEvtRecv) +{ + AssertReturn(hEvtRecv != NIL_RTSEMEVENT, VERR_INVALID_HANDLE); + return intnetR3IfCreateExWorker(phIfCtx, pszNetwork, enmTrunkType, pszTrunk, cbSend, cbRecv, fFlags, hEvtRecv); +} + + DECLHIDDEN(int) IntNetR3IfDestroy(INTNETIFCTX hIfCtx) { PINTNETIFCTXINT pThis = hIfCtx; @@ -369,18 +1227,51 @@ DECLHIDDEN(int) IntNetR3IfDestroy(INTNETIFCTX hIfCtx) #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) if (pThis->fIntNetR3Svc) { -# if defined(RT_OS_DARWIN) +# if defined(INTNETIF_WITH_R3_SVC_XPC) /* Unmap the shared buffer. */ munmap(pThis->pBuf, pThis->cbBuf); - xpc_connection_cancel(pThis->hXpcCon); - pThis->hXpcCon = NULL; + pThis->pBuf = NULL; + pThis->cbBuf = 0; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + if (pThis->pBuf && pThis->hShMemBuf != NIL_RTSHMEM) + { + RTShMemUnmapRegion(pThis->hShMemBuf, pThis->pBuf); + pThis->pBuf = NULL; + pThis->cbBuf = 0; + } + if (pThis->hShMemBuf != NIL_RTSHMEM) + { + RTShMemClose(pThis->hShMemBuf); + pThis->hShMemBuf = NIL_RTSHMEM; + } + intnetR3IfLocalIpcDisconnect(pThis); +# endif +# if defined(INTNETIF_WITH_R3_SVC_XPC) + intnetR3IfXpcDisconnect(pThis); # endif - RTSemEventDestroy(pThis->hEvtRecv); + if (pThis->fOwnEvtRecv) + RTSemEventDestroy(pThis->hEvtRecv); + pThis->hEvtRecv = NIL_RTSEMEVENT; + pThis->fOwnEvtRecv = false; pThis->fIntNetR3Svc = false; } #endif + bool const fSupInited = pThis->fSupInited; RTMemFree(pThis); + if (fSupInited) + SUPR3Term(); + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) IntNetR3IfQueryHandle(INTNETIFCTX hIfCtx, PINTNETIFHANDLE phIf) +{ + PINTNETIFCTXINT pThis = hIfCtx; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(phIf, VERR_INVALID_POINTER); + + *phIf = pThis->hIf; return VINF_SUCCESS; } @@ -426,6 +1317,22 @@ DECLHIDDEN(int) IntNetR3IfSetPromiscuous(INTNETIFCTX hIfCtx, bool fPromiscuous) } +DECLHIDDEN(int) IntNetR3IfSetMacAddress(INTNETIFCTX hIfCtx, PCRTMAC pMac) +{ + PINTNETIFCTXINT pThis = hIfCtx; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pMac, VERR_INVALID_POINTER); + + INTNETIFSETMACADDRESSREQ Req; + Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + Req.Hdr.cbReq = sizeof(Req); + Req.pSession = pThis->pSupDrvSession; + Req.hIf = pThis->hIf; + Req.Mac = *pMac; + return intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS, &Req.Hdr); +} + + DECLHIDDEN(int) IntNetR3IfSend(INTNETIFCTX hIfCtx) { PINTNETIFCTXINT pThis = hIfCtx; @@ -444,8 +1351,22 @@ DECLHIDDEN(int) IntNetR3IfWait(INTNETIFCTX hIfCtx, uint32_t cMillies) { PINTNETIFCTXINT pThis = hIfCtx; AssertPtrReturn(pThis, VERR_INVALID_HANDLE); +#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) + if (pThis->fIntNetR3Svc && ASMAtomicReadBool(&pThis->fNoMoreWaits)) + return VERR_SEM_DESTROYED; +#endif int rc = VINF_SUCCESS; +#ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (g_pTestWaitRaceIf == pThis) + { + rc = RTSemEventSignal(g_hTestWaitRaceReached); + if (RT_SUCCESS(rc)) + rc = RTSemEventWait(g_hTestWaitRaceContinue, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + return rc; + } +#endif INTNETIFWAITREQ WaitReq; WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; WaitReq.Hdr.cbReq = sizeof(WaitReq); @@ -459,8 +1380,16 @@ DECLHIDDEN(int) IntNetR3IfWait(INTNETIFCTX hIfCtx, uint32_t cMillies) rc = intnetR3IfCallSvcAsync(pThis, VMMR0_DO_INTNET_IF_WAIT, &WaitReq.Hdr); if (RT_SUCCESS(rc)) { +# if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + rc = RTSemEventWait(pThis->hEvtRecv, 0 /*cMillies*/); + if (rc == VERR_TIMEOUT) + rc = intnetR3IfLocalIpcReadPoke(pThis, cMillies); +# else /* Wait on the receive semaphore. */ rc = RTSemEventWait(pThis->hEvtRecv, cMillies); +# endif + if (ASMAtomicReadBool(&pThis->fNoMoreWaits)) + rc = VERR_SEM_DESTROYED; } } else @@ -482,6 +1411,25 @@ DECLHIDDEN(int) IntNetR3IfWaitAbort(INTNETIFCTX hIfCtx) AbortWaitReq.pSession = pThis->pSupDrvSession; AbortWaitReq.hIf = pThis->hIf; AbortWaitReq.fNoMoreWaits = true; +#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) + if (pThis->fIntNetR3Svc) + { +# if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + int rc = RTSemMutexRequest(pThis->hIpcCallMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + ASMAtomicWriteBool(&pThis->fNoMoreWaits, true); + RTSemEventSignal(pThis->hEvtRecv); + rc = intnetR3IfLocalIpcCallLocked(pThis, VMMR0_DO_INTNET_IF_ABORT_WAIT, &AbortWaitReq.Hdr); + RTSemMutexRelease(pThis->hIpcCallMtx); + } + return rc; +# else + ASMAtomicWriteBool(&pThis->fNoMoreWaits, true); + RTSemEventSignal(pThis->hEvtRecv); +# endif + } +#endif return intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_ABORT_WAIT, &AbortWaitReq.Hdr); } diff --git a/src/VBox/NetworkServices/NetLib/IntNetIf.h b/src/VBox/NetworkServices/NetLib/IntNetIf.h index a914fc4652c9..547fee8cae71 100644 --- a/src/VBox/NetworkServices/NetLib/IntNetIf.h +++ b/src/VBox/NetworkServices/NetLib/IntNetIf.h @@ -1,4 +1,4 @@ -/* $Id: IntNetIf.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: IntNetIf.h 114878 2026-08-06 21:59:49Z andreas.loeffler@oracle.com $ */ /** @file * IntNetIf - Convenience class implementing an IntNet connection. */ @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -43,7 +44,7 @@ /** - * Low-level internal network access helpers to hide away the different variants (R0 SUP or R3 XPC on macOS). + * Low-level internal network access helpers hiding the R0 SUP and R3 service transports. */ /** Internal networking interface context handle. */ typedef struct INTNETIFCTXINT *INTNETIFCTX; @@ -96,10 +97,16 @@ typedef const INTNETFRAME *PCINTNETFRAME; DECLHIDDEN(int) IntNetR3IfCreate(PINTNETIFCTX phIfCtx, const char *pszNetwork); DECLHIDDEN(int) IntNetR3IfCreateEx(PINTNETIFCTX phIfCtx, const char *pszNetwork, INTNETTRUNKTYPE enmTrunkType, const char *pszTrunk, uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags); +DECLHIDDEN(int) IntNetR3IfCreateExWithRecvEvent(PINTNETIFCTX phIfCtx, const char *pszNetwork, + INTNETTRUNKTYPE enmTrunkType, const char *pszTrunk, + uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags, + RTSEMEVENT hEvtRecv); DECLHIDDEN(int) IntNetR3IfDestroy(INTNETIFCTX hIfCtx); +DECLHIDDEN(int) IntNetR3IfQueryHandle(INTNETIFCTX hIfCtx, PINTNETIFHANDLE phIf); DECLHIDDEN(int) IntNetR3IfQueryBufferPtr(INTNETIFCTX hIfCtx, PINTNETBUF *ppIfBuf); DECLHIDDEN(int) IntNetR3IfSetActive(INTNETIFCTX hIfCtx, bool fActive); DECLHIDDEN(int) IntNetR3IfSetPromiscuous(INTNETIFCTX hIfCtx, bool fPromiscuous); +DECLHIDDEN(int) IntNetR3IfSetMacAddress(INTNETIFCTX hIfCtx, PCRTMAC pMac); DECLHIDDEN(int) IntNetR3IfSend(INTNETIFCTX hIfCtx); DECLHIDDEN(int) IntNetR3IfWait(INTNETIFCTX hIfCtx, uint32_t cMillies); DECLHIDDEN(int) IntNetR3IfWaitAbort(INTNETIFCTX hIfCtx); From 0ad4d15dab38eff8128f1c59db0985bd5a91e9ab Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 6 Aug 2026 22:04:52 +0000 Subject: [PATCH 025/176] =?UTF-8?q?Devices/DrvIntNet:=20Now=20using=20the?= =?UTF-8?q?=20common=20R3=20IntNet=20client.=20=E2=80=8Bbugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174716 --- src/VBox/Devices/Makefile.kmk | 6 +- src/VBox/Devices/Network/DrvIntNet.cpp | 260 +++++++++++-------------- 2 files changed, 113 insertions(+), 153 deletions(-) diff --git a/src/VBox/Devices/Makefile.kmk b/src/VBox/Devices/Makefile.kmk index 52494f6fa9d1..cd9d0a6e634f 100644 --- a/src/VBox/Devices/Makefile.kmk +++ b/src/VBox/Devices/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114503 2026-06-23 15:44:22Z aleksey.ilyushin@oracle.com $ +# $Id: Makefile.kmk 114879 2026-08-06 22:04:52Z andreas.loeffler@oracle.com $ ## @file # Top-level sub-makefile for the devices, drivers and services. # @@ -175,7 +175,8 @@ if !defined(VBOX_ONLY_EXTPACKS) && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SU VBOX_WITH_DMI_OEMSTRINGS \ $(if $(VBOX_WITH_IOMMU_AMD),VBOX_WITH_IOMMU_AMD,) \ $(if $(VBOX_WITH_IOMMU_INTEL),VBOX_WITH_IOMMU_INTEL,) \ - $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) ifeq ($(KBUILD_TARGET_ARCH),x86) VBoxDD_DEFS.darwin = VBOX_WITH_2X_4GB_ADDR_SPACE endif @@ -226,6 +227,7 @@ if !defined(VBOX_ONLY_EXTPACKS) && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SU Input/DrvKeyboardQueue.cpp \ Input/DrvMouseQueue.cpp \ Network/DrvIntNet.cpp \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),../NetworkServices/NetLib/IntNetIf.cpp,) \ Network/DrvDedicatedNic.cpp \ PC/DrvACPI.cpp \ PC/DrvAcpiCpu.cpp \ diff --git a/src/VBox/Devices/Network/DrvIntNet.cpp b/src/VBox/Devices/Network/DrvIntNet.cpp index 4db38f7080e9..1b41517aa978 100644 --- a/src/VBox/Devices/Network/DrvIntNet.cpp +++ b/src/VBox/Devices/Network/DrvIntNet.cpp @@ -1,4 +1,4 @@ -/* $Id: DrvIntNet.cpp 114205 2026-05-29 13:56:57Z andreas.loeffler@oracle.com $ */ +/* $Id: DrvIntNet.cpp 114879 2026-08-06 22:04:52Z andreas.loeffler@oracle.com $ */ /** @file * DrvIntNet - Internal network transport driver. */ @@ -30,9 +30,6 @@ * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DRV_INTNET -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) -# include /* This needs to be here because it drags PVM in and cdefs.h needs to undefine it... */ -#endif #include #include @@ -62,6 +59,9 @@ #endif #include "VBoxDD.h" +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 +# include "../../NetworkServices/NetLib/IntNetIf.h" +#endif /********************************************************************************************************************************* @@ -199,13 +199,11 @@ typedef struct DRVINTNET /** The nano ts of the last receive. */ uint64_t u64LastReceiveTS; #endif -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - /** XPC connection handle to the R3 internal network switch service. */ - xpc_connection_t hXpcCon; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + /** Context for the R3 internal network switch service. */ + INTNETIFCTX hIfCtx; /** Flag whether the R3 internal network service is being used. */ bool fIntNetR3Svc; - /** Size of the communication buffer in bytes. */ - size_t cbBuf; #endif } DRVINTNET; AssertCompileMemberAlignment(DRVINTNET, XmitLock, 8); @@ -241,61 +239,43 @@ typedef DRVINTNETFLAG const *PCDRVINTNETFLAG; */ static int drvR3IntNetCallSvc(PDRVINTNET pThis, uint32_t uOperation, void *pvArg, unsigned cbArg) { -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 if (pThis->fIntNetR3Svc) { - xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); - xpc_dictionary_set_uint64(hObj, "req-id", uOperation); - xpc_dictionary_set_data(hObj, "req", pvArg, cbArg); - xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj); - xpc_release(hObj); - - uint64_t u64Rc = xpc_dictionary_get_uint64(hObjReply, "rc"); - if (INTNET_R3_SVC_IS_VALID_RC(u64Rc)) + switch (uOperation) { - size_t cbReply = 0; - const void *pvData = xpc_dictionary_get_data(hObjReply, "reply", &cbReply); - AssertRelease(cbReply == cbArg); - memcpy(pvArg, pvData, cbArg); - xpc_release(hObjReply); + case VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS: + { + AssertReturn(cbArg == sizeof(INTNETIFSETMACADDRESSREQ), VERR_INVALID_PARAMETER); + PINTNETIFSETMACADDRESSREQ pReq = (PINTNETIFSETMACADDRESSREQ)pvArg; + return IntNetR3IfSetMacAddress(pThis->hIfCtx, &pReq->Mac); + } - return INTNET_R3_SVC_GET_RC(u64Rc); - } + case VMMR0_DO_INTNET_IF_SET_ACTIVE: + { + AssertReturn(cbArg == sizeof(INTNETIFSETACTIVEREQ), VERR_INVALID_PARAMETER); + PINTNETIFSETACTIVEREQ pReq = (PINTNETIFSETACTIVEREQ)pvArg; + return IntNetR3IfSetActive(pThis->hIfCtx, pReq->fActive); + } - xpc_release(hObjReply); - return VERR_INVALID_STATE; - } - else -#endif - return PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, uOperation, pvArg, cbArg); -} + case VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE: + { + AssertReturn(cbArg == sizeof(INTNETIFSETPROMISCUOUSMODEREQ), VERR_INVALID_PARAMETER); + PINTNETIFSETPROMISCUOUSMODEREQ pReq = (PINTNETIFSETPROMISCUOUSMODEREQ)pvArg; + return IntNetR3IfSetPromiscuous(pThis->hIfCtx, pReq->fPromiscuous); + } + case VMMR0_DO_INTNET_IF_SEND: + AssertReturn(cbArg == sizeof(INTNETIFSENDREQ), VERR_INVALID_PARAMETER); + return IntNetR3IfSend(pThis->hIfCtx); -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) -/** - * Calls the internal networking switch service living in either R0 or in another R3 process. - * - * @returns VBox status code. - * @param pThis The internal network driver instance data. - * @param uOperation The operation to execute. - * @param pvArg Pointer to the argument data. - * @param cbArg Size of the argument data in bytes. - */ -static int drvR3IntNetCallSvcAsync(PDRVINTNET pThis, uint32_t uOperation, void *pvArg, unsigned cbArg) -{ - if (pThis->fIntNetR3Svc) - { - xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); - xpc_dictionary_set_uint64(hObj, "req-id", uOperation); - xpc_dictionary_set_data(hObj, "req", pvArg, cbArg); - xpc_connection_send_message(pThis->hXpcCon, hObj); - xpc_release(hObj); - return VINF_SUCCESS; + default: + AssertFailedReturn(VERR_NOT_SUPPORTED); + } } - else - return PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, uOperation, pvArg, cbArg); -} #endif + return PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, uOperation, pvArg, cbArg); +} /** @@ -306,7 +286,10 @@ static int drvR3IntNetCallSvcAsync(PDRVINTNET pThis, uint32_t uOperation, void * */ static int drvR3IntNetMapBufferPointers(PDRVINTNET pThis) { - int rc = VINF_SUCCESS; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (pThis->fIntNetR3Svc) + return IntNetR3IfQueryBufferPtr(pThis->hIfCtx, &pThis->pBufR3); +#endif INTNETIFGETBUFFERPTRSREQ GetBufferPtrsReq; GetBufferPtrsReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; @@ -316,46 +299,15 @@ static int drvR3IntNetMapBufferPointers(PDRVINTNET pThis) GetBufferPtrsReq.pRing3Buf = NULL; GetBufferPtrsReq.pRing0Buf = NIL_RTR0PTR; -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - if (pThis->fIntNetR3Svc) - { - xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); - xpc_dictionary_set_uint64(hObj, "req-id", VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS); - xpc_dictionary_set_data(hObj, "req", &GetBufferPtrsReq, sizeof(GetBufferPtrsReq)); - xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj); - xpc_release(hObj); - - uint64_t u64Rc = xpc_dictionary_get_uint64(hObjReply, "rc"); - if (INTNET_R3_SVC_IS_VALID_RC(u64Rc)) - rc = INTNET_R3_SVC_GET_RC(u64Rc); - else - rc = VERR_INVALID_STATE; - - if (RT_SUCCESS(rc)) - { - /* Get the shared memory object. */ - xpc_object_t hObjShMem = xpc_dictionary_get_value(hObjReply, "buf-ptr"); - size_t cbMem = xpc_shmem_map(hObjShMem, (void **)&pThis->pBufR3); - if (!cbMem) - rc = VERR_NO_MEMORY; - else - pThis->cbBuf = cbMem; - } - - xpc_release(hObjReply); - } - else -#endif + int rc = PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, + &GetBufferPtrsReq, sizeof(GetBufferPtrsReq)); + if (RT_SUCCESS(rc)) { - rc = PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, &GetBufferPtrsReq, sizeof(GetBufferPtrsReq)); - if (RT_SUCCESS(rc)) - { - AssertRelease(RT_VALID_PTR(GetBufferPtrsReq.pRing3Buf)); - pThis->pBufR3 = GetBufferPtrsReq.pRing3Buf; + AssertRelease(RT_VALID_PTR(GetBufferPtrsReq.pRing3Buf)); + pThis->pBufR3 = GetBufferPtrsReq.pRing3Buf; #ifdef VBOX_WITH_DRVINTNET_IN_R0 - pThis->pBufR0 = GetBufferPtrsReq.pRing0Buf; + pThis->pBufR0 = GetBufferPtrsReq.pRing0Buf; #endif - } } return rc; @@ -1005,35 +957,30 @@ static int drvR3IntNetRecvRun(PDRVINTNET pThis) LogFlow(("drvR3IntNetRecvRun: returns VINF_SUCCESS (state changed - #1)\n")); return VERR_STATE_CHANGED; } - INTNETIFWAITREQ WaitReq; - WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; - WaitReq.Hdr.cbReq = sizeof(WaitReq); - WaitReq.pSession = NIL_RTR0PTR; - WaitReq.hIf = pThis->hIf; - WaitReq.cMillies = 30000; /* 30s - don't wait forever, timeout now and then. */ STAM_PROFILE_ADV_STOP(&pThis->StatReceive, a); -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 if (pThis->fIntNetR3Svc) { - /* Send an asynchronous message. */ - int rc = drvR3IntNetCallSvcAsync(pThis, VMMR0_DO_INTNET_IF_WAIT, &WaitReq, sizeof(WaitReq)); - if (RT_SUCCESS(rc)) + int rc = IntNetR3IfWait(pThis->hIfCtx, 30 * RT_MS_1SEC); + if ( RT_FAILURE(rc) + && rc != VERR_TIMEOUT + && rc != VERR_INTERRUPTED + && rc != VERR_SEM_DESTROYED) { - /* Wait on the receive semaphore. */ - rc = RTSemEventWait(pThis->hRecvEvt, 30 * RT_MS_1SEC); - if ( RT_FAILURE(rc) - && rc != VERR_TIMEOUT - && rc != VERR_INTERRUPTED) - { - LogFlow(("drvR3IntNetRecvRun: returns %Rrc\n", rc)); - return rc; - } + LogFlow(("drvR3IntNetRecvRun: returns %Rrc\n", rc)); + return rc; } } else #endif { + INTNETIFWAITREQ WaitReq; + WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + WaitReq.Hdr.cbReq = sizeof(WaitReq); + WaitReq.pSession = NIL_RTR0PTR; + WaitReq.hIf = pThis->hIf; + WaitReq.cMillies = 30000; /* 30s - don't wait forever, timeout now and then. */ int rc = PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, VMMR0_DO_INTNET_IF_WAIT, &WaitReq, sizeof(WaitReq)); if ( RT_FAILURE(rc) && rc != VERR_TIMEOUT @@ -1367,8 +1314,13 @@ static DECLCALLBACK(void) drvR3IntNetDestruct(PPDMDRVINS pDrvIns) if (pThis->hIf != INTNET_HANDLE_INVALID) { -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - if (!pThis->fIntNetR3Svc) /* The R3 service case is handled b the hRecEvt event semaphore. */ +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (pThis->fIntNetR3Svc) + { + int rc = IntNetR3IfWaitAbort(pThis->hIfCtx); + AssertMsg(RT_SUCCESS(rc) || rc == VERR_SEM_DESTROYED, ("%Rrc\n", rc)); RT_NOREF_PV(rc); + } + else #endif { INTNETIFABORTWAITREQ AbortWaitReq; @@ -1434,6 +1386,18 @@ static DECLCALLBACK(void) drvR3IntNetDestruct(PPDMDRVINS pDrvIns) /* * Close the interface */ +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (pThis->fIntNetR3Svc) + { + pThis->hIf = INTNET_HANDLE_INVALID; + pThis->pBufR3 = NULL; + int rc = IntNetR3IfDestroy(pThis->hIfCtx); + AssertRC(rc); + pThis->hIfCtx = NULL; + pThis->fIntNetR3Svc = false; + } + else +#endif if (pThis->hIf != INTNET_HANDLE_INVALID) { INTNETIFCLOSEREQ CloseReq; @@ -1446,17 +1410,6 @@ static DECLCALLBACK(void) drvR3IntNetDestruct(PPDMDRVINS pDrvIns) AssertRC(rc); } -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - if (pThis->fIntNetR3Svc) - { - /* Unmap the shared buffer. */ - munmap(pThis->pBufR3, pThis->cbBuf); - xpc_connection_cancel(pThis->hXpcCon); - pThis->fIntNetR3Svc = false; - pThis->hXpcCon = NULL; - } -#endif - /* * Destroy the semaphores, S/G cache and xmit lock. */ @@ -1575,6 +1528,10 @@ static DECLCALLBACK(int) drvR3IntNetConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg pThis->hSgCache = NIL_RTMEMCACHE; pThis->enmRecvState = RECVSTATE_SUSPENDED; pThis->fActivateEarlyDeactivateLate = false; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + pThis->hIfCtx = NULL; + pThis->fIntNetR3Svc = false; +#endif /* IBase* */ pDrvIns->IBase.pfnQueryInterface = drvR3IntNetIBase_QueryInterface; #ifdef VBOX_WITH_DRVINTNET_IN_R0 @@ -1947,33 +1904,29 @@ static DECLCALLBACK(int) drvR3IntNetConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg /* * Create the interface. */ + OpenReq.hIf = INTNET_HANDLE_INVALID; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 if (SUPR3IsDriverless()) { -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - xpc_connection_t hXpcCon = xpc_connection_create(INTNET_R3_SVC_NAME, NULL); - xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) { - if (xpc_get_type(hObj) == XPC_TYPE_ERROR) - { - /** @todo Error handling - reconnecting. */ - } - else - { - /* Out of band messages should only come when there is something to receive. */ - RTSemEventSignal(pThis->hRecvEvt); - } - }); - - xpc_connection_resume(hXpcCon); - pThis->hXpcCon = hXpcCon; - pThis->fIntNetR3Svc = true; -#else - /** @todo This is probably not good enough for doing fuzz testing, but later... */ - return PDMDrvHlpVMSetError(pDrvIns, VERR_SUP_DRIVERLESS, RT_SRC_POS, - N_("Cannot attach to '%s' in driverless mode"), pThis->szNetwork); + rc = IntNetR3IfCreateExWithRecvEvent(&pThis->hIfCtx, OpenReq.szNetwork, OpenReq.enmTrunkType, + OpenReq.szTrunk, OpenReq.cbSend, OpenReq.cbRecv, OpenReq.fFlags, + pThis->hRecvEvt); + if (RT_SUCCESS(rc)) + { + pThis->fIntNetR3Svc = true; + rc = IntNetR3IfQueryHandle(pThis->hIfCtx, &pThis->hIf); + } + } + else +#endif + { +#ifndef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (SUPR3IsDriverless()) + return PDMDrvHlpVMSetError(pDrvIns, VERR_SUP_DRIVERLESS, RT_SRC_POS, + N_("Cannot attach to '%s' in driverless mode"), pThis->szNetwork); #endif + rc = drvR3IntNetCallSvc(pThis, VMMR0_DO_INTNET_OPEN, &OpenReq, sizeof(OpenReq)); } - OpenReq.hIf = INTNET_HANDLE_INVALID; - rc = drvR3IntNetCallSvc(pThis, VMMR0_DO_INTNET_OPEN, &OpenReq, sizeof(OpenReq)); if (RT_FAILURE(rc)) { if (fIgnoreConnectFailure) @@ -1995,8 +1948,14 @@ static DECLCALLBACK(int) drvR3IntNetConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg N_("Failed to open/create the internal network '%s'"), pThis->szNetwork); } - AssertRelease(OpenReq.hIf != INTNET_HANDLE_INVALID); - pThis->hIf = OpenReq.hIf; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (!pThis->fIntNetR3Svc) +#endif + { + AssertRelease(OpenReq.hIf != INTNET_HANDLE_INVALID); + pThis->hIf = OpenReq.hIf; + } + AssertRelease(pThis->hIf != INTNET_HANDLE_INVALID); Log(("IntNet%d: hIf=%RX32 '%s'\n", pDrvIns->iInstance, pThis->hIf, pThis->szNetwork)); /* @@ -2138,4 +2097,3 @@ const PDMDRVREG g_DrvIntNet = }; #endif /* IN_RING3 */ - From 26799cc8af48c6fa9b0533f33363381b95a6acd1 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 06:47:30 +0000 Subject: [PATCH 026/176] Frontends/VBoxIntnetPcap: Make R3 local IPC available (VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC). bugref:11149 svn:sync-xref-src-repo-rev: r174717 --- src/VBox/Frontends/VBoxIntnetPcap/Makefile.kmk | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VBox/Frontends/VBoxIntnetPcap/Makefile.kmk b/src/VBox/Frontends/VBoxIntnetPcap/Makefile.kmk index fc42365bf0e6..ad9eb8b19dd2 100644 --- a/src/VBox/Frontends/VBoxIntnetPcap/Makefile.kmk +++ b/src/VBox/Frontends/VBoxIntnetPcap/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114880 2026-08-07 06:47:30Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for the VBoxIntnetPcap # @@ -31,7 +31,8 @@ include $(KBUILD_PATH)/subheader.kmk PROGRAMS += VBoxIntnetPcap VBoxIntnetPcap_TEMPLATE := VBoxR3Exe VBoxIntnetPcap_DEFS := \ - $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) VBoxIntnetPcap_INCS := \ ../../NetworkServices/NetLib \ ../../Devices/Network From a8eb923b0fc988e6caeb91b426e7bfdd0135fa39 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 06:50:28 +0000 Subject: [PATCH 027/176] NetworkServices/testcase: Added R3 IntNet switch testcase. bugref:11149 svn:sync-xref-src-repo-rev: r174718 --- src/VBox/NetworkServices/Makefile.kmk | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/VBox/NetworkServices/Makefile.kmk b/src/VBox/NetworkServices/Makefile.kmk index 97303d115eba..c2d66540ca7f 100644 --- a/src/VBox/NetworkServices/Makefile.kmk +++ b/src/VBox/NetworkServices/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114881 2026-08-07 06:50:28Z andreas.loeffler@oracle.com $ ## @file # Top-level makefile for the VBox Network Services. # @@ -43,5 +43,9 @@ if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) include $(PATH_SUB_CURRENT)/IntNetSwitch/Makefile.kmk endif +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) + include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk +endif + include $(FILE_KBUILD_SUB_FOOTER) From 21f6a0ee4ce199fbc66ca2792ea60ad2b3ad80f9 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 07:03:01 +0000 Subject: [PATCH 028/176] =?UTF-8?q?NetworkServices/testcase:=20Added=20R3?= =?UTF-8?q?=20IntNet=20switch=20testcase.=20=E2=80=8Bbugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174719 --- src/VBox/NetworkServices/testcase/.gitignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/VBox/NetworkServices/testcase/.gitignore diff --git a/src/VBox/NetworkServices/testcase/.gitignore b/src/VBox/NetworkServices/testcase/.gitignore new file mode 100644 index 000000000000..e69de29bb2d1 From ab9b55330389c4d39934d1928d0e640ce1d7e3f6 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 07:12:24 +0000 Subject: [PATCH 029/176] NetworkServices/NAT: Added support for driverless R3 IntNet startup. bugref:11149 svn:sync-xref-src-repo-rev: r174720 --- src/VBox/NetworkServices/NAT/Makefile.kmk | 6 ++++-- src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp | 6 +++++- src/VBox/NetworkServices/NAT/VBoxNetSlirpNAT.cpp | 6 +++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/VBox/NetworkServices/NAT/Makefile.kmk b/src/VBox/NetworkServices/NAT/Makefile.kmk index 1f63168d6283..a6b4fca5a2c5 100644 --- a/src/VBox/NetworkServices/NAT/Makefile.kmk +++ b/src/VBox/NetworkServices/NAT/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114516 2026-06-25 07:04:32Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114883 2026-08-07 07:12:24Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for NAT Networking # @@ -45,6 +45,7 @@ ifdef VBOX_WITH_HARDENING else VBoxNetNATHardened_DEFS = SERVICE_NAME=\"VBoxNetNAT\" endif + VBoxNetNATHardened_DEFS += $(if $(and $(VBOX_WITH_DRIVERLESS_NEM_FALLBACK),$(VBOX_WITH_INTNET_SERVICE_IN_R3)),VBOX_WITH_DRIVERLESS_NEM_FALLBACK,) VBoxNetNATHardened_SOURCES = VBoxNetNATHardened.cpp VBoxNetNATHardened_LDFLAGS.win = /SUBSYSTEM:windows $(call VBOX_SET_VER_INFO_EXE,VBoxNetNATHardened,VirtualBox NAT Engine,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description. @@ -63,7 +64,8 @@ VBoxNetNAT_TEMPLATE := $(if-expr defined(VBOX_WITH_HARDENING),VBoxMainDll,VBoxMa VBoxNetNAT_NAME := VBoxNetNAT VBoxNetNAT_DEFS = \ IPv6 \ - $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) VBoxNetNAT_DEFS.win = VBOX_COM_OUTOFPROC_MODULE VBoxNetNAT_INCS += \ $(PATH_ROOT)/src/libs/libslirp-4.9.3/include diff --git a/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp b/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp index ead4ea51351b..4bda5d2a8825 100644 --- a/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp +++ b/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetNATHardened.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxNetNATHardened.cpp 114883 2026-08-07 07:12:24Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetNAT - Hardened main(). */ @@ -33,5 +33,9 @@ int main(int argc, char **argv, char **envp) { +#ifdef VBOX_WITH_DRIVERLESS_NEM_FALLBACK + return SUPR3HardenedMain(SERVICE_NAME, SUPSECMAIN_FLAGS_DRIVERLESS_NEM_FALLBACK, argc, argv, envp); +#else return SUPR3HardenedMain(SERVICE_NAME, 0 /* fFlags */, argc, argv, envp); +#endif } diff --git a/src/VBox/NetworkServices/NAT/VBoxNetSlirpNAT.cpp b/src/VBox/NetworkServices/NAT/VBoxNetSlirpNAT.cpp index d2385a5050bd..20ace9a8f52b 100644 --- a/src/VBox/NetworkServices/NAT/VBoxNetSlirpNAT.cpp +++ b/src/VBox/NetworkServices/NAT/VBoxNetSlirpNAT.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetSlirpNAT.cpp 114457 2026-06-19 11:55:32Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxNetSlirpNAT.cpp 114883 2026-08-07 07:12:24Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetNAT - NAT Service for connecting to IntNet. */ @@ -3090,7 +3090,11 @@ extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp) #ifndef VBOX_WITH_HARDENING int main(int argc, char **argv, char **envp) { +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + int rc = RTR3InitExe(argc, &argv, 0 /* fFlags */); +#else int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); +#endif if (RT_SUCCESS(rc)) return TrustedMain(argc, argv, envp); return RTMsgInitFailure(rc); From c0ebf8a54d534146f3e20316cc978dbdb99fff0d Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 07:22:52 +0000 Subject: [PATCH 030/176] =?UTF-8?q?NetworkServices/NAT:=20Added=20guest-si?= =?UTF-8?q?de=20R3=20testcases.=20=E2=80=8Bbugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174721 --- .../NAT/testcase/tstNATLibslirpVBox.cpp | 67 +- src/VBox/NetworkServices/testcase/.gitignore | 0 .../NetworkServices/testcase/Makefile.kmk | 124 ++ .../testcase/VBoxNetSlirpNATTest.cpp | 554 ++++++++ .../testcase/tstVBoxNatGuestSide.cpp | 1212 +++++++++++++++++ .../testcase/tstVBoxNatGuestSideInternal.h | 148 ++ 6 files changed, 2102 insertions(+), 3 deletions(-) delete mode 100644 src/VBox/NetworkServices/testcase/.gitignore create mode 100644 src/VBox/NetworkServices/testcase/Makefile.kmk create mode 100644 src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp create mode 100644 src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp create mode 100644 src/VBox/NetworkServices/testcase/tstVBoxNatGuestSideInternal.h diff --git a/src/VBox/NetworkServices/NAT/testcase/tstNATLibslirpVBox.cpp b/src/VBox/NetworkServices/NAT/testcase/tstNATLibslirpVBox.cpp index 2dcb88af3cef..b1c4b5cb174b 100644 --- a/src/VBox/NetworkServices/NAT/testcase/tstNATLibslirpVBox.cpp +++ b/src/VBox/NetworkServices/NAT/testcase/tstNATLibslirpVBox.cpp @@ -1,4 +1,4 @@ -/* $Id: tstNATLibslirpVBox.cpp 114518 2026-06-25 08:29:23Z andreas.loeffler@oracle.com $ */ +/* $Id: tstNATLibslirpVBox.cpp 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ */ /** @file * NAT libslirp VBox testcase. */ @@ -37,6 +37,7 @@ #include +#include #include #include #include @@ -47,11 +48,28 @@ #include +/** Captured frame emitted by libslirp toward the guest. */ +typedef struct TSTNATOUTPUT +{ + uint8_t abFrame[2048]; + size_t cbFrame; + uint32_t cFrames; +} TSTNATOUTPUT; +/** Pointer to a captured libslirp output frame. */ +typedef TSTNATOUTPUT *PTSTNATOUTPUT; + + static DECLCALLBACK(slirp_ssize_t) tstSendPacket(const void *pvBuf, ssize_t cbBuf, void *pvOpaque) { - RT_NOREF(pvBuf, pvOpaque); + PTSTNATOUTPUT pOutput = (PTSTNATOUTPUT)pvOpaque; + if (pOutput != NULL && cbBuf > 0) + { + pOutput->cbFrame = RT_MIN((size_t)cbBuf, sizeof(pOutput->abFrame)); + memcpy(pOutput->abFrame, pvBuf, pOutput->cbFrame); + pOutput->cFrames++; + } return cbBuf; } @@ -185,7 +203,9 @@ static void tstVBoxAbi(void) Cb.timer_free = tstTimerFree; Cb.timer_mod = tstTimerMod; - Slirp *pSlirp = slirp_new(&Cfg, &Cb, NULL); + TSTNATOUTPUT Output; + RT_ZERO(Output); + Slirp *pSlirp = slirp_new(&Cfg, &Cb, &Output); if (!pSlirp) { RTTestIFailed("slirp_new failed"); @@ -230,6 +250,47 @@ static void tstVBoxAbi(void) } RTTESTI_CHECK(slirp_version_string() != NULL); + + RTTestISub("Data path: IPv4 gateway ARP"); + + uint8_t abRequest[sizeof(RTNETETHERHDR) + sizeof(RTNETARPIPV4)]; + RT_ZERO(abRequest); + PRTNETETHERHDR pEth = (PRTNETETHERHDR)&abRequest[0]; + memset(&pEth->DstMac, 0xff, sizeof(pEth->DstMac)); + pEth->SrcMac.au8[0] = 0x08; + pEth->SrcMac.au8[1] = 0x00; + pEth->SrcMac.au8[2] = 0x27; + pEth->SrcMac.au8[3] = 0x12; + pEth->SrcMac.au8[4] = 0x34; + pEth->SrcMac.au8[5] = 0x56; + pEth->EtherType = RT_H2N_U16(RTNET_ETHERTYPE_ARP); + + PRTNETARPIPV4 pArp = (PRTNETARPIPV4)&abRequest[sizeof(*pEth)]; + pArp->Hdr.ar_htype = RT_H2N_U16(RTNET_ARP_ETHER); + pArp->Hdr.ar_ptype = RT_H2N_U16(RTNET_ETHERTYPE_IPV4); + pArp->Hdr.ar_hlen = sizeof(RTMAC); + pArp->Hdr.ar_plen = sizeof(RTNETADDRIPV4); + pArp->Hdr.ar_oper = RT_H2N_U16(RTNET_ARPOP_REQUEST); + pArp->ar_sha = pEth->SrcMac; + pArp->ar_spa.u = tstParseIPv4("10.0.2.15").s_addr; + pArp->ar_tpa.u = Cfg.vhost.s_addr; + + RT_ZERO(Output); + slirp_input(pSlirp, abRequest, sizeof(abRequest)); + RTTESTI_CHECK_MSG(Output.cFrames > 0, ("No frame returned for gateway ARP request")); + RTTESTI_CHECK_MSG(Output.cbFrame >= sizeof(RTNETETHERHDR) + sizeof(RTNETARPIPV4), + ("ARP reply is too small: %zu", Output.cbFrame)); + if (Output.cbFrame >= sizeof(RTNETETHERHDR) + sizeof(RTNETARPIPV4)) + { + PCRTNETETHERHDR pReplyEth = (PCRTNETETHERHDR)&Output.abFrame[0]; + PCRTNETARPIPV4 pReplyArp = (PCRTNETARPIPV4)&Output.abFrame[sizeof(*pReplyEth)]; + RTTESTI_CHECK(RT_N2H_U16(pReplyEth->EtherType) == RTNET_ETHERTYPE_ARP); + RTTESTI_CHECK(RT_N2H_U16(pReplyArp->Hdr.ar_oper) == RTNET_ARPOP_REPLY); + RTTESTI_CHECK(pReplyArp->ar_spa.u == Cfg.vhost.s_addr); + RTTESTI_CHECK(pReplyArp->ar_tpa.u == pArp->ar_spa.u); + RTTESTI_CHECK(memcmp(&pReplyArp->ar_tha, &pEth->SrcMac, sizeof(RTMAC)) == 0); + } + slirp_cleanup(pSlirp); } diff --git a/src/VBox/NetworkServices/testcase/.gitignore b/src/VBox/NetworkServices/testcase/.gitignore deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/VBox/NetworkServices/testcase/Makefile.kmk b/src/VBox/NetworkServices/testcase/Makefile.kmk new file mode 100644 index 000000000000..3ad704c9ed69 --- /dev/null +++ b/src/VBox/NetworkServices/testcase/Makefile.kmk @@ -0,0 +1,124 @@ +# $Id: Makefile.kmk 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ +## @file +# Network Services testcases. +# + +# +# Copyright (C) 2026 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) + + if !defined(VBOX_ONLY_BUILD) \ + && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SUPPORTED_HOST_ARCHS))" != "" + PROGRAMS += tstVBoxNatGuestSide + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstVBoxNatGuestSide_0_OUTDIR)/tstVBoxNatGuestSide.run + endif + tstVBoxNatGuestSide_TEMPLATE = VBoxR3TstExe + tstVBoxNatGuestSide_DEFS = \ + VBOX_WITH_TESTCASES \ + VBOX_WITH_INTNET_SERVICE_IN_R3 \ + VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC \ + VBOX_INTNET_TESTCASE_FORCE_R3 \ + VBOX_INTNET_TESTCASE_LOCALIPC + tstVBoxNatGuestSide_INCS = \ + $(PATH_SUB_CURRENT) \ + $(PATH_SUB_CURRENT)/../NetLib \ + $(PATH_SUB_CURRENT)/../IntNetSwitch \ + $(PATH_ROOT)/src/libs/libslirp-4.9.3/include + tstVBoxNatGuestSide_SOURCES = \ + tstVBoxNatGuestSide.cpp \ + VBoxNetSlirpNATTest.cpp \ + ../NetLib/IntNetIf.cpp + tstVBoxNatGuestSide_LIBS = \ + $(PATH_STAGE_LIB)/VBox-libslirp$(VBOX_SUFF_LIB) \ + $(LIB_RUNTIME) + tstVBoxNatGuestSide_LIBS.solaris += socket nsl + tstVBoxNatGuestSide_LIBS.darwin += resolv + tstVBoxNatGuestSide_ORDERDEPS = \ + $(PATH_STAGE_BIN)/testcase/VBoxIntNetR3SwitchTestHelper$(SUFF_EXE) + tstVBoxNatGuestSide_CLEAN = \ + $(tstVBoxNatGuestSide_0_OUTDIR)/tstVBoxNatGuestSide.run + + $$(tstVBoxNatGuestSide_0_OUTDIR)/tstVBoxNatGuestSide.run: \ + $$(tstVBoxNatGuestSide_1_STAGE_TARGET) \ + $$(VBoxIntNetR3SwitchTestHelper_1_STAGE_TARGET) \ + | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstVBoxNatGuestSide_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + endif + + # IntNet R3 Switch cross-process communication testcase and service helper. + PROGRAMS += \ + tstVBoxIntNetR3Switch \ + VBoxIntNetR3SwitchTestHelper + tstVBoxIntNetR3Switch_TEMPLATE = VBoxR3TstExe + tstVBoxIntNetR3Switch_DEFS += \ + VBOX_WITH_INTNET_SERVICE_IN_R3 \ + VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC \ + VBOX_INTNET_TESTCASE_FORCE_R3 \ + VBOX_INTNET_TESTCASE_LOCALIPC + tstVBoxIntNetR3Switch_INCS = \ + $(PATH_SUB_CURRENT) \ + $(PATH_SUB_CURRENT)/../NetLib \ + $(PATH_SUB_CURRENT)/../IntNetSwitch + tstVBoxIntNetR3Switch_SOURCES = \ + tstVBoxIntNetR3Switch.cpp \ + ../NetLib/IntNetIf.cpp + tstVBoxIntNetR3Switch_LIBS = \ + $(LIB_RUNTIME) + tstVBoxIntNetR3Switch_ORDERDEPS = \ + $(PATH_STAGE_BIN)/testcase/VBoxIntNetR3SwitchTestHelper$(SUFF_EXE) + tstVBoxIntNetR3Switch_CLEAN = \ + $(tstVBoxIntNetR3Switch_0_OUTDIR)/tstVBoxIntNetR3Switch.run + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstVBoxIntNetR3Switch_0_OUTDIR)/tstVBoxIntNetR3Switch.run + + $$(tstVBoxIntNetR3Switch_0_OUTDIR)/tstVBoxIntNetR3Switch.run: \ + $$(tstVBoxIntNetR3Switch_1_STAGE_TARGET) \ + $$(VBoxIntNetR3SwitchTestHelper_1_STAGE_TARGET) \ + | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstVBoxIntNetR3Switch_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + endif + + VBoxIntNetR3SwitchTestHelper_TEMPLATE = VBoxR3TstExe + VBoxIntNetR3SwitchTestHelper_DEFS += \ + VBOX_WITH_INTNET_SERVICE_IN_R3 \ + VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC \ + VBOX_INTNET_TESTCASE_LOCALIPC \ + VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + VBoxIntNetR3SwitchTestHelper_INCS = \ + $(PATH_SUB_CURRENT)/../IntNetSwitch + VBoxIntNetR3SwitchTestHelper_SOURCES = \ + ../IntNetSwitch/VBoxIntNetSwitch.cpp \ + ../IntNetSwitch/SrvIntNetWrapper.cpp + VBoxIntNetR3SwitchTestHelper_LIBS = \ + $(LIB_RUNTIME) + +endif # VBOX_WITH_TESTCASES && VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp b/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp new file mode 100644 index 000000000000..c1fc3503f739 --- /dev/null +++ b/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp @@ -0,0 +1,554 @@ +/* $Id: VBoxNetSlirpNATTest.cpp 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ */ +/** @file + * VBoxNetSlirpNAT - Wrapper for guest-side tests. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include +# include +# include +#else +# include +# include +#endif + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tstVBoxNatGuestSideInternal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** One classic libslirp timer. */ +typedef struct VBOXNETSLIRPNATTESTTIMER +{ + /** The next timer. */ + struct VBOXNETSLIRPNATTESTTIMER *pNext; + /** Absolute expiration time in milliseconds; zero means stopped. */ + int64_t msExpire; + /** The libslirp timer callback. */ + SlirpTimerCb pfnCallback; + /** The callback argument. */ + void *pvCallback; + /** Poll generation in which this timer was most recently dispatched. */ + uint64_t uRunGeneration; +} VBOXNETSLIRPNATTESTTIMER; +/** Pointer to a classic libslirp timer. */ +typedef VBOXNETSLIRPNATTESTTIMER *PVBOXNETSLIRPNATTESTTIMER; + +/** Restricted, single-thread-affine libslirp test instance. */ +struct VBOXNETSLIRPNATTEST +{ + /** The libslirp instance. */ + Slirp *pSlirp; + /** Callbacks retained for the complete libslirp lifetime. */ + SlirpCb Callbacks; + /** Frame callback supplied by the testcase. */ + PFNVBOXNETSLIRPNATTESTOUTPUT pfnOutput; + /** Frame callback argument. */ + void *pvOutputUser; + /** First output callback error from the current operation. */ + int rcOutput; + /** First poll-array construction error from the current poll. */ + int rcPoll; + /** Classic libslirp timers. */ + PVBOXNETSLIRPNATTESTTIMER pTimerHead; + /** Generation counter used to dispatch timers safely across list changes. */ + uint64_t uTimerRunGeneration; + /** Poll descriptors. */ + struct pollfd *paPollFds; + /** Number of allocated poll descriptors. */ + uint32_t cPollFdsAllocated; + /** Number of descriptors used by the current poll. */ + uint32_t cPollFds; +#ifdef RT_OS_WINDOWS + /** Whether this instance owns one WSAStartup reference. */ + bool fWsaStarted; +#endif +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** Converts libslirp poll events to the host poll representation. */ +static short vboxNetSlirpNATTestPollEventsToHost(int fEvents) +{ + short fRet = 0; +#ifndef RT_OS_WINDOWS + if (fEvents & SLIRP_POLL_IN) fRet |= POLLIN; + if (fEvents & SLIRP_POLL_OUT) fRet |= POLLOUT; + if (fEvents & SLIRP_POLL_PRI) fRet |= POLLPRI; + if (fEvents & SLIRP_POLL_ERR) fRet |= POLLERR; + if (fEvents & SLIRP_POLL_HUP) fRet |= POLLHUP; +#else + if (fEvents & SLIRP_POLL_IN) fRet |= POLLRDNORM | POLLRDBAND; + if (fEvents & SLIRP_POLL_OUT) fRet |= POLLWRNORM; + if (fEvents & SLIRP_POLL_PRI) fRet |= POLLIN; +#endif + return fRet; +} + + +/** Converts host poll results to the libslirp representation. */ +static int vboxNetSlirpNATTestPollEventsFromHost(short fEvents) +{ + int fRet = 0; +#ifndef RT_OS_WINDOWS + if (fEvents & POLLIN) fRet |= SLIRP_POLL_IN; + if (fEvents & POLLOUT) fRet |= SLIRP_POLL_OUT; + if (fEvents & POLLPRI) fRet |= SLIRP_POLL_PRI; +#else + if (fEvents & (POLLRDNORM | POLLRDBAND)) fRet |= SLIRP_POLL_IN; + if (fEvents & POLLWRNORM) fRet |= SLIRP_POLL_OUT; + if (fEvents & POLLPRI) fRet |= SLIRP_POLL_PRI; +#endif + if (fEvents & POLLERR) fRet |= SLIRP_POLL_ERR; + if (fEvents & POLLHUP) fRet |= SLIRP_POLL_HUP; + return fRet; +} + + +/** Delivers one libslirp frame to the testcase. */ +static DECLCALLBACK(slirp_ssize_t) +vboxNetSlirpNATTestSendPacket(const void *pvFrame, ssize_t cbFrame, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + AssertPtrReturn(pThis, -1); + AssertPtrReturn(pvFrame, -1); + AssertReturn(cbFrame > 0, -1); + + int const rc = pThis->pfnOutput(pvFrame, (size_t)cbFrame, pThis->pvOutputUser); + if (RT_FAILURE(rc)) + { + if (RT_SUCCESS(pThis->rcOutput)) + pThis->rcOutput = rc; + return -1; + } + return cbFrame; +} + + +/** Ignores libslirp diagnostics caused by deliberately malformed guest input. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestGuestError(const char *pszMessage, void *pvUser) +{ + RT_NOREF(pszMessage, pvUser); +} + + +/** Returns libslirp's monotonic virtual clock. */ +static DECLCALLBACK(int64_t) vboxNetSlirpNATTestClockGetNs(void *pvUser) +{ + RT_NOREF(pvUser); + return (int64_t)RTTimeNanoTS(); +} + + +/** Allocates and links a classic libslirp timer. */ +static DECLCALLBACK(void *) +vboxNetSlirpNATTestTimerNew(SlirpTimerCb pfnCallback, void *pvCallback, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + AssertPtrReturn(pThis, NULL); + AssertPtrReturn(pfnCallback, NULL); + + PVBOXNETSLIRPNATTESTTIMER pTimer = (PVBOXNETSLIRPNATTESTTIMER)RTMemAllocZ(sizeof(*pTimer)); + if (pTimer) + { + pTimer->pfnCallback = pfnCallback; + pTimer->pvCallback = pvCallback; + pTimer->pNext = pThis->pTimerHead; + pThis->pTimerHead = pTimer; + } + return pTimer; +} + + +/** Unlinks and frees a classic libslirp timer. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestTimerFree(void *pvTimer, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + PVBOXNETSLIRPNATTESTTIMER pTimer = (PVBOXNETSLIRPNATTESTTIMER)pvTimer; + AssertPtrReturnVoid(pThis); + + PVBOXNETSLIRPNATTESTTIMER *ppCur = &pThis->pTimerHead; + while (*ppCur) + { + if (*ppCur == pTimer) + { + *ppCur = pTimer->pNext; + RTMemFree(pTimer); + return; + } + ppCur = &(*ppCur)->pNext; + } + AssertFailed(); +} + + +/** Arms or stops a classic libslirp timer. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestTimerMod(void *pvTimer, int64_t msExpire, void *pvUser) +{ + PVBOXNETSLIRPNATTESTTIMER pTimer = (PVBOXNETSLIRPNATTESTTIMER)pvTimer; + AssertPtrReturnVoid(pTimer); + pTimer->msExpire = msExpire; + RT_NOREF(pvUser); +} + + +/** Notification is unnecessary because Input and Poll are serialized. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestNotify(void *pvUser) +{ + RT_NOREF(pvUser); +} + + +/** Socket registration is handled by slirp_pollfds_fill_socket. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestRegisterSocket(slirp_os_socket hSocket, void *pvUser) +{ + RT_NOREF(hSocket, pvUser); +} + + +/** Socket unregistration is handled by slirp_pollfds_fill_socket. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestUnregisterSocket(slirp_os_socket hSocket, void *pvUser) +{ + RT_NOREF(hSocket, pvUser); +} + + +/** Adds a descriptor requested by slirp_pollfds_fill_socket. */ +static DECLCALLBACK(int) vboxNetSlirpNATTestAddPollFd(slirp_os_socket hSocket, int fEvents, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + AssertPtrReturn(pThis, -1); + + if (pThis->cPollFds == pThis->cPollFdsAllocated) + { + uint32_t const cNew = pThis->cPollFdsAllocated ? pThis->cPollFdsAllocated * 2 : 64; + void *pvNew = RTMemRealloc(pThis->paPollFds, cNew * sizeof(pThis->paPollFds[0])); + if (!pvNew) + { + pThis->rcPoll = VERR_NO_MEMORY; + return -1; + } + pThis->paPollFds = (struct pollfd *)pvNew; + pThis->cPollFdsAllocated = cNew; + } + + uint32_t const iPoll = pThis->cPollFds++; + AssertReturn(iPoll < INT_MAX, -1); + pThis->paPollFds[iPoll].fd = hSocket; + pThis->paPollFds[iPoll].events = vboxNetSlirpNATTestPollEventsToHost(fEvents); + pThis->paPollFds[iPoll].revents = 0; + return (int)iPoll; +} + + +/** Returns the events observed for a descriptor. */ +static DECLCALLBACK(int) vboxNetSlirpNATTestGetPollEvents(int iPoll, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + AssertPtrReturn(pThis, SLIRP_POLL_ERR); + AssertReturn(iPoll >= 0 && (uint32_t)iPoll < pThis->cPollFds, SLIRP_POLL_ERR); + return vboxNetSlirpNATTestPollEventsFromHost(pThis->paPollFds[iPoll].revents); +} + + +/** Lowers a poll timeout to the earliest classic timer deadline. */ +static uint32_t vboxNetSlirpNATTestAdjustTimeout(PVBOXNETSLIRPNATTEST pThis, uint32_t cMsTimeout) +{ + int64_t msDeadline = INT64_MAX; + for (PVBOXNETSLIRPNATTESTTIMER pTimer = pThis->pTimerHead; pTimer; pTimer = pTimer->pNext) + if (pTimer->msExpire > 0 && pTimer->msExpire < msDeadline) + msDeadline = pTimer->msExpire; + + if (msDeadline != INT64_MAX) + { + int64_t const msNow = (int64_t)(RTTimeNanoTS() / RT_NS_1MS); + if (msDeadline <= msNow) + return 0; + uint64_t const cMsToDeadline = (uint64_t)(msDeadline - msNow); + if (cMsToDeadline < cMsTimeout) + cMsTimeout = (uint32_t)cMsToDeadline; + } + return cMsTimeout; +} + + +/** Runs all expired classic timers. */ +static void vboxNetSlirpNATTestRunExpiredTimers(PVBOXNETSLIRPNATTEST pThis) +{ + int64_t const msNow = (int64_t)(RTTimeNanoTS() / RT_NS_1MS); + uint64_t const uGeneration = ++pThis->uTimerRunGeneration; + for (PVBOXNETSLIRPNATTESTTIMER pTimer = pThis->pTimerHead; pTimer; pTimer = pTimer->pNext) + if (pTimer->msExpire > 0 && pTimer->msExpire <= msNow) + pTimer->uRunGeneration = uGeneration; + + for (;;) + { + PVBOXNETSLIRPNATTESTTIMER pTimer = pThis->pTimerHead; + while (pTimer && pTimer->uRunGeneration != uGeneration) + pTimer = pTimer->pNext; + if (!pTimer) + break; + + SlirpTimerCb const pfnCallback = pTimer->pfnCallback; + void * const pvCallback = pTimer->pvCallback; + pTimer->uRunGeneration = 0; + pTimer->msExpire = 0; + pfnCallback(pvCallback); + } +} + + +/** Validates that an address belongs to the configured IPv4 network. */ +static bool vboxNetSlirpNATTestAddrIsInNetwork(PCVBOXNETSLIRPNATTESTCFG pCfg, RTNETADDRIPV4 Addr) +{ + return (Addr.u & pCfg->IPv4Netmask.u) == pCfg->IPv4Network.u; +} + + +/** Validates the restricted IPv4 configuration accepted by the wrapper. */ +static int vboxNetSlirpNATTestValidateConfig(PCVBOXNETSLIRPNATTESTCFG pCfg) +{ + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + if (!pCfg->fRestricted) + return VERR_ACCESS_DENIED; + + int iPrefix = 0; + int rc = RTNetMaskToPrefixIPv4(&pCfg->IPv4Netmask, &iPrefix); + if (RT_FAILURE(rc) || iPrefix < 1 || iPrefix > 30) + return VERR_INVALID_PARAMETER; + if ( (pCfg->IPv4Network.u & pCfg->IPv4Netmask.u) != pCfg->IPv4Network.u + || !vboxNetSlirpNATTestAddrIsInNetwork(pCfg, pCfg->IPv4Host) + || !vboxNetSlirpNATTestAddrIsInNetwork(pCfg, pCfg->IPv4DhcpStart) + || !vboxNetSlirpNATTestAddrIsInNetwork(pCfg, pCfg->IPv4Nameserver)) + return VERR_INVALID_PARAMETER; + + uint32_t const uBroadcast = pCfg->IPv4Network.u | ~pCfg->IPv4Netmask.u; + if ( pCfg->IPv4Host.u == pCfg->IPv4Network.u + || pCfg->IPv4Host.u == uBroadcast + || pCfg->IPv4DhcpStart.u == pCfg->IPv4Network.u + || pCfg->IPv4DhcpStart.u == uBroadcast + || pCfg->IPv4Nameserver.u == pCfg->IPv4Network.u + || pCfg->IPv4Nameserver.u == uBroadcast) + return VERR_INVALID_PARAMETER; + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Exported Test Functions * +*********************************************************************************************************************************/ +DECLHIDDEN(int) VBoxNetSlirpNATTestCreate(PCVBOXNETSLIRPNATTESTCFG pCfg, + PFNVBOXNETSLIRPNATTESTOUTPUT pfnOutput, + void *pvOutputUser, PVBOXNETSLIRPNATTEST *phNat) +{ + AssertPtrReturn(phNat, VERR_INVALID_POINTER); + *phNat = NULL; + AssertPtrReturn(pfnOutput, VERR_INVALID_POINTER); + + int rc = vboxNetSlirpNATTestValidateConfig(pCfg); + if (RT_FAILURE(rc)) + return rc; + + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)RTMemAllocZ(sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + pThis->pfnOutput = pfnOutput; + pThis->pvOutputUser = pvOutputUser; + pThis->rcOutput = VINF_SUCCESS; + pThis->rcPoll = VINF_SUCCESS; + +#ifdef RT_OS_WINDOWS + WSADATA WsaData; + int const iWsaErr = WSAStartup(MAKEWORD(2, 2), &WsaData); + if (iWsaErr != 0) + { + RTMemFree(pThis); + return RTErrConvertFromWin32(iWsaErr); + } + pThis->fWsaStarted = true; +#endif + + pThis->Callbacks.send_packet = vboxNetSlirpNATTestSendPacket; + pThis->Callbacks.guest_error = vboxNetSlirpNATTestGuestError; + pThis->Callbacks.clock_get_ns = vboxNetSlirpNATTestClockGetNs; + pThis->Callbacks.timer_new = vboxNetSlirpNATTestTimerNew; + pThis->Callbacks.timer_free = vboxNetSlirpNATTestTimerFree; + pThis->Callbacks.timer_mod = vboxNetSlirpNATTestTimerMod; + pThis->Callbacks.notify = vboxNetSlirpNATTestNotify; + pThis->Callbacks.register_poll_socket = vboxNetSlirpNATTestRegisterSocket; + pThis->Callbacks.unregister_poll_socket = vboxNetSlirpNATTestUnregisterSocket; + + SlirpConfig Cfg; + RT_ZERO(Cfg); + Cfg.version = SLIRP_CONFIG_VERSION_MAX; + Cfg.restricted = 1; + Cfg.in_enabled = true; + Cfg.vnetwork.s_addr = pCfg->IPv4Network.u; + Cfg.vnetmask.s_addr = pCfg->IPv4Netmask.u; + Cfg.vhost.s_addr = pCfg->IPv4Host.u; + Cfg.vdhcp_start.s_addr = pCfg->IPv4DhcpStart.u; + Cfg.vnameserver.s_addr = pCfg->IPv4Nameserver.u; + Cfg.in6_enabled = false; + Cfg.if_mtu = 1500; + Cfg.if_mru = 1500; + Cfg.if_mtu_v6 = 1280; + Cfg.if_mru_v6 = 1280; + Cfg.disable_host_loopback = true; + Cfg.enable_emu = false; + Cfg.disable_dns = !pCfg->fDns; + Cfg.disable_dhcp = !pCfg->fDhcp; + Cfg.fForwardBroadcast = false; + Cfg.iSoMaxConn = 10; + Cfg.fDisableIPv6RA = true; + + pThis->pSlirp = slirp_new(&Cfg, &pThis->Callbacks, pThis); + if (!pThis->pSlirp) + { +#ifdef RT_OS_WINDOWS + WSACleanup(); +#endif + RTMemFree(pThis); + return VERR_NO_MEMORY; + } + + *phNat = pThis; + return VINF_SUCCESS; +} + + +DECLHIDDEN(void) VBoxNetSlirpNATTestDestroy(PVBOXNETSLIRPNATTEST hNat) +{ + if (!hNat) + return; + + if (hNat->pSlirp) + { + slirp_cleanup(hNat->pSlirp); + hNat->pSlirp = NULL; + } + while (hNat->pTimerHead) + { + PVBOXNETSLIRPNATTESTTIMER pFree = hNat->pTimerHead; + hNat->pTimerHead = pFree->pNext; + RTMemFree(pFree); + } + RTMemFree(hNat->paPollFds); +#ifdef RT_OS_WINDOWS + if (hNat->fWsaStarted) + WSACleanup(); +#endif + RTMemFree(hNat); +} + + +DECLHIDDEN(int) VBoxNetSlirpNATTestInput(PVBOXNETSLIRPNATTEST hNat, + const void *pvFrame, size_t cbFrame) +{ + AssertPtrReturn(hNat, VERR_INVALID_HANDLE); + if (!pvFrame || cbFrame == 0 || cbFrame > _64K) + return VERR_INVALID_PARAMETER; + + /* Keep the testcase seam aligned with VBoxNetSlirpNAT::processFrame. */ + if (cbFrame < sizeof(RTNETETHERHDR) || cbFrame > 1522) + return VINF_SUCCESS; + + hNat->rcOutput = VINF_SUCCESS; + slirp_input(hNat->pSlirp, (uint8_t const *)pvFrame, (int)cbFrame); + return hNat->rcOutput; +} + + +DECLHIDDEN(int) VBoxNetSlirpNATTestPoll(PVBOXNETSLIRPNATTEST hNat, uint32_t cMsMax) +{ + AssertPtrReturn(hNat, VERR_INVALID_HANDLE); + + hNat->rcOutput = VINF_SUCCESS; + hNat->rcPoll = VINF_SUCCESS; + hNat->cPollFds = 0; + + uint32_t cMsTimeout = RT_MIN(cMsMax, (uint32_t)INT_MAX); + slirp_pollfds_fill_socket(hNat->pSlirp, &cMsTimeout, vboxNetSlirpNATTestAddPollFd, hNat); + if (RT_FAILURE(hNat->rcPoll)) + return hNat->rcPoll; + cMsTimeout = vboxNetSlirpNATTestAdjustTimeout(hNat, cMsTimeout); + + int cReady = 0; + if (hNat->cPollFds == 0) + { + if (cMsTimeout > 0) + RTThreadSleep(cMsTimeout); + } + else + { +#ifdef RT_OS_WINDOWS + cReady = WSAPoll(hNat->paPollFds, hNat->cPollFds, (int)cMsTimeout); + if (cReady == SOCKET_ERROR) + { + int const rc = RTErrConvertFromWin32(WSAGetLastError()); + slirp_pollfds_poll(hNat->pSlirp, true, vboxNetSlirpNATTestGetPollEvents, hNat); + return rc; + } +#else + cReady = poll(hNat->paPollFds, hNat->cPollFds, (int)cMsTimeout); + if (cReady < 0) + { + if (errno == EINTR) + cReady = 0; + else + { + int const rc = RTErrConvertFromErrno(errno); + slirp_pollfds_poll(hNat->pSlirp, true, vboxNetSlirpNATTestGetPollEvents, hNat); + return rc; + } + } +#endif + } + + RT_NOREF(cReady); + slirp_pollfds_poll(hNat->pSlirp, false, vboxNetSlirpNATTestGetPollEvents, hNat); + vboxNetSlirpNATTestRunExpiredTimers(hNat); + return hNat->rcOutput; +} diff --git a/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp new file mode 100644 index 000000000000..cbc8c6971c5f --- /dev/null +++ b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp @@ -0,0 +1,1212 @@ +/* $Id: tstVBoxNatGuestSide.cpp 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ */ +/** @file + * tstVBoxNatGuestSide - Guest-side NAT over Ring-3 IntNet testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "../NetLib/IntNetIf.h" +#include "tstVBoxNatGuestSideInternal.h" + + +/********************************************************************************************************************************* +* Constants * +*********************************************************************************************************************************/ +#define TST_MAX_FRAME 2048 +#define TST_MAX_QUEUED_FRAMES 16 +#define TST_DHCP_XID UINT32_C(0x33445566) +#define TST_WAIT_MS 3000 +#define TST_POLL_MS 5 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** One captured or synthetic Ethernet frame. */ +typedef struct TSTFRAME +{ + /** Frame bytes. */ + uint8_t ab[TST_MAX_FRAME]; + /** Number of valid frame bytes. */ + size_t cb; +} TSTFRAME; +/** Pointer to a frame. */ +typedef TSTFRAME *PTSTFRAME; +/** Pointer to a const frame. */ +typedef const TSTFRAME *PCTSTFRAME; + +/** Bounded FIFO for frames emitted by libslirp. */ +typedef struct TSTFRAMEQUEUE +{ + /** Captured frames. */ + TSTFRAME aFrames[TST_MAX_QUEUED_FRAMES]; + /** Index of the oldest frame. */ + uint32_t iHead; + /** Number of queued frames. */ + uint32_t cFrames; +} TSTFRAMEQUEUE; +/** Pointer to a frame queue. */ +typedef TSTFRAMEQUEUE *PTSTFRAMEQUEUE; + +/** Parsed fields from a DHCP server response. */ +typedef struct TSTDHCPOFFER +{ + /** DHCP message type. */ + uint8_t uMsgType; + /** Offered or acknowledged client address. */ + RTNETADDRIPV4 YiAddr; + /** Subnet mask option. */ + RTNETADDRIPV4 Netmask; + /** Server identifier option. */ + RTNETADDRIPV4 ServerId; + /** Whether a subnet mask option was present. */ + bool fNetmask; + /** Whether a server identifier option was present. */ + bool fServerId; + /** Whether a router option was present. */ + bool fRouter; + /** Whether a DNS option was present. */ + bool fDns; +} TSTDHCPOFFER; +/** Pointer to parsed DHCP fields. */ +typedef TSTDHCPOFFER *PTSTDHCPOFFER; + +/** State for the standalone R3 switch helper. */ +typedef struct TSTSWITCHSERVICE +{ + /** Helper process ID once the auto-start path has published it. */ + RTPROCESS hProcess; + /** Whether the helper was successfully reaped. */ + bool fProcessReaped; + /** Whether the temporary directory was created. */ + bool fTempDirCreated; + /** Unique Local IPC service name. */ + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; + /** Absolute helper executable path. */ + char szExec[RTPATH_MAX]; + /** Per-test temporary directory. */ + char szTempDir[RTPATH_MAX]; + /** Helper PID file. */ + char szPidFile[RTPATH_MAX]; + /** Helper lock file. */ + char szLockFile[RTPATH_MAX]; +} TSTSWITCHSERVICE; +/** Pointer to switch helper state. */ +typedef TSTSWITCHSERVICE *PTSTSWITCHSERVICE; + +/** NAT-facing IntNet receive pump. */ +typedef struct TSTNATPUMP +{ + /** NAT-facing IntNet interface. */ + INTNETIFCTX hIf; + /** Restricted libslirp wrapper. */ + PVBOXNETSLIRPNATTEST hNat; + /** Pump thread. */ + RTTHREAD hThread; + /** First input failure reported by the pump callback. */ + int rcInput; +} TSTNATPUMP; +/** Pointer to NAT pump state. */ +typedef TSTNATPUMP *PTSTNATPUMP; + +/** Guest-facing IntNet receive collector. */ +typedef struct TSTGUESTRX +{ + /** Guest-facing IntNet interface. */ + INTNETIFCTX hIf; + /** Receive thread. */ + RTTHREAD hThread; + /** Signalled when the expected ARP reply is captured. */ + RTSEMEVENT hReplyEvent; + /** Expected gateway address. */ + RTNETADDRIPV4 GatewayIp; + /** Expected guest address. */ + RTNETADDRIPV4 GuestIp; + /** Expected guest MAC address. */ + RTMAC GuestMac; + /** Captured reply. */ + TSTFRAME Reply; + /** Whether Reply contains the expected ARP response. */ + bool fReply; +} TSTGUESTRX; +/** Pointer to guest receive state. */ +typedef TSTGUESTRX *PTSTGUESTRX; + +/** IntNet destination used by the libslirp output callback. */ +typedef struct TSTNATOUTPUT +{ + /** NAT-facing IntNet interface. */ + INTNETIFCTX hIf; +} TSTNATOUTPUT; +/** Pointer to the IntNet output state. */ +typedef TSTNATOUTPUT *PTSTNATOUTPUT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Test handle used by fixture cleanup diagnostics. */ +static RTTEST g_hTest = NIL_RTTEST; + + +/********************************************************************************************************************************* +* Packet Helpers * +*********************************************************************************************************************************/ +/** Constructs a MAC address from literal octets. */ +static RTMAC tstMac(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4, uint8_t b5) +{ + RTMAC Mac; + Mac.au8[0] = b0; + Mac.au8[1] = b1; + Mac.au8[2] = b2; + Mac.au8[3] = b3; + Mac.au8[4] = b4; + Mac.au8[5] = b5; + return Mac; +} + + +/** Returns the Ethernet broadcast address. */ +static RTMAC tstMacBroadcast(void) +{ + return tstMac(0xff, 0xff, 0xff, 0xff, 0xff, 0xff); +} + + +/** Compares two MAC addresses. */ +static bool tstMacEqual(PCRTMAC pLeft, PCRTMAC pRight) +{ + return memcmp(pLeft, pRight, sizeof(*pLeft)) == 0; +} + + +/** Constructs an IPv4 address from dotted-decimal octets. */ +static RTNETADDRIPV4 tstIPv4(uint8_t b3, uint8_t b2, uint8_t b1, uint8_t b0) +{ + return RTNetIPv4AddrFromU8(b3, b2, b1, b0); +} + + +/** Populates the restricted configuration shared by the tests. */ +static void tstNatConfigInit(PVBOXNETSLIRPNATTESTCFG pCfg, bool fDhcp) +{ + RT_ZERO(*pCfg); + pCfg->IPv4Network = tstIPv4(10, 0, 2, 0); + pCfg->IPv4Netmask = tstIPv4(255, 255, 255, 0); + pCfg->IPv4Host = tstIPv4(10, 0, 2, 2); + pCfg->IPv4DhcpStart = tstIPv4(10, 0, 2, 15); + pCfg->IPv4Nameserver = tstIPv4(10, 0, 2, 3); + pCfg->fDhcp = fDhcp; + pCfg->fDns = true; + pCfg->fRestricted = true; +} + + +/** Starts an Ethernet frame. */ +static void tstEthernetBegin(PTSTFRAME pFrame, PCRTMAC pDstMac, PCRTMAC pSrcMac, uint16_t uEtherType) +{ + RT_ZERO(*pFrame); + PRTNETETHERHDR pEthernet = (PRTNETETHERHDR)pFrame->ab; + pEthernet->DstMac = *pDstMac; + pEthernet->SrcMac = *pSrcMac; + pEthernet->EtherType = RT_H2N_U16(uEtherType); + pFrame->cb = sizeof(*pEthernet); +} + + +/** Builds a guest ARP request. */ +static void tstBuildArpRequest(PTSTFRAME pFrame, PCRTMAC pGuestMac, + RTNETADDRIPV4 GuestIp, RTNETADDRIPV4 TargetIp) +{ + RTMAC const Broadcast = tstMacBroadcast(); + tstEthernetBegin(pFrame, &Broadcast, pGuestMac, RTNET_ETHERTYPE_ARP); + + PRTNETARPIPV4 pArp = (PRTNETARPIPV4)&pFrame->ab[pFrame->cb]; + RT_ZERO(*pArp); + pArp->Hdr.ar_htype = RT_H2N_U16(RTNET_ARP_ETHER); + pArp->Hdr.ar_ptype = RT_H2N_U16(RTNET_ETHERTYPE_IPV4); + pArp->Hdr.ar_hlen = sizeof(RTMAC); + pArp->Hdr.ar_plen = sizeof(RTNETADDRIPV4); + pArp->Hdr.ar_oper = RT_H2N_U16(RTNET_ARPOP_REQUEST); + pArp->ar_sha = *pGuestMac; + pArp->ar_spa = GuestIp; + pArp->ar_tpa = TargetIp; + pFrame->cb += sizeof(*pArp); +} + + +/** Recognizes an internally consistent ARP reply for the expected guest. */ +static bool tstIsArpReply(PCTSTFRAME pFrame, RTNETADDRIPV4 SenderIp, RTNETADDRIPV4 TargetIp, + PCRTMAC pTargetMac, PRTMAC pSenderMac) +{ + if (pFrame->cb < sizeof(RTNETETHERHDR) + sizeof(RTNETARPIPV4)) + return false; + + PCRTNETETHERHDR pEthernet = (PCRTNETETHERHDR)pFrame->ab; + PCRTNETARPIPV4 pArp = (PCRTNETARPIPV4)(pEthernet + 1); + if ( RT_N2H_U16(pEthernet->EtherType) != RTNET_ETHERTYPE_ARP + || RT_N2H_U16(pArp->Hdr.ar_htype) != RTNET_ARP_ETHER + || RT_N2H_U16(pArp->Hdr.ar_ptype) != RTNET_ETHERTYPE_IPV4 + || pArp->Hdr.ar_hlen != sizeof(RTMAC) + || pArp->Hdr.ar_plen != sizeof(RTNETADDRIPV4) + || RT_N2H_U16(pArp->Hdr.ar_oper) != RTNET_ARPOP_REPLY + || pArp->ar_spa.u != SenderIp.u + || pArp->ar_tpa.u != TargetIp.u + || !tstMacEqual(&pEthernet->SrcMac, &pArp->ar_sha) + || !tstMacEqual(&pEthernet->DstMac, &pArp->ar_tha) + || (pTargetMac && !tstMacEqual(&pArp->ar_tha, pTargetMac))) + return false; + + if (pSenderMac) + *pSenderMac = pArp->ar_sha; + return true; +} + + +/** Appends a one-byte DHCP option. */ +static void tstDhcpOptionU8(uint8_t **ppb, uint8_t uOption, uint8_t uValue) +{ + *(*ppb)++ = uOption; + *(*ppb)++ = 1; + *(*ppb)++ = uValue; +} + + +/** Appends an IPv4-valued DHCP option. */ +static void tstDhcpOptionIPv4(uint8_t **ppb, uint8_t uOption, RTNETADDRIPV4 Addr) +{ + *(*ppb)++ = uOption; + *(*ppb)++ = sizeof(Addr); + memcpy(*ppb, &Addr, sizeof(Addr)); + *ppb += sizeof(Addr); +} + + +/** Builds a DHCP DISCOVER or REQUEST Ethernet frame. */ +static void tstBuildDhcpRequest(PTSTFRAME pFrame, PCRTMAC pGuestMac, uint8_t uMsgType, + RTNETADDRIPV4 RequestedAddr, RTNETADDRIPV4 ServerId) +{ + RTMAC const Broadcast = tstMacBroadcast(); + RTNETADDRIPV4 const Zero = tstIPv4(0, 0, 0, 0); + RTNETADDRIPV4 const All = tstIPv4(255, 255, 255, 255); + tstEthernetBegin(pFrame, &Broadcast, pGuestMac, RTNET_ETHERTYPE_IPV4); + + PRTNETIPV4 pIp = (PRTNETIPV4)&pFrame->ab[pFrame->cb]; + RT_ZERO(*pIp); + pIp->ip_v = 4; + pIp->ip_hl = 5; + pIp->ip_len = RT_H2N_U16(RTNETIPV4_MIN_LEN + RTNETUDP_MIN_LEN + sizeof(RTNETBOOTP)); + pIp->ip_id = RT_H2N_U16(0x4400); + pIp->ip_off = RT_H2N_U16(RTNETIPV4_FLAGS_DF); + pIp->ip_ttl = 64; + pIp->ip_p = RTNETIPV4_PROT_UDP; + pIp->ip_src = Zero; + pIp->ip_dst = All; + pIp->ip_sum = RTNetIPv4HdrChecksum(pIp); + pFrame->cb += RTNETIPV4_MIN_LEN; + + PRTNETUDP pUdp = (PRTNETUDP)&pFrame->ab[pFrame->cb]; + RT_ZERO(*pUdp); + pUdp->uh_sport = RT_H2N_U16(RTNETIPV4_PORT_BOOTPC); + pUdp->uh_dport = RT_H2N_U16(RTNETIPV4_PORT_BOOTPS); + pUdp->uh_ulen = RT_H2N_U16(RTNETUDP_MIN_LEN + sizeof(RTNETBOOTP)); + pFrame->cb += RTNETUDP_MIN_LEN; + + PRTNETBOOTP pDhcp = (PRTNETBOOTP)&pFrame->ab[pFrame->cb]; + RT_ZERO(*pDhcp); + pDhcp->bp_op = RTNETBOOTP_OP_REQUEST; + pDhcp->bp_htype = RTNET_ARP_ETHER; + pDhcp->bp_hlen = sizeof(RTMAC); + pDhcp->bp_xid = RT_H2N_U32(TST_DHCP_XID); + pDhcp->bp_flags = RT_H2N_U16(RTNET_DHCP_FLAG_BROADCAST); + pDhcp->bp_chaddr.Mac = *pGuestMac; + pDhcp->bp_vend.Dhcp.dhcp_cookie = RT_H2N_U32(RTNET_DHCP_COOKIE); + + uint8_t *pbOption = pDhcp->bp_vend.Dhcp.dhcp_opts; + tstDhcpOptionU8(&pbOption, RTNET_DHCP_OPT_MSG_TYPE, uMsgType); + if (uMsgType == RTNET_DHCP_MT_REQUEST) + { + tstDhcpOptionIPv4(&pbOption, RTNET_DHCP_OPT_REQ_ADDR, RequestedAddr); + tstDhcpOptionIPv4(&pbOption, RTNET_DHCP_OPT_SERVER_ID, ServerId); + } + *pbOption++ = RTNET_DHCP_OPT_PARAM_REQ_LIST; + *pbOption++ = 4; + *pbOption++ = RTNET_DHCP_OPT_SUBNET_MASK; + *pbOption++ = RTNET_DHCP_OPT_ROUTERS; + *pbOption++ = RTNET_DHCP_OPT_DNS; + *pbOption++ = RTNET_DHCP_OPT_DOMAIN_NAME; + *pbOption++ = RTNET_DHCP_OPT_END; + + pFrame->cb += sizeof(*pDhcp); + pUdp->uh_sum = RTNetIPv4UDPChecksum(pIp, pUdp, pDhcp); +} + + +/** Parses a bounded IPv4/UDP DHCP server response. */ +static bool tstParseDhcpResponse(PCTSTFRAME pFrame, PCRTMAC pGuestMac, PTSTDHCPOFFER pOffer) +{ + if (pFrame->cb < sizeof(RTNETETHERHDR) + RTNETIPV4_MIN_LEN + RTNETUDP_MIN_LEN + RTNETBOOTP_DHCP_MIN_LEN) + return false; + + PCRTNETETHERHDR pEthernet = (PCRTNETETHERHDR)pFrame->ab; + if (RT_N2H_U16(pEthernet->EtherType) != RTNET_ETHERTYPE_IPV4) + return false; + PCRTNETIPV4 pIp = (PCRTNETIPV4)(pEthernet + 1); + if (pIp->ip_v != 4 || pIp->ip_hl < 5 || pIp->ip_p != RTNETIPV4_PROT_UDP) + return false; + + size_t const cbIpHdr = pIp->ip_hl * 4; + size_t const cbIpMax = pFrame->cb - sizeof(*pEthernet); + size_t const cbIp = RT_N2H_U16(pIp->ip_len); + if ( !RTNetIPv4IsHdrValid(pIp, cbIpMax, cbIpMax, true /* fChecksum */) + || cbIp < cbIpHdr + RTNETUDP_MIN_LEN + || cbIp > cbIpMax + || (RT_N2H_U16(pIp->ip_off) & (RTNETIPV4_FLAGS_MF | UINT16_C(0x1fff)))) + return false; + PCRTNETUDP pUdp = (PCRTNETUDP)((uint8_t const *)pIp + cbIpHdr); + size_t const cbUdp = RT_N2H_U16(pUdp->uh_ulen); + if ( cbUdp < RTNETUDP_MIN_LEN + RTNETBOOTP_DHCP_MIN_LEN + || cbIpHdr + cbUdp > cbIp + || !RTNetIPv4IsUDPValid(pIp, pUdp, pUdp + 1, cbIp - cbIpHdr, true /* fChecksum */) + || RT_N2H_U16(pUdp->uh_sport) != RTNETIPV4_PORT_BOOTPS + || RT_N2H_U16(pUdp->uh_dport) != RTNETIPV4_PORT_BOOTPC) + return false; + + size_t const cbDhcp = cbUdp - RTNETUDP_MIN_LEN; + PCRTNETBOOTP pDhcp = (PCRTNETBOOTP)(pUdp + 1); + if ( pDhcp->bp_op != RTNETBOOTP_OP_REPLY + || pDhcp->bp_htype != RTNET_ARP_ETHER + || pDhcp->bp_hlen != sizeof(RTMAC) + || pDhcp->bp_xid != RT_H2N_U32(TST_DHCP_XID) + || !tstMacEqual(&pDhcp->bp_chaddr.Mac, pGuestMac) + || pDhcp->bp_vend.Dhcp.dhcp_cookie != RT_H2N_U32(RTNET_DHCP_COOKIE)) + return false; + + RT_ZERO(*pOffer); + pOffer->YiAddr = pDhcp->bp_yiaddr; + uint8_t const *pbOption = pDhcp->bp_vend.Dhcp.dhcp_opts; + uint8_t const *pbEnd = (uint8_t const *)pDhcp + cbDhcp; + while (pbOption < pbEnd) + { + uint8_t const uOption = *pbOption++; + if (uOption == RTNET_DHCP_OPT_END) + break; + if (uOption == RTNET_DHCP_OPT_PAD) + continue; + if (pbOption >= pbEnd) + return false; + uint8_t const cbOption = *pbOption++; + if ((size_t)(pbEnd - pbOption) < cbOption) + return false; + + if (uOption == RTNET_DHCP_OPT_MSG_TYPE && cbOption == 1) + pOffer->uMsgType = pbOption[0]; + else if (uOption == RTNET_DHCP_OPT_SUBNET_MASK && cbOption == sizeof(RTNETADDRIPV4)) + { + memcpy(&pOffer->Netmask, pbOption, sizeof(pOffer->Netmask)); + pOffer->fNetmask = true; + } + else if (uOption == RTNET_DHCP_OPT_SERVER_ID && cbOption == sizeof(RTNETADDRIPV4)) + { + memcpy(&pOffer->ServerId, pbOption, sizeof(pOffer->ServerId)); + pOffer->fServerId = true; + } + else if (uOption == RTNET_DHCP_OPT_ROUTERS && cbOption >= sizeof(RTNETADDRIPV4)) + pOffer->fRouter = true; + else if (uOption == RTNET_DHCP_OPT_DNS && cbOption >= sizeof(RTNETADDRIPV4)) + pOffer->fDns = true; + pbOption += cbOption; + } + return pOffer->uMsgType != 0; +} + + +/********************************************************************************************************************************* +* Direct libslirp Test Helpers * +*********************************************************************************************************************************/ +/** Captures a frame emitted by the restricted libslirp wrapper. */ +static DECLCALLBACK(int) tstCaptureOutput(const void *pvFrame, size_t cbFrame, void *pvUser) +{ + PTSTFRAMEQUEUE pQueue = (PTSTFRAMEQUEUE)pvUser; + if (!pQueue || cbFrame > TST_MAX_FRAME || pQueue->cFrames >= TST_MAX_QUEUED_FRAMES) + return VERR_BUFFER_OVERFLOW; + + uint32_t const iFrame = (pQueue->iHead + pQueue->cFrames) % TST_MAX_QUEUED_FRAMES; + memcpy(pQueue->aFrames[iFrame].ab, pvFrame, cbFrame); + pQueue->aFrames[iFrame].cb = cbFrame; + pQueue->cFrames++; + return VINF_SUCCESS; +} + + +/** Pops the oldest captured frame. */ +static bool tstQueuePop(PTSTFRAMEQUEUE pQueue, PTSTFRAME pFrame) +{ + if (!pQueue->cFrames) + return false; + *pFrame = pQueue->aFrames[pQueue->iHead]; + pQueue->iHead = (pQueue->iHead + 1) % TST_MAX_QUEUED_FRAMES; + pQueue->cFrames--; + return true; +} + + +/** Finds and consumes a matching ARP reply. */ +static bool tstFindArpReply(PTSTFRAMEQUEUE pQueue, RTNETADDRIPV4 SenderIp, RTNETADDRIPV4 TargetIp, + PCRTMAC pTargetMac, PRTMAC pSenderMac) +{ + uint32_t cLeft = pQueue->cFrames; + while (cLeft--) + { + TSTFRAME Frame; + tstQueuePop(pQueue, &Frame); + if (tstIsArpReply(&Frame, SenderIp, TargetIp, pTargetMac, pSenderMac)) + return true; + } + return false; +} + + +/** Finds and consumes a DHCP response of the requested type. */ +static bool tstFindDhcpResponse(PTSTFRAMEQUEUE pQueue, PCRTMAC pGuestMac, uint8_t uMsgType, + PTSTDHCPOFFER pOffer) +{ + uint32_t cLeft = pQueue->cFrames; + while (cLeft--) + { + TSTFRAME Frame; + tstQueuePop(pQueue, &Frame); + if (tstParseDhcpResponse(&Frame, pGuestMac, pOffer) && pOffer->uMsgType == uMsgType) + return true; + } + return false; +} + + +/** Polls libslirp until a matching ARP reply is captured or the deadline expires. */ +static int tstWaitForArpReply(PVBOXNETSLIRPNATTEST hNat, PTSTFRAMEQUEUE pQueue, + RTNETADDRIPV4 SenderIp, RTNETADDRIPV4 TargetIp, + PCRTMAC pTargetMac, PRTMAC pSenderMac) +{ + uint64_t const msStart = RTTimeMilliTS(); + for (;;) + { + if (tstFindArpReply(pQueue, SenderIp, TargetIp, pTargetMac, pSenderMac)) + return VINF_SUCCESS; + if (RTTimeMilliTS() - msStart >= TST_WAIT_MS) + return VERR_TIMEOUT; + int const rc = VBoxNetSlirpNATTestPoll(hNat, TST_POLL_MS); + if (RT_FAILURE(rc)) + return rc; + } +} + + +/** Polls libslirp until a matching DHCP response is captured or the deadline expires. */ +static int tstWaitForDhcpResponse(PVBOXNETSLIRPNATTEST hNat, PTSTFRAMEQUEUE pQueue, + PCRTMAC pGuestMac, uint8_t uMsgType, PTSTDHCPOFFER pOffer) +{ + uint64_t const msStart = RTTimeMilliTS(); + for (;;) + { + if (tstFindDhcpResponse(pQueue, pGuestMac, uMsgType, pOffer)) + return VINF_SUCCESS; + if (RTTimeMilliTS() - msStart >= TST_WAIT_MS) + return VERR_TIMEOUT; + int const rc = VBoxNetSlirpNATTestPoll(hNat, TST_POLL_MS); + if (RT_FAILURE(rc)) + return rc; + } +} + + +/** Verifies the restricted wrapper rejects unsafe or inconsistent configuration. */ +static void tstConfigValidation(void) +{ + RTTestSub(g_hTest, "restricted configuration validation"); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, false); + TSTFRAMEQUEUE Queue; + RT_ZERO(Queue); + PVBOXNETSLIRPNATTEST hNat = NULL; + + Cfg.fRestricted = false; + int rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC(g_hTest, rc, VERR_ACCESS_DENIED); + RTTEST_CHECK(g_hTest, hNat == NULL); + + tstNatConfigInit(&Cfg, false); + Cfg.IPv4Host = tstIPv4(192, 0, 2, 1); + rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC(g_hTest, rc, VERR_INVALID_PARAMETER); + RTTEST_CHECK(g_hTest, hNat == NULL); +} + + +/** Verifies the full restricted DHCP DISCOVER/OFFER/REQUEST/ACK exchange. */ +static void tstDhcp(void) +{ + RTTestSub(g_hTest, "restricted libslirp DHCPv4 lease"); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, true); + TSTFRAMEQUEUE Queue; + RT_ZERO(Queue); + PVBOXNETSLIRPNATTEST hNat = NULL; + int rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC_RETV(g_hTest, rc, VINF_SUCCESS); + + RTMAC const GuestMac = tstMac(0x08, 0x00, 0x27, 0xaa, 0xbb, 0x15); + TSTFRAME Frame; + tstBuildDhcpRequest(&Frame, &GuestMac, RTNET_DHCP_MT_DISCOVER, + tstIPv4(0, 0, 0, 0), tstIPv4(0, 0, 0, 0)); + rc = VBoxNetSlirpNATTestInput(hNat, Frame.ab, Frame.cb); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + TSTDHCPOFFER Offer; + RT_ZERO(Offer); + if (RT_SUCCESS(rc)) + rc = tstWaitForDhcpResponse(hNat, &Queue, &GuestMac, RTNET_DHCP_MT_OFFER, &Offer); + bool const fOffer = RT_SUCCESS(rc); + RTTEST_CHECK_MSG(g_hTest, fOffer, (g_hTest, "No valid DHCPOFFER was returned\n")); + if (fOffer) + { + RTTEST_CHECK(g_hTest, Offer.YiAddr.u != 0); + RTTEST_CHECK(g_hTest, (Offer.YiAddr.u & Cfg.IPv4Netmask.u) == Cfg.IPv4Network.u); + RTTEST_CHECK(g_hTest, Offer.fNetmask && Offer.Netmask.u == Cfg.IPv4Netmask.u); + RTTEST_CHECK(g_hTest, Offer.fServerId && Offer.ServerId.u == Cfg.IPv4Host.u); + RTTEST_CHECK_MSG(g_hTest, !Offer.fRouter && !Offer.fDns, + (g_hTest, "Restricted DHCP unexpectedly advertised host routing or DNS access\n")); + + RT_ZERO(Queue); + tstBuildDhcpRequest(&Frame, &GuestMac, RTNET_DHCP_MT_REQUEST, Offer.YiAddr, Offer.ServerId); + rc = VBoxNetSlirpNATTestInput(hNat, Frame.ab, Frame.cb); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + TSTDHCPOFFER Ack; + RT_ZERO(Ack); + if (RT_SUCCESS(rc)) + rc = tstWaitForDhcpResponse(hNat, &Queue, &GuestMac, RTNET_DHCP_MT_ACK, &Ack); + bool const fAck = RT_SUCCESS(rc); + RTTEST_CHECK_MSG(g_hTest, fAck, (g_hTest, "No valid DHCPACK was returned\n")); + if (fAck) + { + RTTEST_CHECK(g_hTest, Ack.YiAddr.u == Offer.YiAddr.u); + RTTEST_CHECK(g_hTest, Ack.fNetmask && Ack.Netmask.u == Cfg.IPv4Netmask.u); + RTTEST_CHECK(g_hTest, Ack.fServerId && Ack.ServerId.u == Cfg.IPv4Host.u); + } + } + + VBoxNetSlirpNATTestDestroy(hNat); +} + + +/** Verifies gateway ARP and negative ARP behavior directly through libslirp. */ +static void tstArp(void) +{ + RTTestSub(g_hTest, "restricted libslirp ARP"); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, false); + TSTFRAMEQUEUE Queue; + RT_ZERO(Queue); + PVBOXNETSLIRPNATTEST hNat = NULL; + int rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC_RETV(g_hTest, rc, VINF_SUCCESS); + + RTMAC const GuestMac = tstMac(0x08, 0x00, 0x27, 0xaa, 0xbb, 0x16); + RTNETADDRIPV4 const GuestIp = tstIPv4(10, 0, 2, 16); + TSTFRAME Frame; + tstBuildArpRequest(&Frame, &GuestMac, GuestIp, Cfg.IPv4Host); + rc = VBoxNetSlirpNATTestInput(hNat, Frame.ab, Frame.cb); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + RTMAC GatewayMac; + RT_ZERO(GatewayMac); + if (RT_SUCCESS(rc)) + rc = tstWaitForArpReply(hNat, &Queue, Cfg.IPv4Host, GuestIp, &GuestMac, &GatewayMac); + RTTEST_CHECK_MSG(g_hTest, RT_SUCCESS(rc), + (g_hTest, "No valid gateway ARP reply was returned\n")); + RTTEST_CHECK(g_hTest, !tstMacEqual(&GatewayMac, &GuestMac)); + + RT_ZERO(Queue); + tstBuildArpRequest(&Frame, &GuestMac, GuestIp, tstIPv4(10, 0, 2, 99)); + rc = VBoxNetSlirpNATTestInput(hNat, Frame.ab, Frame.cb); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = VBoxNetSlirpNATTestPoll(hNat, 20); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + RTTEST_CHECK_MSG(g_hTest, Queue.cFrames == 0, + (g_hTest, "libslirp answered ARP for an unowned guest-network address\n")); + + VBoxNetSlirpNATTestDestroy(hNat); +} + + +/** Verifies production-sized frame filtering and malformed-input drops. */ +static void tstMalformedFrames(void) +{ + RTTestSub(g_hTest, "malformed and oversized Ethernet input"); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, false); + TSTFRAMEQUEUE Queue; + RT_ZERO(Queue); + PVBOXNETSLIRPNATTEST hNat = NULL; + int rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC_RETV(g_hTest, rc, VINF_SUCCESS); + + uint8_t abFrame[1523]; + RT_ZERO(abFrame); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, NULL, sizeof(abFrame)), VERR_INVALID_PARAMETER); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, 0), VERR_INVALID_PARAMETER); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, _64K + 1), VERR_INVALID_PARAMETER); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, 8), VINF_SUCCESS); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, sizeof(abFrame)), VINF_SUCCESS); + + RTNETETHERHDR *pEthernet = (RTNETETHERHDR *)abFrame; + pEthernet->DstMac = tstMacBroadcast(); + pEthernet->SrcMac = tstMac(0x08, 0x00, 0x27, 0xaa, 0xbb, 0x17); + pEthernet->EtherType = RT_H2N_U16(0x88b5); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, 60), VINF_SUCCESS); + RTTEST_CHECK(g_hTest, Queue.cFrames == 0); + + VBoxNetSlirpNATTestDestroy(hNat); +} + + +/********************************************************************************************************************************* +* Standalone Ring-3 Switch Fixture * +*********************************************************************************************************************************/ +/** Creates a unique name for a service or network. */ +static int tstMakeUuidName(const char *pszPrefix, char *pszName, size_t cbName) +{ + RTUUID Uuid; + int rc = RTUuidCreate(&Uuid); + if (RT_SUCCESS(rc)) + { + char szUuid[RTUUID_STR_LENGTH]; + rc = RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pszName, cbName, "%s-%s", pszPrefix, szUuid); + if (cch < 0 || (size_t)cch >= cbName) + rc = VERR_BUFFER_OVERFLOW; + } + } + return rc; +} + + +/** Returns whether a Local IPC connection failure means no server is present. */ +static bool tstServiceIsAbsent(int rc) +{ + return rc == VERR_FILE_NOT_FOUND + || rc == VERR_PATH_NOT_FOUND + || rc == VERR_NET_CONNECTION_REFUSED + || rc == VERR_PIPE_NOT_CONNECTED; +} + + +/** Reads the auto-started switch process ID. */ +static int tstServiceReadPid(PTSTSWITCHSERVICE pService) +{ + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpen(&hFile, pService->szPidFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + char szPid[32]; + size_t cbRead = 0; + rc = RTFileRead(hFile, szPid, sizeof(szPid) - 1, &cbRead); + int const rcClose = RTFileClose(hFile); + if (RT_SUCCESS(rc)) + rc = rcClose; + if (RT_SUCCESS(rc)) + { + szPid[cbRead] = '\0'; + uint32_t uPid = NIL_RTPROCESS; + rc = RTStrToUInt32Full(szPid, 10, &uPid); + if (RT_SUCCESS(rc)) + { + if (uPid == NIL_RTPROCESS || uPid == RTProcSelf()) + rc = VERR_INVALID_PARAMETER; + else + pService->hProcess = uPid; + } + } + } + return rc; +} + + +/** Waits for the auto-started switch to publish its process ID. */ +static int tstServiceWaitForPid(PTSTSWITCHSERVICE pService) +{ + uint64_t const msStart = RTTimeMilliTS(); + int rc; + do + { + rc = tstServiceReadPid(pService); + if (RT_SUCCESS(rc)) + return rc; + if ( rc != VERR_FILE_NOT_FOUND + && rc != VERR_PATH_NOT_FOUND + && rc != VERR_NO_DIGITS) + return rc; + RTThreadSleep(10); + } while (RTTimeMilliTS() - msStart < TST_WAIT_MS); + return rc; +} + + +/** Prepares unique environment variables used by the production auto-start path. */ +static int tstServiceStart(PTSTSWITCHSERVICE pService) +{ + RT_ZERO(*pService); + pService->hProcess = NIL_RTPROCESS; + + int rc = RTPathTemp(pService->szTempDir, sizeof(pService->szTempDir)); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pService->szTempDir, sizeof(pService->szTempDir), "tstVBoxNatGuestSide-XXXXXX"); + if (RT_SUCCESS(rc)) + { + rc = RTDirCreateTemp(pService->szTempDir, 0700); + if (RT_SUCCESS(rc)) + pService->fTempDirCreated = true; + } + if (RT_SUCCESS(rc)) + rc = tstMakeUuidName("tst-vbox-nat", pService->szService, sizeof(pService->szService)); + if (RT_SUCCESS(rc)) + rc = RTPathExecDir(pService->szExec, sizeof(pService->szExec)); + if (RT_SUCCESS(rc)) +#ifdef RT_OS_WINDOWS + rc = RTPathAppend(pService->szExec, sizeof(pService->szExec), "VBoxIntNetR3SwitchTestHelper.exe"); +#else + rc = RTPathAppend(pService->szExec, sizeof(pService->szExec), "VBoxIntNetR3SwitchTestHelper"); +#endif + if (RT_SUCCESS(rc)) + rc = RTStrCopy(pService->szPidFile, sizeof(pService->szPidFile), pService->szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pService->szPidFile, sizeof(pService->szPidFile), "switch.pid"); + if (RT_SUCCESS(rc)) + rc = RTStrCopy(pService->szLockFile, sizeof(pService->szLockFile), pService->szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pService->szLockFile, sizeof(pService->szLockFile), "switch.lock"); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SVC_NAME", pService->szService); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_EXE", pService->szExec); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_PID_FILE", pService->szPidFile); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_LOCK_FILE", pService->szLockFile); + if (RT_SUCCESS(rc)) + { + RTLOCALIPCSESSION hExisting = NIL_RTLOCALIPCSESSION; + rc = RTLocalIpcSessionConnect(&hExisting, pService->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + { + RTLocalIpcSessionClose(hExisting); + rc = VERR_ALREADY_EXISTS; + } + else if (tstServiceIsAbsent(rc)) + rc = VINF_SUCCESS; + } + return rc; +} + + +/** Removes a stale POSIX Local IPC filesystem node after the helper exits. */ +static void tstServiceCleanupEndpoint(PTSTSWITCHSERVICE pService) +{ + if (!pService->szService[0]) + return; + + RTLOCALIPCSESSION hExisting = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcSessionConnect(&hExisting, pService->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + { + RTLocalIpcSessionClose(hExisting); + RTTestFailed(g_hTest, "Switch IPC endpoint still accepts clients after helper exit"); + return; + } + if (rc == VERR_NET_CONNECTION_REFUSED) + RTTestFailed(g_hTest, "Switch IPC endpoint pathname was left behind after helper exit"); + else if (!tstServiceIsAbsent(rc)) + { + RTTestFailed(g_hTest, "Checking switch IPC endpoint failed: %Rrc", rc); + return; + } + + RTLOCALIPCSERVER hCleanup = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hCleanup, pService->szService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + RTLocalIpcServerDestroy(hCleanup); + else + RTTestFailed(g_hTest, "Cleaning switch IPC endpoint failed: %Rrc", rc); +} + + +/** Waits for the oneshot helper and removes all fixture state. */ +static void tstServiceStop(PTSTSWITCHSERVICE pService) +{ + if ( !pService->fProcessReaped + && pService->hProcess == NIL_RTPROCESS + && pService->szPidFile[0]) + { + uint64_t const msStart = RTTimeMilliTS(); + int rc; + do + { + rc = tstServiceReadPid(pService); + if (RT_SUCCESS(rc) || (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)) + break; + RTThreadSleep(10); + } while (RTTimeMilliTS() - msStart < RT_MS_1SEC); + } + + if (pService->hProcess != NIL_RTPROCESS) + { + RTPROCSTATUS Status; + int rc = VERR_PROCESS_RUNNING; + uint64_t const msStart = RTTimeMilliTS(); + while (rc == VERR_PROCESS_RUNNING && RTTimeMilliTS() - msStart < RT_MS_5SEC) + { + rc = RTProcWait(pService->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &Status); + if (rc == VERR_PROCESS_RUNNING) + RTThreadSleep(10); + } + if (rc == VERR_PROCESS_RUNNING) + { + RTTestFailed(g_hTest, "Switch helper did not exit after its last client disconnected"); + int const rcTerminate = RTProcTerminate(pService->hProcess); + if (RT_SUCCESS(rcTerminate)) + rc = RTProcWait(pService->hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status); + else + rc = rcTerminate; + } + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Waiting for switch helper failed: %Rrc", rc); + else if (Status.enmReason != RTPROCEXITREASON_NORMAL || Status.iStatus != RTEXITCODE_SUCCESS) + RTTestFailed(g_hTest, "Switch helper exit reason/status: %d/%d", Status.enmReason, Status.iStatus); + else + pService->fProcessReaped = true; + pService->hProcess = NIL_RTPROCESS; + } + + tstServiceCleanupEndpoint(pService); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_LOCK_FILE"); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_PID_FILE"); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_EXE"); + RTEnvUnset("VBOX_INTNET_R3_SVC_NAME"); + + if (pService->szPidFile[0]) + RTFileDelete(pService->szPidFile); + if (pService->szLockFile[0]) + RTFileDelete(pService->szLockFile); + if (pService->fTempDirCreated) + { + int const rc = RTDirRemove(pService->szTempDir); + if (RT_FAILURE(rc) && rc != VERR_PATH_NOT_FOUND && rc != VERR_FILE_NOT_FOUND) + RTTestFailed(g_hTest, "Removing switch helper temporary directory failed: %Rrc", rc); + } +} + + +/********************************************************************************************************************************* +* Ring-3 IntNet / NAT Integration * +*********************************************************************************************************************************/ +/** Commits one libslirp output frame to the NAT-facing IntNet interface. */ +static DECLCALLBACK(int) tstIntNetOutput(const void *pvFrame, size_t cbFrame, void *pvUser) +{ + PTSTNATOUTPUT pOutput = (PTSTNATOUTPUT)pvUser; + if (!pOutput || cbFrame > UINT32_MAX) + return VERR_INVALID_PARAMETER; + + INTNETFRAME Frame; + int rc = IntNetR3IfQueryOutputFrame(pOutput->hIf, (uint32_t)cbFrame, &Frame); + if (RT_SUCCESS(rc)) + { + memcpy(Frame.pvFrame, pvFrame, cbFrame); + rc = IntNetR3IfOutputFrameCommit(pOutput->hIf, &Frame); + } + return rc; +} + + +/** Injects one NAT-facing IntNet frame into libslirp. */ +static DECLCALLBACK(void) tstNatInput(void *pvUser, void *pvFrame, uint32_t cbFrame) +{ + PTSTNATPUMP pPump = (PTSTNATPUMP)pvUser; + int const rc = VBoxNetSlirpNATTestInput(pPump->hNat, pvFrame, cbFrame); + if (RT_FAILURE(rc) && RT_SUCCESS(pPump->rcInput)) + pPump->rcInput = rc; +} + + +/** Runs the NAT-facing IntNet receive pump. */ +static DECLCALLBACK(int) tstNatPumpThread(RTTHREAD hThread, void *pvUser) +{ + PTSTNATPUMP pPump = (PTSTNATPUMP)pvUser; + RTThreadUserSignal(hThread); + return IntNetR3IfPumpPkts(pPump->hIf, tstNatInput, pPump, NULL, NULL); +} + + +/** Captures only the expected gateway ARP reply on the guest interface. */ +static DECLCALLBACK(void) tstGuestInput(void *pvUser, void *pvFrame, uint32_t cbFrame) +{ + PTSTGUESTRX pRx = (PTSTGUESTRX)pvUser; + if (!pRx->fReply && cbFrame <= sizeof(pRx->Reply.ab)) + { + TSTFRAME Frame; + Frame.cb = cbFrame; + memcpy(Frame.ab, pvFrame, cbFrame); + if (tstIsArpReply(&Frame, pRx->GatewayIp, pRx->GuestIp, &pRx->GuestMac, NULL)) + { + pRx->Reply = Frame; + pRx->fReply = true; + RTSemEventSignal(pRx->hReplyEvent); + } + } +} + + +/** Runs the guest-facing IntNet receive pump. */ +static DECLCALLBACK(int) tstGuestRxThread(RTTHREAD hThread, void *pvUser) +{ + PTSTGUESTRX pRx = (PTSTGUESTRX)pvUser; + RTThreadUserSignal(hThread); + return IntNetR3IfPumpPkts(pRx->hIf, tstGuestInput, pRx, NULL, NULL); +} + + +/** Starts one waitable IntNet pump thread. */ +static int tstPumpStart(PRTTHREAD phThread, PFNRTTHREAD pfnThread, void *pvUser, const char *pszName) +{ + int rc = RTThreadCreate(phThread, pfnThread, pvUser, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, pszName); + if (RT_SUCCESS(rc)) + rc = RTThreadUserWait(*phThread, RT_MS_1SEC); + return rc; +} + + +/** Aborts and joins one IntNet pump. */ +static void tstPumpStop(INTNETIFCTX hIf, PRTTHREAD phThread, const char *pszName) +{ + if (*phThread == NIL_RTTHREAD) + return; + + int rc = IntNetR3IfWaitAbort(hIf); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(*phThread, RT_MS_5SEC, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "%s did not stop within five seconds: %Rrc", pszName, rc); + int const rcAbort = IntNetR3IfWaitAbort(hIf); + RTTEST_CHECK_RC(g_hTest, rcAbort, VINF_SUCCESS); + rc = RTThreadWait(*phThread, RT_INDEFINITE_WAIT, &rcThread); + } + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + exit(RTEXITCODE_FAILURE); + RTTEST_CHECK_MSG(g_hTest, rcThread == VERR_SEM_DESTROYED, + (g_hTest, "%s returned %Rrc instead of %Rrc\n", pszName, rcThread, VERR_SEM_DESTROYED)); + *phThread = NIL_RTTHREAD; +} + + +/** Sends one Ethernet frame through an IntNet interface. */ +static int tstIntNetSend(INTNETIFCTX hIf, PCTSTFRAME pFrame) +{ + INTNETFRAME Frame; + int rc = IntNetR3IfQueryOutputFrame(hIf, (uint32_t)pFrame->cb, &Frame); + if (RT_SUCCESS(rc)) + { + memcpy(Frame.pvFrame, pFrame->ab, pFrame->cb); + rc = IntNetR3IfOutputFrameCommit(hIf, &Frame); + } + return rc; +} + + +/** Verifies an ARP request/reply through the external Local IPC R3 switch. */ +static void tstR3IntNetNatArp(void) +{ + RTTestSub(g_hTest, "R3 IntNet switch -> restricted NAT -> R3 IntNet"); + + TSTSWITCHSERVICE Service; + int rc = tstServiceStart(&Service); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { + tstServiceStop(&Service); + return; + } + + INTNETIFCTX hGuestIf = NULL; + INTNETIFCTX hNatIf = NULL; + PVBOXNETSLIRPNATTEST hNat = NULL; + TSTNATPUMP NatPump; + RT_ZERO(NatPump); + NatPump.hThread = NIL_RTTHREAD; + NatPump.rcInput = VINF_SUCCESS; + TSTGUESTRX GuestRx; + RT_ZERO(GuestRx); + GuestRx.hThread = NIL_RTTHREAD; + GuestRx.hReplyEvent = NIL_RTSEMEVENT; + + char szNetwork[INTNET_MAX_NETWORK_NAME]; + rc = tstMakeUuidName("tst-nat-r3", szNetwork, sizeof(szNetwork)); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfCreate(&hGuestIf, szNetwork); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = tstServiceWaitForPid(&Service); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfCreate(&hNatIf, szNetwork); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + RTMAC const GuestMac = tstMac(0x08, 0x00, 0x27, 0xaa, 0xbb, 0x18); + RTMAC const NatMac = tstMac(0x52, 0x54, 0x00, 0x12, 0x35, 0x00); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfSetMacAddress(hGuestIf, &GuestMac); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfSetMacAddress(hNatIf, &NatMac); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfSetActive(hGuestIf, true); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfSetActive(hNatIf, true); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, false); + TSTNATOUTPUT NatOutput; + NatOutput.hIf = hNatIf; + if (RT_SUCCESS(rc)) + rc = VBoxNetSlirpNATTestCreate(&Cfg, tstIntNetOutput, &NatOutput, &hNat); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + NatPump.hIf = hNatIf; + NatPump.hNat = hNat; + if (RT_SUCCESS(rc)) + rc = tstPumpStart(&NatPump.hThread, tstNatPumpThread, &NatPump, "NatR3Pump"); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + GuestRx.hIf = hGuestIf; + GuestRx.GatewayIp = Cfg.IPv4Host; + GuestRx.GuestIp = tstIPv4(10, 0, 2, 18); + GuestRx.GuestMac = GuestMac; + if (RT_SUCCESS(rc)) + rc = RTSemEventCreate(&GuestRx.hReplyEvent); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = tstPumpStart(&GuestRx.hThread, tstGuestRxThread, &GuestRx, "GuestR3Rx"); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + if (RT_SUCCESS(rc)) + { + TSTFRAME Request; + tstBuildArpRequest(&Request, &GuestMac, GuestRx.GuestIp, Cfg.IPv4Host); + rc = tstIntNetSend(hGuestIf, &Request); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = RTSemEventWait(GuestRx.hReplyEvent, TST_WAIT_MS); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + RTTEST_CHECK_MSG(g_hTest, GuestRx.fReply, + (g_hTest, "Guest did not receive the NAT gateway ARP reply through the R3 switch\n")); + } + + tstPumpStop(hGuestIf, &GuestRx.hThread, "Guest R3 receive pump"); + tstPumpStop(hNatIf, &NatPump.hThread, "NAT R3 receive pump"); + RTTEST_CHECK_RC(g_hTest, NatPump.rcInput, VINF_SUCCESS); + if (GuestRx.hReplyEvent != NIL_RTSEMEVENT) + RTSemEventDestroy(GuestRx.hReplyEvent); + VBoxNetSlirpNATTestDestroy(hNat); + if (hNatIf) + IntNetR3IfDestroy(hNatIf); + if (hGuestIf) + IntNetR3IfDestroy(hGuestIf); + tstServiceStop(&Service); +} + + +/********************************************************************************************************************************* +* Main * +*********************************************************************************************************************************/ +int main(int argc, char **argv) +{ + RT_NOREF(argc, argv); + + int rc = RTTestInitAndCreate("tstVBoxNatGuestSide", &g_hTest); + if (RT_FAILURE(rc)) + return rc; + RTTestBanner(g_hTest); + + tstConfigValidation(); + tstDhcp(); + tstArp(); + tstMalformedFrames(); + tstR3IntNetNatArp(); + + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSideInternal.h b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSideInternal.h new file mode 100644 index 000000000000..0c190f8d0d0b --- /dev/null +++ b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSideInternal.h @@ -0,0 +1,148 @@ +/* $Id: tstVBoxNatGuestSideInternal.h 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ */ +/** @file + * VBoxNetSlirpNAT guest-side testcase hooks. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_testcase_tstVBoxNatGuestSideInternal_h +#define VBOX_INCLUDED_SRC_testcase_tstVBoxNatGuestSideInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include + +RT_C_DECLS_BEGIN + +/** Opaque test instance owning one restricted libslirp datapath. */ +typedef struct VBOXNETSLIRPNATTEST *PVBOXNETSLIRPNATTEST; + +/** + * Callback for an Ethernet frame emitted toward the guest. + * + * The frame is valid only for the duration of the callback. The callback is + * made synchronously by VBoxNetSlirpNATTestInput or VBoxNetSlirpNATTestPoll; + * no callback may occur after either function returns. + * + * @returns VINF_SUCCESS on success, or an IPRT error to propagate to the + * currently executing test-hook call. + * @param pvFrame Frame bytes emitted toward the guest. + * @param cbFrame Number of valid bytes in @a pvFrame. + * @param pvUser User pointer supplied to VBoxNetSlirpNATTestCreate. + */ +typedef DECLCALLBACKTYPE(int, FNVBOXNETSLIRPNATTESTOUTPUT,(const void *pvFrame, size_t cbFrame, void *pvUser)); +/** Pointer to a guest-output callback. */ +typedef FNVBOXNETSLIRPNATTESTOUTPUT *PFNVBOXNETSLIRPNATTESTOUTPUT; + +/** Deterministic IPv4 configuration for the test-only NAT datapath wrapper. */ +typedef struct VBOXNETSLIRPNATTESTCFG +{ + /** IPv4 network address in IPRT/network representation. */ + RTNETADDRIPV4 IPv4Network; + /** IPv4 netmask in IPRT/network representation. */ + RTNETADDRIPV4 IPv4Netmask; + /** Guest-visible gateway address. */ + RTNETADDRIPV4 IPv4Host; + /** First address in the DHCP pool. */ + RTNETADDRIPV4 IPv4DhcpStart; + /** Guest-visible DNS proxy address advertised by DHCP. */ + RTNETADDRIPV4 IPv4Nameserver; + /** Whether libslirp DHCP is enabled. */ + bool fDhcp; + /** Whether the libslirp DNS proxy is enabled. */ + bool fDns; + /** Whether host-network access is disabled. Tests require true. */ + bool fRestricted; +} VBOXNETSLIRPNATTESTCFG; +/** Pointer to a NAT testcase configuration. */ +typedef VBOXNETSLIRPNATTESTCFG *PVBOXNETSLIRPNATTESTCFG; +/** Pointer to a const NAT testcase configuration. */ +typedef const VBOXNETSLIRPNATTESTCFG *PCVBOXNETSLIRPNATTESTCFG; + +/** + * Create one guest-side test context (without COM). + * + * The implementation must validate that @a pCfg requests a restricted IPv4 + * network and leave frame transport to @a pfnOutput. It intentionally does + * not instantiate the COM VBoxNetSlirpNAT service; its configuration is + * locked down for gest packet tests. The returned handle is + * single-thread affine: Input and Poll must not be called concurrently. + * + * @returns VINF_SUCCESS on success, or an IPRT/VBox error. + * @param pCfg NAT configuration. + * @param pfnOutput Required callback for frames sent toward the guest. + * @param pvOutputUser User pointer passed to @a pfnOutput. + * @param phNat Where to return the opaque test handle. Set to NULL + * on failure. + */ +DECLHIDDEN(int) VBoxNetSlirpNATTestCreate(PCVBOXNETSLIRPNATTESTCFG pCfg, + PFNVBOXNETSLIRPNATTESTOUTPUT pfnOutput, + void *pvOutputUser, PVBOXNETSLIRPNATTEST *phNat); + +/** + * Destroy a test instance. + * + * The implementation must cancel timers, release libslirp state, and guarantee + * that no output callback remains in flight when it returns. + * + * @param hNat Test instance. NULL is accepted as a no-op. + */ +DECLHIDDEN(void) VBoxNetSlirpNATTestDestroy(PVBOXNETSLIRPNATTEST hNat); + +/** + * Inject one complete guest Ethernet frame through the same validation and + * slirp-input path used by VBoxNetSlirpNAT::processFrame. + * + * Immediate output must be delivered before this function returns. Like + * VBoxNetSlirpNAT::processFrame, frames smaller than an Ethernet header or + * larger than 1522 bytes are dropped successfully. NULL input, zero size, or + * input larger than 64 KiB returns VERR_INVALID_PARAMETER. + * + * @returns VINF_SUCCESS when accepted or deliberately dropped, an error from + * argument validation/libslirp, or an output-callback error. + * @param hNat Test instance. + * @param pvFrame Guest Ethernet frame bytes. + * @param cbFrame Number of bytes in @a pvFrame. + */ +DECLHIDDEN(int) VBoxNetSlirpNATTestInput(PVBOXNETSLIRPNATTEST hNat, + const void *pvFrame, size_t cbFrame); + +/** + * Progress production libslirp timers and pending socket work for at most the + * requested interval. + * + * This call must not access the unrestricted host network when fRestricted is + * set. Output callbacks are completed before the function returns. + * + * @returns VINF_SUCCESS, an IPRT/VBox poll error, or an output-callback error. + * @param hNat Test instance. + * @param cMsMax Maximum poll duration in milliseconds. + */ +DECLHIDDEN(int) VBoxNetSlirpNATTestPoll(PVBOXNETSLIRPNATTEST hNat, uint32_t cMsMax); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_testcase_tstVBoxNatGuestSideInternal_h */ From c366a41cfe6e587f746fc6dfdb680ef27d5b6759 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 08:19:17 +0000 Subject: [PATCH 031/176] =?UTF-8?q?NetworkServices/VBoxNetDhcpd:=20Added?= =?UTF-8?q?=20support=20for=20driverless=20R3=20startup.=20=E2=80=8Bbugref?= =?UTF-8?q?:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174724 --- src/VBox/NetworkServices/Dhcpd/Makefile.kmk | 7 ++++--- src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp | 6 +++++- src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp | 6 +++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk index a2213551f792..99ac8258d934 100644 --- a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk +++ b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112878 2026-02-09 09:27:34Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114885 2026-08-07 08:19:17Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for the DHCP server. # @@ -35,6 +35,7 @@ ifdef VBOX_WITH_HARDENING PROGRAMS += VBoxNetDHCPHardened VBoxNetDHCPHardened_TEMPLATE = VBoxR3HardenedExe VBoxNetDHCPHardened_NAME = VBoxNetDHCP + VBoxNetDHCPHardened_DEFS += $(if $(and $(VBOX_WITH_DRIVERLESS_NEM_FALLBACK),$(VBOX_WITH_INTNET_SERVICE_IN_R3)),VBOX_WITH_DRIVERLESS_NEM_FALLBACK,) VBoxNetDHCPHardened_SOURCES = VBoxNetDhcpdHardened.cpp VBoxNetDHCPHardened_LDFLAGS.win = /SUBSYSTEM:windows $(call VBOX_SET_VER_INFO_DLL,VBoxNetDHCPHardened,VirtualBox DHCP Server,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description. @@ -58,7 +59,8 @@ else VBoxNetDHCP_DEFS = KBUILD_TYPE=\"$(KBUILD_TYPE)\" endif VBoxNetDHCP_DEFS += \ - $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) #VBoxNetDHCP_DEFS += IPv6 #VBoxNetDHCP_DEFS.linux = WITH_VALGRIND @@ -95,4 +97,3 @@ else endif include $(FILE_KBUILD_SUB_FOOTER) - diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp index b8e648fa5aa1..ff8c78ef0fba 100644 --- a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp +++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetDhcpd.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxNetDhcpd.cpp 114885 2026-08-07 08:19:17Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetDhcpd - DHCP server for host-only and NAT networks. */ @@ -515,7 +515,11 @@ extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv) int main(int argc, char **argv) { +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + int rc = RTR3InitExe(argc, &argv, 0 /* fFlags */); +#else int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); +#endif if (RT_SUCCESS(rc)) return TrustedMain(argc, argv); return RTMsgInitFailure(rc); diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp index f0b2108d9c0e..cbc16abd9ee9 100644 --- a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp +++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetDhcpdHardened.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxNetDhcpdHardened.cpp 114885 2026-08-07 08:19:17Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetDhcpd - Hardened main(). */ @@ -30,6 +30,10 @@ int main(int argc, char **argv, char **envp) { +#ifdef VBOX_WITH_DRIVERLESS_NEM_FALLBACK + return SUPR3HardenedMain("VBoxNetDHCP", SUPSECMAIN_FLAGS_DRIVERLESS_NEM_FALLBACK, argc, argv, envp); +#else return SUPR3HardenedMain("VBoxNetDHCP", 0 /* fFlags */, argc, argv, envp); +#endif } From cf0bbde8e634e265a029db6371966281cfd92746 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 08:28:10 +0000 Subject: [PATCH 032/176] =?UTF-8?q?NetworkServices/Dhcpd:=20Added=20R3=20D?= =?UTF-8?q?hcpd=20testcase.=20=E2=80=8B=E2=80=8Bbugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174725 --- src/VBox/NetworkServices/Dhcpd/Makefile.kmk | 8 +- .../NetworkServices/Dhcpd/VBoxNetDhcpd.cpp | 203 +++++++++++++++++- 2 files changed, 200 insertions(+), 11 deletions(-) diff --git a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk index 99ac8258d934..9fd2d3dd6d26 100644 --- a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk +++ b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114885 2026-08-07 08:19:17Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114886 2026-08-07 08:28:10Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for the DHCP server. # @@ -28,6 +28,11 @@ SUB_DEPTH := ../../../.. include $(KBUILD_PATH)/subheader.kmk +# Include testcases. +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) + include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk +endif + # # Hardended stub executable. # @@ -97,3 +102,4 @@ else endif include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp index ff8c78ef0fba..88474dedc1db 100644 --- a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp +++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetDhcpd.cpp 114885 2026-08-07 08:19:17Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxNetDhcpd.cpp 114886 2026-08-07 08:28:10Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetDhcpd - DHCP server for host-only and NAT networks. */ @@ -48,6 +48,10 @@ #endif #include "DhcpdInternal.h" +#ifdef VBOXNETDHCPD_INPROC_TESTING +# include +# include +#endif #include #include @@ -107,6 +111,14 @@ class VBoxNetDhcpd int main(int argc, char **argv); +#ifdef VBOXNETDHCPD_INPROC_TESTING + void testSetStartupEvent(RTSEMEVENT hEvtStartup); + int testStop(); + int testQueryStartupStatus(); + bool testIsRunning(); + Config *testTakeConfig(); +#endif + private: /** The logger instance. */ PRTLOGGER m_pStderrReleaseLogger; @@ -121,6 +133,23 @@ class VBoxNetDhcpd /** DHCP server instance. */ DHCPD m_server; +#ifdef VBOXNETDHCPD_INPROC_TESTING + /** Event signalled after in-process startup succeeds or fails. */ + RTSEMEVENT m_hTestStartup; + /** In-process startup status. */ + int32_t volatile m_rcTestStartup; + /** Whether the in-process daemon is pumping IntNet packets. */ + bool volatile m_fTestRunning; + /** Whether the lwIP core was initialized by the in-process daemon. */ + bool m_fTestLwipInitialized; + /** Whether the in-process daemon added its lwIP network interface. */ + bool m_fTestNetIfAdded; + + void testSignalStartup(int rc); + static DECLCALLBACK(void) testLwipFiniCB(void *pvUser); + void testLwipFini(); +#endif + int logInitStderr(); /* @@ -157,6 +186,13 @@ VBoxNetDhcpd::VBoxNetDhcpd() m_LwipNetif(), m_Config(NULL), m_Dhcp4Pcb(NULL) +#ifdef VBOXNETDHCPD_INPROC_TESTING + , m_hTestStartup(NIL_RTSEMEVENT), + m_rcTestStartup(VERR_WRONG_ORDER), + m_fTestRunning(false), + m_fTestLwipInitialized(false), + m_fTestNetIfAdded(false) +#endif { logInitStderr(); } @@ -173,6 +209,115 @@ VBoxNetDhcpd::~VBoxNetDhcpd() } +#ifdef VBOXNETDHCPD_INPROC_TESTING +/** + * Configures the event used to report in-process daemon startup. + * + * @param hEvtStartup Event to signal after startup succeeds or fails. + */ +void VBoxNetDhcpd::testSetStartupEvent(RTSEMEVENT hEvtStartup) +{ + m_hTestStartup = hEvtStartup; +} + + +/** + * Interrupts the in-process daemon's IntNet receive wait. + * + * @returns VBox status code. + */ +int VBoxNetDhcpd::testStop() +{ + if (m_hIf != NULL) + return IntNetR3IfWaitAbort(m_hIf); + return VINF_SUCCESS; +} + + +/** + * Queries the in-process daemon startup result. + * + * @returns VBox status code reported by startup. + */ +int VBoxNetDhcpd::testQueryStartupStatus() +{ + return ASMAtomicReadS32(&m_rcTestStartup); +} + + +/** + * Checks whether the in-process daemon is pumping IntNet packets. + * + * @returns true if the daemon is running, false otherwise. + */ +bool VBoxNetDhcpd::testIsRunning() +{ + return ASMAtomicReadBool(&m_fTestRunning); +} + + +/** + * Detaches the configuration so the testcase can delete it after this object. + * + * @returns Detached configuration, or NULL if no configuration was created. + */ +Config *VBoxNetDhcpd::testTakeConfig() +{ + Config *pConfig = m_Config; + m_Config = NULL; + return pConfig; +} + + +/** + * Reports completion of in-process daemon startup. + * + * @param rc Startup status. + */ +void VBoxNetDhcpd::testSignalStartup(int rc) +{ + ASMAtomicWriteS32(&m_rcTestStartup, rc); + ASMAtomicWriteBool(&m_fTestRunning, RT_SUCCESS(rc)); + if (m_hTestStartup != NIL_RTSEMEVENT) + RTSemEventSignal(m_hTestStartup); +} + + +/** + * Removes the in-process daemon's lwIP objects on the lwIP thread. + * + * @param pvUser VBoxNetDhcpd instance. + */ +/* static */ DECLCALLBACK(void) VBoxNetDhcpd::testLwipFiniCB(void *pvUser) +{ + VBoxNetDhcpd *pThis = static_cast(pvUser); + AssertPtrReturnVoid(pThis); + pThis->testLwipFini(); +} + + +/** + * Removes the in-process daemon's lwIP protocol and interface state. + */ +void VBoxNetDhcpd::testLwipFini() +{ + if (m_Dhcp4Pcb != NULL) + { + udp_remove(m_Dhcp4Pcb); + m_Dhcp4Pcb = NULL; + } + + if (m_fTestNetIfAdded) + { + netif_set_link_down(&m_LwipNetif); + netif_set_down(&m_LwipNetif); + netif_remove(&m_LwipNetif); + m_fTestNetIfAdded = false; + } +} +#endif /* VBOXNETDHCPD_INPROC_TESTING */ + + /* * We don't know the name of the release log file until we parse our * configuration because we use network name as basename. To get @@ -342,6 +487,10 @@ err_t VBoxNetDhcpd::netifLinkOutput(pbuf *pPBuf) int VBoxNetDhcpd::main(int argc, char **argv) { +#ifdef VBOXNETDHCPD_INPROC_TESTING + bool fTestStartupSignalled = false; +#endif + /* * Register string format types. */ @@ -353,7 +502,12 @@ int VBoxNetDhcpd::main(int argc, char **argv) */ m_Config = Config::create(argc, argv); if (m_Config == NULL) + { +#ifdef VBOXNETDHCPD_INPROC_TESTING + testSignalStartup(VERR_GENERAL_FAILURE); +#endif return VERR_GENERAL_FAILURE; + } /* * Initialize the server. @@ -369,11 +523,24 @@ int VBoxNetDhcpd::main(int argc, char **argv) rc = vboxLwipCoreInitialize(lwipInitCB, this); if (RT_SUCCESS(rc)) { +#ifdef VBOXNETDHCPD_INPROC_TESTING + m_fTestLwipInitialized = true; + if ( !m_fTestNetIfAdded + || m_Dhcp4Pcb == NULL) + rc = VERR_NET_INIT_FAILED; + else + { + testSignalStartup(VINF_SUCCESS); + fTestStartupSignalled = true; + } +#endif + /* * Pump packets more or less for ever. */ - rc = IntNetR3IfPumpPkts(m_hIf, ifInput, this, - NULL /*pfnInputGso*/, NULL /*pvUserGso*/); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfPumpPkts(m_hIf, ifInput, this, + NULL /*pfnInputGso*/, NULL /*pvUserGso*/); } else DHCP_LOG_MSG_ERROR(("Terminating - vboxLwipCoreInitialize failed: %Rrc\n", rc)); @@ -383,6 +550,17 @@ int VBoxNetDhcpd::main(int argc, char **argv) } else DHCP_LOG_MSG_ERROR(("Terminating - Dhcpd::init failed: %Rrc\n", rc)); + +#ifdef VBOXNETDHCPD_INPROC_TESTING + ASMAtomicWriteBool(&m_fTestRunning, false); + if (!fTestStartupSignalled) + testSignalStartup(rc); + if (m_fTestLwipInitialized) + { + vboxLwipCoreFinalize(testLwipFiniCB, this); + m_fTestLwipInitialized = false; + } +#endif return rc; } @@ -406,6 +584,9 @@ void VBoxNetDhcpd::lwipInit() if (pNetif == NULL) return; +#ifdef VBOXNETDHCPD_INPROC_TESTING + m_fTestNetIfAdded = true; +#endif netif_set_up(pNetif); netif_set_link_up(pNetif); @@ -503,6 +684,7 @@ void VBoxNetDhcpd::dhcp4Recv(struct udp_pcb *pcb, struct pbuf *p, /* * Entry point. */ +#ifndef VBOXNETDHCPD_INPROC_TESTING extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv) { VBoxNetDhcpd Dhcpd; @@ -511,22 +693,22 @@ extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv) } -#ifndef VBOX_WITH_HARDENING +# ifndef VBOX_WITH_HARDENING int main(int argc, char **argv) { -#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 +# ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 int rc = RTR3InitExe(argc, &argv, 0 /* fFlags */); -#else +# else int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); -#endif +# endif if (RT_SUCCESS(rc)) return TrustedMain(argc, argv); return RTMsgInitFailure(rc); } -# ifdef RT_OS_WINDOWS +# ifdef RT_OS_WINDOWS /** (We don't want a console usually.) */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { @@ -534,6 +716,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine return main(__argc, __argv); } -# endif /* RT_OS_WINDOWS */ +# endif /* RT_OS_WINDOWS */ -#endif /* !VBOX_WITH_HARDENING */ +# endif /* !VBOX_WITH_HARDENING */ +#endif /* !VBOXNETDHCPD_INPROC_TESTING */ From 6def3f3d4be8588fd914cdcff971e5c76972dd8b Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 08:34:21 +0000 Subject: [PATCH 033/176] =?UTF-8?q?NetworkServices/Dhcpd:=20Added=20R3=20D?= =?UTF-8?q?hcpd=20testcase.=20=E2=80=8B=E2=80=8B=E2=80=8Bbugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174726 --- src/VBox/NetworkServices/Dhcpd/testcase/.gitignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/VBox/NetworkServices/Dhcpd/testcase/.gitignore diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/.gitignore b/src/VBox/NetworkServices/Dhcpd/testcase/.gitignore new file mode 100644 index 000000000000..e69de29bb2d1 From e55d66f910c214cb519eabb9c73c44eb28ad9e24 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 08:35:43 +0000 Subject: [PATCH 034/176] VBox/sup.h: Comment fixes. bugref:11149 svn:sync-xref-src-repo-rev: r174727 --- include/VBox/sup.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/VBox/sup.h b/include/VBox/sup.h index a8e70447dcac..15fe0866f183 100644 --- a/include/VBox/sup.h +++ b/include/VBox/sup.h @@ -1356,10 +1356,9 @@ DECLHIDDEN(int) SUPR3HardenedMain(const char *pszProgName, uint32_t fFlags, int * the VBox support driver is unavailable. */ #define SUPSECMAIN_FLAGS_DRIVERLESS_IEM_ALLOWED RT_BIT_32(9) #ifdef VBOX_WITH_DRIVERLESS_NEM_FALLBACK -/** Driverless NEM is a fallback posibility, so don't fail fatally just +/** Driverless NEM is a fallback possibility, so don't fail fatally just * because the VBox support driver is unavailable. - * This may imply checking NEM requirements, depending on the host. - * @note Not supported on Windows. */ + * This may imply checking NEM requirements, depending on the host. */ # define SUPSECMAIN_FLAGS_DRIVERLESS_NEM_FALLBACK RT_BIT_32(10) #endif @@ -3012,4 +3011,3 @@ extern const unsigned g_cbSUPBuildCert; RT_C_DECLS_END #endif /* !VBOX_INCLUDED_sup_h */ - From c63e182d23c98cc060304620668c19ca1a0fe22d Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 08:43:33 +0000 Subject: [PATCH 035/176] Config.kmk: Added VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC for Linux and Windows (disabled by default for now). bugref:11149 svn:sync-xref-src-repo-rev: r174728 --- Config.kmk | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Config.kmk b/Config.kmk index a6399094b20f..8f7720947270 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114863 2026-08-06 10:19:52Z andreas.loeffler@oracle.com $ +# $Id: Config.kmk 114889 2026-08-07 08:43:33Z andreas.loeffler@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -575,6 +575,13 @@ if1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), darwin.amd64 darwin.arm64) VBOX_WITH_INTNET_SERVICE_IN_R3 = 1 endif endif +# The local IPC implementation of the R3 IntNet service is +# experimental and intentionally disabled by default for now, see @bugref{11149} +if1of ($(KBUILD_TARGET), win linux) + ifdef VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC + VBOX_WITH_INTNET_SERVICE_IN_R3 := 1 + endif +endif # Enables the new breakpoint handling code, see @bugref{8650} VBOX_WITH_DBGF_FLOW_TRACING = 1 # Enables ARMv8 API support and if possible virtualization, see @bugref{10383} @@ -9591,7 +9598,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114863 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114889 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9605,7 +9612,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114863 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114889 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif From 1bc3142681023379627a8c35c150ea64fdc60050 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 7 Aug 2026 09:54:48 +0000 Subject: [PATCH 036/176] Shared Clipboard/X11: Addendum for r174704: Moved shClSvcTransferDestroyAll() to fix dynamic linking errors. Also fixed rc nit in ShClSvcTransferInit(). svn:sync-xref-src-repo-rev: r174729 --- .../SharedClipboard/clipboard-transfers.cpp | 26 +++++++++++++++++-- .../VBoxSharedClipboardSvc-transfers.cpp | 25 +----------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index 6fdf50b026ac..1cb4171d22aa 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 114890 2026-08-07 09:54:48Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -5163,6 +5163,28 @@ void ShClSvcTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) LogFlowFuncLeave(); } + +/** + * Destroys all transfers of a Shared Clipboard client. + * + * @param pClient Client to destroy transfers for. + */ +void shClSvcTransferDestroyAll(PSHCLCLIENT pClient) +{ + if (!pClient) + return; + + LogFlowFuncEnter(); + + /* Unregister and destroy all transfers. + * Also make sure to let the backend know that all transfers are getting destroyed. + * + * Note: The index always will be 0, as the transfer gets unregistered. */ + PSHCLTRANSFER pTransfer; + while ((pTransfer = ShClTransferCtxGetTransferByIndex(&pClient->Transfers.Ctx, 0 /* Index */))) + ShClSvcTransferDestroy(pClient, pTransfer); +} + #endif /* VBOX_WITH_SHARED_CLIPBOARD_HOST */ /** @@ -5263,7 +5285,7 @@ int ShClSvcTransferInit(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) ? SHCLTRANSFERSTATUS_INITIALIZED : SHCLTRANSFERSTATUS_ERROR, rc, NULL /* ppEvent */); if (RT_SUCCESS(rc)) - rc2 = rc; + rc = rc2; if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Initializing transfer failed with %Rrc\n", rc)); diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp index 701915e1299f..9f717f721aa1 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp 114890 2026-08-07 09:54:48Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal code for transfer (list) handling. */ @@ -180,29 +180,6 @@ static int shClSvcTransferAbortByHostKey(uint64_t uContextId, SHCLTRANSFERGEN uG } -/** - * Destroys all transfers of a Shared Clipboard client. - * - * @param pClient Client to destroy transfers for. - */ -void shClSvcTransferDestroyAll(PSHCLCLIENT pClient) -{ - if (!pClient) - return; - - LogFlowFuncEnter(); - - /* Unregister and destroy all transfers. - * Also make sure to let the backend know that all transfers are getting destroyed. - * - * Note: The index always will be 0, as the transfer gets unregistered. */ - PSHCLTRANSFER pTransfer; - while ((pTransfer = ShClTransferCtxGetTransferByIndex(&pClient->Transfers.Ctx, 0 /* Index */))) - ShClSvcTransferDestroy(pClient, pTransfer); -} - - - /********************************************************************************************************************************* * HGCM getters / setters * *********************************************************************************************************************************/ From c74186f6e702daaa48f93ba78615cda303b1ddb7 Mon Sep 17 00:00:00 2001 From: Aleksey Ilyushin Date: Fri, 7 Aug 2026 11:59:56 +0000 Subject: [PATCH 037/176] libssh-0.12.2: Applied and adjusted our changes and switched to 0.12.2 bugref:11148 svn:sync-xref-src-repo-rev: r174731 --- Config.kmk | 8 +- .../Network/testcase/tstVBoxLibssh.cpp | 3 +- src/VBox/Main/src-all/VBoxLibSsh.def | 7 +- src/VBox/Main/src-client/CloudGateway.cpp | 13 +- .../Runtime/r3/win/VBoxRT-openssl-3.0.def | 4 +- src/libs/Makefile.kmk | 4 +- src/libs/libssh-0.12.2/.arcconfig | 4 + src/libs/libssh-0.12.2/.clang-format | 29 + src/libs/libssh-0.12.2/.clang-format-ignore | 1 + src/libs/libssh-0.12.2/.cmake-format.yaml | 6 + src/libs/libssh-0.12.2/.editorconfig | 23 + src/libs/libssh-0.12.2/AUTHORS | 15 + src/libs/libssh-0.12.2/BSD | 24 + src/libs/libssh-0.12.2/CHANGELOG | 826 ++ src/libs/libssh-0.12.2/CMakeLists.txt | 284 + src/libs/libssh-0.12.2/CONTRIBUTING.md | 600 ++ src/libs/libssh-0.12.2/COPYING | 469 + src/libs/libssh-0.12.2/CPackConfig.cmake | 44 + src/libs/libssh-0.12.2/CTestConfig.cmake | 9 + src/libs/libssh-0.12.2/CompilerChecks.cmake | 133 + src/libs/libssh-0.12.2/ConfigureChecks.cmake | 487 + src/libs/libssh-0.12.2/DefineOptions.cmake | 109 + src/libs/libssh-0.12.2/INSTALL | 125 + src/libs/libssh-0.12.2/Makefile.kmk | 130 + src/libs/libssh-0.12.2/README | 44 + src/libs/libssh-0.12.2/README.mbedtls | 11 + src/libs/libssh-0.12.2/README.md | 45 + .../cmake/Modules/AddCCompilerFlag.cmake | 21 + .../cmake/Modules/AddCMockaTest.cmake | 125 + .../cmake/Modules/COPYING-CMAKE-SCRIPTS | 22 + .../cmake/Modules/CheckCCompilerFlagSSP.cmake | 29 + .../cmake/Modules/CodeCoverage.cmake | 750 ++ .../cmake/Modules/DefineCMakeDefaults.cmake | 21 + .../cmake/Modules/DefineCompilerFlags.cmake | 49 + .../Modules/DefinePlatformDefaults.cmake | 32 + .../cmake/Modules/ExtractSymbols.cmake | 105 + .../cmake/Modules/FindABIMap.cmake | 491 + .../cmake/Modules/FindArgp.cmake | 70 + .../cmake/Modules/FindCMocka.cmake | 66 + .../cmake/Modules/FindGCrypt.cmake | 118 + .../cmake/Modules/FindGSSAPI.cmake | 344 + .../cmake/Modules/FindMbedTLS.cmake | 143 + .../cmake/Modules/FindNSIS.cmake | 54 + .../cmake/Modules/FindNaCl.cmake | 61 + .../cmake/Modules/Findlibfido2.cmake | 63 + .../cmake/Modules/Findsofthsm.cmake | 36 + .../cmake/Modules/GenerateMap.cmake | 118 + .../cmake/Modules/GetFilesList.cmake | 59 + .../Modules/MacroEnsureOutOfSourceBuild.cmake | 17 + .../cmake/Toolchain-cross-m32.cmake | 23 + src/libs/libssh-0.12.2/config.h.cmake | 304 + src/libs/libssh-0.12.2/doc/CMakeLists.txt | 247 + src/libs/libssh-0.12.2/doc/DoxygenLayout.xml | 242 + .../doc/README.gitlab.freebsd.md | 101 + src/libs/libssh-0.12.2/doc/authentication.dox | 378 + src/libs/libssh-0.12.2/doc/command.dox | 100 + .../doc/curve25519-sha256@libssh.org.txt | 119 + src/libs/libssh-0.12.2/doc/doc_coverage.sh | 52 + src/libs/libssh-0.12.2/doc/doxygen-custom.css | 127 + src/libs/libssh-0.12.2/doc/favicon.png | Bin 0 -> 858 bytes .../doc/fetch_doxygen_awesome.cmake | 41 + src/libs/libssh-0.12.2/doc/fido2.dox | 601 ++ src/libs/libssh-0.12.2/doc/forwarding.dox | 236 + src/libs/libssh-0.12.2/doc/guided_tour.dox | 490 + src/libs/libssh-0.12.2/doc/header.html | 92 + src/libs/libssh-0.12.2/doc/introduction.dox | 55 + src/libs/libssh-0.12.2/doc/linking.dox | 33 + src/libs/libssh-0.12.2/doc/mainpage.dox | 254 + src/libs/libssh-0.12.2/doc/pkcs11.dox | 86 + src/libs/libssh-0.12.2/doc/scp.dox | 268 + src/libs/libssh-0.12.2/doc/sftp.dox | 381 + src/libs/libssh-0.12.2/doc/sftp_aio.dox | 705 ++ src/libs/libssh-0.12.2/doc/shell.dox | 391 + src/libs/libssh-0.12.2/doc/tbd.dox | 14 + src/libs/libssh-0.12.2/doc/threading.dox | 52 + .../libssh-0.12.2/examples/CMakeLists.txt | 105 + .../libssh-0.12.2/examples/authentication.c | 248 + src/libs/libssh-0.12.2/examples/connect_ssh.c | 77 + .../libssh-0.12.2/examples/examples_common.h | 26 + src/libs/libssh-0.12.2/examples/exec.c | 81 + src/libs/libssh-0.12.2/examples/keygen.c | 41 + src/libs/libssh-0.12.2/examples/keygen2.c | 526 + src/libs/libssh-0.12.2/examples/knownhosts.c | 129 + src/libs/libssh-0.12.2/examples/libssh_scp.c | 466 + src/libs/libssh-0.12.2/examples/libsshpp.cpp | 33 + .../examples/libsshpp_noexcept.cpp | 41 + src/libs/libssh-0.12.2/examples/proxy.c | 339 + .../examples/sample_sftpserver.c | 514 + src/libs/libssh-0.12.2/examples/samplesftp.c | 304 + .../libssh-0.12.2/examples/samplesshd-cb.c | 342 + .../examples/samplesshd-kbdint.c | 414 + .../libssh-0.12.2/examples/scp_download.c | 194 + src/libs/libssh-0.12.2/examples/senddata.c | 64 + .../libssh-0.12.2/examples/ssh_X11_client.c | 951 ++ src/libs/libssh-0.12.2/examples/ssh_client.c | 450 + src/libs/libssh-0.12.2/examples/ssh_server.c | 1015 ++ .../examples/sshd_direct-tcpip.c | 746 ++ src/libs/libssh-0.12.2/examples/sshnetcat.c | 287 + src/libs/libssh-0.12.2/include/CMakeLists.txt | 3 + .../include/libssh/CMakeLists.txt | 53 + src/libs/libssh-0.12.2/include/libssh/agent.h | 126 + src/libs/libssh-0.12.2/include/libssh/auth.h | 113 + .../libssh-0.12.2/include/libssh/bignum.h | 41 + src/libs/libssh-0.12.2/include/libssh/bind.h | 68 + .../include/libssh/bind_config.h | 83 + src/libs/libssh-0.12.2/include/libssh/blf.h | 93 + .../libssh-0.12.2/include/libssh/buffer.h | 83 + .../libssh-0.12.2/include/libssh/bytearray.h | 90 + .../libssh-0.12.2/include/libssh/callbacks.h | 1360 +++ .../libssh-0.12.2/include/libssh/chacha.h | 48 + .../include/libssh/chacha20-poly1305-common.h | 54 + .../libssh-0.12.2/include/libssh/channels.h | 125 + .../libssh-0.12.2/include/libssh/config.h | 80 + .../include/libssh/config_parser.h | 86 + .../libssh-0.12.2/include/libssh/crypto.h | 276 + .../libssh-0.12.2/include/libssh/curve25519.h | 69 + .../libssh-0.12.2/include/libssh/dh-gex.h | 41 + src/libs/libssh-0.12.2/include/libssh/dh.h | 95 + src/libs/libssh-0.12.2/include/libssh/ecdh.h | 66 + .../libssh-0.12.2/include/libssh/ed25519.h | 116 + .../libssh-0.12.2/include/libssh/fe25519.h | 76 + .../libssh-0.12.2/include/libssh/ge25519.h | 51 + .../libssh-0.12.2/include/libssh/gssapi.h | 103 + .../include/libssh/hybrid_mlkem.h | 51 + .../libssh-0.12.2/include/libssh/kex-gss.h | 36 + src/libs/libssh-0.12.2/include/libssh/kex.h | 76 + src/libs/libssh-0.12.2/include/libssh/keys.h | 64 + .../libssh-0.12.2/include/libssh/knownhosts.h | 40 + .../libssh-0.12.2/include/libssh/legacy.h | 128 + .../libssh-0.12.2/include/libssh/libcrypto.h | 137 + .../libssh-0.12.2/include/libssh/libgcrypt.h | 128 + .../include/libssh/libmbedcrypto.h | 150 + .../libssh-0.12.2/include/libssh/libssh.h | 1023 ++ .../include/libssh/libssh_version.h | 41 + .../include/libssh/libssh_version.h.cmake | 41 + .../libssh-0.12.2/include/libssh/libsshpp.hpp | 698 ++ .../libssh-0.12.2/include/libssh/messages.h | 116 + src/libs/libssh-0.12.2/include/libssh/misc.h | 152 + src/libs/libssh-0.12.2/include/libssh/mlkem.h | 73 + .../include/libssh/mlkem_native.h | 127 + .../libssh-0.12.2/include/libssh/options.h | 43 + .../libssh-0.12.2/include/libssh/packet.h | 101 + src/libs/libssh-0.12.2/include/libssh/pcap.h | 53 + src/libs/libssh-0.12.2/include/libssh/pki.h | 218 + .../include/libssh/pki_context.h | 103 + .../libssh-0.12.2/include/libssh/pki_priv.h | 181 + .../libssh-0.12.2/include/libssh/pki_sk.h | 90 + src/libs/libssh-0.12.2/include/libssh/poll.h | 170 + .../libssh-0.12.2/include/libssh/poly1305.h | 27 + src/libs/libssh-0.12.2/include/libssh/priv.h | 520 + .../libssh-0.12.2/include/libssh/sc25519.h | 82 + src/libs/libssh-0.12.2/include/libssh/scp.h | 63 + .../libssh-0.12.2/include/libssh/server.h | 415 + .../libssh-0.12.2/include/libssh/session.h | 319 + src/libs/libssh-0.12.2/include/libssh/sftp.h | 1482 +++ .../libssh-0.12.2/include/libssh/sftp_priv.h | 120 + .../libssh-0.12.2/include/libssh/sftpserver.h | 86 + .../libssh-0.12.2/include/libssh/sk_api.h | 283 + .../libssh-0.12.2/include/libssh/sk_common.h | 213 + .../libssh-0.12.2/include/libssh/sk_usbhid.h | 37 + .../libssh-0.12.2/include/libssh/sntrup761.h | 82 + .../libssh-0.12.2/include/libssh/socket.h | 77 + src/libs/libssh-0.12.2/include/libssh/ssh2.h | 90 + .../libssh-0.12.2/include/libssh/string.h | 49 + .../libssh-0.12.2/include/libssh/threads.h | 71 + src/libs/libssh-0.12.2/include/libssh/token.h | 61 + .../libssh-0.12.2/include/libssh/wrapper.h | 140 + src/libs/libssh-0.12.2/libssh.pc.cmake | 11 + src/libs/libssh-0.12.2/src/ABI/current | 1 + .../src/ABI/libssh-4.10.0.symbols | 445 + .../src/ABI/libssh-4.10.1.symbols | 445 + .../src/ABI/libssh-4.10.2.symbols | 445 + .../src/ABI/libssh-4.10.3.symbols | 445 + .../src/ABI/libssh-4.10.4.symbols | 445 + .../src/ABI/libssh-4.11.0.symbols | 465 + .../src/ABI/libssh-4.11.1.symbols | 465 + .../src/ABI/libssh-4.12.0.symbols | 467 + .../src/ABI/libssh-4.5.0.symbols | 411 + .../src/ABI/libssh-4.5.1.symbols | 0 .../src/ABI/libssh-4.6.0.symbols | 412 + .../src/ABI/libssh-4.7.0.symbols | 415 + .../src/ABI/libssh-4.7.1.symbols | 415 + .../src/ABI/libssh-4.7.2.symbols | 415 + .../src/ABI/libssh-4.7.3.symbols | 415 + .../src/ABI/libssh-4.7.4.symbols | 415 + .../src/ABI/libssh-4.8.0.symbols | 419 + .../src/ABI/libssh-4.8.1.symbols | 421 + .../src/ABI/libssh-4.9.0.symbols | 427 + .../src/ABI/libssh-4.9.1.symbols | 427 + .../src/ABI/libssh-4.9.2.symbols | 427 + .../src/ABI/libssh-4.9.3.symbols | 427 + .../src/ABI/libssh-4.9.4.symbols | 427 + .../src/ABI/libssh-4.9.5.symbols | 427 + .../src/ABI/libssh-4.9.6.symbols | 427 + src/libs/libssh-0.12.2/src/CMakeLists.txt | 492 + src/libs/libssh-0.12.2/src/agent.c | 637 ++ src/libs/libssh-0.12.2/src/auth.c | 2580 +++++ src/libs/libssh-0.12.2/src/base64.c | 314 + src/libs/libssh-0.12.2/src/bignum.c | 107 + src/libs/libssh-0.12.2/src/bind.c | 611 ++ src/libs/libssh-0.12.2/src/bind_config.c | 741 ++ src/libs/libssh-0.12.2/src/buffer.c | 1450 +++ src/libs/libssh-0.12.2/src/callbacks.c | 156 + src/libs/libssh-0.12.2/src/chachapoly.c | 205 + src/libs/libssh-0.12.2/src/channels.c | 4162 ++++++++ src/libs/libssh-0.12.2/src/client.c | 936 ++ src/libs/libssh-0.12.2/src/config.c | 1790 ++++ src/libs/libssh-0.12.2/src/config.h | 334 + src/libs/libssh-0.12.2/src/config_parser.c | 293 + src/libs/libssh-0.12.2/src/connect.c | 472 + src/libs/libssh-0.12.2/src/connector.c | 919 ++ src/libs/libssh-0.12.2/src/crypto_common.c | 36 + src/libs/libssh-0.12.2/src/curve25519.c | 370 + .../libssh-0.12.2/src/curve25519_crypto.c | 164 + .../libssh-0.12.2/src/curve25519_fallback.c | 73 + .../libssh-0.12.2/src/curve25519_gcrypt.c | 205 + .../libssh-0.12.2/src/curve25519_mbedcrypto.c | 189 + src/libs/libssh-0.12.2/src/dh-gex.c | 716 ++ src/libs/libssh-0.12.2/src/dh.c | 832 ++ src/libs/libssh-0.12.2/src/dh_crypto.c | 615 ++ src/libs/libssh-0.12.2/src/dh_key.c | 411 + src/libs/libssh-0.12.2/src/ecdh.c | 130 + src/libs/libssh-0.12.2/src/ecdh_crypto.c | 579 ++ src/libs/libssh-0.12.2/src/ecdh_gcrypt.c | 396 + src/libs/libssh-0.12.2/src/ecdh_mbedcrypto.c | 326 + src/libs/libssh-0.12.2/src/error.c | 154 + .../libssh-0.12.2/src/external/bcrypt_pbkdf.c | 191 + .../libssh-0.12.2/src/external/blowfish.c | 691 ++ src/libs/libssh-0.12.2/src/external/chacha.c | 216 + .../src/external/curve25519_ref.c | 271 + src/libs/libssh-0.12.2/src/external/ed25519.c | 222 + src/libs/libssh-0.12.2/src/external/fe25519.c | 418 + src/libs/libssh-0.12.2/src/external/ge25519.c | 369 + .../src/external/ge25519_base.data | 858 ++ .../src/external/libcrux_mlkem768_sha3.c | 8897 +++++++++++++++++ .../libssh-0.12.2/src/external/poly1305.c | 156 + src/libs/libssh-0.12.2/src/external/sc25519.c | 375 + .../libssh-0.12.2/src/external/sntrup761.c | 1058 ++ src/libs/libssh-0.12.2/src/gcrypt_missing.c | 124 + src/libs/libssh-0.12.2/src/getpass.c | 295 + src/libs/libssh-0.12.2/src/getrandom_crypto.c | 64 + src/libs/libssh-0.12.2/src/getrandom_gcrypt.c | 38 + .../libssh-0.12.2/src/getrandom_mbedcrypto.c | 52 + src/libs/libssh-0.12.2/src/gssapi.c | 1458 +++ src/libs/libssh-0.12.2/src/gzip.c | 302 + src/libs/libssh-0.12.2/src/hybrid_mlkem.c | 910 ++ src/libs/libssh-0.12.2/src/init.c | 295 + src/libs/libssh-0.12.2/src/kdf.c | 238 + src/libs/libssh-0.12.2/src/kex-gss.c | 687 ++ src/libs/libssh-0.12.2/src/kex.c | 2073 ++++ src/libs/libssh-0.12.2/src/known_hosts.c | 589 ++ src/libs/libssh-0.12.2/src/knownhosts.c | 1339 +++ src/libs/libssh-0.12.2/src/legacy.c | 790 ++ src/libs/libssh-0.12.2/src/libcrypto-compat.h | 14 + src/libs/libssh-0.12.2/src/libcrypto.c | 1668 +++ src/libs/libssh-0.12.2/src/libgcrypt.c | 1011 ++ src/libs/libssh-0.12.2/src/libmbedcrypto.c | 1120 +++ src/libs/libssh-0.12.2/src/libssh.map | 516 + src/libs/libssh-0.12.2/src/log.c | 262 + src/libs/libssh-0.12.2/src/match.c | 615 ++ .../libssh-0.12.2/src/mbedcrypto-compat.h | 56 + .../libssh-0.12.2/src/mbedcrypto_missing.c | 180 + src/libs/libssh-0.12.2/src/md_crypto.c | 373 + src/libs/libssh-0.12.2/src/md_gcrypt.c | 252 + src/libs/libssh-0.12.2/src/md_mbedcrypto.c | 455 + src/libs/libssh-0.12.2/src/messages.c | 1980 ++++ src/libs/libssh-0.12.2/src/misc.c | 2506 +++++ src/libs/libssh-0.12.2/src/mlkem.c | 40 + src/libs/libssh-0.12.2/src/mlkem_crypto.c | 279 + src/libs/libssh-0.12.2/src/mlkem_gcrypt.c | 207 + src/libs/libssh-0.12.2/src/mlkem_native.c | 202 + src/libs/libssh-0.12.2/src/options.c | 3120 ++++++ src/libs/libssh-0.12.2/src/packet.c | 2285 +++++ src/libs/libssh-0.12.2/src/packet_cb.c | 372 + src/libs/libssh-0.12.2/src/packet_crypt.c | 330 + src/libs/libssh-0.12.2/src/pcap.c | 585 ++ src/libs/libssh-0.12.2/src/pki.c | 4011 ++++++++ .../libssh-0.12.2/src/pki_container_openssh.c | 696 ++ src/libs/libssh-0.12.2/src/pki_context.c | 581 ++ src/libs/libssh-0.12.2/src/pki_crypto.c | 3038 ++++++ src/libs/libssh-0.12.2/src/pki_ed25519.c | 349 + .../libssh-0.12.2/src/pki_ed25519_common.c | 109 + src/libs/libssh-0.12.2/src/pki_gcrypt.c | 2381 +++++ src/libs/libssh-0.12.2/src/pki_mbedcrypto.c | 2032 ++++ src/libs/libssh-0.12.2/src/pki_sk.c | 971 ++ src/libs/libssh-0.12.2/src/poll.c | 1216 +++ src/libs/libssh-0.12.2/src/scp.c | 1222 +++ src/libs/libssh-0.12.2/src/server.c | 1656 +++ src/libs/libssh-0.12.2/src/session.c | 1397 +++ src/libs/libssh-0.12.2/src/sftp.c | 3631 +++++++ src/libs/libssh-0.12.2/src/sftp_aio.c | 501 + src/libs/libssh-0.12.2/src/sftp_common.c | 1082 ++ src/libs/libssh-0.12.2/src/sftpserver.c | 2173 ++++ src/libs/libssh-0.12.2/src/sk_common.c | 290 + src/libs/libssh-0.12.2/src/sk_usbhid.c | 2239 +++++ src/libs/libssh-0.12.2/src/sntrup761.c | 524 + src/libs/libssh-0.12.2/src/socket.c | 1764 ++++ src/libs/libssh-0.12.2/src/string.c | 375 + src/libs/libssh-0.12.2/src/threads.c | 97 + .../libssh-0.12.2/src/threads/libcrypto.c | 36 + .../libssh-0.12.2/src/threads/libgcrypt.c | 74 + src/libs/libssh-0.12.2/src/threads/mbedtls.c | 71 + src/libs/libssh-0.12.2/src/threads/noop.c | 74 + src/libs/libssh-0.12.2/src/threads/pthread.c | 140 + src/libs/libssh-0.12.2/src/threads/winlocks.c | 124 + src/libs/libssh-0.12.2/src/token.c | 545 + src/libs/libssh-0.12.2/src/ttyopts.c | 476 + src/libs/libssh-0.12.2/src/wrapper.c | 646 ++ src/libs/libssh-0.12.2/tests/CMakeLists.txt | 473 + .../tests/benchmarks/CMakeLists.txt | 17 + .../libssh-0.12.2/tests/benchmarks/bench1.sh | 14 + .../libssh-0.12.2/tests/benchmarks/bench2.sh | 14 + .../tests/benchmarks/bench_raw.c | 310 + .../tests/benchmarks/bench_scp.c | 150 + .../tests/benchmarks/bench_sftp.c | 636 ++ .../tests/benchmarks/benchmarks.c | 494 + .../tests/benchmarks/benchmarks.h | 105 + .../libssh-0.12.2/tests/benchmarks/latency.c | 148 + src/libs/libssh-0.12.2/tests/chmodtest.c | 33 + src/libs/libssh-0.12.2/tests/chroot_wrapper.c | 8 + .../libssh-0.12.2/tests/client/CMakeLists.txt | 109 + .../tests/client/torture_algorithms.c | 1128 +++ .../libssh-0.12.2/tests/client/torture_auth.c | 1473 +++ .../client/torture_auth_agent_forwarding.c | 369 + .../tests/client/torture_auth_cert.c | 1125 +++ .../tests/client/torture_auth_common.c | 94 + .../tests/client/torture_auth_pkcs11.c | 294 + .../tests/client/torture_client_callbacks.c | 261 + .../tests/client/torture_client_config.c | 487 + .../client/torture_client_global_requests.c | 152 + .../tests/client/torture_connect.c | 401 + .../tests/client/torture_forward.c | 119 + .../tests/client/torture_get_kex_algo.c | 253 + .../tests/client/torture_gssapi_auth.c | 305 + .../client/torture_gssapi_key_exchange.c | 348 + .../client/torture_gssapi_key_exchange_null.c | 181 + .../tests/client/torture_hostkey.c | 214 + .../tests/client/torture_knownhosts.c | 513 + .../tests/client/torture_knownhosts_verify.c | 519 + .../tests/client/torture_proxycommand.c | 265 + .../tests/client/torture_proxyjump.c | 362 + .../tests/client/torture_rekey.c | 1013 ++ .../tests/client/torture_request_env.c | 137 + .../tests/client/torture_request_pty_modes.c | 264 + .../libssh-0.12.2/tests/client/torture_scp.c | 664 ++ .../tests/client/torture_session.c | 551 + .../tests/client/torture_sftp_aio.c | 778 ++ .../tests/client/torture_sftp_benchmark.c | 133 + .../client/torture_sftp_canonicalize_path.c | 97 + .../tests/client/torture_sftp_dir.c | 104 + .../tests/client/torture_sftp_expand_path.c | 125 + .../tests/client/torture_sftp_ext.c | 35 + .../tests/client/torture_sftp_fsync.c | 137 + .../torture_sftp_get_users_groups_by_id.c | 263 + .../tests/client/torture_sftp_hardlink.c | 114 + .../client/torture_sftp_home_directory.c | 142 + .../tests/client/torture_sftp_init.c | 166 + .../tests/client/torture_sftp_limits.c | 177 + .../tests/client/torture_sftp_packet_read.c | 118 + .../tests/client/torture_sftp_read.c | 121 + .../client/torture_sftp_recv_response_msg.c | 197 + .../tests/client/torture_sftp_rename.c | 120 + .../tests/client/torture_sftp_request_id.c | 183 + .../tests/client/torture_sftp_setstat.c | 382 + src/libs/libssh-0.12.2/tests/cmdline.c | 72 + .../libssh-0.12.2/tests/ctest-default.cmake | 72 + src/libs/libssh-0.12.2/tests/etc/group.in | 5 + src/libs/libssh-0.12.2/tests/etc/hosts.in | 12 + src/libs/libssh-0.12.2/tests/etc/openssl.cnf | 11 + .../libssh-0.12.2/tests/etc/pam.d/sshd.in | 4 + .../tests/etc/pam_matrix_passdb.in | 4 + src/libs/libssh-0.12.2/tests/etc/passwd.in | 9 + src/libs/libssh-0.12.2/tests/etc/shadow.in | 4 + .../tests/external_override/CMakeLists.txt | 205 + .../external_override/chacha20_override.c | 80 + .../external_override/chacha20_override.h | 51 + .../external_override/curve25519_override.c | 58 + .../external_override/curve25519_override.h | 31 + .../external_override/ed25519_override.c | 71 + .../external_override/ed25519_override.h | 39 + .../external_override/mlkem768_override.c | 83 + .../external_override/mlkem768_override.h | 43 + .../external_override/poly1305_override.c | 54 + .../external_override/poly1305_override.h | 35 + .../external_override/sntrup761_override.c | 73 + .../external_override/sntrup761_override.h | 40 + .../external_override/torture_override.c | 466 + src/libs/libssh-0.12.2/tests/fs_wrapper.c | 255 + .../libssh-0.12.2/tests/fuzz/CMakeLists.txt | 40 + src/libs/libssh-0.12.2/tests/fuzz/README.md | 146 + src/libs/libssh-0.12.2/tests/fuzz/fuzzer.c | 49 + src/libs/libssh-0.12.2/tests/fuzz/nallocinc.c | 344 + .../tests/fuzz/ssh_bind_config_fuzzer.c | 75 + .../tests/fuzz/ssh_client_config_fuzzer.c | 78 + .../infinite_loop | 5 + .../wrong_username | 7 + .../tests/fuzz/ssh_client_fuzzer.c | 224 + .../0f9d75a6c1d365115772a502d42b6e48f453198a | Bin 0 -> 2055 bytes .../tests/fuzz/ssh_known_hosts_fuzzer.c | 103 + .../d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 | Bin 0 -> 189 bytes .../tests/fuzz/ssh_privkey_fuzzer.c | 72 + .../855ce609b52aec530bf631a78da7038bed99040a | 8 + .../tests/fuzz/ssh_pubkey_fuzzer.c | 87 + .../b2c9f01394a2835b2cd7c520395a4977143e8d23 | 1 + .../tests/fuzz/ssh_server_fuzzer.c | 281 + .../fd7bd24a85e712fb59159a512b69d34ca21c8383 | Bin 0 -> 2055 bytes .../tests/fuzz/ssh_sshsig_fuzzer.c | 72 + .../5645ecda3771cd2737f0aff9b88eb26a36b10964 | 14 + src/libs/libssh-0.12.2/tests/generate.py | 10 + src/libs/libssh-0.12.2/tests/gss/kdcsetup.sh | 53 + .../libssh-0.12.2/tests/keys/certauth/id_rsa | 27 + .../tests/keys/certauth/id_rsa-cert.pub | 1 + .../tests/keys/certauth/id_rsa.pub | 1 + src/libs/libssh-0.12.2/tests/keys/id_ecdsa | 5 + .../libssh-0.12.2/tests/keys/id_ecdsa.pub | 1 + src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk | 14 + .../libssh-0.12.2/tests/keys/id_ecdsa_sk.pub | 1 + src/libs/libssh-0.12.2/tests/keys/id_ed25519 | 8 + .../libssh-0.12.2/tests/keys/id_ed25519.pub | 1 + .../libssh-0.12.2/tests/keys/id_ed25519_sk | 8 + .../tests/keys/id_ed25519_sk.pub | 1 + src/libs/libssh-0.12.2/tests/keys/id_rsa | 27 + src/libs/libssh-0.12.2/tests/keys/id_rsa.pub | 1 + .../libssh-0.12.2/tests/keys/id_rsa_protected | 30 + .../tests/keys/id_rsa_protected.pub | 1 + .../tests/keys/pkcs11/id_pkcs11_ecdsa_256 | 5 + .../tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub | 4 + .../pkcs11/id_pkcs11_ecdsa_256_openssh.pub | 2 + .../tests/keys/pkcs11/id_pkcs11_ecdsa_384 | 6 + .../tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub | 5 + .../pkcs11/id_pkcs11_ecdsa_384_openssh.pub | 2 + .../tests/keys/pkcs11/id_pkcs11_ecdsa_521 | 7 + .../tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub | 6 + .../pkcs11/id_pkcs11_ecdsa_521_openssh.pub | 2 + .../tests/keys/pkcs11/id_pkcs11_ed25519 | 3 + .../tests/keys/pkcs11/id_pkcs11_ed25519.pub | 3 + .../keys/pkcs11/id_pkcs11_ed25519_openssh.pub | 1 + .../tests/keys/pkcs11/id_pkcs11_rsa | 27 + .../tests/keys/pkcs11/id_pkcs11_rsa.pub | 9 + .../keys/pkcs11/id_pkcs11_rsa_openssh.pub | 2 + .../tests/keys/ssh_host_ecdsa_key | 5 + .../tests/keys/ssh_host_ecdsa_key.pub | 1 + .../libssh-0.12.2/tests/keys/ssh_host_key | Bin 0 -> 978 bytes .../libssh-0.12.2/tests/keys/ssh_host_key.pub | 1 + .../libssh-0.12.2/tests/keys/ssh_host_rsa_key | 27 + .../tests/keys/ssh_host_rsa_key.pub | 1 + src/libs/libssh-0.12.2/tests/keys/user_ca | 27 + .../tests/pkcs11/setup-softhsm-tokens.sh | 93 + .../libssh-0.12.2/tests/pkd/CMakeLists.txt | 55 + src/libs/libssh-0.12.2/tests/pkd/pkd_client.h | 120 + src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.c | 596 ++ src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.h | 62 + src/libs/libssh-0.12.2/tests/pkd/pkd_hello.c | 1161 +++ .../libssh-0.12.2/tests/pkd/pkd_keyutil.c | 269 + .../libssh-0.12.2/tests/pkd/pkd_keyutil.h | 67 + src/libs/libssh-0.12.2/tests/pkd/pkd_util.c | 121 + src/libs/libssh-0.12.2/tests/pkd/pkd_util.h | 17 + .../libssh-0.12.2/tests/server/CMakeLists.txt | 62 + .../tests/server/test_server/CMakeLists.txt | 38 + .../tests/server/test_server/default_cb.c | 1105 ++ .../tests/server/test_server/default_cb.h | 180 + .../tests/server/test_server/main.c | 667 ++ .../tests/server/test_server/sftpserver_cb.c | 425 + .../tests/server/test_server/test_server.c | 395 + .../tests/server/test_server/test_server.h | 82 + .../server/test_server/testserver_common.c | 36 + .../server/test_server/testserver_common.h | 26 + .../tests/server/torture_gssapi_server_auth.c | 456 + .../server/torture_gssapi_server_auth_cb.c | 518 + .../server/torture_gssapi_server_delegation.c | 376 + .../torture_gssapi_server_key_exchange.c | 604 ++ ...ture_gssapi_server_key_exchange_fallback.c | 336 + .../torture_gssapi_server_key_exchange_null.c | 346 + .../tests/server/torture_server_algorithms.c | 454 + .../tests/server/torture_server_auth_kbdint.c | 818 ++ .../tests/server/torture_server_config.c | 805 ++ .../tests/server/torture_server_default.c | 657 ++ .../tests/server/torture_sftpserver.c | 1568 +++ src/libs/libssh-0.12.2/tests/ssh_ping.c | 108 + .../tests/suppressions/lsan.supp | 6 + src/libs/libssh-0.12.2/tests/test_socket.c | 93 + .../libssh-0.12.2/tests/tests_config.h.cmake | 88 + src/libs/libssh-0.12.2/tests/torture.c | 2288 +++++ src/libs/libssh-0.12.2/tests/torture.h | 202 + src/libs/libssh-0.12.2/tests/torture_cmocka.c | 102 + src/libs/libssh-0.12.2/tests/torture_cmocka.h | 55 + src/libs/libssh-0.12.2/tests/torture_key.c | 1137 +++ src/libs/libssh-0.12.2/tests/torture_key.h | 44 + src/libs/libssh-0.12.2/tests/torture_pki.c | 97 + src/libs/libssh-0.12.2/tests/torture_pki.h | 3 + src/libs/libssh-0.12.2/tests/torture_sk.c | 395 + src/libs/libssh-0.12.2/tests/torture_sk.h | 167 + .../tests/unittests/CMakeLists.txt | 161 + .../tests/unittests/hello world.sh | 2 + .../tests/unittests/torture_bignum.c | 176 + .../tests/unittests/torture_bind_config.c | 1888 ++++ .../tests/unittests/torture_buffer.c | 404 + .../tests/unittests/torture_bytearray.c | 410 + .../tests/unittests/torture_callbacks.c | 268 + .../tests/unittests/torture_channel.c | 198 + .../tests/unittests/torture_config.c | 3260 ++++++ .../torture_config_match_localnetwork.c | 752 ++ .../tests/unittests/torture_crypto.c | 337 + .../torture_forwarded_tcpip_callback.c | 336 + .../tests/unittests/torture_hashes.c | 161 + .../tests/unittests/torture_init.c | 69 + .../tests/unittests/torture_isipaddr.c | 66 + .../tests/unittests/torture_keyfiles.c | 256 + .../unittests/torture_knownhosts_parsing.c | 828 ++ .../tests/unittests/torture_list.c | 131 + .../tests/unittests/torture_misc.c | 1329 +++ .../tests/unittests/torture_moduli.c | 115 + .../tests/unittests/torture_options.c | 3267 ++++++ .../tests/unittests/torture_packet.c | 395 + .../tests/unittests/torture_packet_filter.c | 631 ++ .../tests/unittests/torture_pki.c | 428 + .../tests/unittests/torture_pki_dsa.c | 220 + .../tests/unittests/torture_pki_ecdsa.c | 1297 +++ .../tests/unittests/torture_pki_ecdsa_uri.c | 584 ++ .../tests/unittests/torture_pki_ed25519.c | 1210 +++ .../tests/unittests/torture_pki_ed25519_uri.c | 357 + .../tests/unittests/torture_pki_rsa.c | 1259 +++ .../tests/unittests/torture_pki_rsa_uri.c | 310 + .../tests/unittests/torture_pki_sk.c | 536 + .../tests/unittests/torture_pki_sk_ecdsa.c | 487 + .../tests/unittests/torture_pki_sk_ed25519.c | 543 + .../tests/unittests/torture_pki_sshsig.c | 831 ++ .../tests/unittests/torture_push_pop_dir.c | 78 + .../tests/unittests/torture_rand.c | 83 + .../unittests/torture_server_direct_tcpip.c | 265 + .../tests/unittests/torture_server_x11.c | 238 + .../tests/unittests/torture_session_keys.c | 107 + .../tests/unittests/torture_sk_usbhid.c | 383 + .../tests/unittests/torture_string.c | 424 + .../tests/unittests/torture_temp_dir.c | 51 + .../tests/unittests/torture_temp_file.c | 63 + .../tests/unittests/torture_threads_buffer.c | 602 ++ .../tests/unittests/torture_threads_crypto.c | 205 + .../tests/unittests/torture_threads_init.c | 98 + .../tests/unittests/torture_threads_pki_rsa.c | 791 ++ .../tests/unittests/torture_tokens.c | 349 + .../tests/unittests/torture_unit_server.c | 195 + .../tests/unittests/torture_unit_sftp.c | 86 + src/libs/libssh-0.12.2/tests/valgrind.supp | 515 + 544 files changed, 204810 insertions(+), 12 deletions(-) create mode 100644 src/libs/libssh-0.12.2/.arcconfig create mode 100644 src/libs/libssh-0.12.2/.clang-format create mode 100644 src/libs/libssh-0.12.2/.clang-format-ignore create mode 100644 src/libs/libssh-0.12.2/.cmake-format.yaml create mode 100644 src/libs/libssh-0.12.2/.editorconfig create mode 100644 src/libs/libssh-0.12.2/AUTHORS create mode 100644 src/libs/libssh-0.12.2/BSD create mode 100644 src/libs/libssh-0.12.2/CHANGELOG create mode 100644 src/libs/libssh-0.12.2/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/CONTRIBUTING.md create mode 100644 src/libs/libssh-0.12.2/COPYING create mode 100644 src/libs/libssh-0.12.2/CPackConfig.cmake create mode 100644 src/libs/libssh-0.12.2/CTestConfig.cmake create mode 100644 src/libs/libssh-0.12.2/CompilerChecks.cmake create mode 100644 src/libs/libssh-0.12.2/ConfigureChecks.cmake create mode 100644 src/libs/libssh-0.12.2/DefineOptions.cmake create mode 100644 src/libs/libssh-0.12.2/INSTALL create mode 100644 src/libs/libssh-0.12.2/Makefile.kmk create mode 100644 src/libs/libssh-0.12.2/README create mode 100644 src/libs/libssh-0.12.2/README.mbedtls create mode 100644 src/libs/libssh-0.12.2/README.md create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/AddCCompilerFlag.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/AddCMockaTest.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/COPYING-CMAKE-SCRIPTS create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/CheckCCompilerFlagSSP.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/CodeCoverage.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/DefineCMakeDefaults.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/DefineCompilerFlags.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/DefinePlatformDefaults.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/ExtractSymbols.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/FindABIMap.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/FindArgp.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/FindCMocka.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/FindGCrypt.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/FindGSSAPI.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/FindMbedTLS.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/FindNSIS.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/FindNaCl.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/Findlibfido2.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/Findsofthsm.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/GenerateMap.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/GetFilesList.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake create mode 100644 src/libs/libssh-0.12.2/cmake/Toolchain-cross-m32.cmake create mode 100644 src/libs/libssh-0.12.2/config.h.cmake create mode 100644 src/libs/libssh-0.12.2/doc/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/doc/DoxygenLayout.xml create mode 100644 src/libs/libssh-0.12.2/doc/README.gitlab.freebsd.md create mode 100644 src/libs/libssh-0.12.2/doc/authentication.dox create mode 100644 src/libs/libssh-0.12.2/doc/command.dox create mode 100644 src/libs/libssh-0.12.2/doc/curve25519-sha256@libssh.org.txt create mode 100644 src/libs/libssh-0.12.2/doc/doc_coverage.sh create mode 100644 src/libs/libssh-0.12.2/doc/doxygen-custom.css create mode 100644 src/libs/libssh-0.12.2/doc/favicon.png create mode 100644 src/libs/libssh-0.12.2/doc/fetch_doxygen_awesome.cmake create mode 100644 src/libs/libssh-0.12.2/doc/fido2.dox create mode 100644 src/libs/libssh-0.12.2/doc/forwarding.dox create mode 100644 src/libs/libssh-0.12.2/doc/guided_tour.dox create mode 100644 src/libs/libssh-0.12.2/doc/header.html create mode 100644 src/libs/libssh-0.12.2/doc/introduction.dox create mode 100644 src/libs/libssh-0.12.2/doc/linking.dox create mode 100644 src/libs/libssh-0.12.2/doc/mainpage.dox create mode 100644 src/libs/libssh-0.12.2/doc/pkcs11.dox create mode 100644 src/libs/libssh-0.12.2/doc/scp.dox create mode 100644 src/libs/libssh-0.12.2/doc/sftp.dox create mode 100644 src/libs/libssh-0.12.2/doc/sftp_aio.dox create mode 100644 src/libs/libssh-0.12.2/doc/shell.dox create mode 100644 src/libs/libssh-0.12.2/doc/tbd.dox create mode 100644 src/libs/libssh-0.12.2/doc/threading.dox create mode 100644 src/libs/libssh-0.12.2/examples/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/examples/authentication.c create mode 100644 src/libs/libssh-0.12.2/examples/connect_ssh.c create mode 100644 src/libs/libssh-0.12.2/examples/examples_common.h create mode 100644 src/libs/libssh-0.12.2/examples/exec.c create mode 100644 src/libs/libssh-0.12.2/examples/keygen.c create mode 100644 src/libs/libssh-0.12.2/examples/keygen2.c create mode 100644 src/libs/libssh-0.12.2/examples/knownhosts.c create mode 100644 src/libs/libssh-0.12.2/examples/libssh_scp.c create mode 100644 src/libs/libssh-0.12.2/examples/libsshpp.cpp create mode 100644 src/libs/libssh-0.12.2/examples/libsshpp_noexcept.cpp create mode 100644 src/libs/libssh-0.12.2/examples/proxy.c create mode 100644 src/libs/libssh-0.12.2/examples/sample_sftpserver.c create mode 100644 src/libs/libssh-0.12.2/examples/samplesftp.c create mode 100644 src/libs/libssh-0.12.2/examples/samplesshd-cb.c create mode 100644 src/libs/libssh-0.12.2/examples/samplesshd-kbdint.c create mode 100644 src/libs/libssh-0.12.2/examples/scp_download.c create mode 100644 src/libs/libssh-0.12.2/examples/senddata.c create mode 100644 src/libs/libssh-0.12.2/examples/ssh_X11_client.c create mode 100644 src/libs/libssh-0.12.2/examples/ssh_client.c create mode 100644 src/libs/libssh-0.12.2/examples/ssh_server.c create mode 100644 src/libs/libssh-0.12.2/examples/sshd_direct-tcpip.c create mode 100644 src/libs/libssh-0.12.2/examples/sshnetcat.c create mode 100644 src/libs/libssh-0.12.2/include/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/include/libssh/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/include/libssh/agent.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/auth.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/bignum.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/bind.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/bind_config.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/blf.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/buffer.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/bytearray.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/callbacks.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/chacha.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/chacha20-poly1305-common.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/channels.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/config.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/config_parser.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/crypto.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/curve25519.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/dh-gex.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/dh.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/ecdh.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/ed25519.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/fe25519.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/ge25519.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/gssapi.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/hybrid_mlkem.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/kex-gss.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/kex.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/keys.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/knownhosts.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/legacy.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/libcrypto.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/libgcrypt.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/libmbedcrypto.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/libssh.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/libssh_version.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/libssh_version.h.cmake create mode 100644 src/libs/libssh-0.12.2/include/libssh/libsshpp.hpp create mode 100644 src/libs/libssh-0.12.2/include/libssh/messages.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/misc.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/mlkem.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/mlkem_native.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/options.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/packet.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/pcap.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/pki.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/pki_context.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/pki_priv.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/pki_sk.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/poll.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/poly1305.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/priv.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/sc25519.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/scp.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/server.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/session.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/sftp.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/sftp_priv.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/sftpserver.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/sk_api.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/sk_common.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/sk_usbhid.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/sntrup761.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/socket.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/ssh2.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/string.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/threads.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/token.h create mode 100644 src/libs/libssh-0.12.2/include/libssh/wrapper.h create mode 100644 src/libs/libssh-0.12.2/libssh.pc.cmake create mode 100644 src/libs/libssh-0.12.2/src/ABI/current create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.10.0.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.10.1.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.10.2.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.10.3.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.10.4.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.11.0.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.11.1.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.12.0.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.5.0.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.5.1.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.6.0.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.7.0.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.7.1.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.7.2.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.7.3.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.7.4.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.8.0.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.8.1.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.9.0.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.9.1.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.9.2.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.9.3.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.9.4.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.9.5.symbols create mode 100644 src/libs/libssh-0.12.2/src/ABI/libssh-4.9.6.symbols create mode 100644 src/libs/libssh-0.12.2/src/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/src/agent.c create mode 100644 src/libs/libssh-0.12.2/src/auth.c create mode 100644 src/libs/libssh-0.12.2/src/base64.c create mode 100644 src/libs/libssh-0.12.2/src/bignum.c create mode 100644 src/libs/libssh-0.12.2/src/bind.c create mode 100644 src/libs/libssh-0.12.2/src/bind_config.c create mode 100644 src/libs/libssh-0.12.2/src/buffer.c create mode 100644 src/libs/libssh-0.12.2/src/callbacks.c create mode 100644 src/libs/libssh-0.12.2/src/chachapoly.c create mode 100644 src/libs/libssh-0.12.2/src/channels.c create mode 100644 src/libs/libssh-0.12.2/src/client.c create mode 100644 src/libs/libssh-0.12.2/src/config.c create mode 100644 src/libs/libssh-0.12.2/src/config.h create mode 100644 src/libs/libssh-0.12.2/src/config_parser.c create mode 100644 src/libs/libssh-0.12.2/src/connect.c create mode 100644 src/libs/libssh-0.12.2/src/connector.c create mode 100644 src/libs/libssh-0.12.2/src/crypto_common.c create mode 100644 src/libs/libssh-0.12.2/src/curve25519.c create mode 100644 src/libs/libssh-0.12.2/src/curve25519_crypto.c create mode 100644 src/libs/libssh-0.12.2/src/curve25519_fallback.c create mode 100644 src/libs/libssh-0.12.2/src/curve25519_gcrypt.c create mode 100644 src/libs/libssh-0.12.2/src/curve25519_mbedcrypto.c create mode 100644 src/libs/libssh-0.12.2/src/dh-gex.c create mode 100644 src/libs/libssh-0.12.2/src/dh.c create mode 100644 src/libs/libssh-0.12.2/src/dh_crypto.c create mode 100644 src/libs/libssh-0.12.2/src/dh_key.c create mode 100644 src/libs/libssh-0.12.2/src/ecdh.c create mode 100644 src/libs/libssh-0.12.2/src/ecdh_crypto.c create mode 100644 src/libs/libssh-0.12.2/src/ecdh_gcrypt.c create mode 100644 src/libs/libssh-0.12.2/src/ecdh_mbedcrypto.c create mode 100644 src/libs/libssh-0.12.2/src/error.c create mode 100644 src/libs/libssh-0.12.2/src/external/bcrypt_pbkdf.c create mode 100644 src/libs/libssh-0.12.2/src/external/blowfish.c create mode 100644 src/libs/libssh-0.12.2/src/external/chacha.c create mode 100644 src/libs/libssh-0.12.2/src/external/curve25519_ref.c create mode 100644 src/libs/libssh-0.12.2/src/external/ed25519.c create mode 100644 src/libs/libssh-0.12.2/src/external/fe25519.c create mode 100644 src/libs/libssh-0.12.2/src/external/ge25519.c create mode 100644 src/libs/libssh-0.12.2/src/external/ge25519_base.data create mode 100644 src/libs/libssh-0.12.2/src/external/libcrux_mlkem768_sha3.c create mode 100644 src/libs/libssh-0.12.2/src/external/poly1305.c create mode 100644 src/libs/libssh-0.12.2/src/external/sc25519.c create mode 100644 src/libs/libssh-0.12.2/src/external/sntrup761.c create mode 100644 src/libs/libssh-0.12.2/src/gcrypt_missing.c create mode 100644 src/libs/libssh-0.12.2/src/getpass.c create mode 100644 src/libs/libssh-0.12.2/src/getrandom_crypto.c create mode 100644 src/libs/libssh-0.12.2/src/getrandom_gcrypt.c create mode 100644 src/libs/libssh-0.12.2/src/getrandom_mbedcrypto.c create mode 100644 src/libs/libssh-0.12.2/src/gssapi.c create mode 100644 src/libs/libssh-0.12.2/src/gzip.c create mode 100644 src/libs/libssh-0.12.2/src/hybrid_mlkem.c create mode 100644 src/libs/libssh-0.12.2/src/init.c create mode 100644 src/libs/libssh-0.12.2/src/kdf.c create mode 100644 src/libs/libssh-0.12.2/src/kex-gss.c create mode 100644 src/libs/libssh-0.12.2/src/kex.c create mode 100644 src/libs/libssh-0.12.2/src/known_hosts.c create mode 100644 src/libs/libssh-0.12.2/src/knownhosts.c create mode 100644 src/libs/libssh-0.12.2/src/legacy.c create mode 100644 src/libs/libssh-0.12.2/src/libcrypto-compat.h create mode 100644 src/libs/libssh-0.12.2/src/libcrypto.c create mode 100644 src/libs/libssh-0.12.2/src/libgcrypt.c create mode 100644 src/libs/libssh-0.12.2/src/libmbedcrypto.c create mode 100644 src/libs/libssh-0.12.2/src/libssh.map create mode 100644 src/libs/libssh-0.12.2/src/log.c create mode 100644 src/libs/libssh-0.12.2/src/match.c create mode 100644 src/libs/libssh-0.12.2/src/mbedcrypto-compat.h create mode 100644 src/libs/libssh-0.12.2/src/mbedcrypto_missing.c create mode 100644 src/libs/libssh-0.12.2/src/md_crypto.c create mode 100644 src/libs/libssh-0.12.2/src/md_gcrypt.c create mode 100644 src/libs/libssh-0.12.2/src/md_mbedcrypto.c create mode 100644 src/libs/libssh-0.12.2/src/messages.c create mode 100644 src/libs/libssh-0.12.2/src/misc.c create mode 100644 src/libs/libssh-0.12.2/src/mlkem.c create mode 100644 src/libs/libssh-0.12.2/src/mlkem_crypto.c create mode 100644 src/libs/libssh-0.12.2/src/mlkem_gcrypt.c create mode 100644 src/libs/libssh-0.12.2/src/mlkem_native.c create mode 100644 src/libs/libssh-0.12.2/src/options.c create mode 100644 src/libs/libssh-0.12.2/src/packet.c create mode 100644 src/libs/libssh-0.12.2/src/packet_cb.c create mode 100644 src/libs/libssh-0.12.2/src/packet_crypt.c create mode 100644 src/libs/libssh-0.12.2/src/pcap.c create mode 100644 src/libs/libssh-0.12.2/src/pki.c create mode 100644 src/libs/libssh-0.12.2/src/pki_container_openssh.c create mode 100644 src/libs/libssh-0.12.2/src/pki_context.c create mode 100644 src/libs/libssh-0.12.2/src/pki_crypto.c create mode 100644 src/libs/libssh-0.12.2/src/pki_ed25519.c create mode 100644 src/libs/libssh-0.12.2/src/pki_ed25519_common.c create mode 100644 src/libs/libssh-0.12.2/src/pki_gcrypt.c create mode 100644 src/libs/libssh-0.12.2/src/pki_mbedcrypto.c create mode 100644 src/libs/libssh-0.12.2/src/pki_sk.c create mode 100644 src/libs/libssh-0.12.2/src/poll.c create mode 100644 src/libs/libssh-0.12.2/src/scp.c create mode 100644 src/libs/libssh-0.12.2/src/server.c create mode 100644 src/libs/libssh-0.12.2/src/session.c create mode 100644 src/libs/libssh-0.12.2/src/sftp.c create mode 100644 src/libs/libssh-0.12.2/src/sftp_aio.c create mode 100644 src/libs/libssh-0.12.2/src/sftp_common.c create mode 100644 src/libs/libssh-0.12.2/src/sftpserver.c create mode 100644 src/libs/libssh-0.12.2/src/sk_common.c create mode 100644 src/libs/libssh-0.12.2/src/sk_usbhid.c create mode 100644 src/libs/libssh-0.12.2/src/sntrup761.c create mode 100644 src/libs/libssh-0.12.2/src/socket.c create mode 100644 src/libs/libssh-0.12.2/src/string.c create mode 100644 src/libs/libssh-0.12.2/src/threads.c create mode 100644 src/libs/libssh-0.12.2/src/threads/libcrypto.c create mode 100644 src/libs/libssh-0.12.2/src/threads/libgcrypt.c create mode 100644 src/libs/libssh-0.12.2/src/threads/mbedtls.c create mode 100644 src/libs/libssh-0.12.2/src/threads/noop.c create mode 100644 src/libs/libssh-0.12.2/src/threads/pthread.c create mode 100644 src/libs/libssh-0.12.2/src/threads/winlocks.c create mode 100644 src/libs/libssh-0.12.2/src/token.c create mode 100644 src/libs/libssh-0.12.2/src/ttyopts.c create mode 100644 src/libs/libssh-0.12.2/src/wrapper.c create mode 100644 src/libs/libssh-0.12.2/tests/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/tests/benchmarks/CMakeLists.txt create mode 100755 src/libs/libssh-0.12.2/tests/benchmarks/bench1.sh create mode 100755 src/libs/libssh-0.12.2/tests/benchmarks/bench2.sh create mode 100644 src/libs/libssh-0.12.2/tests/benchmarks/bench_raw.c create mode 100644 src/libs/libssh-0.12.2/tests/benchmarks/bench_scp.c create mode 100644 src/libs/libssh-0.12.2/tests/benchmarks/bench_sftp.c create mode 100644 src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.c create mode 100644 src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.h create mode 100644 src/libs/libssh-0.12.2/tests/benchmarks/latency.c create mode 100644 src/libs/libssh-0.12.2/tests/chmodtest.c create mode 100644 src/libs/libssh-0.12.2/tests/chroot_wrapper.c create mode 100644 src/libs/libssh-0.12.2/tests/client/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_algorithms.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_auth.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_auth_agent_forwarding.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_auth_cert.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_auth_common.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_auth_pkcs11.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_client_callbacks.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_client_config.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_client_global_requests.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_connect.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_forward.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_get_kex_algo.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_gssapi_auth.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange_null.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_hostkey.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_knownhosts.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_knownhosts_verify.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_proxycommand.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_proxyjump.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_rekey.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_request_env.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_request_pty_modes.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_scp.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_session.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_aio.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_benchmark.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_canonicalize_path.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_dir.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_expand_path.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_ext.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_fsync.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_get_users_groups_by_id.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_hardlink.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_home_directory.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_init.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_limits.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_packet_read.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_read.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_recv_response_msg.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_rename.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_request_id.c create mode 100644 src/libs/libssh-0.12.2/tests/client/torture_sftp_setstat.c create mode 100644 src/libs/libssh-0.12.2/tests/cmdline.c create mode 100644 src/libs/libssh-0.12.2/tests/ctest-default.cmake create mode 100644 src/libs/libssh-0.12.2/tests/etc/group.in create mode 100644 src/libs/libssh-0.12.2/tests/etc/hosts.in create mode 100644 src/libs/libssh-0.12.2/tests/etc/openssl.cnf create mode 100644 src/libs/libssh-0.12.2/tests/etc/pam.d/sshd.in create mode 100644 src/libs/libssh-0.12.2/tests/etc/pam_matrix_passdb.in create mode 100644 src/libs/libssh-0.12.2/tests/etc/passwd.in create mode 100644 src/libs/libssh-0.12.2/tests/etc/shadow.in create mode 100644 src/libs/libssh-0.12.2/tests/external_override/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/tests/external_override/chacha20_override.c create mode 100644 src/libs/libssh-0.12.2/tests/external_override/chacha20_override.h create mode 100644 src/libs/libssh-0.12.2/tests/external_override/curve25519_override.c create mode 100644 src/libs/libssh-0.12.2/tests/external_override/curve25519_override.h create mode 100644 src/libs/libssh-0.12.2/tests/external_override/ed25519_override.c create mode 100644 src/libs/libssh-0.12.2/tests/external_override/ed25519_override.h create mode 100644 src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.c create mode 100644 src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.h create mode 100644 src/libs/libssh-0.12.2/tests/external_override/poly1305_override.c create mode 100644 src/libs/libssh-0.12.2/tests/external_override/poly1305_override.h create mode 100644 src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.c create mode 100644 src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.h create mode 100644 src/libs/libssh-0.12.2/tests/external_override/torture_override.c create mode 100644 src/libs/libssh-0.12.2/tests/fs_wrapper.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/README.md create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/fuzzer.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/nallocinc.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_bind_config_fuzzer.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer_corpus/0f9d75a6c1d365115772a502d42b6e48f453198a create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer_corpus/fd7bd24a85e712fb59159a512b69d34ca21c8383 create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer.c create mode 100644 src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 create mode 100755 src/libs/libssh-0.12.2/tests/generate.py create mode 100755 src/libs/libssh-0.12.2/tests/gss/kdcsetup.sh create mode 100644 src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa create mode 100644 src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa-cert.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_ecdsa create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_ecdsa.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_ed25519 create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_ed25519.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_rsa create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_rsa.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_rsa_protected create mode 100644 src/libs/libssh-0.12.2/tests/keys/id_rsa_protected.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256 create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384 create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521 create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519 create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519_openssh.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa_openssh.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key create mode 100644 src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/ssh_host_key create mode 100644 src/libs/libssh-0.12.2/tests/keys/ssh_host_key.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key create mode 100644 src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key.pub create mode 100644 src/libs/libssh-0.12.2/tests/keys/user_ca create mode 100755 src/libs/libssh-0.12.2/tests/pkcs11/setup-softhsm-tokens.sh create mode 100644 src/libs/libssh-0.12.2/tests/pkd/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/tests/pkd/pkd_client.h create mode 100644 src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.c create mode 100644 src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.h create mode 100644 src/libs/libssh-0.12.2/tests/pkd/pkd_hello.c create mode 100644 src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.c create mode 100644 src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.h create mode 100644 src/libs/libssh-0.12.2/tests/pkd/pkd_util.c create mode 100644 src/libs/libssh-0.12.2/tests/pkd/pkd_util.h create mode 100644 src/libs/libssh-0.12.2/tests/server/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/tests/server/test_server/CMakeLists.txt create mode 100644 src/libs/libssh-0.12.2/tests/server/test_server/default_cb.c create mode 100644 src/libs/libssh-0.12.2/tests/server/test_server/default_cb.h create mode 100644 src/libs/libssh-0.12.2/tests/server/test_server/main.c create mode 100644 src/libs/libssh-0.12.2/tests/server/test_server/sftpserver_cb.c create mode 100644 src/libs/libssh-0.12.2/tests/server/test_server/test_server.c create mode 100644 src/libs/libssh-0.12.2/tests/server/test_server/test_server.h create mode 100644 src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.c create mode 100644 src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.h create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth_cb.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_delegation.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_fallback.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_null.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_server_algorithms.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_server_auth_kbdint.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_server_config.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_server_default.c create mode 100644 src/libs/libssh-0.12.2/tests/server/torture_sftpserver.c create mode 100644 src/libs/libssh-0.12.2/tests/ssh_ping.c create mode 100644 src/libs/libssh-0.12.2/tests/suppressions/lsan.supp create mode 100644 src/libs/libssh-0.12.2/tests/test_socket.c create mode 100644 src/libs/libssh-0.12.2/tests/tests_config.h.cmake create mode 100644 src/libs/libssh-0.12.2/tests/torture.c create mode 100644 src/libs/libssh-0.12.2/tests/torture.h create mode 100644 src/libs/libssh-0.12.2/tests/torture_cmocka.c create mode 100644 src/libs/libssh-0.12.2/tests/torture_cmocka.h create mode 100644 src/libs/libssh-0.12.2/tests/torture_key.c create mode 100644 src/libs/libssh-0.12.2/tests/torture_key.h create mode 100644 src/libs/libssh-0.12.2/tests/torture_pki.c create mode 100644 src/libs/libssh-0.12.2/tests/torture_pki.h create mode 100644 src/libs/libssh-0.12.2/tests/torture_sk.c create mode 100644 src/libs/libssh-0.12.2/tests/torture_sk.h create mode 100644 src/libs/libssh-0.12.2/tests/unittests/CMakeLists.txt create mode 100755 src/libs/libssh-0.12.2/tests/unittests/hello world.sh create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_bignum.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_bind_config.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_buffer.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_bytearray.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_callbacks.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_channel.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_config.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_config_match_localnetwork.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_crypto.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_forwarded_tcpip_callback.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_hashes.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_init.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_isipaddr.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_keyfiles.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_knownhosts_parsing.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_list.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_misc.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_moduli.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_options.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_packet.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_packet_filter.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_dsa.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa_uri.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519_uri.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa_uri.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ecdsa.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ed25519.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_pki_sshsig.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_push_pop_dir.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_rand.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_server_direct_tcpip.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_server_x11.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_session_keys.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_sk_usbhid.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_string.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_temp_dir.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_temp_file.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_threads_buffer.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_threads_crypto.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_threads_init.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_threads_pki_rsa.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_tokens.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_unit_server.c create mode 100644 src/libs/libssh-0.12.2/tests/unittests/torture_unit_sftp.c create mode 100644 src/libs/libssh-0.12.2/tests/valgrind.supp diff --git a/Config.kmk b/Config.kmk index 8f7720947270..ac3fce0962fb 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114889 2026-08-07 08:43:33Z andreas.loeffler@oracle.com $ +# $Id: Config.kmk 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -5278,7 +5278,7 @@ SDK_VBoxSoftFloatGuestR3Shared_LIBS.$(KBUILD_TARGET_ARCH) = \ ifdef VBOX_WITH_LIBSSH SDK_VBoxLibSsh := libssh for dynamic dll loading with assembly stubs. - SDK_VBoxLibSsh_INCS ?= $(PATH_ROOT)/src/libs/libssh-0.11.4/include + SDK_VBoxLibSsh_INCS ?= $(PATH_ROOT)/src/libs/libssh-0.12.2/include # SDK_VBoxLibSsh_LIBS is not defined, as VBoxLibSsh dll is not linked, but loaded explicitly # by auto-generated stub code (lazy loading) endif @@ -9598,7 +9598,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114889 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114891 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9612,7 +9612,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114889 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114891 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif diff --git a/src/VBox/Devices/Network/testcase/tstVBoxLibssh.cpp b/src/VBox/Devices/Network/testcase/tstVBoxLibssh.cpp index 539d23213352..ef721771f248 100644 --- a/src/VBox/Devices/Network/testcase/tstVBoxLibssh.cpp +++ b/src/VBox/Devices/Network/testcase/tstVBoxLibssh.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVBoxLibssh.cpp 114489 2026-06-22 16:31:04Z aleksey.ilyushin@oracle.com $ */ +/* $Id: tstVBoxLibssh.cpp 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ */ /** @file * tstVBoxLibssh - Testcase for the libssh. Requires sshd with keys configured on some host. */ @@ -73,6 +73,7 @@ static int runSessionAndExec(void) do { + RTTestIPrintf(RTTESTLVL_ALWAYS, "Testing with libssh-" SSH_STRINGIFY(LIBSSH_VERSION) "\n"); RTTestIPrintf(RTTESTLVL_ALWAYS, "Connecting to host=%s user=%s key=%s\n", g_szHost, g_szUser, g_szKeyFile); diff --git a/src/VBox/Main/src-all/VBoxLibSsh.def b/src/VBox/Main/src-all/VBoxLibSsh.def index 6b2e91a6b01d..7f26f2144d33 100644 --- a/src/VBox/Main/src-all/VBoxLibSsh.def +++ b/src/VBox/Main/src-all/VBoxLibSsh.def @@ -1,4 +1,4 @@ -; $Id: VBoxLibSsh.def 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VBoxLibSsh.def 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ ;; @file ; VBoxLibSsh - Definition file for lazy import generation for VBoxC. ; @@ -28,7 +28,10 @@ LIBRARY VBoxLibSsh EXPORTS ssh_key_free - ssh_pki_generate + ssh_pki_ctx_free + ssh_pki_ctx_new + ssh_pki_ctx_options_set + ssh_pki_generate_key ssh_pki_export_privkey_base64 ssh_pki_export_privkey_file ssh_pki_export_pubkey_base64 diff --git a/src/VBox/Main/src-client/CloudGateway.cpp b/src/VBox/Main/src-client/CloudGateway.cpp index 866eeb242ce8..cd58d8fbcb0b 100644 --- a/src/VBox/Main/src-client/CloudGateway.cpp +++ b/src/VBox/Main/src-client/CloudGateway.cpp @@ -1,4 +1,4 @@ -/* $Id: CloudGateway.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: CloudGateway.cpp 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ */ /** @file * Implementation of local and cloud gateway management. */ @@ -252,8 +252,17 @@ HRESULT generateKeys(GatewayInfo& gateway) RT_NOREF(gateway); return E_NOTIMPL; #else /* VBOX_WITH_LIBSSH */ + int iKeySize = 2048; ssh_key single_use_key; - int iRcSsh = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &single_use_key); + ssh_pki_ctx ctx = ssh_pki_ctx_new(); + if (ctx == NULL) + { + LogRel(("Failed to allocate a PKI context.\n")); + return E_FAIL; + } + int iRcSsh = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &iKeySize); + iRcSsh = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &single_use_key); + ssh_pki_ctx_free(ctx); if (iRcSsh != SSH_OK) { LogRel(("Failed to generate a key pair. iRcSsh = %d\n", iRcSsh)); diff --git a/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def b/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def index ec0a4e90a3ac..4707470ed9f2 100644 --- a/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def +++ b/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def @@ -1,4 +1,4 @@ -; $Id: VBoxRT-openssl-3.0.def 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VBoxRT-openssl-3.0.def 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ ;; @file ; IPRT - Windows OpenSSL exports we use outside VBoxRT (keep them few!). ; @@ -373,6 +373,8 @@ EVP_MD_CTX_free EVP_MD_CTX_new EVP_MD_CTX_reset + EVP_MD_fetch + EVP_MD_free EVP_md5 EVP_PKEY_CTX_new EVP_PKEY_CTX_new_id diff --git a/src/libs/Makefile.kmk b/src/libs/Makefile.kmk index ca06ee0516fa..ccf589eba3d9 100644 --- a/src/libs/Makefile.kmk +++ b/src/libs/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114504 2026-06-23 16:56:50Z aleksey.ilyushin@oracle.com $ +# $Id: Makefile.kmk 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ ## @file # Top-level makefile for the external libraries. # @@ -90,7 +90,7 @@ endif if defined(VBOX_WITH_LIBSSH) \ && !defined(VBOX_ONLY_BUILD) \ && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SUPPORTED_HOST_ARCHS))" != "" - include $(PATH_SUB_CURRENT)/libssh-0.11.4/Makefile.kmk + include $(PATH_SUB_CURRENT)/libssh-0.12.2/Makefile.kmk endif diff --git a/src/libs/libssh-0.12.2/.arcconfig b/src/libs/libssh-0.12.2/.arcconfig new file mode 100644 index 000000000000..30b1a60cf8c8 --- /dev/null +++ b/src/libs/libssh-0.12.2/.arcconfig @@ -0,0 +1,4 @@ +{ + "phabricator.uri" : "https://bugs.libssh.org/", + "history.immutable": true +} diff --git a/src/libs/libssh-0.12.2/.clang-format b/src/libs/libssh-0.12.2/.clang-format new file mode 100644 index 000000000000..d6f60e37bc8d --- /dev/null +++ b/src/libs/libssh-0.12.2/.clang-format @@ -0,0 +1,29 @@ +--- +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: Never +AllowShortIfStatementsOnASingleLine: false +BreakBeforeBraces: Custom +BraceWrapping: + AfterEnum: false + AfterFunction: true + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeElse: false + BeforeWhile: false +IndentCaseLabels: false +IndentCaseBlocks: false +ColumnLimit: 80 +AlignAfterOpenBracket: Align +AllowAllParametersOfDeclarationOnNextLine: false +BinPackArguments: false +BinPackParameters: false +AllowAllArgumentsOnNextLine: false +AllowShortFunctionsOnASingleLine: Empty +BreakAfterReturnType: ExceptShortType +AlwaysBreakAfterReturnType: AllDefinitions +AlignEscapedNewlines: Left +ForEachMacros: ['ssh_callbacks_iterate'] +AlignConsecutiveMacros: 'Consecutive' diff --git a/src/libs/libssh-0.12.2/.clang-format-ignore b/src/libs/libssh-0.12.2/.clang-format-ignore new file mode 100644 index 000000000000..459f5916d7fd --- /dev/null +++ b/src/libs/libssh-0.12.2/.clang-format-ignore @@ -0,0 +1 @@ +src/external/* diff --git a/src/libs/libssh-0.12.2/.cmake-format.yaml b/src/libs/libssh-0.12.2/.cmake-format.yaml new file mode 100644 index 000000000000..f411792840f3 --- /dev/null +++ b/src/libs/libssh-0.12.2/.cmake-format.yaml @@ -0,0 +1,6 @@ +--- +line_width: 80 +tab_size: 4 +use_tabchars: false +separate_ctrl_name_with_space: true +separate_fn_name_with_space: false diff --git a/src/libs/libssh-0.12.2/.editorconfig b/src/libs/libssh-0.12.2/.editorconfig new file mode 100644 index 000000000000..bb3dfbb054de --- /dev/null +++ b/src/libs/libssh-0.12.2/.editorconfig @@ -0,0 +1,23 @@ +root = true + +[*] +charset = utf-8 +max_line_length = 80 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{c,h}] +indent_style = space +indent_size = 4 +tab_width = 4 + +[CMakeLists.txt] +indent_style = space +indent_size = 4 +tab_width = 4 + +[*.cmake] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/src/libs/libssh-0.12.2/AUTHORS b/src/libs/libssh-0.12.2/AUTHORS new file mode 100644 index 000000000000..51b3e46f4b9b --- /dev/null +++ b/src/libs/libssh-0.12.2/AUTHORS @@ -0,0 +1,15 @@ +Author(s): +Aris Adamantiadis (project initiator) + +Andreas Schneider (developer) + +Nick Zitzmann (mostly client SFTP stuff) + +Norbert Kiesel (getaddrinfo and other patches) + +Jean-Philippe Garcia Ballester (Port to libgcrypt and configure.in voodoo, debian packaging) + +Contributor(s): + +Laurent Bigonville (debian packaging) + diff --git a/src/libs/libssh-0.12.2/BSD b/src/libs/libssh-0.12.2/BSD new file mode 100644 index 000000000000..b8dba0d24074 --- /dev/null +++ b/src/libs/libssh-0.12.2/BSD @@ -0,0 +1,24 @@ +Some parts are under the BSDv2 License : + + +Copyright (c) 2000 Markus Friedl. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/src/libs/libssh-0.12.2/CHANGELOG b/src/libs/libssh-0.12.2/CHANGELOG new file mode 100644 index 000000000000..109f7747ce57 --- /dev/null +++ b/src/libs/libssh-0.12.2/CHANGELOG @@ -0,0 +1,826 @@ +CHANGELOG +========= + +version 0.12.2 (released 2026-07-28) + * Security: + * CVE-2026-59843: Denial of service via zero advertised channel packet size + (accidentally missed from previous release backports) + * Bugfixes: + * Fix fallback to default provider if FIPS provider does not have ML-KEM + implementation + * Do not override explicitly set options through a configuration file to + match OpenSSH behavior. + * Return SSH_KNOWN_HOSTS_UNKNOWN when GSSAPI key exchange is used without any + hostkey + * New API: + * API to check the GSSAPI key exchange was used for hostkey handling + * API to initiate GSSAPI keyex authentication (missing from last release) + +version 0.12.1 (released 2026-07-21) + * Security: + * CVE-2026-15370: Stack buffer overflow in SFTP server longname construction + * CVE-2026-59842: Information disclosure via short GSSAPI Curve25519 public key + * CVE-2026-59844: Denial of service via oversized SFTP read length + * CVE-2026-59845: Denial of service via unchecked ProxyCommand fork() failure + * CVE-2026-59846: Information disclosure via ProxyCommand %r username expansion + * CVE-2026-59847: Integrity downgrade via OpenSSL AES-GCM tag verification + * CVE-2026-59848: Denial of service via SFTP responses with unknown request IDs + * CVE-2026-59849: Denial of service via automatic certificate authentication loop + * CVE-2026-59850: Use-after-free via data callbacks on closed channels + * CVE-2026-59851: Authentication bypass via missing GSSAPI principal check + * Zero-initialize every ssh_string + * Compatibility: + * Fix compatibility with C23 / gcc16 + * Allow hybrid ML-KEM key exchange in FIPS mode + * Bugfixes: + * Fix multiple memory leaks, null checks, and error checks + * Fix parameter size mismatch in mlkem768x25519-sha256 + * Fix client SFTP messages being ignored if sent at high rate + * Validate peer public key in DH key exchange + * Fix ambiguous error reporting of sftp_init + * Fix hidden integer underflow in socket packet callback + * Avoid remote window overflow + * Fix socket data callback return value on rekey failure + * Avoid off-by-one overflow during kbdint authentication + * Avoid logging uninitialized sequence numbers + * Avoid double conversion of SFTP version number + * Send correct SFTP server version number + * Avoid handling repeated SFTP INIT messages + * Harmonize return values from SFTP server callbacks + +version 0.12.0 (released 2026-02-10) + * Deprecations and removals: + * Bumped minimal RSA key size to 1024 bits + * New functionality: + * Add support for hybrid key exchange mechanisms using Quantum Resistant + cryptography for all backends. These are now preferred: + * sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com + * mlkem768nistp256-sha256 + * mlkem768x25519-sha256 + * mlkem1024nistp384-sha384 (only OpenSSL 3.5+ and libgcrypt) + * New cmake option WITH_HERMETIC_USR + * Added support for Ed25519 keys through PKCS#11 + * Support for host-bound public key authentication + (publickey-hostbound-v00@openssh.com) + * Use curve25519 implementation from mbedTLS and libgcrypt + * New functions for signing arbitrary data (commits) with SSH keys + * sshsig_sign() + * sshsig_verify() + * Support for FIDO/U2F keys (internal implementation using libfido2) + * Compatible with OpenSSH: should work out of the box + * Extensible with callbacks + * Add support for GSSAPI Key Exchange (RFC 4462, RFC 8732) + * Add support for new configuration options (client and server): + * RequiredRsaSize + * AddressFamily (client) + * GSSAPIKeyExchange + * GSSAPIKexAlgorithms + * New option to get list of configured identities (SSH_OPTIONS_NEXT_IDENTITY) + * More OpenSSH compatible percent expansion characters + * Add new server auth_kbdint_function() callback + * New PKI Context structure for key operations + * Stability and compatibility improvements of ProxyJump + * SFTP + * Prevent failures when SFTP status message does not contain error message + * Fix possible timeouts while waiting for SFTP messages + * Support for users-groups-by-id@openssh.com extension in client + * Support for SSH_FXF_TRUNC in server + +version 0.11.4 (released 2026-02-10) + * Security: + * CVE-2025-14821: libssh loads configuration files from the C:\etc directory + on Windows + * CVE-2026-0964: SCP Protocol Path Traversal in ssh_scp_pull_request() + * CVE-2026-0965: Possible Denial of Service when parsing unexpected + configuration files + * CVE-2026-0966: Buffer underflow in ssh_get_hexa() on invalid input + * CVE-2026-0967: Specially crafted patterns could cause DoS + * CVE-2026-0968: OOB Read in sftp_parse_longname() + * libssh-2026-sftp-extensions: Read buffer overrun when handling SFTP + extensions + * Stability and compatibility improvements of ProxyJump + +version 0.11.3 (released 2025-09-09) + * Security: + * CVE-2025-8114: Fix NULL pointer dereference after allocation failure + * CVE-2025-8277: Fix memory leak of ephemeral key pair during repeated wrong KEX + * Potential UAF when send() fails during key exchange + * Fix possible timeout during KEX if client sends authentication too early (#311) + * Cleanup OpenSSL PKCS#11 provider when loaded + * Zeroize buffers containing private key blobs during export + +version 0.11.2 (released 2025-06-24) + * Security: + * CVE-2025-4877 - Write beyond bounds in binary to base64 conversion + * CVE-2025-4878 - Use of uninitialized variable in privatekey_from_file() + * CVE-2025-5318 - Likely read beyond bounds in sftp server handle management + * CVE-2025-5351 - Double free in functions exporting keys + * CVE-2025-5372 - ssh_kdf() returns a success code on certain failures + * CVE-2025-5449 - Likely read beyond bounds in sftp server message decoding + * CVE-2025-5987 - Invalid return code for chacha20 poly1305 with OpenSSL + * Compatibility + * Fixed compatibility with CPM.cmake + * Compatibility with OpenSSH 10.0 + * Tests compatibility with new Dropbear releases + * Removed p11-kit remoting from the pkcs11 testsuite + * Bugfixes + * Implement missing packet filter for DH GEX + * Properly process the SSH2_MSG_DEBUG message + * Allow escaping quotes in quoted arguments to ssh configuration + * Do not fail with unknown match keywords in ssh configuration + * Process packets before selecting signature algorithm during authentication + * Do not fail hard when the SFTP status message is not sent by noncompliant + servers + +version 0.11.1 (released 2024-08-30) + * Fixed default TTY modes that are set when stdin is not connected to tty (#270) + * Fixed zlib cleanup procedure, which could crash on i386 + * Various test fixes improving their stability + * Fixed cygwin build + +version 0.11.0 (released 2024-07-31) + * Deprecations and Removals: + * Dropped support for DSA + * Deprecated Blowfish cipher (will be removed in next release) + * Deprecated SSH_BIND_OPTIONS_{RSA,ECDSA}KEY in favor of generic HOSTKEY + * Removed the usage of deprecated OpenSSL APIs (Note: Minimum supported + OpenSSL version is 1.1.1) + * Disabled preauth compression (zlib) by default + * Support for pkcs#11 engines are deprecated, pkcs11-provider is used instead + * Deprecation of old async SFTP API + * libgcrypt cryptographic backend is deprecated + * Deprecation of knownhosts hashing + * SFTP Improvements: + * Added support for async SFTP IO + * Added support for sftp_limits() and applied capping to SFTP read/write + operations accordingly + * Added sftp_home_directory() API support for sftp extension "home-directory" + * Added sftp_lsetstat() API for lsetstat extensions + * Added sftp_expand_path() to canonicalize path using expand-path@openssh.com + extension + * Implemented stat and realpath in sftpserver + * Added sftp_readlink() API to support hardlink@openssh.com + * New extensible callback based SFTP server + * Introduced the posix-rename@openssh.com extension + * New functions and features: + * Added support for PKCS #11 provider for OpenSSL 3.0 + * Added testing for GSSAPI Authentication + * Implemented proxy jump using libssh + * Recategorized loglevels to show fatal errors and alignment with OpenSSH + log levels + * Added ssh_channel_request_pty_size_modes() API to set terminal modes for + PTYs + * Added function to check username syntax + * Added support to check all keys in authorized_keys instead of one in + example server implementation + * Handled hostkey similar to OpenSSH + * Added ssh_session_socket_close() API in order to not close socket passed + through options on error conditions + * Added option SSH_BIND_OPTIONS_IMPORT_KEY_STR to read user-supplied key + string in ssh_bind_options_set() + * Improved log handling around ssh_set_callbacks + * Added ssh_set_error_invalid in ssh_options_set() + * Prevented signature blob to start with 1 bit in libgcrypt + * Added support to unbreak key comparison of Ed25519 keys imported from PEM + or OpenSSH container + * Added support to calculate missing CRT parameters when building RSA key + * Added ssh_pki_export_privkey_base64_format() and + ssh_pki_export_privkey_file_format() to support exporting keys in different + formats (PEM, OpenSSH) + * Added support to compare certificates and handle automatic certificate + authentication + * Added support to make compile-commands generation conditional + * Built fuzzers for normal testing + * Avoided passing other events to callbacks when called recursively + * Added control master and path options + * Refactored channel_rcv_data, check for errors and report more useful errors + * Added support to connect to other host addresses than just the first one + * Terminated the server properly when the MaxAuthTries is reached + * Added support for no-more-sessions@openssh.com request in both client and + server + * Added callback to support forwarded-tcpip requests + * Bumped minimal CMake version to 3.12 + * Added support for MBedTLS 3.6.x + * Added support for +,-,^ modifiers in front of algorithm lists in options + * Added callbacks for channel open response, and channel request response + * Replaced chroot() from chroot_wrapper internal library with chroot() + from priv_wrapper package + * Added a placeholder for non-expanded identities + * Improved handling of channel transfer window sizes + +version 0.10.6 (released 2023-12-18) + * Fix CVE-2023-6004: Command injection using proxycommand + * Fix CVE-2023-48795: Potential downgrade attack using strict kex + * Fix CVE-2023-6918: Missing checks for return values of MD functions + * Fix ssh_send_issue_banner() for CMD(PowerShell) + * Avoid passing other events to callbacks when poll is called recursively (#202) + * Allow @ in usernames when parsing from URI composes + +version 0.10.5 (released 2023-05-04) + * Fix CVE-2023-1667: a NULL dereference during rekeying with algorithm guessing + * Fix CVE-2023-2283: a possible authorization bypass in + pki_verify_data_signature under low-memory conditions. + * Fix several memory leaks in GSSAPI handling code + * Escape braces in ProxyCommand created from ProxyJump options for zsh + compatibility. + * Fix pkg-config path relocation for MinGW + * Improve doxygen documentation + * Fix build with cygwin due to the glob support + * Do not enqueue outgoing packets after sending SSH2_MSG_NEWKEYS + * Add support for SSH_SUPPRESS_DEPRECATED + * Avoid functions declarations without prototype to build with clang 15 + * Fix spelling issues + * Avoid expanding KnownHosts, ProxyCommands and IdentityFiles repetitively + * Add support sk-* keys through configuration + * Improve checking for Argp library + * Log information about received extensions + * Correctly handle rekey with delayed compression + * Move the EC keys handling to OpenSSL 3.0 API + * Record peer disconnect message + * Avoid deadlock when write buffering occurs and we call poll recursively to + flush the output buffer + * Disable preauthentication compression by default + * Add CentOS 8 Stream / OpenSSL 1.1.1 to CI + * Add accidentally removed default compile flags + * Solve incorrect parsing of ProxyCommand option + +version 0.10.4 (released 2022-09-07) + * Fixed issues with KDF on big endian + +version 0.10.3 (released 2022-09-05) + * Fixed possible infinite loop in known hosts checking + +version 0.10.2 (released 2022-09-02) + * Fixed tilde expansion when handling include directives + * Fixed building the shared torture library + * Made rekey test more robust (fixes running on i586 build systems e.g koji) + +version 0.10.1 (released 2022-08-30) + * Fixed proxycommand support + * Fixed musl libc support + +version 0.10.0 (released 2022-08-26) + * Added support for OpenSSL 3.0 + * Added support for mbedTLS 3 + * Added support for Smart Cards (through openssl pkcs11 engine) + * Added support for chacha20-poly1305@openssh.com with libgcrypt + * Added support ed25519 keys in PEM files + * Added support for sk-ecdsa and sk-ed25519 (server side) + * Added support for limiting RSA key sizes and not accepting small one by + default + * Added support for ssh-agent on Windows + * Added ssh_userauth_publickey_auto_get_current_identity() API + * Added ssh_vlog() API + * Added ssh_send_issue_banner() API + * Added ssh_session_set_disconnect_message() API + * Added new configuration options: + + IdentityAgent + + ModuliFile + * Provided X11 client example + * Disabled DSA support at build time by default (will be removed in the next + release) + * Deprecated the SCP API! + * Deprecated old pubkey, privatekey API + * Avoided some needless large stack buffers to minimize memory footprint + * Removed support for OpenSSL < 1.0.1 + +version 0.9.6 (released 2021-08-26) + * CVE-2021-3634: Fix possible heap-buffer overflow when rekeying with + different key exchange mechanism + * Fix several memory leaks on error paths + * Reset pending_call_state on disconnect + * Fix handshake bug with AEAD ciphers and no HMAC overlap + * Use OPENSSL_CRYPTO_LIBRARIES in CMake + * Ignore request success and failure message if they are not expected + * Support more identity files in configuration + * Avoid setting compiler flags directly in CMake + * Support build directories with special characters + * Include stdlib.h to avoid crash in Windows + * Fix sftp_new_channel constructs an invalid object + * Fix Ninja multiple rules error + * Several tests fixes + +version 0.9.5 (released 2020-09-10) + * CVE-2020-16135: Avoid null pointer dereference in sftpserver (T232) + * Improve handling of library initialization (T222) + * Fix parsing of subsecond times in SFTP (T219) + * Make the documentation reproducible + * Remove deprecated API usage in OpenSSL + * Fix regression of ssh_channel_poll_timeout() returning SSH_AGAIN + * Define version in one place (T226) + * Prevent invalid free when using different C runtimes than OpenSSL (T229) + * Compatibility improvements to testsuite + +version 0.9.4 (released 2020-04-09) + * Fixed CVE-2020-1730 - Possible DoS in client and server when handling + AES-CTR keys with OpenSSL + * Added diffie-hellman-group14-sha256 + * Fixed several possible memory leaks + +version 0.9.3 (released 2019-12-10) + * Fixed CVE-2019-14889 - SCP: Unsanitized location leads to command execution + * SSH-01-003 Client: Missing NULL check leads to crash in erroneous state + * SSH-01-006 General: Various unchecked Null-derefs cause DOS + * SSH-01-007 PKI Gcrypt: Potential UAF/double free with RSA pubkeys + * SSH-01-010 SSH: Deprecated hash function in fingerprinting + * SSH-01-013 Conf-Parsing: Recursive wildcards in hostnames lead to DOS + * SSH-01-014 Conf-Parsing: Integer underflow leads to OOB array access + * SSH-01-001 State Machine: Initial machine states should be set explicitly + * SSH-01-002 Kex: Differently bound macros used to iterate same array + * SSH-01-005 Code-Quality: Integer sign confusion during assignments + * SSH-01-008 SCP: Protocol Injection via unescaped File Names + * SSH-01-009 SSH: Update documentation which RFCs are implemented + * SSH-01-012 PKI: Information leak via uninitialized stack buffer + +version 0.9.2 (released 2019-11-07) + * Fixed libssh-config.cmake + * Fixed issues with rsa algorithm negotiation (T191) + * Fixed detection of OpenSSL ed25519 support (T197) + +version 0.9.1 (released 2019-10-25) + * Added support for Ed25519 via OpenSSL + * Added support for X25519 via OpenSSL + * Added support for localuser in Match keyword + * Fixed Match keyword to be case sensitive + * Fixed compilation with LibreSSL + * Fixed error report of channel open (T75) + * Fixed sftp documentation (T137) + * Fixed known_hosts parsing (T156) + * Fixed build issue with MinGW (T157) + * Fixed build with gcc 9 (T164) + * Fixed deprecation issues (T165) + * Fixed known_hosts directory creation (T166) + +version 0.9.0 (released 2019-02-xx) + * Added support for AES-GCM + * Added improved rekeying support + * Added performance improvements + * Disabled blowfish support by default + * Fixed several ssh config parsing issues + * Added support for DH Group Exchange KEX + * Added support for Encrypt-then-MAC mode + * Added support for parsing server side configuration file + * Added support for ECDSA/Ed25519 certificates + * Added FIPS 140-2 compatibility + * Improved known_hosts parsing + * Improved documentation + * Improved OpenSSL API usage for KEX, DH, and signatures + +version 0.8.0 (released 2018-08-10) + * Removed support for deprecated SSHv1 protocol + * Added new connector API for clients + * Added new known_hosts parsing API + * Added support for OpenSSL 1.1 + * Added support for chacha20-poly1305 cipher + * Added crypto backend for mbedtls crypto library + * Added ECDSA support with gcrypt backend + * Added advanced client and server testing using cwrap.org + * Added support for curve25519-sha256 alias + * Added support for global known_hosts file + * Added support for symbol versioning + * Improved ssh_config parsing + * Improved threading support + +version 0.7.5 (released 2017-04-13) + * Fixed a memory allocation issue with buffers + * Fixed PKI on Windows + * Fixed some SSHv1 functions + * Fixed config hostname expansion + +version 0.7.4 (released 2017-02-03) + * Added id_ed25519 to the default identity list + * Fixed sftp EOF packet handling + * Fixed ssh_send_banner() to confirm with RFC 4253 + * Fixed some memory leaks + +version 0.7.3 (released 2016-01-23) + * Fixed CVE-2016-0739 + * Fixed ssh-agent on big endian + * Fixed some documentation issues + +version 0.7.2 (released 2015-09-15) + * Fixed OpenSSL detection on Windows + * Fixed return status for ssh_userauth_agent() + * Fixed KEX to prefer hmac-sha2-256 + * Fixed sftp packet handling + * Fixed return values of ssh_key_is_(public|private) + * Fixed bug in global success reply + +version 0.7.1 (released 2015-06-30) + * Fixed SSH_AUTH_PARTIAL auth with auto public key + * Fixed memory leak in session options + * Fixed allocation of ed25519 public keys + * Fixed channel exit-status and exit-signal + * Reintroduce ssh_forward_listen() + +version 0.7.0 (released 2015-05-11) + * Added support for ed25519 keys + * Added SHA2 algorithms for HMAC + * Added improved and more secure buffer handling code + * Added callback for auth_none_function + * Added support for ECDSA private key signing + * Added more tests + * Fixed a lot of bugs + * Improved API documentation + +version 0.6.5 (released 2015-04-29) + * Fixed CVE-2015-3146 + * Fixed port handling in config file + * Fixed the build with libgcrypt + * Fixed SFTP endian issues (rlo #179) + * Fixed uninitilized sig variable (rlo #167) + * Fixed polling issues which could result in a hang + * Fixed handling of EINTR in ssh_poll() (rlo #186) + * Fixed C99 issues with __func__ + * Fixed some memory leaks + * Improved macro detection on Windows + +version 0.6.4 (released 2014-12-19) + * Fixed CVE-2014-8132. + * Added SHA-2 for session ID signing with ECDSA keys. + * Added support for ECDSA host keys. + * Added support for more ECDSA hostkey algorithms. + * Added ssh_pki_key_ecdsa_name() API. + * Fixed setting the bindfd only after successful listen. + * Fixed issues with user created sockets. + * Fixed several issues in libssh C++ wrapper. + * Fixed several documentation issues. + * Fixed channel exit-signal request. + * Fixed X11 request screen number in messages. + * Fixed several memory leaks. + +version 0.6.3 (released 2014-03-04) + * Fixed CVE-2014-0017. + * Fixed memory leak with ecdsa signatures. + +version 0.6.2 (released 2014-03-04) + * security: fix for vulnerability CVE-2014-0017 + +version 0.6.1 (released 2014-02-08) + * Added support for libgcrypt 1.6. + * Added ssh_channel_accept_forward(). + * Added known_hosts heuristic during connection (#138). + * Added getters for session cipher names. + * Fixed decrypt of zero length buffer. + * Fixed padding in RSA signature blobs. + * Fixed DSA signature extraction. + * Fixed some memory leaks. + * Fixed read of non-connected socket. + * Fixed thread detection. + +version 0.6.0 (released 2014-01-08) + * Added new publicy key API. + * Added new userauth API. + * Added ssh_get_publickey_hash() function. + * Added ssh_get_poll_flags() function. + * Added gssapi-mic userauth. + * Added GSSAPIServerIdentity option. + * Added GSSAPIClientIdentity option. + * Added GSSAPIDelegateCredentials option. + * Added new callback based server API. + * Added Elliptic Curve DSA (ECDSA) support (with OpenSSL). + * Added Elliptic Curve Diffie Hellman (ECDH) support. + * Added Curve25519 for ECDH key exchange. + * Added improved logging system. + * Added SSH-agent forwarding. + * Added key-reexchange. + * Added more unit tests. + * Improved documentation. + * Fixed timeout handling. + +version 0.5.5 (released 2013-07-26) + * BUG 103: Fix ProxyCommand parsing. + * Fix setting -D_FORTIFY_SOURCE=2. + * Fix pollset error return if empty. + * Fix NULL pointer checks in channel functions. + * Several bugfixes. + +version 0.5.4 (released 2013-01-22) + * CVE-2013-0176 - NULL dereference leads to denial of service + * Fixed several NULL pointer dereferences in SSHv1. + * Fixed a free crash bug in options parsing. + +version 0.5.3 (released 2012-11-20) + * CVE-2012-4559 Fixed multiple double free() flaws. + * CVE-2012-4560 Fixed multiple buffer overflow flaws. + * CVE-2012-4561 Fixed multiple invalid free() flaws. + * BUG #84 - Fix bug in sftp_mkdir not returning on error. + * BUG #85 - Fixed a possible channel infinite loop if the connection dropped. + * BUG #88 - Added missing channel request_state and set it to accepted. + * BUG #89 - Reset error state to no error on successful SSHv1 authentication. + * Fixed a possible use after free in ssh_free(). + * Fixed multiple possible NULL pointer dereferences. + * Fixed multiple memory leaks in error paths. + * Fixed timeout handling. + * Fixed regression in pre-connected socket setting. + * Handle all unknown global messages. + +version 0.5.2 (released 2011-09-17) + * Increased window size x10. + * Fixed SSHv1. + * Fixed bugged lists. + * Fixed use-after-free + inconsistent callbacks call in poll. + * Fixed scp documentation. + * Fixed possible infinite loop in channel_read(). + * Fixed handling of short reads of sftp_async_read(). + * Fixed handling request service timeout in blocking mode. + * Fixed ssh_auth_list() documentation. + * Fixed incorrect return values in ssh_channel_write(). + * Fixed an infinite loop in the termination callback. + * Fixed handling of SSH_AGAIN in channel_open(). + * Fixed "status -5 inflating zlib packet" + +version 0.5.1 (released 2011-08-09) + * Added checks for NULL pointers in string.c. + * Set the channel max packet size to 32768. + * Don't (de)compress empty buffers. + * Fixed ssh_scp_write so it works when doing recursive copy. + * Fixed another source of endless wait. + * Fixed an endless loop in case of a channel_open error. + * Fixed session timeout handling. + * Fixed ssh_channel_from_local() loop. + * Fixed permissions of scp example when we copy a file. + * Workaround ssh_get_user_home_dir on LDAP users. + * Added pkg-config support for libssh_threads. + * Fixed compilation without server and sftp modes. + * Fix static .lib overwriting on Windows. + +version 0.5.0 (released 2011-06-01) + * Added ssh_ prefix to all functions. + * Added complete Windows support. + * Added improved server support. + * Added unit tests for a lot of functions. + * Added asynchronous service request. + * Added a multiplatform ssh_getpass() function. + * Added a tutorial. + * Added a lot of documentation. + * Fixed a lot of bugs. + * Fixed several memory leaks. + +version 0.4.8 (released 2011-01-15) + * Fixed memory leaks in session signing. + * Fixed memory leak in ssh_print_hexa. + * Fixed problem with ssh_connect w/ timeout and fd > 1024. + * Fixed some warnings on OS/2. + * Fixed installation path for OS/2. + +version 0.4.7 (released 2010-12-28) + * Fixed a possible memory leak in ssh_get_user_home(). + * Fixed a memory leak in sftp_xstat. + * Fixed uninitialized fd->revents member. + * Fixed timeout value in ssh_channel_accept(). + * Fixed length checks in ssh_analyze_banner(). + * Fixed a possible data overread and crash bug. + * Fixed setting max_fd which breaks ssh_select(). + * Fixed some pedantic build warnings. + * Fixed a memory leak with session->bindaddr. + +version 0.4.6 (released 2010-09-03) + * Added a cleanup function to free the ws2_32 library. + * Fixed build with gcc 3.4. + * Fixed the Windows build on Vista and newer. + * Fixed the usage of WSAPoll() on Windows. + * Fixed "@deprecated" in doxygen + * Fixed some mingw warnings. + * Fixed handling of opened channels. + * Fixed keepalive problem on older openssh servers. + * Fixed testing for big endian on Windows. + * Fixed the Windows preprocessor macros and defines. + +version 0.4.5 (released 2010-07-13) + * Added option to bind a client to an ip address. + * Fixed the ssh socket polling function. + * Fixed Windows related bugs in bsd_poll(). + * Fixed several build warnings. + +version 0.4.4 (released 2010-06-01) + * Fixed a bug in the expand function for escape sequences. + * Fixed a bug in the tilde expand function. + * Fixed a bug in setting the options. + +version 0.4.3 (released 2010-05-18) + * Added global/keepalive responses. + * Added runtime detection of WSAPoll(). + * Added a select(2) based poll-emulation if poll(2) is not available. + * Added a function to expand an escaped string. + * Added a function to expand the tilde from a path. + * Added a proxycommand support. + * Added ssh_privatekey_type public function + * Added the possibility to define _OPENSSL_DIR and _ZLIB_DIR. + * Fixed sftp_chown. + * Fixed sftp_rename on protocol version 3. + * Fixed a blocking bug in channel_poll. + * Fixed config parsing which has overwritten user specified values. + * Fixed hashed [host]:port format in knownhosts + * Fixed Windows build. + * Fixed doublefree happening after a negotiation error. + * Fixed aes*-ctr with <= OpenSSL 0.9.7b. + * Fixed some documentation. + * Fixed exec example which has broken read usage. + * Fixed broken algorithm choice for server. + * Fixed a typo that we don't export all symbols. + * Removed the unneeded dependency to doxygen. + * Build examples only on the Linux platform. + +version 0.4.2 (released 2010-03-15) + * Added owner and group information in sftp attributes. + * Added missing SSH_OPTIONS_FD option. + * Added printout of owner and group in the sftp example. + * Added a prepend function for ssh_list. + * Added send back replies to openssh's keepalives. + * Fixed documentation in scp code + * Fixed longname parsing, this only workings with readdir. + * Fixed and added support for several identity files. + * Fixed sftp_parse_longname() on Windows. + * Fixed a race condition bug in ssh_scp_close() + * Remove config support for SSHv1 Cipher variable. + * Rename ssh_list_add to ssh_list_append. + * Rename ssh_list_get_head to ssh_list_pop_head + +version 0.4.1 (released 2010-02-13) + * Added support for aes128-ctr, aes192-ctr and aes256-ctr encryption. + * Added an example for exec. + * Added private key type detection feature in privatekey_from_file(). + * Fixed zlib compression fallback. + * Fixed kex bug that client preference should be priority + * Fixed known_hosts file set by the user. + * Fixed a memleak in channel_accept(). + * Fixed underflow when leave_function() are unbalanced + * Fixed memory corruption in handle_channel_request_open(). + * Fixed closing of a file handle case of errors in privatekey_from_file(). + * Fixed ssh_get_user_home_dir() to be thread safe. + * Fixed the doxygen documentation. + +version 0.4.0 (released 2009-12-10) + * Added scp support. + * Added support for sending signals (RFC 4254, section 6.9). + * Added MSVC support. + * Added support for ~/.ssh/config. + * Added sftp extension support. + * Added X11 forwarding support for client. + * Added forward listening. + * Added support for openssh extensions (statvfs, fstatvfs). + * Added a cleaned up interface for setting options. + * Added a generic way to handle sockets asynchronously. + * Added logging of the sftp flags used to open a file. + * Added full poll() support and poll-emulation for win32. + * Added missing 64bit functions in sftp. + * Added support for ~/ and SSH_DIR/ in filenames instead of %s/. + * Fixed Fix channel_get_exit_status bug. + * Fixed calltrace logging to make it optional. + * Fixed compilation on Solaris. + * Fixed resolving of ip addresses. + * Fixed libssh compilation without server support. + * Fixed possible memory corruptions (ticket #14). + +version 0.3.4 (released 2009-09-14) + * Added ssh_basename and ssh_dirname. + * Added a portable ssh_mkdir function. + * Added a sftp_tell64() function. + * Added missing NULL pointer checks to crypt_set_algorithms_server. + * Fixed ssh_write_knownhost if ~/.ssh doesn't exist. + * Fixed a possible integer overflow in buffer_get_data(). + * Fixed possible security bug in packet_decrypt(). + * Fixed a possible stack overflow in agent code. + +version 0.3.3 (released 2009-08-18) + * Fixed double free pointer crash in dsa_public_to_string. + * Fixed channel_get_exit_status bug. + * Fixed ssh_finalize which didn't clear the flag. + * Fixed memory leak introduced by previous bugfix. + * Fixed channel_poll broken when delayed EOF recvd. + * Fixed stupid "can't parse known host key" bug. + * Fixed possible memory corruption (ticket #14). + +version 0.3.2 (released 2009-08-05) + * Added ssh_init() function. + * Added sftp_readlink() function. + * Added sftp_symlink() function. + * Fixed ssh_write_knownhost(). + * Fixed compilation on Solaris. + * Fixed SSHv1 compilation. + +version 0.3.1 (released 2009-07-14) + * Added return code SSH_SERVER_FILE_NOT_FOUND. + * Fixed compilation of SSHv1. + * Fixed several memory leaks. + * Fixed possible infinite loops. + * Fixed a possible crash bug. + * Fixed build warnings. + * Fixed cmake on BSD. + +version 0.3 (released 2009-05-21) + * Added support for ssh-agent authentication. + * Added POSIX like sftp implementation. + * Added error checking to all functions. + * Added const to arguments where it was needed. + * Added a channel_get_exit_status() function. + * Added a channel_read_buffer() function, channel_read() is now + a POSIX like function. + * Added a more generic auth callback function. + * Added printf attribute checking for log and error functions. + * Added runtime function tracer support. + * Added NSIS build support with CPack. + * Added openssh hashed host support. + * Added API documentation for all public functions. + * Added asynchronous SFTP read function. + * Added a ssh_bind_set_fd() function. + * Fixed known_hosts parsing. + * Fixed a lot of build warnings. + * Fixed the Windows build. + * Fixed a lot of memory leaks. + * Fixed a double free corruption in the server support. + * Fixed the "ssh_accept:" bug in server support. + * Fixed important channel bugs. + * Refactored the socket handling. + * Switched to CMake build system. + * Improved performance. + +version 0.2 (released 2007-11-29) + * General cleanup + * More comprehensive API + * Up-to-date Doxygen documentation of each public function + * Basic server-based support + * Libgcrypt support (alternative to openssl and its license) + * SSH1 support (disabled by default) + * Added 3des-cbc + * A lot of bugfixes + +version 0.11-dev + * Server implementation development. + * Small bug corrected when connecting to sun ssh servers. + * Channel weirdness corrected (writing huge data packets) + * Channel_read_nonblocking added + * Channel bug where stderr wasn't correctly read fixed. + * Added sftp_file_set_nonblocking(), which is nonblocking SFTP IO + * Connect_status callback. + * Priv.h contains the internal functions, libssh.h the public interface + * Options_set_timeout (thx marcelo) really working. + * Tcp tunneling through channel_open_forward. + * Channel_request_exec() + * Channel_request_env() + * Ssh_get_pubkey_hash() + * Ssh_is_server_known() + * Ssh_write_known_host() + * Options_set_ssh_dir + * How could this happen ! there weren't any channel_close ! + * Nasty channel_free bug resolved. + * Removed the unsigned long all around the code. use only u8,u32 & u64. + * It now compiles and runs under amd64 ! + * Channel_request_pty_size + * Channel_change_pty_size + * Options_copy() + * Ported the doc to an HTML file. + * Small bugfix in packet.c + * Prefixed error constants with SSH_ + * Sftp_stat, sftp_lstat, sftp_fstat. thanks Michel Bardiaux for the patch. + * Again channel number mismatch fixed. + * Fixed a bug in ssh_select making the select fail when a signal has been + caught. + * Keyboard-interactive authentication working. + +version 0.1 (released 2004-03-05) + * Beginning of sftp subsystem implementation. + * Some cleanup into channels implementation + * Now every channel functions is called by its CHANNEL handler. + * Added channel_poll() and channel_read(). + * Changed the client so it uses the new channel_poll and channel_read interface + * Small use-after-free bug with channels resolved + * Changed stupidities in lot of function names. + * Removed a debug output file opened by default. + * Added API.txt, the libssh programmer handbook. + * Various bug fixes from Nick Zitzmann. + * Developed a cryptographic structure for handling protocols. + * An autoconf script which took me half of a day to set up. + * A ssh_select wrapper has been written. + +version 0.0.4 (released 2003-10-10) + * Some terminal code (eof handling) added + * Channels bugfix (it still needs some tweaking though) + * Zlib support + * Added a wrapper.c file. The goal is to provide a similar API to every + cryptographic functions. bignums and sha/md5 are wrapped now. + * More work than it first looks. + * Support for other crypto libs planned (lighter libs) + * Fixed stupid select() bug. + * Libssh now compiles and links with openssl 0.9.6 + * RSA pubkey authentication code now works ! + +version 0.0.3 (released 2003-09-15) + * Added install target in makefile + * Some cleanup in headers files and source code + * Change default banner and project name to libssh. + * New file auth.c to support more and more authentication ways + * Bugfix(read offbyone) in send_kex + * A base64 parser. don't read the source, it's awful. pure 0xbadc0de. + * Changed the client filename to "ssh". logic isn't it ? + * Dss publickey authentication ! still need to wait for the rsa one + * Bugfix in packet.c + * New misc.c contains misc functions + +version 0.0.2 (released 2003-09-03) + * Initial release. + * Client supports both ssh and dss hostkey verification, but doesn't compare them to openssh's files. (~/.ssh/known_hosts) + * The only supported authentication method is password. + * Compiles on linux and openbsd. freebsd and netbsd should work, too + * Lot of work which hasn't been discussed here. diff --git a/src/libs/libssh-0.12.2/CMakeLists.txt b/src/libs/libssh-0.12.2/CMakeLists.txt new file mode 100644 index 000000000000..c242461d9487 --- /dev/null +++ b/src/libs/libssh-0.12.2/CMakeLists.txt @@ -0,0 +1,284 @@ +cmake_minimum_required(VERSION 3.14.0) + +# Specify search path for CMake modules to be loaded by include() +# and find_package() +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") + +# Add defaults for cmake +# Those need to be set before the project() call. +include(DefineCMakeDefaults) +include(DefineCompilerFlags) + +project(libssh VERSION 0.12.2 LANGUAGES C) + +# global needed variable +set(APPLICATION_NAME ${PROJECT_NAME}) + +# SOVERSION scheme: CURRENT.AGE.REVISION +# If there was an incompatible interface change: +# Increment CURRENT. Set AGE and REVISION to 0 +# If there was a compatible interface change: +# Increment AGE. Set REVISION to 0 +# If the source code was changed, but there were no interface changes: +# Increment REVISION. +set(LIBRARY_VERSION "4.12.0") +set(LIBRARY_SOVERSION "4") + +# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked + +# add definitions +include(DefinePlatformDefaults) +include(DefineOptions.cmake) +include(CPackConfig.cmake) +include(GNUInstallDirs) + +include(CompilerChecks.cmake) + +# disallow in-source build +include(MacroEnsureOutOfSourceBuild) +macro_ensure_out_of_source_build("${PROJECT_NAME} requires an out of source build. Please create a separate build directory and run 'cmake /path/to/${PROJECT_NAME} [options]' there.") + +# Copy library files to a lib sub-directory +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + +set(LIBSSSH_PC_REQUIRES_PRIVATE "") + +# search for libraries +if (WITH_ZLIB) + find_package(ZLIB REQUIRED) +endif (WITH_ZLIB) + +if (WITH_GCRYPT) + find_package(GCrypt 1.5.0 REQUIRED) + message(WARNING "libgcrypt cryptographic backend is deprecated and will be removed in future releases.") +elseif(WITH_MBEDTLS) + find_package(MbedTLS REQUIRED) +else() + find_package(OpenSSL 1.1.1 REQUIRED) +endif() + +if (UNIT_TESTING) + find_package(CMocka REQUIRED) +endif () + +# Find out if we have threading available +set(CMAKE_THREAD_PREFER_PTHREADS ON) +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads) + +if (WITH_GSSAPI) + find_package(GSSAPI) + list(APPEND LIBSSH_PC_REQUIRES_PRIVATE ${GSSAPI_PC_REQUIRES}) +endif (WITH_GSSAPI) + +if (WITH_NACL) + find_package(NaCl) + if (NOT NACL_FOUND) + set(WITH_NACL OFF) + endif (NOT NACL_FOUND) +endif (WITH_NACL) + +if (WITH_FIDO2) + find_package(libfido2) + if (LIBFIDO2_FOUND) + set(HAVE_LIBFIDO2 ON) + else (LIBFIDO2_FOUND) + set(HAVE_LIBFIDO2 OFF) + message(WARNING "libfido2 was not found. Internal support for interacting with FIDO2/U2F devices using the USB HID protocol will not be available.") + endif (LIBFIDO2_FOUND) +endif (WITH_FIDO2) + +# Disable symbol versioning in non UNIX platforms +if (UNIX) + find_package(ABIMap 0.4.0) +else (UNIX) + set(WITH_SYMBOL_VERSIONING OFF) +endif (UNIX) + +# config.h checks +include(ConfigureChecks.cmake) +configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +if (NOT HAVE_ARGP_PARSE) + find_package(Argp) +endif (NOT HAVE_ARGP_PARSE) + +# check subdirectories +add_subdirectory(doc) +add_subdirectory(include) +add_subdirectory(src) + +# pkg-config file +if (UNIX OR MINGW) +configure_file(libssh.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc @ONLY) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/pkgconfig + COMPONENT + pkgconfig +) +endif (UNIX OR MINGW) + +# CMake config files +include(CMakePackageConfigHelpers) + +set(LIBSSH_LIBRARY_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}ssh${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# libssh-config-version.cmake +write_basic_package_version_file(libssh-config-version.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + COMPONENT + devel) + +if (WITH_EXAMPLES) + add_subdirectory(examples) +endif (WITH_EXAMPLES) + +if (UNIT_TESTING) + include(AddCMockaTest) + add_subdirectory(tests) +endif (UNIT_TESTING) + +### SOURCE PACKAGE +if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) + # Get the current ABI version from source + get_filename_component(current_abi_path + "${CMAKE_SOURCE_DIR}/src/ABI/current" + ABSOLUTE) + + # Check if the ABI version should be updated + file(READ ${current_abi_path} CURRENT_ABI_CONTENT) + string(STRIP "${CURRENT_ABI_CONTENT}" CURRENT_ABI_VERSION) + + if (LIBRARY_VERSION VERSION_GREATER CURRENT_ABI_VERSION) + set(UPDATE_ABI TRUE) + endif () + + if (UPDATE_ABI) + message(STATUS "Library version bumped to ${LIBRARY_VERSION}: Updating ABI") + + # Get the list of header files + get_file_list(${PROJECT_NAME}_header_list + DIRECTORIES "${CMAKE_SOURCE_DIR}/include/libssh" + FILES_PATTERNS "*.h") + + # Extract the symbols marked as "LIBSSH_API" from the header files + extract_symbols(${PROJECT_NAME}.symbols + HEADERS_LIST ${PROJECT_NAME}_header_list + FILTER_PATTERN "LIBSSH_API" + COPY_TO "${CMAKE_SOURCE_DIR}/src/ABI/${PROJECT_NAME}-${LIBRARY_VERSION}.symbols") + + if (WITH_ABI_BREAK) + set(ALLOW_ABI_BREAK "BREAK_ABI") + endif() + + if (WITH_FINAL) + set(FINAL "FINAL") + endif() + + # Target we can depend on in 'make dist' + set(_SYMBOL_TARGET "${PROJECT_NAME}.map") + + # Set the path to the current map file + set(MAP_PATH "${CMAKE_SOURCE_DIR}/src/${_SYMBOL_TARGET}") + + # Generate the symbol version map file + generate_map_file(${_SYMBOL_TARGET} + SYMBOLS ${PROJECT_NAME}.symbols + RELEASE_NAME_VERSION ${PROJECT_NAME}_${LIBRARY_VERSION} + CURRENT_MAP ${MAP_PATH} + COPY_TO ${MAP_PATH} + ${FINAL} + ${ALLOW_ABI_BREAK}) + + # Write the current version to the source + file(WRITE ${current_abi_path} ${LIBRARY_VERSION}) + endif(UPDATE_ABI) +endif (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) + +# Coverage +if (WITH_COVERAGE) + ENABLE_LANGUAGE(CXX) + include(CodeCoverage) + setup_target_for_coverage_lcov( + NAME "coverage" + EXECUTABLE make test + DEPENDENCIES ssh tests) + set(GCOVR_ADDITIONAL_ARGS --xml-pretty --exclude-unreachable-branches --print-summary --gcov-ignore-parse-errors) + setup_target_for_coverage_gcovr_xml( + NAME "coverage_xml" + EXECUTABLE make test + DEPENDENCIES ssh tests) +endif (WITH_COVERAGE) + +add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source DEPENDS ${_SYMBOL_TARGET} VERBATIM) + +get_directory_property(hasParent PARENT_DIRECTORY) +if(NOT(hasParent)) + # Link compile database for clangd if we are the master project + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + "${CMAKE_BINARY_DIR}/compile_commands.json" + "${CMAKE_SOURCE_DIR}/compile_commands.json") +endif() + +message(STATUS "********************************************") +message(STATUS "********** ${PROJECT_NAME} build options : **********") + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "Coverage: ${WITH_COVERAGE}") +message(STATUS "zlib support: ${WITH_ZLIB}") +message(STATUS "libgcrypt support: ${WITH_GCRYPT}") +message(STATUS "libmbedTLS support: ${WITH_MBEDTLS}") +message(STATUS "libnacl support: ${WITH_NACL}") +message(STATUS "SFTP support: ${WITH_SFTP}") +message(STATUS "Server support : ${WITH_SERVER}") +message(STATUS "GSSAPI support : ${WITH_GSSAPI}") +message(STATUS "GEX support : ${WITH_GEX}") +message(STATUS "Support insecure none cipher and MAC : ${WITH_INSECURE_NONE}") +message(STATUS "Support exec : ${WITH_EXEC}") +message(STATUS "Pcap debugging support : ${WITH_PCAP}") +message(STATUS "Build shared library: ${BUILD_SHARED_LIBS}") +message(STATUS "Unit testing: ${UNIT_TESTING}") +message(STATUS "Client code testing: ${CLIENT_TESTING}") +message(STATUS "Blowfish cipher support: ${HAVE_BLOWFISH}") +message(STATUS "PKCS #11 URI support: ${WITH_PKCS11_URI}") +message(STATUS "With PKCS #11 provider support: ${WITH_PKCS11_PROVIDER}") +message(STATUS "With FIDO2/U2F support: ${WITH_FIDO2}") +if (WITH_FIDO2) + message(STATUS "With libfido2 (internal usb-hid support): ${HAVE_LIBFIDO2}") +endif (WITH_FIDO2) +set(_SERVER_TESTING OFF) +if (WITH_SERVER) + set(_SERVER_TESTING ${SERVER_TESTING}) +endif() +message(STATUS "Server code testing: ${_SERVER_TESTING}") +if (WITH_INTERNAL_DOC) + message(STATUS "Internal documentation generation") +else (WITH_INTERNAL_DOC) + message(STATUS "Public API documentation generation") +endif (WITH_INTERNAL_DOC) +message(STATUS "Benchmarks: ${WITH_BENCHMARKS}") +message(STATUS "Symbol versioning: ${WITH_SYMBOL_VERSIONING}") +message(STATUS "Allow ABI break: ${WITH_ABI_BREAK}") +message(STATUS "Release is final: ${WITH_FINAL}") +if (WITH_HERMETIC_USR) + message(STATUS "User global client config: ${USR_GLOBAL_CLIENT_CONFIG}") +endif () +message(STATUS "Global client config: ${GLOBAL_CLIENT_CONFIG}") +if (WITH_SERVER) + if (WITH_HERMETIC_USR) + message(STATUS "User global bind config: ${USR_GLOBAL_BIND_CONFIG}") + endif () + message(STATUS "Global bind config: ${GLOBAL_BIND_CONFIG}") +endif() +message(STATUS "********************************************") + diff --git a/src/libs/libssh-0.12.2/CONTRIBUTING.md b/src/libs/libssh-0.12.2/CONTRIBUTING.md new file mode 100644 index 000000000000..6c606b9c6d1c --- /dev/null +++ b/src/libs/libssh-0.12.2/CONTRIBUTING.md @@ -0,0 +1,600 @@ +# How to contribute a patch to libssh + +Please checkout the libssh source code using git. + +For contributions we prefer Merge Requests on Gitlab: + +https://gitlab.com/libssh/libssh-mirror/ + +This way you get continuous integration which runs the complete libssh +testsuite for you. + +For larger code changes, breaking the changes up into a set of simple +patches, each of which does a single thing, are much easier to review. +Patch sets like that will most likely have an easier time being merged +into the libssh code than large single patches that make lots of +changes in one large diff. + +Also bugfixes and new features should be covered by tests. We use the cmocka +and cwrap framework for our testing and you can simply run it locally by +calling `make test`. + +## Ownership of the contributed code + +libssh is a project with distributed copyright ownership, which means +we prefer the copyright on parts of libssh to be held by individuals +rather than corporations if possible. There are historical legal +reasons for this, but one of the best ways to explain it is that it's +much easier to work with individuals who have ownership than corporate +legal departments if we ever need to make reasonable compromises with +people using and working with libssh. + +We track the ownership of every part of libssh via https://git.libssh.org, +our source code control system, so we know the provenance of every piece +of code that is committed to libssh. + +So if possible, if you're doing libssh changes on behalf of a company +who normally owns all the work you do please get them to assign +personal copyright ownership of your changes to you as an individual, +that makes things very easy for us to work with and avoids bringing +corporate legal departments into the picture. + +If you can't do this we can still accept patches from you owned by +your employer under a standard employment contract with corporate +copyright ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux +Kernel community, so it should be very easy to get a sign off from +your corporate legal department. The only changes we've made are to +accommodate the license we use, which is LGPLv2 (or later) whereas the +Linux kernel uses GPLv2. + +The process is called signing. + +## How to sign your work + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + + +``` +libssh Developer's Certificate of Origin. Version 1.0 + + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project under the terms of the + GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of + the License, or (at the option of the project) any later version. + + https://www.gnu.org/licenses/lgpl-2.1.html +``` + +We will maintain a copy of that email as a record that you have the +rights to contribute code to libssh under the required licenses whilst +working for the company where the email came from. + +Then when sending in a patch via the normal mechanisms described +above, add a line that states: + + Signed-off-by: Random J Developer + +using your real name and the email address you sent the original email +you used to send the libssh Developer's Certificate of Origin to us +(sorry, no pseudonyms or anonymous contributions.) + +That's it! Such code can then quite happily contain changes that have +copyright messages such as: + + (c) Example Corporation. + +and can be merged into the libssh codebase in the same way as patches +from any other individual. You don't need to send in a copy of the +libssh Developer's Certificate of Origin for each patch, or inside each +patch. Just the sign-off message is all that is required once we've +received the initial email. + +## Continuous Integration + +Contributing patches through Merge Request workflow on Gitlab allows us to run +various checks on various configuration as part of Gitlab CI. Unfortunately, +some pipelines are slower (as they involve building dependencies) so the default +timeout of 1 hour needs to be extended at least to 2 hours. This can be done in +project settings of your libssh fork: + +https://docs.gitlab.com/ee/ci/pipelines/settings.html#set-a-limit-for-how-long-jobs-can-run + +Otherwise you will encounter errors like these, usually on visualstudio builds: + +``` +ERROR: Job failed: execution took longer than 1h0m0s seconds +The script exceeded the maximum execution time set for the job +``` + +Note, that the built dependencies are cached so after successful build in your +namespace, the rebuilds should be much faster. + +## Running GitLab CI locally (optional helper) + +For contributors working on CI, build system changes, or adding new CI jobs, it can be useful to run GitLab CI pipelines locally before pushing. + +libssh provides a small helper script based on `gitlab-ci-local` that can: + +- List all jobs defined in `.gitlab-ci.yml` +- Run a specific job or the full pipeline locally +- Automatically pick up new jobs when they are added to the CI configuration +- Optionally clean up CI Docker images after execution + +### Requirements + +- Docker (daemon running) +- git +- gitlab-ci-local + https://github.com/firecow/gitlab-ci-local + +### Usage + +```bash +./.gitlab-ci/local-ci.sh --list +./.gitlab-ci/local-ci.sh --run fedora/libressl/x86_64 +./.gitlab-ci/local-ci.sh --all +./.gitlab-ci/local-ci.sh --run fedora/libressl/x86_64 --clean +``` + +# Coding conventions in the libssh tree + +## Quick Start + +Coding style guidelines are about reducing the number of unnecessary +reformatting patches and making things easier for developers to work together. + +You don't have to like them or even agree with them, but once put in place we +all have to abide by them (or vote to change them). However, coding style +should never outweigh coding itself and so the guidelines described here are +hopefully easy enough to follow as they are very common and supported by tools +and editors. + +The basic style for C code, is the Linux kernel coding style (See +Documentation/CodingStyle in the kernel source tree). This closely matches what +libssh developers use already anyways, with a few exceptions as mentioned +below. + +But to save you the trouble of reading the Linux kernel style guide, here +are the highlights. + +* Maximum Line Width is 80 Characters + The reason is not about people with low-res screens but rather sticking + to 80 columns prevents you from easily nesting more than one level of + if statements or other code blocks. + +* Use 4 Spaces to Indent + +* No Trailing Whitespace + Clean up your files before committing. + +* Follow the K&R guidelines. We won't go through all of them here. Do you + have a copy of "The C Programming Language" anyways right? + + +## Editor Hints + +### Emacs + +Add the follow to your $HOME/.emacs file: + + (add-hook 'c-mode-hook + (lambda () + (c-set-style "linux") + (c-toggle-auto-state))) + + +## Neovim/VIM + +For the basic vi editor included with all variants of \*nix, add the +following to ~/.config/nvim/init.rc or ~/.vimrc: + + set ts=4 sw=4 et cindent + +You can use the Vim gitmodline plugin to store this in the git config: + +https://git.cryptomilk.org/projects/vim-gitmodeline.git/ + +For Vim, the following settings in $HOME/.vimrc will also deal with +displaying trailing whitespace: + + if has("syntax") && (&t_Co > 2 || has("gui_running")) + syntax on + function! ActivateInvisibleCharIndicator() + syntax match TrailingSpace "[ \t]\+$" display containedin=ALL + highlight TrailingSpace ctermbg=Red + endf + autocmd BufNewFile,BufRead * call ActivateInvisibleCharIndicator() + endif + " Show tabs, trailing whitespace, and continued lines visually + set list listchars=tab:»·,trail:·,extends:… + + " highlight overly long lines same as TODOs. + set textwidth=80 + autocmd BufNewFile,BufRead *.c,*.h exec 'match Todo /\%>' . &textwidth . 'v.\+/' + + +## FAQ & Statement Reference + +### Comments + +Comments should always use the standard C syntax. C++ style comments are not +currently allowed. + +The lines before a comment should be empty. If the comment directly belongs to +the following code, there should be no empty line after the comment, except if +the comment contains a summary of multiple following code blocks. + +This is good: + + ... + int i; + + /* + * This is a multi line comment, + * which explains the logical steps we have to do: + * + * 1. We need to set i=5, because... + * 2. We need to call complex_fn1 + */ + + /* This is a one line comment about i = 5. */ + i = 5; + + /* + * This is a multi line comment, + * explaining the call to complex_fn1() + */ + ret = complex_fn1(); + if (ret != 0) { + ... + + /** + * @brief This is a doxygen comment. + * + * This is a more detailed explanation of + * this simple function. + * + * @param[in] param1 The parameter value of the function. + * + * @param[out] result1 The result value of the function. + * + * @return 0 on success and -1 on error. + */ + int example(int param1, int *result1); + +This is bad: + + ... + int i; + /* + * This is a multi line comment, + * which explains the logical steps we have to do: + * + * 1. We need to set i=5, because... + * 2. We need to call complex_fn1 + */ + /* This is a one line comment about i = 5. */ + i = 5; + /* + * This is a multi line comment, + * explaining the call to complex_fn1() + */ + ret = complex_fn1(); + if (ret != 0) { + ... + + /*This is a one line comment.*/ + + /* This is a multi line comment, + with some more words...*/ + + /* + * This is a multi line comment, + * with some more words...*/ + +### Indentation & Whitespace & 80 columns + +To avoid confusion, indentations have to be 4 spaces. Do not use tabs!. When +wrapping parameters for function calls, align the parameter list with the first +parameter on the previous line. For example, + + var1 = foo(arg1, + arg2, + arg3); + +The previous example is intended to illustrate alignment of function +parameters across lines and not as encourage for gratuitous line +splitting. Never split a line before columns 70 - 79 unless you +have a really good reason. Be smart about formatting. + + +### If, switch, & Code blocks + +Always follow an 'if' keyword with a space but don't include additional +spaces following or preceding the parentheses in the conditional. +This is good: + + if (x == 1) + +This is bad: + + if ( x == 1 ) + +or + + if (x==1) + +Yes we have a lot of code that uses the second and third form and we are trying +to clean it up without being overly intrusive. + +Note that this is a rule about parentheses following keywords and not +functions. Don't insert a space between the name and left parentheses when +invoking functions. + +Braces for code blocks used by for, if, switch, while, do..while, etc. should +begin on the same line as the statement keyword and end on a line of their own. +You should always include braces, even if the block only contains one +statement. **NOTE**: Functions are different and the beginning left brace should +be located in the first column on the next line. + +If the beginning statement has to be broken across lines due to length, the +beginning brace should be on a line of its own. + +The exception to the ending rule is when the closing brace is followed by +another language keyword such as else or the closing while in a do..while loop. + +Good examples: + + if (x == 1) { + printf("good\n"); + } + + for (x = 1; x < 10; x++) { + print("%d\n", x); + } + + for (really_really_really_really_long_var_name = 0; + really_really_really_really_long_var_name < 10; + really_really_really_really_long_var_name++) + { + print("%d\n", really_really_really_really_long_var_name); + } + + do { + printf("also good\n"); + } while (1); + +Bad examples: + + while (1) + { + print("I'm in a loop!\n"); } + + for (x=1; + x<10; + x++) + { + print("no good\n"); + } + + if (i < 10) + print("I should be in braces.\n"); + + +### Goto + +While many people have been academically taught that "goto"s are fundamentally +evil, they can greatly enhance readability and reduce memory leaks when used as +the single exit point from a function. But in no libssh world what so ever is a +goto outside of a function or block of code a good idea. + +Good Examples: + + int function foo(int y) + { + int *z = NULL; + int rc = 0; + + if (y < 10) { + z = malloc(sizeof(int)*y); + if (z == NULL) { + rc = 1; + goto done; + } + } + + print("Allocated %d elements.\n", y); + + done: + if (z != NULL) { + free(z); + } + + return rc; + } + +### Initialize pointers + +All pointer variables **MUST** be initialized to `NULL`. History has +demonstrated that uninitialized pointer variables have lead to various +bugs and security issues. + +Pointers **MUST** be initialized even if the assignment directly follows +the declaration, like pointer2 in the example below, because the +instructions sequence may change over time. + +Good Example: + + char *pointer1 = NULL; + char *pointer2 = NULL; + + pointer2 = some_func2(); + + ... + + pointer1 = some_func1(); + +### Typedefs + +libssh tries to avoid `typedef struct { .. } x_t;` so we do always try to use +`struct x { .. };`. We know there are still such typedefs in the code, but for +new code, please don't do that anymore. + +### Make use of helper variables + +Please try to avoid passing function calls as function parameters in new code. +This makes the code much easier to read and it's also easier to use the "step" +command within gdb. + +Good Example: + + char *name; + + name = get_some_name(); + if (name == NULL) { + ... + } + + rc = some_function_my_name(name); + ... + + +Bad Example: + + rc = some_function_my_name(get_some_name()); + ... + +Please try to avoid passing function return values to if- or while-conditions. +The reason for this is better handling of code under a debugger. + +Good example: + + x = malloc(sizeof(short) * 10); + if (x == NULL) { + fprintf(stderr, "Unable to alloc memory!\n"); + } + +Bad example: + + if ((x = malloc(sizeof(short)*10)) == NULL ) { + fprintf(stderr, "Unable to alloc memory!\n"); + } + +There are exceptions to this rule. One example is walking a data structure in +an iterator style: + + while ((opt = poptGetNextOpt(pc)) != -1) { + ... do something with opt ... + } + +But in general, please try to avoid this pattern. + + +### Control-Flow changing macros + +Macros like `STATUS_NOT_OK_RETURN` that change control flow (return/goto/etc) +from within the macro are considered bad, because they look like function calls +that never change control flow. Please do not introduce them. + +### Switch/case indentation + +The `case` should not be indented to avoid wasting too much horizontal space. +When the case block contains local variables that need to be wrapped in braces, +they should not be indented again either. + +Good example: + + switch (x) { + case 0: + do_stuff(); + break; + case 1: { + int y; + do_stuff(); + break; + } + default: + do_other_stuff(); + break; + } + +Bad example: + + switch (x) { + case 0: + do_stuff(); + break; + case 1: + { + int y; + do_stuff(); + break; + } + default: + do_other_stuff(); + break; + } + +## ABI Versioning and Symbol Management + +To maintain [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) stability +and ensure backward compatibility, libssh uses **symbol versioning** to track and manage +exported functions and variables. This allows libssh to introduce new symbols or modify +existing functions in an ABI-compatible way. + +When introducing a new symbol: + +1. Use the `LIBSSH_API` macro to mark the symbol as part of the public API. +2. If you have [abimap](https://github.com/ansasaki/abimap) installed, the new symbols are +automatically generated in the `src/libssh_dev.map` file in the **build** directory and used automatically for building the updated library. But, depending on the version of `abimap` under use, you may face linker errors like: `unable to find version dependency LIBSSH_4_9_0`. In this case, you need to manually replace the existing `src/libssh.map` file with the generated `libssh_dev.map` file to update the symbol versioning. +3. If you do not have abimap installed, the modified/added symbols must manually be added to the +`src/libssh.map` file. The symbols must be added in the following format (assuming that 4_10_0 is the latest released version): + +``` +LIBSSH_AFTER_4_10_0 +{ + global: + new_function; + new_variable; +} LIBSSH_4_10_0; +``` +4. After following either of the above steps, the library can be successfully built and +tested without any linker errors. + +5. When submitting the patch, make sure that any new symbols have been added to `libssh.map` as described in step 3, so that the new additions may not be excluded from the next release due to human error. + +Also, to maintain ABI compatibility, existing symbols must not be removed. Instead, they can +be marked as deprecated using the `LIBSSH_DEPRECATED` macro. This allows the symbol to be +removed in a future release without breaking the ABI. + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/src/libs/libssh-0.12.2/COPYING b/src/libs/libssh-0.12.2/COPYING new file mode 100644 index 000000000000..ff91d172e44f --- /dev/null +++ b/src/libs/libssh-0.12.2/COPYING @@ -0,0 +1,469 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + Linking with OpenSSL + + 17. In addition, as a special exception, we give permission to link the code +of its release of libssh with the OpenSSL project's "OpenSSL" library (or with +modified versions of it that use the same license as the "OpenSSL" library), +and distribute the linked executables. You must obey the GNU Lesser General +Public License in all respects for all of the code used other than "OpenSSL". +If you modify this file, you may extend this exception to your version of the +file, but you are not obligated to do so. If you do not wish to do so, delete +this exception statement from your version. + + END OF TERMS AND CONDITIONS diff --git a/src/libs/libssh-0.12.2/CPackConfig.cmake b/src/libs/libssh-0.12.2/CPackConfig.cmake new file mode 100644 index 000000000000..81ed6d0608eb --- /dev/null +++ b/src/libs/libssh-0.12.2/CPackConfig.cmake @@ -0,0 +1,44 @@ +### GENERAL SETTINGS +set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The SSH Library") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README") +set(CPACK_PACKAGE_VENDOR "The SSH Library Development Team") +set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING") + +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) + +# SOURCE GENERATOR +set(CPACK_SOURCE_GENERATOR "TXZ") +set(CPACK_SOURCE_IGNORE_FILES "~$;[.]swp$;/[.]bare/;/[.]git/;/[.]git;/[.]clangd/;/[.]cache/;.gitignore;/build*;/obj*;tags;cscope.*;compile_commands.json;.*\.patch") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") + +### NSIS INSTALLER +if (WIN32) + set(CPACK_GENERATOR "ZIP") + + ### nsis generator + find_package(NSIS) + if (NSIS_MAKE) + set(CPACK_GENERATOR "${CPACK_GENERATOR};NSIS") + set(CPACK_NSIS_DISPLAY_NAME "The SSH Library") + set(CPACK_NSIS_COMPRESSOR "/SOLID zlib") + set(CPACK_NSIS_MENU_LINKS "https://www.libssh.org/" "libssh homepage") + endif (NSIS_MAKE) +endif (WIN32) + +set(CPACK_PACKAGE_INSTALL_DIRECTORY "libssh") + +set(CPACK_PACKAGE_FILE_NAME ${APPLICATION_NAME}-${CPACK_PACKAGE_VERSION}) + +set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") +set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "C/C++ Headers") +set(CPACK_COMPONENT_LIBRARIES_DESCRIPTION + "Libraries used to build programs which use libssh") +set(CPACK_COMPONENT_HEADERS_DESCRIPTION + "C/C++ header files for use with libssh") +set(CPACK_COMPONENT_HEADERS_DEPENDS libraries) +set(CPACK_COMPONENT_LIBRARIES_GROUP "Development") +set(CPACK_COMPONENT_HEADERS_GROUP "Development") + +include(CPack) diff --git a/src/libs/libssh-0.12.2/CTestConfig.cmake b/src/libs/libssh-0.12.2/CTestConfig.cmake new file mode 100644 index 000000000000..7a1867287272 --- /dev/null +++ b/src/libs/libssh-0.12.2/CTestConfig.cmake @@ -0,0 +1,9 @@ +set(UPDATE_TYPE "true") + +set(CTEST_PROJECT_NAME "libssh") +set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") + +set(CTEST_DROP_METHOD "https") +set(CTEST_DROP_SITE "test.libssh.org") +set(CTEST_DROP_LOCATION "/submit.php?project=libssh") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/src/libs/libssh-0.12.2/CompilerChecks.cmake b/src/libs/libssh-0.12.2/CompilerChecks.cmake new file mode 100644 index 000000000000..e9890e2f954c --- /dev/null +++ b/src/libs/libssh-0.12.2/CompilerChecks.cmake @@ -0,0 +1,133 @@ +include(AddCCompilerFlag) +include(CheckCCompilerFlagSSP) + +if (UNIX) + # + # Check for -Werror turned on if possible + # + # This will prevent that compiler flags are detected incorrectly. + # + check_c_compiler_flag("-Werror" REQUIRED_FLAGS_WERROR) + if (REQUIRED_FLAGS_WERROR) + set(CMAKE_REQUIRED_FLAGS "-Werror") + + if (PICKY_DEVELOPER) + list(APPEND SUPPORTED_COMPILER_FLAGS "-Werror") + endif() + endif() + + add_c_compiler_flag("-Wpedantic" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wall" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wshadow" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wmissing-prototypes" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wcast-align" SUPPORTED_COMPILER_FLAGS) + #add_c_compiler_flag("-Wcast-qual" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=address" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wstrict-prototypes" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=strict-prototypes" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wwrite-strings" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=write-strings" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror-implicit-function-declaration" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wpointer-arith" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=pointer-arith" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wdeclaration-after-statement" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=declaration-after-statement" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wreturn-type" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=return-type" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wuninitialized" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=uninitialized" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wimplicit-fallthrough" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=strict-overflow" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wstrict-overflow=2" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wno-format-zero-length" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wmissing-field-initializers" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wsign-compare" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wold-style-definition" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=old-style-definition" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wimplicit-int" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=implicit-int" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wint-conversion" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=int-conversion" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=unused-variable" SUPPORTED_COMPILER_FLAGS) + + check_c_compiler_flag("-Wformat" REQUIRED_FLAGS_WFORMAT) + if (REQUIRED_FLAGS_WFORMAT) + list(APPEND SUPPORTED_COMPILER_FLAGS "-Wformat") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Wformat") + endif() + add_c_compiler_flag("-Wformat-security" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=format-security" SUPPORTED_COMPILER_FLAGS) + + # Allow zero for a variadic macro argument + string(TOLOWER "${CMAKE_C_COMPILER_ID}" _C_COMPILER_ID) + if ("${_C_COMPILER_ID}" STREQUAL "clang") + add_c_compiler_flag("-Wno-gnu-zero-variadic-macro-arguments" SUPPORTED_COMPILER_FLAGS) + endif() + + add_c_compiler_flag("-fno-common" SUPPORTED_COMPILER_FLAGS) + + if (CMAKE_BUILD_TYPE) + string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) + if (CMAKE_BUILD_TYPE_LOWER MATCHES (release|relwithdebinfo|minsizerel)) + add_c_compiler_flag("-Wp,-D_FORTIFY_SOURCE=2" SUPPORTED_COMPILER_FLAGS) + endif() + endif() + + check_c_compiler_flag_ssp("-fstack-protector-strong" WITH_STACK_PROTECTOR_STRONG) + if (WITH_STACK_PROTECTOR_STRONG) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-protector-strong") + # This is needed as Solaris has a separate libssp + if (SOLARIS) + list(APPEND SUPPORTED_LINKER_FLAGS "-fstack-protector-strong") + endif() + else (WITH_STACK_PROTECTOR_STRONG) + check_c_compiler_flag_ssp("-fstack-protector" WITH_STACK_PROTECTOR) + if (WITH_STACK_PROTECTOR) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-protector") + # This is needed as Solaris has a separate libssp + if (SOLARIS) + list(APPEND SUPPORTED_LINKER_FLAGS "-fstack-protector") + endif() + endif() + endif (WITH_STACK_PROTECTOR_STRONG) + + if (NOT WINDOWS AND NOT CYGWIN) + # apple m* chips do not support this option + if (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL arm64) + check_c_compiler_flag_ssp("-fstack-clash-protection" WITH_STACK_CLASH_PROTECTION) + if (WITH_STACK_CLASH_PROTECTION) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-clash-protection") + endif() + endif() + endif() + + if (PICKY_DEVELOPER) + add_c_compiler_flag("-Wno-error=deprecated-declarations" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wno-error=tautological-compare" SUPPORTED_COMPILER_FLAGS) + endif() + + add_c_compiler_flag("-Wno-deprecated-declarations" DEPRECATION_COMPILER_FLAGS) + + # Unset CMAKE_REQUIRED_FLAGS + unset(CMAKE_REQUIRED_FLAGS) +endif() + +if (MSVC) + add_c_compiler_flag("/D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("/D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("/D _CRT_NONSTDC_NO_WARNINGS=1" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("/D _CRT_SECURE_NO_WARNINGS=1" SUPPORTED_COMPILER_FLAGS) +endif() + +# This removes this annoying warning +# "warning: 'BN_CTX_free' is deprecated: first deprecated in OS X 10.7 [-Wdeprecated-declarations]" +if (OSX) + add_c_compiler_flag("-Wno-deprecated-declarations" SUPPORTED_COMPILER_FLAGS) +endif() + +set(DEFAULT_C_COMPILE_FLAGS ${SUPPORTED_COMPILER_FLAGS} CACHE INTERNAL "Default C Compiler Flags" FORCE) +set(DEFAULT_LINK_FLAGS ${SUPPORTED_LINKER_FLAGS} CACHE INTERNAL "Default C Linker Flags" FORCE) + +if (DEPRECATION_COMPILER_FLAGS) + set(DEFAULT_C_NO_DEPRECATION_FLAGS ${DEPRECATION_COMPILER_FLAGS} CACHE INTERNAL "Default no deprecation flags" FORCE) +endif() diff --git a/src/libs/libssh-0.12.2/ConfigureChecks.cmake b/src/libs/libssh-0.12.2/ConfigureChecks.cmake new file mode 100644 index 000000000000..4e32ea531b5f --- /dev/null +++ b/src/libs/libssh-0.12.2/ConfigureChecks.cmake @@ -0,0 +1,487 @@ +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckTypeSize) +include(CheckStructHasMember) +include(TestBigEndian) + +set(PACKAGE ${PROJECT_NAME}) +set(VERSION ${PROJECT_VERSION}) +set(SYSCONFDIR ${CMAKE_INSTALL_SYSCONFDIR}) + +set(BINARYDIR ${CMAKE_BINARY_DIR}) +set(SOURCEDIR ${CMAKE_SOURCE_DIR}) + +function(COMPILER_DUMPVERSION _OUTPUT_VERSION) + # Remove whitespaces from the argument. + # This is needed for CC="ccache gcc" cmake .. + string(REPLACE " " "" _C_COMPILER_ARG "${CMAKE_C_COMPILER_ARG1}") + + execute_process( + COMMAND + ${CMAKE_C_COMPILER} ${_C_COMPILER_ARG} -dumpversion + OUTPUT_VARIABLE _COMPILER_VERSION + ) + + string(REGEX REPLACE "([0-9])\\.([0-9])(\\.[0-9])?" "\\1\\2" + _COMPILER_VERSION "${_COMPILER_VERSION}") + + set(${_OUTPUT_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE) +endfunction() + +if(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) + compiler_dumpversion(GNUCC_VERSION) + if (NOT GNUCC_VERSION EQUAL 34) + set(CMAKE_REQUIRED_FLAGS "-fvisibility=hidden") + check_c_source_compiles( +"void __attribute__((visibility(\"default\"))) test() {} +int main(void){ return 0; } +" WITH_VISIBILITY_HIDDEN) + unset(CMAKE_REQUIRED_FLAGS) + endif (NOT GNUCC_VERSION EQUAL 34) +endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) + +# HEADER FILES +check_function_exists(argp_parse HAVE_ARGP_PARSE) + +set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${ARGP_INCLUDE_DIR}) +check_include_file(argp.h HAVE_ARGP_H) +unset(CMAKE_REQUIRED_INCLUDES) + +check_include_file(pty.h HAVE_PTY_H) +check_include_file(utmp.h HAVE_UTMP_H) +check_include_file(termios.h HAVE_TERMIOS_H) +check_include_file(unistd.h HAVE_UNISTD_H) +check_include_file(stdint.h HAVE_STDINT_H) +check_include_file(util.h HAVE_UTIL_H) +check_include_file(libutil.h HAVE_LIBUTIL_H) +check_include_file(sys/time.h HAVE_SYS_TIME_H) +check_include_file(sys/utime.h HAVE_SYS_UTIME_H) +check_include_file(sys/param.h HAVE_SYS_PARAM_H) +check_include_file(arpa/inet.h HAVE_ARPA_INET_H) +check_include_file(byteswap.h HAVE_BYTESWAP_H) +check_include_file(glob.h HAVE_GLOB_H) +check_include_file(valgrind/valgrind.h HAVE_VALGRIND_VALGRIND_H) +check_include_file(ifaddrs.h HAVE_IFADDRS_H) + +if (WIN32) + check_include_file(io.h HAVE_IO_H) + + check_include_files("winsock2.h;ws2tcpip.h;wspiapi.h" HAVE_WSPIAPI_H) + if (NOT HAVE_WSPIAPI_H) + message(STATUS "WARNING: Without wspiapi.h, this build will only work on Windows XP and newer versions") + endif (NOT HAVE_WSPIAPI_H) + check_include_files("winsock2.h;ws2tcpip.h" HAVE_WS2TCPIP_H) +endif (WIN32) + +if (OPENSSL_FOUND) + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES OpenSSL::Crypto) + + check_include_file(openssl/des.h HAVE_OPENSSL_DES_H) + if (NOT HAVE_OPENSSL_DES_H) + message(FATAL_ERROR "Could not detect openssl/des.h") + endif() + + check_include_file(openssl/aes.h HAVE_OPENSSL_AES_H) + if (NOT HAVE_OPENSSL_AES_H) + message(FATAL_ERROR "Could not detect openssl/aes.h") + endif() + + if (WITH_BLOWFISH_CIPHER) + check_include_file(openssl/blowfish.h HAVE_BLOWFISH) + endif() + + check_include_file(openssl/ecdh.h HAVE_OPENSSL_ECDH_H) + check_include_file(openssl/ec.h HAVE_OPENSSL_EC_H) + check_include_file(openssl/ecdsa.h HAVE_OPENSSL_ECDSA_H) + + check_function_exists(EVP_KDF_CTX_new_id HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID) + check_function_exists(EVP_KDF_CTX_new HAVE_OPENSSL_EVP_KDF_CTX_NEW) + check_function_exists(FIPS_mode HAVE_OPENSSL_FIPS_MODE) + check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES) + check_function_exists(EVP_chacha20 HAVE_OPENSSL_EVP_CHACHA20) + + # Check for ML-KEM availability (OpenSSL 3.5+) + if (OPENSSL_VERSION VERSION_GREATER_EQUAL "3.5.0") + set(HAVE_OPENSSL_MLKEM 1) + set(HAVE_MLKEM1024 1) + endif () + + unset(CMAKE_REQUIRED_INCLUDES) + unset(CMAKE_REQUIRED_LIBRARIES) +endif() + +if (CMAKE_HAVE_PTHREAD_H) + set(HAVE_PTHREAD_H 1) +endif (CMAKE_HAVE_PTHREAD_H) + +if (NOT WITH_GCRYPT AND NOT WITH_MBEDTLS) + if (HAVE_OPENSSL_EC_H AND HAVE_OPENSSL_ECDSA_H) + set(HAVE_OPENSSL_ECC 1) + endif (HAVE_OPENSSL_EC_H AND HAVE_OPENSSL_ECDSA_H) + + if (HAVE_OPENSSL_ECC) + set(HAVE_ECC 1) + endif (HAVE_OPENSSL_ECC) + + if (HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID OR HAVE_OPENSSL_EVP_KDF_CTX_NEW) + set(HAVE_OPENSSL_EVP_KDF_CTX 1) + endif (HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID OR HAVE_OPENSSL_EVP_KDF_CTX_NEW) + +endif () + +# FUNCTIONS + +check_function_exists(isblank HAVE_ISBLANK) +check_function_exists(strncpy HAVE_STRNCPY) +check_function_exists(strndup HAVE_STRNDUP) +check_function_exists(strtoull HAVE_STRTOULL) +check_function_exists(explicit_bzero HAVE_EXPLICIT_BZERO) +check_function_exists(memset_explicit HAVE_MEMSET_EXPLICIT) +check_function_exists(memset_s HAVE_MEMSET_S) + +if (HAVE_GLOB_H) + check_struct_has_member(glob_t gl_flags glob.h HAVE_GLOB_GL_FLAGS_MEMBER) + check_function_exists(glob HAVE_GLOB) +endif (HAVE_GLOB_H) + +if (NOT WIN32) + check_function_exists(vsnprintf HAVE_VSNPRINTF) + check_function_exists(snprintf HAVE_SNPRINTF) +endif (NOT WIN32) + +if (WIN32) + check_symbol_exists(vsnprintf "stdio.h" HAVE_VSNPRINTF) + check_symbol_exists(snprintf "stdio.h" HAVE_SNPRINTF) + + check_symbol_exists(_vsnprintf_s "stdio.h" HAVE__VSNPRINTF_S) + check_symbol_exists(_vsnprintf "stdio.h" HAVE__VSNPRINTF) + check_symbol_exists(_snprintf "stdio.h" HAVE__SNPRINTF) + check_symbol_exists(_snprintf_s "stdio.h" HAVE__SNPRINTF_S) + + if (HAVE_WSPIAPI_H OR HAVE_WS2TCPIP_H) + check_symbol_exists(ntohll winsock2.h HAVE_NTOHLL) + check_symbol_exists(htonll winsock2.h HAVE_HTONLL) + + set(CMAKE_REQUIRED_LIBRARIES ws2_32) + check_symbol_exists(select "winsock2.h;ws2tcpip.h" HAVE_SELECT) + check_symbol_exists(poll "winsock2.h;ws2tcpip.h" HAVE_SELECT) + # The getaddrinfo function is defined to the WspiapiGetAddrInfo inline function + check_symbol_exists(getaddrinfo "winsock2.h;ws2tcpip.h" HAVE_GETADDRINFO) + unset(CMAKE_REQUIRED_LIBRARIES) + endif (HAVE_WSPIAPI_H OR HAVE_WS2TCPIP_H) + + check_function_exists(_strtoui64 HAVE__STRTOUI64) + + set(HAVE_SELECT TRUE) + + check_symbol_exists(SecureZeroMemory "windows.h" HAVE_SECURE_ZERO_MEMORY) +else (WIN32) + check_function_exists(poll HAVE_POLL) + check_function_exists(select HAVE_SELECT) + check_function_exists(getaddrinfo HAVE_GETADDRINFO) + + check_symbol_exists(ntohll arpa/inet.h HAVE_NTOHLL) + check_symbol_exists(htonll arpa/inet.h HAVE_HTONLL) +endif (WIN32) + + +if (UNIX) + if (NOT LINUX) + # libsocket (Solaris) + check_library_exists(socket getaddrinfo "" HAVE_LIBSOCKET) + if (HAVE_LIBSOCKET) + set(HAVE_GETADDRINFO TRUE) + set(_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} socket) + endif (HAVE_LIBSOCKET) + + # libnsl/inet_pton (Solaris) + check_library_exists(nsl inet_pton "" HAVE_LIBNSL) + if (HAVE_LIBNSL) + set(_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} nsl) + endif (HAVE_LIBNSL) + + # librt + check_library_exists(rt nanosleep "" HAVE_LIBRT) + endif (NOT LINUX) + + check_library_exists(rt clock_gettime "" HAVE_CLOCK_GETTIME) + if (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + set(_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} rt) + endif (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + + check_library_exists(util forkpty "" HAVE_LIBUTIL) + check_function_exists(cfmakeraw HAVE_CFMAKERAW) + check_function_exists(__strtoull HAVE___STRTOULL) +endif (UNIX) + +set(LIBSSH_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} CACHE INTERNAL "libssh required system libraries") + +# LIBRARIES +if (OPENSSL_FOUND) + set(HAVE_LIBCRYPTO 1) +endif (OPENSSL_FOUND) + +if (GCRYPT_FOUND) + set(HAVE_LIBGCRYPT 1) + if (GCRYPT_VERSION VERSION_GREATER "1.4.6") + set(HAVE_GCRYPT_ECC 1) + set(HAVE_ECC 1) + endif (GCRYPT_VERSION VERSION_GREATER "1.4.6") + if (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") + set(HAVE_GCRYPT_CHACHA_POLY 1) + set(HAVE_GCRYPT_CURVE25519 1) + endif (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") + if (GCRYPT_VERSION VERSION_GREATER_EQUAL "1.10.1") + set(HAVE_GCRYPT_MLKEM 1) + set(HAVE_MLKEM1024 1) + endif () +endif (GCRYPT_FOUND) + +if (MBEDTLS_FOUND) + set(HAVE_LIBMBEDCRYPTO 1) + set(HAVE_ECC 1) + + set(CMAKE_REQUIRED_INCLUDES "${MBEDTLS_INCLUDE_DIR}/mbedtls") + check_include_file(chacha20.h HAVE_MBEDTLS_CHACHA20_H) + check_include_file(poly1305.h HAVE_MBEDTLS_POLY1305_H) + if (MBEDTLS_VERSION VERSION_LESS "3.0.0") + check_symbol_exists(MBEDTLS_ECP_DP_CURVE25519_ENABLED "config.h" HAVE_MBEDTLS_CURVE25519) + else() + check_symbol_exists(MBEDTLS_ECP_DP_CURVE25519_ENABLED "mbedtls_config.h" HAVE_MBEDTLS_CURVE25519) + endif() + + + if (WITH_BLOWFISH_CIPHER) + check_include_file(blowfish.h HAVE_BLOWFISH) + endif() + + unset(CMAKE_REQUIRED_INCLUDES) + +endif (MBEDTLS_FOUND) + +if (CMAKE_USE_PTHREADS_INIT) + set(HAVE_PTHREAD 1) +endif (CMAKE_USE_PTHREADS_INIT) + +if (UNIT_TESTING) + if (CMOCKA_FOUND) + set(CMAKE_REQUIRED_LIBRARIES ${CMOCKA_LIBRARIES}) + check_function_exists(cmocka_set_test_filter HAVE_CMOCKA_SET_TEST_FILTER) + unset(CMAKE_REQUIRED_LIBRARIES) + endif () +endif () + +# OPTIONS +check_c_source_compiles(" +__thread int tls; + +int main(void) { + return 0; +}" HAVE_GCC_THREAD_LOCAL_STORAGE) + +check_c_source_compiles(" +__declspec(thread) int tls; + +int main(void) { + return 0; +}" HAVE_MSC_THREAD_LOCAL_STORAGE) + +########################################################### +# For detecting attributes we need to treat warnings as +# errors +if (UNIX OR MINGW) + # Get warnings for attributes + check_c_compiler_flag("-Wattributes" REQUIRED_FLAGS_WERROR) + if (REQUIRED_FLAGS_WERROR) + string(APPEND CMAKE_REQUIRED_FLAGS "-Wattributes ") + endif() + + # Turn warnings into errors + check_c_compiler_flag("-Werror" REQUIRED_FLAGS_WERROR) + if (REQUIRED_FLAGS_WERROR) + string(APPEND CMAKE_REQUIRED_FLAGS "-Werror ") + endif() +endif () + +check_c_source_compiles(" +void test_constructor_attribute(void) __attribute__ ((constructor)); + +void test_constructor_attribute(void) +{ + return; +} + +int main(void) { + return 0; +}" HAVE_CONSTRUCTOR_ATTRIBUTE) + +check_c_source_compiles(" +void test_destructor_attribute(void) __attribute__ ((destructor)); + +void test_destructor_attribute(void) +{ + return; +} + +int main(void) { + return 0; +}" HAVE_DESTRUCTOR_ATTRIBUTE) + +check_c_source_compiles(" +#define FALL_THROUGH __attribute__((fallthrough)) + +int main(void) { + int i = 2; + + switch (i) { + case 0: + FALL_THROUGH; + case 1: + break; + default: + break; + } + + return 0; +}" HAVE_FALLTHROUGH_ATTRIBUTE) + +check_c_source_compiles(" +#define WEAK __attribute__((weak)) + +WEAK int sum(int a, int b) +{ + return a + b; +} + +int main(void) +{ + int i = sum(2, 2); + + (void)i; + + return 0; +}" HAVE_WEAK_ATTRIBUTE) + +if (NOT WIN32) + check_c_source_compiles(" + #define __unused __attribute__((unused)) + + static int do_nothing(int i __unused) + { + return 0; + } + + int main(void) + { + int i; + + i = do_nothing(5); + if (i > 5) { + return 1; + } + + return 0; + }" HAVE_UNUSED_ATTRIBUTE) +endif() + +check_c_source_compiles(" +#include + +int main(void) +{ + char buf[] = \"This is some content\"; + + memset(buf, '\\\\0', sizeof(buf)); __asm__ volatile(\"\" : : \"g\"(&buf) : \"memory\"); + + return 0; +}" HAVE_GCC_VOLATILE_MEMORY_PROTECTION) + +check_c_source_compiles(" +#include +int main(void) { + printf(\"%s\", __func__); + return 0; +}" HAVE_COMPILER__FUNC__) + +check_c_source_compiles(" +#include +int main(void) { + printf(\"%s\", __FUNCTION__); + return 0; +}" HAVE_COMPILER__FUNCTION__) + +# This is only available with OpenBSD's gcc implementation */ +if (OPENBSD) +check_c_source_compiles(" +#define ARRAY_LEN 16 +void test_attr(const unsigned char *k) + __attribute__((__bounded__(__minbytes__, 2, 16))); + +int main(void) { + return 0; +}" HAVE_GCC_BOUNDED_ATTRIBUTE) +endif(OPENBSD) + +# Stop treating warnings as errors +unset(CMAKE_REQUIRED_FLAGS) + +# Check for version script support +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/conftest.map" "VERS_1 { + global: sym; +}; +VERS_2 { + global: sym; +} VERS_1; +") + +set(CMAKE_REQUIRED_FLAGS "-Wl,--version-script=\"${CMAKE_CURRENT_BINARY_DIR}/conftest.map\"") +check_c_source_compiles("int main(void) { return 0; }" HAVE_LD_VERSION_SCRIPT) +unset(CMAKE_REQUIRED_FLAGS) +file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/conftest.map") + +if (WITH_DEBUG_CRYPTO) + set(DEBUG_CRYPTO 1) +endif (WITH_DEBUG_CRYPTO) + +if (WITH_DEBUG_PACKET) + set(DEBUG_PACKET 1) +endif (WITH_DEBUG_PACKET) + +if (WITH_DEBUG_CALLTRACE) + set(DEBUG_CALLTRACE 1) +endif (WITH_DEBUG_CALLTRACE) + +if (WITH_GSSAPI AND NOT GSSAPI_FOUND) + set(WITH_GSSAPI 0) +endif (WITH_GSSAPI AND NOT GSSAPI_FOUND) + +if (WITH_PKCS11_URI) + if (WITH_GCRYPT) + message(FATAL_ERROR "PKCS #11 is not supported for gcrypt.") + set(WITH_PKCS11_URI 0) + elseif (WITH_MBEDTLS) + message(FATAL_ERROR "PKCS #11 is not supported for mbedcrypto") + set(WITH_PKCS11_URI 0) + elseif (OPENSSL_FOUND AND OPENSSL_VERSION VERSION_GREATER_EQUAL "3.0.0") + find_library(PKCS11_PROVIDER + NAMES + pkcs11.so + PATH_SUFFIXES + ossl-modules + ) + if (NOT PKCS11_PROVIDER) + set(WITH_PKCS11_PROVIDER 0) + message(WARNING "Could not find pkcs11 provider! Falling back to engines") + message(WARNING "The support for engines is deprecated in OpenSSL and will be removed from libssh in the future releases.") + endif (NOT PKCS11_PROVIDER) + endif () +endif() + +# ENDIAN +if (NOT WIN32) + test_big_endian(WORDS_BIGENDIAN) +endif (NOT WIN32) diff --git a/src/libs/libssh-0.12.2/DefineOptions.cmake b/src/libs/libssh-0.12.2/DefineOptions.cmake new file mode 100644 index 000000000000..f788dec56408 --- /dev/null +++ b/src/libs/libssh-0.12.2/DefineOptions.cmake @@ -0,0 +1,109 @@ +option(WITH_GSSAPI "Build with GSSAPI support" ON) +option(WITH_ZLIB "Build with ZLIB support" ON) +option(WITH_SFTP "Build with SFTP support" ON) +option(WITH_SERVER "Build with SSH server support" ON) +option(WITH_DEBUG_CRYPTO "Build with crypto debug output" OFF) +option(WITH_DEBUG_PACKET "Build with packet debug output" OFF) +option(WITH_DEBUG_CALLTRACE "Build with calltrace debug output" ON) +option(WITH_GCRYPT "Compile against libgcrypt (deprecated)" OFF) +option(WITH_MBEDTLS "Compile against libmbedtls" OFF) +option(WITH_BLOWFISH_CIPHER "Compile with blowfish support" OFF) +option(WITH_PCAP "Compile with Pcap generation support" ON) +option(WITH_INTERNAL_DOC "Compile doxygen internal documentation" OFF) +option(BUILD_SHARED_LIBS "Build shared libraries" ON) +option(WITH_PKCS11_URI "Build with PKCS#11 URI support" OFF) +option(WITH_PKCS11_PROVIDER + "Use the PKCS#11 provider for accessing pkcs11 objects" OFF) +option(WITH_FIDO2 "Build with FIDO2/U2F support" OFF) +option(UNIT_TESTING "Build with unit tests" OFF) +option(CLIENT_TESTING "Build with client tests; requires openssh" OFF) +option(SERVER_TESTING "Build with server tests; requires openssh and dropbear" + OFF) +option( + GSSAPI_TESTING + "Build with GSSAPI tests; requires krb5-server,krb5-libs and krb5-workstation" + OFF) +option(WITH_BENCHMARKS + "Build benchmarks tools; enables unit testing and client tests" OFF) +option(WITH_EXAMPLES "Build examples" ON) +option(WITH_NACL "Build with libnacl (curve25519)" ON) +option(WITH_SYMBOL_VERSIONING "Build with symbol versioning" ON) +option(WITH_ABI_BREAK "Allow ABI break" OFF) +option(WITH_GEX "Enable DH Group exchange mechanisms" ON) +option( + WITH_INSECURE_NONE + "Enable insecure none cipher and MAC algorithms (not suitable for production!)" + OFF) +option( + WITH_EXEC + "Enable libssh to execute arbitrary commands from configuration files or options (match exec, proxy commands and OpenSSH-based proxy-jumps)." + ON) +option( + FUZZ_TESTING + "Build with fuzzer for the server and client (automatically enables none cipher!)" + OFF) +option(PICKY_DEVELOPER "Build with picky developer flags" OFF) +option(WITH_HERMETIC_USR "Build with support for hermetic /usr/" OFF) + +if (WITH_ZLIB) + set(WITH_LIBZ ON) +else (WITH_ZLIB) + set(WITH_LIBZ OFF) +endif (WITH_ZLIB) + +if (WITH_BENCHMARKS) + set(UNIT_TESTING ON) + set(CLIENT_TESTING ON) +endif () + +if (UNIT_TESTING + OR CLIENT_TESTING + OR SERVER_TESTING + OR GSSAPI_TESTING) + set(BUILD_STATIC_LIB ON) +endif () + +if (WITH_NACL) + set(WITH_NACL ON) +endif (WITH_NACL) + +if (WITH_ABI_BREAK) + set(WITH_SYMBOL_VERSIONING ON) +endif (WITH_ABI_BREAK) + +set(GLOBAL_CONF_DIR "/etc/ssh") +if (WIN32) + # Use PROGRAMDATA on Windows + if (DEFINED ENV{PROGRAMDATA}) + set(GLOBAL_CONF_DIR "$ENV{PROGRAMDATA}/ssh") + else () + set(GLOBAL_CONF_DIR "C:/ProgramData/ssh") + endif () + if (WITH_HERMETIC_USR) + set(USR_GLOBAL_CONF_DIR "/usr${GLOBAL_CONF_DIR}") + endif () +endif () + +if (NOT GLOBAL_BIND_CONFIG) + set(GLOBAL_BIND_CONFIG "${GLOBAL_CONF_DIR}/libssh_server_config") + + if (WITH_HERMETIC_USR) + set(USR_GLOBAL_BIND_CONFIG "/usr${GLOBAL_BIND_CONFIG}") + endif () +endif (NOT GLOBAL_BIND_CONFIG) + +if (NOT GLOBAL_CLIENT_CONFIG) + set(GLOBAL_CLIENT_CONFIG "${GLOBAL_CONF_DIR}/ssh_config") + + if (WITH_HERMETIC_USR) + set(USR_GLOBAL_CLIENT_CONFIG "/usr${GLOBAL_CLIENT_CONFIG}") + endif () +endif (NOT GLOBAL_CLIENT_CONFIG) + +if (FUZZ_TESTING) + set(WITH_INSECURE_NONE ON) +endif (FUZZ_TESTING) + +if (WIN32) + set(WITH_EXEC 0) +endif (WIN32) diff --git a/src/libs/libssh-0.12.2/INSTALL b/src/libs/libssh-0.12.2/INSTALL new file mode 100644 index 000000000000..a51914cc596e --- /dev/null +++ b/src/libs/libssh-0.12.2/INSTALL @@ -0,0 +1,125 @@ +# How to build from source + +## Requirements + +### Common requirements + +In order to build libssh, you need to install several components: + +- A C compiler +- [CMake](https://www.cmake.org) >= 3.12.0 +- [libz](https://www.zlib.net) >= 1.2 +- [openssl](https://www.openssl.org) >= 1.1.1 +or +- [gcrypt](https://www.gnu.org/directory/Security/libgcrypt.html) >= 1.5 +or +- [Mbed TLS](https://www.trustedfirmware.org/projects/mbed-tls/) + +optional: +- [cmocka](https://cmocka.org/) >= 1.1.0 +- [socket_wrapper](https://cwrap.org/) >= 1.1.5 +- [nss_wrapper](https://cwrap.org/) >= 1.1.2 +- [uid_wrapper](https://cwrap.org/) >= 1.2.0 +- [pam_wrapper](https://cwrap.org/) >= 1.0.1 +- [priv_wrapper](https://cwrap.org/) >= 1.0.0 + +Note that these version numbers are version we know works correctly. If you +build and run libssh successfully with an older version, please let us know. + +For Windows use vcpkg: + +https://github.com/Microsoft/vcpkg + +which you can use to install openssl and zlib. libssh itself is also part of +vcpkg! + +## Building +First, you need to configure the compilation, using CMake. Go inside the +`build` dir. Create it if it doesn't exist. + +GNU/Linux, MacOS X, MSYS/MinGW: + cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug .. + make + +On Windows you should choose a makefile generator with -G or use + + cmake-gui.exe .. + +To enable building tests use -DUNIT_TESTING=ON. For this, the +[cmocka](https://cmocka.org) dependency is required. + +To enable additional client tests against a local OpenSSH server, add the +compile option -DCLIENT_TESTING=ON. These tests require an OpenSSH +server package and some wrapper libraries (see optional requirements) to +be installed. + +If you're interested in server testing, then a OpenSSH client should be +installed on the system and if possible also dropbear. Once that is done +enable server support with -DWITH_SERVER=ON and enable testing of it with +-DSERVER_TESTING=ON. + +## Testing build + + make test + +### CMake standard options +Here is a list of the most interesting options provided out of the box by +CMake. + +- CMAKE_BUILD_TYPE: The type of build (can be Debug Release MinSizeRel + RelWithDebInfo) +- CMAKE_INSTALL_PREFIX: The prefix to use when running make install (Default + to /usr/local on GNU/Linux and MacOS X) +- CMAKE_C_COMPILER: The path to the C compiler +- CMAKE_CXX_COMPILER: The path to the C++ compiler + +### CMake options defined for libssh + +Options are defined in the following files: + +- DefineOptions.cmake + +They can be changed with the -D option: + +`cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DWITH_ZLIB=OFF ..` + +### Browsing/editing CMake options + +In addition to passing options on the command line, you can browse and edit +CMake options using `cmakesetup` (Windows), `cmake-gui` or `ccmake` (GNU/Linux +and MacOS X). + +- Go to the build dir +- On Windows: run `cmakesetup` +- On GNU/Linux and MacOS X: run `ccmake ..` + +### Useful Windows options: + +If you have installed OpenSSL or ZLIB in non standard directories, maybe you +want to set: + +OPENSSL_ROOT_DIR + +and + +ZLIB_ROOT_DIR + +## Installing + +If you want to install libssh after compilation run: + + make install + +## Running + +The libssh binary can be found in the `build/src` directory. +You can use `build/examples/samplessh` which is a sample client to +test libssh on UNIX. + +## About this document + +This document is written using [Markdown][] syntax, making it possible to +provide usable information in both plain text and HTML format. Whenever +modifying this document please use [Markdown][] syntax. + +[markdown]: https://www.daringfireball.net/projects/markdown diff --git a/src/libs/libssh-0.12.2/Makefile.kmk b/src/libs/libssh-0.12.2/Makefile.kmk new file mode 100644 index 000000000000..cb3f96fa4783 --- /dev/null +++ b/src/libs/libssh-0.12.2/Makefile.kmk @@ -0,0 +1,130 @@ +# $Id: Makefile.kmk 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ +## @file +# Sub-Makefile for libssh. +# + +# +# Copyright (C) 2020-2026 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../.. +include $(KBUILD_PATH)/subheader.kmk + +DLLS += VBoxLibSsh +VBoxLibSsh_TEMPLATE = VBoxR3DllNonPedantic +VBoxLibSsh_DEFS = LIBSSH_EXPORTS +VBoxLibSsh_SDKS = VBoxZlib VBoxOpenSsl +VBoxLibSsh_LIBS += $(LIB_RUNTIME) +# -wd4146: fe25519.c(90): warning C4146: unary minus operator applied to unsigned type, result still unsigned +# -wd4206: agent.c(595): warning C4206: nonstandard extension used: translation unit is empty +# -wd4701: pki_container_openssh.c(475) : warning C4701: potentially uninitialized local variable 'cipher' used +# -wd4204: pki_crypto.c(802): warning C4204: nonstandard extension used: non-constant aggregate initializer +VBoxLibSsh_CFLAGS.win += -wd4146 -wd4206 -wd4701 -wd4204 +VBoxLibSsh_LDFLAGS.darwin += \ + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxLibSsh.dylib +VBoxLibSsh_INCS = \ + include \ + src +VBoxLibSsh_SOURCES = \ + src/agent.c \ + src/auth.c \ + src/base64.c \ + src/bignum.c \ + src/buffer.c \ + src/callbacks.c \ + src/channels.c \ + src/client.c \ + src/config.c \ + src/connect.c \ + src/connector.c \ + src/crypto_common.c \ + src/curve25519.c \ + src/curve25519_crypto.c \ + src/dh.c \ + src/ecdh.c \ + src/error.c \ + src/getpass.c \ + src/getrandom_crypto.c \ + src/hybrid_mlkem.c \ + src/init.c \ + src/kdf.c \ + src/kex.c \ + src/known_hosts.c \ + src/knownhosts.c \ + src/legacy.c \ + src/log.c \ + src/match.c \ + src/messages.c \ + src/md_crypto.c \ + src/misc.c \ + src/mlkem.c \ + src/mlkem_native.c \ + src/options.c \ + src/packet.c \ + src/packet_cb.c \ + src/packet_crypt.c \ + src/pcap.c \ + src/pki.c \ + src/pki_container_openssh.c \ + src/pki_context.c \ + src/poll.c \ + src/session.c \ + src/sntrup761.c \ + src/socket.c \ + src/string.c \ + src/threads.c \ + src/wrapper.c \ + src/external/bcrypt_pbkdf.c \ + src/external/blowfish.c \ + src/external/chacha.c \ + src/external/libcrux_mlkem768_sha3.c \ + src/external/poly1305.c \ + src/external/sntrup761.c \ + src/chachapoly.c \ + src/config_parser.c \ + src/token.c \ + src/ttyopts.c \ + src/pki_ed25519_common.c + +VBoxLibSsh_SOURCES += \ + src/gzip.c + +# OpenSSL +VBoxLibSsh_SOURCES += \ + src/threads/libcrypto.c \ + src/pki_crypto.c \ + src/ecdh_crypto.c \ + src/libcrypto.c \ + src/dh_crypto.c + +# TODO: threads? +if1of ($(KBUILD_TARGET), win) + VBoxLibSsh_SOURCES += \ + src/threads/noop.c \ + src/threads/winlocks.c +else + VBoxLibSsh_SOURCES += \ + src/threads/noop.c \ + src/threads/pthread.c +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/libs/libssh-0.12.2/README b/src/libs/libssh-0.12.2/README new file mode 100644 index 000000000000..09a7e5696f90 --- /dev/null +++ b/src/libs/libssh-0.12.2/README @@ -0,0 +1,44 @@ + _ _ _ _ + (_) (_) (_) (_) + (_) _ (_) _ _ _ _ _ (_) _ + (_) (_) (_)(_) _ (_)(_) (_)(_) (_)(_) _ + (_) (_) (_) (_) _ (_) _ (_) (_) (_) + (_) (_) (_)(_)(_) (_)(_) (_)(_) (_) (_).org + + The SSH library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1* Why ? +-_-_-_-_-_ + +Why not ? :) I've began to work on my own implementation of the ssh protocol +because i didn't like the currently public ones. +Not any allowed you to import and use the functions as a powerful library, +and so i worked on a library-based SSH implementation which was non-existing +in the free and open source software world. + + +2* How/Who ? +-_-_-_-_-_-_-_ + +If you downloaded this file, you must know what it is : a library for +accessing ssh client services through C libraries calls in a simple manner. +Everybody can use this software under the terms of the LGPL - see the COPYING +file + +If you ask yourself how to compile libssh, please read INSTALL before anything. + +3* Where ? +-_-_-_-_-_-_ + +https://www.libssh.org + +4* Contributing +-_-_-_-_-_-_-_-_-_ + +Please read the file 'CONTRIBUTING.md' next to this README file. It explains +our copyright policy and how you should send patches for upstream inclusion. + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/src/libs/libssh-0.12.2/README.mbedtls b/src/libs/libssh-0.12.2/README.mbedtls new file mode 100644 index 000000000000..fdf3b25d5af4 --- /dev/null +++ b/src/libs/libssh-0.12.2/README.mbedtls @@ -0,0 +1,11 @@ +mbedTLS and libssh in multithreaded applications +================================================== + +To use libssh with mbedTLS in a multithreaded application, mbedTLS has to be +built with threading support enabled. + +If threading support is not available and multi threading is used, ssh_init +will fail. + +More information about building mbedTLS with threading support can be found +in the mbedTLS documentation. diff --git a/src/libs/libssh-0.12.2/README.md b/src/libs/libssh-0.12.2/README.md new file mode 100644 index 000000000000..cd6b9eaa2754 --- /dev/null +++ b/src/libs/libssh-0.12.2/README.md @@ -0,0 +1,45 @@ +[![pipeline status](https://gitlab.com/libssh/libssh-mirror/badges/master/pipeline.svg)](https://gitlab.com/libssh/libssh-mirror/commits/master) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/libssh.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:libssh) + +``` + _ _ _ _ + (_) (_) (_) (_) + (_) _ (_) _ _ _ _ _ (_) _ + (_) (_) (_)(_) _ (_)(_) (_)(_) (_)(_) _ + (_) (_) (_) (_) _ (_) _ (_) (_) (_) + (_) (_) (_)(_)(_) (_)(_) (_)(_) (_) (_).org + + The SSH library + +``` + +# Why? + +Why not ? :) I've began to work on my own implementation of the ssh protocol +because i didn't like the currently public ones. +Not any allowed you to import and use the functions as a powerful library, +and so i worked on a library-based SSH implementation which was non-existing +in the free and open source software world. + + +# How/Who? + +If you downloaded this file, you must know what it is : a library for +accessing ssh client services through C libraries calls in a simple manner. +Everybody can use this software under the terms of the LGPL - see the COPYING +file + +If you ask yourself how to compile libssh, please read INSTALL before anything. + +# Where ? + +https://www.libssh.org + +# Contributing + +Please read the file 'CONTRIBUTING.md' next to this README file. It explains +our copyright policy and how you should send patches for upstream inclusion. + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/src/libs/libssh-0.12.2/cmake/Modules/AddCCompilerFlag.cmake b/src/libs/libssh-0.12.2/cmake/Modules/AddCCompilerFlag.cmake new file mode 100644 index 000000000000..c24c215c3882 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/AddCCompilerFlag.cmake @@ -0,0 +1,21 @@ +# +# add_c_compiler_flag("-Werror" SUPPORTED_CFLAGS) +# +# Copyright (c) 2018 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(CheckCCompilerFlag) + +macro(add_c_compiler_flag _COMPILER_FLAG _OUTPUT_VARIABLE) + string(TOUPPER ${_COMPILER_FLAG} _COMPILER_FLAG_NAME) + string(REGEX REPLACE "^-" "" _COMPILER_FLAG_NAME "${_COMPILER_FLAG_NAME}") + string(REGEX REPLACE "(-|=|\ )" "_" _COMPILER_FLAG_NAME "${_COMPILER_FLAG_NAME}") + + check_c_compiler_flag("${_COMPILER_FLAG}" WITH_${_COMPILER_FLAG_NAME}_FLAG) + if (WITH_${_COMPILER_FLAG_NAME}_FLAG) + #string(APPEND ${_OUTPUT_VARIABLE} "${_COMPILER_FLAG} ") + list(APPEND ${_OUTPUT_VARIABLE} ${_COMPILER_FLAG}) + endif() +endmacro() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/AddCMockaTest.cmake b/src/libs/libssh-0.12.2/cmake/Modules/AddCMockaTest.cmake new file mode 100644 index 000000000000..f49961ba4365 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/AddCMockaTest.cmake @@ -0,0 +1,125 @@ +# +# Copyright (c) 2007 Daniel Gollub +# Copyright (c) 2007-2018 Andreas Schneider +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +#.rst: +# AddCMockaTest +# ------------- +# +# This file provides a function to add a test +# +# Functions provided +# ------------------ +# +# :: +# +# add_cmocka_test(target_name +# SOURCES src1 src2 ... srcN +# [COMPILE_OPTIONS opt1 opt2 ... optN] +# [LINK_LIBRARIES lib1 lib2 ... libN] +# [LINK_OPTIONS lopt1 lop2 .. loptN] +# ) +# +# ``target_name``: +# Required, expects the name of the test which will be used to define a target +# +# ``SOURCES``: +# Required, expects one or more source files names +# +# ``COMPILE_OPTIONS``: +# Optional, expects one or more options to be passed to the compiler +# +# ``LINK_LIBRARIES``: +# Optional, expects one or more libraries to be linked with the test +# executable. +# +# ``LINK_OPTIONS``: +# Optional, expects one or more options to be passed to the linker +# +# +# Example: +# +# .. code-block:: cmake +# +# add_cmocka_test(my_test +# SOURCES my_test.c other_source.c +# COMPILE_OPTIONS -g -Wall +# LINK_LIBRARIES mylib +# LINK_OPTIONS -Wl,--enable-syscall-fixup +# ) +# +# Where ``my_test`` is the name of the test, ``my_test.c`` and +# ``other_source.c`` are sources for the binary, ``-g -Wall`` are compiler +# options to be used, ``mylib`` is a target of a library to be linked, and +# ``-Wl,--enable-syscall-fixup`` is an option passed to the linker. +# + +enable_testing() +include(CTest) + +if (CMAKE_CROSSCOMPILING) + if (WIN32) + find_program(WINE_EXECUTABLE + NAMES wine) + set(TARGET_SYSTEM_EMULATOR ${WINE_EXECUTABLE}) + endif() +endif() + +function(ADD_CMOCKA_TEST _TARGET_NAME) + + set(one_value_arguments + ) + + set(multi_value_arguments + SOURCES + COMPILE_OPTIONS + LINK_LIBRARIES + LINK_OPTIONS + ) + + cmake_parse_arguments(_add_cmocka_test + "" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + if (NOT DEFINED _add_cmocka_test_SOURCES) + message(FATAL_ERROR "No sources provided for target ${_TARGET_NAME}") + endif() + + add_executable(${_TARGET_NAME} ${_add_cmocka_test_SOURCES}) + + if (DEFINED _add_cmocka_test_COMPILE_OPTIONS) + target_compile_options(${_TARGET_NAME} + PRIVATE ${_add_cmocka_test_COMPILE_OPTIONS} + ) + endif() + + if (DEFINED _add_cmocka_test_LINK_LIBRARIES) + target_link_libraries(${_TARGET_NAME} + PRIVATE ${_add_cmocka_test_LINK_LIBRARIES} + ) + endif() + + if (DEFINED _add_cmocka_test_LINK_OPTIONS) + set_target_properties(${_TARGET_NAME} + PROPERTIES LINK_FLAGS + ${_add_cmocka_test_LINK_OPTIONS} + ) + endif() + + add_test(${_TARGET_NAME} + ${TARGET_SYSTEM_EMULATOR} ${_TARGET_NAME} + ) + if (WITH_COVERAGE) + ENABLE_LANGUAGE(CXX) + include(CodeCoverage) + append_coverage_compiler_flags_to_target(${_TARGET_NAME}) + endif (WITH_COVERAGE) + +endfunction (ADD_CMOCKA_TEST) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/COPYING-CMAKE-SCRIPTS b/src/libs/libssh-0.12.2/cmake/Modules/COPYING-CMAKE-SCRIPTS new file mode 100644 index 000000000000..4b417765f3a8 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/libs/libssh-0.12.2/cmake/Modules/CheckCCompilerFlagSSP.cmake b/src/libs/libssh-0.12.2/cmake/Modules/CheckCCompilerFlagSSP.cmake new file mode 100644 index 000000000000..ab206ca70c70 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/CheckCCompilerFlagSSP.cmake @@ -0,0 +1,29 @@ +# - Check whether the C compiler supports a given flag in the +# context of a stack checking compiler option. + +# CHECK_C_COMPILER_FLAG_SSP(FLAG VARIABLE) +# +# FLAG - the compiler flag +# VARIABLE - variable to store the result +# +# This actually calls check_c_source_compiles. +# See help for CheckCSourceCompiles for a listing of variables +# that can modify the build. + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# Requires cmake 3.10 +#include_guard(GLOBAL) +include(CheckCSourceCompiles) + +macro(CHECK_C_COMPILER_FLAG_SSP _FLAG _RESULT) + set(SAFE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${_FLAG}") + + check_c_source_compiles("int main(int argc, char **argv) { char buffer[256]; return buffer[argc]=0;}" ${_RESULT}) + + set(CMAKE_REQUIRED_FLAGS "${SAFE_CMAKE_REQUIRED_FLAGS}") +endmacro(CHECK_C_COMPILER_FLAG_SSP) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/CodeCoverage.cmake b/src/libs/libssh-0.12.2/cmake/Modules/CodeCoverage.cmake new file mode 100644 index 000000000000..0fd70ae2062c --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/CodeCoverage.cmake @@ -0,0 +1,750 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# 2022-02-22, Marko Wehle +# - Change gcovr output from -o for --xml and --html output respectively. +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". +# +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags for all supported source files: +# append_coverage_compiler_flags() +# Or for specific target: +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +# Check supported compiler (Clang, GNU and Flang) +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() + +set(COVERAGE_COMPILER_FLAGS "-g --coverage -fprofile-update=atomic" + CACHE INTERNAL "") + +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) + if(HAVE_cxx_fprofile_abs_path) + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() +if(CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCCompilerFlag) + check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) + if(HAVE_c_fprofile_abs_path) + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE SONARQUBE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setting up commands which will be run to generate coverage data. + # Cleanup lcov + set(LCOV_CLEAN_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . + -b ${BASEDIR} --zerocounters + ) + # Create baseline to make sure untouched files show up in the report + set(LCOV_BASELINE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) + # Run tests + set(LCOV_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Capturing lcov counters and generating report + set(LCOV_CAPTURE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) + # add baseline counters + set(LCOV_BASELINE_COUNT_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) + # filter collected data to final coverage report + set(LCOV_FILTER_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) + # Generate HTML output + set(LCOV_GEN_HTML_CMD + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) + if(${Coverage_SONARQUBE}) + # Generate SonarQube output + set(GCOVR_XML_CMD + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + set(GCOVR_XML_CMD_COMMAND + COMMAND ${GCOVR_XML_CMD} + ) + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") + endif() + + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + message(STATUS "Command to clean up lcov: ") + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") + message(STATUS "${LCOV_CLEAN_CMD_SPACED}") + + message(STATUS "Command to create baseline: ") + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") + message(STATUS "${LCOV_BASELINE_CMD_SPACED}") + + message(STATUS "Command to run the tests: ") + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to capture counters and generate report: ") + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") + + message(STATUS "Command to add baseline counters: ") + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") + + message(STATUS "Command to filter collected data: ") + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") + message(STATUS "${LCOV_FILTER_CMD_SPACED}") + + message(STATUS "Command to generate lcov HTML output: ") + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + + if(${Coverage_SONARQUBE}) + message(STATUS "Command to generate SonarQube XML output: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${GCOVR_XML_CMD_BYPRODUCTS} + ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_XML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Running gcovr + set(GCOVR_XML_CMD + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to generate gcovr XML coverage data: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_XML_CMD} + + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_HTML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Create folder + set(GCOVR_HTML_FOLDER_CMD + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) + # Running gcovr + set(GCOVR_HTML_CMD + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to create a folder: ") + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") + + message(STATUS "Command to generate gcovr HTML coverage data: ") + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") + message(STATUS "${GCOVR_HTML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_fastcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# SKIP_HTML # Don't create html report +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths +# ) +function(setup_target_for_coverage_fastcov) + + set(options NO_DEMANGLE SKIP_HTML) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting...") + endif() + + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (Patterns, not paths, for fastcov) + set(FASTCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Set up commands which will be run to generate coverage data + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) + + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + ) + + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) + + if(Coverage_SKIP_HTML) + set(FASTCOV_HTML_CMD ";") + else() + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) + endif() + + set(FASTCOV_POST_CMD ";") + if(Coverage_POST_CMD) + set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) + endif() + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") + + message(" Running tests:") + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") + + message(" Capturing fastcov counters and generating report:") + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") + message(" ${FASTCOV_CAPTURE_CMD_SPACED}") + + message(" Converting fastcov .json to lcov .info:") + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") + message(" ${FASTCOV_CONVERT_CMD_SPACED}") + + if(NOT Coverage_SKIP_HTML) + message(" Generating HTML report: ") + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") + message(" ${FASTCOV_HTML_CMD_SPACED}") + endif() + if(Coverage_POST_CMD) + message(" Running post command: ") + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") + message(" ${FASTCOV_POST_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --zerocounters + + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) + + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") + if(NOT Coverage_SKIP_HTML) + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") + endif() + # Show where to find the fastcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) + +endfunction() # setup_target_for_coverage_fastcov + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags + +# Setup coverage for specific library +function(append_coverage_compiler_flags_to_target name) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() +endfunction() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/DefineCMakeDefaults.cmake b/src/libs/libssh-0.12.2/cmake/Modules/DefineCMakeDefaults.cmake new file mode 100644 index 000000000000..6f369faaa262 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/DefineCMakeDefaults.cmake @@ -0,0 +1,21 @@ +# Always include srcdir and builddir in include path +# This saves typing ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY} in +# about every subdir +# since cmake 2.4.0 +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Put the include dirs which are in the source or build tree +# before all other include dirs, so the headers in the sources +# are preferred over the already installed ones +# since cmake 2.4.1 +set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) + +# Use colored output +# since cmake 2.4.0 +set(CMAKE_COLOR_MAKEFILE ON) + +# Create the compile command database for clang by default +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Always build with -fPIC +set(CMAKE_POSITION_INDEPENDENT_CODE ON) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/DefineCompilerFlags.cmake b/src/libs/libssh-0.12.2/cmake/Modules/DefineCompilerFlags.cmake new file mode 100644 index 000000000000..39378a10ed25 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/DefineCompilerFlags.cmake @@ -0,0 +1,49 @@ +if (UNIX AND NOT WIN32) + # Activate with: -DCMAKE_BUILD_TYPE=Profiling + set(CMAKE_C_FLAGS_PROFILING "-O0 -g -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C compiler during PROFILING builds.") + set(CMAKE_CXX_FLAGS_PROFILING "-O0 -g -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the CXX compiler during PROFILING builds.") + set(CMAKE_SHARED_LINKER_FLAGS_PROFILING "-fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the linker during the creation of shared libraries during PROFILING builds.") + set(CMAKE_MODULE_LINKER_FLAGS_PROFILING "-fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the linker during the creation of shared libraries during PROFILING builds.") + set(CMAKE_EXEC_LINKER_FLAGS_PROFILING "-fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the linker during PROFILING builds.") + + # Activate with: -DCMAKE_BUILD_TYPE=AddressSanitizer + set(CMAKE_C_FLAGS_ADDRESSSANITIZER "-g -O1 -fsanitize=address -fno-omit-frame-pointer" + CACHE STRING "Flags used by the C compiler during ADDRESSSANITIZER builds.") + set(CMAKE_CXX_FLAGS_ADDRESSSANITIZER "-g -O1 -fsanitize=address -fno-omit-frame-pointer" + CACHE STRING "Flags used by the CXX compiler during ADDRESSSANITIZER builds.") + set(CMAKE_SHARED_LINKER_FLAGS_ADDRESSSANITIZER "-fsanitize=address" + CACHE STRING "Flags used by the linker during the creation of shared libraries during ADDRESSSANITIZER builds.") + set(CMAKE_MODULE_LINKER_FLAGS_ADDRESSSANITIZER "-fsanitize=address" + CACHE STRING "Flags used by the linker during the creation of shared libraries during ADDRESSSANITIZER builds.") + set(CMAKE_EXEC_LINKER_FLAGS_ADDRESSSANITIZER "-fsanitize=address" + CACHE STRING "Flags used by the linker during ADDRESSSANITIZER builds.") + + # Activate with: -DCMAKE_BUILD_TYPE=MemorySanitizer + set(CMAKE_C_FLAGS_MEMORYSANITIZER "-g -O2 -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" + CACHE STRING "Flags used by the C compiler during MEMORYSANITIZER builds.") + set(CMAKE_CXX_FLAGS_MEMORYSANITIZER "-g -O2 -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" + CACHE STRING "Flags used by the CXX compiler during MEMORYSANITIZER builds.") + set(CMAKE_SHARED_LINKER_FLAGS_MEMORYSANITIZER "-fsanitize=memory" + CACHE STRING "Flags used by the linker during the creation of shared libraries during MEMORYSANITIZER builds.") + set(CMAKE_MODULE_LINKER_FLAGS_MEMORYSANITIZER "-fsanitize=memory" + CACHE STRING "Flags used by the linker during the creation of shared libraries during MEMORYSANITIZER builds.") + set(CMAKE_EXEC_LINKER_FLAGS_MEMORYSANITIZER "-fsanitize=memory" + CACHE STRING "Flags used by the linker during MEMORYSANITIZER builds.") + + # Activate with: -DCMAKE_BUILD_TYPE=UndefinedSanitizer + set(CMAKE_C_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover=undefined,integer" + CACHE STRING "Flags used by the C compiler during UNDEFINEDSANITIZER builds.") + set(CMAKE_CXX_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover=undefined,integer" + CACHE STRING "Flags used by the CXX compiler during UNDEFINEDSANITIZER builds.") + set(CMAKE_SHARED_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" + CACHE STRING "Flags used by the linker during the creation of shared libraries during UNDEFINEDSANITIZER builds.") + set(CMAKE_MODULE_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" + CACHE STRING "Flags used by the linker during the creation of shared libraries during UNDEFINEDSANITIZER builds.") + set(CMAKE_EXEC_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" + CACHE STRING "Flags used by the linker during UNDEFINEDSANITIZER builds.") +endif() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/DefinePlatformDefaults.cmake b/src/libs/libssh-0.12.2/cmake/Modules/DefinePlatformDefaults.cmake new file mode 100644 index 000000000000..77f8a4618562 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/DefinePlatformDefaults.cmake @@ -0,0 +1,32 @@ +# Set system vars + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + set(LINUX TRUE) +endif(CMAKE_SYSTEM_NAME MATCHES "Linux") + +if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(FREEBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + set(OPENBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + set(NETBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + set(SOLARIS TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + +if (CMAKE_SYSTEM_NAME MATCHES "OS2") + set(OS2 TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OS2") + +if (CMAKE_SYSTEM_NAME MATCHES "Darwin") + set (OSX TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "Darwin") diff --git a/src/libs/libssh-0.12.2/cmake/Modules/ExtractSymbols.cmake b/src/libs/libssh-0.12.2/cmake/Modules/ExtractSymbols.cmake new file mode 100644 index 000000000000..95c850b66442 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/ExtractSymbols.cmake @@ -0,0 +1,105 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# ExtractSymbols +# -------------- +# +# This is a helper script for FindABImap.cmake. +# +# Extract symbols from header files and output a list to a file. +# This script is run in build time to extract symbols from the provided header +# files. This way, symbols added or removed can be checked and used to update +# the symbol version script. +# +# All symbols followed by the character ``'('`` are extracted. If a +# ``FILTER_PATTERN`` is provided, only the lines containing the given string are +# considered. +# +# Expected defined variables +# -------------------------- +# +# ``HEADERS_LIST_FILE``: +# Required, expects a file containing the list of header files to be parsed. +# +# ``OUTPUT_PATH``: +# Required, expects the output file path. +# +# Optionally defined variables +# ---------------------------- +# +# ``FILTER_PATTERN``: +# Expects a string. Only lines containing the given string will be considered +# when extracting symbols. +# + +if (NOT DEFINED OUTPUT_PATH) + message(SEND_ERROR "OUTPUT_PATH not defined") +endif() + +if (NOT DEFINED HEADERS_LIST_FILE) + message(SEND_ERROR "HEADERS not defined") +endif() + +file(READ ${HEADERS_LIST_FILE} HEADERS_LIST) + +set(symbols) +foreach(header ${HEADERS_LIST}) + file(READ ${header} header_content) + + # Filter only lines containing the FILTER_PATTERN + # separated from the function name with one optional newline + string(REGEX MATCHALL + "${FILTER_PATTERN}[^(\n]*\n?[^(\n]*[(]" + contain_filter + "${header_content}" + ) + + # Remove the optional newline now + string(REGEX REPLACE + "(.+)\n?(.*)" + "\\1\\2" + oneline + "${contain_filter}" + ) + + # Remove function-like macros + # and anything with two underscores that sounds suspicious + foreach(line ${oneline}) + if (NOT ${line} MATCHES ".*(#[ ]*define|__)") + list(APPEND not_macro ${line}) + endif() + endforeach() + + set(functions) + + # Get only the function names followed by '(' + foreach(line ${not_macro}) + string(REGEX MATCHALL "[a-zA-Z0-9_]+[ ]*[(]" func ${line}) + list(APPEND functions ${func}) + endforeach() + + set(extracted_symbols) + + # Remove '(' + foreach(line ${functions}) + string(REGEX REPLACE "[(]" "" symbol ${line}) + string(STRIP "${symbol}" symbol) + list(APPEND extracted_symbols ${symbol}) + endforeach() + + list(APPEND symbols ${extracted_symbols}) +endforeach() + +list(REMOVE_DUPLICATES symbols) + +list(SORT symbols) + +string(REPLACE ";" "\n" symbols_list "${symbols}") + +file(WRITE ${OUTPUT_PATH} "${symbols_list}") diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindABIMap.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindABIMap.cmake new file mode 100644 index 000000000000..e7f725d216b3 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindABIMap.cmake @@ -0,0 +1,491 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# FindABIMap +# ---------- +# +# This file provides functions to generate the symbol version script. It uses +# the ``abimap`` tool to generate and update the linker script file. It can be +# installed by calling:: +# +# $ pip install abimap +# +# The ``function generate_map_file`` generates a symbol version script +# containing the provided symbols. It defines a custom command which sets +# ``target_name`` as its ``OUTPUT``. +# +# The experimental function ``extract_symbols()`` is provided as a simple +# parser to extract the symbols from C header files. It simply extracts symbols +# followed by an opening '``(``'. It is recommended to use a filter pattern to +# select the lines to be considered. It defines a custom command which sets +# ``target_name`` as its output. +# +# The helper function ``get_files_list()`` is provided to find files given a +# name pattern. It defines a custom command which sets ``target_name`` as its +# output. +# +# Functions provided +# ------------------ +# +# :: +# +# generate_map_file(target_name +# RELEASE_NAME_VERSION release_name +# SYMBOLS symbols_target +# [CURRENT_MAP cur_map] +# [FINAL] +# [BREAK_ABI] +# [COPY_TO output] +# ) +# +# ``target_name``: +# Required, expects the name of the file to receive the generated symbol +# version script. It should be added as a dependency for the library. Use the +# linker option ``--version-script filename`` to add the version information +# to the symbols when building the library. +# +# ``RELEASE_NAME_VERSION``: +# Required, expects a string containing the name and version information to be +# added to the symbols in the format ``lib_name_1_2_3``. +# +# ``SYMBOLS``: +# Required, expects a target with the property ``LIST_FILE`` containing a path +# to a file containing the list of symbols to be added to the symbol version +# script. +# +# ``CURRENT_MAP``: +# Optional. If given, the new set of symbols will be checked against the +# ones contained in the ``cur_map`` file and updated properly. If an +# incompatible change is detected and ``BREAK_ABI`` is not defined, the build +# will fail. +# +# ``FINAL``: +# Optional. If given, will provide the ``--final`` option to ``abimap`` tool, +# which will mark the modified release in the symbol version script with a +# special comment, preventing later changes. This option should be set when +# creating a library release and the resulting map file should be stored with +# the source code. +# +# ``BREAK_ABI``: +# Optional. If provided, will use ``abimap`` ``--allow-abi-break`` option, which +# accepts incompatible changes to the set of symbols. This is necessary if any +# previously existing symbol were removed. +# +# ``COPY_TO``: +# Optional, expects a string containing the path to where the generated +# map file will be copied. +# +# Example: +# +# .. code-block:: cmake +# +# find_package(ABIMap) +# generate_map_file("lib.map" +# RELEASE_NAME_VERSION "lib_1_0_0" +# SYMBOLS symbols +# ) +# +# Where the target ``symbols`` has its property ``LIST_FILE`` set to the path to +# a file containing:: +# +# ``symbol1`` +# ``symbol2`` +# +# This example would result in the symbol version script to be created in +# ``${CMAKE_CURRENT_BINARY_DIR}/lib.map`` containing the provided symbols. +# +# :: +# +# get_files_list(target_name +# DIRECTORIES dir1 [dir2 ...] +# FILES_PATTERNS exp1 [exp2 ...] +# [COPY_TO output] +# ) +# +# ``target_name``: +# Required, expects the name of the target to be created. A file named as +# ``${target_name}.list`` will be created in +# ``${CMAKE_CURRENT_BINARY_DIR}`` to receive the list of files found. +# +# ``DIRECTORIES``: +# Required, expects a list of directories paths. Only absolute paths are +# supported. +# +# ``FILES_PATTERN``: +# Required, expects a list of matching expressions to find the files to be +# considered in the directories. +# +# ``COPY_TO``: +# Optional, expects a string containing the path to where the file containing +# the list of files will be copied. +# +# This command searches the directories provided in ``DIRECTORIES`` for files +# matching any of the patterns provided in ``FILES_PATTERNS``. The obtained list +# is written to the path specified by ``output``. A target named ``target_name`` +# will be created and its property ``LIST_FILE`` will be set to contain +# ``${CMAKE_CURRENT_BINARY_DIR}/${target_name}.list`` +# +# Example: +# +# .. code-block:: cmake +# +# find_package(ABIMap) +# get_files_list(target +# DIRECTORIES "/include/mylib" +# FILES_PATTERNS "*.h" +# COPY_TO "my_list.txt" +# ) +# +# Consider that ``/include/mylib`` contains 3 files, ``h1.h``, ``h2.h``, and +# ``h3.hpp`` +# +# Will result in a file ``my_list.txt`` containing:: +# +# ``h1.h;h2.h`` +# +# And the target ``target`` will have its property ``LIST_FILE`` set to contain +# ``${CMAKE_CURRENT_BINARY_DIR}/target.list`` +# +# :: +# +# extract_symbols(target_name +# HEADERS_LIST headers_list_target +# [FILTER_PATTERN pattern] +# [COPY_TO output] +# ) +# +# ``target_name``: +# Required, expects the name of the target to be created. A file named after +# the string given in ``target_name`` will be created in +# ``${CMAKE_CURRENT_BINARY_DIR}`` to receive the list of symbols. +# +# ``HEADERS_LIST``: +# Required, expects a target with the property ``LIST_FILE`` set, containing a +# file path. Such file must contain a list of files paths. +# +# ``FILTER_PATTERN``: +# Optional, expects a string. Only the lines containing the filter pattern +# will be considered. +# +# ``COPY_TO``: +# Optional, expects a string containing the path to where the file containing +# the found symbols will be copied. +# +# This command extracts the symbols from the files listed in +# ``headers_list`` and write them on the ``output`` file. If ``pattern`` +# is provided, then only the lines containing the string given in ``pattern`` +# will be considered. It is recommended to provide a ``FILTER_PATTERN`` to mark +# the lines containing exported function declaration, since this function is +# experimental and can return wrong symbols when parsing the header files. A +# target named ``target_name`` will be created with the property ``LIST_FILE`` +# set to contain ``${CMAKE_CURRENT_BINARY_DIR}/${target_name}.list``. +# +# Example: +# +# .. code-block:: cmake +# +# find_package(ABIMap) +# extract_symbols("lib.symbols" +# HEADERS_LIST "headers_target" +# FILTER_PATTERN "API_FUNCTION" +# ) +# +# Where ``LIST_FILE`` property in ``headers_target`` points to a file +# containing:: +# +# header1.h;header2.h +# +# Where ``header1.h`` contains:: +# +# API_FUNCTION int exported_func1(int a, int b); +# +# ``header2.h`` contains:: +# +# API_FUNCTION int exported_func2(int a); +# +# int private_func2(int b); +# +# Will result in a file ``lib.symbols.list`` in ``${CMAKE_CURRENT_BINARY_DIR}`` +# containing:: +# +# ``exported_func1`` +# ``exported_func2`` +# + +# Search for python which is required +if (ABIMap_FIND_REQURIED) + find_package(Python REQUIRED) +else() + find_package(Python) +endif() + +if (TARGET Python::Interpreter) + # Search for abimap tool used to generate the map files + find_program(ABIMAP_EXECUTABLE NAMES abimap DOC "path to the abimap executable") + mark_as_advanced(ABIMAP_EXECUTABLE) + + if (NOT ABIMAP_EXECUTABLE AND UNIX) + message(STATUS "Could not find `abimap` in PATH." + " It can be found in PyPI as `abimap`" + " (try `pip install abimap`)") + endif () + + if (ABIMAP_EXECUTABLE) + # Get the abimap version + execute_process(COMMAND ${ABIMAP_EXECUTABLE} version + OUTPUT_VARIABLE ABIMAP_VERSION_STRING + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # If the version string starts with abimap-, strip it + if ("abimap" STRLESS_EQUAL ${ABIMAP_VERSION_STRING}) + string(REGEX REPLACE "abimap-" "" ABIMAP_VERSION_STRING "${ABIMAP_VERSION_STRING}") + endif() + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(ABIMap + REQUIRED_VARS ABIMAP_EXECUTABLE + VERSION_VAR ABIMAP_VERSION_STRING) +endif() + + +if (ABIMAP_FOUND) + +# Define helper scripts +set(_EXTRACT_SYMBOLS_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/ExtractSymbols.cmake) +set(_GENERATE_MAP_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/GenerateMap.cmake) +set(_GET_FILES_LIST_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/GetFilesList.cmake) + +function(get_file_list _TARGET_NAME) + + set(one_value_arguments + COPY_TO + ) + + set(multi_value_arguments + DIRECTORIES + FILES_PATTERNS + ) + + cmake_parse_arguments(_get_files_list + "" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + # The DIRS argument is required + if (NOT DEFINED _get_files_list_DIRECTORIES) + message(FATAL_ERROR "No directories paths provided. Provide a list of" + " directories paths containing header files.") + endif() + + # The FILES_PATTERNS argument is required + if (NOT DEFINED _get_files_list_FILES_PATTERNS) + message(FATAL_ERROR "No matching expressions provided. Provide a list" + " of matching patterns for the header files.") + endif() + + set(_FILES_LIST_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/${_TARGET_NAME}.list) + + get_filename_component(_get_files_list_OUTPUT_PATH + "${_FILES_LIST_OUTPUT_PATH}" + ABSOLUTE) + + add_custom_target( + ${_TARGET_NAME}_int ALL + COMMAND ${CMAKE_COMMAND} + -DOUTPUT_PATH=${_get_files_list_OUTPUT_PATH} + -DDIRECTORIES=${_get_files_list_DIRECTORIES} + -DFILES_PATTERNS=${_get_files_list_FILES_PATTERNS} + -P ${_GET_FILES_LIST_SCRIPT} + COMMENT + "Searching for files" + VERBATIM + ) + + if (DEFINED _get_files_list_COPY_TO) + # Copy the generated file back to the COPY_TO + add_custom_target(${_TARGET_NAME} ALL + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${_FILES_LIST_OUTPUT_PATH} ${_get_files_list_COPY_TO} + DEPENDS ${_TARGET_NAME}_int + COMMENT "Copying ${_TARGET_NAME} to ${_get_files_list_COPY_TO}" + VERBATIM + ) + else() + add_custom_target(${_TARGET_NAME} ALL + DEPENDS ${_TARGET_NAME}_int + ) + endif() + + set_target_properties(${_TARGET_NAME} + PROPERTIES LIST_FILE ${_FILES_LIST_OUTPUT_PATH} + ) + +endfunction() + +function(extract_symbols _TARGET_NAME) + + set(one_value_arguments + FILTER_PATTERN + HEADERS_LIST + COPY_TO + ) + + set(multi_value_arguments + ) + + cmake_parse_arguments(_extract_symbols + "" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + # The HEADERS_LIST_FILE argument is required + if (NOT DEFINED _extract_symbols_HEADERS_LIST) + message(FATAL_ERROR "No target provided in HEADERS_LIST. Provide a" + " target with the property LIST_FILE set as the" + " path to the file containing the list of headers.") + endif() + + get_filename_component(_SYMBOLS_OUTPUT_PATH + "${CMAKE_CURRENT_BINARY_DIR}/${_TARGET_NAME}.list" + ABSOLUTE + ) + + get_target_property(_HEADERS_LIST_FILE + ${_extract_symbols_HEADERS_LIST} + LIST_FILE + ) + + add_custom_target( + ${_TARGET_NAME}_int ALL + COMMAND ${CMAKE_COMMAND} + -DOUTPUT_PATH=${_SYMBOLS_OUTPUT_PATH} + -DHEADERS_LIST_FILE=${_HEADERS_LIST_FILE} + -DFILTER_PATTERN=${_extract_symbols_FILTER_PATTERN} + -P ${_EXTRACT_SYMBOLS_SCRIPT} + DEPENDS ${_extract_symbols_HEADERS_LIST} + COMMENT "Extracting symbols from headers" + VERBATIM + ) + + if (DEFINED _extract_symbols_COPY_TO) + # Copy the generated file back to the COPY_TO + add_custom_target(${_TARGET_NAME} ALL + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${_SYMBOLS_OUTPUT_PATH} ${_extract_symbols_COPY_TO} + DEPENDS ${_TARGET_NAME}_int + COMMENT "Copying ${_TARGET_NAME} to ${_extract_symbols_COPY_TO}" + VERBATIM + ) + else() + add_custom_target(${_TARGET_NAME} ALL + DEPENDS ${_TARGET_NAME}_int + ) + endif() + + set_target_properties(${_TARGET_NAME} + PROPERTIES LIST_FILE ${_SYMBOLS_OUTPUT_PATH} + ) + +endfunction() + +function(generate_map_file _TARGET_NAME) + + set(options + FINAL + BREAK_ABI + ) + + set(one_value_arguments + RELEASE_NAME_VERSION + SYMBOLS + CURRENT_MAP + COPY_TO + ) + + set(multi_value_arguments + ) + + cmake_parse_arguments(_generate_map_file + "${options}" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + if (NOT DEFINED _generate_map_file_SYMBOLS) + message(FATAL_ERROR "No target provided in SYMBOLS. Provide a target" + " with the property LIST_FILE set as the path to" + " the file containing the list of symbols.") + endif() + + if (NOT DEFINED _generate_map_file_RELEASE_NAME_VERSION) + message(FATAL_ERROR "Release name and version not provided." + " (e.g. libname_1_0_0)") + endif() + + + get_target_property(_SYMBOLS_FILE + ${_generate_map_file_SYMBOLS} + LIST_FILE + ) + + # Set generated map file path + get_filename_component(_MAP_OUTPUT_PATH + "${CMAKE_CURRENT_BINARY_DIR}/${_TARGET_NAME}" + ABSOLUTE + ) + + add_custom_target( + ${_TARGET_NAME}_int ALL + COMMAND ${CMAKE_COMMAND} + -DABIMAP_EXECUTABLE=${ABIMAP_EXECUTABLE} + -DSYMBOLS=${_SYMBOLS_FILE} + -DCURRENT_MAP=${_generate_map_file_CURRENT_MAP} + -DOUTPUT_PATH=${_MAP_OUTPUT_PATH} + -DFINAL=${_generate_map_file_FINAL} + -DBREAK_ABI=${_generate_map_file_BREAK_ABI} + -DRELEASE_NAME_VERSION=${_generate_map_file_RELEASE_NAME_VERSION} + -P ${_GENERATE_MAP_SCRIPT} + DEPENDS ${_generate_map_file_SYMBOLS} + COMMENT "Generating the map ${_TARGET_NAME}" + VERBATIM + ) + + # Add a custom command setting the map as OUTPUT to allow it to be added as + # a generated source + add_custom_command( + OUTPUT ${_MAP_OUTPUT_PATH} + DEPENDS ${_TARGET_NAME}_copy + ) + + if (DEFINED _generate_map_file_COPY_TO) + # Copy the generated map back to the COPY_TO + add_custom_target(${_TARGET_NAME}_copy ALL + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${_MAP_OUTPUT_PATH} + ${_generate_map_file_COPY_TO} + DEPENDS ${_TARGET_NAME}_int + COMMENT "Copying ${_MAP_OUTPUT_PATH} to ${_generate_map_file_COPY_TO}" + VERBATIM + ) + else() + add_custom_target(${_TARGET_NAME}_copy ALL + DEPENDS ${_TARGET_NAME}_int + ) + endif() +endfunction() + +endif (ABIMAP_FOUND) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindArgp.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindArgp.cmake new file mode 100644 index 000000000000..13d74637699b --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindArgp.cmake @@ -0,0 +1,70 @@ +# - Try to find ARGP +# +# The argp can be either shipped as part of libc (ex. glibc) or as a separate +# library that requires additional linking (ex. Windows, Mac, musl libc, ...) +# +# Once done this will define +# +# ARGP_ROOT_DIR - Set this variable to the root installation of ARGP +# +# Read-Only variables: +# ARGP_FOUND - system has ARGP +# ARGP_INCLUDE_DIR - the ARGP include directory +# ARGP_LIBRARIES - Link these to use ARGP +# ARGP_DEFINITIONS - Compiler switches required for using ARGP +# +#============================================================================= +# Copyright (c) 2011-2016 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_ARGP_ROOT_HINTS +) + +set(_ARGP_ROOT_PATHS + "$ENV{PROGRAMFILES}/argp" +) + +find_path(ARGP_ROOT_DIR + NAMES + include/argp.h + HINTS + ${_ARGP_ROOT_HINTS} + PATHS + ${_ARGP_ROOT_PATHS} +) +mark_as_advanced(ARGP_ROOT_DIR) + +find_path(ARGP_INCLUDE_DIR + NAMES + argp.h + PATHS + ${ARGP_ROOT_DIR}/include +) + +find_library(ARGP_LIBRARY + NAMES + argp + PATHS + ${ARGP_ROOT_DIR}/lib +) + +if (ARGP_LIBRARY) + set(ARGP_LIBRARIES + ${ARGP_LIBRARIES} + ${ARGP_LIBRARY} + ) +endif (ARGP_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Argp DEFAULT_MSG ARGP_LIBRARIES ARGP_INCLUDE_DIR) + +# show the ARGP_INCLUDE_DIR and ARGP_LIBRARIES variables only in the advanced view +mark_as_advanced(ARGP_INCLUDE_DIR ARGP_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindCMocka.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindCMocka.cmake new file mode 100644 index 000000000000..76b4ba74d7a5 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindCMocka.cmake @@ -0,0 +1,66 @@ +# - Try to find CMocka +# Once done this will define +# +# CMOCKA_ROOT_DIR - Set this variable to the root installation of CMocka +# +# Read-Only variables: +# CMOCKA_FOUND - system has CMocka +# CMOCKA_INCLUDE_DIR - the CMocka include directory +# CMOCKA_LIBRARIES - Link these to use CMocka +# CMOCKA_DEFINITIONS - Compiler switches required for using CMocka +# +#============================================================================= +# Copyright (c) 2011-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_CMOCKA_ROOT_HINTS +) + +set(_CMOCKA_ROOT_PATHS + "$ENV{PROGRAMFILES}/cmocka" +) + +find_path(CMOCKA_ROOT_DIR + NAMES + include/cmocka.h + HINTS + ${_CMOCKA_ROOT_HINTS} + PATHS + ${_CMOCKA_ROOT_PATHS} +) +mark_as_advanced(CMOCKA_ROOT_DIR) + +find_path(CMOCKA_INCLUDE_DIR + NAMES + cmocka.h + PATHS + ${CMOCKA_ROOT_DIR}/include +) + +find_library(CMOCKA_LIBRARY + NAMES + cmocka + PATHS + ${CMOCKA_ROOT_DIR}/lib +) + +if (CMOCKA_LIBRARY) + set(CMOCKA_LIBRARIES + ${CMOCKA_LIBRARIES} + ${CMOCKA_LIBRARY} + ) +endif (CMOCKA_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CMocka DEFAULT_MSG CMOCKA_LIBRARIES CMOCKA_INCLUDE_DIR) + +# show the CMOCKA_INCLUDE_DIR and CMOCKA_LIBRARIES variables only in the advanced view +mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindGCrypt.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindGCrypt.cmake new file mode 100644 index 000000000000..e28cb8462d4a --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindGCrypt.cmake @@ -0,0 +1,118 @@ +# - Try to find GCrypt +# Once done this will define +# +# GCRYPT_FOUND - system has GCrypt +# GCRYPT_INCLUDE_DIRS - the GCrypt include directory +# GCRYPT_LIBRARIES - Link these to use GCrypt +# GCRYPT_DEFINITIONS - Compiler switches required for using GCrypt +# +#============================================================================= +# Copyright (c) 2009-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_GCRYPT_ROOT_HINTS + $ENV{GCRYTPT_ROOT_DIR} + ${GCRYPT_ROOT_DIR}) + +set(_GCRYPT_ROOT_PATHS + "$ENV{PROGRAMFILES}/libgcrypt") + +set(_GCRYPT_ROOT_HINTS_AND_PATHS + HINTS ${_GCRYPT_ROOT_HINTS} + PATHS ${_GCRYPT_ROOT_PATHS}) + + +find_path(GCRYPT_INCLUDE_DIR + NAMES + gcrypt.h + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_path(GCRYPT_ERROR_INCLUDE_DIR + NAMES + gpg-error.h + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_library(GCRYPT_LIBRARY + NAMES + gcrypt + gcrypt11 + libgcrypt-11 + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) +find_library(GCRYPT_ERROR_LIBRARY + NAMES + gpg-error + libgpg-error-0 + libgpg-error6-0 + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) +set(GCRYPT_LIBRARIES ${GCRYPT_ERROR_LIBRARY} ${GCRYPT_LIBRARY}) + +if (GCRYPT_INCLUDE_DIR) + file(STRINGS "${GCRYPT_INCLUDE_DIR}/gcrypt.h" _gcrypt_version_str REGEX "^#define GCRYPT_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]") + + string(REGEX REPLACE "^.*GCRYPT_VERSION.*([0-9]+\\.[0-9]+\\.[0-9]+).*" "\\1" GCRYPT_VERSION "${_gcrypt_version_str}") +endif (GCRYPT_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) +if (GCRYPT_VERSION) + find_package_handle_standard_args(GCrypt + REQUIRED_VARS + GCRYPT_INCLUDE_DIR + GCRYPT_LIBRARIES + VERSION_VAR + GCRYPT_VERSION + FAIL_MESSAGE + "Could NOT find GCrypt, try to set the path to GCrypt root folder in the system variable GCRYPT_ROOT_DIR" + ) +else (GCRYPT_VERSION) + find_package_handle_standard_args(GCrypt + "Could NOT find GCrypt, try to set the path to GCrypt root folder in the system variable GCRYPT_ROOT_DIR" + GCRYPT_INCLUDE_DIR + GCRYPT_LIBRARIES) +endif (GCRYPT_VERSION) + +# show the GCRYPT_INCLUDE_DIRS, GCRYPT_LIBRARIES and GCRYPT_ERROR_INCLUDE_DIR variables only in the advanced view +mark_as_advanced(GCRYPT_INCLUDE_DIR GCRYPT_ERROR_INCLUDE_DIR GCRYPT_LIBRARIES) + +if(GCRYPT_FOUND) + if(NOT TARGET libgcrypt::libgcrypt) + add_library(libgcrypt::libgcrypt UNKNOWN IMPORTED) + set_target_properties(libgcrypt::libgcrypt PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GCRYPT_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES libgcrypt::libgcrypt + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${GCRYPT_LIBRARY}") + endif() + + if(NOT TARGET libgpg-error::libgpg-error) + add_library(libgpg-error::libgpg-error UNKNOWN IMPORTED) + set_target_properties(libgpg-error::libgpg-error PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GCRYPT_ERROR_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES libgpg-error::libgpg-error + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${GCRYPT_ERROR_LIBRARY}") + endif() +endif() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindGSSAPI.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindGSSAPI.cmake new file mode 100644 index 000000000000..630d7c162bcf --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindGSSAPI.cmake @@ -0,0 +1,344 @@ +# - Try to find GSSAPI +# Once done this will define +# +# KRB5_CONFIG - Path to krb5-config +# GSSAPI_ROOT_DIR - Set this variable to the root installation of GSSAPI +# +# Read-Only variables: +# GSSAPI_FLAVOR_MIT - set to TRUE if MIT Kerberos has been found +# GSSAPI_FLAVOR_HEIMDAL - set to TRUE if Heimdal Keberos has been found +# GSSAPI_FOUND - system has GSSAPI +# GSSAPI_INCLUDE_DIR - the GSSAPI include directory +# GSSAPI_LIBRARIES - Link these to use GSSAPI +# GSSAPI_DEFINITIONS - Compiler switches required for using GSSAPI +# GSSAPI_PC_REQUIRES - pkg-config module name if found, needed for +# Requires.private for static linking +# +#============================================================================= +# Copyright (c) 2013 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_mit_modname "mit-krb5-gssapi") +set(_heimdal_modname "heimdal-gssapi") + +if(NOT _GSSAPI_ROOT_HINTS AND NOT _GSSAPI_ROOT_PATHS) + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_search_module(_GSSAPI ${_mit_modname} ${_heimdal_modname}) + endif() +endif() + +find_path(GSSAPI_ROOT_DIR + NAMES + include/gssapi.h + include/gssapi/gssapi.h + HINTS + ${_GSSAPI_ROOT_HINTS} + "${_GSSAPI_INCLUDEDIR}" + PATHS + ${_GSSAPI_ROOT_PATHS} +) +mark_as_advanced(GSSAPI_ROOT_DIR) + +if (UNIX) + find_program(KRB5_CONFIG + NAMES + krb5-config + PATHS + ${GSSAPI_ROOT_DIR}/bin + /opt/local/bin) + mark_as_advanced(KRB5_CONFIG) + + if (KRB5_CONFIG) + # Check if we have MIT KRB5 + execute_process( + COMMAND + ${KRB5_CONFIG} --vendor + RESULT_VARIABLE + _GSSAPI_VENDOR_RESULT + OUTPUT_VARIABLE + _GSSAPI_VENDOR_STRING) + + if ((_GSSAPI_VENDOR_STRING MATCHES ".*Massachusetts.*") OR (_GSSAPI_VENDOR_STRING + MATCHES ".*MITKerberosShim.*")) + set(GSSAPI_FLAVOR_MIT TRUE) + else() + execute_process( + COMMAND + ${KRB5_CONFIG} --libs gssapi + RESULT_VARIABLE + _GSSAPI_LIBS_RESULT + OUTPUT_VARIABLE + _GSSAPI_LIBS_STRING) + + if (_GSSAPI_LIBS_STRING MATCHES ".*roken.*") + set(GSSAPI_FLAVOR_HEIMDAL TRUE) + endif() + endif() + + # Get the include dir + execute_process( + COMMAND + ${KRB5_CONFIG} --cflags gssapi + RESULT_VARIABLE + _GSSAPI_INCLUDE_RESULT + OUTPUT_VARIABLE + _GSSAPI_INCLUDE_STRING) + string(REGEX REPLACE "(\r?\n)+$" "" _GSSAPI_INCLUDE_STRING "${_GSSAPI_INCLUDE_STRING}") + string(REGEX REPLACE " *-I" "" _GSSAPI_INCLUDEDIR "${_GSSAPI_INCLUDE_STRING}") + endif() + + if (NOT GSSAPI_FLAVOR_MIT AND NOT GSSAPI_FLAVOR_HEIMDAL) + # Check for HEIMDAL + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_GSSAPI heimdal-gssapi) + endif (PKG_CONFIG_FOUND) + + if (_GSSAPI_FOUND) + set(GSSAPI_FLAVOR_HEIMDAL TRUE) + else() + find_path(_GSSAPI_ROKEN + NAMES + roken.h + PATHS + ${GSSAPI_ROOT_DIR}/include + ${_GSSAPI_INCLUDEDIR}) + if (_GSSAPI_ROKEN) + set(GSSAPI_FLAVOR_HEIMDAL TRUE) + endif() + endif () + endif() +endif (UNIX) + +find_path(GSSAPI_INCLUDE_DIR + NAMES + gssapi.h + gssapi/gssapi.h + PATHS + ${GSSAPI_ROOT_DIR}/include + ${_GSSAPI_INCLUDEDIR} +) + +if (GSSAPI_FLAVOR_MIT) + find_library(GSSAPI_LIBRARY + NAMES + gssapi_krb5 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(KRB5_LIBRARY + NAMES + krb5 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(K5CRYPTO_LIBRARY + NAMES + k5crypto + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(COM_ERR_LIBRARY + NAMES + com_err + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + if (GSSAPI_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${GSSAPI_LIBRARY} + ) + endif (GSSAPI_LIBRARY) + + if (KRB5_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${KRB5_LIBRARY} + ) + endif (KRB5_LIBRARY) + + if (K5CRYPTO_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${K5CRYPTO_LIBRARY} + ) + endif (K5CRYPTO_LIBRARY) + + if (COM_ERR_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${COM_ERR_LIBRARY} + ) + endif (COM_ERR_LIBRARY) +endif (GSSAPI_FLAVOR_MIT) + +if (GSSAPI_FLAVOR_HEIMDAL) + find_library(GSSAPI_LIBRARY + NAMES + gssapi + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(KRB5_LIBRARY + NAMES + krb5 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(HCRYPTO_LIBRARY + NAMES + hcrypto + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(COM_ERR_LIBRARY + NAMES + com_err + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(HEIMNTLM_LIBRARY + NAMES + heimntlm + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(HX509_LIBRARY + NAMES + hx509 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(ASN1_LIBRARY + NAMES + asn1 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(WIND_LIBRARY + NAMES + wind + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(ROKEN_LIBRARY + NAMES + roken + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + if (GSSAPI_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${GSSAPI_LIBRARY} + ) + endif (GSSAPI_LIBRARY) + + if (KRB5_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${KRB5_LIBRARY} + ) + endif (KRB5_LIBRARY) + + if (HCRYPTO_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${HCRYPTO_LIBRARY} + ) + endif (HCRYPTO_LIBRARY) + + if (COM_ERR_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${COM_ERR_LIBRARY} + ) + endif (COM_ERR_LIBRARY) + + if (HEIMNTLM_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${HEIMNTLM_LIBRARY} + ) + endif (HEIMNTLM_LIBRARY) + + if (HX509_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${HX509_LIBRARY} + ) + endif (HX509_LIBRARY) + + if (ASN1_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${ASN1_LIBRARY} + ) + endif (ASN1_LIBRARY) + + if (WIND_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${WIND_LIBRARY} + ) + endif (WIND_LIBRARY) + + if (ROKEN_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${WIND_LIBRARY} + ) + endif (ROKEN_LIBRARY) +endif (GSSAPI_FLAVOR_HEIMDAL) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GSSAPI DEFAULT_MSG GSSAPI_LIBRARIES GSSAPI_INCLUDE_DIR) + +if(GSSAPI_FOUND) + if(_GSSAPI_FOUND) # via pkg-config + if (GSSAPI_FLAVOR_MIT) + set(GSSAPI_PC_REQUIRES ${_mit_modname}) + elseif (GSSAPI_FLAVOR_HEIMDAL) + set(GSSAPI_PC_REQUIRES ${_heimdal_modname}) + endif() + endif() +endif() + +# show the GSSAPI_INCLUDE_DIR and GSSAPI_LIBRARIES variables only in the advanced view +mark_as_advanced(GSSAPI_INCLUDE_DIR GSSAPI_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindMbedTLS.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindMbedTLS.cmake new file mode 100644 index 000000000000..7c5379c667bd --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindMbedTLS.cmake @@ -0,0 +1,143 @@ +# - Try to find mbedTLS +# Once done this will define +# +# MBEDTLS_FOUND - system has mbedTLS +# MBEDTLS_INCLUDE_DIRS - the mbedTLS include directory +# MBEDTLS_LIBRARIES - Link these to use mbedTLS +# MBEDTLS_DEFINITIONS - Compiler switches required for using mbedTLS +#============================================================================= +# Copyright (c) 2017 Sartura d.o.o. +# +# Author: Juraj Vijtiuk +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + + +set(_MBEDTLS_ROOT_HINTS + $ENV{MBEDTLS_ROOT_DIR} + ${MBEDTLS_ROOT_DIR}) + +set(_MBEDTLS_ROOT_PATHS + "$ENV{PROGRAMFILES}/libmbedtls") + +set(_MBEDTLS_ROOT_HINTS_AND_PATHS + HINTS ${_MBEDTLS_ROOT_HINTS} + PATHS ${_MBEDTLS_ROOT_PATHS}) + + +find_path(MBEDTLS_INCLUDE_DIR + NAMES + mbedtls/ssl.h + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_library(MBEDTLS_SSL_LIBRARY + NAMES + mbedtls + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + +) + +find_library(MBEDTLS_CRYPTO_LIBRARY + NAMES + mbedcrypto + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) + +find_library(MBEDTLS_X509_LIBRARY + NAMES + mbedx509 + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) + +set(MBEDTLS_LIBRARIES ${MBEDTLS_SSL_LIBRARY} ${MBEDTLS_CRYPTO_LIBRARY} + ${MBEDTLS_X509_LIBRARY}) + +# mbedtls 2.8 +if (MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" _mbedtls_version_str REGEX + "^#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"[0-9]+.[0-9]+.[0-9]+\"") + + string(REGEX REPLACE "^.*MBEDTLS_VERSION_STRING.*([0-9]+\\.[0-9]+\\.[0-9]+).*$" + "\\1" MBEDTLS_VERSION "${_mbedtls_version_str}") +endif() + +# mbedtls 3.6 +if (NOT MBEDTLS_VERSION AND MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h" _mbedtls_version_str REGEX + "^#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"[0-9]+.[0-9]+.[0-9]+\"") + + string(REGEX REPLACE "^.*MBEDTLS_VERSION_STRING.*([0-9]+\\.[0-9]+\\.[0-9]+).*$" + "\\1" MBEDTLS_VERSION "${_mbedtls_version_str}") +endif() + +include(FindPackageHandleStandardArgs) +if (MBEDTLS_VERSION) + find_package_handle_standard_args(MbedTLS + REQUIRED_VARS + MBEDTLS_INCLUDE_DIR + MBEDTLS_LIBRARIES + VERSION_VAR + MBEDTLS_VERSION + FAIL_MESSAGE + "Could NOT find mbedTLS, try to set the path to mbedTLS root folder + in the system variable MBEDTLS_ROOT_DIR" + ) +else (MBEDTLS_VERSION) + find_package_handle_standard_args(MbedTLS + "Could NOT find mbedTLS, try to set the path to mbedTLS root folder in + the system variable MBEDTLS_ROOT_DIR" + MBEDTLS_INCLUDE_DIR + MBEDTLS_LIBRARIES) +endif (MBEDTLS_VERSION) + +# show the MBEDTLS_INCLUDE_DIRS and MBEDTLS_LIBRARIES variables only in the advanced view +mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARIES) + +if(MBEDTLS_FOUND) + if(NOT TARGET MbedTLS::mbedcrypto) + add_library(MbedTLS::mbedcrypto UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedcrypto PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES MbedTLS::mbedcrypto + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_CRYPTO_LIBRARY}") + endif() + + if(NOT TARGET MbedTLS::mbedx509) + add_library(MbedTLS::mbedx509 UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedx509 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES MbedTLS::mbedx509 + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_X509_LIBRARY}") + endif() + + if(NOT TARGET MbedTLS::mbedtls) + add_library(MbedTLS::mbedtls UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedtls PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES MbedTLS::mbedtls + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_LIBRARY}") + endif() +endif() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindNSIS.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindNSIS.cmake new file mode 100644 index 000000000000..9f1ab176c00a --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindNSIS.cmake @@ -0,0 +1,54 @@ +# - Try to find NSIS +# Once done this will define +# +# NSIS_ROOT_PATH - Set this variable to the root installation of NSIS +# +# Read-Only variables: +# +# NSIS_FOUND - system has NSIS +# NSIS_MAKE - NSIS creator executable +# +#============================================================================= +# Copyright (c) 2010-2013 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +if (WIN32) + set(_x86 "(x86)") + + set(_NSIS_ROOT_PATHS + "$ENV{ProgramFiles}/NSIS" + "$ENV{ProgramFiles${_x86}}/NSIS" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS;Default]") + + find_path(NSIS_ROOT_PATH + NAMES + Include/Library.nsh + PATHS + ${_NSIS_ROOT_PATHS} + ) + mark_as_advanced(NSIS_ROOT_PATH) +endif (WIN32) + +find_program(NSIS_MAKE + NAMES + makensis + PATHS + ${NSIS_ROOT_PATH} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(NSIS DEFAULT_MSG NSIS_MAKE) + +if (NSIS_MAKE) + set(NSIS_FOUND TRUE) +endif (NSIS_MAKE) + +mark_as_advanced(NSIS_MAKE) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindNaCl.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindNaCl.cmake new file mode 100644 index 000000000000..b1a8da457d71 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindNaCl.cmake @@ -0,0 +1,61 @@ +# - Try to find NaCl +# Once done this will define +# +# NACL_FOUND - system has NaCl +# NACL_INCLUDE_DIRS - the NaCl include directory +# NACL_LIBRARIES - Link these to use NaCl +# NACL_DEFINITIONS - Compiler switches required for using NaCl +# +# Copyright (c) 2010 Andreas Schneider +# Copyright (c) 2013 Aris Adamantiadis +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +if (NACL_LIBRARIES AND NACL_INCLUDE_DIRS) + # in cache already + set(NACL_FOUND TRUE) +else (NACL_LIBRARIES AND NACL_INCLUDE_DIRS) + + find_path(NACL_INCLUDE_DIR + NAMES + nacl/crypto_box_curve25519xsalsa20poly1305.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + + find_library(NACL_LIBRARY + NAMES + nacl + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + set(NACL_INCLUDE_DIRS + ${NACL_INCLUDE_DIR} + ) + + if (NACL_LIBRARY) + set(NACL_LIBRARIES + ${NACL_LIBRARIES} + ${NACL_LIBRARY} + ) + endif (NACL_LIBRARY) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NaCl DEFAULT_MSG NACL_LIBRARIES NACL_INCLUDE_DIRS) + + # show the NACL_INCLUDE_DIRS and NACL_LIBRARIES variables only in the advanced view + mark_as_advanced(NACL_INCLUDE_DIRS NACL_LIBRARIES) + +endif (NACL_LIBRARIES AND NACL_INCLUDE_DIRS) + diff --git a/src/libs/libssh-0.12.2/cmake/Modules/Findlibfido2.cmake b/src/libs/libssh-0.12.2/cmake/Modules/Findlibfido2.cmake new file mode 100644 index 000000000000..7111a919c980 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/Findlibfido2.cmake @@ -0,0 +1,63 @@ +# - Try to find libfido2 +# Once done this will define +# +# LIBFIDO2_ROOT_DIR - Set this variable to the root installation of libfido2 +# +# Read-Only variables: +# LIBFIDO2_FOUND - system has libfido2 +# LIBFIDO2_INCLUDE_DIR - the libfido2 include directory +# LIBFIDO2_LIBRARIES - Link these to use libfido2 +# +# The libfido2 library provides support for communicating +# with FIDO2/U2F devices over USB/NFC. +# +# Copyright (c) 2025 Praneeth Sarode +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +set(_LIBFIDO2_ROOT_HINTS + $ENV{LIBFIDO2_ROOT_DIR} + ${LIBFIDO2_ROOT_DIR} +) + +set(_LIBFIDO2_ROOT_PATHS + "$ENV{PROGRAMFILES}/libfido2" +) + +set(_LIBFIDO2_ROOT_HINTS_AND_PATHS + HINTS ${_LIBFIDO2_ROOT_HINTS} + PATHS ${_LIBFIDO2_ROOT_PATHS} +) + +find_path(LIBFIDO2_INCLUDE_DIR + NAMES + fido.h + HINTS + ${_LIBFIDO2_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_library(LIBFIDO2_LIBRARY + NAMES + fido2 + HINTS + ${_LIBFIDO2_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + lib64 +) + +set(LIBFIDO2_LIBRARIES + ${LIBFIDO2_LIBRARY} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(libfido2 DEFAULT_MSG LIBFIDO2_LIBRARIES LIBFIDO2_INCLUDE_DIR) + +# show the LIBFIDO2_INCLUDE_DIR and LIBFIDO2_LIBRARIES variables only in the advanced view +mark_as_advanced(LIBFIDO2_INCLUDE_DIR LIBFIDO2_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/Findsofthsm.cmake b/src/libs/libssh-0.12.2/cmake/Modules/Findsofthsm.cmake new file mode 100644 index 000000000000..3a29b6d039bd --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/Findsofthsm.cmake @@ -0,0 +1,36 @@ +# - Try to find softhsm +# Once done this will define +# +# SOFTHSM_FOUND - system has softhsm +# SOFTHSM_LIBRARIES - Link these to use softhsm +# +#============================================================================= +# Copyright (c) 2019 Sahana Prasad +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + + +find_library(SOFTHSM2_LIBRARY + NAMES + softhsm2 +) + +if (SOFTHSM2_LIBRARY) + set(SOFTHSM_LIBRARIES + ${SOFTHSM_LIBRARIES} + ${SOFTHSM2_LIBRARY} + ) +endif (SOFTHSM2_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(softhsm DEFAULT_MSG SOFTHSM_LIBRARIES) + +# show the SOFTHSM_INCLUDE_DIR and SOFTHSM_LIBRARIES variables only in the advanced view +mark_as_advanced(SOFTHSM_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/GenerateMap.cmake b/src/libs/libssh-0.12.2/cmake/Modules/GenerateMap.cmake new file mode 100644 index 000000000000..c22dfbc3c330 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/GenerateMap.cmake @@ -0,0 +1,118 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# GenerateMap +# ----------- +# +# This is a helper script for FindABImap.cmake. +# +# Generates a symbols version script using the abimap tool. +# This script is run in build time to use the correct command depending on the +# existence of the file provided ``CURRENT_MAP``. +# +# If the file exists, the ``abimap update`` subcommand is used to update the +# existing map. Otherwise, the ``abimap new`` subcommand is used to create a new +# map file. +# +# If the file provided in ``CURRENT_MAP`` exists, it is copied to the +# ``OUTPUT_PATH`` before updating. +# This is required because ``abimap`` do not generate output if no symbols were +# changed when updating an existing file. +# +# Expected defined variables +# -------------------------- +# +# ``SYMBOLS``: +# Required file containing the symbols to be used as input. Usually this is +# the ``OUTPUT`` generated by ``extract_symbols()`` function provided in +# FindABImap.cmake +# +# ``RELEASE_NAME_VERSION``: +# Required, expects the library name and version information to be added to +# the symbols in the format ``library_name_1_2_3`` +# +# ``CURRENT_MAP``: +# Required, expects the path to the current map file (or the path were it +# should be) +# +# ``OUTPUT_PATH``: +# Required, expects the output file path. +# +# ``ABIMAP_EXECUTABLE``: +# Required, expects the path to the ``abimap`` tool. +# +# Optionally defined variables +# ---------------------------- +# +# ``FINAL``: +# If defined, will mark the modified set of symbols in the symbol version +# script as final, preventing later changes using ``abimap``. +# +# ``BREAK_ABI``: +# If defined, the build will not fail if symbols were removed. +# If defined and a symbol is removed, a new release is created containing +# all symbols from all released versions. This makes an incompatible release. +# + +if (NOT DEFINED RELEASE_NAME_VERSION) + message(SEND_ERROR "RELEASE_NAME_VERSION not defined") +endif() + +if (NOT DEFINED SYMBOLS) + message(SEND_ERROR "SYMBOLS not defined") +endif() + +if (NOT DEFINED CURRENT_MAP) + message(SEND_ERROR "CURRENT_MAP not defined") +endif() + +if (NOT DEFINED OUTPUT_PATH) + message(SEND_ERROR "OUTPUT_PATH not defined") +endif() + +if (NOT ABIMAP_EXECUTABLE) + message(SEND_ERROR "ABIMAP_EXECUTABLE not defined") +endif() + +set(ARGS_LIST) + +if (FINAL) + list(APPEND ARGS_LIST "--final") +endif() + +if (EXISTS ${CURRENT_MAP}) + if (BREAK_ABI) + list(APPEND ARGS_LIST "--allow-abi-break") + endif() + + execute_process( + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${CURRENT_MAP} ${OUTPUT_PATH} + COMMAND + ${ABIMAP_EXECUTABLE} update ${ARGS_LIST} + -r ${RELEASE_NAME_VERSION} + -i ${SYMBOLS} + -o ${OUTPUT_PATH} + ${CURRENT_MAP} + RESULT_VARIABLE result + ) +else () + execute_process( + COMMAND + ${ABIMAP_EXECUTABLE} new ${ARGS_LIST} + -r ${RELEASE_NAME_VERSION} + -i ${SYMBOLS} + -o ${OUTPUT_PATH} + RESULT_VARIABLE result + ) +endif() + +if (NOT "${result}" STREQUAL "0") + message(SEND_ERROR "Map generation failed") +endif() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/GetFilesList.cmake b/src/libs/libssh-0.12.2/cmake/Modules/GetFilesList.cmake new file mode 100644 index 000000000000..e3e8a2a8b807 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/GetFilesList.cmake @@ -0,0 +1,59 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# GetFilesList +# ------------ +# +# This is a helper script for FindABImap.cmake. +# +# Search in the provided directories for files matching the provided pattern. +# The list of files is then written to the output file. +# +# Expected defined variables +# -------------------------- +# +# ``DIRECTORIES``: +# Required, expects a list of directories paths. +# +# ``FILES_PATTERNS``: +# Required, expects a list of patterns to be used to search files +# +# ``OUTPUT_PATH``: +# Required, expects the output file path. + +if (NOT DEFINED DIRECTORIES) + message(SEND_ERROR "DIRECTORIES not defined") +endif() + +if (NOT DEFINED FILES_PATTERNS) + message(SEND_ERROR "FILES_PATTERNS not defined") +endif() + +if (NOT DEFINED OUTPUT_PATH) + message(SEND_ERROR "OUTPUT_PATH not defined") +endif() + +string(REPLACE " " ";" DIRECTORIES_LIST "${DIRECTORIES}") +string(REPLACE " " ";" FILES_PATTERNS_LIST "${FILES_PATTERNS}") + +# Create the list of expressions for the files +set(glob_expressions) +foreach(dir ${DIRECTORIES_LIST}) + foreach(exp ${FILES_PATTERNS_LIST}) + list(APPEND glob_expressions + "${dir}/${exp}" + ) + endforeach() +endforeach() + +# Create the list of files +file(GLOB files ${glob_expressions}) + +# Write to the output +file(WRITE ${OUTPUT_PATH} "${files}") diff --git a/src/libs/libssh-0.12.2/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake b/src/libs/libssh-0.12.2/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake new file mode 100644 index 000000000000..a2e948099269 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake @@ -0,0 +1,17 @@ +# - MACRO_ENSURE_OUT_OF_SOURCE_BUILD() +# MACRO_ENSURE_OUT_OF_SOURCE_BUILD() + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +macro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD _errorMessage) + + string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" _insource) + if (_insource) + message(SEND_ERROR "${_errorMessage}") + message(FATAL_ERROR "Remove the file CMakeCache.txt in ${CMAKE_SOURCE_DIR} first.") + endif (_insource) + +endmacro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD) diff --git a/src/libs/libssh-0.12.2/cmake/Toolchain-cross-m32.cmake b/src/libs/libssh-0.12.2/cmake/Toolchain-cross-m32.cmake new file mode 100644 index 000000000000..7918c604c622 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Toolchain-cross-m32.cmake @@ -0,0 +1,23 @@ +set(CMAKE_C_FLAGS "-m32" CACHE STRING "C compiler flags" FORCE) +set(CMAKE_CXX_FLAGS "-m32" CACHE STRING "C++ compiler flags" FORCE) + +set(LIB32 /usr/lib) # Fedora + +if(EXISTS /usr/lib32) + set(LIB32 /usr/lib32) # Arch, Solus +endif() + +set(CMAKE_SYSTEM_LIBRARY_PATH ${LIB32} CACHE STRING "system library search path" FORCE) +set(CMAKE_LIBRARY_PATH ${LIB32} CACHE STRING "library search path" FORCE) + +# this is probably unlikely to be needed, but just in case +set(CMAKE_EXE_LINKER_FLAGS "-m32 -L${LIB32}" CACHE STRING "executable linker flags" FORCE) +set(CMAKE_SHARED_LINKER_FLAGS "-m32 -L${LIB32}" CACHE STRING "shared library linker flags" FORCE) +set(CMAKE_MODULE_LINKER_FLAGS "-m32 -L${LIB32}" CACHE STRING "module linker flags" FORCE) + +# on Fedora and Arch and similar, point pkgconfig at 32 bit .pc files. We have +# to include the regular system .pc files as well (at the end), because some +# are not always present in the 32 bit directory +if(EXISTS ${LIB32}/pkgconfig) + set(ENV{PKG_CONFIG_LIBDIR} ${LIB32}/pkgconfig:/usr/share/pkgconfig:/usr/lib/pkgconfig:/usr/lib64/pkgconfig) +endiF() diff --git a/src/libs/libssh-0.12.2/config.h.cmake b/src/libs/libssh-0.12.2/config.h.cmake new file mode 100644 index 000000000000..14e1031cd2b0 --- /dev/null +++ b/src/libs/libssh-0.12.2/config.h.cmake @@ -0,0 +1,304 @@ +/* Name of package */ +#cmakedefine PACKAGE "${PROJECT_NAME}" + +/* Version number of package */ +#cmakedefine VERSION "${PROJECT_VERSION}" + +#cmakedefine SYSCONFDIR "${SYSCONFDIR}" +#cmakedefine BINARYDIR "${BINARYDIR}" +#cmakedefine SOURCEDIR "${SOURCEDIR}" + +/* Global configuration directory */ +#cmakedefine USR_GLOBAL_CONF_DIR "${USR_GLOBAL_CONF_DIR}" +#cmakedefine GLOBAL_CONF_DIR "${GLOBAL_CONF_DIR}" + +/* Global bind configuration file path */ +#cmakedefine USR_GLOBAL_BIND_CONFIG "${USR_GLOBAL_BIND_CONFIG}" +#cmakedefine GLOBAL_BIND_CONFIG "${GLOBAL_BIND_CONFIG}" + +/* Global client configuration file path */ +#cmakedefine USR_GLOBAL_CLIENT_CONFIG "${USR_GLOBAL_CLIENT_CONFIG}" +#cmakedefine GLOBAL_CLIENT_CONFIG "${GLOBAL_CLIENT_CONFIG}" + +/************************** HEADER FILES *************************/ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ARGP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_GLOB_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_VALGRIND_VALGRIND_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PTY_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UTMP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UTIL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LIBUTIL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UTIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IO_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IFADDRS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_AES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WSPIAPI_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_DES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_ECDH_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_EC_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_ECDSA_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PTHREAD_H 1 + +/* Define to 1 if you have elliptic curve cryptography in openssl */ +#cmakedefine HAVE_OPENSSL_ECC 1 + +/* Define to 1 if mbedTLS supports curve25519 */ +#cmakedefine HAVE_MBEDTLS_CURVE25519 1 + +/* Define to 1 if you have elliptic curve cryptography in gcrypt */ +#cmakedefine HAVE_GCRYPT_ECC 1 + +/* Define to 1 if you have elliptic curve cryptography */ +#cmakedefine HAVE_ECC 1 + +/* Define to 1 if you have gl_flags as a glob_t struct member */ +#cmakedefine HAVE_GLOB_GL_FLAGS_MEMBER 1 + +/* Define to 1 if you have gcrypt with ChaCha20/Poly1305 support */ +#cmakedefine HAVE_GCRYPT_CHACHA_POLY 1 + +/* Define to 1 if you have gcrypt with curve25519 support */ +#cmakedefine HAVE_GCRYPT_CURVE25519 + +/*************************** FUNCTIONS ***************************/ + +/* Define to 1 if you have the `EVP_chacha20' function. */ +#cmakedefine HAVE_OPENSSL_EVP_CHACHA20 1 + +/* Define to 1 if you have the `EVP_KDF_CTX_new_id' or `EVP_KDF_CTX_new` function. */ +#cmakedefine HAVE_OPENSSL_EVP_KDF_CTX 1 + +/* Define to 1 if you have the `FIPS_mode' function. */ +#cmakedefine HAVE_OPENSSL_FIPS_MODE 1 + +/* Define to 1 if you have the `snprintf' function. */ +#cmakedefine HAVE_SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf' function. */ +#cmakedefine HAVE__SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf_s' function. */ +#cmakedefine HAVE__SNPRINTF_S 1 + +/* Define to 1 if you have the `vsnprintf' function. */ +#cmakedefine HAVE_VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf' function. */ +#cmakedefine HAVE__VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf_s' function. */ +#cmakedefine HAVE__VSNPRINTF_S 1 + +/* Define to 1 if you have the `isblank' function. */ +#cmakedefine HAVE_ISBLANK 1 + +/* Define to 1 if you have the `strncpy' function. */ +#cmakedefine HAVE_STRNCPY 1 + +/* Define to 1 if you have the `strndup' function. */ +#cmakedefine HAVE_STRNDUP 1 + +/* Define to 1 if you have the `cfmakeraw' function. */ +#cmakedefine HAVE_CFMAKERAW 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#cmakedefine HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `poll' function. */ +#cmakedefine HAVE_POLL 1 + +/* Define to 1 if you have the `select' function. */ +#cmakedefine HAVE_SELECT 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +#cmakedefine HAVE_CLOCK_GETTIME 1 + +/* Define to 1 if you have the `ntohll' function. */ +#cmakedefine HAVE_NTOHLL 1 + +/* Define to 1 if you have the `htonll' function. */ +#cmakedefine HAVE_HTONLL 1 + +/* Define to 1 if you have the `strtoull' function. */ +#cmakedefine HAVE_STRTOULL 1 + +/* Define to 1 if you have the `__strtoull' function. */ +#cmakedefine HAVE___STRTOULL 1 + +/* Define to 1 if you have the `_strtoui64' function. */ +#cmakedefine HAVE__STRTOUI64 1 + +/* Define to 1 if you have the `glob' function. */ +#cmakedefine HAVE_GLOB 1 + +/* Define to 1 if you have the `explicit_bzero' function. */ +#cmakedefine HAVE_EXPLICIT_BZERO 1 + +/* Define to 1 if you have the `memset_explicit' function. */ +#cmakedefine HAVE_MEMSET_EXPLICIT 1 + +/* Define to 1 if you have the `memset_s' function. */ +#cmakedefine HAVE_MEMSET_S 1 + +/* Define to 1 if you have the `SecureZeroMemory' function. */ +#cmakedefine HAVE_SECURE_ZERO_MEMORY 1 + +/* Define to 1 if you have the `cmocka_set_test_filter' function. */ +#cmakedefine HAVE_CMOCKA_SET_TEST_FILTER 1 + +/* Define to 1 if we have support for blowfish */ +#cmakedefine HAVE_BLOWFISH 1 + +/* Define to 1 if we have support for ML-KEM in libgcrypt */ +#cmakedefine HAVE_GCRYPT_MLKEM 1 + +/* Define to 1 if we have support for ML-KEM in OpenSSL */ +#cmakedefine HAVE_OPENSSL_MLKEM 1 + +/* Define to 1 if we have support for ML-KEM1024 in either backend */ +#cmakedefine HAVE_MLKEM1024 1 + +/*************************** LIBRARIES ***************************/ + +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#cmakedefine HAVE_LIBCRYPTO 1 + +/* Define to 1 if you have the `gcrypt' library (-lgcrypt). */ +#cmakedefine HAVE_LIBGCRYPT 1 + +/* Define to 1 if you have the 'mbedTLS' library (-lmbedtls). */ +#cmakedefine HAVE_LIBMBEDCRYPTO 1 + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#cmakedefine HAVE_PTHREAD 1 + +/* Define to 1 if you have the `cmocka' library (-lcmocka). */ +#cmakedefine HAVE_CMOCKA 1 + +/* Define to 1 if you have the `libfido2' library (-lfido2). + * This is required for interacting with FIDO2/U2F devices over USB-HID. */ +#cmakedefine HAVE_LIBFIDO2 1 + +/**************************** OPTIONS ****************************/ + +#cmakedefine HAVE_GCC_THREAD_LOCAL_STORAGE 1 +#cmakedefine HAVE_MSC_THREAD_LOCAL_STORAGE 1 + +#cmakedefine HAVE_FALLTHROUGH_ATTRIBUTE 1 +#cmakedefine HAVE_UNUSED_ATTRIBUTE 1 +#cmakedefine HAVE_WEAK_ATTRIBUTE 1 + +#cmakedefine HAVE_CONSTRUCTOR_ATTRIBUTE 1 +#cmakedefine HAVE_DESTRUCTOR_ATTRIBUTE 1 + +#cmakedefine HAVE_GCC_VOLATILE_MEMORY_PROTECTION 1 + +#cmakedefine HAVE_COMPILER__FUNC__ 1 +#cmakedefine HAVE_COMPILER__FUNCTION__ 1 + +#cmakedefine HAVE_GCC_BOUNDED_ATTRIBUTE 1 + +/* Define to 1 if you want to enable GSSAPI */ +#cmakedefine WITH_GSSAPI 1 + +/* Define to 1 if you want to enable ZLIB */ +#cmakedefine WITH_ZLIB 1 + +/* Define to 1 if you want to enable SFTP */ +#cmakedefine WITH_SFTP 1 + +/* Define to 1 if you want to enable server support */ +#cmakedefine WITH_SERVER 1 + +/* Define to 1 if you want to enable DH group exchange algorithms */ +#cmakedefine WITH_GEX 1 + +/* Define to 1 if you want to enable insecure none cipher and MAC */ +#cmakedefine WITH_INSECURE_NONE 1 + +/* Define to 1 if you want to allow libssh to execute arbitrary commands from + * configuration files or options (match exec, proxy commands and OpenSSH-based + * proxy-jumps). */ +#cmakedefine WITH_EXEC 1 + +/* Define to 1 if you want to enable blowfish cipher support */ +#cmakedefine WITH_BLOWFISH_CIPHER 1 + +/* Define to 1 if you want to enable debug output for crypto functions */ +#cmakedefine DEBUG_CRYPTO 1 + +/* Define to 1 if you want to enable debug output for packet functions */ +#cmakedefine DEBUG_PACKET 1 + +/* Define to 1 if you want to enable pcap output support (experimental) */ +#cmakedefine WITH_PCAP 1 + +/* Define to 1 if you want to enable calltrace debug output */ +#cmakedefine DEBUG_CALLTRACE 1 + +/* Define to 1 if you want to enable NaCl support */ +#cmakedefine WITH_NACL 1 + +/* Define to 1 if you want to enable PKCS #11 URI support */ +#cmakedefine WITH_PKCS11_URI 1 + +/* Define to 1 if we want to build a support for PKCS #11 provider. */ +#cmakedefine WITH_PKCS11_PROVIDER 1 + +/* Define to 1 if you want to enable FIDO2/U2F support */ +#cmakedefine WITH_FIDO2 1 + +/*************************** ENDIAN *****************************/ + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#cmakedefine WORDS_BIGENDIAN 1 diff --git a/src/libs/libssh-0.12.2/doc/CMakeLists.txt b/src/libs/libssh-0.12.2/doc/CMakeLists.txt new file mode 100644 index 000000000000..0e7a95218b48 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/CMakeLists.txt @@ -0,0 +1,247 @@ +# +# Build the documentation +# +# To build the documentation with a local doxygen-awesome-css directory: +# +# cmake -S . -B obj \ +# -DDOXYGEN_AWESOME_CSS_DIR=/path/to/doxygen-awesome-css +# cmake --build obj --target docs +# +# The tarball can be downloaded from: +# https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v2.4.1.tar.gz +# +find_package(Doxygen) + +if (DOXYGEN_FOUND) + set(DOXYGEN_AWESOME_CSS_PROJECT + "https://github.com/jothepro/doxygen-awesome-css") + set(DOXYGEN_AWESOME_CSS_VERSION "2.4.1") + set(DOXYGEN_AWESOME_CSS_URL + "${DOXYGEN_AWESOME_CSS_PROJECT}/archive/refs/tags/v${DOXYGEN_AWESOME_CSS_VERSION}.tar.gz" + ) + + # Allow specifying a local doxygen-awesome-css directory (useful for + # packaging) + if (NOT DEFINED DOXYGEN_AWESOME_CSS_DIR) + # Custom target to download doxygen-awesome-css at build time + add_custom_target( + doxygen-awesome-css + COMMAND + ${CMAKE_COMMAND} -DURL=${DOXYGEN_AWESOME_CSS_URL} + -DDEST_DIR=${CMAKE_CURRENT_BINARY_DIR} + -DVERSION=${DOXYGEN_AWESOME_CSS_VERSION} -P + ${CMAKE_CURRENT_SOURCE_DIR}/fetch_doxygen_awesome.cmake + COMMENT "Fetching doxygen-awesome-css theme") + + set(AWESOME_CSS_DIR + "${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css-${DOXYGEN_AWESOME_CSS_VERSION}" + ) + else () + message( + STATUS + "Using doxygen-awesome-css from ${DOXYGEN_AWESOME_CSS_DIR}") + set(AWESOME_CSS_DIR "${DOXYGEN_AWESOME_CSS_DIR}") + endif () + + # Project title shown in documentation + set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME}) + # Project version number shown in documentation + set(DOXYGEN_PROJECT_NUMBER ${PROJECT_VERSION}) + # Brief description shown below project name + set(DOXYGEN_PROJECT_BRIEF "The SSH library") + # Project favicon (browser tab icon) + set(DOXYGEN_PROJECT_ICON ${CMAKE_CURRENT_SOURCE_DIR}/favicon.png) + + # Number of spaces used for indentation in code blocks + set(DOXYGEN_TAB_SIZE 4) + # Generate output optimized for C (vs C++) + set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) + # Enable parsing of markdown in comments + set(DOXYGEN_MARKDOWN_SUPPORT YES) + # Warn about undocumented members to improve documentation quality + set(DOXYGEN_WARN_IF_UNDOCUMENTED YES) + # Do not extract private class members + set(DOXYGEN_EXTRACT_PRIVATE NO) + if (WITH_INTERNAL_DOC) + # Include internal documentation + set(DOXYGEN_INTERNAL_DOCS YES) + else () + # Do not include internal documentation + set(DOXYGEN_INTERNAL_DOCS NO) + endif( WITH_INTERNAL_DOC) + # Disable built-in clipboard (using doxygen-awesome extension instead) + set(DOXYGEN_HTML_COPY_CLIPBOARD NO) + # Disable page outline panel (using interactive TOC extension instead) + set(DOXYGEN_PAGE_OUTLINE_PANEL NO) + + # Required configuration for doxygen-awesome-css theme Generate treeview + # sidebar for navigation + set(DOXYGEN_GENERATE_TREEVIEW YES) + # Enable default index pages + set(DOXYGEN_DISABLE_INDEX NO) + # Use top navigation bar instead of full sidebar (required for theme + # compatibility) + set(DOXYGEN_FULL_SIDEBAR NO) + # Use light color style (required for Doxygen >= 1.9.5) + set(DOXYGEN_HTML_COLORSTYLE LIGHT) + + # Disable diagram generation (not relevant for C projects) + set(DOXYGEN_HAVE_DOT NO) + set(DOXYGEN_CLASS_DIAGRAMS NO) + set(DOXYGEN_CALL_GRAPH NO) + set(DOXYGEN_CALLER_GRAPH NO) + + # Preprocessor defines to use when parsing code + set(DOXYGEN_PREDEFINED DOXYGEN WITH_SERVER WITH_SFTP + PRINTF_ATTRIBUTE\(x,y\)) + + # Exclude patterns for files we don't want to document + set(DOXYGEN_EXCLUDE_PATTERNS */src/external/* fe25519.h ge25519.h sc25519.h + blf.h) + # Exclude internal structures from documentation + set(DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS + chacha20_poly1305_keysched, + dh_ctx, + dh_ctx, + dh_keypair, + error_struct, + packet_struct, + pem_get_password_struct, + ssh_tokens_st, + sftp_attributes_struct, + sftp_client_message_struct, + sftp_dir_struct, + sftp_ext_struct, + sftp_file_struct, + sftp_message_struct, + sftp_packet_struct, + sftp_request_queue_struct, + sftp_session_struct, + sftp_status_message_struct, + ssh_agent_state_struct, + ssh_agent_struct, + ssh_auth_auto_state_struct, + ssh_auth_request, + ssh_bind_config_keyword_table_s, + ssh_bind_config_match_keyword_table_s, + ssh_bind_struct, + ssh_buffer_struct, + ssh_channel_callbacks_struct, + ssh_channel_read_termination_struct, + ssh_channel_request, + ssh_channel_request_open, + ssh_channel_struct, + ssh_cipher_struct, + ssh_common_struct, + ssh_config_keyword_table_s, + ssh_config_match_keyword_table_s, + ssh_connector_struct, + ssh_counter_struct, + ssh_crypto_struct, + ssh_event_fd_wrapper, + ssh_event_struct, + ssh_global_request, + ssh_gssapi_struct, + ssh_hmac_struct, + ssh_iterator, + ssh_kbdint_struct, + ssh_kex_struct, + ssh_key_struct, + ssh_knownhosts_entry, + ssh_list, + ssh_mac_ctx_struct, + ssh_message_struct, + ssh_packet_callbacks_struct, + ssh_packet_header, + ssh_poll_ctx_struct, + ssh_poll_handle_struct, + ssh_pollfd_struct, + ssh_private_key_struct, + ssh_public_key_struct, + ssh_scp_struct, + ssh_service_request, + ssh_session_struct, + ssh_signature_struct, + ssh_socket_struct, + ssh_string_struct, + ssh_threads_callbacks_struct, + ssh_timestamp) + set(DOXYGEN_EXCLUDE_SYMBOLS_MACRO + SSH_FXP*, + SSH_SOCKET*, + SERVERBANNER, + SOCKOPT_TYPE_ARG4, + SSH_FILEXFER*, + SSH_FXF*, + SSH_S_*, + SFTP_*, + NSS_BUFLEN_PASSWD, + CLOCK, + MAX_LINE_SIZE, + PKCS11_URI, + KNOWNHOSTS_MAXTYPES) + set(DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS + sftp_attributes, + sftp_client_message, + sftp_dir, + sftp_ext, + sftp_file, + sftp_message, + sftp_packet, + sftp_request_queue, + sftp_status_message, + sftp_statvfs_t, + poll_fn, + ssh_callback_int, + ssh_callback_data, + ssh_callback_int_int, + ssh_message_callback, + ssh_channel_callback_int, + ssh_channel_callback_data, + ssh_callbacks, + ssh_gssapi_select_oid_callback, + ssh_gssapi_accept_sec_ctx_callback, + ssh_gssapi_verify_mic_callback, + ssh_server_callbacks, + ssh_socket_callbacks, + ssh_packet_callbacks, + ssh_channel_callbacks, + ssh_bind, + ssh_bind_callbacks) + set(DOXYGEN_EXCLUDE_SYMBOLS + ${DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS} ${DOXYGEN_EXCLUDE_SYMBOLS_MACRO} + ${DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS}) + + # Custom layout file to rename "Topics" to "API Reference" and simplify + # navigation + set(DOXYGEN_LAYOUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml) + # Custom HTML header with doxygen-awesome extension initialization + set(DOXYGEN_HTML_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/header.html) + # Modern CSS theme for documentation with custom libssh.org color scheme + set(DOXYGEN_HTML_EXTRA_STYLESHEET + ${AWESOME_CSS_DIR}/doxygen-awesome.css + ${CMAKE_CURRENT_SOURCE_DIR}/doxygen-custom.css) + # JavaScript extensions: dark mode toggle, copy button, paragraph links, + # interactive TOC + set(DOXYGEN_HTML_EXTRA_FILES + ${AWESOME_CSS_DIR}/doxygen-awesome-darkmode-toggle.js + ${AWESOME_CSS_DIR}/doxygen-awesome-fragment-copy-button.js + ${AWESOME_CSS_DIR}/doxygen-awesome-paragraph-link.js + ${AWESOME_CSS_DIR}/doxygen-awesome-interactive-toc.js) + + set(_doxyfile_template "${CMAKE_BINARY_DIR}/CMakeDoxyfile.in") + set(_target_doxyfile "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.docs") + configure_file("${_doxyfile_template}" "${_target_doxyfile}") + + doxygen_add_docs(docs ${CMAKE_SOURCE_DIR}/include/libssh + ${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}) + + # Make docs depend on doxygen-awesome-css download (if not using local dir) + if (TARGET doxygen-awesome-css) + add_dependencies(docs doxygen-awesome-css) + endif () + + add_custom_target( + docs_coverage COMMAND ${CMAKE_SOURCE_DIR}/doc/doc_coverage.sh + ${CMAKE_BINARY_DIR}) +endif (DOXYGEN_FOUND) diff --git a/src/libs/libssh-0.12.2/doc/DoxygenLayout.xml b/src/libs/libssh-0.12.2/doc/DoxygenLayout.xml new file mode 100644 index 000000000000..14a905052165 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/DoxygenLayout.xml @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libs/libssh-0.12.2/doc/README.gitlab.freebsd.md b/src/libs/libssh-0.12.2/doc/README.gitlab.freebsd.md new file mode 100644 index 000000000000..65628d2d73a6 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/README.gitlab.freebsd.md @@ -0,0 +1,101 @@ +# Install a FreeBSD CI instance + +Install the following packages: + +``` +pkg install -y bash git gmake cmake cmocka openssl wget pkgconf ccache bash +``` + +Create gitlab-runner user: + +``` +pw group add -n gitlab-runner +pw user add -n gitlab-runner -g gitlab-runner -s /usr/local/bin/bash +mkdir /home/gitlab-runner +chown gitlab-runner:gitlab-runner /home/gitlab-runner +``` + +Get the gitlab-runner binary for freebsd: + +``` +wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-freebsd-amd64 +chmod +x /usr/local/bin/gitlab-runner +``` + +Create a log file and allow access: + +``` +touch /var/log/gitlab_runner.log && chown gitlab-runner:gitlab-runner /var/log/gitlab_runner.log +``` + +We need a start script to run it on boot: + +``` +mkdir -p /usr/local/etc/rc.d +cat > /usr/local/etc/rc.d/gitlab_runner << EOF +#!/usr/local/bin/bash +# PROVIDE: gitlab_runner +# REQUIRE: DAEMON NETWORKING +# BEFORE: +# KEYWORD: + +. /etc/rc.subr + +name="gitlab_runner" +rcvar="gitlab_runner_enable" + +load_rc_config $name + +user="gitlab-runner" +user_home="/home/gitlab-runner" +command="/usr/local/bin/gitlab-runner run" +pidfile="/var/run/${name}.pid" + +start_cmd="gitlab_runner_start" +stop_cmd="gitlab_runner_stop" +status_cmd="gitlab_runner_status" + +gitlab_runner_start() +{ + export USER=${user} + export HOME=${user_home} + + if checkyesno ${rcvar}; then + cd ${user_home} + /usr/sbin/daemon -u ${user} -p ${pidfile} ${command} > /var/log/gitlab_runner.log 2>&1 + fi +} + +gitlab_runner_stop() +{ + if [ -f ${pidfile} ]; then + kill `cat ${pidfile}` + fi +} + +gitlab_runner_status() +{ + if [ ! -f ${pidfile} ] || kill -0 `cat ${pidfile}`; then + echo "Service ${name} is not running." + else + echo "${name} appears to be running." + fi +} + +run_rc_command $1 +EOF +chmod +x /usr/local/etc/rc.d/gitlab_runner +``` + +Register your gitlab-runner with your gitlab project + +``` +su gitlab-runner -c 'gitlab-runner register' +``` + +Start the gitlab runner service: + +``` +sysrc -f /etc/rc.conf "gitlab_runner_enable=YES" +service gitlab_runner start +``` diff --git a/src/libs/libssh-0.12.2/doc/authentication.dox b/src/libs/libssh-0.12.2/doc/authentication.dox new file mode 100644 index 000000000000..a0b2df843c03 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/authentication.dox @@ -0,0 +1,378 @@ +/** +@page libssh_tutor_authentication Chapter 2: A deeper insight on authentication +@section authentication_details A deeper insight on authentication + +In our guided tour, we merely mentioned that the user needed to authenticate. +We didn't explain much in detail how that was supposed to happen. +This chapter explains better the four authentication methods: with public keys, +with a password, with challenges and responses (keyboard-interactive), and with +no authentication at all. + +If your software is supposed to connect to an arbitrary server, then you +might need to support all authentication methods. If your software will +connect only to a given server, then it might be enough for your software +to support only the authentication methods used by that server. If you are +the administrator of the server, it might be your call to choose those +authentication methods. + +It is not the purpose of this document to review in detail the advantages +and drawbacks of each authentication method. You are therefore invited +to read the abundant documentation on this topic to fully understand the +advantages and security risks linked to each method. + + +@subsection pubkeys Authenticating with public keys + +libssh is fully compatible with the openssh public and private keys. You +can either use the automatic public key authentication method provided by +libssh, or roll your own using the public key functions. + +The process of authenticating by public key to a server is the following: + - you scan a list of files that contain public keys. each key is sent to + the SSH server, until the server acknowledges a key (a key it knows can be + used to authenticate the user). + - then, you retrieve the private key for this key and send a message + proving that you know that private key. + - when several identity files are specified, then the order of processing of + these files is from the last-mentioned to the first one + (if specified in the ~/.ssh/config, then starting from the bottom to the top). + +The function ssh_userauth_autopubkey() does this using the available keys in +"~/.ssh/". The return values are the following: + - SSH_AUTH_ERROR: some serious error happened during authentication + - SSH_AUTH_DENIED: no key matched + - SSH_AUTH_SUCCESS: you are now authenticated + - SSH_AUTH_PARTIAL: some key matched but you still have to provide an other + mean of authentication (like a password). + +The ssh_userauth_publickey_auto() function also tries to authenticate using the +SSH agent, if you have one running, or the "none" method otherwise. + +If you wish to authenticate with public key by your own, follow these steps: + - Retrieve the public key with ssh_pki_import_pubkey_file(). + - Offer the public key to the SSH server using ssh_userauth_try_publickey(). + If the return value is SSH_AUTH_SUCCESS, the SSH server accepts to + authenticate using the public key and you can go to the next step. + - Retrieve the private key, using the ssh_pki_import_privkey_file() function. + If a passphrase is needed, either the passphrase specified as argument or + a callback will be used. + - Authenticate using ssh_userauth_publickey() with your private key. + - Do not forget cleaning up memory using ssh_key_free(). + +Here is a minimalistic example of public key authentication: + +@code +int authenticate_pubkey(ssh_session session) +{ + int rc; + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@see ssh_userauth_publickey_auto() +@see ssh_userauth_try_publickey() +@see ssh_userauth_publickey() +@see ssh_pki_import_pubkey_file() +@see ssh_pki_import_privkey_file() +@see ssh_key_free() + + +@subsection password Authenticating with a password + +The function ssh_userauth_password() serves the purpose of authenticating +using a password. It will return SSH_AUTH_SUCCESS if the password worked, +or one of other constants otherwise. It's your work to ask the password +and to deallocate it in a secure manner. + +If your server complains that the password is wrong, but you can still +authenticate using openssh's client (issuing password), it's probably +because openssh only accept keyboard-interactive. Switch to +keyboard-interactive authentication, or try to configure plain text passwords +on the SSH server. + +Here is a small example of password authentication: + +@code +int authenticate_password(ssh_session session) +{ + char *password = NULL; + int rc; + + password = getpass("Enter your password: "); + rc = ssh_userauth_password(session, NULL, password); + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@see ssh_userauth_password + + +@subsection keyb_int The keyboard-interactive authentication method + +The keyboard-interactive method is, as its name tells, interactive. The +server will issue one or more challenges that the user has to answer, +until the server takes an authentication decision. + +ssh_userauth_kbdint() is the the main keyboard-interactive function. +It will return SSH_AUTH_SUCCESS,SSH_AUTH_DENIED, SSH_AUTH_PARTIAL, +SSH_AUTH_ERROR, or SSH_AUTH_INFO, depending on the result of the request. + +The keyboard-interactive authentication method of SSH2 is a feature that +permits the server to ask a certain number of questions in an interactive +manner to the client, until it decides to accept or deny the login. + +To begin, you call ssh_userauth_kbdint() (just set user and submethods to +NULL) and store the answer. + +If the answer is SSH_AUTH_INFO, it means that the server has sent a few +questions that you should ask the user. You can retrieve these questions +with the following functions: ssh_userauth_kbdint_getnprompts(), +ssh_userauth_kbdint_getname(), ssh_userauth_kbdint_getinstruction(), and +ssh_userauth_kbdint_getprompt(). + +Set the answer for each question in the challenge using +ssh_userauth_kbdint_setanswer(). + +Then, call again ssh_userauth_kbdint() and start the process again until +these functions returns something else than SSH_AUTH_INFO. + +Here are a few remarks: + - Even the first call can return SSH_AUTH_DENIED or SSH_AUTH_SUCCESS. + - The server can send an empty question set (this is the default behavior + on my system) after you have sent the answers to the first questions. + You must still parse the answer, it might contain some + message from the server saying hello or such things. Just call + ssh_userauth_kbdint() until needed. + - The meaning of "name", "prompt", "instruction" may be a little + confusing. An explanation is given in the RFC section that follows. + +Here is a little note about how to use the information from +keyboard-interactive authentication, coming from the RFC itself (rfc4256): + +@verbatim + + 3.3 User Interface Upon receiving a request message, the client SHOULD + prompt the user as follows: A command line interface (CLI) client SHOULD + print the name and instruction (if non-empty), adding newlines. Then for + each prompt in turn, the client SHOULD display the prompt and read the + user input. + + A graphical user interface (GUI) client has many choices on how to prompt + the user. One possibility is to use the name field (possibly prefixed + with the application's name) as the title of a dialog window in which + the prompt(s) are presented. In that dialog window, the instruction field + would be a text message, and the prompts would be labels for text entry + fields. All fields SHOULD be presented to the user, for example an + implementation SHOULD NOT discard the name field because its windows lack + titles; it SHOULD instead find another way to display this information. If + prompts are presented in a dialog window, then the client SHOULD NOT + present each prompt in a separate window. + + All clients MUST properly handle an instruction field with embedded + newlines. They SHOULD also be able to display at least 30 characters for + the name and prompts. If the server presents names or prompts longer than 30 + characters, the client MAY truncate these fields to the length it can + display. If the client does truncate any fields, there MUST be an obvious + indication that such truncation has occurred. + + The instruction field SHOULD NOT be truncated. Clients SHOULD use control + character filtering as discussed in [SSH-ARCH] to avoid attacks by + including terminal control characters in the fields to be displayed. + + For each prompt, the corresponding echo field indicates whether or not + the user input should be echoed as characters are typed. Clients SHOULD + correctly echo/mask user input for each prompt independently of other + prompts in the request message. If a client does not honor the echo field + for whatever reason, then the client MUST err on the side of + masking input. A GUI client might like to have a checkbox toggling + echo/mask. Clients SHOULD NOT add any additional characters to the prompt + such as ": " (colon-space); the server is responsible for supplying all + text to be displayed to the user. Clients MUST also accept empty responses + from the user and pass them on as empty strings. +@endverbatim + +The following example shows how to perform keyboard-interactive authentication: + +@code +int authenticate_kbdint(ssh_session session) +{ + int rc; + + rc = ssh_userauth_kbdint(session, NULL, NULL); + while (rc == SSH_AUTH_INFO) + { + const char *name = NULL, *instruction = NULL; + int nprompts, iprompt; + + name = ssh_userauth_kbdint_getname(session); + instruction = ssh_userauth_kbdint_getinstruction(session); + nprompts = ssh_userauth_kbdint_getnprompts(session); + + if (strlen(name) > 0) + printf("%s\n", name); + if (strlen(instruction) > 0) + printf("%s\n", instruction); + for (iprompt = 0; iprompt < nprompts; iprompt++) + { + const char *prompt = NULL; + char echo; + + prompt = ssh_userauth_kbdint_getprompt(session, iprompt, &echo); + if (echo) + { + char buffer[128], *ptr; + + printf("%s", prompt); + if (fgets(buffer, sizeof(buffer), stdin) == NULL) + return SSH_AUTH_ERROR; + buffer[sizeof(buffer) - 1] = '\0'; + if ((ptr = strchr(buffer, '\n')) != NULL) + *ptr = '\0'; + if (ssh_userauth_kbdint_setanswer(session, iprompt, buffer) < 0) + return SSH_AUTH_ERROR; + memset(buffer, 0, strlen(buffer)); + } + else + { + char *ptr = NULL; + + ptr = getpass(prompt); + if (ssh_userauth_kbdint_setanswer(session, iprompt, ptr) < 0) + return SSH_AUTH_ERROR; + } + } + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + return rc; +} +@endcode + +@see ssh_userauth_kbdint() +@see ssh_userauth_kbdint_getnprompts() +@see ssh_userauth_kbdint_getname() +@see ssh_userauth_kbdint_getinstruction() +@see ssh_userauth_kbdint_getprompt() +@see ssh_userauth_kbdint_setanswer() + + +@subsection none Authenticating with "none" method + +The primary purpose of the "none" method is to get authenticated **without** +any credential. Don't do that, use one of the other authentication methods, +unless you really want to grant anonymous access. + +If the account has no password, and if the server is configured to let you +pass, ssh_userauth_none() might answer SSH_AUTH_SUCCESS. + +The following example shows how to perform "none" authentication: + +@code +int authenticate_none(ssh_session session) +{ + int rc; + + rc = ssh_userauth_none(session, NULL); + return rc; +} +@endcode + +@subsection auth_list Getting the list of supported authentications + +You are not meant to choose a given authentication method, you can +let the server tell you which methods are available. Once you know them, +you try them one after the other. + +The following example shows how to get the list of available authentication +methods with ssh_userauth_list() and how to use the result: + +@code +int test_several_auth_methods(ssh_session session) +{ + int method, rc; + + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_ERROR) { + return rc; + } + + method = ssh_userauth_list(session, NULL); + + if (method & SSH_AUTH_METHOD_NONE) + { // For the source code of function authenticate_none(), + // refer to the corresponding example + rc = authenticate_none(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_PUBLICKEY) + { // For the source code of function authenticate_pubkey(), + // refer to the corresponding example + rc = authenticate_pubkey(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_INTERACTIVE) + { // For the source code of function authenticate_kbdint(), + // refer to the corresponding example + rc = authenticate_kbdint(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_PASSWORD) + { // For the source code of function authenticate_password(), + // refer to the corresponding example + rc = authenticate_password(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + return SSH_AUTH_ERROR; +} +@endcode + + +@subsection banner Getting the banner + +The SSH server might send a banner, which you can retrieve with +ssh_get_issue_banner(), then display to the user. + +The following example shows how to retrieve and dispose the issue banner: + +@code +int display_banner(ssh_session session) +{ + int rc; + char *banner = NULL; + +/* + *** Does not work without calling ssh_userauth_none() first *** + *** That will be fixed *** +*/ + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_ERROR) + return rc; + + banner = ssh_get_issue_banner(session); + if (banner) + { + printf("%s\n", banner); + free(banner); + } + + return rc; +} +@endcode + +*/ diff --git a/src/libs/libssh-0.12.2/doc/command.dox b/src/libs/libssh-0.12.2/doc/command.dox new file mode 100644 index 000000000000..dde323489602 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/command.dox @@ -0,0 +1,100 @@ +/** +@page libssh_tutor_command Chapter 4: Passing a remote command +@section remote_command Passing a remote command + +Previous chapter has shown how to open a full shell session, with an attached +terminal or not. If you only need to execute a command on the remote end, +you don't need all that complexity. + +The method described here is suited for executing only one remote command. +If you need to issue several commands in a row, you should consider using +a non-interactive remote shell, as explained in previous chapter. + +@see shell + + +@subsection exec_remote Executing a remote command + +The first steps for executing a remote command are identical to those +for opening remote shells. You first need a SSH channel, and then +a SSH session that uses this channel: + +@code +int show_remote_files(ssh_session session) +{ + ssh_channel channel = NULL; + int rc; + + channel = ssh_channel_new(session); + if (channel == NULL) return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } +@endcode + +Once a session is open, you can start the remote command with +ssh_channel_request_exec(): + +@code + rc = ssh_channel_request_exec(channel, "ls -l"); + if (rc != SSH_OK) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return rc; + } +@endcode + +If the remote command displays data, you get them with ssh_channel_read(). +This function returns the number of bytes read. If there is no more +data to read on the channel, this function returns 0, and you can go to next step. +If an error has been encountered, it returns a negative value: + +@code + char buffer[256]; + int nbytes; + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) + { + if (fwrite(buffer, 1, nbytes, stdout) != nbytes) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } +@endcode + +Once you read the result of the remote command, you send an +end-of-file to the channel, close it, and free the memory +that it used: + +@code + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + +Warning: In a single channel, only ONE command can be executed! +If you want to executed multiple commands, allocate separate channels for +them or consider opening interactive shell. +Attempting to run multiple consecutive commands in one channel will fail. + + +*/ diff --git a/src/libs/libssh-0.12.2/doc/curve25519-sha256@libssh.org.txt b/src/libs/libssh-0.12.2/doc/curve25519-sha256@libssh.org.txt new file mode 100644 index 000000000000..04d88575fbe6 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/curve25519-sha256@libssh.org.txt @@ -0,0 +1,119 @@ +curve25519-sha256@libssh.org.txt Aris Adamantiadis + 21/9/2013 + +1. Introduction + +This document describes the key exchange method curve25519-sha256@libssh.org +for SSH version 2 protocol. It is provided as an alternative to the existing +key exchange mechanisms based on either Diffie-Hellman or Elliptic Curve Diffie- +Hellman [RFC5656]. +The reason is the following : During summer of 2013, revelations from ex- +consultant at NSA Edward Snowden gave proof that NSA willingly inserts backdoors +into software, hardware components and published standards. While it is still +believed that the mathematics behind ECC cryptography are still sound and solid, +some people (including Bruce Schneier [SCHNEIER]), showed their lack of confidence +in NIST-published curves such as nistp256, nistp384, nistp521, for which constant +parameters (including the generator point) are defined without explanation. It +is also believed that NSA had a word to say in their definition. These curves +are not the most secure or fastest possible for their key sizes [DJB], and +researchers think it is possible that NSA have ways of cracking NIST curves. +It is also interesting to note that SSH belongs to the list of protocols the NSA +claims to be able to eavesdrop. Having a secure replacement would make passive +attacks much harder if such a backdoor exists. + +However an alternative exists in the form of Curve25519. This algorithm has been +proposed in 2006 by DJB [Curve25519]. Its main strengths are its speed, its +constant-time run time (and resistance against side-channel attacks), and its +lack of nebulous hard-coded constants. + +The reference version being used in this document is the one described in +[Curve25519] as implemented in the library NaCl [NaCl]. +This document does not attempt to provide alternatives to the ecdsa-sha1-* +authentication keys. + +2. Key exchange + +The key exchange procedure is very similar to the one described chapter 4 of +[RFC5656]. Public ephemeral keys are transmitted over SSH encapsulated into +standard SSH strings. + +The following is an overview of the key exchange process: + +Client Server +------ ------ +Generate ephemeral key pair. +SSH_MSG_KEX_ECDH_INIT --------> + Verify that client public key + length is 32 bytes. + Generate ephemeral key pair. + Compute shared secret. + Generate and sign exchange hash. + <-------- SSH_MSG_KEX_ECDH_REPLY +Verify that server public key length is 32 bytes. +* Verify host keys belong to server. +Compute shared secret. +Generate exchange hash. +Verify server's signature. + +* Optional but strongly recommended as this protects against MITM attacks. + +This is implemented using the same messages as described in RFC5656 chapter 4 + +3. Method Name + +The name of this key exchange method is "curve25519-sha256@libssh.org". + +4. Implementation considerations + +The whole method is based on the curve25519 scalar multiplication. In this +method, a private key is a scalar of 256 bits, and a public key is a point +of 256 bits. + +4.1. Private key generation + +A 32 bytes private key should be generated for each new connection, + using a secure PRNG. The following actions must be done on the private key: + mysecret[0] &= 248; + mysecret[31] &= 127; + mysecret[31] |= 64; +In order to keep the key valid. However, many cryptographic libraries will do +this automatically. +It should be noted that, in opposition to NIST curves, no special validation +should be done to ensure the result is a valid and secure private key. + +4.2 Public key generation + +The 32 bytes public key of either a client or a server must be generated using +the 32 bytes private key and a common generator base. This base is defined as 9 +followed by all zeroes: + const unsigned char basepoint[32] = {9}; + +The public key is calculated using the cryptographic scalar multiplication: + const unsigned char privkey[32]; + unsigned char pubkey[32]; + crypto_scalarmult (pubkey, privkey, basepoint); +However some cryptographic libraries may provide a combined function: + crypto_scalarmult_base (pubkey, privkey); + +It should be noted that, in opposition to NIST curves, no special validation +should be done to ensure the received public keys are valid curves point. The +Curve25519 algorithm ensure that every possible public key maps to a valid +ECC Point. + +4.3 Shared secret generation + +The shared secret, k, is defined in SSH specifications to be a big integer. +This number is calculated using the following procedure: + + X is the 32 bytes point obtained by the scalar multiplication of the other + side's public key and the local private key scalar. + + The whole 32 bytes of the number X are then converted into a big integer k. + This conversion follows the network byte order. This step differs from + RFC5656. + +[RFC5656] https://tools.ietf.org/html/rfc5656 +[SCHNEIER] https://www.schneier.com/blog/archives/2013/09/the_nsa_is_brea.html#c1675929 +[DJB] https://cr.yp.to/talks/2013.05.31/slides-dan+tanja-20130531-4x3.pdf +[Curve25519] "Curve25519: new Diffie-Hellman speed records." + https://cr.yp.to/ecdh/curve25519-20060209.pdf diff --git a/src/libs/libssh-0.12.2/doc/doc_coverage.sh b/src/libs/libssh-0.12.2/doc/doc_coverage.sh new file mode 100644 index 000000000000..2f6532757bee --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/doc_coverage.sh @@ -0,0 +1,52 @@ +#!/bin/bash +################################################################################ +# .doc_coverage.sh # +# Script to detect overall documentation coverage of libssh. The script uses # +# doxygen to generate the documentation then parses it's output. # +# # +# maintainer: Norbert Pocs # +################################################################################ +BUILD_DIR="$1" +DOXYFILE_PATH="$BUILD_DIR/doc/Doxyfile.docs" +INDEX_XML_PATH="$BUILD_DIR/doc/xml/index.xml" +# filters +F_EXCLUDE_FILES=' wrapper.h legacy.h crypto.h priv.h chacha.h curve25519.h ' +F_UNDOC_FUNC='(function).*is not documented' +F_FUNC='kind="function"' +F_HEADERS='libssh_8h_|group__libssh__' +F_CUT_BEFORE='.*' +F_CUT_AFTER='<\/name><\/member>' +# Doxygen options +O_QUIET='QUIET=YES' +O_GEN_XML='GENERATE_XML=YES' + +# check if build dir given +if [ $# -eq 0 ]; then + echo "Please provide the build directory e.g.: ./build" + exit 255 +fi + +# modify doxyfile to our needs: +# QUIET - less output +# GENERATE_XML - xml needed to inspect all the functions +# (note: the options are needed to be on separate lines) +# We want to exclude irrelevant files +MOD_DOXYFILE=$(cat "$DOXYFILE_PATH"; echo "$O_QUIET"; echo "$O_GEN_XML") +MOD_DOXYFILE=${MOD_DOXYFILE//EXCLUDE_PATTERNS.*=/EXCLUDE_PATTERNS=$F_EXCLUDE_FILES/g} + +# call doxygen to get the warning messages +# and also generate the xml for inspection +DOXY_WARNINGS=$(echo "$MOD_DOXYFILE" | doxygen - 2>&1) + +# get the number of undocumented functions +UNDOC_FUNC=$(echo "$DOXY_WARNINGS" | grep -cE "$F_UNDOC_FUNC") + +# filter out the lines consisting of functions of our interest +FUNC_LINES=$(grep "$F_FUNC" "$INDEX_XML_PATH" | grep -E "$F_HEADERS") +# cut the irrelevant information and leave just the function names +ALL_FUNC=$(echo "$FUNC_LINES" | sed -e "s/$F_CUT_BEFORE//g" -e "s/$F_CUT_AFTER//") +# remove duplicates and get the number of functions +ALL_FUNC=$(echo "$ALL_FUNC" | sort - | uniq | wc -l) + +# percentage of the documented functions +awk "BEGIN {printf \"Documentation coverage is %.2f%\n\", 100 - (${UNDOC_FUNC}/${ALL_FUNC}*100)}" diff --git a/src/libs/libssh-0.12.2/doc/doxygen-custom.css b/src/libs/libssh-0.12.2/doc/doxygen-custom.css new file mode 100644 index 000000000000..0c68d5759328 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/doxygen-custom.css @@ -0,0 +1,127 @@ +/** + * Custom color scheme for libssh documentation + * Based on libssh.org color palette + */ + +html { + /* Primary colors - using libssh.org orange accent */ + --primary-color: #F78C40; + --primary-dark-color: #f57900; + --primary-light-color: #fab889; + + /* Accent color - neutral gray */ + --primary-lighter-color: #5A5A5A; + + /* Page colors - clean white background */ + --page-background-color: #ffffff; + --page-foreground-color: #333333; + --page-secondary-foreground-color: #666666; + + /* Links - use the warm orange color */ + --link-color: #F78C40; + --link-hover-color: #f0690a; + + /* Code blocks and fragments - very light background */ + --code-background: #f9f9f9; + --fragment-background: #f9f9f9; + + /* Borders - subtle light grey */ + --separator-color: #e0e0e0; + --border-light-color: #f0f0f0; + + /* Side navigation - pure white */ + --side-nav-background: #ffffff; + + /* Menu colors - warm orange accent */ + --menu-selected-background: #F78C40; + + /* Tables and boxes - lighter */ + --tablehead-background: #fbc7a2; + --tablehead-foreground: #333333; +} + +/* Header styling with libssh brand colors */ +#titlearea { + background-color: #5A5A5A; + background-image: linear-gradient(to right, #5A5A5A, #6a6a6a); + border-bottom: 3px solid #F78C40; +} + +#projectname { + color: #ffffff !important; +} + +#projectbrief { + color: #fab889 !important; +} + +/* Top navigation tabs */ +#top { + background: linear-gradient(to bottom, #5A5A5A 0%, #6a6a6a 100%); +} + +.tabs, .tabs2, .tabs3 { + background-image: none; + background-color: transparent; +} + +.tablist li { + background: rgba(255, 255, 255, 0.1); + border-right: 1px solid rgba(255, 255, 255, 0.2); +} + +.tablist li:hover { + background: rgba(255, 255, 255, 0.2); +} + +.tablist li.current { + background: #F78C40; + border-bottom: 3px solid #f57900; +} + +/* Tab text colors - comprehensive selectors */ +#nav-path ul li a, +.tabs a, +.tabs2 a, +.tabs3 a, +.tablist a, +.tablist a:link, +.tablist a:visited, +.tablist li a, +#main-nav a, +.sm > li > a, +.sm > li > a .sub-arrow { + color: #ffffff !important; + text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3); +} + +/* Active/current tab text */ +#nav-path ul li.current a, +.tabs .current a, +.tabs2 .current a, +.tabs3 .current a, +.tablist .current a, +.tablist .current a:link, +.tablist .current a:visited, +.tablist li.current a, +#main-nav .current a, +.sm .current a { + color: #333333 !important; + text-shadow: none; +} + +/* Dropdown arrow - white color for top menu */ +.sm-dox a span.sub-arrow { + border-right-color: #ffffff !important; + border-bottom-color: #ffffff !important; +} + +/* Dropdown menu text - must be dark on white background */ +/* Make this as specific as possible to override white color */ +.sm-dox > li > ul > li > a, +.sm-dox li ul li a, +.sm-dox ul li a, +#main-menu ul li a { + color: #333333 !important; + text-shadow: none !important; +} diff --git a/src/libs/libssh-0.12.2/doc/favicon.png b/src/libs/libssh-0.12.2/doc/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..0b343c153696a9f8056d33a7899f55e7974db93a GIT binary patch literal 858 zcmV-g1Eu_lP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iph@ z3MeA;oxjKc00P=cL_t(|+U=S_OB+EH$A7JDAl)DdDTExvDD=7}o_eauCunZH6+Pz= zJ;_3$Ef)4#4?Sml?9CoL8|@_*quCEo0~JD`!BDadm`e{C3qmYfXLct#|MSj!^ZxVR zoA=%h7#J8B7#J8B+*3RVOHtrZ23`V1AUENYHc$t?S}r?xf`Gsw39JKEAbEqOePA2- zXu0&Kl7PUW2-L1P?dzfqtXVE~Rr`FX$oO}_-Ytzstltyzro#gQhZ?Y|V6UF#vZl2M z1P<$pj0ad1^7Zt9z#$JDg^;)lmdmk@9#9ME=BjBI0)fLSkPnG~ywInW2UJ5Y;Hui> zfWRRKoJ3H?E?6#Y9Xy~MQ5j#>B?OitB49}u0&)=%kkf^Lsm7zQ6m^C%nmvo7UV_dr zM(6iqI>Q**_&M1pBeL;x3h4pM>40qf{O%CY>?Qbe@|1(_?3KiHhB3fm_cR5-i_9-x zE&OCTJ(yksI>Q(rT5~je){WO5bZ0r}&a#{ic(e3FsYl4j0sE6q{q?tJ|7HATIhK%< zxR&0i2)NXk?4G8mG!_|*W~7|ZU^GLevG_MUuF@N6HyAja0m)`BL8Y;%%w0b{|9+7d z`j$&dr)$s~jAqz8oL8E#HV@}7Jj>Iq8QR`z%}I0ne}%(VYYxT(Q+);^wrv*~&j7+Tr`&4(B5xAg>DnQw>9aA;1tIw;K!*$GQ;E ziim)gE(GjEM8J*~1c-9f3)y&2Mj0$K34RWl0BQQ9H1OSSx1Gskav@cPp6~n8<^eLV z!3|LRqD#o%(6SB?F<;4L?MKIZ}Necx}Z-d|{srN!*9D6Yzt37_nX`o1nZ k(HR&R7#J8B7#K{zAABI*9=!!IUjP6A07*qoM6N<$f($ivp8x;= literal 0 HcmV?d00001 diff --git a/src/libs/libssh-0.12.2/doc/fetch_doxygen_awesome.cmake b/src/libs/libssh-0.12.2/doc/fetch_doxygen_awesome.cmake new file mode 100644 index 000000000000..1fa896740047 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/fetch_doxygen_awesome.cmake @@ -0,0 +1,41 @@ +# Script to download doxygen-awesome-css at build time +# +# Usage: +# cmake -P fetch_doxygen_awesome.cmake \ +# -DURL= \ +# -DDEST_DIR= \ +# -DVERSION= + +if(NOT DEFINED URL) + message(FATAL_ERROR "URL not specified") +endif() +if(NOT DEFINED DEST_DIR) + message(FATAL_ERROR "DEST_DIR not specified") +endif() +if(NOT DEFINED VERSION) + message(FATAL_ERROR "VERSION not specified") +endif() + +set(EXTRACT_DIR "${DEST_DIR}/doxygen-awesome-css-${VERSION}") + +if(NOT EXISTS "${EXTRACT_DIR}/doxygen-awesome.css") + message(STATUS "Downloading doxygen-awesome-css ${VERSION}...") + set(TARBALL "${DEST_DIR}/doxygen-awesome-css.tar.gz") + file(DOWNLOAD + "${URL}" + "${TARBALL}" + STATUS download_status + SHOW_PROGRESS + ) + list(GET download_status 0 status_code) + if(NOT status_code EQUAL 0) + list(GET download_status 1 error_msg) + message(FATAL_ERROR "Download failed: ${error_msg}") + endif() + message(STATUS "Extracting doxygen-awesome-css...") + file(ARCHIVE_EXTRACT + INPUT "${TARBALL}" + DESTINATION "${DEST_DIR}" + ) + file(REMOVE "${TARBALL}") +endif() diff --git a/src/libs/libssh-0.12.2/doc/fido2.dox b/src/libs/libssh-0.12.2/doc/fido2.dox new file mode 100644 index 000000000000..98f1ca1344ab --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/fido2.dox @@ -0,0 +1,601 @@ +/** + +@page libssh_tutor_fido2 Chapter 11: FIDO2/U2F Keys Support + +@section fido2_intro Introduction + +The traditional SSH public key model stores the private key on disk +and anyone who obtains that file (and possibly its passphrase) can impersonate +the user. FIDO2 authenticators, such as USB security keys, are hardware tokens +that generate or securely store private key material within a secure element +and may require explicit user interaction such as a touch, PIN, or biometric +verification for use. Hence, security keys are far safer from theft or +exfiltration than traditional file-based SSH keys. libssh provides support +for FIDO2/U2F security keys as hardware-backed SSH authentication credentials. + +This chapter explains the concepts, build prerequisites, the API, and +usage patterns for enrolling (creating) and using security key-backed SSH +keys, including resident (discoverable) credentials. + +@subsection fido2_resident_keys Resident Keys + +Two credential storage modes exist for security keys: + + - Non-resident (default): A credential ID (key handle) and metadata are + stored on the client-side in a key file. This key handle must be + presented to the FIDO2/U2F device while signing. This is somewhat + similar to traditional SSH keys, except that the key handle is not the + private key itself, but used in combination with the device's master key + to derive the actual private key. + + - Resident (discoverable): The credential (and metadata like user id) is + stored on the device. No local file is needed; the device can enumerate or + locate the credential internally when queried. + +Advantages of resident keys include portability (using the same device +across hosts) and resilience (no loss if the local machine is destroyed). +Although, they may be limited by the storage of the authenticator. + +@subsection fido2_presence_verification User Presence vs. User Verification + +FIDO2 distinguishes between: + + - User Presence (UP): A simple physical interaction (touch) to confirm a + human is present. + + - User Verification (UV): Verification of the user’s identity through + biometric authentication or a PIN. + +Requiring UV provides additional protection if the device is stolen +and used without the PIN/biometric. + +libssh exposes flags controlling these requirements (see below). + +@subsection fido2_callbacks The Callback Abstraction + +Different environments may need to access security keys through different +transport layers (e.g., USB-HID, NFC, Bluetooth, etc.). To accommodate +this variability, libssh does not hard-code a single implementation. + +Instead, it defines a small callback interface (`ssh_sk_callbacks`) used for all +security key operations. Any implementation of this callback interface can be used +by higher-level PKI functions to perform enroll/sign/load_resident_keys +operations without needing to know the transport specifics. Hence, users can +define their own implementations for these callbacks to support different +transport protocols or custom hardware. Refer @ref fido2_custom_callbacks +for additional details. + +The callback interface is defined in `libssh/callbacks.h` and the behaviour +and return values are specified by `libssh/sk_api.h`, which is the same +interface defined by OpenSSH for its security key support. This means that +any callback implementations (also called "middleware" in OpenSSH terminology) +developed for OpenSSH can be adapted to libssh with minimal changes. + +The following operations are abstracted by the callback interface: + + - api_version(): Report the version of the SK API that the callback implementation + is based on, so that libssh can check whether this implementation would be + compatible with the SK API version that it supports. + Refer @ref fido2_custom_callbacks_version for additional details. + - enroll(): Create (enroll) a new credential, returning public key, key + handle, attestation data. + - sign(): Produce a signature for supplied inputs using an existing key + handle. + - load_resident_keys(): Enumerate resident (discoverable) credentials stored + on the authenticator. + +libssh provides a default implementation of the `ssh_sk_callbacks` using +the libfido2 library for the USB-HID transport protocol. Hence, by default, +libssh can interact with any FIDO2/U2F device that supports USB-HID and is +compatible with libfido2, without requiring any additional modifications. + +@subsection fido2_build Building with FIDO2 Support + +To enable FIDO2/U2F support, libssh must be built with the WITH_FIDO2 +build option as follows: + +@verbatim + cmake -DWITH_FIDO2=ON .. +@endverbatim + +libssh will also build the default USB-HID `ssh_sk_callbacks`, if the +libfido2 library and headers are installed on your system. + +@warning If built without libfido2, support for interacting with FIDO2/U2F +devices over USB-HID will not be available. + +@subsection fido2_api_overview API Overview + +Security key operations are configured through the `ssh_pki_ctx` +which allows to specify both general PKI options and FIDO2-specific +options such as the sk_callbacks, challenge data, application string, flags, etc. + +The following sections describe the options that can be configured and how +the `ssh_pki_ctx` is used in conjunction with `ssh_key` to perform +enrollment, signing, and resident key loading operations. + +@subsection fido2_key_objects Security Key Objects & Metadata + +Security keys are surfaced as `ssh_key` objects of type +`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519` (corresponding to the +OpenSSH public key algorithm names `sk-ecdsa-sha2-nistp256@openssh.com` and +`sk-ssh-ed25519@openssh.com`). In addition to standard key handling, libssh +exposes the following helper functions to retrieve embedded SK metadata: + + - ssh_key_get_sk_application(): Returns the relying party / application + (RP ID) string. The Relying Party ID (RP ID) is a string + that identifies the application or service requesting key enrollment. It + ensures that a credential is bound to a specific origin, preventing + phishing across sites. During registration, the authenticator associates + the credential with this RP ID so that it can later only be used for + authentication requests from the same relying party. For SSH keys, the + common format is "ssh:user@host". + + - ssh_key_get_sk_user_id(): Returns a copy of the user ID associated with a key + which represents a unique identifier for the user within the relying + party (application) context. It is typically a string (such as an + email, or a random identifier) that helps distinguish credentials + belonging to different users for the same application. + + Though the user ID can be binary data according to the FIDO2 spec, libssh only + supports NUL-terminated strings for enrolling new keys in order to remain compatible + with the OpenSSH's sk-api interface. + + However, libssh does support loading existing resident keys with user IDs containing + arbitrary binary data. It does so by using an `ssh_string` to store the loaded key's + user_id, and an `ssh_string` can contain arbitrary binary data that can not be stored + in a traditional NUL-terminated string (like null bytes). + + @note The user_id is NOT stored in the key file for non-resident keys. It is only + available for resident (discoverable) keys loaded from the authenticator via + ssh_sk_resident_keys_load(). For keys imported from files, this function returns + NULL. + + - ssh_key_get_sk_flags(): Returns the flags associated with the key. The + following are the supported flags and they can be combined using + bitwise OR: + - SSH_SK_USER_PRESENCE_REQD : Require user presence (touch). + - SSH_SK_USER_VERIFICATION_REQD : Require user verification + (PIN/biometric). + - SSH_SK_RESIDENT_KEY : Request a resident discoverable credential. + - SSH_SK_FORCE_OPERATION : Force resident (discoverable) credential + creation even if one with same application and user_id already + exists. + +These functions perform no additional communication with the +authenticator, this metadata is captured during enrollment/loading and +cached in the `ssh_key`. + +@subsection fido2_options Setting Security Key Context Options + +Options are set via ssh_pki_ctx_options_set(). + +Representative security key options: + - SSH_PKI_OPTION_SK_APPLICATION (const char *): Required relying party ID + If not set, a default value of "ssh:" is used. + - SSH_PKI_OPTION_SK_FLAGS (uint8_t *): Flags described above. If not set, + defaults to SSH_SK_USER_PRESENCE_REQD. This is because OpenSSH `sshd` + requires user presence for security key authentication by default. + - SSH_PKI_OPTION_SK_USER_ID (const char *): Represents a unique identifier + for the user within the relying party (application) context. + It is typically a string (such as an email, or a random identifier) that + helps distinguish credentials belonging to different users for the same + application. If not set, defaults to 64 zeros. + - SSH_PKI_OPTION_SK_CHALLENGE (ssh_buffer): Custom challenge; if omitted a + random 32-byte challenge is generated. + - SSH_PKI_OPTION_SK_CALLBACKS (ssh_sk_callbacks): Replace the default + callbacks with custom callbacks. + +PIN callback: Use ssh_pki_ctx_set_sk_pin_callback() to register a function +matching `ssh_auth_callback` to prompt for and supply a PIN. The callback may +be called multiple times to ask for the pin depending on the authenticator policy. + +Callback options: Callback implementations may accept additional configuration +name/value options such as the path to the fido device. These options can be provided via +`ssh_pki_ctx_sk_callbacks_option_set()`. Refer @ref fido2_custom_callbacks_options +for additional details. + +The built-in callback implementation provided by libssh supports additional options, +with their names defined in `libssh.h` prefixed with `SSH_SK_OPTION_NAME_*`, such as: + +SSH_SK_OPTION_NAME_DEVICE_PATH: Used for specifying a device path. +If the device path is not specified and multiple devices are connected, then +depending upon the operation and the flags set, the callback implementation may +automatically select a suitable device, or the user may be prompted to touch the +device they want to use. + +SSH_SK_OPTION_NAME_USER_ID: Used for setting the user ID. +Note that the user ID can also be set using the ssh_pki_ctx_options_set() API. + +@subsection fido2_enrollment Enrollment Example + +An enrollment operation creates a new credential on the authenticator and +returns an ssh_key object representing it. The application and user_id +fields are required for creating the credential. The other options are +optional. A successful enrollment returns the public key, key handle, and +metadata which are stored in the ssh_key object, and may optionally return +attestation data which is used for verifying the authenticator model and +firmware version. + +Below is a simple example enrolling an Ed25519 security key (non-resident) +requiring user presence only: + +@code +#include +#include + +static int pin_cb(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void)prompt; + (void)echo; + (void)verify; + (void)userdata; + + /* In a real application, the user would be prompted to enter the PIN */ + const char *pin = "4242"; + size_t l = strlen(pin); + if (l + 1 > len) { + return SSH_ERROR; + } + + memcpy(buf, pin, l + 1); + return SSH_OK; +} + +int enroll_sk_key() +{ + const char *app = "ssh:user@host"; + const char *user_id = "alice"; + uint8_t flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD; + const char *device_path = "/dev/hidraw6"; /* Optional device path */ + + ssh_pki_ctx pki_ctx = ssh_pki_ctx_new(); + ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_APPLICATION, app); + ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_USER_ID, user_id); + ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_FLAGS, &flags); + + ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); + + ssh_pki_ctx_sk_callbacks_option_set(pki_ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + device_path, + true); + + ssh_key enrolled = NULL; + int rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ED25519, + pki_ctx, + &enrolled); /* produces sk-ed25519 key */ + + /* Save enrolled key using ssh_pki_export_privkey_file, retrieve attestation + * buffer etc. */ + + /* Free context and key when done */ +} +@endcode + +After a successful enrollment, you can retrieve the attestation buffer +(if provided by the authenticator) from the PKI context: + +@code +ssh_buffer att_buf = NULL; +rc = ssh_pki_ctx_get_sk_attestation_buffer(pki_ctx, &att_buf); +if (rc == SSH_OK && att_buf != NULL) { + /* att_buf now contains the serialized attestation + * ("ssh-sk-attest-v01"). You can inspect, save, or + * parse the buffer as needed + */ + ssh_buffer_free(att_buf); +} +@endcode + +Notes: +- The attestation buffer is only populated if the enrollment operation + succeeds and the authenticator provides attestation data. +- `ssh_pki_ctx_get_sk_attestation_buffer()` returns a copy of the attestation + buffer; the caller must free it with `ssh_buffer_free()`. + +@subsection fido2_signing Authenticating with a Stored Security Key Public Key + +To authenticate using a security key, the application typically loads the +previously enrolled sk-* private key, establishes an SSH connection, and +calls `ssh_userauth_publickey()`. libssh automatically recognizes security +key types and transparently handles the required hardware-backed +authentication steps such as prompting for a touch or PIN using the +configured security key callbacks. + +Example: +@code +#include +#include + +int auth_with_sk_file(const char *host, + const char *user, + const char *privkey_path) +{ + ssh_session session = NULL; + ssh_key privkey = NULL; + int rc = SSH_ERROR; + + session = ssh_new(); + ssh_options_set(session, SSH_OPTIONS_HOST, host); + ssh_options_set(session, SSH_OPTIONS_USER, user); + ssh_connect(session); + + ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, &privkey); + + ssh_pki_ctx pki_ctx = ssh_pki_ctx_new(); + /* Optionally set PIN callback, device path, etc. */ + /* ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); */ + + ssh_options_set(session, SSH_OPTIONS_PKI_CONTEXT, pki_ctx); + + rc = ssh_userauth_publickey(session, user, privkey); + if (rc == SSH_AUTH_SUCCESS) { + printf("Authenticated with security key.\n"); + rc = SSH_OK; + } else { + fprintf(stderr, + "Authentication failed rc=%d err=%s\n", + rc, + ssh_get_error(session)); + rc = SSH_ERROR; + } + + /* Free resources */ +} +@endcode + +@subsection fido2_resident Resident Key Enumeration + +Resident keys stored on the device can be discovered and loaded with +ssh_sk_resident_keys_load() which takes a PKI context (configured with +a PIN callback) and returns each key as an ssh_key and the number of keys loaded. + +Example: + +@code +#include +#include +#include + +static int pin_cb(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void)prompt; + (void)echo; + (void)verify; + (void)userdata; + const char *pin = "4242"; + size_t l = strlen(pin); + + if (l + 1 > len) { + return SSH_ERROR; + } + + memcpy(buf, pin, l + 1); + return SSH_OK; +} + +int auth_with_resident(const char *host, + const char *user, + const char *application, + const char *user_id) +{ + ssh_pki_ctx pki_ctx = NULL; + size_t num_found = 0; + ssh_key *keys = NULL; + ssh_key final_key = NULL; + int rc = SSH_ERROR; + + ssh_string cur_application = NULL; + ssh_string cur_user_id = NULL; + ssh_string expected_application = NULL; + ssh_string expected_user_id = NULL; + + pki_ctx = ssh_pki_ctx_new(); + ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); + + expected_application = ssh_string_from_char(application); + expected_user_id = ssh_string_from_char(user_id); + + rc = ssh_sk_resident_keys_load(pki_ctx, &keys, &num_found); + for (size_t i = 0; i < num_found; i++) { + cur_application = ssh_key_get_sk_application(keys[i]); + cur_user_id = ssh_key_get_sk_user_id(keys[i]); + + if (ssh_string_cmp(cur_application, expected_application) == 0 && + ssh_string_cmp(cur_user_id, expected_user_id) == 0) { + SSH_STRING_FREE(cur_application); + SSH_STRING_FREE(cur_user_id); + final_key = keys[i]; + break; + } + + SSH_STRING_FREE(cur_application); + SSH_STRING_FREE(cur_user_id); + } + + SSH_STRING_FREE(expected_application); + SSH_STRING_FREE(expected_user_id); + + /* Continue with authentication using the ssh_key with + * ssh_userauth_publickey as usual, and free resources when done. */ +} +@endcode + +@subsection fido2_sshsig Signing using the sshsig API + +Security keys can also be used for general-purpose signing of arbitrary data +(without SSH authentication) using the existing `sshsig_sign()` and `sshsig_verify()` +functions. These functions work seamlessly with security key types +(`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519`) and will automatically +invoke the configured security key callbacks to perform hardware-backed signing +operations. + +@subsection fido2_custom_callbacks Implementing Custom Callback Implementations + +Users may need to implement custom callback implementations to support +different transport protocols (e.g., NFC, Bluetooth) beyond the default USB-HID +support. This section describes how to implement and integrate custom callback +implementations. + +To implement custom callbacks, you must include the following headers: + +@code +#include /* For ssh_sk_callbacks_struct */ +#include /* For SK API constants and data structures */ +@endcode + +The `libssh/sk_api.h` header provides the complete interface specification including +request/response structures, flags, and version macros. + +@subsubsection fido2_custom_callbacks_version API Version Compatibility + +libssh validates callback implementations by checking the API version returned by +the `api_version()` callback. To ensure compatibility, libssh compares the major +version (upper 16 bits) of the returned value with `LIBSSH_SK_API_VERSION_MAJOR`. +If they don't match, libssh will reject the callback implementation. +This ensures that the callbacks' SK API matches the major version expected by libssh, +while allowing minor version differences. + +@subsubsection fido2_custom_callbacks_implementation Implementation Example + +Here's a minimal example of defining and using custom callbacks: + +@code +#include +#include +#include + +/* Your custom API version callback */ +static uint32_t my_sk_api_version(void) +{ + /* Match the major version, set your own minor version */ + return SSH_SK_VERSION_MAJOR | 0x0001; +} + +/* Your custom enroll callback */ +static int my_sk_enroll(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response) +{ + /* Parse options array to extract custom parameters */ + if (options != NULL) { + for (size_t i = 0; options[i] != NULL; i++) { + if (strcmp(options[i]->name, "my_custom_option") == 0) { + /* Use options[i]->value */ + } + } + } + + /* Implement your enroll logic here */ + /* ... */ + + return SSH_SK_ERR_GENERAL; /* Return appropriate error code */ +} + +/* Implement other required callbacks: sign, load_resident_keys */ +/* ... */ + +/* Define your callback structure */ +static struct ssh_sk_callbacks_struct my_sk_callbacks = { + .size = sizeof(struct ssh_sk_callbacks_struct), + .api_version = my_sk_api_version, + .enroll = my_sk_enroll, + .sign = my_sk_sign, /* Your implementation */ + .load_resident_keys = my_sk_load_resident_keys, /* Your implementation */ +}; + +/* Usage example */ +void use_custom_callbacks(void) +{ + ssh_pki_ctx pki_ctx = ssh_pki_ctx_new(); + + /* Set your custom callbacks */ + ssh_pki_ctx_options_set(pki_ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + &my_sk_callbacks); + + /* Pass custom options to your callbacks */ + ssh_pki_ctx_sk_callbacks_option_set(pki_ctx, + "my_custom_option", + "my_custom_value", + false); + + /* Use the context for enrollment, signing, etc. */ +} +@endcode + +@subsubsection fido2_custom_callbacks_options Passing Custom Options + +The `ssh_pki_ctx_sk_callbacks_option_set()` function allows you to pass +implementation-specific options as name/value string pairs: + +@code +ssh_pki_ctx_sk_callbacks_option_set(pki_ctx, + "option_name", + "option_value", + required); +@endcode + +Parameters: +- `option_name`: The name of the option (e.g., "device_path", "my_custom_param") +- `option_value`: The string value for this option +- `required`: If true, this option must be processed by the callback implementation + and cannot be ignored. If false, the option is advisory and can be skipped if the + callback implementation does not support it. + +These options are passed to your callbacks in the `struct sk_option **options` +parameter as a NULL-terminated array. Each `sk_option` has the following fields: +- `name`: The option name (char *) +- `value`: The option value (char *) +- `required`: Whether the option must be processed (uint8_t, non-zero = required) + +@subsubsection fido2_custom_callbacks_openssh OpenSSH Middleware Compatibility + +Since libssh uses the same SK API as OpenSSH, middleware implementations developed +for OpenSSH can be adapted with minimal changes. +To adapt an OpenSSH middleware for libssh, create a wrapper that populates +`ssh_sk_callbacks_struct` with pointers to the middleware's functions. + +@subsection fido2_testing Testing and Environment Variables + +Unit tests covering USB-HID enroll/sign/load_resident_keys operations can be found +in the `tests/unittests/torture_sk_usbhid.c` file. To run these tests you +must have libfido2 installed and the WITH_FIDO2=ON build option set. +Additionally, you must ensure the following: + + - An actual FIDO2 device must be connected to the test machine. + - The TORTURE_SK_USBHID environment variable must be set. + - The environment variable TORTURE_SK_PIN= must be set. + +If these are not set, the tests are skipped. + +The higher level PKI integration tests can be found in +`tests/unittests/torture_pki_sk.c` and the tests related to the sshsig API +can be found in `tests/unittests/torture_pki_sshsig.c`. +These use the callback implementation provided by OpenSSH's sk-dummy.so, +which simulates an authenticator without requiring any hardware. Hence, these tests +can be run in the CI environment. +However, these tests can also be configured to use the default USB-HID callbacks +by setting the same environment variables as described above. + +The following devices were tested during development: + +- Yubico Security Key NFC - USB-A + +*/ diff --git a/src/libs/libssh-0.12.2/doc/forwarding.dox b/src/libs/libssh-0.12.2/doc/forwarding.dox new file mode 100644 index 000000000000..3ca3aa8a0f2f --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/forwarding.dox @@ -0,0 +1,236 @@ +/** +@page libssh_tutor_forwarding Chapter 7: Forwarding connections (tunnel) +@section forwarding_connections Forwarding connections + +Port forwarding comes in SSH protocol in two different flavours: +direct or reverse port forwarding. Direct port forwarding is also +named local port forwarding, and reverse port forwarding is also called +remote port forwarding. SSH also allows X11 tunnels. + + + +@subsection forwarding_direct Direct port forwarding + +Direct port forwarding is from client to server. The client opens a tunnel, +and forwards whatever data to the server. Then, the server connects to an +end point. The end point can reside on another machine or on the SSH +server itself. + +Example of use of direct port forwarding: +@verbatim +Mail client application Google Mail + | ^ + 5555 (arbitrary) | + | 143 (IMAP2) + V | + SSH client =====> SSH server + +Legend: +--P-->: port connections through port P +=====>: SSH tunnel +@endverbatim +A mail client connects to port 5555 of a client. An encrypted tunnel is +established to the server. The server connects to port 143 of Google Mail (the +end point). Now the local mail client can retrieve mail. + + +@subsection forwarding_reverse Reverse port forwarding + +The reverse forwarding is slightly different. It goes from server to client, +even though the client has the initiative of establishing the tunnel. +Once the tunnel is established, the server will listen on a port. Whenever +a connection to this port is made, the server forwards the data to the client. + +Example of use of reverse port forwarding: +@verbatim + Local mail server Mail client application + ^ | + | 5555 (arbitrary) + 143 (IMAP2) | + | V + SSH client <===== SSH server + +Legend: +--P-->: port connections through port P +=====>: SSH tunnel +@endverbatim +In this example, the SSH client establishes the tunnel, +but it is used to forward the connections established at +the server to the client. + + +@subsection forwarding_x11 X11 tunnels + +X11 tunnels allow a remote application to display locally. + +Example of use of X11 tunnels: +@verbatim + Local display Graphical application + (X11 server) (X11 client) + ^ | + | V + SSH client <===== SSH server + +Legend: +----->: X11 connection through X11 display number +=====>: SSH tunnel +@endverbatim +The SSH tunnel is established by the client. + +How to establish X11 tunnels with libssh has already been described in +this tutorial. + +@see x11 + + +@subsection libssh_direct Doing direct port forwarding with libssh + +To do direct port forwarding, call function ssh_channel_open_forward(): + - you need a separate channel for the tunnel as first parameter; + - second and third parameters are the remote endpoint; + - fourth and fifth parameters are sent to the remote server + so that they can be logged on that server. + +If you don't plan to forward the data you will receive to any local port, +just put fake values like "localhost" and 5555 as your local host and port. + +The example below shows how to open a direct channel that would be +used to retrieve google's home page from the remote SSH server. + +@code +int direct_forwarding(ssh_session session) +{ + ssh_channel forwarding_channel = NULL; + int rc = SSH_ERROR; + char *http_get = "GET / HTTP/1.1\nHost: www.google.com\n\n"; + int nbytes, nwritten; + + forwarding_channel = ssh_channel_new(session); + if (forwarding_channel == NULL) { + return rc; + } + + rc = ssh_channel_open_forward(forwarding_channel, + "www.google.com", 80, + "localhost", 5555); + if (rc != SSH_OK) + { + ssh_channel_free(forwarding_channel); + return rc; + } + + nbytes = strlen(http_get); + nwritten = ssh_channel_write(forwarding_channel, + http_get, + nbytes); + if (nbytes != nwritten) + { + ssh_channel_free(forwarding_channel); + return SSH_ERROR; + } + + ... + + ssh_channel_free(forwarding_channel); + return SSH_OK; +} +@endcode + +The data sent by Google can be retrieved for example with ssh_select() +and ssh_channel_read(). Goggle's home page can then be displayed on the +local SSH client, saved into a local file, made available on a local port, +or whatever use you have for it. + + +@subsection libssh_reverse Doing reverse port forwarding with libssh + +To do reverse port forwarding, call ssh_channel_listen_forward(), +then ssh_channel_accept_forward(). + +When you call ssh_channel_listen_forward(), you can let the remote server +chose the non-privileged port it should listen to. Otherwise, you can chose +your own privileged or non-privileged port. Beware that you should have +administrative privileges on the remote server to open a privileged port +(port number < 1024). + +Below is an example of a very rough web server waiting for connections on port +8080 of remote SSH server. The incoming connections are passed to the +local libssh application, which handles them: + +@code +int web_server(ssh_session session) +{ + int rc; + ssh_channel channel = NULL; + char buffer[256]; + int nbytes, nwritten; + int port = 0; + char *peer_address = NULL; + int peer_port = 0; + char *helloworld = "" +"HTTP/1.1 200 OK\n" +"Content-Type: text/html\n" +"Content-Length: 113\n" +"\n" +"\n" +" \n" +" Hello, World!\n" +" \n" +" \n" +"

Hello, World!

\n" +" \n" +"\n"; + + rc = ssh_channel_listen_forward(session, NULL, 8080, NULL); + if (rc != SSH_OK) + { + fprintf(stderr, "Error opening remote port: %s\n", + ssh_get_error(session)); + return rc; + } + + channel = ssh_channel_open_forward_port(session, 60000, &port, + &peer_address, &peer_port); + if (channel == NULL) + { + fprintf(stderr, "Error waiting for incoming connection: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + while (1) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) + { + fprintf(stderr, "Error reading incoming data: %s\n", + ssh_get_error(session)); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + ssh_string_free_char(peer_address); + return SSH_ERROR; + } + if (strncmp(buffer, "GET /", 5)) continue; + + nbytes = strlen(helloworld); + nwritten = ssh_channel_write(channel, helloworld, nbytes); + if (nwritten != nbytes) + { + fprintf(stderr, "Error sending answer: %s\n", + ssh_get_error(session)); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + ssh_string_free_char(peer_address); + return SSH_ERROR; + } + printf("Sent answer to %s:%d\n", peer_address, peer_port); + } + + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + ssh_string_free_char(peer_address); + return SSH_OK; +} +@endcode + +*/ diff --git a/src/libs/libssh-0.12.2/doc/guided_tour.dox b/src/libs/libssh-0.12.2/doc/guided_tour.dox new file mode 100644 index 000000000000..4169d60a59ba --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/guided_tour.dox @@ -0,0 +1,490 @@ +/** +@page libssh_tutor_guided_tour Chapter 1: A typical SSH session +@section ssh_session A typical SSH session + +A SSH session goes through the following steps: + + - Before connecting to the server, you can set up if you wish one or other + server public key authentication, i.e. RSA, ED25519 or ECDSA. You can choose + cryptographic algorithms you trust and compression algorithms if any. You + must of course set up the hostname. + + - The connection is established. A secure handshake is made, and resulting from + it, a public key from the server is gained. You MUST verify that the public + key is legitimate, using for instance the MD5 fingerprint or the known hosts + file. + + - The client must authenticate: the classical ways are password, or + public keys (from ecdsa, ed25519 and rsa key-pairs generated by openssh). + If a SSH agent is running, it is possible to use it. + + - Now that the user has been authenticated, you must open one or several + channels. Channels are different subways for information into a single ssh + connection. Each channel has a standard stream (stdout) and an error stream + (stderr). You can theoretically open an infinity of channels. + + - With the channel you opened, you can do several things: + - Execute a single command. + - Open a shell. You may want to request a pseudo-terminal before. + - Invoke the sftp subsystem to transfer files. + - Invoke the scp subsystem to transfer files. + - Invoke your own subsystem. This is outside the scope of this document, + but can be done. + + - When everything is finished, just close the channels, and then the connection. + +The sftp and scp subsystems use channels, but libssh hides them to +the programmer. If you want to use those subsystems, instead of a channel, +you'll usually open a "sftp session" or a "scp session". + + +@subsection setup Creating the session and setting options + +The most important object in a SSH connection is the SSH session. In order +to allocate a new SSH session, you use ssh_new(). Don't forget to +always verify that the allocation succeeded. +@code +#include +#include + +int main() +{ + ssh_session my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + ... + ssh_free(my_ssh_session); +} +@endcode + +libssh follows the allocate-it-deallocate-it pattern. Each object that you allocate +using xxxxx_new() must be deallocated using xxxxx_free(). In this case, ssh_new() +does the allocation and ssh_free() does the contrary. + +The ssh_options_set() function sets the options of the session. The most important options are: + - SSH_OPTIONS_HOST: the name of the host you want to connect to + - SSH_OPTIONS_PORT: the used port (default is port 22) + - SSH_OPTIONS_USER: the system user under which you want to connect + - SSH_OPTIONS_LOG_VERBOSITY: the quantity of messages that are printed + +The complete list of options can be found in the documentation of ssh_options_set(). +The only mandatory option is SSH_OPTIONS_HOST. If you don't use SSH_OPTIONS_USER, +the local username of your account will be used. + +Here is a small example of how to use it: + +@code +#include +#include + +int main() +{ + ssh_session my_ssh_session = NULL; + int verbosity = SSH_LOG_PROTOCOL; + int port = 22; + + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(my_ssh_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(my_ssh_session, SSH_OPTIONS_PORT, &port); + + ... + + ssh_free(my_ssh_session); +} +@endcode + +Please notice that all parameters are passed to ssh_options_set() as pointers, +even if you need to set an integer value. + +@see ssh_new +@see ssh_free +@see ssh_options_set +@see ssh_options_parse_config +@see ssh_options_copy +@see ssh_options_getopt + + +@subsection connect Connecting to the server + +Once all settings have been made, you can connect using ssh_connect(). That +function will return SSH_OK if the connection worked, SSH_ERROR otherwise. + +You can get the English error string with ssh_get_error() in order to show the +user what went wrong. Then, use ssh_disconnect() when you want to stop +the session. + +Here's an example: + +@code +#include +#include +#include + +int main() +{ + ssh_session my_ssh_session = NULL; + int rc; + + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + + rc = ssh_connect(my_ssh_session); + if (rc != SSH_OK) + { + fprintf(stderr, "Error connecting to localhost: %s\n", + ssh_get_error(my_ssh_session)); + exit(-1); + } + + ... + + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); +} +@endcode + + +@subsection serverauth Authenticating the server + +Once you're connected, the following step is mandatory: you must check that the server +you just connected to is known and safe to use (remember, SSH is about security and +authentication). + +There are two ways of doing this: + - The first way (recommended) is to use the ssh_session_is_known_server() + function. This function will look into the known host file + (~/.ssh/known_hosts on UNIX), look for the server hostname's pattern, + and determine whether this host is present or not in the list. + - The second way is to use ssh_get_pubkey_hash() to get a binary version + of the public key hash value. You can then use your own database to check + if this public key is known and secure. + +You can also use the ssh_get_pubkey_hash() to show the public key hash +value to the user, in case he knows what the public key hash value is +(some paranoid people write their public key hash values on paper before +going abroad, just in case ...). + +If the remote host is being used to for the first time, you can ask the user whether +he/she trusts it. Once he/she concluded that the host is valid and worth being +added in the known hosts file, you use ssh_write_knownhost() to register it in +the known hosts file, or any other way if you use your own database. + +Note: When GSSAPI key exchange is used, the server identity is already verified +via mutual GSSAPI authentication initiated by the SSH client. In such cases, +most SSH servers won't send their host keys at all. If it fits your use case +(e.g., you don't expect the server to send a host key), you can use +ssh_session_kex_is_gss() to check if GSSAPI key exchange indeed took place +and skip the knownhosts check. + +The following example is part of the examples suite available in the +examples/ directory: + +@code +#include +#include + +int verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + unsigned char *hash = NULL; + ssh_key srv_pubkey = NULL; + size_t hlen; + char buf[10]; + char *p = NULL; + int cmp; + int rc; + + /* If GSSAPI key exchange was used, the server identity was already + * verified via Kerberos mutual authentication (MIC). We might skip + * the host key verification, especially if we don't expect the server + * to send its key. Alternatively, we could proceed without this check + * and handle the scenario when the server does not provide its host key + * later. In that case, ssh_session_is_known_server will return + * SSH_KNOWN_HOSTS_UNKNOWN. + */ + if (ssh_session_kex_is_gss(session)) { + return 0; + } + + rc = ssh_get_server_publickey(session, &srv_pubkey); + if (rc < 0) { + return -1; + } + + rc = ssh_get_publickey_hash(srv_pubkey, + SSH_PUBLICKEY_HASH_SHA256, + &hash, + &hlen); + ssh_key_free(srv_pubkey); + if (rc < 0) { + return -1; + } + + state = ssh_session_is_known_server(session); + switch (state) { + case SSH_KNOWN_HOSTS_OK: + /* OK */ + + break; + case SSH_KNOWN_HOSTS_CHANGED: + fprintf(stderr, "Host key for server changed: it is now:\n"); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + fprintf(stderr, "For security reasons, connection will be stopped\n"); + ssh_clean_pubkey_hash(&hash); + + return -1; + case SSH_KNOWN_HOSTS_OTHER: + fprintf(stderr, "The host key for this server was not found but an other" + "type of key exists.\n"); + fprintf(stderr, "An attacker might change the default server key to" + "confuse your client into thinking the key does not exist\n"); + ssh_clean_pubkey_hash(&hash); + + return -1; + case SSH_KNOWN_HOSTS_NOT_FOUND: + fprintf(stderr, "Could not find known host file.\n"); + fprintf(stderr, "If you accept the host key here, the file will be" + "automatically created.\n"); + + /* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */ + + case SSH_KNOWN_HOSTS_UNKNOWN: + fprintf(stderr,"The server is unknown. Do you trust the host key?\n"); + fprintf(stderr, "Public key hash: "); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + ssh_clean_pubkey_hash(&hash); + p = fgets(buf, sizeof(buf), stdin); + if (p == NULL) { + return -1; + } + + cmp = strncasecmp(buf, "yes", 3); + if (cmp != 0) { + return -1; + } + + rc = ssh_session_update_known_hosts(session); + if (rc < 0) { + fprintf(stderr, "Error %s\n", strerror(errno)); + return -1; + } + + break; + case SSH_KNOWN_HOSTS_ERROR: + fprintf(stderr, "Error %s", ssh_get_error(session)); + ssh_clean_pubkey_hash(&hash); + return -1; + } + + ssh_clean_pubkey_hash(&hash); + return 0; +} +@endcode + +@see ssh_connect +@see ssh_disconnect +@see ssh_get_error +@see ssh_get_error_code +@see ssh_get_server_publickey +@see ssh_get_publickey_hash +@see ssh_session_is_known_server +@see ssh_session_update_known_hosts + + +@subsection auth Authenticating the user + +The authentication process is the way a service provider can identify a +user and verify his/her identity. The authorization process is about enabling +the authenticated user the access to resources. In SSH, the two concepts +are linked. After authentication, the server can grant the user access to +several resources such as port forwarding, shell, sftp subsystem, and so on. + +libssh supports several methods of authentication: + - "none" method. This method allows to get the available authentications + methods. It also gives the server a chance to authenticate the user with + just his/her login. Some very old hardware uses this feature to fallback + the user on a "telnet over SSH" style of login. + - password method. A password is sent to the server, which accepts it or not. + - keyboard-interactive method. The server sends several challenges to the + user, who must answer correctly. This makes possible the authentication + via a codebook for instance ("give code at 23:R on page 3"). + - public key method. The host knows the public key of the user, and the + user must prove he knows the associated private key. This can be done + manually, or delegated to the SSH agent as we'll see later. + +All these methods can be combined. You can for instance force the user to +authenticate with at least two of the authentication methods. In that case, +one speaks of "Partial authentication". A partial authentication is a +response from authentication functions stating that your credential was +accepted, but yet another one is required to get in. + +The example below shows an authentication with password: + +@code +#include +#include +#include + +int main() +{ + ssh_session my_ssh_session = NULL; + int rc; + char *password = NULL; + + // Open session and set options + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + + // Connect to server + rc = ssh_connect(my_ssh_session); + if (rc != SSH_OK) + { + fprintf(stderr, "Error connecting to localhost: %s\n", + ssh_get_error(my_ssh_session)); + ssh_free(my_ssh_session); + exit(-1); + } + + // Verify the server's identity + // For the source code of verify_knownhost(), check previous example + if (verify_knownhost(my_ssh_session) < 0) + { + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); + exit(-1); + } + + // Authenticate ourselves + password = getpass("Password: "); + rc = ssh_userauth_password(my_ssh_session, NULL, password); + if (rc != SSH_AUTH_SUCCESS) + { + fprintf(stderr, "Error authenticating with password: %s\n", + ssh_get_error(my_ssh_session)); + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); + exit(-1); + } + + ... + + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); +} +@endcode + +@see @ref authentication_details + + +@subsection using_ssh Doing something + +At this point, the authenticity of both server and client is established. +Time has come to take advantage of the many possibilities offered by the SSH +protocol: execute a remote command, open remote shells, transfer files, +forward ports, etc. + +The example below shows how to execute a remote command: + +@code +int show_remote_processes(ssh_session session) +{ + ssh_channel channel = NULL; + int rc; + char buffer[256]; + int nbytes; + + channel = ssh_channel_new(session); + if (channel == NULL) + return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } + + rc = ssh_channel_request_exec(channel, "ps aux"); + if (rc != SSH_OK) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return rc; + } + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) + { + if (write(1, buffer, nbytes) != (unsigned int) nbytes) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + +Each ssh_channel_request_exec() needs to be run on freshly created +and connected (with ssh_channel_open_session()) channel. + +@see @ref opening_shell +@see @ref remote_command +@see @ref sftp_subsystem +@see @ref scp_subsystem + + +@subsection errors Handling the errors + +All the libssh functions which return an error value also set an English error message +describing the problem. + +Error values are typically SSH_ERROR for integer values, or NULL for pointers. + +The function ssh_get_error() returns a pointer to the static error message. + +ssh_error_code() returns the error code number : SSH_NO_ERROR, +SSH_REQUEST_DENIED, SSH_INVALID_REQUEST, SSH_CONNECTION_LOST, SSH_FATAL, +or SSH_INVALID_DATA. SSH_REQUEST_DENIED means the ssh server refused your +request, but the situation is recoverable. The others mean something happened +to the connection (some encryption problems, server problems, ...). +SSH_INVALID_REQUEST means the library got some garbage from server, but +might be recoverable. SSH_FATAL means the connection has an important +problem and isn't probably recoverable. + +Most of time, the error returned are SSH_FATAL, but some functions +(generally the ssh_request_xxx ones) may fail because of server denying request. +In these cases, SSH_REQUEST_DENIED is returned. + +For thread safety, errors are bound to ssh_session objects. +As long as your ssh_session object is not NULL, you can retrieve the last error +message and error code from the ssh_session using ssh_get_error() and +ssh_get_error_code() respectively. + +The SFTP subsystem has its own error codes, in addition to libssh ones. + + +*/ diff --git a/src/libs/libssh-0.12.2/doc/header.html b/src/libs/libssh-0.12.2/doc/header.html new file mode 100644 index 000000000000..6e43e8198179 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/header.html @@ -0,0 +1,92 @@ + + + + + + + + +$projectname: $title +$title + + + + + + + + + + + + +$treeview +$search +$mathjax +$darkmode + +$extrastylesheet + + + + + + + + + + + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
$searchbox
+
+ + diff --git a/src/libs/libssh-0.12.2/doc/introduction.dox b/src/libs/libssh-0.12.2/doc/introduction.dox new file mode 100644 index 000000000000..4415c1bb108a --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/introduction.dox @@ -0,0 +1,55 @@ +/** +@page libssh_tutorial The Tutorial +@section introduction Introduction + +libssh is a C library that enables you to write a program that uses the +SSH protocol. With it, you can remotely execute programs, transfer +files, or use a secure and transparent tunnel for your remote programs. +The SSH protocol is encrypted, ensures data integrity, and provides strong +means of authenticating both the server of the client. The library hides +a lot of technical details from the SSH protocol, but this does not +mean that you should not try to know about and understand these details. + +libssh is a Free Software / Open Source project. The libssh library +is distributed under LGPL license. The libssh project has nothing to do with +"libssh2", which is a completely different and independent project. + +libssh can run on top of either libcrypto, mbedtls or libgcrypt (deprecated) +general-purpose cryptographic libraries. + +This tutorial concentrates for its main part on the "client" side of libssh. +To learn how to accept incoming SSH connections (how to write a SSH server), +you'll have to jump to the end of this document. + +This tutorial describes libssh version 0.5.0. This version is a little different +from the 0.4.X series. However, the examples should work with +little changes on versions like 0.4.2 and later. + + +Table of contents: + +@subpage libssh_tutor_guided_tour + +@subpage libssh_tutor_authentication + +@subpage libssh_tutor_shell + +@subpage libssh_tutor_command + +@subpage libssh_tutor_sftp + +@subpage libssh_tutor_scp + +@subpage libssh_tutor_forwarding + +@subpage libssh_tutor_threads + +@subpage libssh_tutor_pkcs11 + +@subpage libssh_tutor_sftp_aio + +@subpage libssh_tutor_fido2 + +@subpage libssh_tutor_todo + +*/ diff --git a/src/libs/libssh-0.12.2/doc/linking.dox b/src/libs/libssh-0.12.2/doc/linking.dox new file mode 100644 index 000000000000..7ae0d31fb1e0 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/linking.dox @@ -0,0 +1,33 @@ +/** + +@page libssh_linking The Linking HowTo + +@section dynamic Dynamic Linking + +On UNIX and Windows systems its the same, you need at least the libssh.h +header file and the libssh shared library. + +@section static Static Linking + +@warning The libssh library is licensed under the LGPL! Make sure you +understand what this means to your codebase if you want to distribute +binaries and link statically against LGPL code! + +On UNIX systems linking against the static version of the library is the +same as linking against the shared library. Both have the same name. Some +build system require to use the full path to the static library. + +To be able to compile the application you're developing you need to either pass +LIBSSH_STATIC as a define in the compiler command line or define it before you +include libssh.h. This is required cause the dynamic library needs to specify +the dllimport attribute. + +@code +#define LIBSSH_STATIC 1 +#include +@endcode + +If you're are statically linking with OpenSSL, read the "Linking your +application" section in the NOTES.[OS] in the OpenSSL source tree! + +*/ diff --git a/src/libs/libssh-0.12.2/doc/mainpage.dox b/src/libs/libssh-0.12.2/doc/mainpage.dox new file mode 100644 index 000000000000..a0ed67767269 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/mainpage.dox @@ -0,0 +1,254 @@ +/** + +@mainpage + +This is the online reference for developing with the libssh library. It +documents the libssh C API and the C++ wrapper. + +@section main-linking Linking + +We created a small howto how to link libssh against your application, read +@subpage libssh_linking. + +@section main-tutorial Tutorial + +You should start by reading @subpage libssh_tutorial, then reading the documentation of +the interesting functions as you go. + +@section main-features Features + +The libssh library provides: + + - Key Exchange Methods: sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com, mlkem768x25519-sha256, mlkem768nistp256-sha256, mlkem1024nistp384-sha384, curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1 + - GSSAPI Key Exchange Methods: gss-group14-sha256-*, gss-group16-sha512-*, gss-nistp256-sha256-*, gss-curve25519-sha256-* + - Public Key Algorithms: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256 + - Ciphers: aes256-ctr, aes192-ctr, aes128-ctr, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc + - Compression Schemes: zlib, zlib@openssh.com, none + - MAC hashes: hmac-sha1, hmac-sha2-256, hmac-sha2-512, hmac-md5 + - Authentication: none, password, public-key, keyboard-interactive, gssapi-with-mic, gssapi-keyex + - Channels: shell, exec (incl. SCP wrapper), direct-tcpip, subsystem, auth-agent-req@openssh.com + - Global Requests: tcpip-forward, forwarded-tcpip + - Channel Requests: x11, pty, exit-status, signal, exit-signal, keepalive@openssh.com, auth-agent-req@openssh.com + - Subsystems: sftp(version 3), OpenSSH Extensions + - SFTP: statvfs@openssh.com, fstatvfs@openssh.com + - Thread-safe: Just don't share sessions + - Non-blocking: it can be used both blocking and non-blocking + - Your sockets: the app hands over the socket, or uses libssh sockets + - OpenSSL, MBedTLS or gcrypt (deprecated): builds with either + +@section main-additional-features Additional Features + + - Client and server support + - SSHv2 protocol support + - Supports Linux, UNIX, BSD, Solaris, OS/2 and Windows + - Automated test cases with nightly tests + - Event model based on poll(2), or a poll(2)-emulation. + +@section main-copyright Copyright Policy + +libssh is a project with distributed copyright ownership, which means we prefer +the copyright on parts of libssh to be held by individuals rather than +corporations if possible. There are historical legal reasons for this, but one +of the best ways to explain it is that it’s much easier to work with +individuals who have ownership than corporate legal departments if we ever need +to make reasonable compromises with people using and working with libssh. + +We track the ownership of every part of libssh via git, our source code control +system, so we know the provenance of every piece of code that is committed to +libssh. + +So if possible, if you’re doing libssh changes on behalf of a company who +normally owns all the work you do please get them to assign personal copyright +ownership of your changes to you as an individual, that makes things very easy +for us to work with and avoids bringing corporate legal departments into the +picture. + +If you can’t do this we can still accept patches from you owned by your +employer under a standard employment contract with corporate copyright +ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux Kernel +community, so it should be very easy to get a sign off from your corporate +legal department. The only changes we’ve made are to accommodate the license we +use, which is LGPLv2 (or later) whereas the Linux kernel uses GPLv2. + +The process is called signing. + +How to sign your work +---------------------- + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + +@verbatim +libssh Developer's Certificate of Origin. Version 1.0 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project under the terms of the + GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of + the License, or (at the option of the project) any later version. + +https://www.gnu.org/licenses/lgpl-2.1.html +@endverbatim + +We will maintain a copy of that email as a record that you have the rights to +contribute code to libssh under the required licenses whilst working for the +company where the email came from. + +Then when sending in a patch via the normal mechanisms described above, add a +line that states: + +@verbatim + Signed-off-by: Random J Developer +@endverbatim + +using your real name and the email address you sent the original email you used +to send the libssh Developer’s Certificate of Origin to us (sorry, no +pseudonyms or anonymous contributions.) + +That’s it! Such code can then quite happily contain changes that have copyright +messages such as: + +@verbatim + (c) Example Corporation. +@endverbatim + +and can be merged into the libssh codebase in the same way as patches from any +other individual. You don’t need to send in a copy of the libssh Developer’s +Certificate of Origin for each patch, or inside each patch. Just the sign-off +message is all that is required once we’ve received the initial email. + +Have fun and happy libssh hacking! + +The libssh Team + +@section main-rfc Internet standard + +@subsection main-rfc-secsh Secure Shell (SSH) + +The following RFC documents described SSH-2 protocol as an Internet standard. + + - RFC 4250, + The Secure Shell (SSH) Protocol Assigned Numbers + - RFC 4251, + The Secure Shell (SSH) Protocol Architecture + - RFC 4252, + The Secure Shell (SSH) Authentication Protocol + - RFC 4253, + The Secure Shell (SSH) Transport Layer Protocol + - RFC 4254, + The Secure Shell (SSH) Connection Protocol + - RFC 4255, + Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints + (not implemented in libssh) + - RFC 4256, + Generic Message Exchange Authentication for the Secure Shell Protocol (SSH) + - RFC 4335, + The Secure Shell (SSH) Session Channel Break Extension + - RFC 4344, + The Secure Shell (SSH) Transport Layer Encryption Modes + +It was later modified and expanded by the following RFCs. + + - RFC 4419, + Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer + Protocol + - RFC 4462, + Generic Security Service Application Program Interface (GSS-API) + Authentication and Key Exchange for the Secure Shell (SSH) Protocol + - RFC 4716, + The Secure Shell (SSH) Public Key File Format + (not implemented in libssh) + - RFC 5647, + AES Galois Counter Mode for the Secure Shell Transport Layer Protocol + (the algorithm negotiation implemented according to openssh.com) + - RFC 5656, + Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer + - RFC 6594, + Use of the SHA-256 Algorithm with RSA, DSA, and ECDSA in SSHFP Resource Records + (not implemented in libssh) + - RFC 6668, + SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol + - RFC 7479, + Using Ed25519 in SSHFP Resource Records + (not implemented in libssh) + - RFC 8160, + IUTF8 Terminal Mode in Secure Shell (SSH) + - RFC 8270, + Increase the Secure Shell Minimum Recommended Diffie-Hellman Modulus Size to 2048 Bits + - RFC 8308, + Extension Negotiation in the Secure Shell (SSH) Protocol + (only the "server-sig-algs" extension implemented) + - RFC 8332, + Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol + - RFC 8709, + Ed25519 and Ed448 Public Key Algorithms for the Secure Shell (SSH) Protocol + - RFC 8731, + Secure Shell (SSH) Key Exchange Method Using Curve25519 and Curve448 + - RFC 9142, + Key Exchange (KEX) Method Updates and Recommendations for Secure Shell (SSH) + +There are also drafts that are being currently developed and followed. + + - draft-miller-ssh-agent-08 + SSH Agent Protocol + - draft-ietf-sshm-mlkem-hybrid-kex-09 + PQ/T Hybrid Key Exchange with ML-KEM in SSH + - draft-ietf-sshm-ntruprime-ssh-06 + Secure Shell (SSH) Key Exchange Method Using Hybrid Streamlined NTRU Prime sntrup761 and X25519 with SHA-512: sntrup761x25519-sha512 + - draft-ietf-sshm-chacha20-poly1305-02 + Secure Shell (SSH) authenticated encryption cipher: chacha20-poly1305 + - draft-ietf-sshm-strict-kex-01 + SSH Strict KEX extension + +Interesting cryptography documents: + + - PKCS #11, PKCS #11 reference documents, describing interface with smartcards. + +@subsection main-rfc-sftp Secure Shell File Transfer Protocol (SFTP) + +The protocol is not an Internet standard but it is still widely implemented. +OpenSSH and most other implementation implement Version 3 of the protocol. We +do the same in libssh. + + - + draft-ietf-secsh-filexfer-02.txt, + SSH File Transfer Protocol + +@subsection main-rfc-extensions Secure Shell Extensions + +The OpenSSH project has defined some extensions to the protocol. We support some of +them like the statvfs calls in SFTP or the ssh-agent. + + - + OpenSSH's deviations and extensions + - + OpenSSH's pubkey certificate authentication + - + OpenSSH private key format (openssh-key-v1) + +*/ diff --git a/src/libs/libssh-0.12.2/doc/pkcs11.dox b/src/libs/libssh-0.12.2/doc/pkcs11.dox new file mode 100644 index 000000000000..b358432c319f --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/pkcs11.dox @@ -0,0 +1,86 @@ +/** +@page libssh_tutor_pkcs11 Chapter 9: Authentication using PKCS #11 URIs +@section how_to How to use PKCS #11 URIs in libssh? + +PKCS #11 is a Cryptographic Token Interface Standard that provides an API +to devices like smart cards that store cryptographic private information. +Such cryptographic devices are referenced as tokens. A mechanism through which +objects stored on the tokens can be uniquely identified is called PKCS #11 URI +(Uniform Resource Identifier) and is defined in RFC 7512 +(https://tools.ietf.org/html/rfc7512). + +# Pre-requisites (OpenSSL < 3.0): + +OpenSSL 1.x defines an abstract layer called the "engine" to achieve +cryptographic acceleration. The engine_pkcs11 module acts like an interface +between the PKCS #11 modules and the OpenSSL application. + +To build and use libssh with PKCS #11 support: +1. Enable the cmake option: $ cmake -DWITH_PKCS11_URI=ON +2. Build with OpenSSL. +3. Install and configure engine_pkcs11 (https://github.com/OpenSC/libp11). +4. Plug in a working smart card or configure softhsm (https://www.opendnssec.org/softhsm). + +@warning The support for Engines was deprecated in OpenSSL 3.0 so this approach +is deprecated in libssh 0.11.x. + +# Pre-requisites (OpenSSL 3.0.8+) + +The OpenSSL 3.0 is deprecating usage of low-level engines in favor of high-level +"providers" to provide alternative implementation of cryptographic operations +or acceleration. + +To build and use libssh with PKCS #11 support using OpenSSL providers: +1. Install and configure pkcs11 provider (https://github.com/latchset/pkcs11-provider). +2. Enable the cmake options: $ cmake -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON +3. Build with OpenSSL. +4. Plug in a working smart card or configure softhsm (https://www.opendnssec.org/softhsm). + +# New API functions + +The functions ssh_pki_import_pubkey_file() and ssh_pki_import_privkey_file() that +import the public and private keys from files respectively are now modified to support +PKCS #11 URIs. These functions automatically detect if the provided filename is a file path +or a PKCS #11 URI (when it begins with "pkcs11:"). If a PKCS #11 URI is detected, +the engine is loaded and initialized. Through the engine, the private/public key +corresponding to the PKCS #11 URI are loaded from the PKCS #11 device. + +If you wish to authenticate using public keys on your own, follow the steps mentioned under +"Authentication with public keys" in Chapter 2 - A deeper insight into authentication. + +The function pki_uri_import() is used to populate the public/private ssh_key from the +engine with PKCS #11 URIs as the look up. + +Here is a minimalistic example of public key authentication using PKCS #11 URIs: + +@code +int authenticate_pkcs11_URI(ssh_session session) +{ + int rc; + char priv_uri[1042] = "pkcs11:token=my-token;object=my-object;type=private?pin-value=1234"; + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, priv_uri); + assert_int_equal(rc, SSH_OK) + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication with PKCS #11 URIs failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@subsection Caveats + +We recommend the users to provide a specific PKCS #11 URI so that it matches only a single slot in the engine. +If the engine discovers multiple slots that could potentially contain the private keys referenced +by the provided PKCS #11 URI, the engine will not try to authenticate. + +For testing, the SoftHSM PKCS#11 library is used. + +*/ diff --git a/src/libs/libssh-0.12.2/doc/scp.dox b/src/libs/libssh-0.12.2/doc/scp.dox new file mode 100644 index 000000000000..618857ef8e7a --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/scp.dox @@ -0,0 +1,268 @@ +/** +@page libssh_tutor_scp Chapter 6: The SCP subsystem +@section scp_subsystem The SCP subsystem + +The SCP subsystem has far less functionality than the SFTP subsystem. +However, if you only need to copy files from and to the remote system, +it does its job. + + +@subsection scp_session Opening and closing a SCP session + +Like in the SFTP subsystem, you don't handle the SSH channels directly. +Instead, you open a "SCP session". + +When you open your SCP session, you have to choose between read or write mode. +You can't do both in the same session. So you specify either SSH_SCP_READ or +SSH_SCP_WRITE as the second parameter of function ssh_scp_new(). + +Another important mode flag for opening your SCP session is SSH_SCP_RECURSIVE. +When you use SSH_SCP_RECURSIVE, you declare that you are willing to emulate +the behaviour of "scp -r" command in your program, no matter it is for +reading or for writing. + +Once your session is created, you initialize it with ssh_scp_init(). When +you have finished transferring files, you terminate the SCP connection with +ssh_scp_close(). Finally, you can dispose the SCP connection with +ssh_scp_free(). + +The example below does the maintenance work to open a SCP connection for writing in +recursive mode: + +@code +int scp_write(ssh_session session) +{ + ssh_scp scp; + int rc; + + scp = ssh_scp_new + (session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, "."); + if (scp == NULL) + { + fprintf(stderr, "Error allocating scp session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = ssh_scp_init(scp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing scp session: %s\n", + ssh_get_error(session)); + ssh_scp_free(scp); + return rc; + } + + ... + + ssh_scp_close(scp); + ssh_scp_free(scp); + return SSH_OK; +} +@endcode + +The example below shows how to open a connection to read a single file: + +@code +int scp_read(ssh_session session) +{ + ssh_scp scp; + int rc; + + scp = ssh_scp_new + (session, SSH_SCP_READ, "helloworld/helloworld.txt"); + if (scp == NULL) + { + fprintf(stderr, "Error allocating scp session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = ssh_scp_init(scp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing scp session: %s\n", + ssh_get_error(session)); + ssh_scp_free(scp); + return rc; + } + + ... + + ssh_scp_close(scp); + ssh_scp_free(scp); + return SSH_OK; +} + +@endcode + + +@subsection scp_write Creating files and directories + +You create directories with ssh_scp_push_directory(). In recursive mode, +you are placed in this directory once it is created. If the directory +already exists and if you are in recursive mode, you simply enter that +directory. + +Creating files is done in two steps. First, you prepare the writing with +ssh_scp_push_file(). Then, you write the data with ssh_scp_write(). +The length of the data to write must be identical between both function calls. +There's no need to "open" nor "close" the file, this is done automatically +on the remote end. If the file already exists, it is overwritten and truncated. + +The following example creates a new directory named "helloworld/", then creates +a file named "helloworld.txt" in that directory: + +@code +int scp_helloworld(ssh_session session, ssh_scp scp) +{ + int rc; + const char *helloworld = "Hello, world!\n"; + int length = strlen(helloworld); + + rc = ssh_scp_push_directory(scp, "helloworld", S_IRWXU); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't create remote directory: %s\n", + ssh_get_error(session)); + return rc; + } + + rc = ssh_scp_push_file + (scp, "helloworld.txt", length, S_IRUSR | S_IWUSR); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't open remote file: %s\n", + ssh_get_error(session)); + return rc; + } + + rc = ssh_scp_write(scp, helloworld, length); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't write to remote file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + + +@subsection scp_recursive_write Copying full directory trees to the remote server + +Let's say you want to copy the following tree of files to the remote site: + +@verbatim + +-- file1 + +-- B --+ + | +-- file2 +-- A --+ + | +-- file3 + +-- C --+ + +-- file4 +@endverbatim + +You would do it that way: + - open the session in recursive mode + - enter directory A + - enter its subdirectory B + - create file1 in B + - create file2 in B + - leave directory B + - enter subdirectory C + - create file3 in C + - create file4 in C + - leave directory C + - leave directory A + +To leave a directory, call ssh_scp_leave_directory(). + + +@subsection scp_read Reading files and directories + + +To receive files, you pull requests from the other side with ssh_scp_pull_request(). +If this function returns SSH_SCP_REQUEST_NEWFILE, then you must get ready for +the reception. You can get the size of the data to receive with ssh_scp_request_get_size() +and allocate a buffer accordingly. When you are ready, you accept the request with +ssh_scp_accept_request(), then read the data with ssh_scp_read(). + +The following example receives a single file. The name of the file to +receive has been given earlier, when the scp session was opened: + +@code +int scp_receive(ssh_session session, ssh_scp scp) +{ + int rc; + int size, mode; + char *filename, *buffer; + + rc = ssh_scp_pull_request(scp); + if (rc != SSH_SCP_REQUEST_NEWFILE) + { + fprintf(stderr, "Error receiving information about file: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + size = ssh_scp_request_get_size(scp); + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("Receiving file %s, size %d, permissions 0%o\n", + filename, size, mode); + free(filename); + + buffer = malloc(size); + if (buffer == NULL) + { + fprintf(stderr, "Memory allocation error\n"); + return SSH_ERROR; + } + + ssh_scp_accept_request(scp); + rc = ssh_scp_read(scp, buffer, size); + if (rc == SSH_ERROR) + { + fprintf(stderr, "Error receiving file data: %s\n", + ssh_get_error(session)); + free(buffer); + return rc; + } + printf("Done\n"); + + write(1, buffer, size); + free(buffer); + + rc = ssh_scp_pull_request(scp); + if (rc != SSH_SCP_REQUEST_EOF) + { + fprintf(stderr, "Unexpected request: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + return SSH_OK; +} +@endcode + +In this example, since we just requested a single file, we expect ssh_scp_request() +to return SSH_SCP_REQUEST_NEWFILE first, then SSH_SCP_REQUEST_EOF. That's quite a +naive approach; for example, the remote server might send a warning as well +(return code SSH_SCP_REQUEST_WARNING) and the example would fail. A more comprehensive +reception program would receive the requests in a loop and analyze them carefully +until SSH_SCP_REQUEST_EOF has been received. + + +@subsection scp_recursive_read Receiving full directory trees from the remote server + +If you opened the SCP session in recursive mode, the remote end will be +telling you when to change directory. + +In that case, when ssh_scp_pull_request() answers +SSH_SCP_REQUEST_NEWDIRECTORY, you should make that local directory (if +it does not exist yet) and enter it. When ssh_scp_pull_request() answers +SSH_SCP_REQUEST_ENDDIRECTORY, you should leave the current directory. + +*/ diff --git a/src/libs/libssh-0.12.2/doc/sftp.dox b/src/libs/libssh-0.12.2/doc/sftp.dox new file mode 100644 index 000000000000..4c176a4b54b1 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/sftp.dox @@ -0,0 +1,381 @@ +/** +@page libssh_tutor_sftp Chapter 5: The SFTP subsystem +@section sftp_subsystem The SFTP subsystem + +SFTP stands for "Secure File Transfer Protocol". It enables you to safely +transfer files between the local and the remote computer. It reminds a lot +of the old FTP protocol. + +SFTP is a rich protocol. It lets you do over the network almost everything +that you can do with local files: + - send files + - modify only a portion of a file + - receive files + - receive only a portion of a file + - get file owner and group + - get file permissions + - set file owner and group + - set file permissions + - remove files + - rename files + - create a directory + - remove a directory + - retrieve the list of files in a directory + - get the target of a symbolic link + - create symbolic links + - get information about mounted filesystems. + +The current implemented version of the SFTP protocol is version 3. All functions +aren't implemented yet, but the most important are. + + +@subsection sftp_session Opening and closing a SFTP session + +Unlike with remote shells and remote commands, when you use the SFTP subsystem, +you don't handle directly the SSH channels. Instead, you open a "SFTP session". + +The function sftp_new() creates a new SFTP session. The function sftp_init() +initializes it. The function sftp_free() deletes it. + +As you see, all the SFTP-related functions start with the "sftp_" prefix +instead of the usual "ssh_" prefix. + +The example below shows how to use these functions: + +@code +#include + +int sftp_helloworld(ssh_session session) +{ + sftp_session sftp; + int rc; + + sftp = sftp_new(session); + if (sftp == NULL) + { + fprintf(stderr, "Error allocating SFTP session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = sftp_init(sftp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing SFTP session: code %d.\n", + sftp_get_error(sftp)); + sftp_free(sftp); + return rc; + } + + ... + + sftp_free(sftp); + return SSH_OK; +} +@endcode + + +@subsection sftp_errors Analyzing SFTP errors + +In case of a problem, the function sftp_get_error() returns a SFTP-specific +error number, in addition to the regular SSH error number returned by +ssh_get_error_number(). + +Possible errors are: + - SSH_FX_OK: no error + - SSH_FX_EOF: end-of-file encountered + - SSH_FX_NO_SUCH_FILE: file does not exist + - SSH_FX_PERMISSION_DENIED: permission denied + - SSH_FX_FAILURE: generic failure + - SSH_FX_BAD_MESSAGE: garbage received from server + - SSH_FX_NO_CONNECTION: no connection has been set up + - SSH_FX_CONNECTION_LOST: there was a connection, but we lost it + - SSH_FX_OP_UNSUPPORTED: operation not supported by libssh yet + - SSH_FX_INVALID_HANDLE: invalid file handle + - SSH_FX_NO_SUCH_PATH: no such file or directory path exists + - SSH_FX_FILE_ALREADY_EXISTS: an attempt to create an already existing file or directory has been made + - SSH_FX_WRITE_PROTECT: write-protected filesystem + - SSH_FX_NO_MEDIA: no media was in remote drive + + +@subsection sftp_mkdir Creating a directory + +The function sftp_mkdir() takes the "SFTP session" we just created as +its first argument. It also needs the name of the file to create, and the +desired permissions. The permissions are the same as for the usual mkdir() +function. To get a comprehensive list of the available permissions, use the +"man 2 stat" command. The desired permissions are combined with the remote +user's mask to determine the effective permissions. + +The code below creates a directory named "helloworld" in the current directory that +can be read and written only by its owner: + +@code +#include +#include + +int sftp_helloworld(ssh_session session, sftp_session sftp) +{ + int rc; + + rc = sftp_mkdir(sftp, "helloworld", S_IRWXU); + if (rc != SSH_OK) + { + if (sftp_get_error(sftp) != SSH_FX_FILE_ALREADY_EXISTS) + { + fprintf(stderr, "Can't create directory: %s\n", + ssh_get_error(session)); + return rc; + } + } + + ... + + return SSH_OK; +} +@endcode + +Unlike its equivalent in the SCP subsystem, this function does NOT change the +current directory to the newly created subdirectory. + + +@subsection sftp_write Writing to a file on the remote computer + +You handle the contents of a remote file just like you would do with a +local file: you open the file in a given mode, move the file pointer in it, +read or write data, and close the file. + +The sftp_open() function is very similar to the regular open() function, +excepted that it returns a file handle of type sftp_file. This file handle +is then used by the other file manipulation functions and remains valid +until you close the remote file with sftp_close(). + +The example below creates a new file named "helloworld.txt" in the +newly created "helloworld" directory. If the file already exists, it will +be truncated. It then writes the famous "Hello, World!" sentence to the +file, followed by a new line character. Finally, the file is closed: + +@code +#include +#include +#include + +int sftp_helloworld(ssh_session session, sftp_session sftp) +{ + int access_type = O_WRONLY | O_CREAT | O_TRUNC; + sftp_file file; + const char *helloworld = "Hello, World!\n"; + int length = strlen(helloworld); + int rc, nwritten; + + ... + + file = sftp_open(sftp, "helloworld/helloworld.txt", + access_type, S_IRWXU); + if (file == NULL) + { + fprintf(stderr, "Can't open file for writing: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + nwritten = sftp_write(file, helloworld, length); + if (nwritten != length) + { + fprintf(stderr, "Can't write data to file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + rc = sftp_close(file); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close the written file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + + +@subsection sftp_read Reading a file from the remote computer + +A synchronous read from a remote file is done using sftp_read(). This +section describes how to download a remote file using sftp_read(). The +next section will discuss more about synchronous/asynchronous read/write +operations using libssh sftp API. + +Files are normally transferred in chunks. A good chunk size is 16 KB. The following +example transfers the remote file "/etc/profile" in 16 KB chunks. For each chunk we +request, sftp_read() blocks till the data has been received: + +@code +// Good chunk size +#define MAX_XFER_BUF_SIZE 16384 + +int sftp_read_sync(ssh_session session, sftp_session sftp) +{ + int access_type; + sftp_file file; + char buffer[MAX_XFER_BUF_SIZE]; + int nbytes, nwritten, rc; + int fd; + + access_type = O_RDONLY; + file = sftp_open(sftp, "/etc/profile", + access_type, 0); + if (file == NULL) { + fprintf(stderr, "Can't open file for reading: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + fd = open("/path/to/profile", O_CREAT); + if (fd < 0) { + fprintf(stderr, "Can't open file for writing: %s\n", + strerror(errno)); + return SSH_ERROR; + } + + for (;;) { + nbytes = sftp_read(file, buffer, sizeof(buffer)); + if (nbytes == 0) { + break; // EOF + } else if (nbytes < 0) { + fprintf(stderr, "Error while reading file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + nwritten = write(fd, buffer, nbytes); + if (nwritten != nbytes) { + fprintf(stderr, "Error writing: %s\n", + strerror(errno)); + sftp_close(file); + return SSH_ERROR; + } + } + + rc = sftp_close(file); + if (rc != SSH_OK) { + fprintf(stderr, "Can't close the read file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + +@subsection sftp_aio Performing an asynchronous read/write on a file on the remote computer + +sftp_read() performs a "synchronous" read operation on a remote file. +This means that sftp_read() will first request the server to read some +data from the remote file and then would wait until the server response +containing data to read (or an error) arrives at the client side. + +sftp_write() performs a "synchronous" write operation on a remote file. +This means that sftp_write() will first request the server to write some +data to the remote file and then would wait until the server response +containing information about the status of the write operation arrives at the +client side. + +If your client program wants to do something other than waiting for the +response after requesting a read/write, the synchronous sftp_read() and +sftp_write() can't be used. In such a case the "asynchronous" sftp aio API +should be used. + +Please go through @ref libssh_tutor_sftp_aio for a detailed description +of the sftp aio API. + +The sftp aio API provides two categories of functions : + - sftp_aio_begin_*() : For requesting a read/write from the server. + - sftp_aio_wait_*() : For waiting for the response of a previously + issued read/write request from the server. + +Hence, the client program can call sftp_aio_begin_*() to request a read/write +and then can perform any number of operations (other than waiting) before +calling sftp_aio_wait_*() for waiting for the response of the previously +issued request. + +We call read/write operations performed in the manner described above as +"asynchronous" read/write operations on a remote file. + +@subsection sftp_ls Listing the contents of a directory + +The functions sftp_opendir(), sftp_readdir(), sftp_dir_eof(), +and sftp_closedir() enable to list the contents of a directory. +They use a new handle_type, "sftp_dir", which gives access to the +directory being read. + +In addition, sftp_readdir() returns a "sftp_attributes" which is a pointer +to a structure with information about a directory entry: + - name: the name of the file or directory + - size: its size in bytes + - etc. + +sftp_readdir() might return NULL under two conditions: + - when the end of the directory has been met + - when an error occurred + +To tell the difference, call sftp_dir_eof(). + +The attributes must be freed with sftp_attributes_free() when no longer +needed. + +The following example reads the contents of some remote directory: + +@code +int sftp_list_dir(ssh_session session, sftp_session sftp) +{ + sftp_dir dir; + sftp_attributes attributes; + int rc; + + dir = sftp_opendir(sftp, "/var/log"); + if (!dir) + { + fprintf(stderr, "Directory not opened: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + printf("Name Size Perms Owner\tGroup\n"); + + while ((attributes = sftp_readdir(sftp, dir)) != NULL) + { + printf("%-20s %10llu %.8o %s(%d)\t%s(%d)\n", + attributes->name, + (long long unsigned int) attributes->size, + attributes->permissions, + attributes->owner, + attributes->uid, + attributes->group, + attributes->gid); + + sftp_attributes_free(attributes); + } + + if (!sftp_dir_eof(dir)) + { + fprintf(stderr, "Can't list directory: %s\n", + ssh_get_error(session)); + sftp_closedir(dir); + return SSH_ERROR; + } + + rc = sftp_closedir(dir); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close directory: %s\n", + ssh_get_error(session)); + return rc; + } +} +@endcode + +*/ diff --git a/src/libs/libssh-0.12.2/doc/sftp_aio.dox b/src/libs/libssh-0.12.2/doc/sftp_aio.dox new file mode 100644 index 000000000000..9c26f5e1f714 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/sftp_aio.dox @@ -0,0 +1,705 @@ +/** + +@page libssh_tutor_sftp_aio Chapter 10: The SFTP asynchronous I/O + +@section sftp_aio_api The SFTP asynchronous I/O + +NOTE : Please read @ref libssh_tutor_sftp before reading this page. The +synchronous sftp_read() and sftp_write() have been described there. + +SFTP AIO stands for "SFTP Asynchronous Input/Output". This API contains +functions which perform async read/write operations on remote files. + +File transfers performed using the asynchronous sftp aio API can be +significantly faster than the file transfers performed using the synchronous +sftp read/write API (see sftp_read() and sftp_write()). + +The sftp aio API functions are divided into two categories : + - sftp_aio_begin_*() [see sftp_aio_begin_read(), sftp_aio_begin_write()]: + These functions send a request for an i/o operation to the server and + provide the caller an sftp aio handle corresponding to the sent request. + + - sftp_aio_wait_*() [see sftp_aio_wait_read(), sftp_aio_wait_write()]: + These functions wait for the server response corresponding to a previously + issued request. Which request ? the request corresponding to the sftp aio + handle supplied by the caller to these functions. + +Conceptually, you can think of the sftp aio handle as a request identifier. + +Technically, the sftp_aio_begin_*() functions dynamically allocate memory to +store information about the i/o request they send and provide the caller a +handle to this memory, we call this handle an sftp aio handle. + +sftp_aio_wait_*() functions use the information stored in that memory (handled +by the caller supplied sftp aio handle) to identify a request, and then they +wait for that request's response. These functions also release the memory +handled by the caller supplied sftp aio handle (except when they return +SSH_AGAIN). + +sftp_aio_free() can also be used to release the memory handled by an sftp aio +handle but unlike the sftp_aio_wait_*() functions, it doesn't wait for a +response. This should be used to release the memory corresponding to an sftp +aio handle when some failure occurs. An example has been provided at the +end of this page to show the usage of sftp_aio_free(). + +To begin with, this tutorial will provide basic examples that describe the +usage of sftp aio API to perform a single read/write operation. + +The later sections describe the usage of the sftp aio API to obtain faster file +transfers as compared to the transfers performed using the synchronous sftp +read/write API. + +On encountering an error, the sftp aio API functions set the sftp and ssh +errors just like any other libssh sftp API function. These errors can be +obtained using sftp_get_error(), ssh_get_error() and ssh_get_error_code(). +The code examples provided on this page ignore error handling for the sake of +brevity. + +@subsection sftp_aio_read Using the sftp aio API for reading (a basic example) + +For performing an async read operation on a sftp file (see sftp_open()), +the first step is to call sftp_aio_begin_read() to send a read request to the +server. The caller is provided an sftp aio handle corresponding to the sent +read request. + +The second step is to pass a pointer to this aio handle to +sftp_aio_wait_read(), this function waits for the server response which +indicates the success/failure of the read request. On success, the response +indicates EOF or contains the data read from the sftp file. + +The following code example shows how a read operation can be performed +on an sftp file using the sftp aio API. + +@code +ssize_t read_chunk(sftp_file file, void *buf, size_t to_read) +{ + ssize_t bytes_requested, bytes_read; + + // Variable to store an sftp aio handle + sftp_aio aio = NULL; + + // Send a read request to the sftp server + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + // Here its possible that (bytes_requested < to_read) as specified in + // the function documentation of sftp_aio_begin_read() + + // Wait for the response of the read request corresponding to the + // sftp aio handle stored in the aio variable. + bytes_read = sftp_aio_wait_read(&aio, buf, to_read); + if (bytes_read == SSH_ERROR) { + // handle error + } + + return bytes_read; +} +@endcode + +@subsection sftp_aio_write Using the sftp aio API for writing (a basic example) + +For performing an async write operation on a sftp file (see sftp_open()), +the first step is to call sftp_aio_begin_write() to send a write request to +the server. The caller is provided an sftp aio handle corresponding to the +sent write request. + +The second step is to pass a pointer to this aio handle to +sftp_aio_wait_write(), this function waits for the server response which +indicates the success/failure of the write request. + +The following code example shows how a write operation can be performed on an +sftp file using the sftp aio API. + +@code +ssize_t write_chunk(sftp_file file, void *buf, size_t to_write) +{ + ssize_t bytes_requested, bytes_written; + + // Variable to store an sftp aio handle + sftp_aio aio = NULL; + + // Send a write request to the sftp server + bytes_requested = sftp_aio_begin_write(file, buf, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + // Here its possible that (bytes_requested < to_write) as specified in + // the function documentation of sftp_aio_begin_write() + + // Wait for the response of the write request corresponding to + // the sftp aio handle stored in the aio variable. + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + // handle error + } + + return bytes_written; +} +@endcode + +@subsection sftp_aio_actual_use Using the sftp aio API to speed up a transfer + +The above examples were provided to introduce the sftp aio API. +This is not how the sftp aio API is intended to be used, because the +above usage offers no advantage over the synchronous sftp read/write API +which does the same thing i.e issue a request and then immediately wait for +its response. + +The facility that the sftp aio API provides is that the user can do +anything between issuing a request and getting the corresponding response. +Any number of operations can be performed after calling sftp_aio_begin_*() +[which issues a request] and before calling sftp_aio_wait_*() [which waits +for a response] + +The code can leverage this feature by calling sftp_aio_begin_*() multiple times +to issue multiple requests before calling sftp_aio_wait_*() to wait for the +response of an earlier issued request. This approach will keep a certain number +of requests outstanding at the client side. + +After issuing those requests, while the client code does something else (for +example waiting for an outstanding request's response, processing an obtained +response, issuing another request or any other operation the client wants +to perform), at the same time : + + - Some of those outstanding requests may be travelling over the + network towards the server. + + - Some of the outstanding requests may have reached the server and may + be queued for processing at the server side. + + - Some of the outstanding requests may have been processed and the + corresponding responses may be travelling over the network towards the + client. + + - Some of the responses corresponding to the outstanding requests may + have already reached the client side. + +Clearly in this case, operations that the client performs and operations +involved in transfer/processing of a outstanding request can occur in +parallel. Also, operations involved in transfer/processing of two or more +outstanding requests may also occur in parallel (for example when one request +travels to the server, another request's response may be incoming towards the +client). Such kind of parallelism makes the overall transfer faster as compared +to a transfer performed using the synchronous sftp read/write API. + +When the synchronous sftp read/write API is used to perform a transfer, +a strict sequence is followed: + + - The client issues a single read/write request. + - Then waits for its response. + - On obtaining the response, the client processes it. + - After the processing ends, the client issues the next read/write request. + +A file transfer performed in this manner would be slower than the case where +multiple read/write requests are kept outstanding at the client side. Because +here at any given time, operations related to transfer/processing of only one +request/response pair occurs. This is in contrast to the multiple outstanding +requests scenario where operations related to transfer/processing of multiple +request/response pairs may occur at the same time. + +Although it's true that keeping multiple requests outstanding can speed up a +transfer, those outstanding requests come at a cost of increased memory +consumption both at the client side and the server side. Hence care must be +taken to use a reasonable limit for the number of requests kept outstanding. + +The further sections provide code examples to show how uploads/downloads +can be performed using the sftp aio API and the concept of outstanding requests +discussed in this section. In those code examples, error handling has been +ignored and at some places pseudo code has been used for the sake of brevity. + +The complete code for performing uploads/downloads using the sftp aio API, +can be found at https://gitlab.com/libssh/libssh-mirror/-/tree/master. + + - libssh benchmarks for uploads performed using the sftp aio API [See + tests/benchmarks/bench_sftp.c] + - libssh benchmarks for downloads performed using the sftp aio API. [See + tests/benchmarks/bench_sftp.c] + - libssh sftp ft API code for performing a local to remote transfer (upload). + [See src/sftp_ft.c] + - libssh sftp ft API code for performing a remote to local transfer + (download). [See src/sftp_ft.c] + +@subsection sftp_aio_cap Capping applied by the sftp aio API + +Before the code examples for uploads and downloads, its important +to know about the capping applied by the sftp aio API. + +sftp_aio_begin_read() caps the number of bytes the caller can request +to read from the remote file. That cap is the value of the max_read_length +field of the sftp_limits_t returned by sftp_limits(). Say that cap is LIM +and the caller passes x as the number of bytes to read to +sftp_aio_begin_read(), then (assuming no error occurs) : + + - if x <= LIM, then sftp_aio_begin_read() will request the server + to read x bytes from the remote file, and will return x. + + - if x > LIM, then sftp_aio_begin_read() will request the server + to read LIM bytes from the remote file and will return LIM. + +Hence to request server to read x bytes (> LIM), the caller would have +to call sftp_aio_begin_read() multiple times, typically in a loop and +break out of the loop when the summation of return values of the multiple +sftp_aio_begin_read() calls becomes equal to x. + +For the sake of simplicity, the code example for download in the upcoming +section would always ask sftp_aio_begin_read() to read x <= LIM bytes, +so that its return value is guaranteed to be x, unless an error occurs. + +Similarly, sftp_aio_begin_write() caps the number of bytes the caller +can request to write to the remote file. That cap is the value of +max_write_length field of the sftp_limits_t returned by sftp_limits(). +Say that cap is LIM and the caller passes x as the number of bytes to +write to sftp_aio_begin_write(), then (assuming no error occurs) : + + - if x <= LIM, then sftp_aio_begin_write() will request the server + to write x bytes to the remote file, and will return x. + + - if x > LIM, then sftp_aio_begin_write() will request the server + to write LIM bytes to the remote file and will return LIM. + +Hence to request server to write x bytes (> LIM), the caller would have +to call sftp_aio_begin_write() multiple times, typically in a loop and +break out of the loop when the summation of return values of the multiple +sftp_aio_begin_write() calls becomes equal to x. + +For the sake of simplicity, the code example for upload in the upcoming +section would always ask sftp_aio_begin_write() to write x <= LIM bytes, +so that its return value is guaranteed to be x, unless an error occurs. + +@subsection sftp_aio_download_example Performing a download using the sftp aio API + +Terminologies used in the following code snippets : + + - sftp : The sftp_session opened using sftp_new() and initialised using + sftp_init() + + - file : The sftp file handle of the remote file to download data + from. (See sftp_open()) + + - file_size : the size of the sftp file to download. This size can be obtained + by statting the remote file to download (e.g by using sftp_stat()) + + - We will need to maintain a queue which will be used to store the sftp aio + handles corresponding to the outstanding requests. + +First, we issue the read requests while ensuring that their count +doesn't exceed a particular limit decided by us, and the number of bytes +requested don't exceed the size of the file to download. + +@code +sftp_aio aio = NULL; + +// Chunk size to use for the transfer +size_t chunk_size; + +// For the limits structure that would be used +// by the code to set the chunk size +sftp_limits_t lim = NULL; + +// Max number of requests to keep outstanding at a time +size_t in_flight_requests = 5; + +// Number of bytes for which requests have been sent +size_t total_bytes_requested = 0; + +// Number of bytes which have been downloaded +size_t bytes_downloaded = 0; + +// Buffer to use for the download +char *buffer = NULL; + +// Helper variables +size_t to_read; +ssize_t bytes_requested; + +// Get the sftp limits +lim = sftp_limits(sftp); +if (lim == NULL) { + // handle error +} + +// Set the chunk size for download = the max limit for reading +// The reason for this has been given in the "Capping applied by +// the sftp aio API" section (Its to make the code simpler) +// +// Assigning a size_t type variable a uint64_t type value here, +// theoretically could cause an overflow, but practically +// max_read_length would never exceed SIZE_MAX so its okay. +chunk_size = lim->max_read_length; + +buffer = malloc(chunk_size); +if (buffer == NULL) { + // handle error +} + +... // Code to open the remote file (to download) using sftp_open(). +... // Code to stat the remote file's file size. +... // Code to open the local file in which downloaded data is to be stored. +... // Code to initialize the queue which will be used to store sftp aio + // handles. + +for (i = 0; + i < in_flight_requests && total_bytes_requested < file_size; + ++i) { + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + // Issue a read request + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_read) { + // Should not happen for this code, as the to_read is <= + // max limit for reading (chunk size), so there is no reason + // for sftp_aio_begin_read() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +@endcode + +At this point, at max in_flight_requests number of requests may be +outstanding. Now we wait for the response corresponding to the earliest +issued outstanding request. + +On getting that response, we issue another read request if there are +still some bytes in the sftp file (to download) for which we haven't sent the +read request. (This happens when total_bytes_requested < file_size) + +This issuing of another read request (under a condition) is done to +keep the number of outstanding requests equal to the value of the +in_flight_requests variable. + +This process has to be repeated for every remaining outstanding request. + +@code +while (the queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + // Wait for the response of the request corresponding to the aio + bytes_read = sftp_aio_wait_read(&aio, buffer, chunk_size); + if (bytes_read == SSH_ERROR) { + //handle error + } + + bytes_downloaded += bytes_read; + if (bytes_read != chunk_size && bytes_downloaded != file_size) { + // A short read encountered on the remote file before reaching EOF, + // short read before reaching EOF should never happen for the sftp aio + // API which respects the max limit for reading. This probably + // indicates a bad server. + } + + // Pseudo code + WRITE bytes_read bytes from the buffer into the local file + in which downloaded data is to be stored ; + + if (total_bytes_requested == file_size) { + // no need to issue more read requests + continue; + } + + // else issue a read request + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_read) { + // Should not happen for this code, as the to_read is <= + // max limit for reading (chunk size), so there is no reason + // for sftp_aio_begin_read() to return a lesser value. + } + + total_bytes_requested += bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +free(buffer); +sftp_limits_free(lim); + +... // Code to destroy the queue which was used to store the sftp aio + // handles. +@endcode + +After exiting the while (the queue is not empty) loop, the download +would've been complete (assuming no error occurs). + +@subsection sftp_aio_upload_example Performing an upload using the sftp aio API + +Terminologies used in the following code snippets : + + - sftp : The sftp_session opened using sftp_new() and initialised using + sftp_init() + + - file : The sftp file handle of the remote file in which uploaded data + is to be stored. (See sftp_open()) + + - file_size : The size of the local file to upload. This size can be + obtained by statting the local file to upload (e.g by using stat()) + + - We will need maintain a queue which will be used to store the sftp aio + handles corresponding to the outstanding requests. + +First, we issue the write requests while ensuring that their count +doesn't exceed a particular limit decided by us, and the number of bytes +requested to write don't exceed the size of the file to upload. + +@code +sftp_aio aio = NULL; + +// The chunk size to use for the transfer +size_t chunk_size; + +// For the limits structure that would be used by +// the code to set the chunk size +sftp_limits_t lim = NULL; + +// Max number of requests to keep outstanding at a time +size_t in_flight_requests = 5; + +// Total number of bytes for which write requests have been sent +size_t total_bytes_requested = 0; + +// Buffer to use for the upload +char *buffer = NULL; + +// Helper variables +size_t to_write; +ssize_t bytes_requested; + +// Get the sftp limits +lim = sftp_limits(sftp); +if (lim == NULL) { + // handle error +} + +// Set the chunk size for upload = the max limit for writing. +// The reason for this has been given in the "Capping applied by +// the sftp aio API" section (Its to make the code simpler) +// +// Assigning a size_t type variable a uint64_t type value here, +// theoretically could cause an overflow, but practically +// max_write_length would never exceed SIZE_MAX so its okay. +chunk_size = lim->max_write_length; + +buffer = malloc(chunk_size); +if (buffer == NULL) { + // handle error +} + +... // Code to open the local file (to upload) [e.g using open(), fopen()]. +... // Code to stat the local file's file size [e.g using stat()]. +... // Code to open the remote file in which uploaded data will be stored [see + // sftp_open()]. +... // Code to initialize the queue which will be used to store sftp aio + // handles. + +for (i = 0; + i < in_flight_requests && total_bytes_requested < file_size; + ++i) { + to_write = file_size - total_bytes_requested; + if (to_write > chunk_size) { + to_write = chunk_size; + } + + // Pseudo code + READ to_write bytes from the local file (to upload) into the buffer; + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_write) { + // Should not happen for this code, as the to_write is <= + // max limit for writing (chunk size), so there is no reason + // for sftp_aio_begin_write() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +@endcode + +At this point, at max in_flight_requests number of requests may be +outstanding. Now we wait for the response corresponding to the earliest +issued outstanding request. + +On getting that response, we issue another write request if there are +still some bytes in the local file (to upload) for which we haven't sent +the write request. (This happens when total_bytes_requested < file_size) + +This issuing of another write request (under a condition) is done to +keep the number of outstanding requests equal to the value of the +in_flight_requests variable. + +This process has to be repeated for every remaining outstanding request. + +@code +while (the queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + // Wait for the response of the request corresponding to the aio + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + // handle error + } + + // sftp_aio_wait_write() won't report a short write, so no need + // to check for a short write here. + + if (total_bytes_requested == file_size) { + // no need to issue more write requests + continue; + } + + // else issue a write request + to_write = file_size - total_bytes_requested; + if (to_write > chunk_size) { + to_write = chunk_size; + } + + // Pseudo code + READ to_write bytes from the local file (to upload) into a buffer; + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_write) { + // Should not happen for this code, as the to_write is <= + // max limit for writing (chunk size), so there is no reason + // for sftp_aio_begin_write() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +free(buffer); + +... // Code to destroy the queue which was used to store the sftp aio + // handles. +@endcode + +After exiting the while (the queue is not empty) loop, the upload +would've been complete (assuming no error occurs). + +@subsection sftp_aio_free Example showing the usage of sftp_aio_free() + +The purpose of sftp_aio_free() was discussed at the beginning of this page, +the following code example shows how it can be used during cleanup. + +@code +void print_sftp_error(sftp_session sftp) +{ + if (sftp == NULL) { + return; + } + + fprintf(stderr, "sftp error : %d\n", sftp_get_error(sftp)); + fprintf(stderr, "ssh error : %s\n", ssh_get_error(sftp->session)); +} + +// Returns 0 on success, -1 on error +int write_strings(sftp_file file) +{ + const char * strings[] = { + "This is the first string", + "This is the second string", + "This is the third string", + "This is the fourth string" + }; + + size_t string_count = sizeof(strings) / sizeof(strings[0]); + size_t i; + + sftp_session sftp = NULL; + sftp_aio aio = NULL; + + int rc; + + if (file == NULL) { + return -1; + } + + ... // Code to initialize the queue which will be used to store sftp aio + // handles + + sftp = file->sftp; + for (i = 0; i < string_count; ++i) { + rc = sftp_aio_begin_write(file, + strings[i], + strlen(strings[i]), + &aio); + if (rc == SSH_ERROR) { + print_sftp_error(sftp); + goto err; + } + + // Pseudo code + ENQUEUE aio in the queue of sftp aio handles + } + + for (i = 0; i < string_count; ++i) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + rc = sftp_aio_wait_write(&aio); + if (rc == SSH_ERROR) { + print_sftp_error(sftp); + goto err; + } + } + + + ... // Code to destroy the queue in which sftp aio handles were + // stored + + return 0; + +err: + + while (queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + sftp_aio_free(aio); + } + + ... // Code to destroy the queue in which sftp aio handles were + // stored. + + return -1; +} + +@endcode + +*/ diff --git a/src/libs/libssh-0.12.2/doc/shell.dox b/src/libs/libssh-0.12.2/doc/shell.dox new file mode 100644 index 000000000000..35fc5c9a027d --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/shell.dox @@ -0,0 +1,391 @@ +/** +@page libssh_tutor_shell Chapter 3: Opening a remote shell +@section opening_shell Opening a remote shell + +We already mentioned that a single SSH connection can be shared +between several "channels". Channels can be used for different purposes. + +This chapter shows how to open one of these channels, and how to use it to +start a command interpreter on a remote computer. + + +@subsection open_channel Opening and closing a channel + +The ssh_channel_new() function creates a channel. It returns the channel as +a variable of type ssh_channel. + +Once you have this channel, you open a SSH session that uses it with +ssh_channel_open_session(). + +Once you don't need the channel anymore, you can send an end-of-file +to it with ssh_channel_close(). At this point, you can destroy the channel +with ssh_channel_free(). + +The code sample below achieves these tasks: + +@code +int shell_session(ssh_session session) +{ + ssh_channel channel = NULL; + int rc; + + channel = ssh_channel_new(session); + if (channel == NULL) + return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } + + ... + + ssh_channel_close(channel); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + + +@subsection interactive Interactive and non-interactive sessions + +A "shell" is a command interpreter. It is said to be "interactive" +if there is a human user typing the commands, one after the +other. The contrary, a non-interactive shell, is similar to +the execution of commands in the background: there is no attached +terminal. + +If you plan using an interactive shell, you need to create a +pseud-terminal on the remote side. A remote terminal is usually referred +to as a "pty", for "pseudo-teletype". The remote processes won't see the +difference with a real text-oriented terminal. + +If needed, you request the pty with the function ssh_channel_request_pty(). +If you want define its dimensions (number of rows and columns), +call ssh_channel_request_pty_size() instead. It's also possible to change +the dimensions after creating the pty with ssh_channel_change_pty_size(). + +These two functions configure the pty using the same terminal modes that +stdin has. If stdin isn't a TTY, they use default modes that configure +the pty with in canonical mode and e.g. preserving CR and LF characters. +If you want to change the terminal modes used by the pty (e.g. to change +CRLF handling), use ssh_channel_request_pty_size_modes(). This function +accepts an additional "modes" buffer that is expected to contain encoded +terminal modes according to RFC 4254 section 8. + +Be your session interactive or not, the next step is to request a +shell with ssh_channel_request_shell(). + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + ... + + return rc; +} +@endcode + + +@subsection read_data Displaying the data sent by the remote computer + +In your program, you will usually need to receive all the data "displayed" +into the remote pty. You will usually analyse, log, or display this data. + +ssh_channel_read() and ssh_channel_read_nonblocking() are the simplest +way to read data from a channel. If you only need to read from a single +channel, they should be enough. + +The example below shows how to wait for remote data using ssh_channel_read(): + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + char buffer[256]; + int nbytes; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) + return SSH_ERROR; + + if (nbytes > 0) + write(1, buffer, nbytes); + } + + return rc; +} +@endcode + +Unlike ssh_channel_read(), ssh_channel_read_nonblocking() never waits for +remote data to be ready. It returns immediately. + +If you plan to use ssh_channel_read_nonblocking() repeatedly in a loop, +you should use a "passive wait" function like usleep(3) in the same +loop. Otherwise, your program will consume all the CPU time, and your +computer might become unresponsive. + + +@subsection write_data Sending user input to the remote computer + +User's input is sent to the remote site with ssh_channel_write(). + +The following example shows how to combine a nonblocking read from a SSH +channel with a nonblocking read from the keyboard. The local input is then +sent to the remote computer: + +@code +/* Under Linux, this function determines whether a key has been pressed. + Under Windows, it is a standard function, so you need not redefine it. +*/ +int kbhit() +{ + struct timeval tv = { 0L, 0L }; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(0, &fds); + + return select(1, &fds, NULL, NULL, &tv); +} + +/* A very simple terminal emulator: + - print data received from the remote computer + - send keyboard input to the remote computer +*/ +int interactive_shell_session(ssh_channel channel) +{ + /* Session and terminal initialization skipped */ + ... + + char buffer[256]; + int nbytes, nwritten; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + nbytes = ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = write(1, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + + if (!kbhit()) + { + usleep(50000L); // 0.05 second + continue; + } + + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = ssh_channel_write(channel, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + } + } + + return rc; +} +@endcode + +Of course, this is a poor terminal emulator, since the echo from the keys +pressed should not be done locally, but should be done by the remote side. +Also, user's input should not be sent once "Enter" key is pressed, but +immediately after each key is pressed. This can be accomplished +by setting the local terminal to "raw" mode with the cfmakeraw(3) function. +cfmakeraw() is a standard function under Linux, on other systems you can +recode it with: + +@code +static void cfmakeraw(struct termios *termios_p) +{ + termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + termios_p->c_oflag &= ~OPOST; + termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); + termios_p->c_cflag &= ~(CSIZE|PARENB); + termios_p->c_cflag |= CS8; +} +@endcode + +If you are not using a local terminal, but some kind of graphical +environment, the solution to this kind of "echo" problems will be different. + + +@subsection select_loop A more elaborate way to get the remote data + +*** Warning: ssh_select() and ssh_channel_select() are not relevant anymore, + since libssh is about to provide an easier system for asynchronous + communications. This subsection should be removed then. *** + +ssh_channel_read() and ssh_channel_read_nonblocking() functions are simple, +but they are not adapted when you expect data from more than one SSH channel, +or from other file descriptors. Last example showed how getting data from +the standard input (the keyboard) at the same time as data from the SSH +channel was complicated. The functions ssh_select() and ssh_channel_select() +provide a more elegant way to wait for data coming from many sources. + +The functions ssh_select() and ssh_channel_select() remind of the standard +UNIX select(2) function. The idea is to wait for "something" to happen: +incoming data to be read, outgoing data to block, or an exception to +occur. Both these functions do a "passive wait", i.e. you can safely use +them repeatedly in a loop, it will not consume exaggerate processor time +and make your computer unresponsive. It is quite common to use these +functions in your application's main loop. + +The difference between ssh_select() and ssh_channel_select() is that +ssh_channel_select() is simpler, but allows you only to watch SSH channels. +ssh_select() is more complete and enables watching regular file descriptors +as well, in the same function call. + +Below is an example of a function that waits both for remote SSH data to come, +as well as standard input from the keyboard: + +@code +int interactive_shell_session(ssh_session session, ssh_channel channel) +{ + /* Session and terminal initialization skipped */ + ... + + char buffer[256]; + int nbytes, nwritten; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + struct timeval timeout; + ssh_channel in_channels[2], out_channels[2]; + fd_set fds; + int maxfd; + + timeout.tv_sec = 30; + timeout.tv_usec = 0; + in_channels[0] = channel; + in_channels[1] = NULL; + FD_ZERO(&fds); + FD_SET(0, &fds); + FD_SET(ssh_get_fd(session), &fds); + maxfd = ssh_get_fd(session) + 1; + + ssh_select(in_channels, out_channels, maxfd, &fds, &timeout); + + if (out_channels[0] != NULL) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = write(1, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + } + } + + if (FD_ISSET(0, &fds)) + { + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = ssh_channel_write(channel, buffer, nbytes); + if (nbytes != nwritten) return SSH_ERROR; + } + } + } + + return rc; +} +@endcode + + +@subsection x11 Using graphical applications on the remote side + +If your remote application is graphical, you can forward the X11 protocol to +your local computer. + +To do that, you first declare a callback to manage channel_open_request_x11_function. +Then you create the forwarding tunnel for the X11 protocol with ssh_channel_request_x11(). + +The following code performs channel initialization and shell session +opening, and handles a parallel X11 connection: + +@code +#include + +ssh_channel x11channel = NULL; + +ssh_channel x11_open_request_callback(ssh_session session, const char *shost, int sport, void *userdata) +{ + x11channel = ssh_channel_new(session); + return x11channel; +} + +int interactive_shell_session(ssh_channel channel) +{ + int rc; + + struct ssh_callbacks_struct cb = + { + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL + }; + + ssh_callbacks_init(&cb); + rc = ssh_set_callbacks(session, &cb); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_x11(channel, 0, NULL, NULL, 0); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + /* Read the data sent by the remote computer here */ + ... +} +@endcode + +Don't forget to check the $DISPLAY environment variable on the remote +side, or the remote applications won't try using the X11 tunnel: + +@code +$ echo $DISPLAY +localhost:10.0 +$ xclock & +@endcode + +See an implementation example at https://gitlab.com/libssh/libssh-mirror/-/tree/master/examples/ssh_X11_client.c for details. + +*/ diff --git a/src/libs/libssh-0.12.2/doc/tbd.dox b/src/libs/libssh-0.12.2/doc/tbd.dox new file mode 100644 index 000000000000..921337ed36d3 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/tbd.dox @@ -0,0 +1,14 @@ +/** +@page libssh_tutor_todo To be done + +*** To be written *** + +@section sshd Writing a libssh-based server + +*** To be written *** + +@section cpp The libssh C++ wrapper + +*** To be written *** + +*/ diff --git a/src/libs/libssh-0.12.2/doc/threading.dox b/src/libs/libssh-0.12.2/doc/threading.dox new file mode 100644 index 000000000000..87d096b74da3 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/threading.dox @@ -0,0 +1,52 @@ +/** +@page libssh_tutor_threads Chapter 8: Threads with libssh +@section threads_with_libssh How to use libssh with threads + +libssh may be used in multithreaded applications, but under several conditions : + - Your system must support libpthread or, in Windows environment, + CriticalSection based mutex control. + - Since version 0.8.0, threads initialization is called automatically in the + library constructor if libssh is dynamically linked. This means it is no + longer necessary to call ssh_init()/ssh_finalize(). + - If libssh is statically linked, threading must be initialized by calling + ssh_init() before using any of libssh provided functions. This initialization + must be done outside of any threading context. Don't forget to call + ssh_finalize() to avoid memory leak + - At all times, you may use different sessions inside threads, make parallel + connections, read/write on different sessions and so on. You *cannot* use a + single session (or channels for a single session) in several threads at the same + time. This will most likely lead to internal state corruption. This limitation is + being worked out and will maybe disappear later. + +@subsection threads_init Initialization of threads + +Since version 0.8.0, it is no longer necessary to call ssh_init()/ssh_finalize() +if libssh is dynamically linked. + +If libssh is statically linked, call ssh_init() before using any of libssh +provided functions. + +@subsection threads_pthread Using libpthread with libssh + +Since version 0.8.0, libpthread is the default threads library used by libssh. + +To use libpthread, simply link it to you application. + +If you are using libssh statically linked, don't forget to call ssh_init() +before using any of libssh provided functions (and ssh_finalize() in the end). + +@subsection threads_other Using another threading library + +Since version 0.8.0, libssh does not support custom threading libraries. +The change makes sense since the newer versions for libcrypto (OpenSSL) and +libgcrypt don't support custom threading libraries. + +The default used threading library is libpthread. +Alternatively, in Windows environment, CriticalSection based mutex control can +be used. + +If your system does not support libpthread nor CriticalSection based mutex +control, unfortunately, you cannot use libssh in multithreaded scenarios. + +Good luck ! +*/ diff --git a/src/libs/libssh-0.12.2/examples/CMakeLists.txt b/src/libs/libssh-0.12.2/examples/CMakeLists.txt new file mode 100644 index 000000000000..4fb842b0531b --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/CMakeLists.txt @@ -0,0 +1,105 @@ +project(libssh-examples C CXX) + +set(examples_SRCS + authentication.c + knownhosts.c + connect_ssh.c +) + +include_directories(${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR}) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif() + +if (UNIX AND NOT WIN32) + add_executable(libssh_scp libssh_scp.c ${examples_SRCS}) + target_compile_options(libssh_scp PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(libssh_scp ssh::ssh) + + add_executable(scp_download scp_download.c ${examples_SRCS}) + target_compile_options(scp_download PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(scp_download ssh::ssh) + + add_executable(sshnetcat sshnetcat.c ${examples_SRCS}) + target_compile_options(sshnetcat PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(sshnetcat ssh::ssh) + + if (WITH_SFTP) + add_executable(samplesftp samplesftp.c ${examples_SRCS}) + target_compile_options(samplesftp PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesftp ssh::ssh) + + if (WITH_SERVER) + add_executable(sample_sftpserver sample_sftpserver.c ${examples_SRCS}) + target_compile_options(sample_sftpserver PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(sample_sftpserver ssh::ssh ${ARGP_LIBRARIES}) + endif (WITH_SERVER) + endif (WITH_SFTP) + + add_executable(ssh-client ssh_client.c ${examples_SRCS}) + target_compile_options(ssh-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh-client ssh::ssh) + + add_executable(ssh-X11-client ssh_X11_client.c ${examples_SRCS}) + target_compile_options(ssh-X11-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh-X11-client ssh::ssh) + + if (WITH_SERVER AND (ARGP_LIBRARIES OR HAVE_ARGP_H)) + if (HAVE_LIBUTIL) + add_executable(ssh_server_fork ssh_server.c) + target_compile_options(ssh_server_fork PRIVATE ${DEFAULT_C_COMPILE_FLAGS} -DWITH_FORK) + target_link_libraries(ssh_server_fork ssh::ssh ${ARGP_LIBRARIES} util) + + add_executable(ssh_server_pthread ssh_server.c) + target_compile_options(ssh_server_pthread PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh_server_pthread ssh::ssh ${ARGP_LIBRARIES} pthread util) + endif (HAVE_LIBUTIL) + + if (WITH_GSSAPI AND GSSAPI_FOUND) + add_executable(proxy proxy.c) + target_compile_options(proxy PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(proxy ssh::ssh ${ARGP_LIBRARIES}) + + add_executable(sshd_direct-tcpip sshd_direct-tcpip.c) + target_compile_options(sshd_direct-tcpip PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(sshd_direct-tcpip ssh::ssh ${ARGP_LIBRARIES}) + endif (WITH_GSSAPI AND GSSAPI_FOUND) + + add_executable(samplesshd-kbdint samplesshd-kbdint.c) + target_compile_options(samplesshd-kbdint PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesshd-kbdint ssh::ssh ${ARGP_LIBRARIES}) + + add_executable(keygen2 keygen2.c ${examples_SRCS}) + target_compile_options(keygen2 PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(keygen2 ssh::ssh ${ARGP_LIBRARIES}) + + endif() +endif (UNIX AND NOT WIN32) + +if (WITH_SERVER) + add_executable(samplesshd-cb samplesshd-cb.c) + target_compile_options(samplesshd-cb PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesshd-cb ssh::ssh) + if (ARGP_LIBRARIES OR HAVE_ARGP_H) + target_link_libraries(samplesshd-cb ${ARGP_LIBRARIES}) + endif(ARGP_LIBRARIES OR HAVE_ARGP_H) +endif() + +add_executable(exec exec.c ${examples_SRCS}) +target_compile_options(exec PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(exec ssh::ssh) + +add_executable(senddata senddata.c ${examples_SRCS}) +target_compile_options(senddata PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(senddata ssh::ssh) + +add_executable(keygen keygen.c) +target_compile_options(keygen PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(keygen ssh::ssh) + +add_executable(libsshpp libsshpp.cpp) +target_link_libraries(libsshpp ssh::ssh) + +add_executable(libsshpp_noexcept libsshpp_noexcept.cpp) +target_link_libraries(libsshpp_noexcept ssh::ssh) diff --git a/src/libs/libssh-0.12.2/examples/authentication.c b/src/libs/libssh-0.12.2/examples/authentication.c new file mode 100644 index 000000000000..be63f04ddf21 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/authentication.c @@ -0,0 +1,248 @@ +/* + * authentication.c + * This file contains an example of how to do an authentication to a + * SSH server using libssh + */ + +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include +#include +#include + +#include +#include "examples_common.h" + +int authenticate_kbdint(ssh_session session, const char *password) +{ + int err; + + err = ssh_userauth_kbdint(session, NULL, NULL); + while (err == SSH_AUTH_INFO) { + const char *instruction = NULL; + const char *name = NULL; + char buffer[128]; + int i, n; + + name = ssh_userauth_kbdint_getname(session); + instruction = ssh_userauth_kbdint_getinstruction(session); + n = ssh_userauth_kbdint_getnprompts(session); + + if (name && strlen(name) > 0) { + printf("%s\n", name); + } + + if (instruction && strlen(instruction) > 0) { + printf("%s\n", instruction); + } + + for (i = 0; i < n; i++) { + const char *answer = NULL; + const char *prompt = NULL; + char echo; + + prompt = ssh_userauth_kbdint_getprompt(session, i, &echo); + if (prompt == NULL) { + break; + } + + if (echo) { + char *p = NULL; + + printf("%s", prompt); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + return SSH_AUTH_ERROR; + } + + if ((p = strchr(buffer, '\n'))) { + *p = '\0'; + } + + if (ssh_userauth_kbdint_setanswer(session, i, buffer) < 0) { + return SSH_AUTH_ERROR; + } + + memset(buffer, 0, sizeof(buffer)); + } else { + if (password && strstr(prompt, "Password:")) { + answer = password; + } else { + buffer[0] = '\0'; + + if (ssh_getpass(prompt, buffer, sizeof(buffer), 0, 0) < 0) { + return SSH_AUTH_ERROR; + } + answer = buffer; + } + err = ssh_userauth_kbdint_setanswer(session, i, answer); + memset(buffer, 0, sizeof(buffer)); + if (err < 0) { + return SSH_AUTH_ERROR; + } + } + } + err=ssh_userauth_kbdint(session,NULL,NULL); + } + + return err; +} + +static int auth_keyfile(ssh_session session, char* keyfile) +{ + ssh_key key = NULL; + char pubkey[132] = {0}; // +".pub" + int rc; + + snprintf(pubkey, sizeof(pubkey), "%s.pub", keyfile); + + rc = ssh_pki_import_pubkey_file( pubkey, &key); + + if (rc != SSH_OK) + return SSH_AUTH_DENIED; + + rc = ssh_userauth_try_publickey(session, NULL, key); + + ssh_key_free(key); + + if (rc!=SSH_AUTH_SUCCESS) + return SSH_AUTH_DENIED; + + rc = ssh_pki_import_privkey_file(keyfile, NULL, NULL, NULL, &key); + + if (rc != SSH_OK) + return SSH_AUTH_DENIED; + + rc = ssh_userauth_publickey(session, NULL, key); + + ssh_key_free(key); + + return rc; +} + + +static void error(ssh_session session) +{ + fprintf(stderr,"Authentication failed: %s\n",ssh_get_error(session)); +} + +int authenticate_console(ssh_session session) +{ + int rc; + int method; + char password[128] = {0}; + char *banner = NULL; + + // Try to authenticate + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } + + method = ssh_userauth_list(session, NULL); + while (rc != SSH_AUTH_SUCCESS) { + if (method & SSH_AUTH_METHOD_GSSAPI_MIC){ + rc = ssh_userauth_gssapi(session); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + if (method & SSH_AUTH_METHOD_GSSAPI_KEYEX) { + rc = ssh_userauth_gssapi_keyex(session); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + if (method & SSH_AUTH_METHOD_PUBLICKEY) { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + { + char buffer[128] = {0}; + char *p = NULL; + + printf("Automatic pubkey failed. " + "Do you want to try a specific key? (y/n)\n"); + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + break; + } + if ((buffer[0]=='Y') || (buffer[0]=='y')) { + printf("private key filename: "); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + return SSH_AUTH_ERROR; + } + + buffer[sizeof(buffer) - 1] = '\0'; + if ((p = strchr(buffer, '\n'))) { + *p = '\0'; + } + + rc = auth_keyfile(session, buffer); + + if(rc == SSH_AUTH_SUCCESS) { + break; + } + fprintf(stderr, "failed with key\n"); + } + } + + // Try to authenticate with keyboard interactive"; + if (method & SSH_AUTH_METHOD_INTERACTIVE) { + rc = authenticate_kbdint(session, NULL); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + + if (ssh_getpass("Password: ", password, sizeof(password), 0, 0) < 0) { + return SSH_AUTH_ERROR; + } + + // Try to authenticate with password + if (method & SSH_AUTH_METHOD_PASSWORD) { + rc = ssh_userauth_password(session, NULL, password); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + memset(password, 0, sizeof(password)); + } + + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n",banner); + SSH_STRING_FREE_CHAR(banner); + } + + return rc; +} diff --git a/src/libs/libssh-0.12.2/examples/connect_ssh.c b/src/libs/libssh-0.12.2/examples/connect_ssh.c new file mode 100644 index 000000000000..b07f824d940a --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/connect_ssh.c @@ -0,0 +1,77 @@ +/* + * connect_ssh.c + * This file contains an example of how to connect to a + * SSH server using libssh + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include +#include "examples_common.h" +#include + +ssh_session connect_ssh(const char *host, const char *port, const char *user, int verbosity) +{ + ssh_session session = NULL; + int auth = 0; + + session = ssh_new(); + if (session == NULL) { + return NULL; + } + + if (user != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + ssh_free(session); + return NULL; + } + } + + if (port != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_PORT_STR, port) < 0) { + ssh_free(session); + return NULL; + } + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + ssh_free(session); + return NULL; + } + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + if (verify_knownhost(session) < 0) { + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + auth = authenticate_console(session); + if (auth == SSH_AUTH_SUCCESS) { + return session; + } else if (auth == SSH_AUTH_DENIED) { + fprintf(stderr, "Authentication failed\n"); + } else { + fprintf(stderr, + "Error while authenticating : %s\n", + ssh_get_error(session)); + } + ssh_disconnect(session); + ssh_free(session); + return NULL; +} diff --git a/src/libs/libssh-0.12.2/examples/examples_common.h b/src/libs/libssh-0.12.2/examples/examples_common.h new file mode 100644 index 000000000000..6f5a1b12c6ce --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/examples_common.h @@ -0,0 +1,26 @@ +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ +#ifndef EXAMPLES_COMMON_H_ +#define EXAMPLES_COMMON_H_ + +#include + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset(&(x), 0, sizeof(x)) + +int authenticate_console(ssh_session session); +int authenticate_kbdint(ssh_session session, const char *password); +int verify_knownhost(ssh_session session); +ssh_session connect_ssh(const char *hostname, const char *port, const char *user, int verbosity); + +#endif /* EXAMPLES_COMMON_H_ */ diff --git a/src/libs/libssh-0.12.2/examples/exec.c b/src/libs/libssh-0.12.2/examples/exec.c new file mode 100644 index 000000000000..9c1d8dbb73a3 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/exec.c @@ -0,0 +1,81 @@ +/* simple exec example */ +#include + +#include +#include "examples_common.h" + +int main(void) { + ssh_session session = NULL; + ssh_channel channel = NULL; + char buffer[256]; + int rbytes, wbytes, total = 0; + int rc; + + session = connect_ssh("localhost", NULL, NULL, 0); + if (session == NULL) { + ssh_finalize(); + return 1; + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + return 1; + } + + rc = ssh_channel_open_session(channel); + if (rc < 0) { + goto failed; + } + + rc = ssh_channel_request_exec(channel, "lsof"); + if (rc < 0) { + goto failed; + } + + rbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (rbytes <= 0) { + goto failed; + } + + do { + wbytes = fwrite(buffer + total, 1, rbytes, stdout); + if (wbytes <= 0) { + goto failed; + } + + total += wbytes; + + /* When it was not possible to write the whole buffer to stdout */ + if (wbytes < rbytes) { + rbytes -= wbytes; + continue; + } + + rbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + total = 0; + } while (rbytes > 0); + + if (rbytes < 0) { + goto failed; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + + return 0; +failed: + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + + return 1; +} diff --git a/src/libs/libssh-0.12.2/examples/keygen.c b/src/libs/libssh-0.12.2/examples/keygen.c new file mode 100644 index 000000000000..99f8c98cf51a --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/keygen.c @@ -0,0 +1,41 @@ +/* keygen.c + * Sample implementation of ssh-keygen using libssh + */ + +/* +Copyright 2019 Red Hat, Inc. + +Author: Jakub Jelen + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include + +int main(void) +{ + ssh_key key = NULL; + int rv; + + /* Generate a new ED25519 private key file */ + rv = ssh_pki_generate(SSH_KEYTYPE_ED25519, 0, &key); + if (rv != SSH_OK) { + fprintf(stderr, "Failed to generate private key"); + return -1; + } + + /* Write it to a file testkey in the current directory */ + rv = ssh_pki_export_privkey_file(key, NULL, NULL, NULL, "testkey"); + if (rv != SSH_OK) { + fprintf(stderr, "Failed to write private key file"); + return -1; + } + + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/keygen2.c b/src/libs/libssh-0.12.2/examples/keygen2.c new file mode 100644 index 000000000000..73d702115704 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/keygen2.c @@ -0,0 +1,526 @@ +/* + * keygen2.c - Generate SSH keys using libssh + * Author: Anderson Toshiyuki Sasaki + */ + +/* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct arguments_st { + enum ssh_keytypes_e type; + unsigned long bits; + char *file; + char *passphrase; + char *format; + int action_list; +}; + +static struct argp_option options[] = { + { + .name = "bits", + .key = 'b', + .arg = "BITS", + .flags = 0, + .doc = "The size of the key to be generated. " + "If omitted, a default value is used depending on the TYPE. " + "Accepted values are: " + "1024, 2048, 3072 (default), 4096, and 8192 for TYPE=\"rsa\"; " + "256 (default), 384, and 521 for TYPE=\"ecdsa\"; " + "can be omitted for TYPE=\"ed25519\" " + "(it will be ignored if provided).\n", + .group = 0 + }, + { + .name = "file", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "The output file. " + "If not provided, the used file name will be generated " + "according to the key type as \"id_TYPE\" " + "(e.g. \"id_rsa\" for type \"rsa\"). " + "The public key file name is generated from the private key " + "file name by appending \".pub\".\n", + .group = 0 + }, + { + .name = "passphrase", + .key = 'p', + .arg = "PASSPHRASE", + .flags = 0, + .doc = "The passphrase used to encrypt the private key. " + "If omitted the file will not be encrypted.\n", + .group = 0 + }, + { + .name = "type", + .key = 't', + .arg = "TYPE", + .flags = 0, + .doc = "The type of the key to be generated. " + "Accepted values are: " + "\"rsa\", \"ecdsa\", and \"ed25519\".\n", + .group = 0 + }, + { + .name = "list", + .key = 'l', + .arg = NULL, + .flags = 0, + .doc = "List the Fingerprint of the given key\n", + .group = 0 + }, + { + .name = "format", + .key = 'm', + .arg = "FORMAT", + .flags = 0, + .doc = "Write the file in specific format. The supported values are " + "'PEM'and 'OpenSSH' file format. By default Ed25519 " + "keys are exported in OpenSSH format and others in PEM.\n", + .group = 0 + }, + { + /* End of the options */ + 0 + }, +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct arguments_st *arguments = NULL; + error_t rc = 0; + + if (state == NULL) { + return EINVAL; + } + + arguments = state->input; + if (arguments == NULL) { + fprintf(stderr, "Error: NULL pointer to arguments structure " + "provided\n"); + rc = EINVAL; + goto end; + } + + switch (key) { + case 'b': + errno = 0; + arguments->bits = strtoul(arg, NULL, 10); + if (errno != 0) { + rc = errno; + goto end; + } + break; + case 'f': + arguments->file = strdup(arg); + if (arguments->file == NULL) { + fprintf(stderr, "Error: Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'p': + arguments->passphrase = strdup(arg); + if (arguments->passphrase == NULL) { + fprintf(stderr, "Error: Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 't': + if (!strcmp(arg, "rsa")) { + arguments->type = SSH_KEYTYPE_RSA; + } + else if (!strcmp(arg, "ecdsa")) { + arguments->type = SSH_KEYTYPE_ECDSA; + } + else if (!strcmp(arg, "ed25519")) { + arguments->type = SSH_KEYTYPE_ED25519; + } + else { + fprintf(stderr, "Error: Invalid key type\n"); + argp_usage(state); + rc = EINVAL; + goto end; + } + break; + case 'l': + arguments->action_list = 1; + break; + case 'm': + arguments->format = strdup(arg); + break; + case ARGP_KEY_ARG: + if (state->arg_num > 0) { + /* Too many arguments. */ + printf("Error: Too many arguments\n"); + argp_usage(state); + } + break; + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + } + +end: + return rc; +} + +static int validate_args(struct arguments_st *args) +{ + int rc = 0; + + if (args == NULL) { + return EINVAL; + } + + /* no other arguments needed for listing key fingerprints */ + if (args->action_list) { + return 0; + } + + switch (args->type) { + case SSH_KEYTYPE_RSA: + switch (args->bits) { + case 0: + /* If not provided, use default value */ + args->bits = 3072; + break; + case 1024: + case 2048: + case 3072: + case 4096: + case 8192: + break; + default: + fprintf(stderr, "Error: Invalid bits parameter provided\n"); + rc = EINVAL; + break; + } + + if (args->file == NULL) { + args->file = strdup("id_rsa"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + case SSH_KEYTYPE_ECDSA: + switch (args->bits) { + case 0: + /* If not provided, use default value */ + args->bits = 256; + break; + case 256: + case 384: + case 521: + break; + default: + fprintf(stderr, "Error: Invalid bits parameter provided\n"); + rc = EINVAL; + break; + } + if (args->file == NULL) { + args->file = strdup("id_ecdsa"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + case SSH_KEYTYPE_ED25519: + /* Ignore value and overwrite with a zero */ + args->bits = 0; + + if (args->file == NULL) { + args->file = strdup("id_ed25519"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + default: + fprintf(stderr, "Error: unknown key type\n"); + rc = EINVAL; + break; + } + + return rc; +} + +/* Program documentation. */ +static char doc[] = "Generate an SSH key pair. " + "The \"--type\" (short: \"-t\") option is required."; + +/* Our argp parser */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; + +static void +list_fingerprint(char *file) +{ + ssh_key key = NULL; + unsigned char *hash = NULL; + size_t hlen = 0; + int rc; + + rc = ssh_pki_import_privkey_file(file, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + fprintf(stderr, "Failed to import private key %s\n", file); + return; + } + + rc = ssh_get_publickey_hash(key, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen); + if (rc != SSH_OK) { + fprintf(stderr, "Failed to get key fingerprint\n"); + ssh_key_free(key); + return; + } + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + + ssh_clean_pubkey_hash(&hash); + ssh_key_free(key); +} + +int main(int argc, char *argv[]) +{ + ssh_key key = NULL; + int rc = 0; + char overwrite[1024] = ""; + + char *pubkey_file = NULL; + + struct arguments_st arguments = { + .type = SSH_KEYTYPE_UNKNOWN, + .bits = 0, + .file = NULL, + .passphrase = NULL, + .action_list = 0, + }; + + if (argc < 2) { + argp_help(&argp, stdout, ARGP_HELP_DOC | ARGP_HELP_USAGE, argv[0]); + goto end; + } + + rc = argp_parse(&argp, argc, argv, 0, 0, &arguments); + if (rc != 0) { + goto end; + } + + rc = validate_args(&arguments); + if (rc != 0) { + goto end; + } + + if (arguments.file == NULL) { + fprintf(stderr, "Error: Missing argument file\n"); + goto end; + } + + if (arguments.action_list) { + list_fingerprint(arguments.file); + goto end; + } + + errno = 0; + rc = open(arguments.file, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); + if (rc < 0) { + if (errno == EEXIST) { + printf("File \"%s\" exists. Overwrite it? (y|n) ", arguments.file); + rc = scanf("%1023s", overwrite); + if (rc > 0 && tolower(overwrite[0]) == 'y') { + rc = open(arguments.file, O_WRONLY); + if (rc > 0) { + close(rc); + errno = 0; + rc = chmod(arguments.file, S_IRUSR | S_IWUSR); + if (rc != 0) { + fprintf(stderr, + "Error(%d): Could not set file permissions\n", + errno); + goto end; + } + } else { + fprintf(stderr, + "Error: Could not create private key file\n"); + goto end; + } + } else { + goto end; + } + } else { + fprintf(stderr, "Error opening \"%s\" file\n", arguments.file); + goto end; + } + } else { + close(rc); + } + + /* Generate a new private key */ + rc = ssh_pki_generate(arguments.type, arguments.bits, &key); + if (rc != SSH_OK) { + fprintf(stderr, "Error: Failed to generate keys"); + goto end; + } + + /* Write the private key */ + if (arguments.format != NULL) { + if (strcasecmp(arguments.format, "PEM") == 0) { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_PEM); + } else if (strcasecmp(arguments.format, "OpenSSH") == 0) { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_OPENSSH); + } else { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_DEFAULT); + } + } else { + rc = ssh_pki_export_privkey_file(key, + arguments.passphrase, + NULL, + NULL, + arguments.file); + } + if (rc != SSH_OK) { + fprintf(stderr, "Error: Failed to write private key file"); + goto end; + } + + /* If a passphrase was provided, overwrite and free it as it is not needed + * anymore */ + if (arguments.passphrase != NULL) { +#ifdef HAVE_EXPLICIT_BZERO + explicit_bzero(arguments.passphrase, strlen(arguments.passphrase)); +#else + bzero(arguments.passphrase, strlen(arguments.passphrase)); +#endif + free(arguments.passphrase); + arguments.passphrase = NULL; + } + + pubkey_file = (char *)malloc(strlen(arguments.file) + 5); + if (pubkey_file == NULL) { + rc = ENOMEM; + goto end; + } + + sprintf(pubkey_file, "%s.pub", arguments.file); + + errno = 0; + rc = open(pubkey_file, + O_CREAT | O_EXCL | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (rc < 0) { + if (errno == EEXIST) { + printf("File \"%s\" exists. Overwrite it? (y|n) ", pubkey_file); + rc = scanf("%1023s", overwrite); + if (rc > 0 && tolower(overwrite[0]) == 'y') { + rc = open(pubkey_file, O_WRONLY); + if (rc > 0) { + close(rc); + errno = 0; + rc = chmod(pubkey_file, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (rc != 0) { + fprintf(stderr, + "Error(%d): Could not set file permissions\n", + errno); + goto end; + } + } else { + fprintf(stderr, + "Error: Could not create public key file\n"); + goto end; + } + } else { + goto end; + } + } else { + fprintf(stderr, "Error opening \"%s\" file\n", pubkey_file); + goto end; + } + } else { + close(rc); + } + + /* Write the public key */ + rc = ssh_pki_export_pubkey_file(key, pubkey_file); + if (rc != SSH_OK) { + fprintf(stderr, "Error: Failed to write public key file"); + goto end; + } + +end: + if (key != NULL) { + ssh_key_free(key); + } + + if (arguments.file != NULL) { + free(arguments.file); + } + + if (arguments.passphrase != NULL) { +#ifdef HAVE_EXPLICIT_BZERO + explicit_bzero(arguments.passphrase, strlen(arguments.passphrase)); +#else + bzero(arguments.passphrase, strlen(arguments.passphrase)); +#endif + free(arguments.passphrase); + } + + if (pubkey_file != NULL) { + free(pubkey_file); + } + return rc; +} diff --git a/src/libs/libssh-0.12.2/examples/knownhosts.c b/src/libs/libssh-0.12.2/examples/knownhosts.c new file mode 100644 index 000000000000..fdd9960cb0c4 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/knownhosts.c @@ -0,0 +1,129 @@ +/* + * knownhosts.c + * This file contains an example of how verify the identity of a + * SSH server using libssh + */ + +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/priv.h" +#include +#include "examples_common.h" + +#ifdef _WIN32 +#define strncasecmp _strnicmp +#endif + +int verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + char buf[10]; + unsigned char *hash = NULL; + size_t hlen; + ssh_key srv_pubkey = NULL; + int rc; + + /* If GSSAPI key exchange was used, the server identity was already + * verified via Kerberos mutual authentication (MIC). We might skip + * the host key verification, especially if we don't expect the server + * to send its key. Alternatively, we could proceed without this check + * and handle the scenario when the server does not provide its host key + * later. In that case, ssh_session_is_known_server will return + * SSH_KNOWN_HOSTS_UNKNOWN. + */ + if (ssh_session_kex_is_gss(session)) { + return 0; + } + + rc = ssh_get_server_publickey(session, &srv_pubkey); + if (rc < 0) { + return -1; + } + + rc = ssh_get_publickey_hash(srv_pubkey, + SSH_PUBLICKEY_HASH_SHA256, + &hash, + &hlen); + ssh_key_free(srv_pubkey); + if (rc < 0) { + return -1; + } + + state = ssh_session_is_known_server(session); + + switch(state) { + case SSH_KNOWN_HOSTS_CHANGED: + fprintf(stderr,"Host key for server changed : server's one is now :\n"); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + ssh_clean_pubkey_hash(&hash); + fprintf(stderr,"For security reason, connection will be stopped\n"); + return -1; + case SSH_KNOWN_HOSTS_OTHER: + fprintf(stderr,"The host key for this server was not found but an other type of key exists.\n"); + fprintf(stderr,"An attacker might change the default server key to confuse your client" + "into thinking the key does not exist\n" + "We advise you to rerun the client with -d or -r for more safety.\n"); + return -1; + case SSH_KNOWN_HOSTS_NOT_FOUND: + fprintf(stderr,"Could not find known host file. If you accept the host key here,\n"); + fprintf(stderr,"the file will be automatically created.\n"); + /* fallback to SSH_SERVER_NOT_KNOWN behavior */ + FALL_THROUGH; + case SSH_SERVER_NOT_KNOWN: + fprintf(stderr, + "The server is unknown. Do you trust the host key (yes/no)?\n"); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + + if (fgets(buf, sizeof(buf), stdin) == NULL) { + ssh_clean_pubkey_hash(&hash); + return -1; + } + if(strncasecmp(buf,"yes",3)!=0){ + ssh_clean_pubkey_hash(&hash); + return -1; + } + fprintf(stderr,"This new key will be written on disk for further usage. do you agree ?\n"); + if (fgets(buf, sizeof(buf), stdin) == NULL) { + ssh_clean_pubkey_hash(&hash); + return -1; + } + if(strncasecmp(buf,"yes",3)==0){ + rc = ssh_session_update_known_hosts(session); + if (rc != SSH_OK) { + ssh_clean_pubkey_hash(&hash); + fprintf(stderr, "error %s\n", strerror(errno)); + return -1; + } + } + + break; + case SSH_KNOWN_HOSTS_ERROR: + ssh_clean_pubkey_hash(&hash); + fprintf(stderr,"%s",ssh_get_error(session)); + return -1; + case SSH_KNOWN_HOSTS_OK: + break; /* ok */ + } + + ssh_clean_pubkey_hash(&hash); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/libssh_scp.c b/src/libs/libssh-0.12.2/examples/libssh_scp.c new file mode 100644 index 000000000000..2b1a7627345f --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/libssh_scp.c @@ -0,0 +1,466 @@ +/* libssh_scp.c + * Sample implementation of a SCP client + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include +#include +#include +#include + +#include +#include "examples_common.h" + +#ifndef BUF_SIZE +#define BUF_SIZE 16384 +#endif + +static char **sources = NULL; +static int nsources; +static char *destination = NULL; +static int verbosity = 0; +static char *port = NULL; + +struct location { + int is_ssh; + char *user; + char *host; + char *path; + ssh_session session; + ssh_scp scp; + FILE *file; +}; + +enum { + READ, + WRITE +}; + +static void usage(const char *argv0) { + fprintf(stderr, "Usage : %s [options] [[user@]host1:]file1 ... \n" + " [[user@]host2:]destination\n" + "sample scp client - libssh-%s\n" + "Options :\n" + " -P : use port to connect to remote host\n" + " -v : increase verbosity of libssh. Can be used multiple times\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv) { + int i; + + while((i = getopt(argc, argv, "P:v")) != -1) { + switch(i) { + case 'v': + verbosity++; + break; + case 'P': + port = optarg; + break; + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(argv[0]); + return -1; + } + } + + nsources = argc - optind - 1; + if (nsources < 1) { + usage(argv[0]); + return -1; + } + + sources = malloc((nsources + 1) * sizeof(char *)); + if (sources == NULL) { + return -1; + } + + for(i = 0; i < nsources; ++i) { + sources[i] = argv[optind]; + optind++; + } + + sources[i] = NULL; + destination = argv[optind]; + return 0; +} + +static void location_free(struct location *loc) +{ + if (loc) { + if (loc->path) { + free(loc->path); + } + loc->path = NULL; + if (loc->is_ssh) { + if (loc->host) { + free(loc->host); + } + loc->host = NULL; + if (loc->user) { + free(loc->user); + } + loc->user = NULL; + } + free(loc); + } +} + +static struct location *parse_location(char *loc) +{ + struct location *location = NULL; + char *ptr = NULL; + + if (loc == NULL) { + return NULL; + } + + location = malloc(sizeof(struct location)); + if (location == NULL) { + return NULL; + } + memset(location, 0, sizeof(struct location)); + + location->host = location->user = NULL; + ptr = strchr(loc, ':'); + + if (ptr != NULL) { + location->is_ssh = 1; + location->path = strdup(ptr+1); + *ptr = '\0'; + ptr = strchr(loc, '@'); + + if (ptr != NULL) { + location->host = strdup(ptr+1); + *ptr = '\0'; + location->user = strdup(loc); + } else { + location->host = strdup(loc); + } + } else { + location->is_ssh = 0; + location->path = strdup(loc); + } + return location; +} + +static void close_location(struct location *loc) { + int rc; + + if (loc) { + if (loc->is_ssh) { + if (loc->scp) { + rc = ssh_scp_close(loc->scp); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error closing scp: %s\n", + ssh_get_error(loc->session)); + } + ssh_scp_free(loc->scp); + loc->scp = NULL; + } + if (loc->session) { + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + } + } else { + if (loc->file) { + fclose(loc->file); + loc->file = NULL; + } + } + } +} + +static int open_location(struct location *loc, int flag) { + if (loc->is_ssh && flag == WRITE) { + loc->session = connect_ssh(loc->host, port, loc->user, verbosity); + if (!loc->session) { + fprintf(stderr, "Couldn't connect to %s\n", loc->host); + return -1; + } + + loc->scp = ssh_scp_new(loc->session, SSH_SCP_WRITE, loc->path); + if (!loc->scp) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + + if (ssh_scp_init(loc->scp) == SSH_ERROR) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_scp_free(loc->scp); + loc->scp = NULL; + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + return 0; + } else if (loc->is_ssh && flag == READ) { + loc->session = connect_ssh(loc->host, port, loc->user, verbosity); + if (!loc->session) { + fprintf(stderr, "Couldn't connect to %s\n", loc->host); + return -1; + } + + loc->scp = ssh_scp_new(loc->session, SSH_SCP_READ, loc->path); + if (!loc->scp) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + + if (ssh_scp_init(loc->scp) == SSH_ERROR) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_scp_free(loc->scp); + loc->scp = NULL; + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + return 0; + } else if (loc->path != NULL) { + loc->file = fopen(loc->path, flag == READ ? "r":"w"); + if (!loc->file) { + if (errno == EISDIR) { + if (chdir(loc->path)) { + fprintf(stderr, + "Error changing directory to %s: %s\n", + loc->path, strerror(errno)); + return -1; + } + return 0; + } + fprintf(stderr, + "Error opening %s: %s\n", + loc->path, strerror(errno)); + return -1; + } + return 0; + } + return -1; +} + +/** @brief copies files from source location to destination + * @param src source location + * @param dest destination location + * @param recursive Copy also directories + */ +static int do_copy(struct location *src, struct location *dest, int recursive) { + size_t size; + socket_t fd; + struct stat s; + int w, r; + char buffer[BUF_SIZE]; + size_t total = 0; + mode_t mode; + char *filename = NULL; + + /* recursive mode doesn't work yet */ + (void)recursive; + /* Get the file name and size*/ + if (!src->is_ssh) { + fd = fileno(src->file); + if (fd < 0) { + fprintf(stderr, + "Invalid file pointer, error: %s\n", + strerror(errno)); + return -1; + } + r = fstat(fd, &s); + if (r < 0) { + return -1; + } + size = s.st_size; + mode = s.st_mode & ~S_IFMT; + filename = ssh_basename(src->path); + } else { + size = 0; + do { + r = ssh_scp_pull_request(src->scp); + if (r == SSH_SCP_REQUEST_NEWDIR) { + ssh_scp_deny_request(src->scp, "Not in recursive mode"); + continue; + } + if (r == SSH_SCP_REQUEST_NEWFILE) { + size = ssh_scp_request_get_size(src->scp); + filename = strdup(ssh_scp_request_get_filename(src->scp)); + mode = ssh_scp_request_get_permissions(src->scp); + //ssh_scp_accept_request(src->scp); + break; + } + if (r == SSH_ERROR) { + fprintf(stderr, + "Error: %s\n", + ssh_get_error(src->session)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } while(r != SSH_SCP_REQUEST_NEWFILE); + } + + if (dest->is_ssh) { + r = ssh_scp_push_file(dest->scp, src->path, size, mode); + // snprintf(buffer, sizeof(buffer), "C0644 %d %s\n", size, src->path); + if (r == SSH_ERROR) { + fprintf(stderr, + "error: %s\n", + ssh_get_error(dest->session)); + SSH_STRING_FREE_CHAR(filename); + ssh_scp_free(dest->scp); + dest->scp = NULL; + return -1; + } + } else { + if (!dest->file) { + dest->file = fopen(filename, "w"); + if (!dest->file) { + fprintf(stderr, + "Cannot open %s for writing: %s\n", + filename, strerror(errno)); + if (src->is_ssh) { + ssh_scp_deny_request(src->scp, "Cannot open local file"); + } + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } + if (src->is_ssh) { + ssh_scp_accept_request(src->scp); + } + } + + do { + if (src->is_ssh) { + r = ssh_scp_read(src->scp, buffer, sizeof(buffer)); + if (r == SSH_ERROR) { + fprintf(stderr, + "Error reading scp: %s\n", + ssh_get_error(src->session)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + + if (r == 0) { + break; + } + } else { + r = fread(buffer, 1, sizeof(buffer), src->file); + if (r == 0) { + break; + } + + if (r < 0) { + fprintf(stderr, + "Error reading file: %s\n", + strerror(errno)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } + + if (dest->is_ssh) { + w = ssh_scp_write(dest->scp, buffer, r); + if (w == SSH_ERROR) { + fprintf(stderr, + "Error writing in scp: %s\n", + ssh_get_error(dest->session)); + ssh_scp_free(dest->scp); + dest->scp = NULL; + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } else { + w = fwrite(buffer, r, 1, dest->file); + if (w <= 0) { + fprintf(stderr, + "Error writing in local file: %s\n", + strerror(errno)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } + total += r; + + } while(total < size); + + SSH_STRING_FREE_CHAR(filename); + printf("wrote %zu bytes\n", total); + return 0; +} + +int main(int argc, char **argv) { + struct location *dest, *src; + int i; + int r; + if (opts(argc, argv) < 0) { + return EXIT_FAILURE; + } + + ssh_init(); + + dest = parse_location(destination); + if (dest == NULL) { + r = EXIT_FAILURE; + goto end; + } + + if (open_location(dest, WRITE) < 0) { + location_free(dest); + r = EXIT_FAILURE; + goto end; + } + + for (i = 0; i < nsources; ++i) { + src = parse_location(sources[i]); + if (src == NULL) { + r = EXIT_FAILURE; + goto close_dest; + } + + if (open_location(src, READ) < 0) { + location_free(src); + r = EXIT_FAILURE; + goto close_dest; + } + + if (do_copy(src, dest, 0) < 0) { + close_location(src); + location_free(src); + break; + } + + close_location(src); + location_free(src); + } + + r = 0; + +close_dest: + close_location(dest); + location_free(dest); +end: + ssh_finalize(); + free(sources); + return r; +} diff --git a/src/libs/libssh-0.12.2/examples/libsshpp.cpp b/src/libs/libssh-0.12.2/examples/libsshpp.cpp new file mode 100644 index 000000000000..8f042a459dc5 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/libsshpp.cpp @@ -0,0 +1,33 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +*/ + +/* This file demonstrates the use of the C++ wrapper to libssh */ + +#include +#include +#include + +int main(int argc, const char **argv){ + ssh::Session session; + try { + if(argc>1) + session.setOption(SSH_OPTIONS_HOST,argv[1]); + else + session.setOption(SSH_OPTIONS_HOST,"localhost"); + session.connect(); + session.userauthPublickeyAuto(); + session.disconnect(); + } catch (ssh::SshException e){ + std::cout << "Error during connection : "; + std::cout << e.getError() << std::endl; + } + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/libsshpp_noexcept.cpp b/src/libs/libssh-0.12.2/examples/libsshpp_noexcept.cpp new file mode 100644 index 000000000000..eff8cc191345 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/libsshpp_noexcept.cpp @@ -0,0 +1,41 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +*/ + +/* This file demonstrates the use of the C++ wrapper to libssh + * specifically, without C++ exceptions + */ + +#include +#define SSH_NO_CPP_EXCEPTIONS +#include + +int main(int argc, const char **argv){ + ssh::Session session,s2; + int err; + if(argc>1) + err=session.setOption(SSH_OPTIONS_HOST,argv[1]); + else + err=session.setOption(SSH_OPTIONS_HOST,"localhost"); + if(err==SSH_ERROR) + goto error; + err=session.connect(); + if(err==SSH_ERROR) + goto error; + err=session.userauthPublickeyAuto(); + if(err==SSH_ERROR) + goto error; + + return 0; + error: + std::cout << "Error during connection : "; + std::cout << session.getError() << std::endl; + return 1; +} diff --git a/src/libs/libssh-0.12.2/examples/proxy.c b/src/libs/libssh-0.12.2/examples/proxy.c new file mode 100644 index 000000000000..ab69b18e2a3c --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/proxy.c @@ -0,0 +1,339 @@ +/* This is a sample implementation of a libssh based SSH proxy */ +/* +Copyright 2003-2013 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include + +#ifndef BUF_SIZE +#define BUF_SIZE 2048 +#endif + +#define USER "myuser" +#define PASSWORD "mypassword" + +static int authenticated=0; +static int tries = 0; +static int error = 0; +static ssh_channel chan = NULL; +static char *username = NULL; +static ssh_gssapi_creds client_creds = NULL; + +static int auth_password(ssh_session session, const char *user, + const char *password, void *userdata){ + + (void)userdata; + + printf("Authenticating user %s pwd %s\n",user, password); + if(strcmp(user,USER) == 0 && strcmp(password, PASSWORD) == 0){ + authenticated = 1; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + if (tries >= 3){ + printf("Too many authentication tries\n"); + ssh_disconnect(session); + error = 1; + return SSH_AUTH_DENIED; + } + tries++; + return SSH_AUTH_DENIED; +} + +static int auth_gssapi_mic(ssh_session session, const char *user, const char *principal, void *userdata){ + (void)userdata; + client_creds = ssh_gssapi_get_creds(session); + printf("Authenticating user %s with gssapi principal %s\n",user, principal); + if (client_creds != NULL) + printf("Received some gssapi credentials\n"); + else + printf("Not received any forwardable creds\n"); + printf("authenticated\n"); + authenticated = 1; + username = strdup(principal); + return SSH_AUTH_SUCCESS; +} + +static int pty_request(ssh_session session, ssh_channel channel, const char *term, + int x,int y, int px, int py, void *userdata){ + (void) session; + (void) channel; + (void) term; + (void) x; + (void) y; + (void) px; + (void) py; + (void) userdata; + printf("Allocated terminal\n"); + return 0; +} + +static int shell_request(ssh_session session, ssh_channel channel, void *userdata){ + (void)session; + (void)channel; + (void)userdata; + printf("Allocated shell\n"); + return 0; +} +struct ssh_channel_callbacks_struct channel_cb = { + .channel_pty_request_function = pty_request, + .channel_shell_request_function = shell_request +}; + +static ssh_channel new_session_channel(ssh_session session, void *userdata){ + (void) session; + (void) userdata; + if(chan != NULL) + return NULL; + printf("Allocated session channel\n"); + chan = ssh_channel_new(session); + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(chan, &channel_cb); + return chan; +} + + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh proxy example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa host key (deprecated alias to 'k').", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'r': + /* deprecated */ + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event mainloop = NULL; + ssh_session client_session = NULL; + + struct ssh_server_callbacks_struct cb = { + .userdata = NULL, + .auth_password_function = auth_password, + .auth_gssapi_mic_function = auth_gssapi_mic, + .channel_open_request_session_function = new_session_channel + }; + + char buf[BUF_SIZE]; + char host[128]=""; + char *ptr = NULL; + int i,r, rc; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, "sshd_rsa"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n",ssh_get_error(sshbind)); + return 1; + } + r=ssh_bind_accept(sshbind,session); + if(r==SSH_ERROR){ + printf("error accepting a connection : %s\n",ssh_get_error(sshbind)); + return 1; + } + ssh_callbacks_init(&cb); + ssh_set_server_callbacks(session, &cb); + + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + ssh_set_auth_methods(session,SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + mainloop = ssh_event_new(); + ssh_event_add_session(mainloop, session); + + while (!(authenticated && chan != NULL)){ + if(error) + break; + r = ssh_event_dopoll(mainloop, -1); + if (r == SSH_ERROR){ + printf("Error : %s\n",ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + } + if(error){ + printf("Error, exiting loop\n"); + return 1; + } else + printf("Authenticated and got a channel\n"); + if (!client_creds){ + snprintf(buf,sizeof(buf), "Sorry, but you do not have forwardable tickets. Try again with -K\r\n"); + ssh_channel_write(chan,buf,strlen(buf)); + printf("%s",buf); + ssh_disconnect(session); + return 1; + } + snprintf(buf,sizeof(buf), "Hello %s, welcome to the Sample SSH proxy.\r\nPlease select your destination: ", username); + ssh_channel_write(chan, buf, strlen(buf)); + do{ + i=ssh_channel_read(chan,buf, sizeof(buf), 0); + if(i>0) { + ssh_channel_write(chan, buf, i); + if(strlen(host) + i < sizeof(host)){ + strncat(host, buf, i); + } + if (strchr(host, '\x0d')) { + *strchr(host, '\x0d')='\0'; + ssh_channel_write(chan, "\n", 1); + break; + } + } else { + printf ("Error: %s\n", ssh_get_error(session) ); + return 1; + } + } while (i>0); + snprintf(buf,sizeof(buf),"Trying to connect to \"%s\"\r\n", host); + ssh_channel_write(chan, buf, strlen(buf)); + printf("%s",buf); + + client_session = ssh_new(); + + /* ssh servers expect username without realm */ + ptr = strchr(username,'@'); + if(ptr) + *ptr= '\0'; + ssh_options_set(client_session, SSH_OPTIONS_HOST, host); + ssh_options_set(client_session, SSH_OPTIONS_USER, username); + ssh_gssapi_set_creds(client_session, client_creds); + rc = ssh_connect(client_session); + if (rc != SSH_OK){ + printf("Error connecting to %s: %s", host, ssh_get_error(client_session)); + return 1; + } + rc = ssh_userauth_none(client_session, NULL); + if(rc == SSH_AUTH_SUCCESS){ + printf("Authenticated using method none\n"); + } else { + rc = ssh_userauth_gssapi(client_session); + if(rc != SSH_AUTH_SUCCESS){ + printf("GSSAPI Authentication failed: %s\n",ssh_get_error(client_session)); + return 1; + } + } + snprintf(buf,sizeof(buf), "Authentication success\r\n"); + printf("%s",buf); + ssh_channel_write(chan,buf,strlen(buf)); + ssh_disconnect(client_session); + ssh_disconnect(session); + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/sample_sftpserver.c b/src/libs/libssh-0.12.2/examples/sample_sftpserver.c new file mode 100644 index 000000000000..dcab1540183a --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/sample_sftpserver.c @@ -0,0 +1,514 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2014 Audrius Butkevicius + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. +*/ + +#include "config.h" + +#include +#include +#include +#include + +#include +#ifdef HAVE_ARGP_H +#include +#endif +#include +#ifdef HAVE_LIBUTIL_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif +#include +#include +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif +#include +#include +#include +#include +#include + +/* below are for sftp */ +#include +#include +#include +#include +#include +#include + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "myuser" +#define PASS "mypassword" +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) + +static void set_default_keys(ssh_bind sshbind, + int rsa_already_set, + int ecdsa_already_set) +{ + if (!rsa_already_set) + { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + } + if (!ecdsa_already_set) + { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_ecdsa_key"); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_ed25519_key"); +} +#define DEF_STR_SIZE 1024 +char authorizedkeys[DEF_STR_SIZE] = {0}; +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh sftp server example " SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "Sftp server implemented with libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + {.name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0}, + {.name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set a host key. Can be used multiple times. " + "Implies no default keys.", + .group = 0}, + {.name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0}, + {.name = "ecdsakey", + .key = 'e', + .arg = "FILE", + .flags = 0, + .doc = "Set the ecdsa key.", + .group = 0}, + {.name = "authorizedkeys", + .key = 'a', + .arg = "FILE", + .flags = 0, + .doc = "Set the authorized keys file.", + .group = 0}, + {.name = "no-default-keys", + .key = 'n', + .arg = NULL, + .flags = 0, + .doc = "Do not set default key locations.", + .group = 0}, + {.name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0}, + {NULL, 0, NULL, 0, NULL, 0}}; + +/* Parse a single option. */ +static error_t parse_opt(int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. */ + ssh_bind sshbind = state->input; + static int no_default_keys = 0; + static int rsa_already_set = 0, ecdsa_already_set = 0; + static int verbosity = 0; + + switch (key) + { + case 'n': + no_default_keys = 1; + break; + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + /* We can't track the types of keys being added with this + option, so let's ensure we keep the keys we're adding + by just not setting the default keys */ + no_default_keys = 1; + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + rsa_already_set = 1; + break; + case 'e': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + ecdsa_already_set = 1; + break; + case 'a': + strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1); + break; + case 'v': + verbosity++; + ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &verbosity); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) + { + /* Too many arguments. */ + argp_usage(state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) + { + /* Not enough arguments. */ + argp_usage(state); + } + + if (!no_default_keys) + { + set_default_keys(sshbind, + rsa_already_set, + ecdsa_already_set); + } + + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +/* A userdata struct for channel. */ +struct channel_data_struct { + sftp_session sftp; +}; + +/* A userdata struct for session. */ +struct session_data_struct +{ + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + int auth_attempts; + int authenticated; +}; + +static int auth_password(ssh_session session, const char *user, + const char *pass, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + (void)session; + + if (strcmp(user, USER) == 0 && strcmp(pass, PASS) == 0) + { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + + sdata->auth_attempts++; + return SSH_AUTH_DENIED; +} + +static int auth_publickey(ssh_session session, + const char *user, + struct ssh_key_struct *pubkey, + char signature_state, + void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + (void)session; + (void)user; + + if (signature_state == SSH_PUBLICKEY_STATE_NONE) + { + return SSH_AUTH_SUCCESS; + } + + if (signature_state != SSH_PUBLICKEY_STATE_VALID) + { + return SSH_AUTH_DENIED; + } + + // valid so far. Now look through authorized keys for a match + if (authorizedkeys[0]) + { + ssh_key key = NULL; + int result; + struct stat buf; + + if (stat(authorizedkeys, &buf) == 0) + { + result = ssh_pki_import_pubkey_file(authorizedkeys, &key); + if ((result != SSH_OK) || (key == NULL)) + { + fprintf(stderr, + "Unable to import public key file %s\n", + authorizedkeys); + } + else + { + result = ssh_key_cmp(key, pubkey, SSH_KEY_CMP_PUBLIC); + ssh_key_free(key); + if (result == 0) + { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + } + } + } + + // no matches + sdata->authenticated = 0; + return SSH_AUTH_DENIED; +} + +static ssh_channel channel_open(ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + /* This server supports only one channel -- fail for repeated channel + * requests */ + if (sdata->channel != NULL) { + return NULL; + } + + sdata->channel = ssh_channel_new(session); + return sdata->channel; +} + +static void handle_session(ssh_event event, ssh_session session) +{ + int n; + + /* Our struct holding information about the channel. */ + struct channel_data_struct cdata = { + .sftp = NULL, + }; + + /* Our struct holding information about the session. */ + struct session_data_struct sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + }; + + struct ssh_channel_callbacks_struct channel_cb = { + .userdata = &(cdata.sftp), + .channel_data_function = sftp_channel_default_data_callback, + .channel_subsystem_request_function = sftp_channel_default_subsystem_request, + }; + + struct ssh_server_callbacks_struct server_cb = { + .userdata = &sdata, + .auth_password_function = auth_password, + .channel_open_request_session_function = channel_open, + }; + + if (authorizedkeys[0]) + { + server_cb.auth_pubkey_function = auth_publickey; + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY); + } + else + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); + + ssh_callbacks_init(&server_cb); + ssh_callbacks_init(&channel_cb); + + ssh_set_server_callbacks(session, &server_cb); + + if (ssh_handle_key_exchange(session) != SSH_OK) + { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= 3 || n >= 100) { + return; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + n++; + } + + ssh_set_channel_callbacks(sdata.channel, &channel_cb); + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + } while (ssh_channel_is_open(sdata.channel) && + !ssh_channel_is_eof(sdata.channel)); + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } +} + +/* SIGCHLD handler for cleaning up dead children. */ +static void sigchld_handler(int signo) +{ + (void)signo; + + while (waitpid(-1, NULL, WNOHANG) > 0) + ; +} + +int main(int argc, char **argv) +{ + ssh_bind sshbind = NULL; + ssh_session session = NULL; + ssh_event event = NULL; + struct sigaction sa; + int rc; + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, NULL) != 0) + { + fprintf(stderr, "Failed to register SIGCHLD handler\n"); + return 1; + } + + rc = ssh_init(); + if (rc < 0) + { + fprintf(stderr, "ssh_init failed\n"); + goto exit; + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) + { + fprintf(stderr, "ssh_bind_new failed\n"); + goto exit; + } + +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, sshbind); +#else + (void)argc; + (void)argv; + + set_default_keys(sshbind, 0, 0); +#endif /* HAVE_ARGP_H */ + + if (ssh_bind_listen(sshbind) < 0) + { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + goto exit; + } + + while (1) + { + session = ssh_new(); + if (session == NULL) + { + fprintf(stderr, "Failed to allocate session\n"); + continue; + } + + /* Blocks until there is a new incoming connection. */ + if (ssh_bind_accept(sshbind, session) != SSH_ERROR) + { + switch (fork()) + { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) + { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + handle_session(event, session); + ssh_event_free(event); + } + else + { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + + exit(0); + case -1: + fprintf(stderr, "Failed to fork\n"); + } + } + else + { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + } + /* Since the session has been passed to a child fork, do some cleaning + * up at the parent process. */ + ssh_disconnect(session); + ssh_free(session); + } + +exit: + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/samplesftp.c b/src/libs/libssh-0.12.2/examples/samplesftp.c new file mode 100644 index 000000000000..d82a556af258 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/samplesftp.c @@ -0,0 +1,304 @@ +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +#include "examples_common.h" +#ifdef WITH_SFTP + +#ifndef BUF_SIZE +#define BUF_SIZE 65536 +#endif + +static void do_sftp(ssh_session session) { + sftp_session sftp = sftp_new(session); + sftp_dir dir; + sftp_attributes file; + sftp_statvfs_t sftpstatvfs; + struct statvfs sysstatvfs; + sftp_file source; + sftp_file to; + int len = 1; + unsigned int i; + char data[BUF_SIZE] = {0}; + char *lnk = NULL; + + unsigned int count; + + if (!sftp) { + fprintf(stderr, "sftp error initialising channel: %s\n", + ssh_get_error(session)); + goto end; + } + + if (sftp_init(sftp)) { + fprintf(stderr, "error initialising sftp: %s\n", + ssh_get_error(session)); + goto end; + } + + printf("Additional SFTP extensions provided by the server:\n"); + count = sftp_extensions_get_count(sftp); + for (i = 0; i < count; i++) { + printf("\t%s, version: %s\n", + sftp_extensions_get_name(sftp, i), + sftp_extensions_get_data(sftp, i)); + } + + /* test symlink and readlink */ + if (sftp_symlink(sftp, "/tmp/this_is_the_link", + "/tmp/sftp_symlink_test") < 0) + { + fprintf(stderr, "Could not create link (%s)\n", + ssh_get_error(session)); + goto end; + } + + lnk = sftp_readlink(sftp, "/tmp/sftp_symlink_test"); + if (lnk == NULL) { + fprintf(stderr, "Could not read link (%s)\n", ssh_get_error(session)); + goto end; + } + printf("readlink /tmp/sftp_symlink_test: %s\n", lnk); + ssh_string_free_char(lnk); + + sftp_unlink(sftp, "/tmp/sftp_symlink_test"); + + if (sftp_extension_supported(sftp, "statvfs@openssh.com", "2")) { + sftpstatvfs = sftp_statvfs(sftp, "/tmp"); + if (sftpstatvfs == NULL) { + fprintf(stderr, "statvfs failed (%s)\n", ssh_get_error(session)); + goto end; + } + + printf("sftp statvfs:\n" + "\tfile system block size: %llu\n" + "\tfundamental fs block size: %llu\n" + "\tnumber of blocks (unit f_frsize): %llu\n" + "\tfree blocks in file system: %llu\n" + "\tfree blocks for non-root: %llu\n" + "\ttotal file inodes: %llu\n" + "\tfree file inodes: %llu\n" + "\tfree file inodes for to non-root: %llu\n" + "\tfile system id: %llu\n" + "\tbit mask of f_flag values: %llu\n" + "\tmaximum filename length: %llu\n", + (unsigned long long) sftpstatvfs->f_bsize, + (unsigned long long) sftpstatvfs->f_frsize, + (unsigned long long) sftpstatvfs->f_blocks, + (unsigned long long) sftpstatvfs->f_bfree, + (unsigned long long) sftpstatvfs->f_bavail, + (unsigned long long) sftpstatvfs->f_files, + (unsigned long long) sftpstatvfs->f_ffree, + (unsigned long long) sftpstatvfs->f_favail, + (unsigned long long) sftpstatvfs->f_fsid, + (unsigned long long) sftpstatvfs->f_flag, + (unsigned long long) sftpstatvfs->f_namemax); + + sftp_statvfs_free(sftpstatvfs); + + if (statvfs("/tmp", &sysstatvfs) < 0) { + fprintf(stderr, "statvfs failed (%s)\n", strerror(errno)); + goto end; + } + + printf("sys statvfs:\n" + "\tfile system block size: %llu\n" + "\tfundamental fs block size: %llu\n" + "\tnumber of blocks (unit f_frsize): %llu\n" + "\tfree blocks in file system: %llu\n" + "\tfree blocks for non-root: %llu\n" + "\ttotal file inodes: %llu\n" + "\tfree file inodes: %llu\n" + "\tfree file inodes for to non-root: %llu\n" + "\tfile system id: %llu\n" + "\tbit mask of f_flag values: %llu\n" + "\tmaximum filename length: %llu\n", + (unsigned long long) sysstatvfs.f_bsize, + (unsigned long long) sysstatvfs.f_frsize, + (unsigned long long) sysstatvfs.f_blocks, + (unsigned long long) sysstatvfs.f_bfree, + (unsigned long long) sysstatvfs.f_bavail, + (unsigned long long) sysstatvfs.f_files, + (unsigned long long) sysstatvfs.f_ffree, + (unsigned long long) sysstatvfs.f_favail, + (unsigned long long) sysstatvfs.f_fsid, + (unsigned long long) sysstatvfs.f_flag, + (unsigned long long) sysstatvfs.f_namemax); + } + + /* the connection is made */ + /* opening a directory */ + dir = sftp_opendir(sftp, "./"); + if (!dir) { + fprintf(stderr, "Directory not opened(%s)\n", ssh_get_error(session)); + goto end; + } + + /* reading the whole directory, file by file */ + while ((file = sftp_readdir(sftp, dir))) { + fprintf(stderr, "%30s(%.8o) : %s(%.5d) %s(%.5d) : %.10llu bytes\n", + file->name, + file->permissions, + file->owner, + file->uid, + file->group, + file->gid, + (long long unsigned int) file->size); + sftp_attributes_free(file); + } + + /* when file = NULL, an error has occurred OR the directory listing is end of + * file */ + if (!sftp_dir_eof(dir)) { + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + goto end; + } + + if (sftp_closedir(dir)) { + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + goto end; + } + /* this will open a file and copy it into your /home directory */ + /* the small buffer size was intended to stress the library. of course, you + * can use a buffer till 20kbytes without problem */ + + source = sftp_open(sftp, "/usr/bin/ssh", O_RDONLY, 0); + if (!source) { + fprintf(stderr, "Error opening /usr/bin/ssh: %s\n", + ssh_get_error(session)); + goto end; + } + + /* open a file for writing... */ + to = sftp_open(sftp, "ssh-copy", O_WRONLY | O_CREAT, 0700); + if (!to) { + fprintf(stderr, "Error opening ssh-copy for writing: %s\n", + ssh_get_error(session)); + sftp_close(source); + goto end; + } + + while ((len = sftp_read(source, data, 4096)) > 0) { + if (sftp_write(to, data, len) != len) { + fprintf(stderr, "Error writing %d bytes: %s\n", + len, ssh_get_error(session)); + sftp_close(to); + sftp_close(source); + goto end; + } + } + + printf("finished\n"); + if (len < 0) { + fprintf(stderr, "Error reading file: %s\n", ssh_get_error(session)); + } + + sftp_close(source); + sftp_close(to); + printf("file closed\n"); + to = sftp_open(sftp, "/tmp/large_file", O_WRONLY|O_CREAT, 0644); + + for (i = 0; i < 1000; ++i) { + len = sftp_write(to, data, sizeof(data)); + printf("wrote %d bytes\n", len); + if (len != sizeof(data)) { + printf("chunk %d : %d (%s)\n", i, len, ssh_get_error(session)); + } + } + + sftp_close(to); +end: + /* close the sftp session */ + sftp_free(sftp); + printf("sftp session terminated\n"); +} + +static void usage(const char *argv0) { + fprintf(stderr, "Usage : %s [-v] remotehost\n" + "sample sftp test client - libssh-%s\n" + "Options :\n" + " -l user : log in as user\n" + " -p port : connect to port\n" + " -v : increase log verbosity\n", + argv0, + ssh_version(0)); + exit(0); +} + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + char *destination = NULL; + int auth = 0; + int state; + + ssh_init(); + session = ssh_new(); + + if (ssh_options_getopt(session, &argc, argv)) { + fprintf(stderr, + "Error parsing command line: %s\n", + ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); + usage(argv[0]); + return EXIT_FAILURE; + } + if (argc < 1) { + usage(argv[0]); + return EXIT_FAILURE; + } + destination = argv[1]; + + if (ssh_options_set(session, SSH_OPTIONS_HOST, destination) < 0) { + return -1; + } + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + return -1; + } + + state = verify_knownhost(session); + if (state != 0) { + return -1; + } + + auth = authenticate_console(session); + if (auth != SSH_AUTH_SUCCESS) { + return -1; + } + + do_sftp(session); + ssh_disconnect(session); + ssh_free(session); + + ssh_finalize(); + + return 0; +} + +#endif diff --git a/src/libs/libssh-0.12.2/examples/samplesshd-cb.c b/src/libs/libssh-0.12.2/examples/samplesshd-cb.c new file mode 100644 index 000000000000..31e035ff6a09 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/samplesshd-cb.c @@ -0,0 +1,342 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#ifndef BUF_SIZE +#define BUF_SIZE 2049 +#endif + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "myuser" +#define PASSWORD "mypassword" + +static int authenticated=0; +static int tries = 0; +static int error = 0; +static ssh_channel chan=NULL; + +static int auth_none(ssh_session session, + const char *user, + void *userdata) +{ + ssh_string banner = NULL; + + (void)user; /* unused */ + (void)userdata; /* unused */ + + ssh_set_auth_methods(session, + SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + + banner = ssh_string_from_char("Banner Example\n"); + if (banner != NULL) { + ssh_send_issue_banner(session, banner); + } + ssh_string_free(banner); + + return SSH_AUTH_DENIED; +} + +static int auth_password(ssh_session session, const char *user, + const char *password, void *userdata){ + (void)userdata; + printf("Authenticating user %s pwd %s\n",user, password); + if(strcmp(user,USER) == 0 && strcmp(password, PASSWORD) == 0){ + authenticated = 1; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + if (tries >= 3){ + printf("Too many authentication tries\n"); + ssh_disconnect(session); + error = 1; + return SSH_AUTH_DENIED; + } + tries++; + return SSH_AUTH_DENIED; +} + +#ifdef WITH_GSSAPI +static int auth_gssapi_mic(ssh_session session, const char *user, const char *principal, void *userdata){ + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + (void)userdata; + printf("Authenticating user %s with gssapi principal %s\n",user, principal); + if (creds != NULL) + printf("Received some gssapi credentials\n"); + else + printf("Not received any forwardable creds\n"); + printf("authenticated\n"); + authenticated = 1; + return SSH_AUTH_SUCCESS; +} +#endif + +static int pty_request(ssh_session session, ssh_channel channel, const char *term, + int x,int y, int px, int py, void *userdata){ + (void) session; + (void) channel; + (void) term; + (void) x; + (void) y; + (void) px; + (void) py; + (void) userdata; + printf("Allocated terminal\n"); + return 0; +} + +static int shell_request(ssh_session session, ssh_channel channel, void *userdata){ + (void)session; + (void)channel; + (void)userdata; + printf("Allocated shell\n"); + return 0; +} +struct ssh_channel_callbacks_struct channel_cb = { + .channel_pty_request_function = pty_request, + .channel_shell_request_function = shell_request +}; + +static ssh_channel new_session_channel(ssh_session session, void *userdata){ + (void) session; + (void) userdata; + if(chan != NULL) + return NULL; + printf("Allocated session channel\n"); + chan = ssh_channel_new(session); + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(chan, &channel_cb); + return chan; +} + + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + { + .name = "config", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "Configuration file to use.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'r': + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case 'f': + ssh_bind_options_parse_config(sshbind, arg); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event mainloop = NULL; + struct ssh_server_callbacks_struct cb = { + .userdata = NULL, + .auth_none_function = auth_none, + .auth_password_function = auth_password, +#ifdef WITH_GSSAPI + .auth_gssapi_mic_function = auth_gssapi_mic, +#endif + .channel_open_request_session_function = new_session_channel + }; + + char buf[BUF_SIZE]; + int i; + int r; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n",ssh_get_error(sshbind)); + return 1; + } + r=ssh_bind_accept(sshbind,session); + if(r==SSH_ERROR){ + printf("error accepting a connection : %s\n",ssh_get_error(sshbind)); + return 1; + } + ssh_callbacks_init(&cb); + ssh_set_server_callbacks(session, &cb); + + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + ssh_set_auth_methods(session,SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + mainloop = ssh_event_new(); + ssh_event_add_session(mainloop, session); + + while (!(authenticated && chan != NULL)){ + if(error) + break; + r = ssh_event_dopoll(mainloop, -1); + if (r == SSH_ERROR){ + printf("Error : %s\n",ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + } + if(error){ + printf("Error, exiting loop\n"); + } else + printf("Authenticated and got a channel\n"); + do{ + i=ssh_channel_read(chan, buf, sizeof(buf) - 1, 0); + if(i>0) { + if (ssh_channel_write(chan, buf, i) == SSH_ERROR) { + printf("error writing to channel\n"); + return 1; + } + + buf[i] = '\0'; + printf("%s", buf); + fflush(stdout); + + if (buf[0] == '\x0d') { + if (ssh_channel_write(chan, "\n", 1) == SSH_ERROR) { + printf("error writing to channel\n"); + return 1; + } + + printf("\n"); + } + } + } while (i>0); + ssh_disconnect(session); + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/samplesshd-kbdint.c b/src/libs/libssh-0.12.2/examples/samplesshd-kbdint.c new file mode 100644 index 000000000000..919eb3383a1a --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/samplesshd-kbdint.c @@ -0,0 +1,414 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2011 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include +#include + +#ifndef BUF_SIZE +#define BUF_SIZE 2048 +#endif + +#define SSHD_USER "libssh" +#define SSHD_PASSWORD "libssh" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +static int port = 22; +static bool authenticated = false; + +#ifdef WITH_PCAP +static const char *pcap_file = "debug.server.pcap"; +static ssh_pcap_file pcap; + +static void set_pcap(ssh_session session){ + if(!pcap_file) + return; + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap=NULL; + return; + } + ssh_set_pcap_file(session,pcap); +} + +static void cleanup_pcap(void) { + ssh_pcap_file_free(pcap); + pcap=NULL; +} +#endif + + +static int auth_password(const char *user, const char *password) +{ + int cmp; + + cmp = strcmp(user, SSHD_USER); + if (cmp != 0) { + return 0; + } + cmp = strcmp(password, SSHD_PASSWORD); + if (cmp != 0) { + return 0; + } + + authenticated = true; + return 1; // authenticated +} +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " + SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, 0, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + port = atoi(arg); + break; + case 'r': + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +static const char *name = NULL; +static const char *instruction = NULL; +static const char *prompts[2]; +static char echo[] = { 1, 0 }; + +static int kbdint_check_response(ssh_session session) { + int count; + + count = ssh_userauth_kbdint_getnanswers(session); + if(count != 2) { + instruction = "Something weird happened :("; + return 0; + } + if(strcasecmp("Arthur Dent", + ssh_userauth_kbdint_getanswer(session, 0)) != 0) { + instruction = "OK, this is not YOUR name, " + "but it's a reference to the HGTG..."; + prompts[0] = "The main character's full name: "; + return 0; + } + if(strcmp("42", ssh_userauth_kbdint_getanswer(session, 1)) != 0) { + instruction = "Make an effort !!! What is the Answer to the Ultimate " + "Question of Life, the Universe, and Everything ?"; + prompts[1] = "Answer to the Ultimate Question of Life, the Universe, " + "and Everything: "; + return 0; + } + + authenticated = true; + return 1; +} + +static int authenticate(ssh_session session) { + ssh_message message; + + name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + instruction = "Please enter your real name and your password"; + prompts[0] = "Real name: "; + prompts[1] = "Password: "; + + do { + message=ssh_message_get(session); + if(!message) + break; + switch(ssh_message_type(message)){ + case SSH_REQUEST_AUTH: + switch(ssh_message_subtype(message)){ + case SSH_AUTH_METHOD_PASSWORD: + printf("User %s wants to auth with pass %s\n", + ssh_message_auth_user(message), + ssh_message_auth_password(message)); + if(auth_password(ssh_message_auth_user(message), + ssh_message_auth_password(message))){ + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + // not authenticated, send default message + ssh_message_reply_default(message); + break; + + case SSH_AUTH_METHOD_INTERACTIVE: + if(!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, name, + instruction, 2, prompts, echo); + } else { + if(kbdint_check_response(session)) { + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + break; + case SSH_AUTH_METHOD_NONE: + default: + printf("User %s wants to auth with unknown auth %d\n", + ssh_message_auth_user(message), + ssh_message_subtype(message)); + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + break; + } + break; + default: + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + ssh_message_free(message); + } while (1); + return 0; +} + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_message message = NULL; + ssh_channel chan = NULL; + char buf[BUF_SIZE]; + int auth=0; + int shell=0; + int i; + int r; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif +#ifdef WITH_PCAP + set_pcap(session); +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); + return 1; + } + printf("Started sample libssh sshd on port %d\n", port); + printf("You can login as the user %s with the password %s\n", SSHD_USER, + SSHD_PASSWORD); + r = ssh_bind_accept(sshbind, session); + if(r==SSH_ERROR){ + printf("Error accepting a connection: %s\n", ssh_get_error(sshbind)); + return 1; + } + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + + /* proceed to authentication */ + auth = authenticate(session); + if (!auth || !authenticated) { + printf("Authentication error: %s\n", ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + + + /* wait for a channel session */ + do { + message = ssh_message_get(session); + if(message){ + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(message) == SSH_CHANNEL_SESSION) { + chan = ssh_message_channel_request_open_reply_accept(message); + ssh_message_free(message); + break; + } else { + ssh_message_reply_default(message); + ssh_message_free(message); + } + } else { + break; + } + } while(!chan); + + if (!chan) { + printf("Error: client did not ask for a channel session (%s)\n", + ssh_get_error(session)); + ssh_finalize(); + return 1; + } + + + /* wait for a shell */ + do { + message = ssh_message_get(session); + if(message != NULL) { + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL && + ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) { + shell = 1; + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + break; + } + ssh_message_reply_default(message); + ssh_message_free(message); + } else { + break; + } + } while(!shell); + + if(!shell) { + printf("Error: No shell requested (%s)\n", ssh_get_error(session)); + return 1; + } + + + printf("it works !\n"); + do{ + i=ssh_channel_read(chan,buf, sizeof(buf), 0); + if(i>0) { + if(*buf == '' || *buf == '') + break; + if(i == 1 && *buf == '\r') + ssh_channel_write(chan, "\r\n", 2); + else + ssh_channel_write(chan, buf, i); + if (write(1,buf,i) < 0) { + printf("error writing to buffer\n"); + return 1; + } + } + } while (i>0); + ssh_channel_close(chan); + ssh_disconnect(session); + ssh_bind_free(sshbind); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/scp_download.c b/src/libs/libssh-0.12.2/examples/scp_download.c new file mode 100644 index 000000000000..24355e299f91 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/scp_download.c @@ -0,0 +1,194 @@ +/* scp_download.c + * Sample implementation of a tiny SCP downloader client + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include +#include +#include +#include + +#include +#include "examples_common.h" + +#ifndef BUF_SIZE +#define BUF_SIZE 16384 +#endif + +static int verbosity = 0; +static const char *createcommand = + "rm -fr /tmp/libssh_tests && mkdir /tmp/libssh_tests && " + "cd /tmp/libssh_tests && date > a && date > b && mkdir c && date > d"; +static char *host = NULL; + +static void usage(const char *argv0) +{ + fprintf(stderr, + "Usage : %s [options] host\n" + "sample tiny scp downloader client - libssh-%s\n" + "This program will create files in /tmp and try to fetch them\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc, argv, "v")) != -1) { + switch (i) { + case 'v': + verbosity++; + break; + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(argv[0]); + return -1; + } + } + host = argv[optind]; + if (host == NULL) + usage(argv[0]); + return 0; +} + +static void create_files(ssh_session session) +{ + ssh_channel channel = ssh_channel_new(session); + char buffer[1]; + int rc; + + if (channel == NULL) { + fprintf(stderr, "Error creating channel: %s\n", ssh_get_error(session)); + exit(EXIT_FAILURE); + } + if (ssh_channel_open_session(channel) != SSH_OK) { + fprintf(stderr, "Error creating channel: %s\n", ssh_get_error(session)); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + if (ssh_channel_request_exec(channel, createcommand) != SSH_OK) { + fprintf(stderr, + "Error executing command: %s\n", + ssh_get_error(session)); + ssh_channel_close(channel); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + while (!ssh_channel_is_eof(channel)) { + rc = ssh_channel_read(channel, buffer, 1, 1); + if (rc != 1) { + fprintf(stderr, "Error reading from channel\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); + return; + } + + rc = write(1, buffer, 1); + if (rc < 0) { + fprintf(stderr, "Error writing to buffer\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); + return; + } + } + ssh_channel_close(channel); + ssh_channel_free(channel); +} + +static int fetch_files(ssh_session session) +{ + int size; + char buffer[BUF_SIZE]; + int mode; + char *filename = NULL; + int r; + ssh_scp scp = ssh_scp_new(session, + SSH_SCP_READ | SSH_SCP_RECURSIVE, + "/tmp/libssh_tests/*"); + if (ssh_scp_init(scp) != SSH_OK) { + fprintf(stderr, "error initializing scp: %s\n", ssh_get_error(session)); + ssh_scp_free(scp); + return -1; + } + printf("Trying to download 3 files (a,b,d) and 1 directory (c)\n"); + do { + r = ssh_scp_pull_request(scp); + switch (r) { + case SSH_SCP_REQUEST_NEWFILE: + size = ssh_scp_request_get_size(scp); + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("downloading file %s, size %d, perms 0%o\n", + filename, + size, + mode); + free(filename); + ssh_scp_accept_request(scp); + r = ssh_scp_read(scp, buffer, sizeof(buffer)); + if (r == SSH_ERROR) { + fprintf(stderr, + "Error reading scp: %s\n", + ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + } + printf("done\n"); + break; + case SSH_ERROR: + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + case SSH_SCP_REQUEST_WARNING: + fprintf(stderr, "Warning: %s\n", ssh_scp_request_get_warning(scp)); + break; + case SSH_SCP_REQUEST_NEWDIR: + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("downloading directory %s, perms 0%o\n", filename, mode); + free(filename); + ssh_scp_accept_request(scp); + break; + case SSH_SCP_REQUEST_ENDDIR: + printf("End of directory\n"); + break; + case SSH_SCP_REQUEST_EOF: + printf("End of requests\n"); + goto end; + } + } while (1); +end: + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +} + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + if (opts(argc, argv) < 0) + return EXIT_FAILURE; + session = connect_ssh(host, NULL, NULL, verbosity); + if (session == NULL) + return EXIT_FAILURE; + create_files(session); + fetch_files(session); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/senddata.c b/src/libs/libssh-0.12.2/examples/senddata.c new file mode 100644 index 000000000000..5be65af34e1c --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/senddata.c @@ -0,0 +1,64 @@ +#include + +#include +#include "examples_common.h" + +#define LIMIT 0x100000000UL + +int main(void) +{ + ssh_session session = NULL; + ssh_channel channel = NULL; + char buffer[1024 * 1024] = {0}; + int rc; + uint64_t total = 0; + uint64_t lastshown = 4096; + session = connect_ssh("localhost", NULL, NULL, 0); + if (session == NULL) { + return 1; + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + ssh_disconnect(session); + return 1; + } + + rc = ssh_channel_open_session(channel); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + rc = ssh_channel_request_exec(channel, "cat > /dev/null"); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + while ((rc = ssh_channel_write(channel, buffer, sizeof(buffer))) > 0) { + total += rc; + if (total / 2 >= lastshown) { + printf("written %llx\n", (long long unsigned int)total); + lastshown = total; + } + if (total > LIMIT) + break; + } + + if (rc < 0) { + printf("error : %s\n", ssh_get_error(session)); + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + + ssh_disconnect(session); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/ssh_X11_client.c b/src/libs/libssh-0.12.2/examples/ssh_X11_client.c new file mode 100644 index 000000000000..195b259b39e1 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/ssh_X11_client.c @@ -0,0 +1,951 @@ +/* + * ssh.c - Simple example of SSH X11 client using libssh + * + * Copyright (C) 2022 Marco Fortina + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + * + * + * ssh_X11_client + * ============== + * + * AUTHOR URL + * https://gitlab.com/marco.fortina/libssh-x11-client/ + * + * This is a simple example of SSH X11 client using libssh. + * + * Features: + * + * - support local display (e.g. :0) + * - support remote display (e.g. localhost:10.0) + * - using callbacks and event polling to significantly reduce CPU utilization + * - use X11 forwarding with authentication spoofing (like openssh) + * + * Note: + * + * - part of this code was inspired by openssh's one. + * + * Dependencies: + * + * - gcc >= 7.5.0 + * - libssh >= 0.8.0 + * - libssh-dev >= 0.8.0 + * + * To Build: + * gcc -o ssh_X11_client ssh_X11_client.c -lssh -g + * + * Donations: + * + * If you liked this work and wish to support the developer please donate to: + * Bitcoin: 1N2rQimKbeUQA8N2LU5vGopYQJmZsBM2d6 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* + * Data Structures and Macros + */ + +#define _PATH_UNIX_X "/tmp/.X11-unix/X%d" +#define _XAUTH_CMD "/usr/bin/xauth list %s 2>/dev/null" + +typedef struct item { + ssh_channel channel; + int fd_in; + int fd_out; + int protected; + struct item *next; +} node_t; + +node_t *node = NULL; + + +/* + * Mutex + */ + +pthread_mutex_t mutex; + + +/* + * Function declarations + */ + +/* Linked nodes to manage channel/fd tuples */ +static int insert_item(ssh_channel channel, int fd_in, int fd_out, + int protected); +static void delete_item(ssh_channel channel); +static node_t * search_item(ssh_channel channel); + +/* X11 Display */ +const char * ssh_gai_strerror(int gaierr); +static int x11_get_proto(const char *display, char **_proto, char **_data); +static void set_nodelay(int fd); +static int connect_local_xsocket_path(const char *pathname); +static int connect_local_xsocket(int display_number); +static int x11_connect_display(void); + +/* Send data to channel */ +static int copy_fd_to_channel_callback(int fd, int revents, void *userdata); + +/* Read data from channel */ +static int copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, + void *userdata); + +/* EOF&Close channel */ +static void channel_close_callback(ssh_session session, ssh_channel channel, + void *userdata); + +/* X11 Request */ +static ssh_channel x11_open_request_callback(ssh_session session, + const char *shost, int sport, + void *userdata); + +/* Main loop */ +static int main_loop(ssh_channel channel); + +/* Internals */ +int64_t _current_timestamp(void); + +/* Global variables */ +const char *hostname = NULL; +int enableX11 = 1; + +/* + * Callbacks Data Structures + */ + +/* SSH Channel Callbacks */ +struct ssh_channel_callbacks_struct channel_cb = +{ + .channel_data_function = copy_channel_to_fd_callback, + .channel_eof_function = channel_close_callback, + .channel_close_function = channel_close_callback, + .userdata = NULL +}; + +/* SSH Callbacks */ +struct ssh_callbacks_struct cb = +{ + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL +}; + + +/* + * SSH Event Context + */ + +short events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; +ssh_event event; + + +/* + * Internal data structures + */ + +struct termios _saved_tio; + + +/* + * Internal functions + */ + +int64_t _current_timestamp(void) +{ + struct timeval tv; + int64_t milliseconds; + + gettimeofday(&tv, NULL); + milliseconds = (int64_t)(tv.tv_sec) * 1000 + (tv.tv_usec / 1000); + + return milliseconds; +} + +static void _logging_callback(int priority, const char *function, + const char *buffer, void *userdata) +{ + FILE *fp = NULL; + char buf[100]; + int64_t milliseconds; + + time_t now = time(0); + + (void)userdata; + + strftime(buf, 100, "%Y-%m-%d %H:%M:%S", localtime(&now)); + + fp = fopen("debug.log","a"); + if (fp == NULL) { + printf("Error!"); + exit(-11); + } + + milliseconds = _current_timestamp(); + + fprintf(fp, "[%s.%" PRId64 ", %d] %s: %s\n", buf, milliseconds, priority, + function, buffer); + fclose(fp); +} + +static int _enter_term_raw_mode(void) +{ + struct termios tio; + int ret = tcgetattr(fileno(stdin), &tio); + if (ret != -1) { + _saved_tio = tio; + tio.c_iflag |= IGNPAR; + tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); +#ifdef IUCLC + tio.c_iflag &= ~IUCLC; +#endif + tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); +#ifdef IEXTEN + tio.c_lflag &= ~IEXTEN; +#endif + tio.c_oflag &= ~OPOST; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + ret = tcsetattr(fileno(stdin), TCSADRAIN, &tio); + } + + return ret; +} + +static int _leave_term_raw_mode(void) +{ + int ret = tcsetattr(fileno(stdin), TCSADRAIN, &_saved_tio); + return ret; +} + + +/* + * Functions + */ + +static int insert_item(ssh_channel channel, int fd_in, int fd_out, + int protected) +{ + node_t *node_iterator = NULL, *new = NULL; + + pthread_mutex_lock(&mutex); + + if (node == NULL) { + /* Calloc ensure that node is full of 0 */ + node = (node_t *) calloc(1, sizeof(node_t)); + if (node == NULL) { + pthread_mutex_unlock(&mutex); + return -1; + } + node->channel = channel; + node->fd_in = fd_in; + node->fd_out = fd_out; + node->protected = protected; + node->next = NULL; + } else { + node_iterator = node; + while (node_iterator->next != NULL) { + node_iterator = node_iterator->next; + } + /* Create the new node */ + new = (node_t *) malloc(sizeof(node_t)); + if (new == NULL) { + pthread_mutex_unlock(&mutex); + return -1; + } + new->channel = channel; + new->fd_in = fd_in; + new->fd_out = fd_out; + new->protected = protected; + new->next = NULL; + node_iterator->next = new; + + } + + pthread_mutex_unlock(&mutex); + return 0; +} + + +static void delete_item(ssh_channel channel) +{ + node_t *current = NULL, *previous = NULL; + + pthread_mutex_lock(&mutex); + + for (current = node; current; previous = current, current = current->next) { + if (current->channel != channel) { + continue; + } + + if (previous == NULL) { + node = current->next; + } else { + previous->next = current->next; + } + + free(current); + pthread_mutex_unlock(&mutex); + return; + } + + pthread_mutex_unlock(&mutex); +} + + +static node_t *search_item(ssh_channel channel) +{ + node_t *current = NULL; + + pthread_mutex_lock(&mutex); + + current = node; + while (current != NULL) { + if (current->channel == channel) { + pthread_mutex_unlock(&mutex); + return current; + } else { + current = current->next; + } + } + + pthread_mutex_unlock(&mutex); + + return NULL; +} + + + +static void set_nodelay(int fd) +{ + int opt, rc; + socklen_t optlen; + + optlen = sizeof(opt); + + rc = getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen); + if (rc == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "getsockopt TCP_NODELAY: %.100s", + strerror(errno)); + return; + } + if (opt == 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d is TCP_NODELAY", fd); + return; + } + opt = 1; + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d setting TCP_NODELAY", fd); + + rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + if (rc == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "setsockopt TCP_NODELAY: %.100s", + strerror(errno)); + } +} + + +const char *ssh_gai_strerror(int gaierr) +{ + if (gaierr == EAI_SYSTEM && errno != 0) { + return strerror(errno); + } + return gai_strerror(gaierr); +} + + + +static int x11_get_proto(const char *display, char **_proto, char **_cookie) +{ + char cmd[1024], line[512], xdisplay[512]; + static char proto[512], cookie[512]; + FILE *f = NULL; + int ret = 0; + + *_proto = proto; + *_cookie = cookie; + + proto[0] = cookie[0] = '\0'; + + if (strncmp(display, "localhost:", 10) == 0) { + ret = snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10); + if (ret < 0 || (size_t)ret >= sizeof(xdisplay)) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "display name too long. display: %s", display); + return -1; + } + display = xdisplay; + } + + snprintf(cmd, sizeof(cmd), _XAUTH_CMD, display); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "xauth cmd: %s", cmd); + + f = popen(cmd, "r"); + if (f && fgets(line, sizeof(line), f) && + sscanf(line, "%*s %511s %511s", proto, cookie) == 2) { + ret = 0; + } else { + ret = 1; + } + + if (f) { + pclose(f); + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s - ret: %d", + proto, cookie, ret); + + return ret; +} + +static int connect_local_xsocket_path(const char *pathname) +{ + int sock, rc; + struct sockaddr_un addr; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", + strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_path[0] = '\0'; + /* pathname is guaranteed to be initialized and larger than addr.sun_path[108] */ + memcpy(addr.sun_path + 1, pathname, sizeof(addr.sun_path) - 1); + rc = connect(sock, (struct sockaddr *)&addr, + offsetof(struct sockaddr_un, sun_path) + 1 + strlen(pathname)); + if (rc == 0) { + return sock; + } + close(sock); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s: %.100s", + addr.sun_path, strerror(errno)); + + return -1; +} + + +static int connect_local_xsocket(int display_number) +{ + char buf[1024] = {0}; + snprintf(buf, sizeof(buf), _PATH_UNIX_X, display_number); + return connect_local_xsocket_path(buf); +} + + +static int x11_connect_display(void) +{ + int display_number; + const char *display = NULL; + char buf[1024], *cp = NULL; + struct addrinfo hints, *ai = NULL, *aitop = NULL; + char strport[NI_MAXSERV]; + int gaierr = 0, sock = 0; + + /* Try to open a socket for the local X server. */ + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (display == 0) { + return -1; + } + + /* Check if it is a unix domain socket. */ + if (strncmp(display, "unix:", 5) == 0 || display[0] == ':') { + /* Connect to the unix domain socket. */ + if (sscanf(strrchr(display, ':') + 1, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Could not parse display number from DISPLAY: %.100s", + display); + return -1; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display_number: %d", + display_number); + + /* Create a socket. */ + sock = connect_local_xsocket(display_number); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %d", sock); + + if (sock < 0) { + return -1; + } + + /* OK, we now have a connection to the display. */ + return sock; + } + + /* Connect to an inet socket. */ + strncpy(buf, display, sizeof(buf) - 1); + cp = strchr(buf, ':'); + if (cp == 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Could not find ':' in DISPLAY: %.100s", display); + return -1; + } + *cp = 0; + if (sscanf(cp + 1, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Could not parse display number from DISPLAY: %.100s", + display); + return -1; + } + + /* Look up the host address */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof(strport), "%u", 6000 + display_number); + gaierr = getaddrinfo(buf, strport, &hints, &aitop); + if (gaierr != 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "%.100s: unknown host. (%s)", + buf, ssh_gai_strerror(gaierr)); + return -1; + } + for (ai = aitop; ai; ai = ai->ai_next) { + /* Create a socket. */ + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", + strerror(errno)); + continue; + } + /* Connect it to the display. */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "connect %.100s port %u: %.100s", buf, + 6000 + display_number, strerror(errno)); + close(sock); + continue; + } + /* Success */ + break; + } + freeaddrinfo(aitop); + if (ai == 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s port %u: %.100s", + buf, 6000 + display_number, strerror(errno)); + return -1; + } + set_nodelay(sock); + + return sock; +} + + + +static int copy_fd_to_channel_callback(int fd, int revents, void *userdata) +{ + ssh_channel channel = (ssh_channel)userdata; + char buf[2097152]; + int sz = 0, ret = 0; + + node_t *temp_node = search_item(channel); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "event: %d - fd: %d", revents, fd); + + if (channel == NULL) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel does not exist."); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + + if (fcntl(fd, F_GETFD) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fcntl error. fd: %d", fd); + ssh_channel_close(channel); + return -1; + } + + if ((revents & POLLIN) || (revents & POLLPRI)) { + sz = read(fd, buf, sizeof(buf)); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sz: %d", sz); + if (sz > 0) { + ret = ssh_channel_write(channel, buf, sz); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel_write ret: %d", ret); + } else if (sz < 0) { + ssh_channel_close(channel); + return -1; + } else { + /* sz = 0. Why the hell I'm here? */ + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Why the hell am I here?: sz: %d", sz); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + } + + if ((revents & POLLHUP) || (revents & POLLNVAL) || (revents & POLLERR)) { + ssh_channel_close(channel); + return -1; + } + + return sz; +} + + +static int copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, + void *userdata) +{ + node_t *temp_node = NULL; + int fd, sz; + + (void)session; + (void)is_stderr; + (void)userdata; + + temp_node = search_item(channel); + + fd = temp_node->fd_out; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "len: %d - fd: %d - is_stderr: %d", + len, fd, is_stderr); + + sz = write(fd, data, len); + + return sz; +} + + +static void channel_close_callback(ssh_session session, ssh_channel channel, + void *userdata) +{ + node_t *temp_node = NULL; + + (void)session; + (void)userdata; + + temp_node = search_item(channel); + + if (temp_node != NULL) { + int fd = temp_node->fd_in; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd: %d", fd); + + delete_item(channel); + ssh_event_remove_fd(event, fd); + + if (temp_node->protected == 0) { + close(fd); + } + } +} + + +static ssh_channel x11_open_request_callback(ssh_session session, + const char *shost, int sport, + void *userdata) +{ + ssh_channel channel = NULL; + int sock, rv; + + (void)shost; + (void)sport; + (void)userdata; + + channel = ssh_channel_new(session); + + sock = x11_connect_display(); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sock: %d", sock); + + rv = insert_item(channel, sock, sock, 0); + if (rv != 0) { + ssh_channel_free(channel); + return NULL; + } + + ssh_event_add_fd(event, sock, events, copy_fd_to_channel_callback, channel); + ssh_event_add_session(event, session); + + ssh_add_channel_callbacks(channel, &channel_cb); + + return channel; +} + + + +/* + * MAIN LOOP + */ + +static int main_loop(ssh_channel channel) +{ + ssh_session session = ssh_channel_get_session(channel); + int rv; + + rv = insert_item(channel, fileno(stdin), fileno(stdout), 1); + if (rv != 0) { + return -1; + } + + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(channel, &channel_cb); + + event = ssh_event_new(); + if (event == NULL) { + printf("Couldn't get a event\n"); + return -1; + } + + rv = ssh_event_add_fd(event, fileno(stdin), events, + copy_fd_to_channel_callback, channel); + if (rv != SSH_OK) { + printf("Couldn't add an fd to the event\n"); + return -1; + } + + rv = ssh_event_add_session(event, session); + if (rv != SSH_OK) { + printf("Couldn't add the session to the event\n"); + return -1; + } + + do { + if (ssh_event_dopoll(event, 1000) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + /* fall through */ + } + } while (!ssh_channel_is_closed(channel)); + + delete_item(channel); + ssh_event_remove_fd(event, fileno(stdin)); + ssh_event_remove_session(event, session); + ssh_event_free(event); + + return 0; +} + + +/* + * USAGE + */ + +static void usage(void) +{ + fprintf(stderr, + "Usage : ssh-X11-client [options] [login@]hostname\n" + "sample X11 client - libssh-%s\n" + "Options :\n" + " -l user : Specifies the user to log in as on the remote " + "machine.\n" + " -p port : Port to connect to on the remote host.\n" + " -v : Verbose mode. Multiple -v options increase the " + "verbosity. The maximum is 5.\n" + " -C : Requests compression of all data.\n" + " -x : Disables X11 forwarding.\n" + "\n", + ssh_version(0)); + + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc,argv,"x")) != -1) { + switch (i) { + case 'x': + enableX11 = 0; + break; + default: + fprintf(stderr, "Unknown option %c\n", optopt); + return -1; + } + } + + if (optind < argc) { + hostname = argv[optind++]; + } + + if (hostname == NULL) { + return -1; + } + + return 0; +} + +/* + * MAIN + */ + +int main(int argc, char **argv) +{ + char *password = NULL; + + ssh_session session = NULL; + ssh_channel channel = NULL; + + int ret; + + const char *display = NULL; + char *proto = NULL, *cookie = NULL; + + ssh_set_log_callback(_logging_callback); + ret = ssh_init(); + if (ret != SSH_OK) { + return ret; + } + + session = ssh_new(); + if (session == NULL) { + exit(-1); + } + + if (ssh_options_getopt(session, &argc, argv) || opts(argc, argv)) { + fprintf(stderr, "Error parsing command line: %s\n", + ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); + usage(); + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, hostname) < 0) { + return -1; + } + + ret = ssh_connect(session); + if (ret != SSH_OK) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + exit(-1); + } + + password = getpass("Password: "); + ret = ssh_userauth_password(session, NULL, password); + if (ret != SSH_AUTH_SUCCESS) { + fprintf(stderr, "Error authenticating with password: %s\n", + ssh_get_error(session)); + exit(-1); + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + return SSH_ERROR; + } + + ret = ssh_channel_open_session(channel); + if (ret != SSH_OK) { + return ret; + } + + ret = ssh_channel_request_pty(channel); + if (ret != SSH_OK) { + return ret; + } + + ret = ssh_channel_change_pty_size(channel, 80, 24); + if (ret != SSH_OK) { + return ret; + } + + if (enableX11 == 1) { + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (display) { + ssh_callbacks_init(&cb); + ret = ssh_set_callbacks(session, &cb); + if (ret != SSH_OK) { + return ret; + } + + ret = x11_get_proto(display, &proto, &cookie); + if (ret != 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Using fake authentication data for X11 forwarding"); + proto = NULL; + cookie = NULL; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s", + proto, cookie); + /* See https://gitlab.com/libssh/libssh-mirror/-/blob/master/src/channels.c#L2062 for details. */ + ret = ssh_channel_request_x11(channel, 0, proto, cookie, 0); + if (ret != SSH_OK) { + return ret; + } + } + } + + ret = _enter_term_raw_mode(); + if (ret != 0) { + exit(-1); + } + + ret = ssh_channel_request_shell(channel); + if (ret != SSH_OK) { + return ret; + } + + ret = main_loop(channel); + if (ret != SSH_OK) { + return ret; + } + + _leave_term_raw_mode(); + + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); +} diff --git a/src/libs/libssh-0.12.2/examples/ssh_client.c b/src/libs/libssh-0.12.2/examples/ssh_client.c new file mode 100644 index 000000000000..1b5168300c28 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/ssh_client.c @@ -0,0 +1,450 @@ +/* ssh_client.c */ + +/* + * Copyright 2003-2015 Aris Adamantiadis + * + * This file is part of the SSH Library + * + * You are free to copy this file, modify it in any way, consider it being public + * domain. This does not apply to the rest of the library though, but it is + * allowed to cut-and-paste working code from this file to any license of + * program. + * The goal is to show the API in action. It's not a reference on how terminal + * clients must be made or how a client should react. + */ + +#include "config.h" +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_TERMIOS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif + +#include +#include +#include +#include + +#include +#include + +#include "examples_common.h" +#define MAXCMD 10 + +static char *host = NULL; +static char *user = NULL; +static char *cmds[MAXCMD]; +static char *config_file = NULL; +static struct termios terminal; + +static char *pcap_file = NULL; + +static char *proxycommand = NULL; + +static int auth_callback(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void) verify; + (void) userdata; + + return ssh_getpass(prompt, buf, len, echo, verify); +} + +struct ssh_callbacks_struct cb = { + .auth_function = auth_callback, + .userdata = NULL, +}; + +static void add_cmd(char *cmd) +{ + int n; + + for (n = 0; (n < MAXCMD) && cmds[n] != NULL; n++); + + if (n == MAXCMD) { + return; + } + + cmds[n] = cmd; +} + +static void usage(void) +{ + fprintf( + stderr, + "Usage : ssh [options] [login@]hostname\n" + "sample client - libssh-%s\n" + "Options :\n" + " -l user : log in as user\n" + " -p port : connect to port\n" + " -o option : set configuration option (e.g., -o Compression=yes)\n" + " -r : use RSA to verify host public key\n" + " -F file : parse configuration file instead of default one\n" +#ifdef WITH_PCAP + " -P file : create a pcap debugging file\n" +#endif +#ifndef _WIN32 + " -T proxycommand : command to execute as a socket proxy\n" +#endif + "\n", + ssh_version(0)); + + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc, argv, "T:P:F:")) != -1) { + switch (i) { + case 'P': + pcap_file = optarg; + break; + case 'F': + config_file = optarg; + break; +#ifndef _WIN32 + case 'T': + proxycommand = optarg; + break; +#endif + default: + fprintf(stderr, "Unknown option %c\n", optopt); + return -1; + } + } + if (optind < argc) { + host = argv[optind++]; + } + + while(optind < argc) { + add_cmd(argv[optind++]); + } + + if (host == NULL) { + return -1; + } + + return 0; +} + +#ifndef HAVE_CFMAKERAW +static void cfmakeraw(struct termios *termios_p) +{ + termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + termios_p->c_oflag &= ~OPOST; + termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); + termios_p->c_cflag &= ~(CSIZE|PARENB); + termios_p->c_cflag |= CS8; +} +#endif + + +static void do_cleanup(int i) +{ + (void)i; + + tcsetattr(0, TCSANOW, &terminal); +} + +static void do_exit(int i) +{ + (void)i; + + do_cleanup(0); + exit(0); +} + +static int signal_delayed = 0; + +#ifdef SIGWINCH +static void sigwindowchanged(int i) +{ + (void)i; + signal_delayed = 1; +} +#endif + +static void setsignal(void) +{ +#ifdef SIGWINCH + signal(SIGWINCH, sigwindowchanged); +#endif + signal_delayed = 0; +} + +static void sizechanged(ssh_channel chan) +{ + struct winsize win = { + .ws_row = 0, + }; + + ioctl(1, TIOCGWINSZ, &win); + ssh_channel_change_pty_size(chan,win.ws_col, win.ws_row); + setsignal(); +} + +static void select_loop(ssh_session session,ssh_channel channel) +{ + ssh_connector connector_in, connector_out, connector_err; + int rc; + + ssh_event event = ssh_event_new(); + + /* stdin */ + connector_in = ssh_connector_new(session); + ssh_connector_set_out_channel(connector_in, channel, SSH_CONNECTOR_STDINOUT); + ssh_connector_set_in_fd(connector_in, STDIN_FILENO); + ssh_event_add_connector(event, connector_in); + + /* stdout */ + connector_out = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_out, STDOUT_FILENO); + ssh_connector_set_in_channel(connector_out, channel, SSH_CONNECTOR_STDINOUT); + ssh_event_add_connector(event, connector_out); + + /* stderr */ + connector_err = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_err, STDERR_FILENO); + ssh_connector_set_in_channel(connector_err, channel, SSH_CONNECTOR_STDERR); + ssh_event_add_connector(event, connector_err); + + while (ssh_channel_is_open(channel)) { + if (signal_delayed) { + sizechanged(channel); + } + rc = ssh_event_dopoll(event, 60000); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error in ssh_event_dopoll()\n"); + break; + } + } + ssh_event_remove_connector(event, connector_in); + ssh_event_remove_connector(event, connector_out); + ssh_event_remove_connector(event, connector_err); + + ssh_connector_free(connector_in); + ssh_connector_free(connector_out); + ssh_connector_free(connector_err); + + ssh_event_free(event); +} + +static void shell(ssh_session session) +{ + ssh_channel channel = NULL; + struct termios terminal_local; + int interactive = isatty(0); + + channel = ssh_channel_new(session); + if (channel == NULL) { + return; + } + + if (interactive) { + tcgetattr(0, &terminal_local); + memcpy(&terminal, &terminal_local, sizeof(struct termios)); + } + + if (ssh_channel_open_session(channel)) { + printf("Error opening channel : %s\n", ssh_get_error(session)); + ssh_channel_free(channel); + return; + } + if (interactive) { + ssh_channel_request_pty(channel); + sizechanged(channel); + } + + if (ssh_channel_request_shell(channel)) { + printf("Requesting shell : %s\n", ssh_get_error(session)); + ssh_channel_free(channel); + return; + } + + if (interactive) { + cfmakeraw(&terminal_local); + tcsetattr(0, TCSANOW, &terminal_local); + setsignal(); + } + signal(SIGTERM, do_cleanup); + select_loop(session, channel); + if (interactive) { + do_cleanup(0); + } + ssh_channel_free(channel); +} + +static void batch_shell(ssh_session session) +{ + ssh_channel channel; + char *buffer = NULL; + size_t i, s, n; + + channel = ssh_channel_new(session); + if (channel == NULL) { + return; + } + + n = 0; + for (i = 0; i < MAXCMD && cmds[i]; ++i) { + /* Including space after cmds[i] */ + n += strlen(cmds[i]) + 1; + } + /* Trailing \0 */ + n += 1; + + buffer = malloc(n); + if (buffer == NULL) { + ssh_channel_free(channel); + return; + } + + s = 0; + for (i = 0; i < MAXCMD && cmds[i]; ++i) { + s += snprintf(buffer + s, n - s, "%s ", cmds[i]); + } + + ssh_channel_open_session(channel); + if (ssh_channel_request_exec(channel, buffer)) { + printf("Error executing '%s' : %s\n", buffer, ssh_get_error(session)); + free(buffer); + ssh_channel_free(channel); + return; + } + free(buffer); + select_loop(session, channel); + ssh_channel_free(channel); +} + +static int client(ssh_session session) +{ + int auth = 0; + char *banner = NULL; + int state; + + if (user) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + return -1; + } + } + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + return -1; + } + if (proxycommand != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, proxycommand)) { + return -1; + } + } + /* Parse configuration file if specified: The command-line options will + * overwrite items loaded from configuration file */ + if (ssh_options_parse_config(session, config_file) < 0) { + return -1; + } + + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + return -1; + } + + state = verify_knownhost(session); + if (state != 0) { + return -1; + } + + ssh_userauth_none(session, NULL); + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n", banner); + free(banner); + } + auth = authenticate_console(session); + if (auth != SSH_AUTH_SUCCESS) { + return -1; + } + if (cmds[0] == NULL) { + shell(session); + } else { + batch_shell(session); + } + + return 0; +} + +static ssh_pcap_file pcap; +static void set_pcap(ssh_session session) +{ + if (pcap_file == NULL) { + return; + } + + pcap = ssh_pcap_file_new(); + if (pcap == NULL) { + return; + } + + if (ssh_pcap_file_open(pcap, pcap_file) == SSH_ERROR) { + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap = NULL; + return; + } + ssh_set_pcap_file(session, pcap); +} + +static void cleanup_pcap(void) +{ + if (pcap != NULL) { + ssh_pcap_file_free(pcap); + } + pcap = NULL; +} + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + + ssh_init(); + session = ssh_new(); + + ssh_callbacks_init(&cb); + ssh_set_callbacks(session,&cb); + + if (ssh_options_getopt(session, &argc, argv) || opts(argc, argv)) { + fprintf(stderr, + "Error parsing command line: %s\n", + ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); + usage(); + } + signal(SIGTERM, do_exit); + + set_pcap(session); + client(session); + + ssh_disconnect(session); + ssh_free(session); + cleanup_pcap(); + + ssh_finalize(); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/ssh_server.c b/src/libs/libssh-0.12.2/examples/ssh_server.c new file mode 100644 index 000000000000..fb1541d92799 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/ssh_server.c @@ -0,0 +1,1015 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2014 Audrius Butkevicius + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. +*/ + +#include "config.h" + +#include +#include + +#include +#ifdef HAVE_ARGP_H +#include +#endif +#include +#ifdef HAVE_LIBUTIL_H +#include +#endif +#include +#ifdef HAVE_PTY_H +#include +#endif +#include +#include +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif +#include +#include +#include +#include + +#ifndef BUF_SIZE +#define BUF_SIZE 1048576 +#endif + +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) +#define SFTP_SERVER_PATH "/usr/lib/sftp-server" +#define AUTH_KEYS_MAX_LINE_SIZE 2048 + +#define DEF_STR_SIZE 1024 +char authorizedkeys[DEF_STR_SIZE] = {0}; +char username[128] = "myuser"; +char password[128] = "mypassword"; +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set a host key. Can be used multiple times. " + "Implies no default keys.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "ecdsakey", + .key = 'e', + .arg = "FILE", + .flags = 0, + .doc = "Set the ecdsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "authorizedkeys", + .key = 'a', + .arg = "FILE", + .flags = 0, + .doc = "Set the authorized keys file.", + .group = 0 + }, + { + .name = "user", + .key = 'u', + .arg = "USERNAME", + .flags = 0, + .doc = "Set expected username.", + .group = 0 + }, + { + .name = "pass", + .key = 'P', + .arg = "PASSWORD", + .flags = 0, + .doc = "Set expected password.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t +parse_opt(int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'e': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'a': + strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1); + break; + case 'u': + strncpy(username, arg, sizeof(username) - 1); + break; + case 'P': + strncpy(password, arg, sizeof(password) - 1); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage(state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#else +static int +parse_opt(int argc, char **argv, ssh_bind sshbind) +{ + int key; + + while((key = getopt(argc, argv, "a:e:k:p:P:r:u:v")) != -1) { + if (key == 'p') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, optarg); + } else if (key == 'k') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); + } else if (key == 'r') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); + } else if (key == 'e') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); + } else if (key == 'a') { + strncpy(authorizedkeys, optarg, DEF_STR_SIZE-1); + } else if (key == 'u') { + strncpy(username, optarg, sizeof(username) - 1); + } else if (key == 'P') { + strncpy(password, optarg, sizeof(password) - 1); + } else if (key == 'v') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + "3"); + } else { + break; + } + } + + if (key != -1) { + printf("Usage: %s [OPTION...] BINDADDR\n" + "libssh %s -- a Secure Shell protocol implementation\n" + "\n" + " -a, --authorizedkeys=FILE Set the authorized keys file.\n" + " -e, --ecdsakey=FILE Set the ecdsa key (deprecated alias for 'k').\n" + " -k, --hostkey=FILE Set a host key. Can be used multiple times.\n" + " Implies no default keys.\n" + " -p, --port=PORT Set the port to bind.\n" + " -P, --pass=PASSWORD Set expected password.\n" + " -r, --rsakey=FILE Set the rsa key (deprecated alias for 'k').\n" + " -u, --user=USERNAME Set expected username.\n" + " -v, --verbose Get verbose output.\n" + " -?, --help Give this help list\n" + "\n" + "Mandatory or optional arguments to long options are also mandatory or optional\n" + "for any corresponding short options.\n" + "\n" + "Report bugs to .\n", + argv[0], SSH_STRINGIFY(LIBSSH_VERSION)); + return -1; + } + + if (optind != argc - 1) { + printf("Usage: %s [OPTION...] BINDADDR\n", argv[0]); + return -1; + } + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, argv[optind]); + + return 0; +} +#endif /* HAVE_ARGP_H */ + +/* A userdata struct for channel. */ +struct channel_data_struct { + /* pid of the child process the channel will spawn. */ + pid_t pid; + /* For PTY allocation */ + socket_t pty_master; + socket_t pty_slave; + /* For communication with the child process. */ + socket_t child_stdin; + socket_t child_stdout; + /* Only used for subsystem and exec requests. */ + socket_t child_stderr; + /* Event which is used to poll the above descriptors. */ + ssh_event event; + /* Terminal size struct. */ + struct winsize *winsize; +}; + +/* A userdata struct for session. */ +struct session_data_struct { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + int auth_attempts; + int authenticated; +}; + +static int +data_function(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void)session; + (void)channel; + (void)is_stderr; + + if (len == 0 || cdata->pid < 1 || kill(cdata->pid, 0) < 0) { + return 0; + } + + return write(cdata->child_stdin, (char *)data, len); +} + +static int +pty_request(ssh_session session, + ssh_channel channel, + const char *term, + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + int rc; + + (void)session; + (void)channel; + (void)term; + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + rc = openpty(&cdata->pty_master, + &cdata->pty_slave, + NULL, + NULL, + cdata->winsize); + if (rc != 0) { + fprintf(stderr, "Failed to open pty\n"); + return SSH_ERROR; + } + return SSH_OK; +} + +static int +pty_resize(ssh_session session, + ssh_channel channel, + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void)session; + (void)channel; + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + if (cdata->pty_master != -1) { + return ioctl(cdata->pty_master, TIOCSWINSZ, cdata->winsize); + } + + return SSH_ERROR; +} + +static int +exec_pty(const char *mode, + const char *command, + struct channel_data_struct *cdata) +{ + cdata->pid = fork(); + switch (cdata->pid) { + case -1: + close(cdata->pty_master); + close(cdata->pty_slave); + fprintf(stderr, "Failed to fork\n"); + return SSH_ERROR; + case 0: + close(cdata->pty_master); + if (login_tty(cdata->pty_slave) != 0) { + exit(1); + } + execl("/bin/sh", "sh", mode, command, NULL); + exit(0); + default: + close(cdata->pty_slave); + /* pty fd is bi-directional */ + cdata->child_stdout = cdata->child_stdin = cdata->pty_master; + } + return SSH_OK; +} + +static int +exec_nopty(const char *command, struct channel_data_struct *cdata) +{ + int in[2], out[2], err[2]; + + /* Do the plumbing to be able to talk with the child process. */ + if (pipe(in) != 0) { + goto stdin_failed; + } + if (pipe(out) != 0) { + goto stdout_failed; + } + if (pipe(err) != 0) { + goto stderr_failed; + } + + cdata->pid = fork(); + switch (cdata->pid) { + case -1: + goto fork_failed; + case 0: + /* Finish the plumbing in the child process. */ + close(in[1]); + close(out[0]); + close(err[0]); + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + dup2(err[1], STDERR_FILENO); + close(in[0]); + close(out[1]); + close(err[1]); + /* exec the requested command. */ + execl("/bin/sh", "sh", "-c", command, NULL); + exit(0); + } + + close(in[0]); + close(out[1]); + close(err[1]); + + cdata->child_stdin = in[1]; + cdata->child_stdout = out[0]; + cdata->child_stderr = err[0]; + + return SSH_OK; + +fork_failed: + close(err[0]); + close(err[1]); +stderr_failed: + close(out[0]); + close(out[1]); +stdout_failed: + close(in[0]); + close(in[1]); +stdin_failed: + return SSH_ERROR; +} + +static int +exec_request(ssh_session session, + ssh_channel channel, + const char *command, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void)session; + (void)channel; + + if (cdata->pid > 0) { + return SSH_ERROR; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + return exec_pty("-c", command, cdata); + } + return exec_nopty(command, cdata); +} + +static int +shell_request(ssh_session session, ssh_channel channel, void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void)session; + (void)channel; + + if (cdata->pid > 0) { + return SSH_ERROR; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + return exec_pty("-l", NULL, cdata); + } + /* Client requested a shell without a pty, let's pretend we allow that */ + return SSH_OK; +} + +static int +subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + /* subsystem requests behave similarly to exec requests. */ + if (strcmp(subsystem, "sftp") == 0) { + return exec_request(session, channel, SFTP_SERVER_PATH, userdata); + } + return SSH_ERROR; +} + +static int +auth_password(ssh_session session, + const char *user, + const char *pass, + void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + (void)session; + + if (strcmp(user, username) == 0 && strcmp(pass, password) == 0) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + + sdata->auth_attempts++; + return SSH_AUTH_DENIED; +} + +static int +auth_publickey(ssh_session session, + const char *user, + struct ssh_key_struct *pubkey, + char signature_state, + void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + ssh_key key = NULL; + FILE *fp = NULL; + char line[AUTH_KEYS_MAX_LINE_SIZE] = {0}; + char *p = NULL; + const char *q = NULL; + unsigned int lineno = 0; + int result; + int i; + enum ssh_keytypes_e type; + + (void)user; + (void)session; + + if (signature_state == SSH_PUBLICKEY_STATE_NONE) { + return SSH_AUTH_SUCCESS; + } + + if (signature_state != SSH_PUBLICKEY_STATE_VALID) { + return SSH_AUTH_DENIED; + } + + fp = fopen(authorizedkeys, "r"); + if (fp == NULL) { + fprintf(stderr, "Error: opening authorized keys file %s failed, reason: %s\n", + authorizedkeys, strerror(errno)); + return SSH_AUTH_DENIED; + } + + while (fgets(line, sizeof(line), fp)) { + lineno++; + + /* Skip leading whitespace and ignore comments */ + p = line; + + for (i = 0; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (!isspace((int)p[i])) { + break; + } + } + + if (i >= AUTH_KEYS_MAX_LINE_SIZE) { + fprintf(stderr, + "warning: The line %d in %s too long! Skipping.\n", + lineno, + authorizedkeys); + continue; + } + + if (p[i] == '#' || p[i] == '\0' || p[i] == '\n') { + continue; + } + + q = &p[i]; + for (; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + type = ssh_key_type_from_name(q); + + i++; + if (i >= AUTH_KEYS_MAX_LINE_SIZE) { + fprintf(stderr, + "warning: The line %d in %s too long! Skipping.\n", + lineno, + authorizedkeys); + continue; + } + + q = &p[i]; + for (; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + result = ssh_pki_import_pubkey_base64(q, type, &key); + if (result != SSH_OK) { + fprintf(stderr, + "Warning: Cannot import key on line no. %d in authorized keys file: %s\n", + lineno, + authorizedkeys); + continue; + } + + result = ssh_key_cmp(key, pubkey, SSH_KEY_CMP_PUBLIC); + ssh_key_free(key); + if (result == 0) { + sdata->authenticated = 1; + fclose(fp); + return SSH_AUTH_SUCCESS; + } + } + if (ferror(fp) != 0) { + fprintf(stderr, + "Error: Reading from authorized keys file %s failed, reason: %s\n", + authorizedkeys, strerror(errno)); + } + fclose(fp); + + /* no matches */ + return SSH_AUTH_DENIED; +} + +static int kbdint_check_response(ssh_session session) +{ + int count, cmp; + const char *answer = NULL; + + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + cmp = strcasecmp("omnitrix", answer); + if (cmp != 0) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 1); + cmp = strcmp("000", answer); + if (cmp != 0) { + return 0; + } + + return 1; +} + +static int +auth_kbdint(ssh_message message, ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + const char *name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + const char *instruction = "Most powerful weapon in the galaxy"; + const char *prompts[2] = {"Name of the weapon: ", "Destruct Code: "}; + char echo[] = {1, 0}; + if (!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, + name, + instruction, + 2, + prompts, + echo); + return SSH_AUTH_INFO; + } else { + if (kbdint_check_response(session)) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + return SSH_AUTH_DENIED; + } +} + +static ssh_channel +channel_open(ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + sdata->channel = ssh_channel_new(session); + return sdata->channel; +} + +static int +process_stdout(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel)userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int +process_stderr(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel)userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +static void +handle_session(ssh_event event, ssh_session session) +{ + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_struct cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_struct sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0 + }; + + struct ssh_channel_callbacks_struct channel_cb = { + .userdata = &cdata, + .channel_pty_request_function = pty_request, + .channel_pty_window_change_function = pty_resize, + .channel_shell_request_function = shell_request, + .channel_exec_request_function = exec_request, + .channel_data_function = data_function, + .channel_subsystem_request_function = subsystem_request + }; + + struct ssh_server_callbacks_struct server_cb = { + .userdata = &sdata, + .auth_password_function = auth_password, + .auth_kbdint_function = auth_kbdint, + .channel_open_request_session_function = channel_open, + }; + + if (authorizedkeys[0]) { + server_cb.auth_pubkey_function = auth_publickey; + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_INTERACTIVE); + } else + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_INTERACTIVE); + + ssh_callbacks_init(&server_cb); + ssh_callbacks_init(&channel_cb); + + ssh_set_server_callbacks(session, &server_cb); + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= 3 || n >= 100) { + return; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + n++; + } + + ssh_set_channel_callbacks(sdata.channel, &channel_cb); + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, -1) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while (ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } +} + +#ifdef WITH_FORK +/* SIGCHLD handler for cleaning up dead children. */ +static void sigchld_handler(int signo) +{ + (void)signo; + while (waitpid(-1, NULL, WNOHANG) > 0); +} +#else +static void *session_thread(void *arg) +{ + ssh_session session = arg; + ssh_event event; + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child thread exiting, or client disconnecting. */ + handle_session(event, session); + ssh_event_free(event); + } else { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + return NULL; +} +#endif + +int main(int argc, char **argv) +{ + ssh_bind sshbind = NULL; + ssh_session session = NULL; + int rc; +#ifdef WITH_FORK + struct sigaction sa; + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGCHLD handler\n"); + return 1; + } +#endif + + rc = ssh_init(); + if (rc < 0) { + fprintf(stderr, "ssh_init failed\n"); + return 1; + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + fprintf(stderr, "ssh_bind_new failed\n"); + ssh_finalize(); + return 1; + } + +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, sshbind); +#else + if (parse_opt(argc, argv, sshbind) < 0) { + ssh_bind_free(sshbind); + ssh_finalize(); + return 1; + } +#endif /* HAVE_ARGP_H */ + + rc = ssh_bind_listen(sshbind); + if (rc < 0) { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + ssh_bind_free(sshbind); + ssh_finalize(); + return 1; + } + + while (1) { + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Failed to allocate session\n"); + continue; + } + + /* Blocks until there is a new incoming connection. */ + rc = ssh_bind_accept(sshbind, session); + if (rc != SSH_ERROR) { +#ifdef WITH_FORK + ssh_event event; + + pid_t pid = fork(); + switch (pid) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + handle_session(event, session); + ssh_event_free(event); + } else { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + + exit(0); + case -1: + fprintf(stderr, "Failed to fork\n"); + } +#else + pthread_t tid; + + rc = pthread_create(&tid, NULL, session_thread, session); + if (rc == 0) { + pthread_detach(tid); + continue; + } + fprintf(stderr, "Failed to pthread_create\n"); +#endif + } else { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + } + /* Since the session has been passed to a child fork, do some cleaning + * up at the parent process. */ + ssh_disconnect(session); + ssh_free(session); + } + + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/sshd_direct-tcpip.c b/src/libs/libssh-0.12.2/examples/sshd_direct-tcpip.c new file mode 100644 index 000000000000..9bb09111f805 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/sshd_direct-tcpip.c @@ -0,0 +1,746 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2009 Aris Adamantiadis +Copyright 2018 T. Wimmer + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +/* + Example: + ./sshd_direct-tcpip -v -p 2022 -r serverkey.rsa 127.0.0.1 +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#ifndef _WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#ifndef BUF_SIZE +#define BUF_SIZE 16384 +#endif + +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) + +#ifndef __unused__ +# ifdef HAVE_UNUSED_ATTRIBUTE +# define __unused__ __attribute__((unused)) +# else /* HAVE_UNUSED_ATTRIBUTE */ +# define __unused__ +# endif /* HAVE_UNUSED_ATTRIBUTE */ +#endif /* __unused__ */ + +#ifndef UNUSED_PARAM +#define UNUSED_PARAM(param) param __unused__ +#endif /* UNUSED_PARAM */ + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "user" +#define PASSWORD "pwd" + +struct event_fd_data_struct { + int *p_fd; + ssh_channel channel; + struct ssh_channel_callbacks_struct *cb_chan; + int stacked; +}; + +struct cleanup_node_struct { + struct event_fd_data_struct *data; + struct cleanup_node_struct *next; +}; + +static bool authenticated = false; +static int tries = 0; +static bool error_set = false; +static int sockets_cnt = 0; +static ssh_event mainloop = NULL; +static struct cleanup_node_struct *cleanup_stack = NULL; + +static void _close_socket(struct event_fd_data_struct event_fd_data); + +static void +cleanup_push(struct cleanup_node_struct** head_ref, + struct event_fd_data_struct *new_data) +{ + // Allocate memory for node + struct cleanup_node_struct *new_node = malloc(sizeof *new_node); + if (new_node == NULL) { + return; + } + + if (*head_ref != NULL) { + new_node->next = *head_ref; + } else { + new_node->next = NULL; + } + + // Copy new_data + new_node->data = new_data; + + // Change head pointer as new node is added at the beginning + (*head_ref) = new_node; +} + +static void +do_cleanup(struct cleanup_node_struct **head_ref) +{ + struct cleanup_node_struct *current = (*head_ref); + struct cleanup_node_struct *previous = NULL, *gone = NULL; + + while (current != NULL) { + if (ssh_channel_is_closed(current->data->channel)) { + if (current == (*head_ref)) { + (*head_ref) = current->next; + } + if (previous != NULL) { + previous->next = current->next; + } + gone = current; + current = current->next; + + if (gone->data->channel) { + _close_socket(*gone->data); + ssh_remove_channel_callbacks(gone->data->channel, gone->data->cb_chan); + ssh_channel_free(gone->data->channel); + gone->data->channel = NULL; + + SAFE_FREE(gone->data->p_fd); + SAFE_FREE(gone->data->cb_chan); + SAFE_FREE(gone->data); + SAFE_FREE(gone); + } + else { + fprintf(stderr, "channel already freed!\n"); + } + _ssh_log(SSH_LOG_FUNCTIONS, "=== do_cleanup", "Freed."); + } + else { + ssh_channel_close(current->data->channel); + previous = current; + current = current->next; + } + } +} + +static int +auth_password(ssh_session session, + const char *user, + const char *password, + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, + "=== auth_password", "Authenticating user %s pwd %s", + user, + password); + if (strcmp(user, USER) == 0 && strcmp(password, PASSWORD) == 0) { + authenticated = true; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + if (tries >= 3) { + printf("Too many authentication tries\n"); + ssh_disconnect(session); + error_set = true; + return SSH_AUTH_DENIED; + } + tries++; + return SSH_AUTH_DENIED; +} + +static int +auth_gssapi_mic(ssh_session session, + const char *user, + const char *principal, + UNUSED_PARAM(void *userdata)) +{ + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + printf("Authenticating user %s with gssapi principal %s\n", + user, principal); + if (creds != NULL) { + printf("Received some gssapi credentials\n"); + } else { + printf("Not received any forwardable creds\n"); + } + printf("authenticated\n"); + authenticated = true; + return SSH_AUTH_SUCCESS; +} + +static int +subsystem_request(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + const char *subsystem, + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, + "=== subsystem_request", "Channel subsystem request: %s", + subsystem); + return 0; +} + +struct ssh_channel_callbacks_struct channel_cb = { + .channel_subsystem_request_function = subsystem_request +}; + +static ssh_channel +new_session_channel(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, "=== subsystem_request", "Session channel request"); + /* For TCP forward only there seems to be no need for a session channel */ + /*if(chan != NULL) + return NULL; + printf("Session channel request\n"); + chan = ssh_channel_new(session); + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(chan, &channel_cb); + return chan;*/ + return NULL; +} + +static void +stack_socket_close(UNUSED_PARAM(ssh_session session), + struct event_fd_data_struct *event_fd_data) +{ + if (event_fd_data->stacked != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== stack_socket_close", + "Closing fd = %d sockets_cnt = %d", *event_fd_data->p_fd, + sockets_cnt); + event_fd_data->stacked = 1; + cleanup_push(&cleanup_stack, event_fd_data); + } +} + +static void +_close_socket(struct event_fd_data_struct event_fd_data) +{ + _ssh_log(SSH_LOG_FUNCTIONS, "=== close_socket", + "Closing fd = %d sockets_cnt = %d", *event_fd_data.p_fd, + sockets_cnt); + ssh_event_remove_fd(mainloop, *event_fd_data.p_fd); + sockets_cnt--; +#ifdef _WIN32 + closesocket(*event_fd_data.p_fd); +#else + close(*event_fd_data.p_fd); +#endif // _WIN32 + (*event_fd_data.p_fd) = SSH_INVALID_SOCKET; +} + +static int +service_request(UNUSED_PARAM(ssh_session session), + const char *service, + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, "=== service_request", "Service request: %s", service); + return 0; +} + +static void +global_request(UNUSED_PARAM(ssh_session session), + ssh_message message, + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, + "=== global_request", "Global request, message type: %d", + ssh_message_type(message)); +} + +static void +my_channel_close_function(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_close_function", + "Channel closed by remote."); + + stack_socket_close(session, event_fd_data); +} + +static void +my_channel_eof_function(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_eof_function", + "Got EOF on channel. Shutting down write on socket (fd = %d).", + *event_fd_data->p_fd); + + stack_socket_close(session, event_fd_data); +} + +static void +my_channel_exit_status_function(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + int exit_status, + void *userdata) +{ + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_exit_status_function", + "Got exit status %d on channel fd = %d.", + exit_status, *event_fd_data->p_fd); +} + +static int +my_channel_data_function(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + int i = 0; + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + + if (event_fd_data->channel == NULL) { + fprintf(stderr, "Why we're here? Stacked = %d\n", event_fd_data->stacked); + } + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_data_function", + "%d bytes waiting on channel for reading. Fd = %d", + len, + *event_fd_data->p_fd); + if (len > 0) { + i = send(*event_fd_data->p_fd, data, len, 0); + } + if (i < 0) { + _ssh_log(SSH_LOG_WARNING, "=== my_channel_data_function", + "Writing to tcp socket %d: %s", *event_fd_data->p_fd, + strerror(errno)); + stack_socket_close(session, event_fd_data); + } + else { + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_channel_data_function", "Sent %d bytes", i); + } + return i; +} + +static int +my_fd_data_function(UNUSED_PARAM(socket_t fd), + int revents, + void *userdata) +{ + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + ssh_channel channel = event_fd_data->channel; + ssh_session session = NULL; + int len, i, wr; + char buf[BUF_SIZE]; + int blocking; + + if (channel == NULL) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "channel == NULL!"); + return 0; + } + + session = ssh_channel_get_session(channel); + + if (ssh_channel_is_closed(channel)) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "channel is closed!"); + stack_socket_close(session, event_fd_data); + return 0; + } + + if (!(revents & POLLIN)) { + if (revents & POLLPRI) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLPRI"); + } + if (revents & POLLOUT) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLOUT"); + } + if (revents & POLLHUP) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLHUP"); + } + if (revents & POLLNVAL) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLNVAL"); + } + if (revents & POLLERR) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLERR"); + } + return 0; + } + + blocking = ssh_is_blocking(session); + ssh_set_blocking(session, 0); + + _ssh_log(SSH_LOG_FUNCTIONS, + "=== my_fd_data_function", + "Trying to read from tcp socket fd = %d", + *event_fd_data->p_fd); +#ifdef _WIN32 + struct sockaddr from; + int fromlen = sizeof(from); + len = recvfrom(*event_fd_data->p_fd, buf, sizeof(buf), 0, &from, &fromlen); +#else + len = recv(*event_fd_data->p_fd, buf, sizeof(buf), 0); +#endif // _WIN32 + if (len < 0) { + _ssh_log(SSH_LOG_WARNING, "=== my_fd_data_function", "Reading from tcp socket: %s", strerror(errno)); + + ssh_channel_send_eof(channel); + } + else if (len > 0) { + if (ssh_channel_is_open(channel)) { + wr = 0; + do { + i = ssh_channel_write(channel, buf, len); + if (i < 0) { + _ssh_log(SSH_LOG_WARNING, "=== my_fd_data_function", "Error writing on the direct-tcpip channel: %d", i); + len = wr; + break; + } + wr += i; + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "ssh_channel_write (%d from %d)", wr, len); + } while (i > 0 && wr < len); + } + else { + _ssh_log(SSH_LOG_WARNING, "=== my_fd_data_function", "Can't write on closed channel!"); + } + } + else { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "The destination host has disconnected!"); + + ssh_channel_close(channel); +#ifdef _WIN32 + shutdown(*event_fd_data->p_fd, SD_RECEIVE); +#else + shutdown(*event_fd_data->p_fd, SHUT_RD); +#endif // _WIN32 + } + ssh_set_blocking(session, blocking); + + return len; +} + +static int +open_tcp_socket(ssh_message msg) +{ + struct sockaddr_in sin; + int forwardsock = -1; + struct hostent *host = NULL; + const char *dest_hostname = NULL; + int dest_port; + + forwardsock = socket(AF_INET, SOCK_STREAM, 0); + if (forwardsock < 0) { + _ssh_log(SSH_LOG_WARNING, "=== open_tcp_socket", "ERROR opening socket: %s", strerror(errno)); + return -1; + } + + dest_hostname = ssh_message_channel_request_open_destination(msg); + dest_port = ssh_message_channel_request_open_destination_port(msg); + + _ssh_log(SSH_LOG_PROTOCOL, "=== open_tcp_socket", "Connecting to %s on port %d", dest_hostname, dest_port); + + host = gethostbyname(dest_hostname); + if (host == NULL) { + close(forwardsock); + _ssh_log(SSH_LOG_WARNING, "=== open_tcp_socket", "ERROR, no such host: %s", dest_hostname); + return -1; + } + + memset((char *)&sin, '\0', sizeof(sin)); + sin.sin_family = AF_INET; + memcpy((char *)&sin.sin_addr.s_addr, (char *)host->h_addr, host->h_length); + sin.sin_port = htons(dest_port); + + if (connect(forwardsock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + close(forwardsock); + _ssh_log(SSH_LOG_WARNING, "=== open_tcp_socket", "ERROR connecting: %s", strerror(errno)); + return -1; + } + + sockets_cnt++; + _ssh_log(SSH_LOG_FUNCTIONS, "=== open_tcp_socket", "Connected. sockets_cnt = %d", sockets_cnt); + return forwardsock; +} + +static int +message_callback(UNUSED_PARAM(ssh_session session), + ssh_message message, + UNUSED_PARAM(void *userdata)) +{ + ssh_channel channel; + int socket_fd, *pFd = NULL; + struct ssh_channel_callbacks_struct *cb_chan = NULL; + struct event_fd_data_struct *event_fd_data; + + _ssh_log(SSH_LOG_PACKET, "=== message_callback", "Message type: %d", + ssh_message_type(message)); + _ssh_log(SSH_LOG_PACKET, "=== message_callback", "Message Subtype: %d", + ssh_message_subtype(message)); + if (ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN) { + _ssh_log(SSH_LOG_PROTOCOL, "=== message_callback", "channel_request_open"); + + if (ssh_message_subtype(message) == SSH_CHANNEL_DIRECT_TCPIP) { + channel = ssh_message_channel_request_open_reply_accept(message); + + if (channel == NULL) { + _ssh_log(SSH_LOG_WARNING, "=== message_callback", "Accepting direct-tcpip channel failed!"); + return 1; + } + else { + _ssh_log(SSH_LOG_PROTOCOL, "=== message_callback", "Connected to channel!"); + + socket_fd = open_tcp_socket(message); + if (-1 == socket_fd) { + return 1; + } + + pFd = malloc(sizeof *pFd); + cb_chan = calloc(1, sizeof *cb_chan); + event_fd_data = malloc(sizeof *event_fd_data); + if (pFd == NULL || cb_chan == NULL || event_fd_data == NULL) { + SAFE_FREE(pFd); + SAFE_FREE(cb_chan); + SAFE_FREE(event_fd_data); + close(socket_fd); + return 1; + } + + (*pFd) = socket_fd; + event_fd_data->channel = channel; + event_fd_data->p_fd = pFd; + event_fd_data->stacked = 0; + event_fd_data->cb_chan = cb_chan; + + cb_chan->userdata = event_fd_data; + cb_chan->channel_eof_function = my_channel_eof_function; + cb_chan->channel_close_function = my_channel_close_function; + cb_chan->channel_data_function = my_channel_data_function; + cb_chan->channel_exit_status_function = my_channel_exit_status_function; + + ssh_callbacks_init(cb_chan); + ssh_set_channel_callbacks(channel, cb_chan); + + ssh_event_add_fd(mainloop, (socket_t)*pFd, POLLIN, my_fd_data_function, event_fd_data); + + return 0; + } + } + } + return 1; +} + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'r': + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "1"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int +main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + struct ssh_server_callbacks_struct cb = { + .userdata = NULL, + .auth_password_function = auth_password, + .auth_gssapi_mic_function = auth_gssapi_mic, + .channel_open_request_session_function = new_session_channel, + .service_request_function = service_request + }; + struct ssh_callbacks_struct cb_gen = { + .userdata = NULL, + .global_request_function = global_request + }; + + int ret = 1; + + sshbind = ssh_bind_new(); + session = ssh_new(); + mainloop = ssh_event_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void)argc; + (void)argv; +#endif + + if (ssh_bind_listen(sshbind) < 0) { + printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); + return 1; + } + + if (ssh_bind_accept(sshbind, session) == SSH_ERROR) { + printf("error accepting a connection : %s\n", ssh_get_error(sshbind)); + ret = 1; + goto shutdown; + } + + ssh_callbacks_init(&cb); + ssh_callbacks_init(&cb_gen); + ssh_set_server_callbacks(session, &cb); + ssh_set_callbacks(session, &cb_gen); + ssh_set_message_callback(session, message_callback, (void *)NULL); + + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + ret = 1; + goto shutdown; + } + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + ssh_event_add_session(mainloop, session); + + while (!authenticated) { + if (error_set) { + break; + } + if (ssh_event_dopoll(mainloop, -1) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + ret = 1; + goto shutdown; + } + } + if (error_set) { + printf("Error, exiting loop\n"); + } else { + printf("Authenticated and got a channel\n"); + + while (!error_set) { + if (ssh_event_dopoll(mainloop, 100) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + ret = 1; + goto shutdown; + } + do_cleanup(&cleanup_stack); + } + } + +shutdown: + ssh_disconnect(session); + ssh_bind_free(sshbind); + ssh_finalize(); + return ret; +} diff --git a/src/libs/libssh-0.12.2/examples/sshnetcat.c b/src/libs/libssh-0.12.2/examples/sshnetcat.c new file mode 100644 index 000000000000..3dba51e8a9dd --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/sshnetcat.c @@ -0,0 +1,287 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" +#include +#include +#include +#ifdef HAVE_TERMIOS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "examples_common.h" + +#ifndef BUF_SIZE +#define BUF_SIZE 4096 +#endif + +char *host = NULL; +const char *desthost = "localhost"; +const char *port = "22"; + +#ifdef WITH_PCAP +#include +char *pcap_file = NULL; +#endif + +static void usage(void) +{ + fprintf(stderr, + "Usage : sshnetcat [user@]host forwarded_host forwarded_port\n"); + exit(1); +} + +static int opts(int argc, char **argv) +{ + int i; + while ((i = getopt(argc, argv, "P:")) != -1) { + switch (i) { +#ifdef WITH_PCAP + case 'P': + pcap_file = optarg; + break; +#endif + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(); + } + } + if (optind < argc) + host = argv[optind++]; + if (optind < argc) + desthost = argv[optind++]; + if (optind < argc) + port = argv[optind++]; + if (host == NULL) + usage(); + return 0; +} + +static void select_loop(ssh_session session, ssh_channel channel) +{ + fd_set fds; + struct timeval timeout; + char buffer[BUF_SIZE]; + /* channels will be set to the channels to poll. + * outchannels will contain the result of the poll + */ + ssh_channel channels[2], outchannels[2]; + int lus; + int eof = 0; + int maxfd; + int ret; + while (channel) { + do { + int fd; + + ZERO_STRUCT(fds); + FD_ZERO(&fds); + if (!eof) + FD_SET(0, &fds); + timeout.tv_sec = 30; + timeout.tv_usec = 0; + + fd = ssh_get_fd(session); + if (fd == -1) { + fprintf(stderr, + "Error getting the session file descriptor: %s\n", + ssh_get_error(session)); + return; + } + FD_SET(fd, &fds); + maxfd = fd + 1; + + channels[0] = channel; // set the first channel we want to read from + channels[1] = NULL; + ret = ssh_select(channels, outchannels, maxfd, &fds, &timeout); + if (ret == EINTR) + continue; + if (FD_ISSET(0, &fds)) { + lus = read(0, buffer, sizeof(buffer)); + if (lus) + ssh_channel_write(channel, buffer, lus); + else { + eof = 1; + ssh_channel_send_eof(channel); + } + } + if (channel && ssh_channel_is_closed(channel)) { + ssh_channel_free(channel); + channel = NULL; + channels[0] = NULL; + } + if (outchannels[0]) { + while (channel && ssh_channel_is_open(channel) && + ssh_channel_poll(channel, 0)) { + lus = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (lus == -1) { + fprintf(stderr, + "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if (lus == 0) { + ssh_channel_free(channel); + channel = channels[0] = NULL; + } else { + ret = write(1, buffer, lus); + if (ret < 0) { + fprintf(stderr, + "Error writing to stdin: %s", + strerror(errno)); + return; + } + } + } + while (channel && ssh_channel_is_open(channel) && + ssh_channel_poll(channel, 1)) { /* stderr */ + lus = ssh_channel_read(channel, buffer, sizeof(buffer), 1); + if (lus == -1) { + fprintf(stderr, + "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if (lus == 0) { + ssh_channel_free(channel); + channel = channels[0] = NULL; + } else { + ret = write(2, buffer, lus); + if (ret < 0) { + fprintf(stderr, + "Error writing to stderr: %s", + strerror(errno)); + return; + } + } + } + } + if (channel && ssh_channel_is_closed(channel)) { + ssh_channel_free(channel); + channel = NULL; + } + } while (ret == EINTR || ret == SSH_EINTR); + } +} + +static void forwarding(ssh_session session) +{ + ssh_channel channel; + int r; + channel = ssh_channel_new(session); + r = ssh_channel_open_forward(channel, desthost, atoi(port), "localhost", 22); + if (r < 0) { + printf("error forwarding port : %s\n", ssh_get_error(session)); + return; + } + select_loop(session, channel); +} + +static int client(ssh_session session) +{ + int auth = 0; + char *banner = NULL; + int state; + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) + return -1; + ssh_options_parse_config(session, NULL); + + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + return -1; + } + state = verify_knownhost(session); + if (state != 0) + return -1; + ssh_userauth_none(session, NULL); + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n", banner); + free(banner); + } + auth = authenticate_console(session); + if (auth != SSH_AUTH_SUCCESS) { + return -1; + } + forwarding(session); + return 0; +} + +#ifdef WITH_PCAP +ssh_pcap_file pcap; +void set_pcap(ssh_session session); +void set_pcap(ssh_session session) +{ + if (!pcap_file) + return; + pcap = ssh_pcap_file_new(); + if (ssh_pcap_file_open(pcap, pcap_file) == SSH_ERROR) { + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap = NULL; + return; + } + ssh_set_pcap_file(session, pcap); +} + +void cleanup_pcap(void); +void cleanup_pcap(void) +{ + ssh_pcap_file_free(pcap); + pcap = NULL; +} +#endif + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + + session = ssh_new(); + + if (ssh_options_getopt(session, &argc, argv)) { + fprintf(stderr, + "error parsing command line :%s\n", + ssh_get_error(session)); + usage(); + } + opts(argc, argv); +#ifdef WITH_PCAP + set_pcap(session); +#endif + client(session); + + ssh_disconnect(session); + ssh_free(session); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + + ssh_finalize(); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/include/CMakeLists.txt b/src/libs/libssh-0.12.2/include/CMakeLists.txt new file mode 100644 index 000000000000..a01b5298e92f --- /dev/null +++ b/src/libs/libssh-0.12.2/include/CMakeLists.txt @@ -0,0 +1,3 @@ +project(libssh-headers-x C) + +add_subdirectory(libssh) diff --git a/src/libs/libssh-0.12.2/include/libssh/CMakeLists.txt b/src/libs/libssh-0.12.2/include/libssh/CMakeLists.txt new file mode 100644 index 000000000000..109e6837a1c8 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/CMakeLists.txt @@ -0,0 +1,53 @@ +project(libssh-headers C) + +set(libssh_HDRS + callbacks.h + libssh.h + ssh2.h + legacy.h + libsshpp.hpp +) + +if (WITH_SFTP) + set(libssh_HDRS + ${libssh_HDRS} + sftp.h + ) +endif (WITH_SFTP) + +if (WITH_SERVER) + set(libssh_HDRS + ${libssh_HDRS} + server.h + ) + + if (WITH_SFTP) + set(libssh_HDRS + ${libssh_HDRS} + sftpserver.h + ) + endif (WITH_SFTP) +endif (WITH_SERVER) + +if (WITH_FIDO2) + set(libssh_HDRS + ${libssh_HDRS} + sk_api.h + ) +endif (WITH_FIDO2) + +install( + FILES + ${libssh_HDRS} + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${APPLICATION_NAME} + COMPONENT + headers +) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libssh_version.h.cmake + ${libssh_BINARY_DIR}/include/libssh/libssh_version.h + @ONLY) +install(FILES ${libssh_BINARY_DIR}/include/libssh/libssh_version.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${APPLICATION_NAME} + COMPONENT headers) diff --git a/src/libs/libssh-0.12.2/include/libssh/agent.h b/src/libs/libssh-0.12.2/include/libssh/agent.h new file mode 100644 index 000000000000..caf8d3e263d4 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/agent.h @@ -0,0 +1,126 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __AGENT_H +#define __AGENT_H + +#include "libssh/libssh.h" + +/* Messages for the authentication agent connection. */ +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENT_RSA_RESPONSE 4 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 +#define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 + +/* private OpenSSH extensions for SSH2 */ +#define SSH2_AGENTC_REQUEST_IDENTITIES 11 +#define SSH2_AGENT_IDENTITIES_ANSWER 12 +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH2_AGENT_SIGN_RESPONSE 14 +#define SSH2_AGENTC_ADD_IDENTITY 17 +#define SSH2_AGENTC_REMOVE_IDENTITY 18 +#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 + +/* smartcard */ +#define SSH_AGENTC_ADD_SMARTCARD_KEY 20 +#define SSH_AGENTC_REMOVE_SMARTCARD_KEY 21 + +/* lock/unlock the agent */ +#define SSH_AGENTC_LOCK 22 +#define SSH_AGENTC_UNLOCK 23 + +/* add key with constraints */ +#define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED 24 +#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25 +#define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26 + +#define SSH_AGENT_CONSTRAIN_LIFETIME 1 +#define SSH_AGENT_CONSTRAIN_CONFIRM 2 + +/* extended failure messages */ +#define SSH2_AGENT_FAILURE 30 + +/* additional error code for ssh.com's ssh-agent2 */ +#define SSH_COM_AGENT2_FAILURE 102 + +#define SSH_AGENT_OLD_SIGNATURE 0x01 +/* Signature flags from draft-miller-ssh-agent-02 */ +#define SSH_AGENT_RSA_SHA2_256 0x02 +#define SSH_AGENT_RSA_SHA2_512 0x04 + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_agent_struct { + struct ssh_socket_struct *sock; + ssh_buffer ident; + unsigned int count; + ssh_channel channel; +}; + +/* agent.c */ +/** + * @brief Create a new ssh agent structure. + * + * @return An allocated ssh agent structure or NULL on error. + */ +struct ssh_agent_struct *ssh_agent_new(struct ssh_session_struct *session); + +void ssh_agent_close(struct ssh_agent_struct *agent); + +/** + * @brief Free an allocated ssh agent structure. + * + * @param agent The ssh agent structure to free. + */ +void ssh_agent_free(struct ssh_agent_struct *agent); + +/** + * @brief Check if the ssh agent is running. + * + * @param session The ssh session to check for the agent. + * + * @return 1 if it is running, 0 if not. + */ +int ssh_agent_is_running(struct ssh_session_struct *session); + +uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session); + +ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, + char **comment); + +ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, + char **comment); + +ssh_string ssh_agent_sign_data(ssh_session session, + const ssh_key pubkey, + struct ssh_buffer_struct *data); + +#ifdef __cplusplus +} +#endif + +#endif /* __AGENT_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/auth.h b/src/libs/libssh-0.12.2/include/libssh/auth.h new file mode 100644 index 000000000000..6c630292fc2d --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/auth.h @@ -0,0 +1,113 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AUTH_H_ +#define AUTH_H_ +#include "config.h" +#include "libssh/callbacks.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner); +SSH_PACKET_CALLBACK(ssh_packet_userauth_failure); +SSH_PACKET_CALLBACK(ssh_packet_userauth_success); +SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response); + +/** @internal + * kdbint structure must be shared with message.c + * and server.c + */ +struct ssh_kbdint_struct { + uint32_t nprompts; + uint32_t nanswers; + char *name; + char *instruction; + char **prompts; + unsigned char *echo; /* bool array */ + char **answers; +}; +typedef struct ssh_kbdint_struct* ssh_kbdint; + +ssh_kbdint ssh_kbdint_new(void); +void ssh_kbdint_clean(ssh_kbdint kbd); +void ssh_kbdint_free(ssh_kbdint kbd); + +/** @internal + * States of authentication in the client-side. They describe + * what was the last response from the server + */ +enum ssh_auth_state_e { + /** No authentication asked */ + SSH_AUTH_STATE_NONE = 0, + /** Last authentication response was a partial success */ + SSH_AUTH_STATE_PARTIAL, + /** Last authentication response was a success */ + SSH_AUTH_STATE_SUCCESS, + /** Last authentication response was failed */ + SSH_AUTH_STATE_FAILED, + /** Last authentication was erroneous */ + SSH_AUTH_STATE_ERROR, + /** Last state was a keyboard-interactive ask for info */ + SSH_AUTH_STATE_INFO, + /** Last state was a public key accepted for authentication */ + SSH_AUTH_STATE_PK_OK, + /** We asked for a keyboard-interactive authentication */ + SSH_AUTH_STATE_KBDINT_SENT, + /** We have sent an userauth request with gssapi-with-mic */ + SSH_AUTH_STATE_GSSAPI_REQUEST_SENT, + /** We are exchanging tokens until authentication */ + SSH_AUTH_STATE_GSSAPI_TOKEN, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_MIC_SENT, + /** We have offered a pubkey to check if it is supported */ + SSH_AUTH_STATE_PUBKEY_OFFER_SENT, + /** We have sent pubkey and signature expecting to be authenticated */ + SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + /** We have sent a password expecting to be authenticated */ + SSH_AUTH_STATE_PASSWORD_AUTH_SENT, + /** We have sent a request without auth information (method 'none') */ + SSH_AUTH_STATE_AUTH_NONE_SENT, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT, +}; + +/** @internal + * @brief states of the authentication service request + */ +enum ssh_auth_service_state_e { + /** initial state */ + SSH_AUTH_SERVICE_NONE=0, + /** Authentication service request packet sent */ + SSH_AUTH_SERVICE_SENT, + /** Service accepted */ + SSH_AUTH_SERVICE_ACCEPTED, + /** Access to service denied (fatal) */ + SSH_AUTH_SERVICE_DENIED, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* AUTH_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/bignum.h b/src/libs/libssh-0.12.2/include/libssh/bignum.h new file mode 100644 index 000000000000..37e87bc05125 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/bignum.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BIGNUM_H_ +#define BIGNUM_H_ + +#include "libssh/libcrypto.h" +#include "libssh/libgcrypt.h" +#include "libssh/libmbedcrypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bignum ssh_make_string_bn(ssh_string string); +ssh_string ssh_make_bignum_string(bignum num); +ssh_string ssh_make_padded_bignum_string(bignum num, size_t pad_len); +void ssh_print_bignum(const char *which, const_bignum num); + +#ifdef __cplusplus +} +#endif + +#endif /* BIGNUM_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/bind.h b/src/libs/libssh-0.12.2/include/libssh/bind.h new file mode 100644 index 000000000000..a848003e4072 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/bind.h @@ -0,0 +1,68 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BIND_H_ +#define BIND_H_ + +#include "libssh/priv.h" +#include "libssh/kex.h" +#include "libssh/session.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_bind_struct { + struct ssh_common_struct common; /* stuff common to ssh_bind and ssh_session */ + struct ssh_bind_callbacks_struct *bind_callbacks; + void *bind_callbacks_userdata; + + struct ssh_poll_handle_struct *poll; + /* options */ + char *wanted_methods[SSH_KEX_METHODS]; + char *banner; + char *ecdsakey; + char *rsakey; + char *ed25519key; + ssh_key ecdsa; + ssh_key rsa; + ssh_key ed25519; + char *bindaddr; + socket_t bindfd; + unsigned int bindport; + int blocking; + int toaccept; + bool config_processed; + char *config_dir; + char *pubkey_accepted_key_types; + char* moduli_file; + int rsa_min_size; + bool gssapi_key_exchange; + char *gssapi_key_exchange_algs; +}; + +struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct + *sshbind); + +#ifdef __cplusplus +} +#endif + +#endif /* BIND_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/bind_config.h b/src/libs/libssh-0.12.2/include/libssh/bind_config.h new file mode 100644 index 000000000000..54346d8eb16c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/bind_config.h @@ -0,0 +1,83 @@ +/* + * bind_config.h - Parse the SSH server configuration file + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef BIND_CONFIG_H_ +#define BIND_CONFIG_H_ + +#include "libssh/server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_bind_config_opcode_e { + /* Known but not allowed in Match block */ + BIND_CFG_NOT_ALLOWED_IN_MATCH = -4, + /* Unknown opcode */ + BIND_CFG_UNKNOWN = -3, + /* Known and not applicable to libssh */ + BIND_CFG_NA = -2, + /* Known but not supported by current libssh version */ + BIND_CFG_UNSUPPORTED = -1, + BIND_CFG_INCLUDE, + BIND_CFG_HOSTKEY, + BIND_CFG_LISTENADDRESS, + BIND_CFG_PORT, + BIND_CFG_LOGLEVEL, + BIND_CFG_CIPHERS, + BIND_CFG_MACS, + BIND_CFG_KEXALGORITHMS, + BIND_CFG_MATCH, + BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES, + BIND_CFG_HOSTKEY_ALGORITHMS, + BIND_CFG_REQUIRED_RSA_SIZE, + + BIND_CFG_MAX /* Keep this one last in the list */ +}; + +/* @brief Parse configuration file and set the options to the given ssh_bind + * + * @params[in] sshbind The ssh_bind context to be configured + * @params[in] filename The path to the configuration file + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_bind_config_parse_file(ssh_bind sshbind, const char *filename); + +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input); + +#ifdef __cplusplus +} +#endif + +#endif /* BIND_CONFIG_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/blf.h b/src/libs/libssh-0.12.2/include/libssh/blf.h new file mode 100644 index 000000000000..71928a7d261c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/blf.h @@ -0,0 +1,93 @@ +/* $OpenBSD: blf.h,v 1.8 2021/11/29 01:04:45 djm Exp $ */ +/* + * Blowfish - a fast block cipher designed by Bruce Schneier + * + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _BLF_H_ +#define _BLF_H_ + +//#include "includes.h" + +#if !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) + +/* Schneier specifies a maximum key length of 56 bytes. + * This ensures that every key bit affects every cipher + * bit. However, the subkeys can hold up to 72 bytes. + * Warning: For normal blowfish encryption only 56 bytes + * of the key affect all cipherbits. + */ + +#define BLF_N 16 /* Number of Subkeys */ +#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */ +#define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Blowfish context */ +typedef struct BlowfishContext { + uint32_t S[4][256]; /* S-Boxes */ + uint32_t P[BLF_N + 2]; /* Subkeys */ +} ssh_blf_ctx; + +/* Raw access to customized Blowfish + * blf_key is just: + * Blowfish_initstate( state ) + * Blowfish_expand0state( state, key, keylen ) + */ + +void Blowfish_encipher(ssh_blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_decipher(ssh_blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_initstate(ssh_blf_ctx *); +void Blowfish_expand0state(ssh_blf_ctx *, const uint8_t *, uint16_t); +void Blowfish_expandstate +(ssh_blf_ctx *, const uint8_t *, uint16_t, const uint8_t *, uint16_t); + +/* Standard Blowfish */ + +void ssh_blf_key(ssh_blf_ctx *, const uint8_t *, uint16_t); +void ssh_blf_enc(ssh_blf_ctx *, uint32_t *, uint16_t); +void ssh_blf_dec(ssh_blf_ctx *, uint32_t *, uint16_t); + +void ssh_blf_ecb_encrypt(ssh_blf_ctx *, uint8_t *, uint32_t); +void ssh_blf_ecb_decrypt(ssh_blf_ctx *, uint8_t *, uint32_t); + +void ssh_blf_cbc_encrypt(ssh_blf_ctx *, uint8_t *, uint8_t *, uint32_t); +void ssh_blf_cbc_decrypt(ssh_blf_ctx *, uint8_t *, uint8_t *, uint32_t); + +/* Converts uint8_t to uint32_t */ +uint32_t Blowfish_stream2word(const uint8_t *, uint16_t , uint16_t *); + +#endif /* !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) */ + +#ifdef __cplusplus +} +#endif + +#endif /* _BLF_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/buffer.h b/src/libs/libssh-0.12.2/include/libssh/buffer.h new file mode 100644 index 000000000000..108225f13504 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/buffer.h @@ -0,0 +1,83 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BUFFER_H_ +#define BUFFER_H_ + +#include + +#include "libssh/libssh.h" + +#define SSH_BUFFER_PACK_END ((uint32_t) 0x4f65feb3) + +#ifdef __cplusplus +extern "C" { +#endif + +void ssh_buffer_set_secure(ssh_buffer buffer); +int ssh_buffer_add_ssh_string(ssh_buffer buffer, ssh_string string); +int ssh_buffer_add_u8(ssh_buffer buffer, uint8_t data); +int ssh_buffer_add_u16(ssh_buffer buffer, uint16_t data); +int ssh_buffer_add_u32(ssh_buffer buffer, uint32_t data); +int ssh_buffer_add_u64(ssh_buffer buffer, uint64_t data); + +int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len); + +void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len); +int ssh_buffer_allocate_size(struct ssh_buffer_struct *buffer, uint32_t len); +int _ssh_buffer_pack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...); +#define ssh_buffer_pack(buffer, format, ...) \ + _ssh_buffer_pack((buffer), (format), __VA_NARG__(__VA_ARGS__), __VA_ARGS__, SSH_BUFFER_PACK_END) + +int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, + const char *format, size_t argc, + va_list ap); +int _ssh_buffer_unpack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...); +#define ssh_buffer_unpack(buffer, format, ...) \ + _ssh_buffer_unpack((buffer), (format), __VA_NARG__(__VA_ARGS__), __VA_ARGS__, SSH_BUFFER_PACK_END) + +int ssh_buffer_prepend_data(ssh_buffer buffer, const void *data, uint32_t len); +int ssh_buffer_add_buffer(ssh_buffer buffer, ssh_buffer source); + +/* buffer_read_*() returns the number of bytes read, except for ssh strings */ +uint32_t ssh_buffer_get_u8(ssh_buffer buffer, uint8_t *data); +uint32_t ssh_buffer_get_u32(ssh_buffer buffer, uint32_t *data); +uint32_t ssh_buffer_get_u64(ssh_buffer buffer, uint64_t *data); + +/* ssh_buffer_get_ssh_string() is an exception. if the String read is too large or invalid, it will answer NULL. */ +ssh_string ssh_buffer_get_ssh_string(ssh_buffer buffer); + +/* ssh_buffer_pass_bytes acts as if len bytes have been read (used for padding) */ +uint32_t ssh_buffer_pass_bytes_end(ssh_buffer buffer, uint32_t len); +uint32_t ssh_buffer_pass_bytes(ssh_buffer buffer, uint32_t len); + +ssh_buffer ssh_buffer_dup(const ssh_buffer buffer); + +#ifdef __cplusplus +} +#endif + +#endif /* BUFFER_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/bytearray.h b/src/libs/libssh-0.12.2/include/libssh/bytearray.h new file mode 100644 index 000000000000..0c0690d67cb9 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/bytearray.h @@ -0,0 +1,90 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef _BYTEARRAY_H +#define _BYTEARRAY_H + +#define _DATA_BYTE_CONST(data, pos) \ + ((uint8_t)(((const uint8_t *)(data))[(pos)])) + +#define _DATA_BYTE(data, pos) \ + (((uint8_t *)(data))[(pos)]) + +/* + * These macros pull or push integer values from byte arrays stored in + * little-endian byte order. + */ +#define PULL_LE_U8(data, pos) \ + (_DATA_BYTE_CONST(data, pos)) + +#define PULL_LE_U16(data, pos) \ + ((uint16_t)PULL_LE_U8(data, pos) | ((uint16_t)(PULL_LE_U8(data, (pos) + 1))) << 8) + +#define PULL_LE_U32(data, pos) \ + ((uint32_t)(PULL_LE_U16(data, pos) | ((uint32_t)PULL_LE_U16(data, (pos) + 2)) << 16)) + +#define PULL_LE_U64(data, pos) \ + ((uint64_t)(PULL_LE_U32(data, pos) | ((uint64_t)PULL_LE_U32(data, (pos) + 4)) << 32)) + + +#define PUSH_LE_U8(data, pos, val) \ + (_DATA_BYTE(data, pos) = ((uint8_t)(val))) + +#define PUSH_LE_U16(data, pos, val) \ + (PUSH_LE_U8((data), (pos), (uint8_t)((uint16_t)(val) & 0xff)), PUSH_LE_U8((data), (pos) + 1, (uint8_t)((uint16_t)(val) >> 8))) + +#define PUSH_LE_U32(data, pos, val) \ + (PUSH_LE_U16((data), (pos), (uint16_t)((uint32_t)(val) & 0xffff)), PUSH_LE_U16((data), (pos) + 2, (uint16_t)((uint32_t)(val) >> 16))) + +#define PUSH_LE_U64(data, pos, val) \ + (PUSH_LE_U32((data), (pos), (uint32_t)((uint64_t)(val) & 0xffffffff)), PUSH_LE_U32((data), (pos) + 4, (uint32_t)((uint64_t)(val) >> 32))) + + + +/* + * These macros pull or push integer values from byte arrays stored in + * big-endian byte order (network byte order). + */ +#define PULL_BE_U8(data, pos) \ + (_DATA_BYTE_CONST(data, pos)) + +#define PULL_BE_U16(data, pos) \ + ((((uint16_t)(PULL_BE_U8(data, pos))) << 8) | (uint16_t)PULL_BE_U8(data, (pos) + 1)) + +#define PULL_BE_U32(data, pos) \ + ((((uint32_t)PULL_BE_U16(data, pos)) << 16) | (uint32_t)(PULL_BE_U16(data, (pos) + 2))) + +#define PULL_BE_U64(data, pos) \ + ((((uint64_t)PULL_BE_U32(data, pos)) << 32) | (uint64_t)(PULL_BE_U32(data, (pos) + 4))) + + + +#define PUSH_BE_U8(data, pos, val) \ + (_DATA_BYTE(data, pos) = ((uint8_t)(val))) + +#define PUSH_BE_U16(data, pos, val) \ + (PUSH_BE_U8((data), (pos), (uint8_t)(((uint16_t)(val)) >> 8)), PUSH_BE_U8((data), (pos) + 1, (uint8_t)((val) & 0xff))) + +#define PUSH_BE_U32(data, pos, val) \ + (PUSH_BE_U16((data), (pos), (uint16_t)(((uint32_t)(val)) >> 16)), PUSH_BE_U16((data), (pos) + 2, (uint16_t)((val) & 0xffff))) + +#define PUSH_BE_U64(data, pos, val) \ + (PUSH_BE_U32((data), (pos), (uint32_t)(((uint64_t)(val)) >> 32)), PUSH_BE_U32((data), (pos) + 4, (uint32_t)((val) & 0xffffffff))) + +#endif /* _BYTEARRAY_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/callbacks.h b/src/libs/libssh-0.12.2/include/libssh/callbacks.h new file mode 100644 index 000000000000..b719532a5449 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/callbacks.h @@ -0,0 +1,1360 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* callback.h + * This file includes the public declarations for the libssh callback mechanism + */ + +#ifndef _SSH_CALLBACK_H +#define _SSH_CALLBACK_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup libssh_callbacks The libssh callbacks + * @ingroup libssh + * + * Callback which can be replaced in libssh. + * + * @{ + */ + +/** @internal + * @brief callback to process simple codes + * @param code value to transmit + * @param user Userdata to pass in callback + */ +typedef void (*ssh_callback_int) (int code, void *user); + +/** @internal + * @brief callback for data received messages. + * @param data data retrieved from the socket or stream + * @param len number of bytes available from this stream + * @param user user-supplied pointer sent along with all callback messages + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef size_t (*ssh_callback_data) (const void *data, size_t len, void *user); + +typedef void (*ssh_callback_int_int) (int code, int errno_code, void *user); + +typedef int (*ssh_message_callback) (ssh_session, ssh_message message, void *user); +typedef int (*ssh_channel_callback_int) (ssh_channel channel, int code, void *user); +typedef int (*ssh_channel_callback_data) (ssh_channel channel, int code, void *data, size_t len, void *user); + +/** + * @brief SSH log callback. All logging messages will go through this callback + * @param session Current session handler + * @param priority Priority of the log, the smaller being the more important + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_log_callback) (ssh_session session, int priority, + const char *message, void *userdata); + +/** + * @brief SSH log callback. + * + * All logging messages will go through this callback. + * + * @param priority Priority of the log, the smaller being the more important. + * + * @param function The function name calling the logging functions. + * + * @param buffer The actual message + * + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_logging_callback) (int priority, + const char *function, + const char *buffer, + void *userdata); + +/** + * @brief SSH Connection status callback. + * @param session Current session handler + * @param status Percentage of connection status, going from 0.0 to 1.0 + * once connection is done. + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_status_callback) (ssh_session session, float status, + void *userdata); + +/** + * @brief SSH global request callback. All global request will go through this + * callback. + * @param session Current session handler + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_global_request_callback) (ssh_session session, + ssh_message message, void *userdata); + +/** + * @brief SSH connect status callback. These are functions that report the + * status of the connection i,e. a function indicating the completed percentage + * of the connection + * steps. + * @param userdata Userdata to be passed to the callback function. + * @param status Percentage of connection status, going from 0.0 to 1.0 + * once connection is done. + */ +typedef void (*ssh_connect_status_callback)(void *userdata, float status); + +/** + * @brief Handles an SSH new channel open X11 request. This happens when the server + * sends back an X11 connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @param originator_address IP address of the machine who sent the request + * @param originator_port port number of the machine who sent the request + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_x11_callback) (ssh_session session, + const char * originator_address, int originator_port, void *userdata); + +/** + * @brief Handles an SSH new channel open "auth-agent" request. This happens when the server + * sends back an "auth-agent" connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_auth_agent_callback) (ssh_session session, + void *userdata); + +/** + * @brief Handles an SSH new channel open "forwarded-tcpip" request. This + * happens when the server forwards an incoming TCP connection on a port it was + * previously requested to listen on. This is a client-side API + * @param session current session handler + * @param destination_address the address that the TCP connection connected to + * @param destination_port the port that the TCP connection connected to + * @param originator_address the originator IP address + * @param originator_port the originator port + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the + * application. + */ +typedef ssh_channel (*ssh_channel_open_request_forwarded_tcpip_callback) (ssh_session session, + const char *destination_address, int destination_port, + const char *originator_address, int originator_port, + void *userdata); + +/** + * The structure to replace libssh functions with appropriate callbacks. + */ +struct ssh_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called if e.g. a keyphrase is needed. + */ + ssh_auth_callback auth_function; + /** + * This function will be called each time a loggable event happens. + */ + ssh_log_callback log_function; + /** + * This function gets called during connection time to indicate the + * percentage of connection steps completed. + */ + ssh_connect_status_callback connect_status_function; + /** + * This function will be called each time a global request is received. + */ + ssh_global_request_callback global_request_function; + /** This function will be called when an incoming X11 request is received. + */ + ssh_channel_open_request_x11_callback channel_open_request_x11_function; + /** This function will be called when an incoming "auth-agent" request is received. + */ + ssh_channel_open_request_auth_agent_callback channel_open_request_auth_agent_function; + /** + * This function will be called when an incoming "forwarded-tcpip" + * request is received. + */ + ssh_channel_open_request_forwarded_tcpip_callback channel_open_request_forwarded_tcpip_function; +}; +typedef struct ssh_callbacks_struct *ssh_callbacks; + +/** These are callbacks used specifically in SSH servers. + */ + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param password Password used for authentication + * @param userdata Userdata to be passed to the callback function. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. + */ +typedef int (*ssh_auth_password_callback) (ssh_session session, const char *user, const char *password, + void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the + * "none" method which is anonymous or passwordless. + * @param session Current session handler + * @param user User that wants to authenticate + * @param userdata Userdata to be passed to the callback function. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. + */ +typedef int (*ssh_auth_none_callback) (ssh_session session, const char *user, void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticate user with the + * "gssapi-with-mic" or "gssapi-keyex" method. This callback is dispatched + * after the server has already verified the authenticity of the principal. + * @param session Current session handler + * @param user Username of the user (can be spoofed) + * @param principal Authenticated principal of the user, including realm. + * @param userdata Userdata to be passed to the callback function. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. + * @warning Using this callback, implementations should verify that the principal + * is allowed to log in as the local user, e.g. by checking that the username + * matches the principal in some way. + */ +typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *user, const char *principal, + void *userdata); + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param pubkey public key used for authentication + * @param signature_state `SSH_PUBLICKEY_STATE_NONE` if the key is not signed + * (simple public key probe), `SSH_PUBLICKEY_STATE_VALID` if the signature is + * valid. Others values should be replied with a `SSH_AUTH_DENIED`. + * @param userdata Userdata to be passed to the callback function. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. + */ +typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user, struct ssh_key_struct *pubkey, + char signature_state, void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the "keyboard-interactive" method + * @param message Current message + * @param session Current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_INFO More info required for authentication. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. +*/ +typedef int (*ssh_auth_kbdint_callback) (ssh_message message, ssh_session session, void *userdata); + +/** + * @brief Handles an SSH service request + * @param session current session handler + * @param service name of the service (e.g. "ssh-userauth") requested + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the request is to be allowed + * @returns -1 if the request should not be allowed + */ +typedef int (*ssh_service_request_callback) (ssh_session session, const char *service, void *userdata); + +/** + * @brief Handles an SSH new channel open session request + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session session, void *userdata); + +/** + * @brief handle the beginning of a GSSAPI authentication, server side. + * Callback should select the oid and also acquire the server credential. + * @param session current session handler + * @param user the username of the client + * @param n_oid number of available oids + * @param oids OIDs provided by the client + * @returns an ssh_string containing the chosen OID, that's supported by both + * client and server. + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef ssh_string (*ssh_gssapi_select_oid_callback) (ssh_session session, const char *user, + int n_oid, ssh_string *oids, void *userdata); + +/** + * @brief handle the negotiation of a security context, server side. + * @param session current session handler + * @param[in] input_token input token provided by client + * @param[out] output_token output of the gssapi accept_sec_context method, + * NULL after completion. + * @returns `SSH_OK` if the token was generated correctly or accept_sec_context + * returned GSS_S_COMPLETE + * @returns `SSH_ERROR` in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_accept_sec_ctx_callback) (ssh_session session, + ssh_string input_token, ssh_string *output_token, void *userdata); + +/** + * @brief Verify and authenticates a MIC, server side. + * @param session current session handler + * @param[in] mic input mic to be verified provided by client + * @param[in] mic_buffer buffer of data to be signed. + * @param[in] mic_buffer_size size of mic_buffer + * @returns `SSH_OK` if the MIC was authenticated correctly + * @returns `SSH_ERROR` in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_verify_mic_callback) (ssh_session session, + ssh_string mic, void *mic_buffer, size_t mic_buffer_size, void *userdata); + +/** + * @brief Handles an SSH new channel open "direct-tcpip" request. This + * happens when the client forwards an incoming TCP connection on a port it + * wants to forward to the destination. This is a server-side API + * @param session current session handler + * @param destination_address the address that the TCP connection connected to + * @param destination_port the port that the TCP connection connected to + * @param originator_address the originator IP address + * @param originator_port the originator port + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the + * application. + */ +typedef ssh_channel (*ssh_channel_open_request_direct_tcpip_callback)( + ssh_session session, + const char *destination_address, + int destination_port, + const char *originator_address, + int originator_port, + void *userdata); + +/** + * This structure can be used to implement a libssh server, with appropriate callbacks. + */ + +struct ssh_server_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** This function gets called when a client tries to authenticate through + * password method. + */ + ssh_auth_password_callback auth_password_function; + + /** This function gets called when a client tries to authenticate through + * none method. + */ + ssh_auth_none_callback auth_none_function; + + /** This function gets called when a client tries to authenticate through + * gssapi-mic method. + */ + ssh_auth_gssapi_mic_callback auth_gssapi_mic_function; + + /** this function gets called when a client tries to authenticate or offer + * a public key. + */ + ssh_auth_pubkey_callback auth_pubkey_function; + + /** This functions gets called when a service request is issued by the + * client + */ + ssh_service_request_callback service_request_function; + /** This functions gets called when a new channel request is issued by + * the client + */ + ssh_channel_open_request_session_callback channel_open_request_session_function; + /** This function will be called when a new gssapi authentication is attempted. + * This should select the oid and acquire credential for the server. + */ + ssh_gssapi_select_oid_callback gssapi_select_oid_function; + /** This function will be called when a gssapi token comes in. + */ + ssh_gssapi_accept_sec_ctx_callback gssapi_accept_sec_ctx_function; + /** This function will be called when a MIC needs to be verified. + */ + ssh_gssapi_verify_mic_callback gssapi_verify_mic_function; + /** + * This function will be called when an incoming "direct-tcpip" + * request is received. + */ + ssh_channel_open_request_direct_tcpip_callback + channel_open_request_direct_tcpip_function; + + /** This function gets called when a client tries to authenticate through + * keyboard interactive method. + */ + ssh_auth_kbdint_callback auth_kbdint_function; + +}; +typedef struct ssh_server_callbacks_struct *ssh_server_callbacks; + +/** + * @brief Set the session server callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for user authentication, new channels and requests. + * + * Note, that the structure is not copied to the session structure so it needs + * to be valid for the whole session lifetime. + * + * @code + * struct ssh_server_callbacks_struct cb = { + * .userdata = data, + * .auth_password_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_server_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + */ +LIBSSH_API int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb); + +/** + * These are the callbacks exported by the socket structure + * They are called by the socket module when a socket event appears + */ +struct ssh_socket_callbacks_struct { + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This function will be called each time data appears on socket. The data + * not consumed will appear on the next data event. + */ + ssh_callback_data data; + /** This function will be called each time a controlflow state changes, i.e. + * the socket is available for reading or writing. + */ + ssh_callback_int controlflow; + /** This function will be called each time an exception appears on socket. An + * exception can be a socket problem (timeout, ...) or an end-of-file. + */ + ssh_callback_int_int exception; + /** This function is called when the ssh_socket_connect was used on the socket + * on nonblocking state, and the connection succeeded. + */ + ssh_callback_int_int connected; +}; +typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks; + +#define SSH_SOCKET_FLOW_WRITEWILLBLOCK 1 +#define SSH_SOCKET_FLOW_WRITEWONTBLOCK 2 + +#define SSH_SOCKET_EXCEPTION_EOF 1 +#define SSH_SOCKET_EXCEPTION_ERROR 2 + +#define SSH_SOCKET_CONNECTED_OK 1 +#define SSH_SOCKET_CONNECTED_ERROR 2 +#define SSH_SOCKET_CONNECTED_TIMEOUT 3 + +/** + * @brief Initializes an ssh_callbacks_struct + * A call to this macro is mandatory when you have set a new + * ssh_callback_struct structure. Its goal is to maintain the binary + * compatibility with future versions of libssh as the structure + * evolves with time. + */ +#define ssh_callbacks_init(p) do {\ + (p)->size=sizeof(*(p)); \ +} while(0); + +/** + * @internal + * @brief tests if a callback can be called without crash + * verifies that the struct size if big enough + * verifies that the callback pointer exists + * @param p callback pointer + * @param c callback name + * @returns nonzero if callback can be called + */ +#define ssh_callbacks_exists(p,c) (\ + (p != NULL) && ( (char *)&((p)-> c) < (char *)(p) + (p)->size ) && \ + ((p)-> c != NULL) \ + ) + +/** + * @internal + * + * @brief Iterate through a list of callback structures + * + * This tests for their validity and executes them. The userdata argument is + * automatically passed through. + * + * @param list list of callbacks + * + * @param cbtype type of the callback + * + * @param c callback name + * + * @param va_args parameters to be passed + */ +#define ssh_callbacks_execute_list(list, cbtype, c, ...) \ + do { \ + struct ssh_iterator *i = ssh_list_get_iterator(list); \ + cbtype cb; \ + while (i != NULL){ \ + cb = ssh_iterator_value(cbtype, i); \ + if (ssh_callbacks_exists(cb, c)) \ + cb-> c (__VA_ARGS__, cb->userdata); \ + i = i->next; \ + } \ + } while(0) + +/** + * @internal + * + * @brief iterate through a list of callback structures. + * + * This tests for their validity and give control back to the calling code to + * execute them. Caller can decide to break the loop or continue executing the + * callbacks with different parameters + * + * @code + * ssh_callbacks_iterate(channel->callbacks, ssh_channel_callbacks, + * channel_eof_function){ + * rc = ssh_callbacks_iterate_exec(session, channel); + * if (rc != SSH_OK){ + * break; + * } + * } + * ssh_callbacks_iterate_end(); + * @endcode + */ +#define ssh_callbacks_iterate(_cb_list, _cb_type, _cb_name) \ + do { \ + struct ssh_iterator *_cb_i = ssh_list_get_iterator(_cb_list); \ + _cb_type _cb; \ + for (; _cb_i != NULL; _cb_i = _cb_i->next) { \ + _cb = ssh_iterator_value(_cb_type, _cb_i); \ + if (ssh_callbacks_exists(_cb, _cb_name)) + +#define ssh_callbacks_iterate_exec(_cb_name, ...) \ + _cb->_cb_name(__VA_ARGS__, _cb->userdata) + +#define ssh_callbacks_iterate_end() \ + } \ + } while(0) + +/** @brief Prototype for a packet callback, to be called when a new packet + * arrives + * @param session The current session of the packet + * @param type packet type (see ssh2.h) + * @param packet buffer containing the packet, excluding size, type and padding + * fields + * @param user user argument to the callback + * and are called each time a packet shows up + * @returns `SSH_PACKET_USED` Packet was parsed and used + * @returns `SSH_PACKET_NOT_USED` Packet was not used or understood, processing + * must continue + */ +typedef int (*ssh_packet_callback) (ssh_session session, uint8_t type, ssh_buffer packet, void *user); + +/** return values for a ssh_packet_callback */ +/** Packet was used and should not be parsed by another callback */ +#define SSH_PACKET_USED 1 +/** Packet was not used and should be passed to any other callback + * available */ +#define SSH_PACKET_NOT_USED 2 + + +/** @brief This macro declares a packet callback handler + * @code + * SSH_PACKET_CALLBACK(mycallback){ + * ... + * } + * @endcode + */ +#define SSH_PACKET_CALLBACK(name) \ + int name (ssh_session session, uint8_t type, ssh_buffer packet, void *user) + +struct ssh_packet_callbacks_struct { + /** Index of the first packet type being handled */ + uint8_t start; + /** Number of packets being handled by this callback struct */ + uint8_t n_callbacks; + /** A pointer to n_callbacks packet callbacks */ + ssh_packet_callback *callbacks; + /** + * User-provided data. User is free to set anything he wants here + */ + void *user; +}; + +typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks; + +/** + * @brief Set the session callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for auth, logging and status. + * + * Note, that the callback structure is not copied into the session so it needs + * to be valid for the whole session lifetime. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + */ +LIBSSH_API int ssh_set_callbacks(ssh_session session, ssh_callbacks cb); + +/** + * @brief SSH channel data callback. Called when data is available on a channel + * @param session Current session handler + * @param channel the actual channel + * @param data the data that has been read on the channel + * @param len the length of the data + * @param is_stderr is 0 for stdout or 1 for stderr + * @param userdata Userdata to be passed to the callback function. + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef int (*ssh_channel_data_callback) (ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); + +/** + * @brief SSH channel eof callback. Called when a channel receives EOF + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_eof_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel close callback. Called when a channel is closed by remote peer + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_close_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel signal callback. Called when a channel has received a signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +/** + * @brief SSH channel exit status callback. Called when a channel has received an exit status + * @param session Current session handler + * @param channel the actual channel + * @param exit_status Exit status of the ran command + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_status_callback) (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +/** + * @brief SSH channel exit signal callback. Called when a channel has received an exit signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param core a boolean telling whether a core has been dumped or not + * @param errmsg the description of the exception + * @param lang the language of the description (format: RFC 3066) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +/** + * @brief SSH channel PTY request from a client. + * @param session the session + * @param channel the channel + * @param term The type of terminal emulation + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pwheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_request_callback) (ssh_session session, + ssh_channel channel, + const char *term, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Shell request from a client. + * @param session the session + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the shell request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_shell_request_callback) (ssh_session session, + ssh_channel channel, + void *userdata); +/** + * @brief SSH auth-agent-request from the client. This request is + * sent by a client when agent forwarding is available. + * Server is free to ignore this callback, no answer is expected. + * @param session the session + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_auth_agent_req_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH X11 request from the client. This request is + * sent by a client when X11 forwarding is requested(and available). + * Server is free to ignore this callback, no answer is expected. + * @param session the session + * @param channel the channel + * @param single_connection If true, only one channel should be forwarded + * @param auth_protocol The X11 authentication method to be used + * @param auth_cookie Authentication cookie encoded hexadecimal + * @param screen_number Screen number + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_x11_req_callback) (ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata); +/** + * @brief SSH channel PTY windows change (terminal size) from a client. + * @param session the session + * @param channel the channel + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pwheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_window_change_callback) (ssh_session session, + ssh_channel channel, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Exec request from a client. + * @param session the session + * @param channel the channel + * @param command the shell command to be executed + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the exec request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_exec_request_callback) (ssh_session session, + ssh_channel channel, + const char *command, + void *userdata); + +/** + * @brief SSH channel environment request from a client. + * @param session the session + * @param channel the channel + * @param env_name name of the environment value to be set + * @param env_value value of the environment value to be set + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the env request is accepted + * @returns 1 if the request is denied + * @warning some environment variables can be dangerous if changed (e.g. + * LD_PRELOAD) and should not be fulfilled. + */ +typedef int (*ssh_channel_env_request_callback) (ssh_session session, + ssh_channel channel, + const char *env_name, + const char *env_value, + void *userdata); +/** + * @brief SSH channel subsystem request from a client. + * @param session the session + * @param channel the channel + * @param subsystem the subsystem required + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the subsystem request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_subsystem_request_callback) (ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata); + +/** + * @brief SSH channel write will not block (flow control). + * + * @param session the session + * + * @param channel the channel + * + * @param[in] bytes size of the remote window in bytes. Writing as much data + * will not block. + * + * @param[in] userdata Userdata to be passed to the callback function. + * + * @returns 0 default return value (other return codes may be added in future). + */ +typedef int (*ssh_channel_write_wontblock_callback) (ssh_session session, + ssh_channel channel, + uint32_t bytes, + void *userdata); + +/** + * @brief SSH channel open callback. Called when a channel open succeeds or fails. + * @param session Current session handler + * @param channel the actual channel + * @param is_success is 1 when the open succeeds, and 0 otherwise. + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_open_resp_callback) (ssh_session session, + ssh_channel channel, + bool is_success, + void *userdata); + +/** + * @brief SSH channel request response callback. Called when a response to the pending request is received. + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_request_resp_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +struct ssh_channel_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called when there is data available. + */ + ssh_channel_data_callback channel_data_function; + /** + * This functions will be called when the channel has received an EOF. + */ + ssh_channel_eof_callback channel_eof_function; + /** + * This functions will be called when the channel has been closed by remote + */ + ssh_channel_close_callback channel_close_function; + /** + * This functions will be called when a signal has been received + */ + ssh_channel_signal_callback channel_signal_function; + /** + * This functions will be called when an exit status has been received + */ + ssh_channel_exit_status_callback channel_exit_status_function; + /** + * This functions will be called when an exit signal has been received + */ + ssh_channel_exit_signal_callback channel_exit_signal_function; + /** + * This function will be called when a client requests a PTY + */ + ssh_channel_pty_request_callback channel_pty_request_function; + /** + * This function will be called when a client requests a shell + */ + ssh_channel_shell_request_callback channel_shell_request_function; + /** This function will be called when a client requests agent + * authentication forwarding. + */ + ssh_channel_auth_agent_req_callback channel_auth_agent_req_function; + /** This function will be called when a client requests X11 + * forwarding. + */ + ssh_channel_x11_req_callback channel_x11_req_function; + /** This function will be called when a client requests a + * window change. + */ + ssh_channel_pty_window_change_callback channel_pty_window_change_function; + /** This function will be called when a client requests a + * command execution. + */ + ssh_channel_exec_request_callback channel_exec_request_function; + /** This function will be called when a client requests an environment + * variable to be set. + */ + ssh_channel_env_request_callback channel_env_request_function; + /** This function will be called when a client requests a subsystem + * (like sftp). + */ + ssh_channel_subsystem_request_callback channel_subsystem_request_function; + /** This function will be called when the channel write is guaranteed + * not to block. + */ + ssh_channel_write_wontblock_callback channel_write_wontblock_function; + /** + * This functions will be called when the channel has received a channel open confirmation or failure. + */ + ssh_channel_open_resp_callback channel_open_response_function; + /** + * This functions will be called when the channel has received the response to the pending request. + */ + ssh_channel_request_resp_callback channel_request_response_function; +}; + +typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks; + +/** + * @brief Set the channel callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for channel data and exceptions. + * + * Note, that the structure is not copied to the channel structure so it needs + * to be valid as for the whole life of the channel or until it is removed with + * ssh_remove_channel_callbacks(). + * + * @code + * struct ssh_channel_callbacks_struct cb = { + * .userdata = data, + * .channel_data_function = my_channel_data_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_channel_callbacks(channel, &cb); + * @endcode + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * @warning this function will not replace existing callbacks but set the + * new one atop of them. + */ +LIBSSH_API int ssh_set_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Add channel callback functions + * + * This function will add channel callback functions to the channel callback + * list. + * Callbacks missing from a callback structure will be probed in the next + * on the list. + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * + * @see ssh_set_channel_callbacks + */ +LIBSSH_API int ssh_add_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Remove a channel callback. + * + * The channel has been added with ssh_add_channel_callbacks or + * ssh_set_channel_callbacks in this case. + * + * @param channel The channel to remove the callback structure from. + * + * @param cb The callback structure to remove + * + * @returns `SSH_OK` on success, `SSH_ERROR` on error. + */ +LIBSSH_API int ssh_remove_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** @} */ + +/** @addtogroup libssh_threads + * @{ + */ + +typedef int (*ssh_thread_callback) (void **lock); + +typedef unsigned long (*ssh_thread_id_callback) (void); +struct ssh_threads_callbacks_struct { + const char *type; + ssh_thread_callback mutex_init; + ssh_thread_callback mutex_destroy; + ssh_thread_callback mutex_lock; + ssh_thread_callback mutex_unlock; + ssh_thread_id_callback thread_id; +}; + +/** + * @brief Set the thread callbacks structure. + * + * This is necessary if your program is using libssh in a multithreaded fashion. + * This function must be called first, outside of any threading context (in your + * main() function for instance), before you call ssh_init(). + * + * @param[in] cb A pointer to a ssh_threads_callbacks_struct structure, which + * contains the different callbacks to be set. + * + * @returns Always returns `SSH_OK`. + * + * @see ssh_threads_callbacks_struct + * @see SSH_THREADS_PTHREAD + * @bug libgcrypt 1.6 and bigger backend does not support custom callback. + * Using anything else than pthreads here will fail. + */ +LIBSSH_API int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct + *cb); + +/** + * @brief Returns a pointer to the appropriate callbacks structure for the + * environment, to be used with ssh_threads_set_callbacks. + * + * @returns A pointer to a ssh_threads_callbacks_struct to be used with + * ssh_threads_set_callbacks. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_default(void); + +/** + * @brief Returns a pointer on the pthread threads callbacks, to be used with + * ssh_threads_set_callbacks. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void); + +/** + * @brief Get the noop threads callbacks structure + * + * This can be used with ssh_threads_set_callbacks. These callbacks do nothing + * and are being used by default. + * + * @return Always returns a valid pointer to the noop callbacks structure. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void); +/** @} */ + +/** + * @brief Set the logging callback function. + * + * @param[in] cb The callback to set. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int ssh_set_log_callback(ssh_logging_callback cb); + +/** + * @brief Get the pointer to the logging callback function. + * + * @return The pointer the the callback or NULL if none set. + */ +LIBSSH_API ssh_logging_callback ssh_get_log_callback(void); + +/** + * @brief SSH proxyjump before connection callback. Called before calling + * ssh_connect() + * @param session Jump session handler + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_jump_before_connection_callback)(ssh_session session, + void *userdata); + +/** + * @brief SSH proxyjump verify knownhost callback. Verify the host. + * If not specified default function will be used. + * @param session Jump session handler + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_jump_verify_knownhost_callback)(ssh_session session, + void *userdata); + +/** + * @brief SSH proxyjump user authentication callback. Authenticate the user. + * @param session Jump session handler + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_jump_authenticate_callback)(ssh_session session, + void *userdata); + +struct ssh_jump_callbacks_struct { + void *userdata; + ssh_jump_before_connection_callback before_connection; + ssh_jump_verify_knownhost_callback verify_knownhost; + ssh_jump_authenticate_callback authenticate; +}; + +/* Security key callbacks */ + +/* + * Forward declarations for structs that have been defined in sk_api.h. + * If you need to work with the fields inside them, please include + * libssh/sk_api.h + */ +struct sk_enroll_response; +struct sk_sign_response; +struct sk_resident_key; +struct sk_option; + +#define LIBSSH_SK_API_VERSION_MAJOR 0x000a0000 + +/** + * @brief FIDO2/U2F SK API version callback. + * + * Returns the version of the FIDO2/U2F API that the callbacks implement. + * This callback allows custom callback implementations to specify their + * SK API version for compatibility checking with libssh's security key + * interface. + * + * @details Version compatibility is determined by comparing the major version + * portion (upper 16 bits) of the returned value with SSH_SK_VERSION_MAJOR. + * + * For compatibility, implementations should return a version where: + * (returned_version & SSH_SK_VERSION_MAJOR_MASK) == SSH_SK_VERSION_MAJOR + * + * This ensures that the callbacks' SK API matches the major version expected + * by libssh, while allowing minor version differences for backward + * compatibility. + * + * @see LIBSSH_SK_API_VERSION_MAJOR Current expected major API version + * @see SSH_SK_VERSION_MAJOR_MASK Mask for extracting major version (0xffff0000) + */ +typedef uint32_t (*sk_api_version_callback)(void); + +/** + * @brief FIDO2/U2F key enrollment callback. + * + * Enrolls a new FIDO2/U2F security key credential (private key generation). + * This callback handles the creation of new FIDO2/U2F credentials, including + * both resident and non-resident keys. + * + * @param[in] alg The cryptographic algorithm to use + * @param[in] challenge Random challenge data for enrollment + * @param[in] challenge_len Length of the challenge data + * @param[in] application Application identifier (relying party ID) + * @param[in] flags Enrollment flags + * @param[in] pin PIN for user verification (may be NULL) + * @param[in] options Array of enrollment options (device path, user ID, etc.) + * @param[out] enroll_response Enrollment response containing public key, + * key handle, signature, and attestation data + * + * @returns SSH_OK on success, SSH_SK_ERR_* codes on failure. + */ +typedef int (*sk_enroll_callback)(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response); + +/** + * @brief FIDO2/U2F security key signing callback. + * + * Signs data using a FIDO2 security key credential. This callback performs + * cryptographic signing operations using previously enrolled FIDO2/U2F + * credentials. + * + * @param[in] alg The cryptographic algorithm used by the key + * @param[in] data Data to be signed + * @param[in] data_len Length of the data to sign + * @param[in] application Application identifier (relying party ID) + * @param[in] key_handle Key handle identifying the credential + * @param[in] key_handle_len Length of the key handle + * @param[in] flags Signing flags + * @param[in] pin PIN for user verification (may be NULL) + * @param[in] options Array of signing options (device path, etc.) + * @param[out] sign_response Signature response containing signature data, + * flags, and counter information + * + * @returns SSH_OK on success, SSH_SK_ERR_* codes on failure. + */ +typedef int (*sk_sign_callback)(uint32_t alg, + const uint8_t *data, + size_t data_len, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_sign_response **sign_response); + +/** + * @brief FIDO2 security key resident keys loading callback. + * + * Enumerates and loads all resident keys (discoverable credentials) stored + * on FIDO2 devices. Resident keys are credentials stored directly on + * the device itself and can be discovered without prior knowledge + * of key handles. + * + * @param[in] pin PIN for accessing resident keys (required for most operations) + * @param[in] options Array of options (device path, etc.) + * @param[out] resident_keys Array of resident key structures containing key + * data, application IDs, user information, and metadata + * @param[out] num_keys_found Number of resident keys found and loaded + * + * @returns SSH_OK on success, SSH_SK_ERR_* codes on failure. + */ +typedef int (*sk_load_resident_keys_callback)( + const char *pin, + struct sk_option **options, + struct sk_resident_key ***resident_keys, + size_t *num_keys_found); + +/** + * @brief FIDO2/U2F security key callbacks structure. + * + * This structure contains callbacks for FIDO2/U2F operations. + * It allows applications to provide custom implementations of FIDO2/U2F + * operations to override the default libfido2-based implementation. + * + * @warning These callbacks will only be called if libssh was built with + * FIDO2/U2F support enabled. (WITH_FIDO2 = ON). + */ +struct ssh_sk_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + + /** + * This callback returns the SK API version used by the callback + * implementation. + * + * @see sk_api_version_callback for detailed documentation + */ + sk_api_version_callback api_version; + + /** + * This callback enrolls a new FIDO2/U2F credential, generating + * a new key pair and optionally storing it on the device itself + * (resident keys). + * + * @see sk_enroll_callback for detailed documentation + */ + sk_enroll_callback enroll; + + /** + * This callback performs cryptographic signing operations using a + * previously enrolled FIDO2/U2F credential. + * + * @see sk_sign_callback for detailed documentation + */ + sk_sign_callback sign; + + /** + * This callback enumerates and loads all resident keys (discoverable + * credentials) stored on the FIDO2 device. + * + * @see sk_load_resident_keys_callback for detailed documentation + */ + sk_load_resident_keys_callback load_resident_keys; +}; + +typedef struct ssh_sk_callbacks_struct *ssh_sk_callbacks; + +const struct ssh_sk_callbacks_struct *ssh_sk_get_default_callbacks(void); + +#ifdef __cplusplus +} +#endif + +#endif /*_SSH_CALLBACK_H */ + +/* @} */ diff --git a/src/libs/libssh-0.12.2/include/libssh/chacha.h b/src/libs/libssh-0.12.2/include/libssh/chacha.h new file mode 100644 index 000000000000..ab3fe492846c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/chacha.h @@ -0,0 +1,48 @@ +/* $OpenBSD: chacha.h,v 1.3 2014/05/02 03:27:54 djm Exp $ */ + +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#ifndef CHACHA_H +#define CHACHA_H + +struct chacha_ctx { + uint32_t input[16]; +}; + +#define CHACHA_MINKEYLEN 16 +#define CHACHA_NONCELEN 8 +#define CHACHA_CTRLEN 8 +#define CHACHA_STATELEN (CHACHA_NONCELEN+CHACHA_CTRLEN) + +#ifdef __cplusplus +extern "C" { +#endif + +void chacha_keysetup(struct chacha_ctx *x, const uint8_t *k, uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif + ; +void chacha_ivsetup(struct chacha_ctx *x, const uint8_t *iv, const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif + ; +void chacha_encrypt_bytes(struct chacha_ctx *x, const uint8_t *m, + uint8_t *c, uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* CHACHA_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/chacha20-poly1305-common.h b/src/libs/libssh-0.12.2/include/libssh/chacha20-poly1305-common.h new file mode 100644 index 000000000000..b2f0231b7292 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/chacha20-poly1305-common.h @@ -0,0 +1,54 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 Red Hat, Inc. + * + * Author: Jakub Jelen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * chacha20-poly1305.h file + * This file includes definitions needed for Chacha20-poly1305 AEAD cipher + * using different crypto backends. + */ + +#ifndef CHACHA20_POLY1305_H +#define CHACHA20_POLY1305_H + +#define CHACHA20_BLOCKSIZE 64 +#define CHACHA20_KEYLEN 32 + +#define POLY1305_TAGLEN 16 +/* size of the keys k1 and k2 as defined in specs */ +#define POLY1305_KEYLEN 32 + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif +struct ssh_packet_header { + uint32_t length; + uint8_t payload[]; +} +#if defined(__GNUC__) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack(pop) +#endif +; + +#endif /* CHACHA20_POLY1305_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/channels.h b/src/libs/libssh-0.12.2/include/libssh/channels.h new file mode 100644 index 000000000000..7a3535eca589 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/channels.h @@ -0,0 +1,125 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CHANNELS_H_ +#define CHANNELS_H_ +#include "libssh/priv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @internal + * Describes the different possible states in a + * outgoing (client) channel request + */ +enum ssh_channel_request_state_e { + /** No request has been made */ + SSH_CHANNEL_REQ_STATE_NONE = 0, + /** A request has been made and answer is pending */ + SSH_CHANNEL_REQ_STATE_PENDING, + /** A request has been replied and accepted */ + SSH_CHANNEL_REQ_STATE_ACCEPTED, + /** A request has been replied and refused */ + SSH_CHANNEL_REQ_STATE_DENIED, + /** A request has been replied and an error happened */ + SSH_CHANNEL_REQ_STATE_ERROR +}; + +enum ssh_channel_state_e { + SSH_CHANNEL_STATE_NOT_OPEN = 0, + SSH_CHANNEL_STATE_OPENING, + SSH_CHANNEL_STATE_OPEN_DENIED, + SSH_CHANNEL_STATE_OPEN, + SSH_CHANNEL_STATE_CLOSED +}; + +/* The channel has been closed by the remote side */ +#define SSH_CHANNEL_FLAG_CLOSED_REMOTE 0x0001 + +/* The channel has been closed locally */ +#define SSH_CHANNEL_FLAG_CLOSED_LOCAL 0x0002 + +/* The channel has been freed by the calling program */ +#define SSH_CHANNEL_FLAG_FREED_LOCAL 0x0004 + +/* the channel has not yet been bound to a remote one */ +#define SSH_CHANNEL_FLAG_NOT_BOUND 0x0008 + +struct ssh_channel_struct { + ssh_session session; /* SSH_SESSION pointer */ + uint32_t local_channel; + uint32_t local_window; + int local_eof; + uint32_t local_maxpacket; + + uint32_t remote_channel; + uint32_t remote_window; + int remote_eof; /* end of file received */ + uint32_t remote_maxpacket; + enum ssh_channel_state_e state; + int delayed_close; + int flags; + ssh_buffer stdout_buffer; + ssh_buffer stderr_buffer; + void *userarg; + struct { + bool status; + uint32_t code; + char *signal; + bool core_dumped; + } exit; + enum ssh_channel_request_state_e request_state; + struct ssh_list *callbacks; /* list of ssh_channel_callbacks */ + + /* counters */ + ssh_counter counter; +}; + +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf); +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail); +SSH_PACKET_CALLBACK(ssh_packet_channel_success); +SSH_PACKET_CALLBACK(ssh_packet_channel_failure); +SSH_PACKET_CALLBACK(ssh_request_success); +SSH_PACKET_CALLBACK(ssh_request_denied); + +SSH_PACKET_CALLBACK(channel_rcv_change_window); +SSH_PACKET_CALLBACK(channel_rcv_eof); +SSH_PACKET_CALLBACK(channel_rcv_close); +SSH_PACKET_CALLBACK(channel_rcv_request); +SSH_PACKET_CALLBACK(channel_rcv_data); + +int channel_default_bufferize(ssh_channel channel, + void *data, uint32_t len, + bool is_stderr); +int ssh_channel_flush(ssh_channel channel); +uint32_t ssh_channel_new_id(ssh_session session); +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id); +void ssh_channel_do_free(ssh_channel channel); +int ssh_global_request(ssh_session session, + const char *request, + ssh_buffer buffer, + int reply); + +#ifdef __cplusplus +} +#endif + +#endif /* CHANNELS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/config.h b/src/libs/libssh-0.12.2/include/libssh/config.h new file mode 100644 index 000000000000..87cc25be06b5 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/config.h @@ -0,0 +1,80 @@ +/* + * config.h - parse the ssh config file + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef LIBSSH_CONFIG_H_ +#define LIBSSH_CONFIG_H_ + +#include "libssh/libssh.h" + +enum ssh_config_opcode_e { + /* Unknown opcode */ + SOC_UNKNOWN = -3, + /* Known and not applicable to libssh */ + SOC_NA = -2, + /* Known but not supported by current libssh version */ + SOC_UNSUPPORTED = -1, + SOC_HOST, + SOC_MATCH, + SOC_HOSTNAME, + SOC_PORT, + SOC_USERNAME, + SOC_IDENTITY, + SOC_CIPHERS, + SOC_MACS, + SOC_COMPRESSION, + SOC_TIMEOUT, + SOC_STRICTHOSTKEYCHECK, + SOC_KNOWNHOSTS, + SOC_PROXYCOMMAND, + SOC_PROXYJUMP, + SOC_GSSAPISERVERIDENTITY, + SOC_GSSAPICLIENTIDENTITY, + SOC_GSSAPIDELEGATECREDENTIALS, + SOC_INCLUDE, + SOC_BINDADDRESS, + SOC_GLOBALKNOWNHOSTSFILE, + SOC_LOGLEVEL, + SOC_HOSTKEYALGORITHMS, + SOC_KEXALGORITHMS, + SOC_GSSAPIAUTHENTICATION, + SOC_KBDINTERACTIVEAUTHENTICATION, + SOC_PASSWORDAUTHENTICATION, + SOC_PUBKEYAUTHENTICATION, + SOC_PUBKEYACCEPTEDKEYTYPES, + SOC_REKEYLIMIT, + SOC_IDENTITYAGENT, + SOC_IDENTITIESONLY, + SOC_CONTROLMASTER, + SOC_CONTROLPATH, + SOC_CERTIFICATE, + SOC_REQUIRED_RSA_SIZE, + SOC_ADDRESSFAMILY, + SOC_GSSAPIKEYEXCHANGE, + SOC_GSSAPIKEXALGORITHMS, + + SOC_MAX /* Keep this one last in the list */ +}; +enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword); +int ssh_config_parse_line_cli(ssh_session session, const char *line); + +#endif /* LIBSSH_CONFIG_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/config_parser.h b/src/libs/libssh-0.12.2/include/libssh/config_parser.h new file mode 100644 index 000000000000..f5d1fee83b22 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/config_parser.h @@ -0,0 +1,86 @@ +/* + * config_parser.h - Common configuration file parser functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef CONFIG_PARSER_H_ +#define CONFIG_PARSER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "libssh/libssh.h" +#include + +char *ssh_config_get_cmd(char **str); + +char *ssh_config_get_token(char **str); + +long ssh_config_get_long(char **str, long notfound); + +const char *ssh_config_get_str_tok(char **str, const char *def); + +int ssh_config_get_yesno(char **str, int notfound); + +/* @brief Parse SSH URI in format [user@]host[:port] from the given string + * + * @param[in] tok String to parse + * @param[out] username Pointer to the location, where the new username will + * be stored or NULL if we do not care about the result. + * @param[out] hostname Pointer to the location, where the new hostname will + * be stored or NULL if we do not care about the result. + * @param[out] port Pointer to the location, where the new port will + * be stored or NULL if we do not care about the result. + * @param[in] ignore_port Set to true if we should not attempt to parse + * port number. + * + * @returns SSH_OK if the provided string is in format of SSH URI, + * SSH_ERROR on failure + */ +int ssh_config_parse_uri(const char *tok, + char **username, + char **hostname, + char **port, + bool ignore_port); + +/** + * @brief: Parse the ProxyJump configuration line and if parsing, + * stores the result in the configuration option + * + * @param[in] session The ssh session + * @param[in] s The string to be parsed. + * @param[in] do_parsing Whether to parse or not. + * + * @returns SSH_OK if the provided string is formatted and parsed correctly + * SSH_ERROR on failure + */ +int ssh_config_parse_proxy_jump(ssh_session session, + const char *s, + bool do_parsing); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBSSH_CONFIG_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/crypto.h b/src/libs/libssh-0.12.2/include/libssh/crypto.h new file mode 100644 index 000000000000..dd7fa2e8e2b4 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/crypto.h @@ -0,0 +1,276 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * crypto.h is an include file for internal cryptographic structures of libssh + */ + +#ifndef _CRYPTO_H_ +#define _CRYPTO_H_ + +#include +#include "config.h" + +#ifdef HAVE_LIBGCRYPT +#include +#elif defined(HAVE_LIBMBEDCRYPTO) +#include +#endif +#include "libssh/wrapper.h" + +#ifdef cbc_encrypt +#undef cbc_encrypt +#endif +#ifdef cbc_decrypt +#undef cbc_decrypt +#endif + +#ifdef HAVE_OPENSSL_ECDH_H +#include +#endif +#include "libssh/curve25519.h" +#include "libssh/dh.h" +#include "libssh/ecdh.h" +#include "libssh/kex.h" +#include "libssh/sntrup761.h" + +#define DIGEST_MAX_LEN 64 + +#define AES_GCM_TAGLEN 16 +#define AES_GCM_IVLEN 12 + +enum ssh_key_exchange_e { + /* diffie-hellman-group1-sha1 */ + SSH_KEX_DH_GROUP1_SHA1 = 1, + /* diffie-hellman-group14-sha1 */ + SSH_KEX_DH_GROUP14_SHA1, +#ifdef WITH_GEX + /* diffie-hellman-group-exchange-sha1 */ + SSH_KEX_DH_GEX_SHA1, + /* diffie-hellman-group-exchange-sha256 */ + SSH_KEX_DH_GEX_SHA256, +#endif /* WITH_GEX */ + /* ecdh-sha2-nistp256 */ + SSH_KEX_ECDH_SHA2_NISTP256, + /* ecdh-sha2-nistp384 */ + SSH_KEX_ECDH_SHA2_NISTP384, + /* ecdh-sha2-nistp521 */ + SSH_KEX_ECDH_SHA2_NISTP521, + /* curve25519-sha256@libssh.org */ + SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG, + /* curve25519-sha256 */ + SSH_KEX_CURVE25519_SHA256, + /* diffie-hellman-group16-sha512 */ + SSH_KEX_DH_GROUP16_SHA512, + /* diffie-hellman-group18-sha512 */ + SSH_KEX_DH_GROUP18_SHA512, + /* diffie-hellman-group14-sha256 */ + SSH_KEX_DH_GROUP14_SHA256, + /* sntrup761x25519-sha512@openssh.com */ + SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM, + /* sntrup761x25519-sha512 */ + SSH_KEX_SNTRUP761X25519_SHA512, + /* mlkem768x25519-sha256 */ + SSH_KEX_MLKEM768X25519_SHA256, + /* mlkem768nistp256-sha256 */ + SSH_KEX_MLKEM768NISTP256_SHA256, +#ifdef HAVE_MLKEM1024 + /* mlkem1024nistp384-sha384 */ + SSH_KEX_MLKEM1024NISTP384_SHA384, +#endif /* HAVE_MLKEM1024 */ + /* gss-group14-sha256-* */ + SSH_GSS_KEX_DH_GROUP14_SHA256, + /* gss-group16-sha512-* */ + SSH_GSS_KEX_DH_GROUP16_SHA512, + /* gss-nistp256-sha256-* */ + SSH_GSS_KEX_ECDH_NISTP256_SHA256, + /* gss-curve25519-sha256-* */ + SSH_GSS_KEX_CURVE25519_SHA256, +}; + +enum ssh_cipher_e { + SSH_NO_CIPHER=0, +#ifdef HAVE_BLOWFISH + SSH_BLOWFISH_CBC, +#endif /* HAVE_BLOWFISH */ + SSH_3DES_CBC, + SSH_AES128_CBC, + SSH_AES192_CBC, + SSH_AES256_CBC, + SSH_AES128_CTR, + SSH_AES192_CTR, + SSH_AES256_CTR, + SSH_AEAD_AES128_GCM, + SSH_AEAD_AES256_GCM, + SSH_AEAD_CHACHA20_POLY1305 +}; + +struct dh_ctx; + +struct ssh_crypto_struct { + bignum shared_secret; + ssh_string hybrid_client_init; + ssh_string hybrid_server_reply; + ssh_string hybrid_shared_secret; + struct dh_ctx *dh_ctx; +#ifdef WITH_GEX + size_t dh_pmin; size_t dh_pn; size_t dh_pmax; /* preferred group parameters */ +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdh_privkey; +#else + EVP_PKEY *ecdh_privkey; +#endif /* OPENSSL_VERSION_NUMBER */ +#elif defined HAVE_GCRYPT_ECC + gcry_sexp_t ecdh_privkey; +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_ecp_keypair *ecdh_privkey; +#endif + ssh_string ecdh_client_pubkey; + ssh_string ecdh_server_pubkey; +#endif +#ifdef HAVE_CURVE25519 +#ifdef HAVE_LIBCRYPTO + EVP_PKEY *curve25519_privkey; +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_sexp_t curve25519_privkey; +#else + ssh_curve25519_privkey curve25519_privkey; +#endif + ssh_curve25519_pubkey curve25519_client_pubkey; + ssh_curve25519_pubkey curve25519_server_pubkey; +#endif +#ifdef HAVE_OPENSSL_MLKEM + EVP_PKEY *mlkem_privkey; +#else + unsigned char *mlkem_privkey; + size_t mlkem_privkey_len; +#endif + ssh_string mlkem_client_pubkey; + ssh_string mlkem_ciphertext; +#ifdef HAVE_SNTRUP761 + ssh_sntrup761_privkey sntrup761_privkey; + ssh_sntrup761_pubkey sntrup761_client_pubkey; + ssh_sntrup761_ciphertext sntrup761_ciphertext; +#endif + ssh_string dh_server_signature; /* information used by dh_handshake. */ + size_t session_id_len; + unsigned char *session_id; + size_t digest_len; /* len of the secret hash */ + unsigned char *secret_hash; /* Secret hash is same as session id until re-kex */ + unsigned char *encryptIV; + unsigned char *decryptIV; + unsigned char *decryptkey; + unsigned char *encryptkey; + unsigned char *encryptMAC; + unsigned char *decryptMAC; + unsigned char hmacbuf[DIGEST_MAX_LEN]; + struct ssh_cipher_struct *in_cipher, *out_cipher; /* the cipher structures/objects */ + enum ssh_hmac_e in_hmac, out_hmac; /* the MAC algorithms used */ + bool in_hmac_etm, out_hmac_etm; /* Whether EtM mode is used or not */ + + ssh_key server_pubkey; + int do_compress_out; /* idem */ + int do_compress_in; /* don't set them, set the option instead */ + int delayed_compress_in; /* Use of zlib@openssh.org */ + int delayed_compress_out; + void *compress_out_ctx; /* don't touch it */ + void *compress_in_ctx; /* really, don't */ + /* kex sent by server, client, and mutually elected methods */ + struct ssh_kex_struct server_kex; + struct ssh_kex_struct client_kex; + char *kex_methods[SSH_KEX_METHODS]; + enum ssh_key_exchange_e kex_type; + enum ssh_kdf_digest digest_type; /* Digest type for session keys derivation */ + enum ssh_crypto_direction_e used; /* Is this crypto still used for either of directions? */ +}; + +struct ssh_cipher_struct { + const char *name; /* ssh name of the algorithm */ + unsigned int blocksize; /* blocksize of the algo */ + enum ssh_cipher_e ciphertype; + uint32_t lenfield_blocksize; /* blocksize of the packet length field */ + size_t keylen; /* length of the key structure */ +#ifdef HAVE_LIBGCRYPT + gcry_cipher_hd_t *key; + unsigned char last_iv[AES_GCM_IVLEN]; +#elif defined HAVE_LIBCRYPTO + struct ssh_3des_key_schedule *des3_key; + struct ssh_aes_key_schedule *aes_key; + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX *ctx; +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_cipher_context_t encrypt_ctx; + mbedtls_cipher_context_t decrypt_ctx; + mbedtls_cipher_type_t type; +#ifdef MBEDTLS_GCM_C + mbedtls_gcm_context gcm_ctx; + unsigned char last_iv[AES_GCM_IVLEN]; +#endif /* MBEDTLS_GCM_C */ +#endif + struct chacha20_poly1305_keysched *chacha20_schedule; + unsigned int keysize; /* bytes of key used. != keylen */ + size_t tag_size; /* overhead required for tag */ + /* Counters for rekeying initialization */ + uint32_t packets; + uint64_t blocks; + /* Rekeying limit for the cipher or manually enforced */ + uint64_t max_blocks; + /* sets the new key for immediate use */ + int (*set_encrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + int (*set_decrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + void (*encrypt)(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len); + void (*decrypt)(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len); + void (*aead_encrypt)(struct ssh_cipher_struct *cipher, void *in, void *out, + size_t len, uint8_t *mac, uint64_t seq); + int (*aead_decrypt_length)(struct ssh_cipher_struct *cipher, void *in, + uint8_t *out, size_t len, uint64_t seq); + int (*aead_decrypt)(struct ssh_cipher_struct *cipher, void *complete_packet, uint8_t *out, + size_t encrypted_size, uint64_t seq); + void (*cleanup)(struct ssh_cipher_struct *cipher); +}; + +#ifdef __cplusplus +extern "C" { +#endif + +const struct ssh_cipher_struct *ssh_get_chacha20poly1305_cipher(void); +int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len); + +int secure_memcmp(const void *s1, const void *s2, size_t n); + +void compress_cleanup(struct ssh_crypto_struct *crypto); + +#ifdef __cplusplus +} +#endif + +#endif /* _CRYPTO_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/curve25519.h b/src/libs/libssh-0.12.2/include/libssh/curve25519.h new file mode 100644 index 000000000000..e5691157b29d --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/curve25519.h @@ -0,0 +1,69 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CURVE25519_H_ +#define CURVE25519_H_ + +#include "config.h" +#include "libssh.h" + +#ifdef WITH_NACL + +#include +#define CURVE25519_PUBKEY_SIZE crypto_scalarmult_curve25519_BYTES +#define CURVE25519_PRIVKEY_SIZE crypto_scalarmult_curve25519_SCALARBYTES +#define crypto_scalarmult_base crypto_scalarmult_curve25519_base +#define crypto_scalarmult crypto_scalarmult_curve25519 +#else + +#ifdef __cplusplus +extern "C" { +#endif + +#define CURVE25519_PUBKEY_SIZE 32 +#define CURVE25519_PRIVKEY_SIZE 32 +int crypto_scalarmult_base(unsigned char *q, const unsigned char *n); +int crypto_scalarmult(unsigned char *q, const unsigned char *n, const unsigned char *p); +#endif /* WITH_NACL */ + +#ifdef HAVE_ECC +#define HAVE_CURVE25519 1 +#endif + +typedef unsigned char ssh_curve25519_pubkey[CURVE25519_PUBKEY_SIZE]; +typedef unsigned char ssh_curve25519_privkey[CURVE25519_PRIVKEY_SIZE]; + +int ssh_curve25519_init(ssh_session session); +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k); +int ssh_curve25519_create_k(ssh_session session, ssh_curve25519_pubkey k); +int ssh_curve25519_build_k(ssh_session session); + +int ssh_client_curve25519_init(ssh_session session); +void ssh_client_curve25519_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_curve25519_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* CURVE25519_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/dh-gex.h b/src/libs/libssh-0.12.2/include/libssh/dh-gex.h new file mode 100644 index 000000000000..0f547e37a047 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/dh-gex.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2016 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + + +#ifndef SRC_DH_GEX_H_ +#define SRC_DH_GEX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_client_dhgex_init(ssh_session session); +void ssh_client_dhgex_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_dhgex_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* SRC_DH_GEX_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/dh.h b/src/libs/libssh-0.12.2/include/libssh/dh.h new file mode 100644 index 000000000000..34c4a7ed9e35 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/dh.h @@ -0,0 +1,95 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DH_H_ +#define DH_H_ + +#include "config.h" + +#include "libssh/crypto.h" + +struct dh_ctx; + +#define DH_CLIENT_KEYPAIR 0 +#define DH_SERVER_KEYPAIR 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* functions implemented by crypto backends */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto); +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto); + +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator); +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator); +#endif /* OPENSSL_VERSION_NUMBER */ +int ssh_dh_set_parameters(struct dh_ctx *ctx, + const bignum modulus, const bignum generator); + +int ssh_dh_keypair_gen_keys(struct dh_ctx *ctx, int peer); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub); +#else +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub); +#endif /* OPENSSL_VERSION_NUMBER */ +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + bignum priv, bignum pub); + +int ssh_dh_compute_shared_secret(struct dh_ctx *ctx, int local, int remote, + bignum *dest); + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c); + +/* common functions */ +int ssh_dh_init(void); +void ssh_dh_finalize(void); + +int ssh_dh_import_next_pubkey_blob(ssh_session session, + ssh_string pubkey_blob); + +ssh_key ssh_dh_get_current_server_publickey(ssh_session session); +int ssh_dh_get_current_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); +ssh_key ssh_dh_get_next_server_publickey(ssh_session session); +int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); +int dh_handshake(ssh_session session); + +int ssh_client_dh_init(ssh_session session); +void ssh_client_dh_remove_callbacks(ssh_session session); +#ifdef WITH_SERVER +void ssh_server_dh_init(ssh_session session); +#endif /* WITH_SERVER */ +int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet); +int ssh_fallback_group(uint32_t pmax, bignum *p, bignum *g); +bool ssh_dh_is_known_group(bignum modulus, bignum generator); + +#ifdef __cplusplus +} +#endif + +#endif /* DH_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/ecdh.h b/src/libs/libssh-0.12.2/include/libssh/ecdh.h new file mode 100644 index 000000000000..2f763528ae99 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/ecdh.h @@ -0,0 +1,66 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ECDH_H_ +#define ECDH_H_ + +#include "config.h" +#include "libssh/callbacks.h" + +#ifdef HAVE_LIBCRYPTO +#ifdef HAVE_OPENSSL_ECDH_H + +#ifdef HAVE_ECC +#define HAVE_ECDH 1 +#endif + +#endif /* HAVE_OPENSSL_ECDH_H */ +#endif /* HAVE_LIBCRYPTO */ + +#ifdef HAVE_GCRYPT_ECC +#define HAVE_ECDH 1 +#endif + +#ifdef HAVE_LIBMBEDCRYPTO +#define HAVE_ECDH 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks; +/* Backend-specific functions. */ +int ssh_ecdh_init(ssh_session session); +int ssh_client_ecdh_init(ssh_session session); +void ssh_client_ecdh_remove_callbacks(ssh_session session); +int ecdh_build_k(ssh_session session); + +#ifdef WITH_SERVER +extern struct ssh_packet_callbacks_struct ssh_ecdh_server_callbacks; +void ssh_server_ecdh_init(ssh_session session); +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* ECDH_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/ed25519.h b/src/libs/libssh-0.12.2/include/libssh/ed25519.h new file mode 100644 index 000000000000..a6bcdaf3d4d3 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/ed25519.h @@ -0,0 +1,116 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ED25519_H_ +#define ED25519_H_ +#include "libssh/priv.h" + +/** + * @defgroup ed25519 ed25519 API + * @brief API for DJB's ed25519 + * + * @{ + */ + +/** @internal + * @brief ED25519 public key. + * Ed25519 public key consist of 32 bytes. + */ +#define ED25519_PK_LEN 32 + +/** @internal + * @brief ED25519 secret key. + * Ed25519 secret key consist of 64 bytes. + */ +#define ED25519_SK_LEN 64 + +/** @internal + * @brief ED25519 signature. + * Ed25519 signatures consist of 64 bytes. + */ +#define ED25519_SIG_LEN 64 + +/** @internal + * @brief ED25519 public key. + * The public key consists of 32 bytes and can be used for signature + * verification. + */ +typedef uint8_t ed25519_pubkey[ED25519_PK_LEN]; + +/** @internal + * @brief ED25519 private key. + * The private key consists of 64 bytes and should be kept secret. + */ +typedef uint8_t ed25519_privkey[ED25519_SK_LEN]; + +/** @internal + * @brief ED25519 signature. + * Ed25519 signatures consists of 64 bytes. + */ +typedef uint8_t ed25519_signature[ED25519_SIG_LEN]; + +#ifdef __cplusplus +extern "C" { +#endif + +/** @internal + * @brief generate an ed25519 key pair + * @param[out] pk generated public key + * @param[out] sk generated secret key + * @return 0 on success, -1 on error. + */ +int crypto_sign_ed25519_keypair(ed25519_pubkey pk, ed25519_privkey sk); + +/** @internal + * @brief sign a message with ed25519 + * @param[out] sm location to store the signed message. + * Its length should be mlen + 64. + * @param[out] smlen pointer to the size of the signed message + * @param[in] m message to be signed + * @param[in] mlen length of the message to be signed + * @param[in] sk secret key to sign the message with + * @return 0 on success. + */ +int crypto_sign_ed25519( + unsigned char *sm, uint64_t *smlen, + const unsigned char *m, uint64_t mlen, + const ed25519_privkey sk); + +/** @internal + * @brief "open" and verify the signature of a signed message + * @param[out] m location to store the verified message. + * Its length should be equal to smlen. + * @param[out] mlen pointer to the size of the verified message + * @param[in] sm signed message to verify + * @param[in] smlen length of the signed message to verify + * @param[in] pk public key used to sign the message + * @returns 0 on success (supposedly). + */ +int crypto_sign_ed25519_open( + unsigned char *m, uint64_t *mlen, + const unsigned char *sm, uint64_t smlen, + const ed25519_pubkey pk); + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* ED25519_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/fe25519.h b/src/libs/libssh-0.12.2/include/libssh/fe25519.h new file mode 100644 index 000000000000..0dfb0613fbe7 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/fe25519.h @@ -0,0 +1,76 @@ +/* $OpenBSD: fe25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/fe25519.h + */ + +#ifndef FE25519_H +#define FE25519_H + +#include "libssh/priv.h" + +#define fe25519 crypto_sign_ed25519_ref_fe25519 +#define fe25519_freeze crypto_sign_ed25519_ref_fe25519_freeze +#define fe25519_unpack crypto_sign_ed25519_ref_fe25519_unpack +#define fe25519_pack crypto_sign_ed25519_ref_fe25519_pack +#define fe25519_iszero crypto_sign_ed25519_ref_fe25519_iszero +#define fe25519_iseq_vartime crypto_sign_ed25519_ref_fe25519_iseq_vartime +#define fe25519_cmov crypto_sign_ed25519_ref_fe25519_cmov +#define fe25519_setone crypto_sign_ed25519_ref_fe25519_setone +#define fe25519_setzero crypto_sign_ed25519_ref_fe25519_setzero +#define fe25519_neg crypto_sign_ed25519_ref_fe25519_neg +#define fe25519_getparity crypto_sign_ed25519_ref_fe25519_getparity +#define fe25519_add crypto_sign_ed25519_ref_fe25519_add +#define fe25519_sub crypto_sign_ed25519_ref_fe25519_sub +#define fe25519_mul crypto_sign_ed25519_ref_fe25519_mul +#define fe25519_square crypto_sign_ed25519_ref_fe25519_square +#define fe25519_invert crypto_sign_ed25519_ref_fe25519_invert +#define fe25519_pow2523 crypto_sign_ed25519_ref_fe25519_pow2523 + +typedef struct { + uint32_t v[32]; +} fe25519; + +#ifdef __cplusplus +extern "C" { +#endif + +void fe25519_freeze(fe25519 *r); + +void fe25519_unpack(fe25519 *r, const unsigned char x[32]); + +void fe25519_pack(unsigned char r[32], const fe25519 *x); + +uint32_t fe25519_iszero(const fe25519 *x); + +int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y); + +void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b); + +void fe25519_setone(fe25519 *r); + +void fe25519_setzero(fe25519 *r); + +void fe25519_neg(fe25519 *r, const fe25519 *x); + +unsigned char fe25519_getparity(const fe25519 *x); + +void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_square(fe25519 *r, const fe25519 *x); + +void fe25519_invert(fe25519 *r, const fe25519 *x); + +void fe25519_pow2523(fe25519 *r, const fe25519 *x); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/ge25519.h b/src/libs/libssh-0.12.2/include/libssh/ge25519.h new file mode 100644 index 000000000000..480f29dc4050 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/ge25519.h @@ -0,0 +1,51 @@ +/* $OpenBSD: ge25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519.h + */ + +#ifndef GE25519_H +#define GE25519_H + +#include "fe25519.h" +#include "sc25519.h" + +#define ge25519 crypto_sign_ed25519_ref_ge25519 +#define ge25519_base crypto_sign_ed25519_ref_ge25519_base +#define ge25519_unpackneg_vartime crypto_sign_ed25519_ref_unpackneg_vartime +#define ge25519_pack crypto_sign_ed25519_ref_pack +#define ge25519_isneutral_vartime crypto_sign_ed25519_ref_isneutral_vartime +#define ge25519_double_scalarmult_vartime crypto_sign_ed25519_ref_double_scalarmult_vartime +#define ge25519_scalarmult_base crypto_sign_ed25519_ref_scalarmult_base + +typedef struct +{ + fe25519 x; + fe25519 y; + fe25519 z; + fe25519 t; +} ge25519; + +#ifdef __cplusplus +extern "C" { +#endif + +extern const ge25519 ge25519_base; + +int ge25519_unpackneg_vartime(ge25519 *r, const unsigned char p[32]); + +void ge25519_pack(unsigned char r[32], const ge25519 *p); + +int ge25519_isneutral_vartime(const ge25519 *p); + +void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const sc25519 *s1, const ge25519 *p2, const sc25519 *s2); + +void ge25519_scalarmult_base(ge25519 *r, const sc25519 *s); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/gssapi.h b/src/libs/libssh-0.12.2/include/libssh/gssapi.h new file mode 100644 index 000000000000..fd1216f2eeb1 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/gssapi.h @@ -0,0 +1,103 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef GSSAPI_H_ +#define GSSAPI_H_ + +#include "config.h" +#ifdef WITH_GSSAPI +#include "session.h" +#include + +/* all OID begin with the tag identifier + length */ +#define SSH_OID_TAG 06 + +#define GSSAPI_KEY_EXCHANGE_SUPPORTED "gss-group14-sha256-," \ + "gss-group16-sha512-," \ + "gss-nistp256-sha256-," \ + "gss-curve25519-sha256-" + +typedef struct ssh_gssapi_struct *ssh_gssapi; + +#ifdef __cplusplus +extern "C" { +#endif + +/** current state of an GSSAPI authentication */ +enum ssh_gssapi_state_e { + SSH_GSSAPI_STATE_NONE, /* no status */ + SSH_GSSAPI_STATE_RCV_TOKEN, /* Expecting a token */ + SSH_GSSAPI_STATE_RCV_MIC, /* Expecting a MIC */ +}; + +struct ssh_gssapi_struct{ + enum ssh_gssapi_state_e state; /* current state */ + gss_cred_id_t server_creds; /* credentials of server */ + gss_cred_id_t client_creds; /* creds delegated by the client */ + gss_ctx_id_t ctx; /* the authentication context */ + gss_name_t client_name; /* Identity of the client */ + char *user; /* username of client */ + char *canonic_user; /* canonic form of the client's username */ + struct { + gss_name_t server_name; /* identity of server */ + OM_uint32 flags; /* flags used for init context */ + gss_OID oid; /* mech being used for authentication */ + gss_cred_id_t creds; /* creds used to initialize context */ + gss_cred_id_t client_deleg_creds; /* delegated creds (const, not freeable) */ + } client; +}; + +#ifdef WITH_SERVER +int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic); +int ssh_gssapi_server_oids(gss_OID_set *selected); +#endif /* WITH_SERVER */ + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response); + + +int ssh_gssapi_init(ssh_session session); +void ssh_gssapi_log_error(int verb, const char *msg_a, int maj_stat, int min_stat); +int ssh_gssapi_auth_mic(ssh_session session); +void ssh_gssapi_free(ssh_session session); +int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids); +char *ssh_gssapi_name_to_char(gss_name_t name); +int ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host); +OM_uint32 ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi, + gss_buffer_desc *input_token, + gss_buffer_desc *output_token, + OM_uint32 *ret_flags); + +char *ssh_gssapi_oid_hash(ssh_string oid); +char *ssh_gssapi_kex_mechs(ssh_session session); +int ssh_gssapi_check_client_config(ssh_session session); +ssh_buffer ssh_gssapi_build_mic(ssh_session session, const char *context); +int ssh_gssapi_auth_keyex_mic(ssh_session session, + gss_buffer_desc *mic_token_buf); + +#ifdef __cplusplus +} +#endif + +#endif /* WITH_GSSAPI */ +#endif /* GSSAPI_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/hybrid_mlkem.h b/src/libs/libssh-0.12.2/include/libssh/hybrid_mlkem.h new file mode 100644 index 000000000000..ca9b6a20150c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/hybrid_mlkem.h @@ -0,0 +1,51 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Sahana Prasad + * Author: Pavol Žáčik + * Author: Claude (Anthropic) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef HYBRID_MLKEM_H_ +#define HYBRID_MLKEM_H_ + +#include "libssh/mlkem.h" +#include "libssh/wrapper.h" + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NISTP256_SHARED_SECRET_SIZE 32 +#define NISTP384_SHARED_SECRET_SIZE 48 + +int ssh_client_hybrid_mlkem_init(ssh_session session); +void ssh_client_hybrid_mlkem_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_hybrid_mlkem_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* HYBRID_MLKEM_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/kex-gss.h b/src/libs/libssh-0.12.2/include/libssh/kex-gss.h new file mode 100644 index 000000000000..65ae2fe4f9ad --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/kex-gss.h @@ -0,0 +1,36 @@ +/* + * kex-gss.h - GSSAPI key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Gauravsingh Sisodia + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ +#ifndef KEX_GSS_H_ +#define KEX_GSS_H_ + +#include "config.h" +#ifdef WITH_GSSAPI + +int ssh_client_gss_kex_init(ssh_session session); +void ssh_server_gss_kex_init(ssh_session session); +int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet); +void ssh_client_gss_kex_remove_callbacks(ssh_session session); +void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session); + +#endif /* WITH_GSSAPI */ +#endif /* KEX_GSS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/kex.h b/src/libs/libssh-0.12.2/include/libssh/kex.h new file mode 100644 index 000000000000..435ecc88bdf3 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/kex.h @@ -0,0 +1,76 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KEX_H_ +#define KEX_H_ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" + +#define SSH_KEX_METHODS 10 + +struct ssh_kex_struct { + unsigned char cookie[16]; + char *methods[SSH_KEX_METHODS]; +}; + +/* crypto.h needs ssh_kex_struct so it is included below the struct definition */ +#include "libssh/crypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_kexinit); + +int ssh_send_kex(ssh_session session); +void ssh_list_kex(struct ssh_kex_struct *kex); +int ssh_set_client_kex(ssh_session session); +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex); +int ssh_kex_select_methods(ssh_session session); +int ssh_verify_existing_algo(enum ssh_kex_types_e algo, const char *name); +char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_add_to_default_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_remove_from_default_algos(enum ssh_kex_types_e algo, + const char *list); +char *ssh_prefix_default_algos(enum ssh_kex_types_e algo, const char *list); +char **ssh_space_tokenize(const char *chain); +int ssh_get_kex1(ssh_session session); +char *ssh_find_matching(const char *in_d, const char *what_d); +const char *ssh_kex_get_supported_method(enum ssh_kex_types_e type); +const char *ssh_kex_get_default_methods(enum ssh_kex_types_e type); +const char *ssh_kex_get_fips_methods(enum ssh_kex_types_e type); +const char *ssh_kex_get_description(enum ssh_kex_types_e type); +char *ssh_client_select_hostkeys(ssh_session session); +int ssh_send_rekex(ssh_session session); +int server_set_kex(ssh_session session); +int ssh_make_sessionid(ssh_session session); +/* add data for the final cookie */ +int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie); +int ssh_hashbufout_add_cookie(ssh_session session); +int ssh_generate_session_keys(ssh_session session); +bool ssh_kex_is_gss(struct ssh_crypto_struct *crypto); + +#ifdef __cplusplus +} +#endif + +#endif /* KEX_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/keys.h b/src/libs/libssh-0.12.2/include/libssh/keys.h new file mode 100644 index 000000000000..1379539e0c22 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/keys.h @@ -0,0 +1,64 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KEYS_H_ +#define KEYS_H_ + +#include "config.h" +#include "libssh/libssh.h" +#include "libssh/wrapper.h" + +struct ssh_public_key_struct { + int type; + const char *type_c; /* Don't free it ! it is static */ +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t rsa_pub; +#elif defined(HAVE_LIBCRYPTO) + EVP_PKEY *key_pub; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa_pub; +#endif +}; + +struct ssh_private_key_struct { + int type; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t rsa_priv; +#elif defined(HAVE_LIBCRYPTO) + EVP_PKEY *key_priv; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa_priv; +#endif +}; + +#ifdef __cplusplus +extern "C" { +#endif + +const char *ssh_type_to_char(int type); +int ssh_type_from_name(const char *name); + +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s); + +#ifdef __cplusplus +} +#endif + +#endif /* KEYS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/knownhosts.h b/src/libs/libssh-0.12.2/include/libssh/knownhosts.h new file mode 100644 index 000000000000..b50018cdb0b2 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/knownhosts.h @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 20014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef SSH_KNOWNHOSTS_H_ +#define SSH_KNOWNHOSTS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session); +char *ssh_known_hosts_get_algorithms_names(ssh_session session); +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry_file(ssh_session session, + const char *filename, + struct ssh_knownhosts_entry **pentry); + +#ifdef __cplusplus +} +#endif + +#endif /* SSH_KNOWNHOSTS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/legacy.h b/src/libs/libssh-0.12.2/include/libssh/legacy.h new file mode 100644 index 000000000000..38bef4dac79f --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/legacy.h @@ -0,0 +1,128 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Since libssh.h includes legacy.h, it's important that libssh.h is included + * first. we don't define LEGACY_H now because we want it to be defined when + * included from libssh.h + * All function calls declared in this header are deprecated and meant to be + * removed in future. + */ + +#ifndef LEGACY_H_ +#define LEGACY_H_ + +typedef struct ssh_private_key_struct* ssh_private_key; +typedef struct ssh_public_key_struct* ssh_public_key; + +#ifdef __cplusplus +extern "C" { +#endif + +LIBSSH_API int ssh_auth_list(ssh_session session); +LIBSSH_API int ssh_userauth_offer_pubkey(ssh_session session, const char *username, int type, ssh_string publickey); +LIBSSH_API int ssh_userauth_pubkey(ssh_session session, const char *username, ssh_string publickey, ssh_private_key privatekey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent_pubkey(ssh_session session, const char *username, + ssh_public_key publickey); +#endif +LIBSSH_API int ssh_userauth_autopubkey(ssh_session session, const char *passphrase); +LIBSSH_API int ssh_userauth_privatekey_file(ssh_session session, const char *username, + const char *filename, const char *passphrase); + +SSH_DEPRECATED LIBSSH_API void buffer_free(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API void *buffer_get(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API uint32_t buffer_get_len(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API ssh_buffer buffer_new(void); + +SSH_DEPRECATED LIBSSH_API ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_change_pty_size(ssh_channel channel,int cols,int rows); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_close(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int channel_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API void channel_free(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_get_exit_status(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_session channel_get_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_closed(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_open(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_new(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +SSH_DEPRECATED LIBSSH_API int channel_open_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_poll(ssh_channel channel, int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_request_env(ssh_channel channel, const char *name, const char *value); +SSH_DEPRECATED LIBSSH_API int channel_request_exec(ssh_channel channel, const char *cmd); +SSH_DEPRECATED LIBSSH_API int channel_request_pty(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +SSH_DEPRECATED LIBSSH_API int channel_request_shell(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_send_signal(ssh_channel channel, const char *signum); +SSH_DEPRECATED LIBSSH_API int channel_request_sftp(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_subsystem(ssh_channel channel, const char *subsystem); +SSH_DEPRECATED LIBSSH_API int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +SSH_DEPRECATED LIBSSH_API int channel_send_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +SSH_DEPRECATED LIBSSH_API void channel_set_blocking(ssh_channel channel, int blocking); +SSH_DEPRECATED LIBSSH_API int channel_write(ssh_channel channel, const void *data, uint32_t len); + +SSH_DEPRECATED LIBSSH_API void privatekey_free(ssh_private_key prv); +SSH_DEPRECATED LIBSSH_API ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +SSH_DEPRECATED LIBSSH_API void publickey_free(ssh_public_key key); +SSH_DEPRECATED LIBSSH_API int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +SSH_DEPRECATED LIBSSH_API ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type); +SSH_DEPRECATED LIBSSH_API ssh_public_key publickey_from_privatekey(ssh_private_key prv); +SSH_DEPRECATED LIBSSH_API ssh_string publickey_to_string(ssh_public_key key); +SSH_DEPRECATED LIBSSH_API int ssh_try_publickey_from_file(ssh_session session, const char *keyfile, + ssh_string *publickey, int *type); +SSH_DEPRECATED LIBSSH_API enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey); + +LIBSSH_API ssh_string ssh_get_pubkey(ssh_session session); + +LIBSSH_API ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype); +LIBSSH_API ssh_public_key ssh_message_auth_publickey(ssh_message msg); + +SSH_DEPRECATED LIBSSH_API void string_burn(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_copy(ssh_string str); +SSH_DEPRECATED LIBSSH_API void *string_data(ssh_string str); +SSH_DEPRECATED LIBSSH_API int string_fill(ssh_string str, const void *data, size_t len); +SSH_DEPRECATED LIBSSH_API void string_free(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_from_char(const char *what); +SSH_DEPRECATED LIBSSH_API size_t string_len(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_new(size_t size); +SSH_DEPRECATED LIBSSH_API char *string_to_char(ssh_string str); + +#ifdef __cplusplus +} +#endif + +#endif /* LEGACY_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libcrypto.h b/src/libs/libssh-0.12.2/include/libssh/libcrypto.h new file mode 100644 index 000000000000..b33665144d1e --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libcrypto.h @@ -0,0 +1,137 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBCRYPTO_H_ +#define LIBCRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBCRYPTO + +#include "libssh/libssh.h" +#include +#include +#include +#include +#include +#include +#include + +typedef EVP_MD_CTX* SHACTX; +typedef EVP_MD_CTX* SHA256CTX; +typedef EVP_MD_CTX* SHA384CTX; +typedef EVP_MD_CTX* SHA512CTX; +typedef EVP_MD_CTX* MD5CTX; +typedef EVP_MD_CTX* HMACCTX; + +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH +#ifdef MD5_DIGEST_LEN + #undef MD5_DIGEST_LEN +#endif +#define MD5_DIGEST_LEN MD5_DIGEST_LENGTH + +#ifdef HAVE_OPENSSL_ECC +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE +#endif + +/* Use ssh_crypto_free() to release memory allocated by bignum_bn2dec(), + bignum_bn2hex() and other functions that use crypto-library functions that + are documented to allocate memory that needs to be de-allocate with + OPENSSL_free. */ +#define ssh_crypto_free(x) OPENSSL_free(x) + +#include +#include + +typedef BIGNUM* bignum; +typedef const BIGNUM* const_bignum; +typedef BN_CTX* bignum_CTX; + +#define bignum_new() BN_new() +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + BN_clear_free((num)); \ + (num)=NULL; \ + } \ + } while(0) +#define bignum_set_word(bn,n) BN_set_word(bn,n) +#define bignum_bin2bn(data, datalen, dest) \ + do { \ + (*dest) = BN_new(); \ + if ((*dest) != NULL) { \ + BN_bin2bn(data,datalen,(*dest)); \ + } \ + } while(0) +#define bignum_bn2dec(num) BN_bn2dec(num) +#define bignum_dec2bn(data, bn) BN_dec2bn(bn, data) +#define bignum_hex2bn(data, bn) BN_hex2bn(bn, data) +#define bignum_bn2hex(num, dest) (*dest)=(unsigned char *)BN_bn2hex(num) +#define bignum_rand(rnd, bits) BN_rand(rnd, bits, 0, 1) +#define bignum_rand_range(rnd, max) BN_rand_range(rnd, max) +#define bignum_ctx_new() BN_CTX_new() +#define bignum_ctx_free(num) BN_CTX_free(num) +#define bignum_ctx_invalid(ctx) ((ctx) == NULL) +#define bignum_mod_exp(dest,generator,exp,modulo,ctx) BN_mod_exp(dest,generator,exp,modulo,ctx) +#define bignum_add(dest, a, b) BN_add(dest, a, b) +#define bignum_sub(dest, a, b) BN_sub(dest, a, b) +#define bignum_mod(dest, a, b, ctx) BN_mod(dest, a, b, ctx) +#define bignum_num_bytes(num) (size_t)BN_num_bytes(num) +#define bignum_num_bits(num) (size_t)BN_num_bits(num) +#define bignum_is_bit_set(num,bit) BN_is_bit_set(num, (int)bit) +#define bignum_bn2bin(num,len, ptr) BN_bn2bin(num, ptr) +#define bignum_cmp(num1,num2) BN_cmp(num1,num2) +#define bignum_rshift1(dest, src) BN_rshift1(dest, src) +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = BN_dup(orig); \ + } else { \ + BN_copy(*(dest), orig); \ + } \ + } while(0) + + +/* Returns true if the OpenSSL is operating in FIPS mode */ +#ifdef HAVE_OPENSSL_FIPS_MODE +#define ssh_fips_mode() (FIPS_mode() != 0) +#elif OPENSSL_VERSION_NUMBER >= 0x30000000L +#define ssh_fips_mode() EVP_default_properties_is_fips_enabled(NULL) +#else +#define ssh_fips_mode() false +#endif + +ssh_string pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p); +int pki_key_ecgroup_name_to_nid(const char *group); + +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) +int pki_load_pkcs11_provider(void); +#else +ENGINE *pki_get_engine(void); +#endif +#endif /* WITH_PKCS11_PROVIDER */ + +#define FIPS_FALLBACK_PROPQ "provider=default,-fips" + +#endif /* HAVE_LIBCRYPTO */ + +#endif /* LIBCRYPTO_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libgcrypt.h b/src/libs/libssh-0.12.2/include/libssh/libgcrypt.h new file mode 100644 index 000000000000..ce0beefdb5b3 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libgcrypt.h @@ -0,0 +1,128 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBGCRYPT_H_ +#define LIBGCRYPT_H_ + +#include "config.h" + +#ifdef HAVE_LIBGCRYPT + +#include +typedef gcry_md_hd_t SHACTX; +typedef gcry_md_hd_t SHA256CTX; +typedef gcry_md_hd_t SHA384CTX; +typedef gcry_md_hd_t SHA512CTX; +typedef gcry_md_hd_t MD5CTX; +typedef gcry_md_hd_t HMACCTX; +#define SHA_DIGEST_LENGTH 20 +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define MD5_DIGEST_LEN 16 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH + +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 64 +#endif + +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE + +#define ssh_crypto_free(x) gcry_free(x) + +typedef gcry_mpi_t bignum; +typedef const struct gcry_mpi *const_bignum; +typedef void* bignum_CTX; + +/* Constants for curves. */ +#define NID_gcrypt_nistp256 0 +#define NID_gcrypt_nistp384 1 +#define NID_gcrypt_nistp521 2 + +/* missing gcrypt functions */ +int ssh_gcry_dec2bn(bignum *bn, const char *data); +char *ssh_gcry_bn2dec(bignum bn); +int ssh_gcry_rand_range(bignum rnd, bignum max); + +#define bignum_new() gcry_mpi_new(0) +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + gcry_mpi_release((num)); \ + (num)=NULL; \ + } \ + } while (0) +#define bignum_free(num) gcry_mpi_release(num) +#define bignum_ctx_new() NULL +#define bignum_ctx_free(ctx) do {(ctx) = NULL;} while(0) +#define bignum_ctx_invalid(ctx) (ctx != NULL) +#define bignum_set_word(bn,n) (gcry_mpi_set_ui(bn,n)!=NULL ? 1 : 0) +#define bignum_bin2bn(data,datalen,dest) gcry_mpi_scan(dest,GCRYMPI_FMT_USG,data,datalen,NULL) +#define bignum_bn2dec(num) ssh_gcry_bn2dec(num) +#define bignum_dec2bn(num, data) ssh_gcry_dec2bn(data, num) + +#define bignum_bn2hex(num, data) \ + gcry_mpi_aprint(GCRYMPI_FMT_HEX, data, NULL, (const gcry_mpi_t)num) + +#define bignum_hex2bn(data, num) (gcry_mpi_scan(num,GCRYMPI_FMT_HEX,data,0,NULL)==0?1:0) +#define bignum_rand(num,bits) 1,gcry_mpi_randomize(num,bits,GCRY_STRONG_RANDOM),gcry_mpi_set_bit(num,bits-1),gcry_mpi_set_bit(num,0) +#define bignum_mod_exp(dest,generator,exp,modulo, ctx) 1,gcry_mpi_powm(dest,generator,exp,modulo) +#define bignum_num_bits(num) gcry_mpi_get_nbits(num) +#define bignum_num_bytes(num) ((gcry_mpi_get_nbits(num)+7)/8) +#define bignum_is_bit_set(num,bit) gcry_mpi_test_bit(num,bit) +#define bignum_bn2bin(num,datalen,data) gcry_mpi_print(GCRYMPI_FMT_USG,data,datalen,NULL,num) +#define bignum_cmp(num1,num2) gcry_mpi_cmp(num1,num2) +#define bignum_rshift1(dest, src) gcry_mpi_rshift (dest, src, 1) +#define bignum_add(dst, a, b) gcry_mpi_add(dst, a, b) +#define bignum_sub(dst, a, b) gcry_mpi_sub(dst, a, b) +#define bignum_mod(dst, a, b, ctx) 1,gcry_mpi_mod(dst, a, b) +#define bignum_rand_range(rnd, max) ssh_gcry_rand_range(rnd, max); +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = gcry_mpi_copy((const gcry_mpi_t)orig); \ + } else { \ + gcry_mpi_set(*(dest), (const gcry_mpi_t)orig); \ + } \ + } while(0) +/* Helper functions for data conversions. */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Extract an MPI from the given s-expression SEXP named NAME which is + encoded using INFORMAT and store it in a newly allocated ssh_string + encoded using OUTFORMAT. */ +ssh_string ssh_sexp_extract_mpi(const gcry_sexp_t sexp, + const char *name, + enum gcry_mpi_format informat, + enum gcry_mpi_format outformat); + +#define ssh_fips_mode() false + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_LIBGCRYPT */ + +#endif /* LIBGCRYPT_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libmbedcrypto.h b/src/libs/libssh-0.12.2/include/libssh/libmbedcrypto.h new file mode 100644 index 000000000000..71ebcccd0f70 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libmbedcrypto.h @@ -0,0 +1,150 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef LIBMBEDCRYPTO_H_ +#define LIBMBEDCRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBMBEDCRYPTO + +#include +#include +#include +#include +#include +#include +#include + +typedef mbedtls_md_context_t *SHACTX; +typedef mbedtls_md_context_t *SHA256CTX; +typedef mbedtls_md_context_t *SHA384CTX; +typedef mbedtls_md_context_t *SHA512CTX; +typedef mbedtls_md_context_t *MD5CTX; +typedef mbedtls_md_context_t *HMACCTX; + +#define SHA_DIGEST_LENGTH 20 +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define MD5_DIGEST_LEN 16 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH + +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 64 +#endif + +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE + +#define ssh_crypto_free(x) mbedtls_free(x) + +typedef mbedtls_mpi *bignum; +typedef const mbedtls_mpi *const_bignum; +typedef void* bignum_CTX; + +/* Constants for curves */ +#define NID_mbedtls_nistp256 0 +#define NID_mbedtls_nistp384 1 +#define NID_mbedtls_nistp521 2 + +struct mbedtls_ecdsa_sig { + bignum r; + bignum s; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +bignum ssh_mbedcry_bn_new(void); +void ssh_mbedcry_bn_free(bignum num); +char *ssh_mbedcry_bn2num(const_bignum num, int radix); +int ssh_mbedcry_rand(bignum rnd, int bits, int top, int bottom); +int ssh_mbedcry_is_bit_set(bignum num, size_t pos); +int ssh_mbedcry_rand_range(bignum dest, bignum max); +int ssh_mbedcry_hex2bn(bignum *dest, char *data); + +#define bignum_new() ssh_mbedcry_bn_new() +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + ssh_mbedcry_bn_free(num); \ + (num)=NULL; \ + } \ + } while(0) +#define bignum_ctx_new() NULL +#define bignum_ctx_free(num) do {(num) = NULL;} while(0) +#define bignum_ctx_invalid(ctx) (ctx == NULL?0:1) +#define bignum_set_word(bn, n) (mbedtls_mpi_lset(bn, n)==0?1:0) /* TODO fix + overflow/underflow */ +#define bignum_bin2bn(data, datalen, bn) do { \ + *(bn) = bignum_new(); \ + if (*(bn) != NULL) { \ + mbedtls_mpi_read_binary(*(bn), data, datalen); \ + } \ + } while(0) +#define bignum_bn2dec(num) ssh_mbedcry_bn2num(num, 10) +#define bignum_dec2bn(data, bn) mbedtls_mpi_read_string(bn, 10, data) +#define bignum_bn2hex(num, dest) (*dest)=(unsigned char *)ssh_mbedcry_bn2num(num, 16) +#define bignum_hex2bn(data, dest) ssh_mbedcry_hex2bn(dest, data) +#define bignum_rand(rnd, bits) ssh_mbedcry_rand((rnd), (bits), 0, 1) +#define bignum_rand_range(rnd, max) ssh_mbedcry_rand_range(rnd, max) +#define bignum_mod_exp(dest, generator, exp, modulo, ctx) \ + (mbedtls_mpi_exp_mod(dest, generator, exp, modulo, NULL)==0?1:0) +#define bignum_add(dest, a, b) mbedtls_mpi_add_mpi(dest, a, b) +#define bignum_sub(dest, a, b) mbedtls_mpi_sub_mpi(dest, a, b) +#define bignum_mod(dest, a, b, ctx) \ + (mbedtls_mpi_mod_mpi(dest, a, b) == 0 ? 1 : 0) +#define bignum_num_bytes(num) mbedtls_mpi_size(num) +#define bignum_num_bits(num) mbedtls_mpi_bitlen(num) +#define bignum_is_bit_set(num, bit) ssh_mbedcry_is_bit_set(num, bit) +#define bignum_bn2bin(num, len, ptr) mbedtls_mpi_write_binary(num, ptr, \ + mbedtls_mpi_size(num)) +#define bignum_cmp(num1, num2) mbedtls_mpi_cmp_mpi(num1, num2) +#define bignum_rshift1(dest, src) mbedtls_mpi_copy(dest, src), mbedtls_mpi_shift_r(dest, 1) +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = bignum_new(); \ + } \ + if (*(dest) != NULL) { \ + mbedtls_mpi_copy(*(dest), orig); \ + } \ + } while(0) + +mbedtls_ctr_drbg_context *ssh_get_mbedtls_ctr_drbg_context(void); + +int ssh_mbedtls_random(void *where, int len, int strong); + +ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const + mbedtls_ecp_point *p); + +#define ssh_fips_mode() false + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_LIBMBEDCRYPTO */ +#endif /* LIBMBEDCRYPTO_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libssh.h b/src/libs/libssh-0.12.2/include/libssh/libssh.h new file mode 100644 index 000000000000..0c9e774654a9 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libssh.h @@ -0,0 +1,1023 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2026 by Aris Adamantiadis and the libssh team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_H +#define _LIBSSH_H + +#include + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef LIBSSH_STATIC + #define LIBSSH_API + #else + #ifdef LIBSSH_EXPORTS + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllexport)) + #else + #define LIBSSH_API __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllimport)) + #else + #define LIBSSH_API __declspec(dllimport) + #endif + #endif + #endif +#else + #if __GNUC__ >= 4 && !defined(__OS2__) + #define LIBSSH_API __attribute__((visibility("default"))) + #else + #define LIBSSH_API + #endif +#endif + +#include +#include +#include +#include + +#ifdef _MSC_VER + typedef int mode_t; +#else /* _MSC_VER */ + #include + #include +#endif /* _MSC_VER */ + +#ifdef _WIN32 + #include +#else /* _WIN32 */ + #include /* for fd_set * */ + #include +#endif /* _WIN32 */ + +#define SSH_STRINGIFY(s) SSH_TOSTRING(s) +#define SSH_TOSTRING(s) #s + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +#if !defined(SSH_SUPPRESS_DEPRECATED) && defined(__GNUC__) +#define SSH_DEPRECATED __attribute__ ((deprecated)) +#else +#define SSH_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_counter_struct { + uint64_t in_bytes; + uint64_t out_bytes; + uint64_t in_packets; + uint64_t out_packets; +}; +typedef struct ssh_counter_struct *ssh_counter; + +typedef struct ssh_agent_struct* ssh_agent; +typedef struct ssh_buffer_struct* ssh_buffer; +typedef struct ssh_channel_struct* ssh_channel; +typedef struct ssh_message_struct* ssh_message; +typedef struct ssh_pcap_file_struct* ssh_pcap_file; +typedef struct ssh_key_struct* ssh_key; +typedef struct ssh_scp_struct* ssh_scp; +typedef struct ssh_session_struct* ssh_session; +typedef struct ssh_string_struct* ssh_string; +typedef struct ssh_event_struct* ssh_event; +typedef struct ssh_connector_struct * ssh_connector; +typedef struct ssh_pki_ctx_struct *ssh_pki_ctx; +typedef void* ssh_gssapi_creds; + +/* Socket type */ +#ifdef _WIN32 +#ifndef socket_t +typedef SOCKET socket_t; +#endif /* socket_t */ +#else /* _WIN32 */ +#ifndef socket_t +typedef int socket_t; +#endif +#endif /* _WIN32 */ + +#define SSH_INVALID_SOCKET ((socket_t) -1) + +/* the offsets of methods */ +enum ssh_kex_types_e { + SSH_KEX=0, + SSH_HOSTKEYS, + SSH_CRYPT_C_S, + SSH_CRYPT_S_C, + SSH_MAC_C_S, + SSH_MAC_S_C, + SSH_COMP_C_S, + SSH_COMP_S_C, + SSH_LANG_C_S, + SSH_LANG_S_C +}; + +#define SSH_CRYPT 2 +#define SSH_MAC 3 +#define SSH_COMP 4 +#define SSH_LANG 5 + +enum ssh_auth_e { + SSH_AUTH_SUCCESS=0, + SSH_AUTH_DENIED, + SSH_AUTH_PARTIAL, + SSH_AUTH_INFO, + SSH_AUTH_AGAIN, + SSH_AUTH_ERROR=-1 +}; + +/* auth flags */ +#define SSH_AUTH_METHOD_UNKNOWN 0x0000u +#define SSH_AUTH_METHOD_NONE 0x0001u +#define SSH_AUTH_METHOD_PASSWORD 0x0002u +#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u +#define SSH_AUTH_METHOD_HOSTBASED 0x0008u +#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u +#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u +#define SSH_AUTH_METHOD_GSSAPI_KEYEX 0x0040u + +/* messages */ +enum ssh_requests_e { + SSH_REQUEST_AUTH=1, + SSH_REQUEST_CHANNEL_OPEN, + SSH_REQUEST_CHANNEL, + SSH_REQUEST_SERVICE, + SSH_REQUEST_GLOBAL +}; + +enum ssh_channel_type_e { + SSH_CHANNEL_UNKNOWN=0, + SSH_CHANNEL_SESSION, + SSH_CHANNEL_DIRECT_TCPIP, + SSH_CHANNEL_FORWARDED_TCPIP, + SSH_CHANNEL_X11, + SSH_CHANNEL_AUTH_AGENT +}; + +enum ssh_channel_requests_e { + SSH_CHANNEL_REQUEST_UNKNOWN=0, + SSH_CHANNEL_REQUEST_PTY, + SSH_CHANNEL_REQUEST_EXEC, + SSH_CHANNEL_REQUEST_SHELL, + SSH_CHANNEL_REQUEST_ENV, + SSH_CHANNEL_REQUEST_SUBSYSTEM, + SSH_CHANNEL_REQUEST_WINDOW_CHANGE, + SSH_CHANNEL_REQUEST_X11 +}; + +enum ssh_global_requests_e { + SSH_GLOBAL_REQUEST_UNKNOWN=0, + SSH_GLOBAL_REQUEST_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_KEEPALIVE, + SSH_GLOBAL_REQUEST_NO_MORE_SESSIONS +}; + +enum ssh_publickey_state_e { + SSH_PUBLICKEY_STATE_ERROR=-1, + SSH_PUBLICKEY_STATE_NONE=0, + SSH_PUBLICKEY_STATE_VALID=1, + SSH_PUBLICKEY_STATE_WRONG=2 +}; + +/* Status flags */ +/** Socket is closed */ +#define SSH_CLOSED 0x01 +/** Reading to socket won't block */ +#define SSH_READ_PENDING 0x02 +/** Session was closed due to an error */ +#define SSH_CLOSED_ERROR 0x04 +/** Output buffer not empty */ +#define SSH_WRITE_PENDING 0x08 + +enum ssh_server_known_e { + SSH_SERVER_ERROR=-1, + SSH_SERVER_NOT_KNOWN=0, + SSH_SERVER_KNOWN_OK, + SSH_SERVER_KNOWN_CHANGED, + SSH_SERVER_FOUND_OTHER, + SSH_SERVER_FILE_NOT_FOUND +}; + +enum ssh_known_hosts_e { + /** + * There had been an error checking the host. + */ + SSH_KNOWN_HOSTS_ERROR = -2, + + /** + * The known host file does not exist. The host is thus unknown. File will + * be created if host key is accepted. + */ + SSH_KNOWN_HOSTS_NOT_FOUND = -1, + + /** + * The server is unknown. User should confirm the public key hash is + * correct. + */ + SSH_KNOWN_HOSTS_UNKNOWN = 0, + + /** + * The server is known and has not changed. + */ + SSH_KNOWN_HOSTS_OK, + + /** + * The server key has changed. Either you are under attack or the + * administrator changed the key. You HAVE to warn the user about a + * possible attack. + */ + SSH_KNOWN_HOSTS_CHANGED, + + /** + * The server gave use a key of a type while we had an other type recorded. + * It is a possible attack. + */ + SSH_KNOWN_HOSTS_OTHER, +}; + +#ifndef MD5_DIGEST_LEN + #define MD5_DIGEST_LEN 16 +#endif +/* errors */ + +enum ssh_error_types_e { + SSH_NO_ERROR=0, + SSH_REQUEST_DENIED, + SSH_FATAL, + SSH_EINTR +}; + +/* some types for keys */ +enum ssh_keytypes_e{ + SSH_KEYTYPE_UNKNOWN=0, + SSH_KEYTYPE_DSS=1, /* deprecated */ + SSH_KEYTYPE_RSA, + SSH_KEYTYPE_RSA1, + SSH_KEYTYPE_ECDSA, /* deprecated */ + SSH_KEYTYPE_ED25519, + SSH_KEYTYPE_DSS_CERT01, /* deprecated */ + SSH_KEYTYPE_RSA_CERT01, + SSH_KEYTYPE_ECDSA_P256, + SSH_KEYTYPE_ECDSA_P384, + SSH_KEYTYPE_ECDSA_P521, + SSH_KEYTYPE_ECDSA_P256_CERT01, + SSH_KEYTYPE_ECDSA_P384_CERT01, + SSH_KEYTYPE_ECDSA_P521_CERT01, + SSH_KEYTYPE_ED25519_CERT01, + SSH_KEYTYPE_SK_ECDSA, + SSH_KEYTYPE_SK_ECDSA_CERT01, + SSH_KEYTYPE_SK_ED25519, + SSH_KEYTYPE_SK_ED25519_CERT01, +}; + +enum ssh_keycmp_e { + SSH_KEY_CMP_PUBLIC = 0, + SSH_KEY_CMP_PRIVATE = 1, + SSH_KEY_CMP_CERTIFICATE = 2, +}; + +#define SSH_ADDRSTRLEN 46 + +struct ssh_knownhosts_entry { + char *hostname; + char *unparsed; + ssh_key publickey; + char *comment; +}; + + +/* Error return codes */ +#define SSH_OK 0 /* No error */ +#define SSH_ERROR -1 /* Error of some kind */ +#define SSH_AGAIN -2 /* The nonblocking call must be repeated */ +#define SSH_EOF -127 /* We have already a eof */ + +/** + * @addtogroup libssh_log + * + * @{ + */ + +enum { + /** No logging at all + */ + SSH_LOG_NOLOG=0, + /** Only unrecoverable errors + */ + SSH_LOG_WARNING, + /** Information for the users + */ + SSH_LOG_PROTOCOL, + /** Debug information, to see what is going on + */ + SSH_LOG_PACKET, + /** Trace information and recoverable error messages + */ + SSH_LOG_FUNCTIONS +}; +/** @} */ +#define SSH_LOG_RARE SSH_LOG_WARNING + +/** + * @name Logging levels + * + * @brief Debug levels for logging. + * @{ + */ + +/** No logging at all */ +#define SSH_LOG_NONE 0 +/** Show only fatal warnings */ +#define SSH_LOG_WARN 1 +/** Get some information what's going on */ +#define SSH_LOG_INFO 2 +/** Get detailed debugging information **/ +#define SSH_LOG_DEBUG 3 +/** Get trace output, packet information, ... */ +#define SSH_LOG_TRACE 4 + +/** @} */ + +enum ssh_control_master_options_e { + SSH_CONTROL_MASTER_NO, + SSH_CONTROL_MASTER_AUTO, + SSH_CONTROL_MASTER_YES, + SSH_CONTROL_MASTER_ASK, + SSH_CONTROL_MASTER_AUTOASK +}; + +enum ssh_address_family_options_e { + SSH_ADDRESS_FAMILY_ANY, + SSH_ADDRESS_FAMILY_INET, + SSH_ADDRESS_FAMILY_INET6 +}; + +enum ssh_options_e { + SSH_OPTIONS_HOST, + SSH_OPTIONS_PORT, + SSH_OPTIONS_PORT_STR, + SSH_OPTIONS_FD, + SSH_OPTIONS_USER, + SSH_OPTIONS_SSH_DIR, + SSH_OPTIONS_IDENTITY, + SSH_OPTIONS_ADD_IDENTITY, + SSH_OPTIONS_KNOWNHOSTS, + SSH_OPTIONS_TIMEOUT, + SSH_OPTIONS_TIMEOUT_USEC, + SSH_OPTIONS_SSH1, + SSH_OPTIONS_SSH2, + SSH_OPTIONS_LOG_VERBOSITY, + SSH_OPTIONS_LOG_VERBOSITY_STR, + SSH_OPTIONS_CIPHERS_C_S, + SSH_OPTIONS_CIPHERS_S_C, + SSH_OPTIONS_COMPRESSION_C_S, + SSH_OPTIONS_COMPRESSION_S_C, + SSH_OPTIONS_PROXYCOMMAND, + SSH_OPTIONS_BINDADDR, + SSH_OPTIONS_STRICTHOSTKEYCHECK, + SSH_OPTIONS_COMPRESSION, + SSH_OPTIONS_COMPRESSION_LEVEL, + SSH_OPTIONS_KEY_EXCHANGE, + SSH_OPTIONS_HOSTKEYS, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, + SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, + SSH_OPTIONS_HMAC_C_S, + SSH_OPTIONS_HMAC_S_C, + SSH_OPTIONS_PASSWORD_AUTH, + SSH_OPTIONS_PUBKEY_AUTH, + SSH_OPTIONS_KBDINT_AUTH, + SSH_OPTIONS_GSSAPI_AUTH, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + SSH_OPTIONS_NODELAY, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + SSH_OPTIONS_PROCESS_CONFIG, + SSH_OPTIONS_REKEY_DATA, + SSH_OPTIONS_REKEY_TIME, + SSH_OPTIONS_RSA_MIN_SIZE, + SSH_OPTIONS_IDENTITY_AGENT, + SSH_OPTIONS_IDENTITIES_ONLY, + SSH_OPTIONS_CONTROL_MASTER, + SSH_OPTIONS_CONTROL_PATH, + SSH_OPTIONS_CERTIFICATE, + SSH_OPTIONS_PROXYJUMP, + SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, + SSH_OPTIONS_PKI_CONTEXT, + SSH_OPTIONS_ADDRESS_FAMILY, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + SSH_OPTIONS_NEXT_IDENTITY, +}; + +enum { + /** Code is going to write/create remote files */ + SSH_SCP_WRITE, + /** Code is going to read remote files */ + SSH_SCP_READ, + SSH_SCP_RECURSIVE=0x10 +}; + +enum ssh_scp_request_types { + /** A new directory is going to be pulled */ + SSH_SCP_REQUEST_NEWDIR=1, + /** A new file is going to be pulled */ + SSH_SCP_REQUEST_NEWFILE, + /** End of requests */ + SSH_SCP_REQUEST_EOF, + /** End of directory */ + SSH_SCP_REQUEST_ENDDIR, + /** Warning received */ + SSH_SCP_REQUEST_WARNING +}; + +enum ssh_connector_flags_e { + /** Only the standard stream of the channel */ + SSH_CONNECTOR_STDOUT = 1, + SSH_CONNECTOR_STDINOUT = 1, + /** Only the exception stream of the channel */ + SSH_CONNECTOR_STDERR = 2, + /** Merge both standard and exception streams */ + SSH_CONNECTOR_BOTH = 3 +}; + +LIBSSH_API int ssh_blocking_flush(ssh_session session, int timeout); +LIBSSH_API ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms); +LIBSSH_API int ssh_channel_change_pty_size(ssh_channel channel,int cols,int rows); +LIBSSH_API int ssh_channel_close(ssh_channel channel); +#define SSH_CHANNEL_FREE(x) \ + do { \ + if ((x) != NULL) { \ + ssh_channel_free(x); \ + (x) = NULL; \ + } \ + } while (0) +LIBSSH_API void ssh_channel_free(ssh_channel channel); +LIBSSH_API int ssh_channel_get_exit_state(ssh_channel channel, + uint32_t *pexit_code, + char **pexit_signal, + int *pcore_dumped); +SSH_DEPRECATED LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); +LIBSSH_API ssh_session ssh_channel_get_session(ssh_channel channel); +LIBSSH_API int ssh_channel_is_closed(ssh_channel channel); +LIBSSH_API int ssh_channel_is_eof(ssh_channel channel); +LIBSSH_API int ssh_channel_is_open(ssh_channel channel); +LIBSSH_API ssh_channel ssh_channel_new(ssh_session session); +LIBSSH_API int ssh_channel_open_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_forward_unix(ssh_channel channel, const char *remotepath, + const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_session(ssh_channel channel); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, const char *orig_addr, int orig_port); +LIBSSH_API int ssh_channel_open_tunnel(ssh_channel channel, int remote_unit); +LIBSSH_API int ssh_channel_poll(ssh_channel channel, int is_stderr); +LIBSSH_API int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr); +LIBSSH_API int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); +LIBSSH_API int ssh_channel_read_timeout(ssh_channel channel, void *dest, uint32_t count, int is_stderr, int timeout_ms); +LIBSSH_API int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +LIBSSH_API int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value); +LIBSSH_API int ssh_channel_request_exec(ssh_channel channel, const char *cmd); +LIBSSH_API int ssh_channel_request_pty(ssh_channel channel); +LIBSSH_API int ssh_channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +LIBSSH_API int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *term, + int cols, int rows, const unsigned char* modes, size_t modes_len); +LIBSSH_API int ssh_channel_request_shell(ssh_channel channel); +LIBSSH_API int ssh_channel_request_send_signal(ssh_channel channel, const char *signum); +LIBSSH_API int ssh_channel_request_send_break(ssh_channel channel, uint32_t length); +LIBSSH_API int ssh_channel_request_sftp(ssh_channel channel); +LIBSSH_API int ssh_channel_request_subsystem(ssh_channel channel, const char *subsystem); +LIBSSH_API int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +LIBSSH_API int ssh_channel_request_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_send_eof(ssh_channel channel); +LIBSSH_API void ssh_channel_set_blocking(ssh_channel channel, int blocking); +LIBSSH_API void ssh_channel_set_counter(ssh_channel channel, + ssh_counter counter); +LIBSSH_API int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len); +LIBSSH_API int ssh_channel_write_stderr(ssh_channel channel, + const void *data, + uint32_t len); +LIBSSH_API uint32_t ssh_channel_window_size(ssh_channel channel); + +LIBSSH_API char *ssh_basename (const char *path); +LIBSSH_API void ssh_clean_pubkey_hash(unsigned char **hash); +LIBSSH_API int ssh_connect(ssh_session session); + +LIBSSH_API ssh_connector ssh_connector_new(ssh_session session); +LIBSSH_API void ssh_connector_free(ssh_connector connector); +LIBSSH_API int ssh_connector_set_in_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API int ssh_connector_set_out_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd); +LIBSSH_API void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd); + +LIBSSH_API const char *ssh_copyright(void); +LIBSSH_API void ssh_disconnect(ssh_session session); +LIBSSH_API char *ssh_dirname (const char *path); +LIBSSH_API int ssh_finalize(void); + +/* REVERSE PORT FORWARDING */ +LIBSSH_API ssh_channel ssh_channel_open_forward_port(ssh_session session, + int timeout_ms, + int *destination_port, + char **originator, + int *originator_port); +SSH_DEPRECATED LIBSSH_API ssh_channel ssh_channel_accept_forward(ssh_session session, + int timeout_ms, + int *destination_port); +LIBSSH_API int ssh_channel_cancel_forward(ssh_session session, + const char *address, + int port); +LIBSSH_API int ssh_channel_listen_forward(ssh_session session, + const char *address, + int port, + int *bound_port); + +LIBSSH_API void ssh_free(ssh_session session); +LIBSSH_API const char *ssh_get_disconnect_message(ssh_session session); +LIBSSH_API const char *ssh_get_error(void *error); +LIBSSH_API int ssh_get_error_code(void *error); +LIBSSH_API socket_t ssh_get_fd(ssh_session session); +LIBSSH_API char *ssh_get_hexa(const unsigned char *what, size_t len); +LIBSSH_API char *ssh_get_issue_banner(ssh_session session); +LIBSSH_API int ssh_get_openssh_version(ssh_session session); +LIBSSH_API int ssh_request_no_more_sessions(ssh_session session); + +LIBSSH_API int ssh_get_server_publickey(ssh_session session, ssh_key *key); + +enum ssh_publickey_hash_type { + SSH_PUBLICKEY_HASH_SHA1, + SSH_PUBLICKEY_HASH_MD5, + SSH_PUBLICKEY_HASH_SHA256 +}; +LIBSSH_API int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, + unsigned char **hash, + size_t *hlen); + +/* DEPRECATED FUNCTIONS */ +SSH_DEPRECATED LIBSSH_API int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash); +SSH_DEPRECATED LIBSSH_API ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int ssh_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API int ssh_get_publickey(ssh_session session, ssh_key *key); +SSH_DEPRECATED LIBSSH_API int ssh_write_knownhost(ssh_session session); +SSH_DEPRECATED LIBSSH_API char *ssh_dump_knownhost(ssh_session session); +SSH_DEPRECATED LIBSSH_API int ssh_is_server_known(ssh_session session); +SSH_DEPRECATED LIBSSH_API void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len); +SSH_DEPRECATED LIBSSH_API int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); + +SSH_DEPRECATED LIBSSH_API int ssh_scp_accept_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_close(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_deny_request(ssh_scp scp, const char *reason); +SSH_DEPRECATED LIBSSH_API void ssh_scp_free(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_init(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_leave_directory(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location); +SSH_DEPRECATED LIBSSH_API int ssh_scp_pull_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_read(ssh_scp scp, void *buffer, size_t size); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_filename(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_request_get_permissions(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API size_t ssh_scp_request_get_size(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API uint64_t ssh_scp_request_get_size64(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_warning(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len); + + +LIBSSH_API int ssh_get_random(void *where,int len,int strong); +LIBSSH_API int ssh_get_version(ssh_session session); +LIBSSH_API int ssh_get_status(ssh_session session); +LIBSSH_API int ssh_get_poll_flags(ssh_session session); +LIBSSH_API int ssh_init(void); +LIBSSH_API int ssh_is_blocking(ssh_session session); +LIBSSH_API int ssh_is_connected(ssh_session session); + +/* KNOWN HOSTS */ +LIBSSH_API void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry); +#define SSH_KNOWNHOSTS_ENTRY_FREE(e) do { \ + if ((e) != NULL) { \ + ssh_knownhosts_entry_free(e); \ + e = NULL; \ + } \ +} while(0) + +LIBSSH_API int ssh_known_hosts_parse_line(const char *host, + const char *line, + struct ssh_knownhosts_entry **entry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session); + +LIBSSH_API int ssh_session_export_known_hosts_entry(ssh_session session, + char **pentry_string); +LIBSSH_API int ssh_session_update_known_hosts(ssh_session session); + +LIBSSH_API enum ssh_known_hosts_e ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session); + +/* LOGGING */ +LIBSSH_API int ssh_set_log_level(int level); +LIBSSH_API int ssh_get_log_level(void); +LIBSSH_API void *ssh_get_log_userdata(void); +LIBSSH_API int ssh_set_log_userdata(void *data); +LIBSSH_API void ssh_vlog(int verbosity, + const char *function, + const char *format, + va_list *va) PRINTF_ATTRIBUTE(3, 0); +LIBSSH_API void _ssh_log(int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +/* legacy */ +SSH_DEPRECATED LIBSSH_API void ssh_log(ssh_session session, + int prioriry, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +LIBSSH_API ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_channel chan); +LIBSSH_API int ssh_message_channel_request_reply_success(ssh_message msg); +#define SSH_MESSAGE_FREE(x) \ + do { if ((x) != NULL) { ssh_message_free(x); (x) = NULL; } } while(0) +LIBSSH_API void ssh_message_free(ssh_message msg); +LIBSSH_API ssh_message ssh_message_get(ssh_session session); +LIBSSH_API int ssh_message_subtype(ssh_message msg); +LIBSSH_API int ssh_message_type(ssh_message msg); +LIBSSH_API int ssh_mkdir (const char *pathname, mode_t mode); +LIBSSH_API ssh_session ssh_new(void); + +LIBSSH_API int ssh_options_copy(ssh_session src, ssh_session *dest); +LIBSSH_API int ssh_options_getopt(ssh_session session, int *argcptr, char **argv); +LIBSSH_API int ssh_options_parse_config(ssh_session session, const char *filename); +LIBSSH_API int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value); +LIBSSH_API int ssh_options_get(ssh_session session, enum ssh_options_e type, + char **value); +LIBSSH_API int ssh_options_get_port(ssh_session session, unsigned int * port_target); +LIBSSH_API int ssh_pcap_file_close(ssh_pcap_file pcap); +LIBSSH_API void ssh_pcap_file_free(ssh_pcap_file pcap); +LIBSSH_API ssh_pcap_file ssh_pcap_file_new(void); +LIBSSH_API int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename); + +/** + * @addtogroup libssh_auth + * + * @{ + */ + +/** + * @brief SSH authentication callback for password and publickey auth. + * + * @param prompt Prompt to be displayed. + * @param buf Buffer to save the password. You should null-terminate it. + * @param len Length of the buffer. + * @param echo Enable or disable the echo of what you type. + * @param verify Should the password be verified? + * @param userdata Userdata to be passed to the callback function. Useful + * for GUI applications. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_auth_callback) (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata); + +/** @} */ + +enum ssh_file_format_e { + SSH_FILE_FORMAT_DEFAULT = 0, + SSH_FILE_FORMAT_OPENSSH, + SSH_FILE_FORMAT_PEM, +}; + +LIBSSH_API ssh_key ssh_key_new(void); +#define SSH_KEY_FREE(x) \ + do { if ((x) != NULL) { ssh_key_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_key_free (ssh_key key); +LIBSSH_API enum ssh_keytypes_e ssh_key_type(const ssh_key key); +LIBSSH_API const char *ssh_key_type_to_char(enum ssh_keytypes_e type); +LIBSSH_API enum ssh_keytypes_e ssh_key_type_from_name(const char *name); +LIBSSH_API int ssh_key_is_public(const ssh_key k); +LIBSSH_API int ssh_key_is_private(const ssh_key k); +LIBSSH_API int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); +LIBSSH_API ssh_key ssh_key_dup(const ssh_key key); +LIBSSH_API uint32_t ssh_key_get_sk_flags(const ssh_key key); +LIBSSH_API ssh_string ssh_key_get_sk_application(const ssh_key key); +LIBSSH_API ssh_string ssh_key_get_sk_user_id(const ssh_key key); + +SSH_DEPRECATED LIBSSH_API int +ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey); + +LIBSSH_API int ssh_pki_generate_key(enum ssh_keytypes_e type, + ssh_pki_ctx pki_context, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key); +LIBSSH_API int +ssh_pki_export_privkey_base64_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key, + enum ssh_file_format_e format); +LIBSSH_API int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename); +LIBSSH_API int +ssh_pki_export_privkey_file_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename, + enum ssh_file_format_e format); + +LIBSSH_API int ssh_pki_copy_cert_to_privkey(const ssh_key cert_key, + ssh_key privkey); + +LIBSSH_API int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_pubkey_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_import_cert_base64(const char *b64_cert, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_cert_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key); +LIBSSH_API int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename); + +LIBSSH_API const char *ssh_pki_key_ecdsa_name(const ssh_key key); + +LIBSSH_API char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len); +LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char *hash, size_t len); +LIBSSH_API int ssh_send_ignore (ssh_session session, const char *data); +LIBSSH_API int ssh_send_debug (ssh_session session, const char *message, int always_display); +LIBSSH_API void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds); +LIBSSH_API int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout); +LIBSSH_API int ssh_service_request(ssh_session session, const char *service); +LIBSSH_API int ssh_set_agent_channel(ssh_session session, ssh_channel channel); +LIBSSH_API int ssh_set_agent_socket(ssh_session session, socket_t fd); +LIBSSH_API void ssh_set_blocking(ssh_session session, int blocking); +LIBSSH_API void ssh_set_counters(ssh_session session, ssh_counter scounter, + ssh_counter rcounter); +LIBSSH_API void ssh_set_fd_except(ssh_session session); +LIBSSH_API void ssh_set_fd_toread(ssh_session session); +LIBSSH_API void ssh_set_fd_towrite(ssh_session session); +LIBSSH_API void ssh_silent_disconnect(ssh_session session); +LIBSSH_API int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile); + +/* USERAUTH */ +LIBSSH_API int ssh_userauth_none(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_list(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey); +LIBSSH_API int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey); +LIBSSH_API int ssh_userauth_agent(ssh_session session, + const char *username); +LIBSSH_API int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, + char** value); +LIBSSH_API int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase); +LIBSSH_API int ssh_userauth_password(ssh_session session, + const char *username, + const char *password); + +LIBSSH_API int ssh_userauth_kbdint(ssh_session session, const char *user, const char *submethods); +LIBSSH_API const char *ssh_userauth_kbdint_getinstruction(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getname(ssh_session session); +LIBSSH_API int ssh_userauth_kbdint_getnprompts(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo); +LIBSSH_API int ssh_userauth_kbdint_getnanswers(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i); +LIBSSH_API int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer); +LIBSSH_API int ssh_userauth_gssapi(ssh_session session); +LIBSSH_API int ssh_userauth_gssapi_keyex(ssh_session session); +LIBSSH_API const char *ssh_version(int req_version); + +LIBSSH_API void ssh_string_burn(ssh_string str); +LIBSSH_API ssh_string ssh_string_copy(ssh_string str); +LIBSSH_API void *ssh_string_data(ssh_string str); +LIBSSH_API int ssh_string_fill(ssh_string str, const void *data, size_t len); +#define SSH_STRING_FREE(x) \ + do { if ((x) != NULL) { ssh_string_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free(ssh_string str); +LIBSSH_API ssh_string ssh_string_from_char(const char *what); +LIBSSH_API ssh_string ssh_string_from_data(const void *data, size_t len); +LIBSSH_API size_t ssh_string_len(ssh_string str); +LIBSSH_API ssh_string ssh_string_new(size_t size); +LIBSSH_API const char *ssh_string_get_char(ssh_string str); +LIBSSH_API char *ssh_string_to_char(ssh_string str); +#define SSH_STRING_FREE_CHAR(x) \ + do { if ((x) != NULL) { ssh_string_free_char(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free_char(char *s); +LIBSSH_API int ssh_string_cmp(ssh_string s1, ssh_string s2); + +LIBSSH_API int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, + int verify); + + +typedef int (*ssh_event_callback)(socket_t fd, int revents, void *userdata); + +LIBSSH_API ssh_event ssh_event_new(void); +LIBSSH_API int ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata); +LIBSSH_API int ssh_event_add_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_add_connector(ssh_event event, ssh_connector connector); +LIBSSH_API int ssh_event_dopoll(ssh_event event, int timeout); +LIBSSH_API int ssh_event_remove_fd(ssh_event event, socket_t fd); +LIBSSH_API int ssh_event_remove_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_remove_connector(ssh_event event, ssh_connector connector); +LIBSSH_API void ssh_event_free(ssh_event event); +LIBSSH_API const char* ssh_get_clientbanner(ssh_session session); +LIBSSH_API const char* ssh_get_serverbanner(ssh_session session); +LIBSSH_API const char* ssh_get_kex_algo(ssh_session session); +LIBSSH_API bool ssh_session_kex_is_gss(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_in(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_out(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_in(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_out(ssh_session session); +LIBSSH_API const char *ssh_get_supported_methods(enum ssh_kex_types_e type); + +LIBSSH_API ssh_buffer ssh_buffer_new(void); +LIBSSH_API void ssh_buffer_free(ssh_buffer buffer); +#define SSH_BUFFER_FREE(x) \ + do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0) +LIBSSH_API int ssh_buffer_reinit(ssh_buffer buffer); +LIBSSH_API int ssh_buffer_add_data(ssh_buffer buffer, const void *data, uint32_t len); +LIBSSH_API uint32_t ssh_buffer_get_data(ssh_buffer buffer, void *data, uint32_t requestedlen); +LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer); +LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer); +LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message); + +/* SSHSIG hashes data independently from the key used, so we use a new enum + to avoid confusion. See + https://gitlab.com/jas/ietf-sshsig-format/-/blob/cc70a225cbd695d5a6f20aaebdb4b92b0818e43a/ietf-sshsig-format.md#L137 + */ +enum sshsig_digest_e { + SSHSIG_DIGEST_SHA2_256 = 0, + SSHSIG_DIGEST_SHA2_512 = 1, +}; + +LIBSSH_API int sshsig_sign(const void *data, + size_t data_length, + ssh_key privkey, + ssh_pki_ctx pki_context, + const char *sig_namespace, + enum sshsig_digest_e hash_alg, + char **signature); +LIBSSH_API int sshsig_verify(const void *data, + size_t data_length, + const char *signature, + const char *sig_namespace, + ssh_key *sign_key); + +/* PKI context API */ + +enum ssh_pki_options_e { + SSH_PKI_OPTION_RSA_KEY_SIZE, + + /* Security Key options */ + SSH_PKI_OPTION_SK_APPLICATION, + SSH_PKI_OPTION_SK_FLAGS, + SSH_PKI_OPTION_SK_USER_ID, + SSH_PKI_OPTION_SK_CHALLENGE, + SSH_PKI_OPTION_SK_CALLBACKS, +}; + +/* FIDO2/U2F Operation Flags */ + +/** Requires user presence confirmation (tap/touch) */ +#ifndef SSH_SK_USER_PRESENCE_REQD +#define SSH_SK_USER_PRESENCE_REQD 0x01 +#endif + +/** Requires user verification (PIN/biometric) - FIDO2 only */ +#ifndef SSH_SK_USER_VERIFICATION_REQD +#define SSH_SK_USER_VERIFICATION_REQD 0x04 +#endif + +/** Force resident key enrollment even if a resident key with given user ID + * already exists - FIDO2 only */ +#ifndef SSH_SK_FORCE_OPERATION +#define SSH_SK_FORCE_OPERATION 0x10 +#endif + +/** Create/use resident key stored on authenticator - FIDO2 only */ +#ifndef SSH_SK_RESIDENT_KEY +#define SSH_SK_RESIDENT_KEY 0x20 +#endif + +LIBSSH_API ssh_pki_ctx ssh_pki_ctx_new(void); + +LIBSSH_API int ssh_pki_ctx_options_set(ssh_pki_ctx context, + enum ssh_pki_options_e option, + const void *value); + +LIBSSH_API int ssh_pki_ctx_set_sk_pin_callback(ssh_pki_ctx context, + ssh_auth_callback pin_callback, + void *userdata); + +#define SSH_SK_OPTION_NAME_DEVICE_PATH "device" +#define SSH_SK_OPTION_NAME_USER_ID "user" + +LIBSSH_API int ssh_pki_ctx_sk_callbacks_option_set(ssh_pki_ctx context, + const char *name, + const char *value, + bool required); + +LIBSSH_API int ssh_pki_ctx_sk_callbacks_options_clear(ssh_pki_ctx context); + +LIBSSH_API int +ssh_pki_ctx_get_sk_attestation_buffer(const struct ssh_pki_ctx_struct *context, + ssh_buffer *attestation_buffer); + +LIBSSH_API void ssh_pki_ctx_free(ssh_pki_ctx context); + +#define SSH_PKI_CTX_FREE(x) \ + do { \ + if ((x) != NULL) { \ + ssh_pki_ctx_free(x); \ + x = NULL; \ + } \ + } while (0) + +/* Security key resident keys API */ + +LIBSSH_API int +ssh_sk_resident_keys_load(const struct ssh_pki_ctx_struct *pki_context, + ssh_key **resident_keys_result, + size_t *num_keys_found_result); + +#ifndef LIBSSH_LEGACY_0_4 +#include "libssh/legacy.h" +#endif + +#ifdef __cplusplus +} +#endif +#endif /* _LIBSSH_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libssh_version.h b/src/libs/libssh-0.12.2/include/libssh/libssh_version.h new file mode 100644 index 000000000000..453b38cd65ac --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libssh_version.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Heiko Thiery + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_VERSION_H +#define _LIBSSH_VERSION_H + +/* libssh version macros */ +#define SSH_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define SSH_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define SSH_VERSION(a, b, c) SSH_VERSION_DOT(a, b, c) + +/* libssh version */ +#define LIBSSH_VERSION_MAJOR 0 +#define LIBSSH_VERSION_MINOR 12 +#define LIBSSH_VERSION_MICRO 2 + +#define LIBSSH_VERSION_INT SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) +#define LIBSSH_VERSION SSH_VERSION(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) + +#endif /* _LIBSSH_VERSION_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libssh_version.h.cmake b/src/libs/libssh-0.12.2/include/libssh/libssh_version.h.cmake new file mode 100644 index 000000000000..464fa14d0b6c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libssh_version.h.cmake @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Heiko Thiery + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_VERSION_H +#define _LIBSSH_VERSION_H + +/* libssh version macros */ +#define SSH_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define SSH_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define SSH_VERSION(a, b, c) SSH_VERSION_DOT(a, b, c) + +/* libssh version */ +#define LIBSSH_VERSION_MAJOR @libssh_VERSION_MAJOR@ +#define LIBSSH_VERSION_MINOR @libssh_VERSION_MINOR@ +#define LIBSSH_VERSION_MICRO @libssh_VERSION_PATCH@ + +#define LIBSSH_VERSION_INT SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) +#define LIBSSH_VERSION SSH_VERSION(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) + +#endif /* _LIBSSH_VERSION_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libsshpp.hpp b/src/libs/libssh-0.12.2/include/libssh/libsshpp.hpp new file mode 100644 index 000000000000..553a77771ca2 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libsshpp.hpp @@ -0,0 +1,698 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBSSHPP_HPP_ +#define LIBSSHPP_HPP_ + +/** + * @defgroup ssh_cpp The libssh C++ wrapper + * + * The C++ bindings for libssh are completely embedded in a single .hpp file, and + * this for two reasons: + * - C++ is hard to keep binary compatible, C is easy. We try to keep libssh C version + * as much as possible binary compatible between releases, while this would be hard for + * C++. If you compile your program with these headers, you will only link to the C version + * of libssh which will be kept ABI compatible. No need to recompile your C++ program + * each time a new binary-compatible version of libssh is out + * - Most of the functions in this file are really short and are probably worth the "inline" + * linking mode, which the compiler can decide to do in some case. There would be nearly no + * performance penalty of using the wrapper rather than native calls. + * + * Please visit the documentation of ssh::Session and ssh::Channel + * @see ssh::Session + * @see ssh::Channel + * + * If you wish not to use C++ exceptions, please define SSH_NO_CPP_EXCEPTIONS: + * @code + * #define SSH_NO_CPP_EXCEPTIONS + * #include + * @endcode + * All functions will then return SSH_ERROR in case of error. + * @{ + */ + +/* do not use deprecated functions */ +#define LIBSSH_LEGACY_0_4 + +#include +#include +#include +#include +#include +#include + +namespace ssh { + +class Channel; +/** Some people do not like C++ exceptions. With this define, we give + * the choice to use or not exceptions. + * @brief if defined, disable C++ exceptions for libssh c++ wrapper + */ +#ifndef SSH_NO_CPP_EXCEPTIONS + +/** @brief This class describes a SSH Exception object. This object can be thrown + * by several SSH functions that interact with the network, and may fail because of + * socket, protocol or memory errors. + */ +class SshException{ +public: + SshException(ssh_session csession){ + code=ssh_get_error_code(csession); + description=std::string(ssh_get_error(csession)); + } + SshException(const SshException &e){ + code=e.code; + description=e.description; + } + /** @brief returns the Error code + * @returns SSH_FATAL Fatal error happened (not recoverable) + * @returns SSH_REQUEST_DENIED Request was denied by remote host + * @see ssh_get_error_code + */ + int getCode(){ + return code; + } + /** @brief returns the error message of the last exception + * @returns pointer to a c string containing the description of error + * @see ssh_get_error + */ + std::string getError(){ + return description; + } +private: + int code; + std::string description; +}; + +/** @internal + * @brief Macro to throw exception if there was an error + */ +#define ssh_throw(x) if((x)==SSH_ERROR) throw SshException(getCSession()) +#define ssh_throw_null(CSession,x) if((x)==NULL) throw SshException(CSession) +#define void_throwable void +#define return_throwable return + +#else + +/* No exception at all. All functions will return an error code instead + * of an exception + */ +#define ssh_throw(x) if((x)==SSH_ERROR) return SSH_ERROR +#define ssh_throw_null(CSession,x) if((x)==NULL) return NULL +#define void_throwable int +#define return_throwable return SSH_OK +#endif + +/** + * The ssh::Session class contains the state of a SSH connection. + */ +class Session { + friend class Channel; +public: + Session(){ + c_session=ssh_new(); + } + ~Session(){ + ssh_free(c_session); + c_session=NULL; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option cstring containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, const char *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option long integer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, long int option){ + ssh_throw(ssh_options_set(c_session,type,&option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option void pointer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, void *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief connects to the remote host + * @throws SshException on error + * @see ssh_connect + */ + void_throwable connect(){ + int ret=ssh_connect(c_session); + ssh_throw(ret); + return_throwable; + } + /** @brief Authenticates automatically using public key + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_autopubkey + */ + int userauthPublickeyAuto(void){ + int ret=ssh_userauth_publickey_auto(c_session, NULL, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the "none" method. Prefer using autopubkey if + * possible. + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_none + * @see Session::userauthAutoPubkey + */ + int userauthNone(){ + int ret=ssh_userauth_none(c_session,NULL); + ssh_throw(ret); + return ret; + } + + /** + * @brief Authenticate through the "keyboard-interactive" method. + * + * @param[in] username The username to authenticate. You can specify NULL if + * ssh_option_set_username() has been used. You cannot + * try two different logins in a row. + * + * @param[in] submethods Undocumented. Set it to NULL. + * + * @throws SshException on error + * + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED, + * SSH_AUTH_ERROR, SSH_AUTH_INFO, SSH_AUTH_AGAIN + * + * @see ssh_userauth_kbdint + */ + int userauthKbdint(const char* username, const char* submethods){ + int ret = ssh_userauth_kbdint(c_session, username, submethods); + ssh_throw(ret); + return ret; + } + + /** @brief Get the number of prompts (questions) the server has given. + * @returns The number of prompts. + * @see ssh_userauth_kbdint_getnprompts + */ + int userauthKbdintGetNPrompts(){ + return ssh_userauth_kbdint_getnprompts(c_session); + } + + /** + * @brief Set the answer for a question from a message block. + * + * @param[in] index The index number of the prompt. + * @param[in] answer The answer to give to the server. The answer MUST be + * encoded UTF-8. It is up to the server how to interpret + * the value and validate it. However, if you read the + * answer in some other encoding, you MUST convert it to + * UTF-8. + * + * @throws SshException on error + * + * @returns 0 on success, < 0 on error + * + * @see ssh_userauth_kbdint_setanswer + */ + int userauthKbdintSetAnswer(unsigned int index, const char *answer) + { + int ret = ssh_userauth_kbdint_setanswer(c_session, index, answer); + ssh_throw(ret); + return ret; + } + + + + /** @brief Authenticates using the password method. + * @param[in] password password to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_password + */ + int userauthPassword(const char *password){ + int ret=ssh_userauth_password(c_session,NULL,password); + ssh_throw(ret); + return ret; + } + /** @brief Try to authenticate using the publickey method. + * @param[in] pubkey public key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS if the pubkey is accepted, + * @returns SSH_AUTH_DENIED if the pubkey is denied + * @see ssh_userauth_try_pubkey + */ + int userauthTryPublickey(ssh_key pubkey){ + int ret=ssh_userauth_try_publickey(c_session, NULL, pubkey); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the publickey method. + * @param[in] privkey private key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_pubkey + */ + int userauthPublickey(ssh_key privkey){ + int ret=ssh_userauth_publickey(c_session, NULL, privkey); + ssh_throw(ret); + return ret; + } + + /** @brief Returns the available authentication methods from the server + * @throws SshException on error + * @returns Bitfield of available methods. + * @see ssh_userauth_list + */ + int getAuthList(){ + int ret=ssh_userauth_list(c_session, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Disconnects from the SSH server and closes connection + * @see ssh_disconnect + */ + void disconnect(){ + ssh_disconnect(c_session); + } + /** @brief Returns the disconnect message from the server, if any + * @returns pointer to the message, or NULL. Do not attempt to free + * the pointer. + */ + const char *getDisconnectMessage(){ + const char *msg=ssh_get_disconnect_message(c_session); + return msg; + } + /** @internal + * @brief gets error message + */ + const char *getError(){ + return ssh_get_error(c_session); + } + /** @internal + * @brief returns error code + */ + int getErrorCode(){ + return ssh_get_error_code(c_session); + } + /** @brief returns the file descriptor used for the communication + * @returns the file descriptor + * @warning if a proxycommand is used, this function will only return + * one of the two file descriptors being used + * @see ssh_get_fd + */ + socket_t getSocket(){ + return ssh_get_fd(c_session); + } + /** @brief gets the Issue banner from the ssh server + * @returns the issue banner. This is generally a MOTD from server + * @see ssh_get_issue_banner + */ + std::string getIssueBanner(){ + char *banner = ssh_get_issue_banner(c_session); + std::string ret = ""; + if (banner != NULL) { + ret = std::string(banner); + ::free(banner); + } + return ret; + } + /** @brief returns the OpenSSH version (server) if possible + * @returns openssh version code + * @see ssh_get_openssh_version + */ + int getOpensshVersion(){ + return ssh_get_openssh_version(c_session); + } + /** @brief returns the version of the SSH protocol being used + * @returns the SSH protocol version + * @see ssh_get_version + */ + int getVersion(){ + return ssh_get_version(c_session); + } + /** @brief verifies that the server is known + * @throws SshException on error + * @returns Integer value depending on the knowledge of the + * server key + * @see ssh_session_update_known_hosts + */ + int isServerKnown(){ + int state = ssh_session_is_known_server(c_session); + ssh_throw(state); + return state; + } + void log(int priority, const char *format, ...){ + va_list va; + + va_start(va, format); + ssh_vlog(priority, "libsshpp", format, &va); + va_end(va); + } + + /** @brief copies options from a session to another + * @throws SshException on error + * @see ssh_options_copy + */ + void_throwable optionsCopy(const Session &source){ + ssh_throw(ssh_options_copy(source.c_session,&c_session)); + return_throwable; + } + /** @brief parses a configuration file for options + * @throws SshException on error + * @param[in] file configuration file name + * @see ssh_options_parse_config + */ + void_throwable optionsParseConfig(const char *file){ + ssh_throw(ssh_options_parse_config(c_session,file)); + return_throwable; + } + /** @brief silently disconnect from remote host + * @see ssh_silent_disconnect + */ + void silentDisconnect(){ + ssh_silent_disconnect(c_session); + } + /** @brief Writes the known host file with current + * host key + * @throws SshException on error + * @see ssh_write_knownhost + */ + int writeKnownhost(){ + int ret = ssh_session_update_known_hosts(c_session); + ssh_throw(ret); + return ret; + } + + /** @brief accept an incoming forward connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the forward connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_forward_accept + * @see Session::listenForward + */ + inline Channel *acceptForward(int timeout_ms); + /* implemented outside the class due Channel references */ + + void_throwable cancelForward(const char *address, int port){ + int err=ssh_channel_cancel_forward(c_session, address, port); + ssh_throw(err); + return_throwable; + } + + void_throwable listenForward(const char *address, int port, + int &boundport){ + int err=ssh_channel_listen_forward(c_session, address, port, &boundport); + ssh_throw(err); + return_throwable; + } + + ssh_session getCSession(){ + return c_session; + } + +protected: + ssh_session c_session; + +private: + /* No copy constructor, no = operator */ + Session(const Session &); + Session& operator=(const Session &); +}; + +/** @brief the ssh::Channel class describes the state of an SSH + * channel. + * @see ssh_channel + */ +class Channel { + friend class Session; +public: + Channel(Session &ssh_session){ + channel = ssh_channel_new(ssh_session.getCSession()); + this->session = &ssh_session; + } + ~Channel(){ + ssh_channel_free(channel); + channel=NULL; + } + + /** @brief accept an incoming X11 connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the X11 connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_accept_x11 + * @see Channel::requestX11 + */ + Channel *acceptX11(int timeout_ms){ + ssh_channel x11chan = ssh_channel_accept_x11(channel,timeout_ms); + ssh_throw_null(getCSession(),x11chan); + Channel *newchan = new Channel(getSession(),x11chan); + return newchan; + } + /** @brief change the size of a pseudoterminal + * @param[in] cols number of columns + * @param[in] rows number of rows + * @throws SshException on error + * @see ssh_channel_change_pty_size + */ + void_throwable changePtySize(int cols, int rows){ + int err=ssh_channel_change_pty_size(channel,cols,rows); + ssh_throw(err); + return_throwable; + } + + /** @brief closes a channel + * @throws SshException on error + * @see ssh_channel_close + */ + void_throwable close(){ + ssh_throw(ssh_channel_close(channel)); + return_throwable; + } + + /* + * @deprecated Please use getExitState() + */ + int getExitStatus() { + uint32_t exit_status = (uint32_t)-1; + ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + return exit_status; + } + void_throwable getExitState(uint32_t & pexit_code, + char **pexit_signal, + int & pcore_dumped) { + ssh_throw(ssh_channel_get_exit_state(channel, + &pexit_code, + pexit_signal, + &pcore_dumped)); + return_throwable; + } + Session &getSession(){ + return *session; + } + /** @brief returns true if channel is in closed state + * @see ssh_channel_is_closed + */ + bool isClosed(){ + return ssh_channel_is_closed(channel) != 0; + } + /** @brief returns true if channel is in EOF state + * @see ssh_channel_is_eof + */ + bool isEof(){ + return ssh_channel_is_eof(channel) != 0; + } + /** @brief returns true if channel is in open state + * @see ssh_channel_is_open + */ + bool isOpen(){ + return ssh_channel_is_open(channel) != 0; + } + int openForward(const char *remotehost, int remoteport, + const char *sourcehost, int localport=0){ + int err=ssh_channel_open_forward(channel,remotehost,remoteport, + sourcehost, localport); + ssh_throw(err); + return err; + } + /* TODO: completely remove this ? */ + void_throwable openSession(){ + int err=ssh_channel_open_session(channel); + ssh_throw(err); + return_throwable; + } + int poll(bool is_stderr=false){ + int err=ssh_channel_poll(channel,is_stderr); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,false,-1); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count, int timeout){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,false,timeout); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count, bool is_stderr=false, int timeout=-1){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,is_stderr,timeout); + ssh_throw(err); + return err; + } + int readNonblocking(void *dest, size_t count, bool is_stderr=false){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_nonblocking(channel,dest,count,is_stderr); + ssh_throw(err); + return err; + } + void_throwable requestEnv(const char *name, const char *value){ + int err=ssh_channel_request_env(channel,name,value); + ssh_throw(err); + return_throwable; + } + + void_throwable requestExec(const char *cmd){ + int err=ssh_channel_request_exec(channel,cmd); + ssh_throw(err); + return_throwable; + } + void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0, + const unsigned char* modes=NULL, size_t modes_len=0){ + int err; + if(term != NULL && cols != 0 && rows != 0 && modes != NULL) + err=ssh_channel_request_pty_size_modes(channel,term,cols,rows,modes,modes_len); + else if(term != NULL && cols != 0 && rows != 0) + err=ssh_channel_request_pty_size(channel,term,cols,rows); + else + err=ssh_channel_request_pty(channel); + ssh_throw(err); + return_throwable; + } + + void_throwable requestShell(){ + int err=ssh_channel_request_shell(channel); + ssh_throw(err); + return_throwable; + } + void_throwable requestSendSignal(const char *signum){ + int err=ssh_channel_request_send_signal(channel, signum); + ssh_throw(err); + return_throwable; + } + void_throwable requestSubsystem(const char *subsystem){ + int err=ssh_channel_request_subsystem(channel,subsystem); + ssh_throw(err); + return_throwable; + } + int requestX11(bool single_connection, + const char *protocol, const char *cookie, int screen_number){ + int err=ssh_channel_request_x11(channel,single_connection, + protocol, cookie, screen_number); + ssh_throw(err); + return err; + } + void_throwable sendEof(){ + int err=ssh_channel_send_eof(channel); + ssh_throw(err); + return_throwable; + } + /** @brief Writes on a channel + * @param data data to write. + * @param len number of bytes to write. + * @param is_stderr write should be done on the stderr channel (server only) + * @returns number of bytes written + * @throws SshException in case of error + * @see ssh_channel_write + * @see ssh_channel_write_stderr + */ + int write(const void *data, size_t len, bool is_stderr=false){ + int ret; + if(is_stderr){ + ret=ssh_channel_write_stderr(channel,data,len); + } else { + ret=ssh_channel_write(channel,data,len); + } + ssh_throw(ret); + return ret; + } + + ssh_session getCSession(){ + return session->getCSession(); + } + + ssh_channel getCChannel() { + return channel; + } + +protected: + Session *session; + ssh_channel channel; + +private: + Channel (Session &ssh_session, ssh_channel c_channel){ + this->channel=c_channel; + this->session = &ssh_session; + } + /* No copy and no = operator */ + Channel(const Channel &); + Channel &operator=(const Channel &); +}; + + +inline Channel *Session::acceptForward(int timeout_ms){ + ssh_channel forward = + ssh_channel_open_forward_port(c_session, timeout_ms, NULL, NULL, NULL); + ssh_throw_null(c_session,forward); + Channel *newchan = new Channel(*this,forward); + return newchan; + } + +} // namespace ssh + +/** @} */ +#endif /* LIBSSHPP_HPP_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/messages.h b/src/libs/libssh-0.12.2/include/libssh/messages.h new file mode 100644 index 000000000000..9dd6b06cae22 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/messages.h @@ -0,0 +1,116 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MESSAGES_H_ +#define MESSAGES_H_ + +#include "config.h" + +struct ssh_auth_request { + char *username; + int method; + char *password; + struct ssh_key_struct *pubkey; + struct ssh_key_struct *server_pubkey; + char *sigtype; + enum ssh_publickey_state_e signature_state; + char kbdint_response; +}; + +struct ssh_channel_request_open { + int type; + uint32_t sender; + uint32_t window; + uint32_t packet_size; + char *originator; + uint16_t originator_port; + char *destination; + uint16_t destination_port; +}; + +struct ssh_service_request { + char *service; +}; + +struct ssh_global_request { + int type; + uint8_t want_reply; + char *bind_address; + uint16_t bind_port; +}; + +struct ssh_channel_request { + int type; + ssh_channel channel; + uint8_t want_reply; + /* pty-req type specifics */ + char *TERM; + uint32_t width; + uint32_t height; + uint32_t pxwidth; + uint32_t pxheight; + ssh_string modes; + + /* env type request */ + char *var_name; + char *var_value; + /* exec type request */ + char *command; + /* subsystem */ + char *subsystem; + + /* X11 */ + uint8_t x11_single_connection; + char *x11_auth_protocol; + char *x11_auth_cookie; + uint32_t x11_screen_number; +}; + +struct ssh_message_struct { + ssh_session session; + int type; + struct ssh_auth_request auth_request; + struct ssh_channel_request_open channel_request_open; + struct ssh_channel_request channel_request; + struct ssh_service_request service_request; + struct ssh_global_request global_request; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_channel_open); +SSH_PACKET_CALLBACK(ssh_packet_global_request); + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_service_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_request); +#endif /* WITH_SERVER */ + +int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, + const char *request, uint8_t want_reply); +ssh_message ssh_message_pop_head(ssh_session session); + +#ifdef __cplusplus +} +#endif + +#endif /* MESSAGES_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/misc.h b/src/libs/libssh-0.12.2/include/libssh/misc.h new file mode 100644 index 000000000000..781a540fd1a5 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/misc.h @@ -0,0 +1,152 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MISC_H_ +#define MISC_H_ + +#ifdef _WIN32 + +# ifdef _MSC_VER +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ +# endif /* _MSC_VER */ + +#else +#include +#include +#endif /* _WIN32 */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* in misc.c */ +/* gets the user home dir. */ +char *ssh_get_user_home_dir(ssh_session session); +char *ssh_get_local_username(void); +char *ssh_get_local_hostname(void); +int ssh_file_readaccess_ok(const char *file); +int ssh_dir_writeable(const char *path); + +char *ssh_path_expand_tilde(const char *d); +char *ssh_path_expand_escape(ssh_session session, const char *s); +int ssh_analyze_banner(ssh_session session, int server); +int ssh_is_ipaddr_v4(const char *str); +int ssh_is_ipaddr(const char *str); + +/* list processing */ + +struct ssh_list { + struct ssh_iterator *root; + struct ssh_iterator *end; +}; + +struct ssh_iterator { + struct ssh_iterator *next; + const void *data; +}; + +struct ssh_jump_info_struct { + char *hostname; + char *username; + /** + * Port number of the jump host, in the range 1-65535. Zero means the + * ProxyJump specification did not give a port, in which case the jump + * host's own configuration (or the connection default) supplies it. + */ + int port; +}; + +struct ssh_timestamp { + long seconds; + long useconds; +}; + +enum ssh_quote_state_e { + NO_QUOTE, + SINGLE_QUOTE, + DOUBLE_QUOTE +}; + +struct ssh_list *ssh_list_new(void); +void ssh_list_free(struct ssh_list *list); +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list); +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value); +size_t ssh_list_count(const struct ssh_list *list); +int ssh_list_append(struct ssh_list *list, const void *data); +int ssh_list_prepend(struct ssh_list *list, const void *data); +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator); +char *ssh_lowercase(const char* str); +char *ssh_hostport(const char *host, int port); + +const void *_ssh_list_pop_head(struct ssh_list *list); + +#define ssh_iterator_value(type, iterator)\ + ((type)((iterator)->data)) + +/** @brief fetch the head element of a list and remove it from list + * @param type type of the element to return + * @param ssh_list the ssh_list to use + * @return the first element of the list, or NULL if the list is empty + */ +#define ssh_list_pop_head(type, ssh_list)\ + ((type)_ssh_list_pop_head(ssh_list)) + +#define SSH_LIST_FREE(x) \ + do { if ((x) != NULL) { ssh_list_free(x); (x) = NULL; } } while(0) + +int ssh_make_milliseconds(unsigned long sec, unsigned long usec); +void ssh_timestamp_init(struct ssh_timestamp *ts); +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout); +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout); + +void uint64_inc(unsigned char *counter); + +void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len); + +int ssh_mkdirs(const char *pathname, mode_t mode); + +int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len); +int ssh_newline_vis(const char *string, char *buf, size_t buf_len); +int ssh_tmpname(char *name); + +char *ssh_strreplace(const char *src, const char *pattern, const char *repl); + +ssize_t ssh_readn(int fd, void *buf, size_t nbytes); +ssize_t ssh_writen(int fd, const void *buf, size_t nbytes); + +int ssh_check_hostname_syntax(const char *hostname); +int ssh_check_username_syntax(const char *username); + +void ssh_proxyjumps_free(struct ssh_list *proxy_jump_list); +bool ssh_libssh_proxy_jumps(void); + +FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); + +#ifdef __cplusplus +} +#endif + +#endif /* MISC_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/mlkem.h b/src/libs/libssh-0.12.2/include/libssh/mlkem.h new file mode 100644 index 000000000000..2b76a760a744 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/mlkem.h @@ -0,0 +1,73 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Pavol Žáčik + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef MLKEM_H_ +#define MLKEM_H_ + +#include "libssh/crypto.h" +#include "libssh/libssh.h" +#include "libssh/session.h" + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct mlkem_type_info { + size_t pubkey_size; + size_t ciphertext_size; +#ifdef HAVE_GCRYPT_MLKEM + size_t privkey_size; + enum gcry_kem_algos alg; +#elif defined(HAVE_OPENSSL_MLKEM) + const char *name; +#else + size_t privkey_size; +#endif +}; + +extern const struct mlkem_type_info MLKEM768_INFO; +#ifdef HAVE_MLKEM1024 +extern const struct mlkem_type_info MLKEM1024_INFO; +#endif + +#define MLKEM_SHARED_SECRET_SIZE 32 + +typedef unsigned char ssh_mlkem_shared_secret[MLKEM_SHARED_SECRET_SIZE]; + +const struct mlkem_type_info * +kex_type_to_mlkem_info(enum ssh_key_exchange_e kex_type); + +int ssh_mlkem_init(ssh_session session); + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret); + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret); + +#ifdef __cplusplus +} +#endif + +#endif /* MLKEM_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/mlkem_native.h b/src/libs/libssh-0.12.2/include/libssh/mlkem_native.h new file mode 100644 index 000000000000..d5fd8334f672 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/mlkem_native.h @@ -0,0 +1,127 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef MLKEM_NATIVE_H_ +#define MLKEM_NATIVE_H_ + +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +A monomorphic instance of libcrux_ml_kem.types.MlKemPrivateKey +with const generics +- $2400size_t +*/ +typedef struct libcrux_ml_kem_types_MlKemPrivateKey_d9_s { + uint8_t value[2400U]; +} libcrux_ml_kem_types_MlKemPrivateKey_d9; + +/** +A monomorphic instance of libcrux_ml_kem.types.MlKemPublicKey +with const generics +- $1184size_t +*/ +typedef struct libcrux_ml_kem_types_MlKemPublicKey_30_s { + uint8_t value[1184U]; +} libcrux_ml_kem_types_MlKemPublicKey_30; + +typedef struct libcrux_ml_kem_mlkem768_MlKem768KeyPair_s { + libcrux_ml_kem_types_MlKemPrivateKey_d9 sk; + libcrux_ml_kem_types_MlKemPublicKey_30 pk; +} libcrux_ml_kem_mlkem768_MlKem768KeyPair; + +typedef struct libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s { + uint8_t value[1088U]; +} libcrux_ml_kem_mlkem768_MlKem768Ciphertext; + +/** +A monomorphic instance of K. +with types libcrux_ml_kem_types_MlKemCiphertext[[$1088size_t]], +uint8_t[32size_t] + +*/ +typedef struct tuple_c2_s { + libcrux_ml_kem_mlkem768_MlKem768Ciphertext fst; + uint8_t snd[32U]; +} tuple_c2; + +/** + Generate ML-KEM 768 Key Pair +*/ +libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_mlkem768_portable_generate_key_pair(uint8_t randomness[64U]); + +/** + Validate a public key. + + Returns `true` if valid, and `false` otherwise. +*/ +bool libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key); + +/** + Encapsulate ML-KEM 768 + + Generates an ([`MlKem768Ciphertext`], [`MlKemSharedSecret`]) tuple. + The input is a reference to an [`MlKem768PublicKey`] and [`SHARED_SECRET_SIZE`] + bytes of `randomness`. +*/ +tuple_c2 libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]); + +/** + Decapsulate ML-KEM 768 + + Generates an [`MlKemSharedSecret`]. + The input is a reference to an [`MlKem768PrivateKey`] and an + [`MlKem768Ciphertext`]. +*/ +void libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, + uint8_t ret[32U]); + +/* rename some types to be a bit more ergonomic */ +#define libcrux_mlkem768_keypair libcrux_ml_kem_mlkem768_MlKem768KeyPair_s +#define libcrux_mlkem768_pk libcrux_ml_kem_types_MlKemPublicKey_30_s +#define libcrux_mlkem768_sk libcrux_ml_kem_types_MlKemPrivateKey_d9_s +#define libcrux_mlkem768_ciphertext libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s +#define libcrux_mlkem768_enc_result tuple_c2_s +/* defines for PRNG inputs */ +#define LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN 64U +#define LIBCRUX_ML_KEM_ENC_PRNG_LEN 32 + +#ifdef __cplusplus +} +#endif + +#endif /* MLKEM_NATIVE_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/options.h b/src/libs/libssh-0.12.2/include/libssh/options.h new file mode 100644 index 000000000000..63b207fa9f4a --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/options.h @@ -0,0 +1,43 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _OPTIONS_H +#define _OPTIONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_config_parse(ssh_session session, FILE *fp, bool global); +int ssh_config_parse_file(ssh_session session, const char *filename); +int ssh_config_parse_string(ssh_session session, const char *input); +int ssh_options_set_algo(ssh_session session, + enum ssh_kex_types_e algo, + const char *list, + char **place); +int ssh_options_apply(ssh_session session); + +char *ssh_options_get_algo(ssh_session session, enum ssh_kex_types_e algo); + +#ifdef __cplusplus +} +#endif + +#endif /* _OPTIONS_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/packet.h b/src/libs/libssh-0.12.2/include/libssh/packet.h new file mode 100644 index 000000000000..531d7e4bd40e --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/packet.h @@ -0,0 +1,101 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PACKET_H_ +#define PACKET_H_ + +#include "libssh/wrapper.h" + +struct ssh_socket_struct; + +/* this structure should go someday */ +typedef struct packet_struct { + int valid; + uint32_t len; + uint8_t type; +} PACKET; + +/** different state of packet reading. */ +enum ssh_packet_state_e { + /** Packet not initialized, must read the size of packet */ + PACKET_STATE_INIT, + /** Size was read, waiting for the rest of data */ + PACKET_STATE_SIZEREAD, + /** Full packet was read and callbacks are being called. Future packets + * should wait for the end of the callback. */ + PACKET_STATE_PROCESSING +}; + +enum ssh_packet_filter_result_e { + SSH_PACKET_UNKNOWN, + SSH_PACKET_ALLOWED, + SSH_PACKET_DENIED +}; + +int ssh_packet_send(ssh_session session); + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_unimplemented); +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback); +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback); +SSH_PACKET_CALLBACK(ssh_packet_debug_callback); +SSH_PACKET_CALLBACK(ssh_packet_dh_reply); +SSH_PACKET_CALLBACK(ssh_packet_newkeys); +SSH_PACKET_CALLBACK(ssh_packet_service_accept); +SSH_PACKET_CALLBACK(ssh_packet_ext_info); + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_kexdh_init); +#endif + +int ssh_packet_send_newkeys(ssh_session session); +int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum); +int ssh_packet_parse_type(ssh_session session); +//int packet_flush(ssh_session session, int enforce_blocking); + +size_t ssh_packet_socket_callback(const void *data, size_t len, void *user); +void ssh_packet_register_socket_callback(ssh_session session, struct ssh_socket_struct *s); +void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_set_default_callbacks(ssh_session session); +void ssh_packet_process(ssh_session session, uint8_t type); + +/* PACKET CRYPT */ +uint32_t ssh_packet_decrypt_len(ssh_session session, uint8_t *destination, uint8_t *source); +int ssh_packet_decrypt(ssh_session session, uint8_t *destination, uint8_t *source, + size_t start, size_t encrypted_size); +unsigned char *ssh_packet_encrypt(ssh_session session, + void *packet, + size_t len); +int ssh_packet_hmac_verify(ssh_session session, const void *data, size_t len, + unsigned char *mac, enum ssh_hmac_e type); +int ssh_packet_set_newkeys(ssh_session session, + enum ssh_crypto_direction_e direction); +struct ssh_crypto_struct *ssh_packet_get_current_crypto(ssh_session session, + enum ssh_crypto_direction_e direction); + +#ifdef __cplusplus +} +#endif + +#endif /* PACKET_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pcap.h b/src/libs/libssh-0.12.2/include/libssh/pcap.h new file mode 100644 index 000000000000..2a6c3c279c96 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pcap.h @@ -0,0 +1,53 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PCAP_H_ +#define PCAP_H_ + +#include "config.h" +#include "libssh/libssh.h" + +#ifdef WITH_PCAP +typedef struct ssh_pcap_context_struct* ssh_pcap_context; + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len); + +ssh_pcap_context ssh_pcap_context_new(ssh_session session); +void ssh_pcap_context_free(ssh_pcap_context ctx); + +enum ssh_pcap_direction{ + SSH_PCAP_DIR_IN, + SSH_PCAP_DIR_OUT +}; +void ssh_pcap_context_set_file(ssh_pcap_context, ssh_pcap_file); +int ssh_pcap_context_write(ssh_pcap_context,enum ssh_pcap_direction direction, void *data, + uint32_t len, uint32_t origlen); + + +#ifdef __cplusplus +} +#endif + +#endif /* WITH_PCAP */ +#endif /* PCAP_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pki.h b/src/libs/libssh-0.12.2/include/libssh/pki.h new file mode 100644 index 000000000000..e22c05f84446 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pki.h @@ -0,0 +1,218 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PKI_H_ +#define PKI_H_ + +#include +#include "libssh/priv.h" +#ifdef HAVE_OPENSSL_EC_H +#include +#endif +#ifdef HAVE_OPENSSL_ECDSA_H +#include +#endif +#ifdef HAVE_LIBCRYPTO +#include +#endif +#include "libssh/crypto.h" +#ifdef HAVE_LIBCRYPTO +/* If using OpenSSL implementation, define the signature length which would be + * defined in libssh/ed25519.h otherwise */ +#define ED25519_SIG_LEN 64 +#else +#include "libssh/ed25519.h" +#endif +/* This definition is used for both OpenSSL and internal implementations */ +#define ED25519_KEY_LEN 32 + +#define MAX_PUBKEY_SIZE 0x100000 /* 1M */ +#define MAX_PRIVKEY_SIZE 0x400000 /* 4M */ + +#define RSA_MIN_KEY_SIZE 1024 +#define RSA_MIN_FIPS_KEY_SIZE 2048 +#define RSA_DEFAULT_KEY_SIZE 3072 + +#define SSH_KEY_FLAG_EMPTY 0x0 +#define SSH_KEY_FLAG_PUBLIC 0x0001 +#define SSH_KEY_FLAG_PRIVATE 0x0002 +#define SSH_KEY_FLAG_PKCS11_URI 0x0004 + +/* Constants matching the Lightweight Secure Shell Signature Format */ +/* https://datatracker.ietf.org/doc/draft-josefsson-sshsig-format */ +#define SSHSIG_VERSION 0x01 +#define SSHSIG_MAGIC_PREAMBLE "SSHSIG" +#define SSHSIG_MAGIC_PREAMBLE_LEN (sizeof(SSHSIG_MAGIC_PREAMBLE) - 1) +#define SSHSIG_BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----" +#define SSHSIG_END_SIGNATURE "-----END SSH SIGNATURE-----" +#define SSHSIG_LINE_LENGTH 76 + +struct ssh_key_struct { + enum ssh_keytypes_e type; + int flags; + const char *type_c; /* Don't free it ! it is static */ + int ecdsa_nid; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t rsa; + gcry_sexp_t ecdsa; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *pk; + mbedtls_ecdsa_context *ecdsa; +#elif defined(HAVE_LIBCRYPTO) + /* This holds either ENGINE/PROVIDER key for PKCS#11 support + * or just key in high-level format */ + EVP_PKEY *key; + /* keep this around for FIPS mode so we can parse the public keys. We won't + * be able to use them nor use the private keys though */ + uint8_t *ed25519_pubkey; +#endif /* HAVE_LIBGCRYPT */ +#ifndef HAVE_LIBCRYPTO + ed25519_pubkey *ed25519_pubkey; + ed25519_privkey *ed25519_privkey; +#endif /* HAVE_LIBCRYPTO */ + ssh_string sk_application; + ssh_buffer cert; + enum ssh_keytypes_e cert_type; + + /* Security Key specific private data */ + uint8_t sk_flags; + ssh_string sk_key_handle; + ssh_string sk_reserved; + + /* Resident key specific metadata */ + ssh_string sk_user_id; +}; + +struct ssh_signature_struct { + enum ssh_keytypes_e type; + enum ssh_digest_e hash_type; + const char *type_c; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t rsa_sig; + gcry_sexp_t ecdsa_sig; +#elif defined(HAVE_LIBMBEDCRYPTO) + ssh_string rsa_sig; + struct mbedtls_ecdsa_sig ecdsa_sig; +#endif /* HAVE_LIBGCRYPT */ +#ifndef HAVE_LIBCRYPTO + ed25519_signature *ed25519_sig; +#endif /* HAVE_LIBGCRYPT */ + ssh_string raw_sig; + + /* Security Key specific additions */ + uint8_t sk_flags; + uint32_t sk_counter; +}; + +typedef struct ssh_signature_struct *ssh_signature; + +#ifdef __cplusplus +extern "C" { +#endif + +/* SSH Key Functions */ +void ssh_key_clean (ssh_key key); + +const char * +ssh_key_get_signature_algorithm(ssh_session session, + enum ssh_keytypes_e type); +enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name); +enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type); +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type); +enum ssh_digest_e ssh_key_hash_from_name(const char *name); + +#define is_ecdsa_key_type(t) \ + ((t) >= SSH_KEYTYPE_ECDSA_P256 && (t) <= SSH_KEYTYPE_ECDSA_P521) + +#define is_cert_type(kt)\ + ((kt) == SSH_KEYTYPE_RSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ECDSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ED25519_CERT01 ||\ + ((kt) >= SSH_KEYTYPE_ECDSA_P256_CERT01 &&\ + (kt) <= SSH_KEYTYPE_ED25519_CERT01)) + +#define is_sk_key_type(kt) \ + ((kt) == SSH_KEYTYPE_SK_ECDSA || (kt) == SSH_KEYTYPE_SK_ED25519 || \ + (kt) == SSH_KEYTYPE_SK_ECDSA_CERT01 || \ + (kt) == SSH_KEYTYPE_SK_ED25519_CERT01) + +/* SSH Signature Functions */ +ssh_signature ssh_signature_new(void); +void ssh_signature_free(ssh_signature sign); +#define SSH_SIGNATURE_FREE(x) \ + do { ssh_signature_free(x); x = NULL; } while(0) + +int ssh_pki_export_signature_blob(const ssh_signature sign, + ssh_string *sign_blob); +int ssh_pki_import_signature_blob(const ssh_string sig_blob, + const ssh_key pubkey, + ssh_signature *psig); +int ssh_pki_signature_verify(ssh_session session, + ssh_signature sig, + const ssh_key key, + const unsigned char *digest, + size_t dlen); + +/* SSH Public Key Functions */ +int ssh_pki_export_pubkey_blob(const ssh_key key, + ssh_string *pblob); +int ssh_pki_import_pubkey_blob(const ssh_string key_blob, + ssh_key *pkey); + +int ssh_pki_import_cert_blob(const ssh_string cert_blob, + ssh_key *pkey); + +/* SSH Private Key Functions */ +int ssh_pki_export_privkey_blob(const ssh_key key, + ssh_string *pblob); + + +/* SSH Signing Functions */ +ssh_string ssh_pki_do_sign(ssh_session session, ssh_buffer sigbuf, + const ssh_key privatekey, enum ssh_digest_e hash_type); +ssh_string ssh_pki_do_sign_agent(ssh_session session, + struct ssh_buffer_struct *buf, + const ssh_key pubkey); +ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, + const ssh_key privkey, + const enum ssh_digest_e digest); + +/* Temporary functions, to be removed after migration to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key); +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key); + +int ssh_key_algorithm_allowed(ssh_session session, const char *type); +bool ssh_key_size_allowed(ssh_session session, ssh_key key); + +/* Return the key size in bits */ +int ssh_key_size(ssh_key key); + +/* PKCS11 URI function to check if filename is a path or a PKCS11 URI */ +#ifdef WITH_PKCS11_URI +bool ssh_pki_is_uri(const char *filename); +char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri); +#endif /* WITH_PKCS11_URI */ + +#ifdef __cplusplus +} +#endif + +#endif /* PKI_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pki_context.h b/src/libs/libssh-0.12.2/include/libssh/pki_context.h new file mode 100644 index 000000000000..6fb48bcfbbbd --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pki_context.h @@ -0,0 +1,103 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef PKI_CONTEXT_H +#define PKI_CONTEXT_H + +#include "libssh/callbacks.h" +#include "libssh/libssh.h" + +/** + * @brief Security key context structure + * + * Context structure containing all parameters and callbacks + * needed for FIDO2/U2F security key operations. + */ +struct ssh_pki_ctx_struct { + /** @brief Desired RSA modulus size in bits + * + * Specified size of RSA keys to generate. If set to 0, defaults to 3072 + * bits. Must be greater than or equal to 1024, as anything below is + * considered insecure. + */ + int rsa_key_size; + + /** @brief Security key callbacks + * + * Provides enroll/sign/load_resident_keys operations. + */ + const struct ssh_sk_callbacks_struct *sk_callbacks; + + /** @brief Application identifier string for the security key credential + * + * FIDO2 relying party identifier, typically "ssh:user@hostname" format. + * This is required for all security key operations. + */ + char *sk_application; + + /** @brief FIDO2 operation flags + * + * Bitfield controlling authenticator behavior. Combine with bitwise OR: + * - SSH_SK_USER_PRESENCE_REQD (0x01): Require user touch + * - SSH_SK_USER_VERIFICATION_REQD (0x04): Require PIN/biometric + * - SSH_SK_FORCE_OPERATION (0x10): Override duplicate detection + * - SSH_SK_RESIDENT_KEY (0x20): Create discoverable credential + */ + uint8_t sk_flags; + + /** @brief PIN callback for authenticator user verification (optional) + * + * Callback invoked to obtain a PIN or perform user verification when + * SSH_SK_USER_VERIFICATION_REQD is set or the authenticator requires it. + * If NULL, no interactive PIN retrieval is performed. + */ + ssh_auth_callback sk_pin_callback; + + /** @brief User supplied pointer passed to callbacks (optional) + * + * Generic pointer set by the application and forwarded to + * interactive callbacks (e.g. PIN callback) to allow applications to + * carry state context. + */ + void *sk_userdata; + + /** @brief Custom challenge data for enrollment (optional) + * + * Buffer containing challenge data signed by the authenticator. + * If NULL, a random 32-byte challenge is automatically generated. + */ + ssh_buffer sk_challenge_buffer; + + /** @brief Options to be passed to the sk_callbacks (optional) + * + * NULL-terminated array of sk_option pointers owned by this context. + */ + struct sk_option **sk_callbacks_options; + + /** @brief The buffer used to store attestation information returned in a + * key enrollment operation + */ + ssh_buffer sk_attestation_buffer; +}; + +/* Internal PKI context functions */ +ssh_pki_ctx ssh_pki_ctx_dup(const ssh_pki_ctx context); + +#endif /* PKI_CONTEXT_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pki_priv.h b/src/libs/libssh-0.12.2/include/libssh/pki_priv.h new file mode 100644 index 000000000000..2a6f8966b36a --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pki_priv.h @@ -0,0 +1,181 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PKI_PRIV_H_ +#define PKI_PRIV_H_ + +#include "libssh/pki.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* defined in bcrypt_pbkdf.c */ +int bcrypt_pbkdf(const char *pass, + size_t passlen, + const uint8_t *salt, + size_t saltlen, + uint8_t *key, + size_t keylen, + unsigned int rounds); + +#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" +#define ECDSA_HEADER_BEGIN "-----BEGIN EC PRIVATE KEY-----" +#define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" +#define OPENSSH_HEADER_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----" +#define OPENSSH_HEADER_END "-----END OPENSSH PRIVATE KEY-----" +/* Magic defined in OpenSSH/PROTOCOL.key */ +#define OPENSSH_AUTH_MAGIC "openssh-key-v1" + +/* Determine type of ssh key. */ +enum ssh_key_e { + SSH_KEY_PUBLIC = 0, + SSH_KEY_PRIVATE +}; + +void pki_key_clean(ssh_key key); + +int pki_key_ecdsa_nid_from_name(const char *name); +const char *pki_key_ecdsa_nid_to_name(int nid); +const char *ssh_key_signature_to_char(enum ssh_keytypes_e type, + enum ssh_digest_e hash_type); +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type); + +/* SSH Key Functions */ +ssh_key pki_key_dup_common_init(const ssh_key key, int demote); +ssh_key pki_key_dup(const ssh_key key, int demote); +int pki_key_generate_rsa(ssh_key key, int parameter); +int pki_key_generate_ecdsa(ssh_key key, int parameter); +int pki_key_generate_ed25519(ssh_key key); + +int pki_key_compare(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); + +int pki_key_check_hash_compatible(ssh_key key, + enum ssh_digest_e hash_type); +/* SSH Private Key Functions */ +enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey); +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data); + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data); +int pki_import_privkey_buffer(enum ssh_keytypes_e type, + ssh_buffer buffer, + ssh_key *pkey); + +/* SSH Public Key Functions */ +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n); +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e); +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type); + +/* SSH Private Key Functions */ +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + ssh_string iqmp, + ssh_string p, + ssh_string q); +int pki_privkey_build_ecdsa(ssh_key key, + int nid, + ssh_string e, + ssh_string exp); + +/* SSH Signature Functions */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len); +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len); +ssh_string pki_signature_to_blob(const ssh_signature sign); +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type); + +/* SSH Signing Functions */ +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *input, + size_t input_len, + enum ssh_digest_e hash_type); +ssh_signature pki_do_sign_hash(const ssh_key privkey, + const unsigned char *hash, + size_t hlen, + enum ssh_digest_e hash_type); +#ifndef HAVE_LIBCRYPTO +int pki_ed25519_sign(const ssh_key privkey, ssh_signature sig, + const unsigned char *hash, size_t hlen); +int pki_ed25519_verify(const ssh_key pubkey, ssh_signature sig, + const unsigned char *hash, size_t hlen); +#endif /* HAVE_LIBCRYPTO */ +int pki_ed25519_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); +int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key); +int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key); +int pki_ed25519_private_key_to_blob(ssh_buffer buffer, const ssh_key privkey); +ssh_string pki_ed25519_signature_to_blob(ssh_signature sig); +int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob); +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey); +int pki_pubkey_build_ed25519(ssh_key key, ssh_string pubkey); + +/* PKI Container OpenSSH */ +ssh_key ssh_pki_openssh_pubkey_import(const char *text_key); +ssh_key ssh_pki_openssh_privkey_import(const char *text_key, + const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); +ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, + const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); + +#ifdef WITH_PKCS11_URI +/* URI Function */ +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type); +#endif /* WITH_PKCS11_URI */ + +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key); + +/* Security Key Helper Functions */ +int pki_buffer_pack_sk_priv_data(ssh_buffer buffer, const ssh_key key); +int pki_buffer_unpack_sk_priv_data(ssh_buffer buffer, ssh_key key); +int pki_sk_signature_buffer_prepare(const ssh_key key, + const ssh_signature sig, + const unsigned char *input, + size_t input_len, + ssh_buffer *sk_buffer_out); + +#ifdef __cplusplus +} +#endif + +#endif /* PKI_PRIV_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pki_sk.h b/src/libs/libssh-0.12.2/include/libssh/pki_sk.h new file mode 100644 index 000000000000..f6c33300b5ad --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pki_sk.h @@ -0,0 +1,90 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef PKI_SK_H +#define PKI_SK_H + +#include "libssh/libssh.h" +#include "libssh/pki.h" + +#include + +#define SSH_SK_MAX_USER_ID_LEN 64 + +/** + * @brief Enroll a new security key using a U2F/FIDO2 authenticator + * + * Creates a new security key credential configured according to the parameters + * in the PKI context. This function handles key enrollment for both ECDSA and + * Ed25519 algorithms, generates appropriate challenges, and returns the + * enrolled key with optional attestation data. + * + * The PKI context must be configured with appropriate security key parameters + * using ssh_pki_ctx_options_set() before calling this function. Required + * options include SSH_PKI_OPTION_SK_APPLICATION, SSH_PKI_OPTION_SK_USER_ID, and + * SSH_PKI_OPTION_SK_CALLBACKS. + * + * @param[in] context The PKI context containing security key configuration and + * parameters + * @param[in] key_type The type of key to enroll (SSH_KEYTYPE_SK_ECDSA or + * SSH_KEYTYPE_SK_ED25519) + * @param[out] enrolled_key_result Pointer to store the enrolled ssh_key + * + * @return SSH_OK on success, SSH_ERROR on failure + * + * @see ssh_pki_ctx_new() + * @see ssh_pki_ctx_options_set() + * @see ssh_pki_ctx_get_sk_attestation_buffer() + */ +int pki_sk_enroll_key(ssh_pki_ctx context, + enum ssh_keytypes_e key_type, + ssh_key *enrolled_key_result); + +/** + * @brief Sign arbitrary data using a security key and a PKI context + * + * This function performs signing operations configured according to the + * parameters in the PKI context and returns a properly formatted + * ssh_signature. The caller must free the signature when it is no longer + * needed. + * + * The PKI context should be configured with appropriate security key parameters + * using ssh_pki_ctx_options_set() before calling this function. The security + * key must have been previously enrolled or loaded. + * + * @param[in] context The PKI context containing security key configuration and + * parameters + * @param[in] key The security key to use for signing + * @param[in] data The data to sign + * @param[in] data_len Length of data to sign + * + * @return A valid ssh_signature on success, NULL on failure + * + * @see ssh_pki_ctx_new() + * @see ssh_pki_ctx_options_set() + * @see pki_sk_enroll_key() + * @see ssh_signature_free() + */ +ssh_signature pki_sk_do_sign(ssh_pki_ctx context, + const ssh_key key, + const uint8_t *data, + size_t data_len); + +#endif /* PKI_SK_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/poll.h b/src/libs/libssh-0.12.2/include/libssh/poll.h new file mode 100644 index 000000000000..478764b6aa29 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/poll.h @@ -0,0 +1,170 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef POLL_H_ +#define POLL_H_ + +#include "config.h" + +#ifdef HAVE_POLL + +#include +typedef struct pollfd ssh_pollfd_t; + +#else /* HAVE_POLL */ + +/* poll emulation support */ + +typedef struct ssh_pollfd_struct { + socket_t fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +} ssh_pollfd_t; + +typedef unsigned long int nfds_t; + +#ifdef _WIN32 + +#ifndef POLLRDNORM +#define POLLRDNORM 0x0100 +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x0200 +#endif +#ifndef POLLIN +#define POLLIN (POLLRDNORM | POLLRDBAND) +#endif +#ifndef POLLPRI +#define POLLPRI 0x0400 +#endif + +#ifndef POLLWRNORM +#define POLLWRNORM 0x0010 +#endif +#ifndef POLLOUT +#define POLLOUT (POLLWRNORM) +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x0020 +#endif + +#ifndef POLLERR +#define POLLERR 0x0001 +#endif +#ifndef POLLHUP +#define POLLHUP 0x0002 +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x0004 +#endif + +#else /* _WIN32 */ + +/* poll.c */ +#ifndef POLLIN +#define POLLIN 0x001 /* There is data to read. */ +#endif +#ifndef POLLPRI +#define POLLPRI 0x002 /* There is urgent data to read. */ +#endif +#ifndef POLLOUT +#define POLLOUT 0x004 /* Writing now will not block. */ +#endif + +#ifndef POLLERR +#define POLLERR 0x008 /* Error condition. */ +#endif +#ifndef POLLHUP +#define POLLHUP 0x010 /* Hung up. */ +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x020 /* Invalid polling request. */ +#endif + +#ifndef POLLRDNORM +#define POLLRDNORM 0x040 /* mapped to read fds_set */ +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x080 /* mapped to exception fds_set */ +#endif +#ifndef POLLWRNORM +#define POLLWRNORM 0x100 /* mapped to write fds_set */ +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x200 /* mapped to write fds_set */ +#endif + +#endif /* WIN32 */ +#endif /* HAVE_POLL */ + +#ifdef __cplusplus +extern "C" { +#endif + +void ssh_poll_init(void); +void ssh_poll_cleanup(void); +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout); +typedef struct ssh_poll_ctx_struct *ssh_poll_ctx; +typedef struct ssh_poll_handle_struct *ssh_poll_handle; + +/** + * @brief SSH poll callback. This callback will be used when an event + * caught on the socket. + * + * @param p Poll object this callback belongs to. + * @param fd The raw socket. + * @param revents The current poll events on the socket. + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 if you removed the poll object from + * its poll context. + */ +typedef int (*ssh_poll_callback)(ssh_poll_handle p, socket_t fd, int revents, + void *userdata); + +struct ssh_socket_struct; + +ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, + void *userdata); +void ssh_poll_free(ssh_poll_handle p); +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p); +short ssh_poll_get_events(ssh_poll_handle p); +void ssh_poll_set_events(ssh_poll_handle p, short events); +void ssh_poll_add_events(ssh_poll_handle p, short events); +void ssh_poll_remove_events(ssh_poll_handle p, short events); +socket_t ssh_poll_get_fd(ssh_poll_handle p); +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd); +void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata); +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size); +void ssh_poll_ctx_free(ssh_poll_ctx ctx); +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p); +int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, struct ssh_socket_struct *s); +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p); +bool ssh_poll_is_locked(ssh_poll_handle p); +int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout); +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session); +int ssh_event_add_poll(ssh_event event, ssh_poll_handle p); +void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p); + +#ifdef __cplusplus +} +#endif + +#endif /* POLL_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/poly1305.h b/src/libs/libssh-0.12.2/include/libssh/poly1305.h new file mode 100644 index 000000000000..a22fea878969 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/poly1305.h @@ -0,0 +1,27 @@ +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#ifndef POLY1305_H +#define POLY1305_H +#include "libssh/chacha20-poly1305-common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void poly1305_auth(uint8_t out[POLY1305_TAGLEN], const uint8_t *m, size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* POLY1305_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/priv.h b/src/libs/libssh-0.12.2/include/libssh/priv.h new file mode 100644 index 000000000000..375436af4f9c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/priv.h @@ -0,0 +1,520 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * priv.h file + * This include file contains everything you shouldn't deal with in + * user programs. Consider that anything in this file might change + * without notice; libssh.h file will keep backward compatibility + * on binary & source + */ + +#ifndef _LIBSSH_PRIV_H +#define _LIBSSH_PRIV_H + +#include +#include +#include +#include +#include +#include + +#if !defined(HAVE_STRTOULL) +# if defined(HAVE___STRTOULL) +# define strtoull __strtoull +# elif defined(HAVE__STRTOUI64) +# define strtoull _strtoui64 +# elif defined(__hpux) && defined(__LP64__) +# define strtoull strtoul +# else +# error "no strtoull function found" +# endif +#endif /* !defined(HAVE_STRTOULL) */ + +#ifdef HAVE_TERMIOS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(HAVE_STRNDUP) +char *strndup(const char *s, size_t n); +#endif /* ! HAVE_STRNDUP */ + +#ifdef HAVE_BYTESWAP_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#ifndef bswap_32 +#define bswap_32(x) \ + ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) +#endif + +#ifdef _WIN32 + +# ifndef PRIu64 +# if __WORDSIZE == 64 +# define PRIu64 "lu" +# else +# define PRIu64 "llu" +# endif /* __WORDSIZE */ +# endif /* PRIu64 */ + +# ifndef PRIu32 +# define PRIu32 "u" +# endif /* PRIu32 */ + +# ifndef PRIx64 +# if __WORDSIZE == 64 +# define PRIx64 "lx" +# else +# define PRIx64 "llx" +# endif /* __WORDSIZE */ +# endif /* PRIx64 */ + +# ifndef PRIx32 +# define PRIx32 "x" +# endif /* PRIx32 */ + +# ifdef _MSC_VER +# include +# include /* va_copy define check */ + +/* On Microsoft compilers define inline to __inline on all others use inline */ +# undef inline +# define inline __inline + +# ifndef va_copy +# define va_copy(dest, src) (dest = src) +# endif + +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# if ! defined(HAVE_ISBLANK) +# define isblank(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\n' || (ch) == '\r') +# endif + +# define usleep(X) Sleep(((X)+1000)/1000) + +# undef strtok_r +# define strtok_r strtok_s + +# if defined(HAVE__SNPRINTF_S) +# undef snprintf +# define snprintf(d, n, ...) _snprintf_s((d), (n), _TRUNCATE, __VA_ARGS__) +# else /* HAVE__SNPRINTF_S */ +# if defined(HAVE__SNPRINTF) +# undef snprintf +# define snprintf _snprintf +# else /* HAVE__SNPRINTF */ +# if !defined(HAVE_SNPRINTF) +# error "no snprintf compatible function found" +# endif /* HAVE_SNPRINTF */ +# endif /* HAVE__SNPRINTF */ +# endif /* HAVE__SNPRINTF_S */ + +# if defined(HAVE__VSNPRINTF_S) +# undef vsnprintf +# define vsnprintf(s, n, f, v) _vsnprintf_s((s), (n), _TRUNCATE, (f), (v)) +# else /* HAVE__VSNPRINTF_S */ +# if defined(HAVE__VSNPRINTF) +# undef vsnprintf +# define vsnprintf _vsnprintf +# else +# if !defined(HAVE_VSNPRINTF) +# error "No vsnprintf compatible function found" +# endif /* HAVE_VSNPRINTF */ +# endif /* HAVE__VSNPRINTF */ +# endif /* HAVE__VSNPRINTF_S */ + +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ + +# endif /* _MSC_VER */ + +struct timeval; +int ssh_gettimeofday(struct timeval *__p, void *__t); + +#define gettimeofday ssh_gettimeofday + +struct tm *ssh_localtime(const time_t *timer, struct tm *result); +# define localtime_r ssh_localtime + +#define _XCLOSESOCKET closesocket + +# ifdef HAVE_IO_H +# include +# undef open +# define open _open +# undef close +# define close _close +# undef read +# define read _read +# undef write +# define write _write +# undef unlink +# define unlink _unlink +# endif /* HAVE_IO_H */ + +#else /* _WIN32 */ + +#include + +#define _XCLOSESOCKET close + +#endif /* _WIN32 */ + +#include "libssh/libssh.h" +#include "libssh/callbacks.h" + +/* some constants */ +#ifndef PATH_MAX +#ifdef MAX_PATH +#define PATH_MAX MAX_PATH +#else +#define PATH_MAX 4096 +#endif +#endif + +#ifndef MAX_PACKET_LEN +#define MAX_PACKET_LEN 262144 +#endif +#ifndef ERROR_BUFFERLEN +#define ERROR_BUFFERLEN 1024 +#endif + +#ifndef CLIENT_BANNER_SSH2 +#define CLIENT_BANNER_SSH2 "SSH-2.0-libssh_" SSH_STRINGIFY(LIBSSH_VERSION) +#endif /* CLIENT_BANNER_SSH2 */ + +#ifndef KBDINT_MAX_PROMPT +#define KBDINT_MAX_PROMPT 256 /* more than openssh's :) */ +#endif +#ifndef MAX_BUF_SIZE +#define MAX_BUF_SIZE 4096 +#endif + +#ifndef HAVE_COMPILER__FUNC__ +# ifdef HAVE_COMPILER__FUNCTION__ +# define __func__ __FUNCTION__ +# else +# error "Your system must provide a __func__ macro" +# endif +#endif + +#if defined(HAVE_GCC_THREAD_LOCAL_STORAGE) +# define LIBSSH_THREAD __thread +#elif defined(HAVE_MSC_THREAD_LOCAL_STORAGE) +# define LIBSSH_THREAD __declspec(thread) +#else +# define LIBSSH_THREAD +#endif + +/* + * This makes sure that the compiler doesn't optimize out the code + * + * Use it in a macro where the provided variable is 'x'. + */ +#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION) +# define LIBSSH_MEM_PROTECTION __asm__ volatile("" : : "r"(&(x)) : "memory") +#else +# define LIBSSH_MEM_PROTECTION +#endif + +#define SSH_DANGEROUS_SHELL_CHARS "'`\";&<>|(){}$\\," + +/* forward declarations */ +struct ssh_common_struct; +struct ssh_kex_struct; + +enum ssh_digest_e { + SSH_DIGEST_AUTO=0, + SSH_DIGEST_SHA1=1, + SSH_DIGEST_SHA256, + SSH_DIGEST_SHA384, + SSH_DIGEST_SHA512, +}; + +int ssh_get_key_params(ssh_session session, + ssh_key *privkey, + enum ssh_digest_e *digest); + +/* LOGGING */ +void ssh_log_function(int verbosity, + const char *function, + const char *buffer); +#define SSH_LOG(priority, ...) \ + _ssh_log(priority, __func__, __VA_ARGS__) + +/* LEGACY */ +void ssh_log_common(struct ssh_common_struct *common, + int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(4, 5); + +void _ssh_remove_legacy_log_cb(void); + +/* log.c */ +void _ssh_reset_log_cb(void); + +/* ERROR HANDLING */ + +/* error handling structure */ +struct error_struct { + int error_code; + char error_buffer[ERROR_BUFFERLEN]; +}; + +#define ssh_set_error(error, code, ...) \ + _ssh_set_error(error, code, __func__, __VA_ARGS__) +void _ssh_set_error(void *error, + int code, + const char *function, + const char *descr, ...) PRINTF_ATTRIBUTE(4, 5); + +#define ssh_set_error_oom(error) \ + _ssh_set_error_oom(error, __func__) +void _ssh_set_error_oom(void *error, const char *function); + +#define ssh_set_error_invalid(error) \ + _ssh_set_error_invalid(error, __func__) +void _ssh_set_error_invalid(void *error, const char *function); + +void ssh_reset_error(void *error); + +/* server.c */ +#ifdef WITH_SERVER +int ssh_auth_reply_default(ssh_session session,int partial); +int ssh_auth_reply_success(ssh_session session, int partial); +#endif +/* client.c */ + +int ssh_send_banner(ssh_session session, int is_server); +void ssh_session_socket_close(ssh_session session); + +/* connect.c */ +socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + const char *bind_addr, int port); + +/* in base64.c */ +ssh_buffer base64_to_bin(const char *source); +uint8_t *bin_to_base64(const uint8_t *source, size_t len); + +/* gzip.c */ +int compress_buffer(ssh_session session,ssh_buffer buf); +int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen); + +/* match.c */ +int match_pattern_list(const char *string, const char *pattern, + size_t len, int dolower); +int match_hostname(const char *host, const char *pattern, size_t len); +#ifndef _WIN32 +int match_cidr_address_list(const char *address, + const char *addrlist, + int sa_family); +#endif +int match_group(const char *group, const char *object); + +/* connector.c */ +int ssh_connector_set_event(ssh_connector connector, ssh_event event); +int ssh_connector_remove_event(ssh_connector connector); + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/** Free memory space */ +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset(&(x), 0, sizeof(x)) + +/** Zero a structure given a pointer to the structure */ +#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((x), 0, sizeof(*(x))); } while(0) + +/** Get the size of an array */ +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +/** Securely zero memory in a way that won't be optimized away */ +#if defined(HAVE_MEMSET_EXPLICIT) +#define ssh_burn(ptr, len) memset_explicit((ptr), '\0', (len)) +#elif defined(HAVE_EXPLICIT_BZERO) +#define ssh_burn(ptr, len) explicit_bzero((ptr), (len)) +#elif defined(HAVE_MEMSET_S) +#define ssh_burn(ptr, len) memset_s((ptr), (len), '\0', (len)) +#elif defined(HAVE_SECURE_ZERO_MEMORY) +#define ssh_burn(ptr, len) SecureZeroMemory((ptr), (len)) +#else +#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION) +#define ssh_burn(ptr, len) \ + do { \ + memset((ptr), '\0', (len)); \ + __asm__ volatile("" : : "g"(ptr) : "memory"); \ + } while (0) +#else +#define ssh_burn(ptr, len) \ + do { \ + memset((ptr), '\0', (len)); \ + } while (0) +#endif +#endif + +void burn_free(void *ptr, size_t len); + +/** Free memory space after zeroing it */ +#define BURN_FREE(x, len) \ + do { \ + if ((x) != NULL) { \ + burn_free((x), (len)); \ + (x) = NULL; \ + } \ + } while (0) + +/** + * This is a hack to fix warnings. The idea is to use this everywhere that we + * get the "discarding const" warning by the compiler. That doesn't actually + * fix the real issue, but marks the place and you can search the code for + * discard_const. + * + * Please use this macro only when there is no other way to fix the warning. + * We should use this function in only in a very few places. + * + * Also, please call this via the discard_const_p() macro interface, as that + * makes the return type safe. + */ +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) + +/** + * Type-safe version of discard_const + */ +#define discard_const_p(type, ptr) ((type *)discard_const(ptr)) + +#ifndef __VA_NARG__ +/** + * Get the argument count of variadic arguments + */ +/* + * Since MSVC 2010 there is a bug in passing __VA_ARGS__ to subsequent + * macros as a single token, which results in: + * warning C4003: not enough actual parameters for macro '_VA_ARG_N' + * and incorrect behavior. This fixes issue. + */ +#define VA_APPLY_VARIADIC_MACRO(macro, tuple) macro tuple + +#define __VA_NARG__(...) \ + (__VA_NARG_(__VA_ARGS__, __RSEQ_N())) +#define __VA_NARG_(...) \ + VA_APPLY_VARIADIC_MACRO(__VA_ARG_N, (__VA_ARGS__)) +#define __VA_ARG_N( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ + _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ + _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ + _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ + _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ + _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ + _61,_62,_63,N,...) N +#define __RSEQ_N() \ + 63, 62, 61, 60, \ + 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \ + 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \ + 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \ + 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ + 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +#endif + +#define CLOSE_SOCKET(s) do { if ((s) != SSH_INVALID_SOCKET) { _XCLOSESOCKET(s); (s) = SSH_INVALID_SOCKET;} } while(0) + +#ifndef HAVE_HTONLL +# ifdef WORDS_BIGENDIAN +# define htonll(x) (x) +# else +# define htonll(x) \ + (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) +# endif +#endif + +#ifndef HAVE_NTOHLL +# ifdef WORDS_BIGENDIAN +# define ntohll(x) (x) +# else +# define ntohll(x) \ + (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) +# endif +#endif + +#ifndef FALL_THROUGH +# ifdef HAVE_FALLTHROUGH_ATTRIBUTE +# define FALL_THROUGH __attribute__ ((fallthrough)) +# else /* HAVE_FALLTHROUGH_ATTRIBUTE */ +# define FALL_THROUGH +# endif /* HAVE_FALLTHROUGH_ATTRIBUTE */ +#endif /* FALL_THROUGH */ + +#ifndef __attr_unused__ +# ifdef HAVE_UNUSED_ATTRIBUTE +# define __attr_unused__ __attribute__((unused)) +# else /* HAVE_UNUSED_ATTRIBUTE */ +# define __attr_unused__ +# endif /* HAVE_UNUSED_ATTRIBUTE */ +#endif /* __attr_unused__ */ + +#ifndef UNUSED_PARAM +#define UNUSED_PARAM(param) param __attr_unused__ +#endif /* UNUSED_PARAM */ + +#ifndef UNUSED_VAR +#define UNUSED_VAR(var) __attr_unused__ var +#endif /* UNUSED_VAR */ + +void ssh_agent_state_free(void *data); + +bool is_ssh_initialized(void); + +#define SSH_ERRNO_MSG_MAX 1024 +char *ssh_strerror(int err_num, char *buf, size_t buflen); + +/** 55 defined options (5 bytes each) + terminator */ +#define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1) +int encode_current_tty_opts(unsigned char *buf, size_t buflen); + +/** The default maximum file size for a configuration file */ +#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024 + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSSH_PRIV_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sc25519.h b/src/libs/libssh-0.12.2/include/libssh/sc25519.h new file mode 100644 index 000000000000..43b09a0560fb --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sc25519.h @@ -0,0 +1,82 @@ +/* $OpenBSD: sc25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/sc25519.h + */ + +#ifndef SC25519_H +#define SC25519_H + +#define sc25519 crypto_sign_ed25519_ref_sc25519 +#define shortsc25519 crypto_sign_ed25519_ref_shortsc25519 +#define sc25519_from32bytes crypto_sign_ed25519_ref_sc25519_from32bytes +#define shortsc25519_from16bytes crypto_sign_ed25519_ref_shortsc25519_from16bytes +#define sc25519_from64bytes crypto_sign_ed25519_ref_sc25519_from64bytes +#define sc25519_from_shortsc crypto_sign_ed25519_ref_sc25519_from_shortsc +#define sc25519_to32bytes crypto_sign_ed25519_ref_sc25519_to32bytes +#define sc25519_iszero_vartime crypto_sign_ed25519_ref_sc25519_iszero_vartime +#define sc25519_isshort_vartime crypto_sign_ed25519_ref_sc25519_isshort_vartime +#define sc25519_lt_vartime crypto_sign_ed25519_ref_sc25519_lt_vartime +#define sc25519_add crypto_sign_ed25519_ref_sc25519_add +#define sc25519_sub_nored crypto_sign_ed25519_ref_sc25519_sub_nored +#define sc25519_mul crypto_sign_ed25519_ref_sc25519_mul +#define sc25519_mul_shortsc crypto_sign_ed25519_ref_sc25519_mul_shortsc +#define sc25519_window3 crypto_sign_ed25519_ref_sc25519_window3 +#define sc25519_window5 crypto_sign_ed25519_ref_sc25519_window5 +#define sc25519_2interleave2 crypto_sign_ed25519_ref_sc25519_2interleave2 + +typedef struct { + uint32_t v[32]; +} sc25519; + +typedef struct { + uint32_t v[16]; +} shortsc25519; + +#ifdef __cplusplus +extern "C" { +#endif + +void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]); + +void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]); + +void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]); + +void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x); + +void sc25519_to32bytes(unsigned char r[32], const sc25519 *x); + +int sc25519_iszero_vartime(const sc25519 *x); + +int sc25519_isshort_vartime(const sc25519 *x); + +int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y); + +void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y); + +/* Convert s into a representation of the form \sum_{i=0}^{84}r[i]2^3 + * with r[i] in {-4,...,3} + */ +void sc25519_window3(signed char r[85], const sc25519 *s); + +/* Convert s into a representation of the form \sum_{i=0}^{50}r[i]2^5 + * with r[i] in {-16,...,15} + */ +void sc25519_window5(signed char r[51], const sc25519 *s); + +void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/scp.h b/src/libs/libssh-0.12.2/include/libssh/scp.h new file mode 100644 index 000000000000..089fcfc94a68 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/scp.h @@ -0,0 +1,63 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SCP_H +#define _SCP_H + +enum ssh_scp_states { + SSH_SCP_NEW, //Data structure just created + SSH_SCP_WRITE_INITED, //Gave our intention to write + SSH_SCP_WRITE_WRITING,//File was opened and currently writing + SSH_SCP_READ_INITED, //Gave our intention to read + SSH_SCP_READ_REQUESTED, //We got a read request + SSH_SCP_READ_READING, //File is opened and reading + SSH_SCP_ERROR, //Something bad happened + SSH_SCP_TERMINATED //Transfer finished +}; + +struct ssh_scp_struct { + ssh_session session; + int mode; + int recursive; + ssh_channel channel; + char *location; + enum ssh_scp_states state; + uint64_t filelen; + uint64_t processed; + enum ssh_scp_request_types request_type; + char *request_name; + char *warning; + int request_mode; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len); +int ssh_scp_integer_mode(const char *mode); +char *ssh_scp_string_mode(int mode); +int ssh_scp_response(ssh_scp scp, char **response); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/server.h b/src/libs/libssh-0.12.2/include/libssh/server.h new file mode 100644 index 000000000000..ee800567b7ab --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/server.h @@ -0,0 +1,415 @@ +/* Public include file for server support */ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_server The libssh server API + * + * @{ + */ + +#ifndef SERVER_H +#define SERVER_H + +#include "libssh/libssh.h" +#define SERVERBANNER CLIENTBANNER + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_bind_options_e { + SSH_BIND_OPTIONS_BINDADDR, + SSH_BIND_OPTIONS_BINDPORT, + SSH_BIND_OPTIONS_BINDPORT_STR, + SSH_BIND_OPTIONS_HOSTKEY, + SSH_BIND_OPTIONS_DSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_RSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_BANNER, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + SSH_BIND_OPTIONS_ECDSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_IMPORT_KEY, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + SSH_BIND_OPTIONS_CIPHERS_C_S, + SSH_BIND_OPTIONS_CIPHERS_S_C, + SSH_BIND_OPTIONS_HMAC_C_S, + SSH_BIND_OPTIONS_HMAC_S_C, + SSH_BIND_OPTIONS_CONFIG_DIR, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + SSH_BIND_OPTIONS_PROCESS_CONFIG, + SSH_BIND_OPTIONS_MODULI, + SSH_BIND_OPTIONS_RSA_MIN_SIZE, + SSH_BIND_OPTIONS_IMPORT_KEY_STR, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, +}; + +typedef struct ssh_bind_struct* ssh_bind; + +/* Callback functions */ + +/** + * @brief Incoming connection callback. This callback is called when a ssh_bind + * has a new incoming connection. + * @param sshbind Current sshbind session handler + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_bind_incoming_connection_callback) (ssh_bind sshbind, + void *userdata); + +/** + * @brief These are the callbacks exported by the ssh_bind structure. + * + * They are called by the server module when events appear on the network. + */ +struct ssh_bind_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** A new connection is available. */ + ssh_bind_incoming_connection_callback incoming_connection; +}; +typedef struct ssh_bind_callbacks_struct *ssh_bind_callbacks; + +/** + * @brief Creates a new SSH server bind. + * + * @return A newly allocated ssh_bind session pointer. + */ +LIBSSH_API ssh_bind ssh_bind_new(void); + +LIBSSH_API int ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, const void *value); + +LIBSSH_API int ssh_bind_options_parse_config(ssh_bind sshbind, + const char *filename); + +/** + * @brief Start listening to the socket. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o); + +/** + * @brief Set the callback for this bind. + * + * @param[in] sshbind The bind to set the callback on. + * + * @param[in] callbacks An already set up ssh_bind_callbacks instance. + * + * @param[in] userdata A pointer to private data to pass to the callbacks. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_bind_set_callbacks(session, &cb); + * @endcode + */ +LIBSSH_API int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, + void *userdata); + +/** + * @brief Set the session to blocking/nonblocking mode. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @param blocking Zero for nonblocking mode. + */ +LIBSSH_API void ssh_bind_set_blocking(ssh_bind ssh_bind_o, int blocking); + +/** + * @brief Recover the file descriptor from the session. + * + * @param ssh_bind_o The ssh server bind to get the fd from. + * + * @return The file descriptor. + */ +LIBSSH_API socket_t ssh_bind_get_fd(ssh_bind ssh_bind_o); + +/** + * @brief Set the file descriptor for a session. + * + * @param ssh_bind_o The ssh server bind to set the fd. + * + * @param fd The file descriptssh_bind B + */ +LIBSSH_API void ssh_bind_set_fd(ssh_bind ssh_bind_o, socket_t fd); + +/** + * @brief Allow the file descriptor to accept new sessions. + * + * @param ssh_bind_o The ssh server bind to use. + */ +LIBSSH_API void ssh_bind_fd_toaccept(ssh_bind ssh_bind_o); + +/** + * @brief Accept an incoming ssh connection and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @see ssh_new + * @return `SSH_OK` when a connection is established + */ +LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session); + +/** + * @brief Accept an incoming ssh connection on the given file descriptor + * and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @param fd A file descriptor of an already established TCP + * inbound connection + * @see ssh_new + * @see ssh_bind_accept + * @return `SSH_OK` when a connection is established + */ +LIBSSH_API int ssh_bind_accept_fd(ssh_bind ssh_bind_o, ssh_session session, + socket_t fd); + +LIBSSH_API ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session); + +/** + * @brief Handles the key exchange and set up encryption + * + * @param session A connected ssh session + * @see ssh_bind_accept + * @return `SSH_OK` if the key exchange was successful + */ +LIBSSH_API int ssh_handle_key_exchange(ssh_session session); + +/** + * @brief Initialize the set of key exchange, hostkey, ciphers, MACs, and + * compression algorithms for the given ssh_session. + * + * The selection of algorithms and keys used are determined by the + * options that are currently set in the given ssh_session structure. + * May only be called before the initial key exchange has begun. + * + * @param session The session structure to initialize. + * + * @see ssh_handle_key_exchange + * @see ssh_options_set + * + * @return `SSH_OK` if initialization succeeds. + */ +LIBSSH_API int ssh_server_init_kex(ssh_session session); + +/** + * @brief Free a ssh servers bind. + * + * Note that this will also free options that have been set on the bind, + * including keys set with SSH_BIND_OPTIONS_IMPORT_KEY. + * + * @param ssh_bind_o The ssh server bind to free. + */ +LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); + +/** + * @brief Set the acceptable authentication methods to be sent to the client. + * + * + * @param[in] session The server session + * + * @param[in] auth_methods The authentication methods we will support, which + * can be bitwise-or'd. + * + * Supported methods are: + * + * SSH_AUTH_METHOD_PASSWORD + * SSH_AUTH_METHOD_PUBLICKEY + * SSH_AUTH_METHOD_HOSTBASED + * SSH_AUTH_METHOD_INTERACTIVE + * SSH_AUTH_METHOD_GSSAPI_MIC + * SSH_AUTH_METHOD_GSSAPI_KEYEX + */ +LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods); + +/** + * @brief Send the server's issue-banner to client. + * + * + * @param[in] session The server session. + * + * @param[in] banner The server's banner. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + */ +LIBSSH_API int ssh_send_issue_banner(ssh_session session, const ssh_string banner); + +/********************************************************** + * SERVER MESSAGING + **********************************************************/ + +/** + * @brief Reply with a standard reject message. + * + * Use this function if you don't know what to respond or if you want to reject + * a request. + * + * @param[in] msg The message to use for the reply. + * + * @return 0 on success, -1 on error. + * + * @see ssh_message_get() + */ +LIBSSH_API int ssh_message_reply_default(ssh_message msg); + +/** + * @brief Get the name of the authenticated user. + * + * @param[in] msg The message to get the username from. + * + * @return The username or NULL if an error occurred. + * + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API const char *ssh_message_auth_user(ssh_message msg); + +/** + * @brief Get the password of the authenticated user. + * + * @param[in] msg The message to get the password from. + * + * @return The password or NULL if an error occurred. + * + * @see ssh_message_get() + * @see ssh_message_type() + * @deprecated This function should not be used anymore as there is a + * callback based server implementation now auth_password_function. + */ +SSH_DEPRECATED LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); + +/** + * @brief Get the publickey of the authenticated user. + * + * If you need the key for later user you should duplicate it. + * + * @param[in] msg The message to get the public key from. + * + * @return The public key or NULL. + * + * @see ssh_key_dup() + * @see ssh_key_cmp() + * @see ssh_message_get() + * @see ssh_message_type() + * @deprecated This function should not be used anymore as there is a + * callback based server implementation auth_pubkey_function. + */ +SSH_DEPRECATED LIBSSH_API ssh_key ssh_message_auth_pubkey(ssh_message msg); + +LIBSSH_API int ssh_message_auth_kbdint_is_response(ssh_message msg); + +/** + * @param[in] msg The message to get the public key state from. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation auth_pubkey_function + */ +SSH_DEPRECATED LIBSSH_API enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg); + +LIBSSH_API int ssh_message_auth_reply_success(ssh_message msg,int partial); +LIBSSH_API int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey); +LIBSSH_API int ssh_message_auth_reply_pk_ok_simple(ssh_message msg); + +LIBSSH_API int ssh_message_auth_set_methods(ssh_message msg, int methods); + +LIBSSH_API int ssh_message_auth_interactive_request(ssh_message msg, + const char *name, const char *instruction, + unsigned int num_prompts, const char **prompts, char *echo); + +LIBSSH_API int ssh_message_service_reply_success(ssh_message msg); +LIBSSH_API const char *ssh_message_service_service(ssh_message msg); + +LIBSSH_API int ssh_message_global_request_reply_success(ssh_message msg, + uint16_t bound_port); + +LIBSSH_API void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data); +LIBSSH_API int ssh_execute_message_callbacks(ssh_session session); + +LIBSSH_API const char *ssh_message_channel_request_open_originator(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_originator_port(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_open_destination(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_destination_port(ssh_message msg); + +LIBSSH_API ssh_channel ssh_message_channel_request_channel(ssh_message msg); + +/* Replaced by callback based server implementation function channel_pty_request_function*/ +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_pty_term(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_width(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_height(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_pxwidth(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_pxheight(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_env_name(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_env_value(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_command(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_subsystem(ssh_message msg); + +/* Replaced by callback based server implementation function channel_open_request_x11_function*/ +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_x11_single_connection(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_x11_screen_number(ssh_message msg); + +LIBSSH_API const char *ssh_message_global_request_address(ssh_message msg); +LIBSSH_API int ssh_message_global_request_port(ssh_message msg); + +LIBSSH_API int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port); + +LIBSSH_API int ssh_channel_request_send_exit_status(ssh_channel channel, + int exit_status); +LIBSSH_API int ssh_channel_request_send_exit_signal(ssh_channel channel, + const char *signum, + int core, + const char *errmsg, + const char *lang); + +LIBSSH_API int ssh_send_keepalive(ssh_session session); + +/* deprecated functions */ +SSH_DEPRECATED LIBSSH_API int ssh_accept(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_write_stderr(ssh_channel channel, + const void *data, uint32_t len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SERVER_H */ + +/** @} */ diff --git a/src/libs/libssh-0.12.2/include/libssh/session.h b/src/libs/libssh-0.12.2/include/libssh/session.h new file mode 100644 index 000000000000..da39df2a7f08 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/session.h @@ -0,0 +1,319 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SESSION_H_ +#define SESSION_H_ +#include + +#include "libssh/priv.h" +#include "libssh/callbacks.h" +#include "libssh/kex.h" +#include "libssh/packet.h" +#include "libssh/pcap.h" +#include "libssh/auth.h" +#include "libssh/channels.h" +#include "libssh/poll.h" +#include "libssh/config.h" +#include "libssh/misc.h" + +/* These are the different states a SSH session can be into its life */ +enum ssh_session_state_e { + SSH_SESSION_STATE_NONE=0, + SSH_SESSION_STATE_CONNECTING, + SSH_SESSION_STATE_SOCKET_CONNECTED, + SSH_SESSION_STATE_BANNER_RECEIVED, + SSH_SESSION_STATE_INITIAL_KEX, + SSH_SESSION_STATE_KEXINIT_RECEIVED, + SSH_SESSION_STATE_DH, + SSH_SESSION_STATE_AUTHENTICATING, + SSH_SESSION_STATE_AUTHENTICATED, + SSH_SESSION_STATE_ERROR, + SSH_SESSION_STATE_DISCONNECTED +}; + +enum ssh_dh_state_e { + DH_STATE_INIT=0, + DH_STATE_GROUP_SENT, + DH_STATE_REQUEST_SENT, + DH_STATE_INIT_SENT, + DH_STATE_NEWKEYS_SENT, + DH_STATE_FINISHED +}; + +enum ssh_pending_call_e { + SSH_PENDING_CALL_NONE = 0, + SSH_PENDING_CALL_CONNECT, + SSH_PENDING_CALL_AUTH_NONE, + SSH_PENDING_CALL_AUTH_PASSWORD, + SSH_PENDING_CALL_AUTH_OFFER_PUBKEY, + SSH_PENDING_CALL_AUTH_PUBKEY, + SSH_PENDING_CALL_AUTH_AGENT, + SSH_PENDING_CALL_AUTH_KBDINT_INIT, + SSH_PENDING_CALL_AUTH_KBDINT_SEND, + SSH_PENDING_CALL_AUTH_GSSAPI_MIC, + SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX, +}; + +/* libssh calls may block an undefined amount of time */ +#define SSH_SESSION_FLAG_BLOCKING 0x0001 + +/* Client successfully authenticated */ +#define SSH_SESSION_FLAG_AUTHENTICATED 0x0002 + +/* Do not accept new session channels (no-more-sessions@openssh.com) */ +#define SSH_SESSION_FLAG_NO_MORE_SESSIONS 0x0004 + +/* The KEXINIT message can be sent first by either of the parties so this flag + * indicates that the message was already sent to make sure it is sent and avoid + * sending it twice during key exchange to simplify the state machine. */ +#define SSH_SESSION_FLAG_KEXINIT_SENT 0x0008 + +/* The current SSH2 session implements the "strict KEX" feature and should behave + * differently on SSH2_MSG_NEWKEYS. */ +#define SSH_SESSION_FLAG_KEX_STRICT 0x0010 +/* Unexpected packets have been sent while the session was still unencrypted */ +#define SSH_SESSION_FLAG_KEX_TAINTED 0x0020 +/* The scp on server can not handle quoted paths. Skip the mitigation for + * CVE-2019-14889 when using scp */ +#define SSH_SESSION_FLAG_SCP_QUOTING_BROKEN 0x0040 + +/* codes to use with ssh_handle_packets*() */ +/* Infinite timeout */ +#define SSH_TIMEOUT_INFINITE -1 +/* Use the timeout defined by user if any. Mostly used with new connections */ +#define SSH_TIMEOUT_USER -2 +/* Use the default timeout, depending on ssh_is_blocking() */ +#define SSH_TIMEOUT_DEFAULT -3 +/* Don't block at all */ +#define SSH_TIMEOUT_NONBLOCKING 0 + +/* options flags */ +/* Authentication with *** allowed */ +#define SSH_OPT_FLAG_PASSWORD_AUTH 0x1 +#define SSH_OPT_FLAG_PUBKEY_AUTH 0x2 +#define SSH_OPT_FLAG_KBDINT_AUTH 0x4 +#define SSH_OPT_FLAG_GSSAPI_AUTH 0x8 + +/* Escape expansion of different variables */ +#define SSH_OPT_EXP_FLAG_KNOWNHOSTS 0x1 +#define SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS 0x2 +#define SSH_OPT_EXP_FLAG_PROXYCOMMAND 0x4 +#define SSH_OPT_EXP_FLAG_IDENTITY 0x8 +#define SSH_OPT_EXP_FLAG_CONTROL_PATH 0x10 + +/* extensions flags */ +/* negotiation enabled */ +#define SSH_EXT_NEGOTIATION 0x01 +/* server-sig-algs extension */ +#define SSH_EXT_SIG_RSA_SHA256 0x02 +#define SSH_EXT_SIG_RSA_SHA512 0x04 +/* Host-bound public key authentication extension */ +#define SSH_EXT_PUBLICKEY_HOSTBOUND 0x08 + +/* members that are common to ssh_session and ssh_bind */ +struct ssh_common_struct { + struct error_struct error; + ssh_callbacks callbacks; /* Callbacks to user functions */ + int log_verbosity; /* verbosity of the log functions */ +}; + +struct ssh_session_struct { + struct ssh_common_struct common; + struct ssh_socket_struct *socket; + char *serverbanner; + char *clientbanner; + int protoversion; + int server; + int client; + int openssh; + uint32_t send_seq; + uint32_t recv_seq; + struct ssh_timestamp last_rekey_time; + bool proxy_root; + + int connected; + /* !=0 when the user got a session handle */ + int alive; + /* two previous are deprecated */ + /* int auth_service_asked; */ + + /* session flags (SSH_SESSION_FLAG_*) */ + int flags; + + /* Extensions negotiated using RFC 8308 */ + uint32_t extensions; + + ssh_string banner; /* that's the issue banner from the server */ + char *peer_discon_msg; /* disconnect message from the remote host */ + char *disconnect_message; /* disconnect message to be set */ + ssh_buffer in_buffer; + PACKET in_packet; + ssh_buffer out_buffer; + struct ssh_list *out_queue; /* This list is used for delaying packets + when rekeying is required */ + + /* the states are used by the nonblocking stuff to remember */ + /* where it was before being interrupted */ + enum ssh_pending_call_e pending_call_state; + enum ssh_session_state_e session_state; + enum ssh_packet_state_e packet_state; + enum ssh_dh_state_e dh_handshake_state; + enum ssh_channel_request_state_e global_req_state; + struct ssh_agent_state_struct *agent_state; + + struct { + struct ssh_auth_auto_state_struct *auto_state; + enum ssh_auth_service_state_e service_state; + enum ssh_auth_state_e state; + uint32_t supported_methods; + uint32_t current_method; + } auth; + + /* Sending this flag before key exchange to save one round trip during the + * key exchange. This might make sense on high-latency connections. + * So far internal only for testing. Usable only on the client side -- + * there is no key exchange method that would start with server message */ + bool send_first_kex_follows; + /* + * RFC 4253, 7.1: if the first_kex_packet_follows flag was set in + * the received SSH_MSG_KEXINIT, but the guess was wrong, this + * field will be set such that the following guessed packet will + * be ignored on the receiving side. Once that packet has been received and + * ignored, this field is cleared. + * On the sending side, this is set after we got peer KEXINIT message and we + * need to resend the initial message of the negotiated KEX algorithm. + */ + bool first_kex_follows_guess_wrong; + + ssh_string gssapi_key_exchange_mic; + + ssh_buffer in_hashbuf; + ssh_buffer out_hashbuf; + struct ssh_crypto_struct *current_crypto; + /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ + struct ssh_crypto_struct *next_crypto; + + struct ssh_list *channels; /* linked list of channels */ + uint32_t maxchannel; + ssh_agent agent; /* ssh agent */ + + /* keyboard interactive data */ + struct ssh_kbdint_struct *kbdint; + struct ssh_gssapi_struct *gssapi; + + /* server host keys */ + struct { + ssh_key rsa_key; + ssh_key ecdsa_key; + ssh_key ed25519_key; + /* The type of host key wanted by client */ + enum ssh_keytypes_e hostkey; + enum ssh_digest_e hostkey_digest; + } srv; + + /* auths accepted by server */ + struct ssh_list *ssh_message_list; /* list of delayed SSH messages */ + int (*ssh_message_callback)(struct ssh_session_struct *session, + ssh_message msg, void *userdata); + void *ssh_message_callback_data; + ssh_server_callbacks server_callbacks; + void (*ssh_connection_callback)( struct ssh_session_struct *session); + struct ssh_packet_callbacks_struct default_packet_callbacks; + struct ssh_list *packet_callbacks; + struct ssh_socket_callbacks_struct socket_callbacks; + ssh_poll_ctx default_poll_ctx; + /* options */ +#ifdef WITH_PCAP + ssh_pcap_context pcap_ctx; /* pcap debugging context */ +#endif + struct { + struct ssh_list *identity; + struct ssh_list *identity_non_exp; + struct ssh_iterator *identity_it; + struct ssh_list *certificate; + struct ssh_list *certificate_non_exp; + struct ssh_list *proxy_jumps; + struct ssh_list *proxy_jumps_user_cb; + char *proxy_jumps_str; + char *username; + char *host; + char *bindaddr; /* bind the client to an ip addr */ + char *homedir; + char *sshdir; + char *knownhosts; + char *global_knownhosts; + char *wanted_methods[SSH_KEX_METHODS]; + char *pubkey_accepted_types; + char *ProxyCommand; + char *agent_socket; + unsigned long timeout; /* seconds */ + unsigned long timeout_usec; + uint16_t port; + socket_t fd; + int StrictHostKeyChecking; + char compressionlevel; + char *gss_server_identity; + char *gss_client_identity; + bool gssapi_key_exchange; + char *gssapi_key_exchange_algs; + int gss_delegate_creds; + int flags; + int exp_flags; + int nodelay; + bool config_processed; + uint8_t options_seen[SOC_MAX]; + uint64_t rekey_data; + uint32_t rekey_time; + int rsa_min_size; + bool identities_only; + int control_master; + char *control_path; + int address_family; + } opts; + + /* server options */ + struct { + char *custombanner; + char *moduli_file; + } server_opts; + + /* counters */ + ssh_counter socket_counter; + ssh_counter raw_counter; + + /* PKI context structure containing various parameters to configure PKI + * operations */ + struct ssh_pki_ctx_struct *pki_context; +}; + +/** @internal + * @brief a termination function evaluates the status of an object + * @param user[in] object to evaluate + * @returns 1 if the polling routine should terminate, 0 instead + */ +typedef int (*ssh_termination_function)(void *user); +int ssh_handle_packets(ssh_session session, int timeout); +int ssh_handle_packets_termination(ssh_session session, + int timeout, + ssh_termination_function fct, + void *user); +void ssh_socket_exception_callback(int code, int errno_code, void *user); + +#endif /* SESSION_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sftp.h b/src/libs/libssh-0.12.2/include/libssh/sftp.h new file mode 100644 index 000000000000..f401ec035c82 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sftp.h @@ -0,0 +1,1482 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_sftp The libssh SFTP API + * + * @brief SFTP handling functions + * + * SFTP commands are channeled by the ssh sftp subsystem. Every packet is + * sent/read using a sftp_packet type structure. Related to these packets, + * most of the server answers are messages having an ID and a message + * specific part. It is described by sftp_message when reading a message, + * the sftp system puts it into the queue, so the process having asked for + * it can fetch it, while continuing to read for other messages (it is + * unspecified in which order messages may be sent back to the client + * + * @{ + */ + +#ifndef SFTP_H +#define SFTP_H + +#include + +#include "libssh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#ifndef uid_t + typedef uint32_t uid_t; +#endif /* uid_t */ +#ifndef gid_t + typedef uint32_t gid_t; +#endif /* gid_t */ +#ifdef _MSC_VER + +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ + +#endif /* _MSC_VER */ +#endif /* _WIN32 */ + +#define LIBSFTP_VERSION 3 + +typedef struct sftp_attributes_struct* sftp_attributes; +typedef struct sftp_client_message_struct* sftp_client_message; +typedef struct sftp_dir_struct* sftp_dir; +typedef struct sftp_ext_struct *sftp_ext; +typedef struct sftp_file_struct* sftp_file; +typedef struct sftp_message_struct* sftp_message; +typedef struct sftp_packet_struct* sftp_packet; +typedef struct sftp_request_queue_struct* sftp_request_queue; + +/** + * @brief SFTP session handle. + * + * This type represents an active SFTP session associated with an SSH channel. + * It is created and destroyed via the libssh SFTP API and is internally + * managed by libssh. It is used by applications to perform SFTP operations + * such as file access and directory management. + * + * The internal structure of this type is opaque and must not be accessed + * directly by applications. + * + * @see sftp_new + * @see sftp_free + */ +typedef struct sftp_session_struct* sftp_session; +typedef struct sftp_status_message_struct* sftp_status_message; +typedef struct sftp_statvfs_struct* sftp_statvfs_t; +typedef struct sftp_limits_struct* sftp_limits_t; +typedef struct sftp_aio_struct* sftp_aio; +typedef struct sftp_name_id_map_struct *sftp_name_id_map; + +struct sftp_session_struct { + ssh_session session; + ssh_channel channel; + int server_version; + int client_version; + int version; + sftp_request_queue queue; + uint32_t id_counter; + int errnum; + void **handles; + sftp_ext ext; + sftp_packet read_packet; + sftp_limits_t limits; + struct ssh_list *outstanding_ids; +}; + +struct sftp_packet_struct { + sftp_session sftp; + uint8_t type; + ssh_buffer payload; +}; + +/* file handler */ +struct sftp_file_struct { + sftp_session sftp; + char *name; + uint64_t offset; + ssh_string handle; + int eof; + int nonblocking; +}; + +struct sftp_dir_struct { + sftp_session sftp; + char *name; + ssh_string handle; /* handle to directory */ + ssh_buffer buffer; /* contains raw attributes from server which haven't been parsed */ + uint32_t count; /* counts the number of following attributes structures into buffer */ + int eof; /* end of directory listing */ +}; + +struct sftp_message_struct { + sftp_session sftp; + uint8_t packet_type; + ssh_buffer payload; + uint32_t id; +}; + +/* this is a bunch of all data that could be into a message */ +struct sftp_client_message_struct { + sftp_session sftp; + uint8_t type; + uint32_t id; + char *filename; /* can be "path" */ + uint32_t flags; + sftp_attributes attr; + ssh_string handle; + uint64_t offset; + uint32_t len; + int attr_num; + ssh_buffer attrbuf; /* used by sftp_reply_attrs */ + ssh_string data; /* can be newpath of rename() */ + ssh_buffer complete_message; /* complete message in case of retransmission*/ + char *str_data; /* cstring version of data */ + char *submessage; /* for extended messages */ +}; + +struct sftp_request_queue_struct { + sftp_request_queue next; + sftp_message message; +}; + +/* SSH_FXP_MESSAGE described into .7 page 26 */ +struct sftp_status_message_struct { + uint32_t id; + uint32_t status; + ssh_string error_unused; /* not used anymore */ + ssh_string lang_unused; /* not used anymore */ + char *errormsg; + char *langmsg; +}; + +struct sftp_attributes_struct { + char *name; + char *longname; /* ls -l output on openssh, not reliable else */ + uint32_t flags; + uint8_t type; + uint64_t size; + uint32_t uid; + uint32_t gid; + char *owner; /* set if openssh and version 4 */ + char *group; /* set if openssh and version 4 */ + uint32_t permissions; + uint64_t atime64; + uint32_t atime; + uint32_t atime_nseconds; + uint64_t createtime; + uint32_t createtime_nseconds; + uint64_t mtime64; + uint32_t mtime; + uint32_t mtime_nseconds; + ssh_string acl; + uint32_t extended_count; + ssh_string extended_type; + ssh_string extended_data; +}; + +/** + * @brief SFTP statvfs structure. + */ +struct sftp_statvfs_struct { + uint64_t f_bsize; /** file system block size */ + uint64_t f_frsize; /** fundamental fs block size */ + uint64_t f_blocks; /** number of blocks (unit f_frsize) */ + uint64_t f_bfree; /** free blocks in file system */ + uint64_t f_bavail; /** free blocks for non-root */ + uint64_t f_files; /** total file inodes */ + uint64_t f_ffree; /** free file inodes */ + uint64_t f_favail; /** free file inodes for to non-root */ + uint64_t f_fsid; /** file system id */ + uint64_t f_flag; /** bit mask of f_flag values */ + uint64_t f_namemax; /** maximum filename length */ +}; + +/** + * @brief SFTP limits structure. + */ +struct sftp_limits_struct { + uint64_t max_packet_length; /** maximum number of bytes in a single sftp packet */ + uint64_t max_read_length; /** maximum length in a SSH_FXP_READ packet */ + uint64_t max_write_length; /** maximum length in a SSH_FXP_WRITE packet */ + uint64_t max_open_handles; /** maximum number of active handles allowed by server */ +}; + +/** + * @brief SFTP names map structure to store the mapping between ids and names. + * + * This is mainly for the use of sftp_get_users_groups_by_id() function. + */ +struct sftp_name_id_map_struct { + /** @brief Count of name-id pairs in the map */ + uint32_t count; + + /** @brief Array of ids, ids[i] mapped to names[i] */ + uint32_t *ids; + + /** @brief Array of names, names[i] mapped to ids[i] */ + char **names; +}; + +/** + * @brief Creates a new sftp session. + * + * This function creates a new sftp session and allocates a new sftp channel + * with the server inside of the provided ssh session. This function call is + * usually followed by the sftp_init(), which initializes SFTP protocol itself. + * + * @param session The ssh session to use. The session *must* be in + * blocking mode since most `sftp_*` functions do not + * support the non-blocking API. + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + * @see sftp_init() + * @see ssh_set_blocking() + */ +LIBSSH_API sftp_session sftp_new(ssh_session session); + +/** + * @brief Start a new sftp session with an existing channel. + * + * @param session The ssh session to use. The session *must* be in + * blocking mode since most `sftp_*` functions do not + * support the non-blocking API. + * @param channel An open session channel with subsystem already allocated + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + * @see ssh_set_blocking() + */ +LIBSSH_API sftp_session sftp_new_channel(ssh_session session, ssh_channel channel); + + +/** + * @brief Close and deallocate a sftp session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_free(sftp_session sftp); + +/** + * @brief Initialize the sftp protocol with the server. + * + * This function involves the SFTP protocol initialization (as described + * in the SFTP specification), including the version and extensions negotiation. + * + * @param sftp The sftp session to initialize. + * + * @return 0 on success, < 0 on error with ssh error set. + * + * @see sftp_new() + */ +LIBSSH_API int sftp_init(sftp_session sftp); + +/** + * @brief Get the last sftp error. + * + * Use this function to get the latest error set by a posix like sftp function. + * + * @param sftp The sftp session where the error is saved. + * + * @return The saved error (see server responses), < 0 if an error + * in the function occurred. + * + * @see Server responses + */ +LIBSSH_API int sftp_get_error(sftp_session sftp); + +/** + * @brief Get the count of extensions provided by the server. + * + * @param sftp The sftp session to use. + * + * @return The count of extensions provided by the server, 0 on error or + * not available. + */ +LIBSSH_API unsigned int sftp_extensions_get_count(sftp_session sftp); + +/** + * @brief Get the name of the extension provided by the server. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension name you want. + * + * @return The name of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_name(sftp_session sftp, unsigned int indexn); + +/** + * @brief Get the data of the extension provided by the server. + * + * This is normally the version number of the extension. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension data you want. + * + * @return The data of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_data(sftp_session sftp, unsigned int indexn); + +/** + * @brief Check if the given extension is supported. + * + * @param sftp The sftp session to use. + * + * @param name The name of the extension. + * + * @param data The data of the extension. + * + * @return 1 if supported, 0 if not. + * + * Example: + * + * @code + * sftp_extension_supported(sftp, "statvfs@openssh.com", "2"); + * @endcode + */ +LIBSSH_API int sftp_extension_supported(sftp_session sftp, const char *name, + const char *data); + +/** + * @brief Open a directory used to obtain directory entries. + * + * @param session The sftp session handle to open the directory. + * @param path The path of the directory to open. + * + * @return A sftp directory handle or NULL on error with ssh and + * sftp error set. + * + * @see sftp_readdir + * @see sftp_closedir + */ +LIBSSH_API sftp_dir sftp_opendir(sftp_session session, const char *path); + +/** + * @brief Get a single file attributes structure of a directory. + * + * @param session The sftp session handle to read the directory entry. + * @param dir The opened sftp directory handle to read from. + * + * @return A file attribute structure or NULL at the end of the + * directory. + * + * @see sftp_opendir() + * @see sftp_attribute_free() + * @see sftp_closedir() + */ +LIBSSH_API sftp_attributes sftp_readdir(sftp_session session, sftp_dir dir); + +/** + * @brief Tell if the directory has reached EOF (End Of File). + * + * @param dir The sftp directory handle. + * + * @return 1 if the directory is EOF, 0 if not. + * + * @see sftp_readdir() + */ +LIBSSH_API int sftp_dir_eof(sftp_dir dir); + +/** + * @brief Get information about a file or directory. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_stat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory. + * + * Identical to sftp_stat, but if the file or directory is a symbolic link, + * then the link itself is stated, not the file that it refers to. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_lstat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory from a file handle. + * + * @param file The sftp file handle to get the stat information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_fstat(sftp_file file); + +/** + * @brief Free a sftp attribute structure. + * + * @param file The sftp attribute structure to free. + */ +LIBSSH_API void sftp_attributes_free(sftp_attributes file); + +/** + * @brief Close a directory handle opened by sftp_opendir(). + * + * @param dir The sftp directory handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occurred. + */ +LIBSSH_API int sftp_closedir(sftp_dir dir); + +/** + * @brief Close an open file handle. + * + * @param file The open sftp file handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occurred. + * + * @see sftp_open() + */ +LIBSSH_API int sftp_close(sftp_file file); + +/** + * @brief Open a file on the server. + * + * @param session The sftp session handle. + * + * @param file The file to be opened. + * + * @param accesstype Is one of O_RDONLY, O_WRONLY or O_RDWR which request + * opening the file read-only,write-only or read/write. + * Acesss may also be bitwise-or'd with one or more of + * the following: + * O_CREAT - If the file does not exist it will be + * created. + * O_EXCL - When used with O_CREAT, if the file already + * exists it is an error and the open will fail. + * O_TRUNC - If the file already exists it will be + * truncated. + * + * @param mode Mode specifies the permissions to use if a new file is + * created. It is modified by the process's umask in + * the usual way: The permissions of the created file are + * (mode & ~umask) + * + * @return A sftp file handle, NULL on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_file sftp_open(sftp_session session, const char *file, int accesstype, + mode_t mode); + +/** + * @brief Make the sftp communication for this file handle non blocking. + * + * @param[in] handle The file handle to set non blocking. + */ +LIBSSH_API void sftp_file_set_nonblocking(sftp_file handle); + +/** + * @brief Make the sftp communication for this file handle blocking. + * + * @param[in] handle The file handle to set blocking. + */ +LIBSSH_API void sftp_file_set_blocking(sftp_file handle); + +/** + * @brief Read from a file using an opened sftp file handle. + * + * This function caps the length a user is allowed to read from an sftp file. + * + * The value used for the cap is same as the value of the max_read_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file The opened sftp file handle to be read from. + * + * @param buf Pointer to buffer to receive read data. + * + * @param count Size of the buffer in bytes. + * + * @return Number of bytes read, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API ssize_t sftp_read(sftp_file file, void *buf, size_t count); + +/** + * @brief Start an asynchronous read from a file using an opened sftp file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous read. To do so, you must call 2 functions: + * + * sftp_async_read_begin() and sftp_async_read(). + * + * The first step is to call sftp_async_read_begin(). This function returns a + * request identifier. The second step is to call sftp_async_read() using the + * returned identifier. + * + * @param file The opened sftp file handle to be read from. + * + * @param len Size to read in bytes. + * + * @return An identifier corresponding to the sent request, < 0 on + * error. + * + * @warning When calling this function, the internal offset is + * updated corresponding to the len parameter. + * + * @warning A call to sftp_async_read_begin() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_async_read() is called. + * Not calling sftp_async_read() will lead to memory + * leaks. + * + * @see sftp_async_read() + * @see sftp_open() + */ +SSH_DEPRECATED LIBSSH_API int sftp_async_read_begin(sftp_file file, + uint32_t len); + +/** + * @brief Wait for an asynchronous read to complete and save the data. + * + * @param file The opened sftp file handle to be read from. + * + * @param data Pointer to buffer to receive read data. + * + * @param len Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_async_read_begin() call. + * + * @param id The identifier returned by the sftp_async_read_begin() + * function. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occurred, SSH_AGAIN if the file is opened in nonblocking + * mode and the request hasn't been executed yet. + * + * @warning A call to this function with an invalid identifier + * will never return. + * + * @see sftp_async_read_begin() + */ +SSH_DEPRECATED LIBSSH_API int sftp_async_read(sftp_file file, + void *data, + uint32_t len, + uint32_t id); + +/** + * @brief Write to a file using an opened sftp file handle. + * + * This function caps the length a user is allowed to write to an sftp file. + * + * The value used for the cap is same as the value of the max_write_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file Open sftp file handle to write to. + * + * @param buf Pointer to buffer to write data. + * + * @param count Size of buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_open() + * @see sftp_read() + * @see sftp_close() + */ +LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count); + +/** + * @brief Deallocate memory corresponding to a sftp aio handle. + * + * This function deallocates memory corresponding to the aio handle returned + * by the sftp_aio_begin_*() functions. Users can use this function to free + * memory corresponding to an aio handle for an outstanding async i/o request + * on encountering some error. + * + * @param aio sftp aio handle corresponding to which memory has + * to be deallocated. + * + * @see sftp_aio_begin_read() + * @see sftp_aio_wait_read() + * @see sftp_aio_begin_write() + * @see sftp_aio_wait_write() + */ +LIBSSH_API void sftp_aio_free(sftp_aio aio); +#define SFTP_AIO_FREE(x) \ + do { if(x != NULL) {sftp_aio_free(x); x = NULL;} } while(0) + +/** + * @brief Start an asynchronous read from a file using an opened sftp + * file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous read. To do so, you must call 2 functions : + * + * sftp_aio_begin_read() and sftp_aio_wait_read(). + * + * - The first step is to call sftp_aio_begin_read(). This function sends a + * read request to the sftp server, dynamically allocates memory to store + * information about the sent request and provides the caller an sftp aio + * handle to that memory. + * + * - The second step is to call sftp_aio_wait_read() and pass it the address + * of a location storing the sftp aio handle provided by + * sftp_aio_begin_read(). + * + * These two functions do not close the open sftp file handle passed to + * sftp_aio_begin_read() irrespective of whether they fail or not. + * + * It is the responsibility of the caller to ensure that the open sftp file + * handle passed to sftp_aio_begin_read() must not be closed before the + * corresponding call to sftp_aio_wait_read(). After sftp_aio_wait_read() + * returns, it is caller's decision whether to immediately close the file by + * calling sftp_close() or to keep it open and perform some more operations + * on it. + * + * This function caps the length a user is allowed to read from an sftp file, + * the value of len parameter after capping is returned on success. + * + * The value used for the cap is same as the value of the max_read_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file The opened sftp file handle to be read from. + * + * @param len Number of bytes to read. + * + * @param aio Pointer to a location where the sftp aio handle + * (corresponding to the sent request) should be stored. + * + * @returns On success, the number of bytes the server is + * requested to read (value of len parameter after + * capping). On error, SSH_ERROR with sftp and ssh + * errors set. + * + * @warning When calling this function, the internal file offset is + * updated corresponding to the number of bytes requested + * to read. + * + * @warning A call to sftp_aio_begin_read() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_aio_wait_read() is called. + * Not calling sftp_aio_wait_read() will lead to memory + * leaks. + * + * @see sftp_aio_wait_read() + * @see sftp_aio_free() + * @see sftp_open() + * @see sftp_close() + * @see sftp_get_error() + * @see ssh_get_error() + */ +LIBSSH_API ssize_t sftp_aio_begin_read(sftp_file file, + size_t len, + sftp_aio *aio); + +/** + * @brief Wait for an asynchronous read to complete and store the read data + * in the supplied buffer. + * + * A pointer to an sftp aio handle should be passed while calling + * this function. Except when the return value is SSH_AGAIN, + * this function releases the memory corresponding to the supplied + * aio handle and assigns NULL to that aio handle using the passed + * pointer to that handle. + * + * If the file is opened in non-blocking mode and the request hasn't been + * executed yet, this function returns SSH_AGAIN and must be called again + * using the same sftp aio handle. + * + * @param aio Pointer to the sftp aio handle returned by + * sftp_aio_begin_read(). + * + * @param buf Pointer to the buffer in which read data will be stored. + * + * @param buf_size Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_aio_begin_read() call. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occurred, SSH_AGAIN if the file is opened in nonblocking + * mode and the request hasn't been executed yet. + * + * @warning A call to this function with an invalid sftp aio handle + * may never return. + * + * @see sftp_aio_begin_read() + * @see sftp_aio_free() + */ +LIBSSH_API ssize_t sftp_aio_wait_read(sftp_aio *aio, + void *buf, + size_t buf_size); + +/** + * @brief Start an asynchronous write to a file using an opened sftp + * file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous write. To do so, you must call 2 functions : + * + * sftp_aio_begin_write() and sftp_aio_wait_write(). + * + * - The first step is to call sftp_aio_begin_write(). This function sends a + * write request to the sftp server, dynamically allocates memory to store + * information about the sent request and provides the caller an sftp aio + * handle to that memory. + * + * - The second step is to call sftp_aio_wait_write() and pass it the address + * of a location storing the sftp aio handle provided by + * sftp_aio_begin_write(). + * + * These two functions do not close the open sftp file handle passed to + * sftp_aio_begin_write() irrespective of whether they fail or not. + * + * It is the responsibility of the caller to ensure that the open sftp file + * handle passed to sftp_aio_begin_write() must not be closed before the + * corresponding call to sftp_aio_wait_write(). After sftp_aio_wait_write() + * returns, it is caller's decision whether to immediately close the file by + * calling sftp_close() or to keep it open and perform some more operations + * on it. + * + * This function caps the length a user is allowed to write to an sftp file, + * the value of len parameter after capping is returned on success. + * + * The value used for the cap is same as the value of the max_write_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file The opened sftp file handle to write to. + * + * @param buf Pointer to the buffer containing data to write. + * + * @param len Number of bytes to write. + * + * @param aio Pointer to a location where the sftp aio handle + * (corresponding to the sent request) should be stored. + * + * @returns On success, the number of bytes the server is + * requested to write (value of len parameter after + * capping). On error, SSH_ERROR with sftp and ssh errors + * set. + * + * @warning When calling this function, the internal file offset is + * updated corresponding to the number of bytes requested + * to write. + * + * @warning A call to sftp_aio_begin_write() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_aio_wait_write() is + * called. Not calling sftp_aio_wait_write() will lead to + * memory leaks. + * + * @see sftp_aio_wait_write() + * @see sftp_aio_free() + * @see sftp_open() + * @see sftp_close() + * @see sftp_get_error() + * @see ssh_get_error() + */ +LIBSSH_API ssize_t sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio); + +/** + * @brief Wait for an asynchronous write to complete. + * + * A pointer to an sftp aio handle should be passed while calling + * this function. Except when the return value is SSH_AGAIN, + * this function releases the memory corresponding to the supplied + * aio handle and assigns NULL to that aio handle using the passed + * pointer to that handle. + * + * If the file is opened in non-blocking mode and the request hasn't + * been executed yet, this function returns SSH_AGAIN and must be called + * again using the same sftp aio handle. + * + * @param aio Pointer to the sftp aio handle returned by + * sftp_aio_begin_write(). + * + * @return Number of bytes written on success, SSH_ERROR + * if an error occurred, SSH_AGAIN if the file is + * opened in nonblocking mode and the request hasn't + * been executed yet. + * + * @warning A call to this function with an invalid sftp aio handle + * may never return. + * + * @see sftp_aio_begin_write() + * @see sftp_aio_free() + */ +LIBSSH_API ssize_t sftp_aio_wait_write(sftp_aio *aio); + +/** + * @brief Seek to a specific location in a file. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek(sftp_file file, uint32_t new_offset); + +/** + * @brief Seek to a specific location in a file. This is the + * 64bit version. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek64(sftp_file file, uint64_t new_offset); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API unsigned long sftp_tell(sftp_file file); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. + */ +LIBSSH_API uint64_t sftp_tell64(sftp_file file); + +/** + * @brief Rewinds the position of the file pointer to the beginning of the + * file. + * + * @param file Open sftp file handle. + */ +LIBSSH_API void sftp_rewind(sftp_file file); + +/** + * @brief Unlink (delete) a file. + * + * @param sftp The sftp session handle. + * + * @param file The file to unlink/delete. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_unlink(sftp_session sftp, const char *file); + +/** + * @brief Remove a directory. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to remove. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rmdir(sftp_session sftp, const char *directory); + +/** + * @brief Create a directory. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to create. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode); + +/** + * @brief Rename or move a file or directory. + * + * @param sftp The sftp session handle. + * + * @param original The original url (source url) of file or directory to + * be moved. + * + * @param newname The new url (destination url) of the file or directory + * after the move. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rename(sftp_session sftp, const char *original, const char *newname); + +/** + * @brief Set file attributes on a file, directory or symbolic link. + * + * Note, that this function can only set time values using 32 bit values due to + * the restrictions in the SFTP protocol version 3 implemented by libssh. + * The support for 64 bit time values was introduced in SFTP version 5, which is + * not implemented by libssh nor any major SFTP servers. + * + * @param sftp The sftp session handle. + * + * @param file The file which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr); + +/** + * @brief This request is like setstat (excluding mode and size) but sets file + * attributes on symlinks themselves. + * + * Note, that this function can only set time values using 32 bit values due to + * the restrictions in the SFTP protocol version 3 implemented by libssh. + * The support for 64 bit time values was introduced in SFTP version 5, which is + * not implemented by libssh nor any major SFTP servers. + * + * @param sftp The sftp session handle. + * + * @param file The symbolic link which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int +sftp_lsetstat(sftp_session sftp, const char *file, sftp_attributes attr); + +/** + * @brief Change the file owner and group + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param owner The new owner which should be set. + * + * @param group The new group which should be set. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group); + +/** + * @brief Change permissions of a file + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chmod(sftp_session sftp, const char *file, mode_t mode); + +/** + * @brief Change the last modification and access time of a file. + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param times A timeval structure which contains the desired access + * and modification time. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_utimes(sftp_session sftp, const char *file, const struct timeval *times); + +/** + * @brief Create a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param target Specifies the target of the symlink. + * + * @param dest Specifies the path name of the symlink to be created. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_symlink(sftp_session sftp, const char *target, const char *dest); + +/** + * @brief Read the value of a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param path Specifies the path name of the symlink to be read. + * + * @return The target of the link, NULL on error. + * The caller needs to free the memory + * using ssh_string_free_char(). + * + * @see sftp_get_error() + */ +LIBSSH_API char *sftp_readlink(sftp_session sftp, const char *path); + +/** + * @brief Create a hard link. + * + * @param sftp The sftp session handle. + * + * @param oldpath Specifies the pathname of the file for + * which the new hardlink is to be created. + * + * @param newpath Specifies the pathname of the hardlink to be created. + * + * @return 0 on success, -1 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath); + +/** + * @brief Get information about a mounted file system. + * + * @param sftp The sftp session handle. + * + * @param path The pathname of any file within the mounted file system. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param file An opened file. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_fstatvfs(sftp_file file); + +/** + * @brief Free the memory of an allocated statvfs. + * + * @param statvfs_o The statvfs to free. + */ +LIBSSH_API void sftp_statvfs_free(sftp_statvfs_t statvfs_o); + +/** + * @brief Synchronize a file's in-core state with storage device + * + * This calls the "fsync@openssh.com" extension. You should check if the + * extensions is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, "fsync@openssh.com", "1"); + * @endcode + * + * @param file The opened sftp file handle to sync + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + */ +LIBSSH_API int sftp_fsync(sftp_file file); + +/** + * @brief Get information about the various limits the server might impose. + * + * @param sftp The sftp session handle. + * + * @return A limits structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_limits_t sftp_limits(sftp_session sftp); + +/** + * @brief Free the memory of an allocated limits. + * + * @param limits The limits to free. + */ +LIBSSH_API void sftp_limits_free(sftp_limits_t limits); + +/** + * @brief Canonicalize a sftp path. + * + * @param sftp The sftp session handle. + * + * @param path The path to be canonicalized. + * + * @return A pointer to the newly allocated canonicalized path, + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_canonicalize_path(sftp_session sftp, const char *path); + +/** + * @brief Get the version of the SFTP protocol supported by the server + * + * @param sftp The sftp session handle. + * + * @return The server version. + */ +LIBSSH_API int sftp_server_version(sftp_session sftp); + +/** + * @brief Canonicalize path using expand-path@openssh.com extension + * + * @param sftp The sftp session handle. + * + * @param path The path to be canonicalized. + * + * @return A pointer to the newly allocated canonicalized path, + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_expand_path(sftp_session sftp, const char *path); + +/** + * @brief Get the specified user's home directory + * + * This calls the "home-directory" extension. You should check if the extension + * is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, "home-directory", "1"); + * @endcode + * + * @param sftp The sftp session handle. + * + * @param username username of the user whose home directory is requested. + * + * @return On success, a newly allocated string containing the + * absolute real-path of the home directory of the user. + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_home_directory(sftp_session sftp, const char *username); + +/** + * @brief Create a new sftp_name_id_map struct. + * + * @param count The number of ids/names to store in the map. + * + * @return A pointer to the newly allocated sftp_name_id_map + * struct. + */ +LIBSSH_API sftp_name_id_map sftp_name_id_map_new(uint32_t count); + +/** + * @brief Free the memory of an allocated `sftp_name_id_map` struct. + * + * @param map A pointer to the `sftp_name_id_map` struct to free. + */ +LIBSSH_API void sftp_name_id_map_free(sftp_name_id_map map); + +/** + * @brief Retrieves usernames and group names based on provided user and group + * IDs. + * + * The retrieved names are stored in the `names` field of the + * `sftp_name_id_map` structure. In case a uid or gid is not found, an empty + * string is stored. + * + * This calls the "users-groups-by-id@openssh.com" extension. + * You should check if the extension is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, + * "users-groups-by-id@openssh.com", "1"); + * @endcode + * + * @param sftp The SFTP session handle. + * + * @param users_map A pointer to a `sftp_name_id_map` struct with the user + * IDs. Can be NULL if only group names are needed. + * + * @param groups_map A pointer to a `sftp_name_id_map` struct with the group + * IDs. Can be NULL if only user names are needed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @note The caller needs to free the memory used for + * the maps later using `sftp_name_id_map_free()`. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_get_users_groups_by_id(sftp_session sftp, + sftp_name_id_map users_map, + sftp_name_id_map groups_map); + +#ifdef WITH_SERVER +/** + * @brief Create a new sftp server session. + * + * @param session The ssh session to use. + * + * @param chan The ssh channel to use. + * + * @return A new sftp server session. + */ +LIBSSH_API sftp_session sftp_server_new(ssh_session session, ssh_channel chan); + +/** + * @brief Initialize the sftp server. + * + * @param sftp The sftp session to init. + * + * @return 0 on success, < 0 on error. + */ +SSH_DEPRECATED LIBSSH_API int sftp_server_init(sftp_session sftp); + +/** + * @brief Close and deallocate a sftp server session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_server_free(sftp_session sftp); +#endif /* WITH_SERVER */ + +/* sftpserver.c */ + +LIBSSH_API sftp_client_message sftp_get_client_message(sftp_session sftp); +LIBSSH_API void sftp_client_message_free(sftp_client_message msg); +LIBSSH_API uint8_t sftp_client_message_get_type(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_filename(sftp_client_message msg); +LIBSSH_API void sftp_client_message_set_filename(sftp_client_message msg, const char *newname); +LIBSSH_API const char *sftp_client_message_get_data(sftp_client_message msg); +LIBSSH_API uint32_t sftp_client_message_get_flags(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_submessage(sftp_client_message msg); +LIBSSH_API int sftp_send_client_message(sftp_session sftp, sftp_client_message msg); +LIBSSH_API int sftp_reply_name(sftp_client_message msg, const char *name, + sftp_attributes attr); +LIBSSH_API int sftp_reply_handle(sftp_client_message msg, ssh_string handle); +LIBSSH_API ssh_string sftp_handle_alloc(sftp_session sftp, void *info); +LIBSSH_API int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr); +LIBSSH_API void *sftp_handle(sftp_session sftp, ssh_string handle); +LIBSSH_API int sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message); +LIBSSH_API int sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr); +LIBSSH_API int sftp_reply_names(sftp_client_message msg); +LIBSSH_API int sftp_reply_data(sftp_client_message msg, const void *data, int len); +LIBSSH_API void sftp_handle_remove(sftp_session sftp, void *handle); + +/* SFTP commands and constants */ +#define SSH_FXP_INIT 1 +#define SSH_FXP_VERSION 2 +#define SSH_FXP_OPEN 3 +#define SSH_FXP_CLOSE 4 +#define SSH_FXP_READ 5 +#define SSH_FXP_WRITE 6 +#define SSH_FXP_LSTAT 7 +#define SSH_FXP_FSTAT 8 +#define SSH_FXP_SETSTAT 9 +#define SSH_FXP_FSETSTAT 10 +#define SSH_FXP_OPENDIR 11 +#define SSH_FXP_READDIR 12 +#define SSH_FXP_REMOVE 13 +#define SSH_FXP_MKDIR 14 +#define SSH_FXP_RMDIR 15 +#define SSH_FXP_REALPATH 16 +#define SSH_FXP_STAT 17 +#define SSH_FXP_RENAME 18 +#define SSH_FXP_READLINK 19 +#define SSH_FXP_SYMLINK 20 + +#define SSH_FXP_STATUS 101 +#define SSH_FXP_HANDLE 102 +#define SSH_FXP_DATA 103 +#define SSH_FXP_NAME 104 +#define SSH_FXP_ATTRS 105 + +#define SSH_FXP_EXTENDED 200 +#define SSH_FXP_EXTENDED_REPLY 201 + +/* attributes */ +/* sftp draft is completely braindead : version 3 and 4 have different flags for same constants */ +/* and even worst, version 4 has same flag for 2 different constants */ +/* follow up : i won't develop any sftp4 compliant library before having a clarification */ + +#define SSH_FILEXFER_ATTR_SIZE 0x00000001 +#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 +#define SSH_FILEXFER_ATTR_ACCESSTIME 0x00000008 +#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 +#define SSH_FILEXFER_ATTR_CREATETIME 0x00000010 +#define SSH_FILEXFER_ATTR_MODIFYTIME 0x00000020 +#define SSH_FILEXFER_ATTR_ACL 0x00000040 +#define SSH_FILEXFER_ATTR_OWNERGROUP 0x00000080 +#define SSH_FILEXFER_ATTR_SUBSECOND_TIMES 0x00000100 +#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 +#define SSH_FILEXFER_ATTR_UIDGID 0x00000002 + +/* types */ +#define SSH_FILEXFER_TYPE_REGULAR 1 +#define SSH_FILEXFER_TYPE_DIRECTORY 2 +#define SSH_FILEXFER_TYPE_SYMLINK 3 +#define SSH_FILEXFER_TYPE_SPECIAL 4 +#define SSH_FILEXFER_TYPE_UNKNOWN 5 + +/** + * @name Server responses + * + * @brief Responses returned by the sftp server. + * @{ + */ + +/** No error */ +#define SSH_FX_OK 0 +/** End-of-file encountered */ +#define SSH_FX_EOF 1 +/** File doesn't exist */ +#define SSH_FX_NO_SUCH_FILE 2 +/** Permission denied */ +#define SSH_FX_PERMISSION_DENIED 3 +/** Generic failure */ +#define SSH_FX_FAILURE 4 +/** Garbage received from server */ +#define SSH_FX_BAD_MESSAGE 5 +/** No connection has been set up */ +#define SSH_FX_NO_CONNECTION 6 +/** There was a connection, but we lost it */ +#define SSH_FX_CONNECTION_LOST 7 +/** Operation not supported by the server */ +#define SSH_FX_OP_UNSUPPORTED 8 +/** Invalid file handle */ +#define SSH_FX_INVALID_HANDLE 9 +/** No such file or directory path exists */ +#define SSH_FX_NO_SUCH_PATH 10 +/** An attempt to create an already existing file or directory has been made */ +#define SSH_FX_FILE_ALREADY_EXISTS 11 +/** We are trying to write on a write-protected filesystem */ +#define SSH_FX_WRITE_PROTECT 12 +/** No media in remote drive */ +#define SSH_FX_NO_MEDIA 13 + +/** @} */ + +/* file flags */ +#define SSH_FXF_READ 0x01 +#define SSH_FXF_WRITE 0x02 +#define SSH_FXF_APPEND 0x04 +#define SSH_FXF_CREAT 0x08 +#define SSH_FXF_TRUNC 0x10 +#define SSH_FXF_EXCL 0x20 +#define SSH_FXF_TEXT 0x40 + +/* file type flags */ +#define SSH_S_IFMT 00170000 +#define SSH_S_IFSOCK 0140000 +#define SSH_S_IFLNK 0120000 +#define SSH_S_IFREG 0100000 +#define SSH_S_IFBLK 0060000 +#define SSH_S_IFDIR 0040000 +#define SSH_S_IFCHR 0020000 +#define SSH_S_IFIFO 0010000 + +/* rename flags */ +#define SSH_FXF_RENAME_OVERWRITE 0x00000001 +#define SSH_FXF_RENAME_ATOMIC 0x00000002 +#define SSH_FXF_RENAME_NATIVE 0x00000004 + +#define SFTP_OPEN SSH_FXP_OPEN +#define SFTP_CLOSE SSH_FXP_CLOSE +#define SFTP_READ SSH_FXP_READ +#define SFTP_WRITE SSH_FXP_WRITE +#define SFTP_LSTAT SSH_FXP_LSTAT +#define SFTP_FSTAT SSH_FXP_FSTAT +#define SFTP_SETSTAT SSH_FXP_SETSTAT +#define SFTP_FSETSTAT SSH_FXP_FSETSTAT +#define SFTP_OPENDIR SSH_FXP_OPENDIR +#define SFTP_READDIR SSH_FXP_READDIR +#define SFTP_REMOVE SSH_FXP_REMOVE +#define SFTP_MKDIR SSH_FXP_MKDIR +#define SFTP_RMDIR SSH_FXP_RMDIR +#define SFTP_REALPATH SSH_FXP_REALPATH +#define SFTP_STAT SSH_FXP_STAT +#define SFTP_RENAME SSH_FXP_RENAME +#define SFTP_READLINK SSH_FXP_READLINK +#define SFTP_SYMLINK SSH_FXP_SYMLINK +#define SFTP_EXTENDED SSH_FXP_EXTENDED + +/* openssh flags */ +#define SSH_FXE_STATVFS_ST_RDONLY 0x1 /* read-only */ +#define SSH_FXE_STATVFS_ST_NOSUID 0x2 /* no setuid */ + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_H */ + +/** @} */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sftp_priv.h b/src/libs/libssh-0.12.2/include/libssh/sftp_priv.h new file mode 100644 index 000000000000..0ddb5b5fd09a --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sftp_priv.h @@ -0,0 +1,120 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SFTP_PRIV_H +#define SFTP_PRIV_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +sftp_packet sftp_packet_read(sftp_session sftp); +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload); +void sftp_packet_free(sftp_packet packet); +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr); +sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname); +/** + * @brief Reply to the SSH_FXP_INIT message with the SSH_FXP_VERSION message + * + * @param client_msg The pointer to client message. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +int sftp_reply_version(sftp_client_message client_msg); +/** + * @brief Decode the data from channel buffer into sftp read_packet. + * + * @param sftp The sftp session handle. + * + * @param data The pointer to the data buffer of channel. + * @param len The data buffer length + * + * @return Length of data decoded. + */ +int sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len); + +void sftp_set_error(sftp_session sftp, int errnum); + +void sftp_message_free(sftp_message msg); + +int sftp_read_and_dispatch(sftp_session sftp); + +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id); + +/** + * @brief Receive the response of an sftp request + * + * In blocking mode, if the response hasn't arrived at the time of call, this + * function waits for the response to arrive. + * + * @param sftp The sftp session via which the request was sent. + * + * @param id The request identifier of the request whose + * corresponding response is required. + * + * @param blocking Flag to indicate the operating mode. true indicates + * blocking mode and false indicates non-blocking mode + * + * @param msg_ptr Pointer to the location to store the response message. + * In case of success, the message is allocated + * dynamically and must be freed (using + * sftp_message_free()) by the caller after usage. In case + * of failure, this is left untouched. + * + * @returns SSH_OK on success + * @returns SSH_ERROR on failure with the sftp and ssh errors set + * @returns SSH_AGAIN in case of non-blocking mode if the response hasn't + * arrived yet. + * + * @warning In blocking mode, this may block indefinitely for an invalid request + * identifier. + */ +int sftp_recv_response_msg(sftp_session sftp, + uint32_t id, + bool blocking, + sftp_message *msg_ptr); + +/** + * @brief Assigns a new SFTP ID for new requests and assures there is no + * collision between them. + * + * @param sftp The sftp session handle. + * @param id_out Pointer to store the new ID. + * + * @returns SSH_OK on success with the new ID stored in *id + * @returns SSH_ERROR on failure with the sftp and ssh errors set + */ +int sftp_get_new_id(sftp_session sftp, uint32_t *id_out); + +sftp_status_message parse_status_msg(sftp_message msg); + +void status_msg_free(sftp_status_message status); + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_PRIV_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sftpserver.h b/src/libs/libssh-0.12.2/include/libssh/sftpserver.h new file mode 100644 index 000000000000..9982830c45d1 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sftpserver.h @@ -0,0 +1,86 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2022 Zeyu Sheng + * Copyright (c) 2023 Red Hat, Inc. + * + * Authors: Jakub Jelen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SFTP_SERVER_H +#define SFTP_SERVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "libssh/libssh.h" +#include "libssh/sftp.h" + +/** + * @defgroup libssh_sftp_server The libssh SFTP server API + * + * @brief SFTP server handling functions + * + * TODO + * + * @{ + */ + +/** + * @brief Macro to declare an SFTP message callback function. + * + * @param name The name of the callback function to declare. + * + * @return SSH_OK for properly processed messages (including errors reported to + * client over the channel and SSH_ERROR for protocol errors + * that the channel callback should treat as fatal. + */ +#define SSH_SFTP_CALLBACK(name) \ + static int name(sftp_client_message message) + +typedef int (*sftp_server_message_callback)(sftp_client_message message); + +struct sftp_message_handler +{ + const char *name; + const char *extended_name; + uint8_t type; + + sftp_server_message_callback cb; +}; + +LIBSSH_API int sftp_channel_default_subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata); +LIBSSH_API int sftp_channel_default_data_callback(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_SERVER_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sk_api.h b/src/libs/libssh-0.12.2/include/libssh/sk_api.h new file mode 100644 index 000000000000..f85f4b8bdc50 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sk_api.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2019 Google LLC + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file is a copy of the OpenSSH project's sk-api.h file pulled from + * https://github.com/openssh/openssh-portable/commit/a9cbe10da2be5be76755af0cea029db0f9c1f263 + * with only the flags, algorithms, error codes, and struct definitions. The + * function declarations and other OpenSSH-specific code have been removed. + */ + +#ifndef SK_API_H +#define SK_API_H 1 + +#include +#include + +/* FIDO2/U2F Operation Flags */ + +/** Requires user presence confirmation (tap/touch) */ +#ifndef SSH_SK_USER_PRESENCE_REQD +#define SSH_SK_USER_PRESENCE_REQD 0x01 +#endif + +/** Requires user verification (PIN/biometric) - FIDO2 only */ +#ifndef SSH_SK_USER_VERIFICATION_REQD +#define SSH_SK_USER_VERIFICATION_REQD 0x04 +#endif + +/** Force resident key enrollment even if a resident key with given user ID + * already exists - FIDO2 only */ +#ifndef SSH_SK_FORCE_OPERATION +#define SSH_SK_FORCE_OPERATION 0x10 +#endif + +/** Create/use resident key stored on authenticator - FIDO2 only */ +#ifndef SSH_SK_RESIDENT_KEY +#define SSH_SK_RESIDENT_KEY 0x20 +#endif + +/* Algorithms */ + +/** ECDSA with P-256 curve */ +#define SSH_SK_ECDSA 0x00 + +/** Ed25519 - FIDO2 only */ +#define SSH_SK_ED25519 0x01 + +/* Error codes */ + +/** General unspecified failure */ +#define SSH_SK_ERR_GENERAL -1 + +/** Requested algorithm/feature/option not supported */ +#define SSH_SK_ERR_UNSUPPORTED -2 + +/** PIN (or other user verification) required but either missing or invalid */ +#define SSH_SK_ERR_PIN_REQUIRED -3 + +/** No suitable security key / authenticator device was found */ +#define SSH_SK_ERR_DEVICE_NOT_FOUND -4 + +/** Attempt to create a resident key that already exists (duplicate) */ +#define SSH_SK_ERR_CREDENTIAL_EXISTS -5 + +/** + * @brief Response structure for FIDO2/U2F key enrollment operations + * + * Contains all data returned by a FIDO2/U2F authenticator after successful + * enrollment of a new credential. + */ +struct sk_enroll_response { + /** @brief FIDO2/U2F authenticator flags from the enrollment operation + * + * Contains flags indicating authenticator capabilities and state during + * enrollment, such as user presence (UP), user verification + * (UV), and resident key. + */ + uint8_t flags; + + /** @brief Public key data in standard format + * + * For ECDSA (P-256): 65 bytes in SEC1 uncompressed point format + * (0x04 prefix + 32-byte X coordinate + 32-byte Y coordinate) + * For Ed25519: 32 bytes containing the raw public key (FIDO2 only) + */ + uint8_t *public_key; + + /** @brief Length of public_key buffer in bytes + * + * Expected values: 65 for ECDSA P-256, 32 for Ed25519 + */ + size_t public_key_len; + + /** @brief Opaque credential handle/ID used to identify this key + * + * Authenticator-generated binary data that uniquely identifies this + * credential. Used in subsequent sign operations to specify which + * key to use. Format and contents are authenticator-specific. + */ + uint8_t *key_handle; + + /** @brief Length of key_handle buffer in bytes + * + * Length varies by authenticator. + */ + size_t key_handle_len; + + /** @brief Enrollment signature over the enrollment data + * + * FIDO2/U2F authenticator signature proving the credential was created + * by this specific authenticator. Used for enrollment verification. + * Format depends on algorithm. + */ + uint8_t *signature; + + /** @brief Length of signature buffer in bytes + * + * Length varies by algorithm. + */ + size_t signature_len; + + /** @brief X.509 attestation certificate + * + * Certificate that attests to the authenticity of the authenticator + * and the enrollment operation. Used to verify the authenticator's + * identity and manufacturer. + */ + uint8_t *attestation_cert; + + /** @brief Length of attestation_cert buffer in bytes */ + size_t attestation_cert_len; + + /** @brief FIDO2/U2F authenticator data from enrollment + * + * CBOR-encoded authenticator data containing RP ID hash, flags, + * counter, and attested credential data. Used for attestation + * verification according to the FIDO2 specification. + */ + uint8_t *authdata; + + /** @brief Length of authdata buffer in bytes + * + * Length varies depending on credential data and extensions. + */ + size_t authdata_len; +}; + +/** + * @brief Response structure for FIDO2/U2F key signing operations + * + * Contains signature components and metadata returned by a FIDO2/U2F + * authenticator after a successful signing operation. + */ +struct sk_sign_response { + /** @brief FIDO2/U2F authenticator flags from the signing operation + * + * Contains flags indicating authenticator state during signing, + * including user presence (UP) and user verification (UV) flags. + * Used to verify that proper user interaction occurred while signing. + */ + uint8_t flags; + + /** @brief Authenticator signature counter value + * + * Monotonically increasing counter maintained by the authenticator. + * Incremented on each successful signing operation. Used to detect + * cloned or duplicated authenticators. + */ + uint32_t counter; + + /** @brief R component of ECDSA signature or Ed25519 signature */ + uint8_t *sig_r; + + /** @brief Length of sig_r buffer in bytes */ + size_t sig_r_len; + + /** @brief S component of ECDSA signature */ + uint8_t *sig_s; + + /** @brief Length of sig_s buffer in bytes */ + size_t sig_s_len; +}; + +/** + * @brief Structure representing a resident/discoverable credential + * + * Represents a FIDO2 resident key (discoverable credential) that is + * stored on the authenticator and can be discovered without providing + * a credential ID. + */ +struct sk_resident_key { + /** @brief Cryptographic algorithm identifier for this key + * + * SSH_SK_ECDSA (0x00): ECDSA with P-256 curve + * SSH_SK_ED25519 (0x01): Ed25519 signature algorithm + */ + uint32_t alg; + + /** @brief Slot/index number of this key on the authenticator + * + * Zero-based index indicating the position of this resident key + * in the authenticator's internal storage. Used for key management + * and identification when multiple resident keys exist. + */ + size_t slot; + + /** @brief Relying Party (application) identifier string + * + * The RP ID (typically a domain name) that this resident key + * is associated with. Determines which application/service + * this key can be used for. + */ + char *application; + + /** @brief Embedded enrollment response containing key material + * + * Contains the same data as returned during initial enrollment, + * including public key, key handle, and associated metadata. + */ + struct sk_enroll_response key; + + /** @brief Flags associated with this resident key + * + * SSH_SK_USER_PRESENCE_REQD: Requires user presence for operations + * SSH_SK_USER_VERIFICATION_REQD: Requires user verification + * (PIN/biometric) + */ + uint8_t flags; + + /** @brief User identifier associated with this resident key + * + * Binary user ID that was provided during key enrollment. + * Used to identify which user account this key belongs to. + */ + uint8_t *user_id; + + /** @brief Length of user_id buffer in bytes + * + * Length of the user identifier. + */ + size_t user_id_len; +}; + +/** + * @brief Configuration option structure for FIDO2/U2F operations + * + * Represents a single configuration parameter that can be passed + * to FIDO2/U2F middleware. + */ +struct sk_option { + /** @brief Option name/key identifier */ + char *name; + + /** @brief Option value as bytes */ + char *value; + + /** @brief Indicates if this option is required for the operation + * + * Non-zero if this option must be processed and cannot be ignored. + * Zero if this option is advisory and can be skipped if the + * middleware does not support it. + */ + uint8_t required; +}; + +/** Current SK API version */ +#define SSH_SK_VERSION_MAJOR 0x000a0000 +#define SSH_SK_VERSION_MAJOR_MASK 0xffff0000 + +#endif /* SK_API_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sk_common.h b/src/libs/libssh-0.12.2/include/libssh/sk_common.h new file mode 100644 index 000000000000..7c18448de2b0 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sk_common.h @@ -0,0 +1,213 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef SK_COMMON_H +#define SK_COMMON_H + +#include "libssh/callbacks.h" +#include "libssh/sk_api.h" + +#include + +#define SK_MAX_USER_ID_LEN 64 + +#define SK_NOT_SUPPORTED_MSG \ + "Security Key functionality is not supported in this build of libssh. " \ + "Please enable support by building using the WITH_FIDO2 build option." + +/** + * @brief Convert security key error code to human-readable string + * + * Converts a security key error code to a descriptive string representation + * that can be used for logging user-facing error messages. + * + * @param[in] sk_err The security key error code to convert. + * + * @return Constant string describing the error. Never returns NULL. + * Returns "Unknown error" for unrecognized error codes. + * + * @note The returned string is statically allocated and should not be freed. + */ +const char *ssh_sk_err_to_string(int sk_err); + +/** + * @brief Securely clear the contents of an sk_enroll_response structure + * + * Overwrites sensitive data within the enrollment response structure with + * zeros to prevent information leakage. This function only clears and frees the + * contents and does not free the structure itself. + * + * @param[in] enroll_response The enrollment response structure to clear. + * Can be NULL (no operation performed). + * + * @note This function only frees the memory for the contents and does not free + * memory for the structure itself. Use sk_enroll_response_free() for complete + * cleanup, which also performs secure clearing internally. + */ +void sk_enroll_response_burn(struct sk_enroll_response *enroll_response); + +/** + * @brief Securely free an sk_enroll_response structure + * + * Performs secure clearing of sensitive data within the enrollment response + * structure before freeing the allocated memory. This function internally + * calls sk_enroll_response_burn() before deallocation. + * + * @param[in] enroll_response The enrollment response structure to free. + * Can be NULL (no operation performed). + * + * @note Developers do not need to call sk_enroll_response_burn() before + * calling this function, as secure clearing is performed automatically. + */ +void sk_enroll_response_free(struct sk_enroll_response *enroll_response); + +/** + * @brief Free an sk_sign_response structure + * + * Frees the memory allocated for a sign response structure and all its + * associated data. This function performs secure clearing of sensitive + * data before deallocation. + * + * @param[in] sign_response The sign response structure to free. + * Can be NULL (no operation performed). + * + * @note This is a secure free operation that clears sensitive data before + * memory deallocation to prevent information leakage. + */ +void sk_sign_response_free(struct sk_sign_response *sign_response); + +/** + * @brief Free an sk_resident_key structure + * + * Frees the memory allocated for a resident key structure and all its + * associated data. This function performs secure clearing of sensitive + * data before deallocation. + * + * @param[in] resident_key The resident key structure to free. + * Can be NULL (no operation performed). + * + * @note This is a secure free operation that clears sensitive data before + * memory deallocation to prevent information leakage. + */ +void sk_resident_key_free(struct sk_resident_key *resident_key); + +/** + * @brief Free an sk_option array and all its contents + * + * Frees a NULL-terminated array of sk_option structures, including all + * allocated memory for option names and values within each structure. + * + * @param[in] options NULL-terminated array of sk_option pointers to free. + * Can be NULL (no operation performed). + * + * @note The options array must be NULL-terminated for proper freeing. + * Each sk_option structure and its name/value strings will be freed. + */ +void sk_options_free(struct sk_option **options); + +/** + * @brief Validate options and extract values for specific keys + * + * Validates that all required options are supported and extracts values + * for the specified keys. This function is primarily intended for use + * by the SK callback implementations. + * + * @param[in] options NULL-terminated array of sk_option pointers to validate. + * @param[in] keys NULL-terminated array of supported option keys. + * @param[out] values Pointer to array that will be allocated and filled with + * copied values (same order as keys). The caller must free + * this array and all contained strings when done. + * + * @return SSH_OK on success, SSH_ERROR if unsupported required options found + * or memory allocation fails. + * + * @note The values array is allocated by this function and contains copies + * of the option values. The caller must free both the array and all + * non-NULL string values within it. Values for keys not found in + * options will be set to NULL. + */ +int sk_options_validate_get(const struct sk_option **options, + const char **keys, + char ***values); + +/** + * @brief Duplicate an array of sk_option structures + * + * Creates a deep copy of an array of security key options. Each option + * structure and its string fields are duplicated. + * + * @param[in] options The array of options to duplicate. Must be + * NULL-terminated array of struct sk_option pointers. + * Can be NULL. + * + * @return A newly allocated array of duplicated options on success, + * NULL on failure or if options is NULL. + * The returned array should be freed with SK_OPTIONS_FREE(). + */ +struct sk_option **sk_options_dup(const struct sk_option **options); + +/** + * @brief Check version compatibility of security key callbacks + * + * Validates that the provided security key callbacks use an SK API + * version whose major portion is the same as the major version that libssh + * supports. + * + * @param[in] callbacks Pointer to the sk_callbacks structure to check. + * + * @return true if the callbacks are compatible, false otherwise. + */ +bool sk_callbacks_check_compatibility( + const struct ssh_sk_callbacks_struct *callbacks); + +/* Convenience macros for secure freeing with NULL checks and pointer reset */ +#define SK_ENROLL_RESPONSE_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_enroll_response_free(x); \ + x = NULL; \ + } \ + } while (0) + +#define SK_SIGN_RESPONSE_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_sign_response_free(x); \ + x = NULL; \ + } \ + } while (0) + +#define SK_RESIDENT_KEY_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_resident_key_free(x); \ + x = NULL; \ + } \ + } while (0) + +#define SK_OPTIONS_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_options_free(x); \ + x = NULL; \ + } \ + } while (0) + +#endif /* SK_COMMON_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sk_usbhid.h b/src/libs/libssh-0.12.2/include/libssh/sk_usbhid.h new file mode 100644 index 000000000000..025c3daf35d3 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sk_usbhid.h @@ -0,0 +1,37 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef SK_USBHID_H +#define SK_USBHID_H + +/** + * @brief Get the USB-HID security key callbacks. + * + * This function returns a pointer to the implementation of + * security key callbacks for FIDO2/U2F devices using the USB-HID + * protocol. + * + * @return Pointer to the ssh_sk_callbacks_struct + * + * @see ssh_sk_callbacks_struct + */ +const struct ssh_sk_callbacks_struct *ssh_sk_get_usbhid_callbacks(void); + +#endif /* SK_USBHID_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sntrup761.h b/src/libs/libssh-0.12.2/include/libssh/sntrup761.h new file mode 100644 index 000000000000..aa05a9fd2030 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sntrup761.h @@ -0,0 +1,82 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * Copyright (c) 2023 Simon Josefsson + * Copyright (c) 2025 Jakub Jelen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SNTRUP761_H_ +#define SNTRUP761_H_ + +#include "config.h" +#include "curve25519.h" +#include "libssh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef HAVE_CURVE25519 +#define HAVE_SNTRUP761 1 +#endif + +/* + * Derived from public domain source, written by (in alphabetical order): + * - Daniel J. Bernstein + * - Chitchanok Chuengsatiansup + * - Tanja Lange + * - Christine van Vredendaal + */ + +#include +#include + +#define SNTRUP761_SECRETKEY_SIZE 1763 +#define SNTRUP761_PUBLICKEY_SIZE 1158 +#define SNTRUP761_CIPHERTEXT_SIZE 1039 +#define SNTRUP761_SIZE 32 + +typedef void sntrup761_random_func(void *ctx, size_t length, uint8_t *dst); + +void sntrup761_keypair(uint8_t *pk, + uint8_t *sk, + void *random_ctx, + sntrup761_random_func *random); +void sntrup761_enc(uint8_t *c, + uint8_t *k, + const uint8_t *pk, + void *random_ctx, + sntrup761_random_func *random); +void sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk); + +typedef unsigned char ssh_sntrup761_pubkey[SNTRUP761_PUBLICKEY_SIZE]; +typedef unsigned char ssh_sntrup761_privkey[SNTRUP761_SECRETKEY_SIZE]; +typedef unsigned char ssh_sntrup761_ciphertext[SNTRUP761_CIPHERTEXT_SIZE]; + +int ssh_client_sntrup761x25519_init(ssh_session session); +void ssh_client_sntrup761x25519_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_sntrup761x25519_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* SNTRUP761_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/socket.h b/src/libs/libssh-0.12.2/include/libssh/socket.h new file mode 100644 index 000000000000..fadd252e2500 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/socket.h @@ -0,0 +1,77 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SOCKET_H_ +#define SOCKET_H_ + +#include "libssh/callbacks.h" +struct ssh_poll_handle_struct; +/* socket.c */ + +struct ssh_socket_struct; +typedef struct ssh_socket_struct* ssh_socket; + +int ssh_socket_init(void); +void ssh_socket_cleanup(void); +ssh_socket ssh_socket_new(ssh_session session); +void ssh_socket_reset(ssh_socket s); +void ssh_socket_free(ssh_socket s); +int ssh_socket_set_fd(ssh_socket s, socket_t fd); +socket_t ssh_socket_get_fd(ssh_socket s); +void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p); +int ssh_socket_unix(ssh_socket s, const char *path); +#if WITH_EXEC +void ssh_execute_command(const char *command, socket_t in, socket_t out); +int ssh_socket_connect_proxycommand(ssh_socket s, const char *command); +#endif +#define VBOX_PROXY_PREFIX "#VBoxProxy" +#define VBOX_PROXY_PREFIX_LENGTH (sizeof(VBOX_PROXY_PREFIX) - 1) +int ssh_socket_connect_proxycommand_vbox(ssh_socket s, const char *host, + uint16_t port, const char *command); +int ssh_socket_connect_proxyjump(ssh_socket s); +void ssh_socket_close(ssh_socket s); +int ssh_socket_write(ssh_socket s,const void *buffer, uint32_t len); +int ssh_socket_is_open(ssh_socket s); +int ssh_socket_fd_isset(ssh_socket s, fd_set *set); +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd); +void ssh_socket_set_fd_in(ssh_socket s, socket_t fd); +void ssh_socket_set_fd_out(ssh_socket s, socket_t fd); +int ssh_socket_nonblocking_flush(ssh_socket s); +void ssh_socket_set_write_wontblock(ssh_socket s); +void ssh_socket_set_read_wontblock(ssh_socket s); +void ssh_socket_set_except(ssh_socket s); +int ssh_socket_get_status(ssh_socket s); +int ssh_socket_get_poll_flags(ssh_socket s); +int ssh_socket_buffered_write_bytes(ssh_socket s); +int ssh_socket_data_available(ssh_socket s); +int ssh_socket_data_writable(ssh_socket s); +int ssh_socket_set_nonblocking(socket_t fd); +int ssh_socket_set_blocking(socket_t fd); + +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks); +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s); +struct ssh_poll_handle_struct * ssh_socket_get_poll_handle(ssh_socket s); + +int ssh_socket_connect(ssh_socket s, + const char *host, + uint16_t port, + const char *bind_addr); + +#endif /* SOCKET_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/ssh2.h b/src/libs/libssh-0.12.2/include/libssh/ssh2.h new file mode 100644 index 000000000000..dc3954383dfe --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/ssh2.h @@ -0,0 +1,90 @@ +#ifndef __SSH2_H +#define __SSH2_H + +#define SSH2_MSG_DISCONNECT 1 +#define SSH2_MSG_IGNORE 2 +#define SSH2_MSG_UNIMPLEMENTED 3 +#define SSH2_MSG_DEBUG 4 +#define SSH2_MSG_SERVICE_REQUEST 5 +#define SSH2_MSG_SERVICE_ACCEPT 6 +#define SSH2_MSG_EXT_INFO 7 + +#define SSH2_MSG_KEXINIT 20 +#define SSH2_MSG_NEWKEYS 21 + +#define SSH2_MSG_KEXDH_INIT 30 +#define SSH2_MSG_KEXDH_REPLY 31 +#define SSH2_MSG_KEX_ECDH_INIT 30 +#define SSH2_MSG_KEX_ECDH_REPLY 31 +#define SSH2_MSG_KEX_HYBRID_INIT 30 +#define SSH2_MSG_KEX_HYBRID_REPLY 31 + +#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 +#define SSH2_MSG_KEX_DH_GEX_INIT 32 +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 +#define SSH2_MSG_KEX_DH_GEX_REQUEST 34 + +#define SSH2_MSG_KEXGSS_INIT 30 +#define SSH2_MSG_KEXGSS_CONTINUE 31 +#define SSH2_MSG_KEXGSS_COMPLETE 32 +#define SSH2_MSG_KEXGSS_HOSTKEY 33 +#define SSH2_MSG_KEXGSS_ERROR 34 +#define SSH2_MSG_KEXGSS_GROUPREQ 40 +#define SSH2_MSG_KEXGSS_GROUP 41 + +#define SSH2_MSG_USERAUTH_REQUEST 50 +#define SSH2_MSG_USERAUTH_FAILURE 51 +#define SSH2_MSG_USERAUTH_SUCCESS 52 +#define SSH2_MSG_USERAUTH_BANNER 53 +#define SSH2_MSG_USERAUTH_PK_OK 60 +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 +#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 +#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 + +#define SSH2_MSG_GLOBAL_REQUEST 80 +#define SSH2_MSG_REQUEST_SUCCESS 81 +#define SSH2_MSG_REQUEST_FAILURE 82 +#define SSH2_MSG_CHANNEL_OPEN 90 +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH2_MSG_CHANNEL_DATA 94 +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH2_MSG_CHANNEL_EOF 96 +#define SSH2_MSG_CHANNEL_CLOSE 97 +#define SSH2_MSG_CHANNEL_REQUEST 98 +#define SSH2_MSG_CHANNEL_SUCCESS 99 +#define SSH2_MSG_CHANNEL_FAILURE 100 + +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 +#define SSH2_DISCONNECT_RESERVED 4 +#define SSH2_DISCONNECT_MAC_ERROR 5 +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH2_DISCONNECT_CONNECTION_LOST 10 +#define SSH2_DISCONNECT_BY_APPLICATION 11 +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH2_OPEN_CONNECT_FAILED 2 +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 + +#define SSH2_EXTENDED_DATA_STDERR 1 + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/string.h b/src/libs/libssh-0.12.2/include/libssh/string.h new file mode 100644 index 000000000000..35b2ea2e97ee --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/string.h @@ -0,0 +1,49 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef STRING_H_ +#define STRING_H_ +#include "libssh/priv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* must be 32 bits number + immediately our data */ +#ifdef _MSC_VER +#pragma pack(1) +#endif +struct ssh_string_struct { + uint32_t size; + unsigned char data[1]; +} +#if defined(__GNUC__) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack() +#endif +; + +#ifdef __cplusplus +} +#endif + +#endif /* STRING_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/threads.h b/src/libs/libssh-0.12.2/include/libssh/threads.h new file mode 100644 index 000000000000..47340d17a7a5 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/threads.h @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef THREADS_H_ +#define THREADS_H_ + +#include +#include + +#if HAVE_PTHREAD + +#include +#define SSH_MUTEX pthread_mutex_t + +#if defined(PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP) +#define SSH_MUTEX_STATIC_INIT PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP +#else +#define SSH_MUTEX_STATIC_INIT PTHREAD_MUTEX_INITIALIZER +#endif + +#elif (defined _WIN32) || (defined _WIN64) + +#include +#include +#define SSH_MUTEX CRITICAL_SECTION * +#define SSH_MUTEX_STATIC_INIT NULL + +#else + +# define SSH_MUTEX void * +#define SSH_MUTEX_STATIC_INIT NULL + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_threads_init(void); +void ssh_threads_finalize(void); +const char *ssh_threads_get_type(void); + +void ssh_mutex_lock(SSH_MUTEX *mutex); +void ssh_mutex_unlock(SSH_MUTEX *mutex); + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void); +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks); +void crypto_thread_finalize(void); + +#ifdef __cplusplus +} +#endif + +#endif /* THREADS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/token.h b/src/libs/libssh-0.12.2/include/libssh/token.h new file mode 100644 index 000000000000..550ad792e2c2 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/token.h @@ -0,0 +1,61 @@ +/* + * token.h - Tokens list handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef TOKEN_H_ +#define TOKEN_H_ + +struct ssh_tokens_st { + char *buffer; + char **tokens; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator); + +void ssh_tokens_free(struct ssh_tokens_st *tokens); + +char *ssh_find_matching(const char *available_d, + const char *preferred_d); + +char *ssh_find_all_matching(const char *available_d, + const char *preferred_d); + +char *ssh_remove_duplicates(const char *list); + +char *ssh_append_without_duplicates(const char *list, + const char *appended_list); +char *ssh_prefix_without_duplicates(const char *list, + const char *prefixed_list); +char *ssh_remove_all_matching(const char *list, + const char *remove_list); + +#ifdef __cplusplus +} +#endif + +#endif /* TOKEN_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/wrapper.h b/src/libs/libssh-0.12.2/include/libssh/wrapper.h new file mode 100644 index 000000000000..9214a928e9e4 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/wrapper.h @@ -0,0 +1,140 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef WRAPPER_H_ +#define WRAPPER_H_ + +#include + +#include "config.h" +#include "libssh/libssh.h" +#include "libssh/libcrypto.h" +#include "libssh/libgcrypt.h" +#include "libssh/libmbedcrypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_kdf_digest { + SSH_KDF_SHA1=1, + SSH_KDF_SHA256, + SSH_KDF_SHA384, + SSH_KDF_SHA512 +}; + +enum ssh_hmac_e { + SSH_HMAC_SHA1 = 1, + SSH_HMAC_SHA256, + SSH_HMAC_SHA512, + SSH_HMAC_MD5, + SSH_HMAC_AEAD_POLY1305, + SSH_HMAC_AEAD_GCM, + SSH_HMAC_NONE, +}; + +enum ssh_des_e { + SSH_3DES, + SSH_DES +}; + +struct ssh_hmac_struct { + const char* name; + enum ssh_hmac_e hmac_type; + bool etm; +}; + +enum ssh_crypto_direction_e { + SSH_DIRECTION_IN = 1, + SSH_DIRECTION_OUT = 2, + SSH_DIRECTION_BOTH = 3, +}; + +struct ssh_cipher_struct; +struct ssh_crypto_struct; + +typedef struct ssh_mac_ctx_struct *ssh_mac_ctx; +MD5CTX md5_init(void); +void md5_ctx_free(MD5CTX); +int md5_update(MD5CTX c, const void *data, size_t len); +int md5_final(unsigned char *md, MD5CTX c); +int md5(const unsigned char *digest, size_t len, unsigned char *hash); + +SHACTX sha1_init(void); +void sha1_ctx_free(SHACTX); +int sha1_update(SHACTX c, const void *data, size_t len); +int sha1_final(unsigned char *md,SHACTX c); +int sha1(const unsigned char *digest,size_t len, unsigned char *hash); + +SHA256CTX sha256_init(void); +void sha256_ctx_free(SHA256CTX); +int sha256_update(SHA256CTX c, const void *data, size_t len); +int sha256_final(unsigned char *md,SHA256CTX c); +int sha256(const unsigned char *digest, size_t len, unsigned char *hash); + +SHA384CTX sha384_init(void); +void sha384_ctx_free(SHA384CTX); +int sha384_update(SHA384CTX c, const void *data, size_t len); +int sha384_final(unsigned char *md,SHA384CTX c); +int sha384(const unsigned char *digest, size_t len, unsigned char *hash); + +SHA512CTX sha512_init(void); +void sha512_ctx_free(SHA512CTX); +int sha512_update(SHA512CTX c, const void *data, size_t len); +int sha512_final(unsigned char *md,SHA512CTX c); +int sha512(const unsigned char *digest, size_t len, unsigned char *hash); + +HMACCTX hmac_init(const void *key,size_t len, enum ssh_hmac_e type); +int hmac_update(HMACCTX c, const void *data, size_t len); +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *len); +size_t hmac_digest_len(enum ssh_hmac_e type); + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len); + +int crypt_set_algorithms_client(ssh_session session); +int crypt_set_algorithms_server(ssh_session session); +struct ssh_crypto_struct *crypto_new(void); +void crypto_free(struct ssh_crypto_struct *crypto); + +void ssh_reseed(void); +int ssh_crypto_init(void); +void ssh_crypto_finalize(void); + +void ssh_cipher_clear(struct ssh_cipher_struct *cipher); +struct ssh_hmac_struct *ssh_get_hmactab(void); +struct ssh_cipher_struct *ssh_get_ciphertab(void); +const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm); + +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L +int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, EVP_PKEY **pkey, int selection); +int evp_dup_dsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_ed25519_pkey(const ssh_key key, ssh_key new_key, int demote); +#endif /* HAVE_LIBCRYPTO && OPENSSL_VERSION_NUMBER */ + +#ifdef __cplusplus +} +#endif + +#endif /* WRAPPER_H_ */ diff --git a/src/libs/libssh-0.12.2/libssh.pc.cmake b/src/libs/libssh-0.12.2/libssh.pc.cmake new file mode 100644 index 000000000000..970db5e2f170 --- /dev/null +++ b/src/libs/libssh-0.12.2/libssh.pc.cmake @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: The SSH Library +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -lssh +Cflags: -I${includedir} +Requires.private: @LIBSSH_PC_REQUIRES_PRIVATE@ diff --git a/src/libs/libssh-0.12.2/src/ABI/current b/src/libs/libssh-0.12.2/src/ABI/current new file mode 100644 index 000000000000..bcd250ed080f --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/current @@ -0,0 +1 @@ +4.12.0 \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.0.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.0.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.1.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.1.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.2.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.2.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.2.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.3.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.3.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.3.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.4.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.4.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.4.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.0.symbols new file mode 100644 index 000000000000..0b1a917ec4e6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.0.symbols @@ -0,0 +1,465 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_get_users_groups_by_id +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_name_id_map_free +sftp_name_id_map_new +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_supported_methods +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_get_sk_application +ssh_key_get_sk_flags +ssh_key_get_sk_user_id +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_ctx_free +ssh_pki_ctx_get_sk_attestation_buffer +ssh_pki_ctx_new +ssh_pki_ctx_options_set +ssh_pki_ctx_set_sk_pin_callback +ssh_pki_ctx_sk_callbacks_option_set +ssh_pki_ctx_sk_callbacks_options_clear +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_generate_key +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_sk_resident_keys_load +ssh_string_burn +ssh_string_cmp +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_from_data +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +sshsig_sign +sshsig_verify +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.1.symbols new file mode 100644 index 000000000000..0b1a917ec4e6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.1.symbols @@ -0,0 +1,465 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_get_users_groups_by_id +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_name_id_map_free +sftp_name_id_map_new +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_supported_methods +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_get_sk_application +ssh_key_get_sk_flags +ssh_key_get_sk_user_id +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_ctx_free +ssh_pki_ctx_get_sk_attestation_buffer +ssh_pki_ctx_new +ssh_pki_ctx_options_set +ssh_pki_ctx_set_sk_pin_callback +ssh_pki_ctx_sk_callbacks_option_set +ssh_pki_ctx_sk_callbacks_options_clear +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_generate_key +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_sk_resident_keys_load +ssh_string_burn +ssh_string_cmp +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_from_data +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +sshsig_sign +sshsig_verify +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.12.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.12.0.symbols new file mode 100644 index 000000000000..0625b89be34e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.12.0.symbols @@ -0,0 +1,467 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_get_users_groups_by_id +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_name_id_map_free +sftp_name_id_map_new +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_supported_methods +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_get_sk_application +ssh_key_get_sk_flags +ssh_key_get_sk_user_id +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_ctx_free +ssh_pki_ctx_get_sk_attestation_buffer +ssh_pki_ctx_new +ssh_pki_ctx_options_set +ssh_pki_ctx_set_sk_pin_callback +ssh_pki_ctx_sk_callbacks_option_set +ssh_pki_ctx_sk_callbacks_options_clear +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_generate_key +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_kex_is_gss +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_sk_resident_keys_load +ssh_string_burn +ssh_string_cmp +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_from_data +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_gssapi_keyex +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +sshsig_sign +sshsig_verify +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.0.symbols new file mode 100644 index 000000000000..1848fe000f96 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.0.symbols @@ -0,0 +1,411 @@ +ssh_set_server_callbacks +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_add_channel_callbacks +ssh_remove_channel_callbacks +ssh_threads_set_callbacks +ssh_threads_get_pthread +ssh_threads_get_noop +ssh_set_log_callback +ssh_get_log_callback +ssh_auth_list +ssh_userauth_offer_pubkey +ssh_userauth_pubkey +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_privatekey_file +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_forward_accept +channel_close +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_shell +channel_request_send_signal +channel_request_sftp +channel_request_subsystem +channel_request_x11 +channel_send_eof +channel_select +channel_set_blocking +channel_write +privatekey_free +privatekey_from_file +publickey_free +ssh_publickey_to_file +publickey_from_file +publickey_from_privatekey +publickey_to_string +ssh_try_publickey_from_file +ssh_privatekey_type +ssh_get_pubkey +ssh_message_retrieve +ssh_message_auth_publickey +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char +ssh_blocking_flush +ssh_channel_accept_x11 +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_timeout +ssh_channel_read_nonblocking +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_shell +ssh_channel_request_send_signal +ssh_channel_request_send_break +ssh_channel_request_sftp +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_request_auth_agent +ssh_channel_send_eof +ssh_channel_select +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_write +ssh_channel_write_stderr +ssh_channel_window_size +ssh_basename +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_new +ssh_connector_free +ssh_connector_set_in_channel +ssh_connector_set_out_channel +ssh_connector_set_in_fd +ssh_connector_set_out_fd +ssh_copyright +ssh_disconnect +ssh_dirname +ssh_finalize +ssh_channel_accept_forward +ssh_channel_cancel_forward +ssh_channel_listen_forward +ssh_free +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_hexa +ssh_get_issue_banner +ssh_get_openssh_version +ssh_get_server_publickey +ssh_get_publickey_hash +ssh_get_pubkey_hash +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_get_publickey +ssh_get_random +ssh_get_version +ssh_get_status +ssh_get_poll_flags +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_knownhosts_entry_free +ssh_known_hosts_parse_line +ssh_session_has_known_hosts_entry +ssh_session_export_known_hosts_entry +ssh_session_update_known_hosts +ssh_session_is_known_server +ssh_set_log_level +ssh_get_log_level +ssh_get_log_userdata +ssh_set_log_userdata +_ssh_log +ssh_log +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_reply_success +ssh_message_free +ssh_message_get +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_options_get +ssh_options_get_port +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_key_new +ssh_key_free +ssh_key_type +ssh_key_type_to_char +ssh_key_type_from_name +ssh_key_is_public +ssh_key_is_private +ssh_key_cmp +ssh_pki_generate +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_export_privkey_file +ssh_pki_copy_cert_to_privkey +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hexa +ssh_send_ignore +ssh_send_debug +ssh_gssapi_set_creds +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_service_request +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_blocking +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_silent_disconnect +ssh_set_pcap_file +ssh_userauth_none +ssh_userauth_list +ssh_userauth_try_publickey +ssh_userauth_publickey +ssh_userauth_agent +ssh_userauth_publickey_auto +ssh_userauth_password +ssh_userauth_kbdint +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_setanswer +ssh_userauth_gssapi +ssh_version +ssh_write_knownhost +ssh_dump_knownhost +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_from_char +ssh_string_len +ssh_string_new +ssh_string_get_char +ssh_string_to_char +ssh_string_free_char +ssh_getpass +ssh_event_new +ssh_event_add_fd +ssh_event_add_session +ssh_event_add_connector +ssh_event_dopoll +ssh_event_remove_fd +ssh_event_remove_session +ssh_event_remove_connector +ssh_event_free +ssh_get_clientbanner +ssh_get_serverbanner +ssh_get_kex_algo +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_hmac_in +ssh_get_hmac_out +ssh_buffer_new +ssh_buffer_free +ssh_buffer_reinit +ssh_buffer_add_data +ssh_buffer_get_data +ssh_buffer_get +ssh_buffer_get_len +ssh_bind_new +ssh_bind_options_set +ssh_bind_listen +ssh_bind_set_callbacks +ssh_bind_set_blocking +ssh_bind_get_fd +ssh_bind_set_fd +ssh_bind_fd_toaccept +ssh_bind_accept +ssh_bind_accept_fd +ssh_gssapi_get_creds +ssh_handle_key_exchange +ssh_server_init_kex +ssh_bind_free +ssh_set_auth_methods +ssh_message_reply_default +ssh_message_auth_user +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_kbdint_is_response +ssh_message_auth_publickey_state +ssh_message_auth_reply_success +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_set_methods +ssh_message_auth_interactive_request +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_global_request_reply_success +ssh_set_message_callback +ssh_execute_message_callbacks +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_channel +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_command +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_single_connection +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_screen_number +ssh_message_global_request_address +ssh_message_global_request_port +ssh_channel_open_reverse_forward +ssh_channel_request_send_exit_status +ssh_channel_request_send_exit_signal +ssh_send_keepalive +ssh_accept +channel_write_stderr +sftp_new +sftp_new_channel +sftp_free +sftp_init +sftp_get_error +sftp_extensions_get_count +sftp_extensions_get_name +sftp_extensions_get_data +sftp_extension_supported +sftp_opendir +sftp_readdir +sftp_dir_eof +sftp_stat +sftp_lstat +sftp_fstat +sftp_attributes_free +sftp_closedir +sftp_close +sftp_open +sftp_file_set_nonblocking +sftp_file_set_blocking +sftp_read +sftp_async_read_begin +sftp_async_read +sftp_write +sftp_seek +sftp_seek64 +sftp_tell +sftp_tell64 +sftp_rewind +sftp_unlink +sftp_rmdir +sftp_mkdir +sftp_rename +sftp_setstat +sftp_chown +sftp_chmod +sftp_utimes +sftp_symlink +sftp_readlink +sftp_statvfs +sftp_fstatvfs +sftp_statvfs_free +sftp_fsync +sftp_canonicalize_path +sftp_server_version +sftp_server_new +sftp_server_init +sftp_get_client_message +sftp_client_message_free +sftp_client_message_get_type +sftp_client_message_get_filename +sftp_client_message_set_filename +sftp_client_message_get_data +sftp_client_message_get_flags +sftp_send_client_message +sftp_reply_name +sftp_reply_handle +sftp_handle_alloc +sftp_reply_attr +sftp_handle +sftp_reply_status +sftp_reply_names_add +sftp_reply_names +sftp_reply_data +sftp_handle_remove \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.1.symbols new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.6.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.6.0.symbols new file mode 100644 index 000000000000..1fd60955ca21 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.6.0.symbols @@ -0,0 +1,412 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.0.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.0.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.1.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.1.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.2.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.2.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.2.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.3.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.3.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.3.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.4.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.4.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.4.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.0.symbols new file mode 100644 index 000000000000..6ef89434f3ef --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.0.symbols @@ -0,0 +1,419 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.1.symbols new file mode 100644 index 000000000000..dce4addd7442 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.1.symbols @@ -0,0 +1,421 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.0.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.0.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.1.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.1.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.2.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.2.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.2.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.3.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.3.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.3.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.4.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.4.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.4.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.5.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.5.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.5.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.6.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.6.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.6.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/CMakeLists.txt b/src/libs/libssh-0.12.2/src/CMakeLists.txt new file mode 100644 index 000000000000..a46f585bebc4 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/CMakeLists.txt @@ -0,0 +1,492 @@ +set(LIBSSH_PUBLIC_INCLUDE_DIRS ${libssh_SOURCE_DIR}/include) + +set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${libssh_BINARY_DIR}/include + ${libssh_BINARY_DIR} +) + +set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_REQUIRED_LIBRARIES} +) + +if (TARGET OpenSSL::Crypto) + list(APPEND LIBSSH_LINK_LIBRARIES OpenSSL::Crypto) +endif () + +if (TARGET MbedTLS::mbedcrypto) + list(APPEND LIBSSH_LINK_LIBRARIES MbedTLS::mbedcrypto) +endif () + +if (TARGET libgcrypt::libgcrypt) + list(APPEND LIBSSH_LINK_LIBRARIES ${GCRYPT_LIBRARIES}) +endif () + +if (WITH_ZLIB) + list(APPEND LIBSSH_LINK_LIBRARIES ZLIB::ZLIB) +endif (WITH_ZLIB) + +if (WITH_GSSAPI AND GSSAPI_FOUND) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${GSSAPI_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${GSSAPI_LIBRARIES} + ) +endif (WITH_GSSAPI AND GSSAPI_FOUND) + +if (WITH_NACL AND NACL_FOUND) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${NACL_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${NACL_LIBRARY} + ) +endif (WITH_NACL AND NACL_FOUND) + +if (MINGW AND Threads_FOUND) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + Threads::Threads + ) +endif() + +if (HAVE_LIBFIDO2) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${LIBFIDO2_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${LIBFIDO2_LIBRARIES} + ) +endif (HAVE_LIBFIDO2) + +# The ws2_32 needs to be last for mingw to build +# https://gitlab.com/libssh/libssh-mirror/-/issues/84 +if (WIN32) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + iphlpapi + ws2_32 + ) +endif (WIN32) + +if (BUILD_STATIC_LIB) + set(LIBSSH_STATIC_LIBRARY + ssh_static + CACHE INTERNAL "libssh static library" + ) +endif (BUILD_STATIC_LIB) + +set(libssh_SRCS + agent.c + auth.c + base64.c + bignum.c + buffer.c + callbacks.c + channels.c + client.c + config.c + connect.c + connector.c + crypto_common.c + curve25519.c + sntrup761.c + dh.c + ecdh.c + error.c + getpass.c + gzip.c + hybrid_mlkem.c + init.c + kdf.c + kex.c + known_hosts.c + knownhosts.c + legacy.c + log.c + match.c + messages.c + misc.c + mlkem.c + options.c + packet.c + packet_cb.c + packet_crypt.c + pcap.c + pki.c + pki_context.c + pki_container_openssh.c + poll.c + session.c + scp.c + socket.c + string.c + threads.c + ttyopts.c + wrapper.c + external/bcrypt_pbkdf.c + external/blowfish.c + config_parser.c + token.c + pki_ed25519_common.c +) + +if (DEFAULT_C_NO_DEPRECATION_FLAGS) + set_source_files_properties(known_hosts.c + PROPERTIES + COMPILE_FLAGS ${DEFAULT_C_NO_DEPRECATION_FLAGS}) +endif() + +if (CMAKE_USE_PTHREADS_INIT) + set(libssh_SRCS + ${libssh_SRCS} + threads/noop.c + threads/pthread.c + ) +elseif (CMAKE_USE_WIN32_THREADS_INIT) + set(libssh_SRCS + ${libssh_SRCS} + threads/noop.c + threads/winlocks.c + ) +else() + set(libssh_SRCS + ${libssh_SRCS} + threads/noop.c + ) +endif() + +if (WITH_GCRYPT) + set(libssh_SRCS + ${libssh_SRCS} + threads/libgcrypt.c + libgcrypt.c + gcrypt_missing.c + pki_gcrypt.c + ecdh_gcrypt.c + getrandom_gcrypt.c + md_gcrypt.c + dh_key.c + pki_ed25519.c + external/ed25519.c + external/fe25519.c + external/ge25519.c + external/sc25519.c + ) + if (NOT HAVE_GCRYPT_CHACHA_POLY) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif (NOT HAVE_GCRYPT_CHACHA_POLY) + + if (HAVE_GCRYPT_CURVE25519) + set(libssh_SRCS + ${libssh_SRCS} + curve25519_gcrypt.c + ) + endif(HAVE_GCRYPT_CURVE25519) + + if (HAVE_GCRYPT_MLKEM) + set(libssh_SRCS + ${libssh_SRCS} + mlkem_gcrypt.c + ) + endif (HAVE_GCRYPT_MLKEM) +elseif (WITH_MBEDTLS) + set(libssh_SRCS + ${libssh_SRCS} + threads/mbedtls.c + libmbedcrypto.c + mbedcrypto_missing.c + pki_mbedcrypto.c + ecdh_mbedcrypto.c + getrandom_mbedcrypto.c + md_mbedcrypto.c + dh_key.c + pki_ed25519.c + external/ed25519.c + external/fe25519.c + external/ge25519.c + external/sc25519.c + external/sntrup761.c + ) + if (NOT (HAVE_MBEDTLS_CHACHA20_H AND HAVE_MBEDTLS_POLY1305_H)) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif() + if (HAVE_MBEDTLS_CURVE25519) + set(libssh_SRCS + ${libssh_SRCS} + curve25519_mbedcrypto.c + ) + endif(HAVE_MBEDTLS_CURVE25519) +else (WITH_GCRYPT) + set(libssh_SRCS + ${libssh_SRCS} + threads/libcrypto.c + pki_crypto.c + ecdh_crypto.c + curve25519_crypto.c + getrandom_crypto.c + md_crypto.c + libcrypto.c + dh_crypto.c + external/sntrup761.c + ) + if (NOT HAVE_OPENSSL_EVP_CHACHA20) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif (NOT HAVE_OPENSSL_EVP_CHACHA20) + if (HAVE_OPENSSL_MLKEM) + set(libssh_SRCS + ${libssh_SRCS} + mlkem_crypto.c + ) + endif (HAVE_OPENSSL_MLKEM) +endif (WITH_GCRYPT) + +if (WITH_SFTP) + set(libssh_SRCS + ${libssh_SRCS} + sftp.c + sftp_common.c + sftp_aio.c + ) + + if (WITH_SERVER) + set(libssh_SRCS + ${libssh_SRCS} + sftpserver.c + ) + endif (WITH_SERVER) +endif (WITH_SFTP) + +if (WITH_SERVER) + set(libssh_SRCS + ${libssh_SRCS} + server.c + bind.c + bind_config.c + ) +endif (WITH_SERVER) + +if (WITH_GEX) + set(libssh_SRCS + ${libssh_SRCS} + dh-gex.c + ) +endif (WITH_GEX) + +if (WITH_GSSAPI AND GSSAPI_FOUND) + set(libssh_SRCS + ${libssh_SRCS} + gssapi.c + kex-gss.c + ) +endif (WITH_GSSAPI AND GSSAPI_FOUND) + +if (NOT WITH_NACL) + if (NOT (HAVE_LIBCRYPTO OR HAVE_MBEDTLS_CURVE25519 OR HAVE_GCRYPT_CURVE25519)) + set(libssh_SRCS + ${libssh_SRCS} + curve25519_fallback.c + external/curve25519_ref.c + ) + endif() +endif (NOT WITH_NACL) + +if (NOT HAVE_MLKEM1024) + set(libssh_SRCS + ${libssh_SRCS} + mlkem_native.c + external/libcrux_mlkem768_sha3.c + ) + if (WITH_WERROR_DECLARATION_AFTER_STATEMENT_FLAG) + set_source_files_properties(external/libcrux_mlkem768_sha3.c + PROPERTIES + COMPILE_FLAGS -Wno-error=declaration-after-statement) + endif() +endif() + +if (WITH_FIDO2) + set(libssh_SRCS + ${libssh_SRCS} + sk_common.c + pki_sk.c + ) + + if (HAVE_LIBFIDO2) + set(libssh_SRCS + ${libssh_SRCS} + sk_usbhid.c + ) + endif (HAVE_LIBFIDO2) +endif (WITH_FIDO2) + +# Set the path to the default map file +set(MAP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.map") + +if (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT AND ABIMAP_FOUND) + # Get the list of header files + get_file_list(dev_header_list + DIRECTORIES "${LIBSSH_PUBLIC_INCLUDE_DIRS}/libssh" + FILES_PATTERNS "*.h") + + # Extract the symbols marked as "LIBSSH_API" from the header files + extract_symbols("${PROJECT_NAME}_dev.symbols" + HEADERS_LIST dev_header_list + FILTER_PATTERN "LIBSSH_API") + + if (WITH_ABI_BREAK) + set(ALLOW_ABI_BREAK "BREAK_ABI") + endif() + + # Generate the symbol version map file + generate_map_file("${PROJECT_NAME}_dev.map" + SYMBOLS "${PROJECT_NAME}_dev.symbols" + RELEASE_NAME_VERSION ${PROJECT_NAME}_AFTER_${LIBRARY_VERSION} + CURRENT_MAP ${MAP_PATH} + ${ALLOW_ABI_BREAK}) + + set(libssh_SRCS + ${libssh_SRCS} + ${PROJECT_NAME}_dev.map + ) +endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT AND ABIMAP_FOUND) + +# This gets built as a static library, if -DBUILD_SHARED_LIBS=OFF is passed to +# cmake. +add_library(ssh ${libssh_SRCS}) +target_compile_options(ssh + PRIVATE + ${DEFAULT_C_COMPILE_FLAGS}) +if (CYGWIN) + target_compile_definitions(ssh PRIVATE _GNU_SOURCE) +endif () +target_include_directories(ssh + PUBLIC + $ + $ + $ + PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) + +target_link_libraries(ssh + PRIVATE ${LIBSSH_LINK_LIBRARIES}) + +if (WIN32 AND NOT BUILD_SHARED_LIBS) + target_compile_definitions(ssh PUBLIC "LIBSSH_STATIC") +endif () + +add_library(ssh::ssh ALIAS ssh) + +if (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT) + if (ABIMAP_FOUND) + # Change path to devel map file + set(MAP_PATH "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_dev.map") + endif (ABIMAP_FOUND) + + target_link_libraries(ssh PRIVATE "-Wl,--version-script,\"${MAP_PATH}\"") +endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT) + +set_target_properties(ssh + PROPERTIES + C_STANDARD + 99 + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + DEFINE_SYMBOL + LIBSSH_EXPORTS +) + +if (WITH_VISIBILITY_HIDDEN) + set_target_properties(ssh PROPERTIES C_VISIBILITY_PRESET hidden) +endif (WITH_VISIBILITY_HIDDEN) + +if (MINGW) + target_link_libraries(ssh PRIVATE "-Wl,--enable-stdcall-fixup") + target_compile_definitions(ssh PRIVATE "_POSIX_SOURCE") +endif () +if (WITH_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags_to_target(ssh) +endif (WITH_COVERAGE) + + +install(TARGETS ssh + EXPORT libssh-config + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT libraries) + +install(EXPORT libssh-config + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) + +if (BUILD_STATIC_LIB) + add_library(ssh-static STATIC ${libssh_SRCS}) + target_compile_options(ssh-static + PRIVATE + ${DEFAULT_C_COMPILE_FLAGS}) + if (CYGWIN) + target_compile_definitions(ssh-static PRIVATE _GNU_SOURCE) + endif () + + target_include_directories(ssh-static + PUBLIC + $ + $ + $ + PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) + target_link_libraries(ssh-static + PUBLIC ${LIBSSH_LINK_LIBRARIES}) + add_library(ssh::static ALIAS ssh-static) + + if (MSVC) + set(OUTPUT_SUFFIX static) + else (MSVC) + set(OUTPUT_SUFFIX ) + endif (MSVC) + set_target_properties( + ssh-static + PROPERTIES + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + OUTPUT_NAME + ssh + ARCHIVE_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUFFIX} + ) + + if (WIN32) + target_compile_definitions(ssh-static PUBLIC "LIBSSH_STATIC") + endif (WIN32) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(ssh-static) + endif (WITH_COVERAGE) +endif (BUILD_STATIC_LIB) + +message(STATUS "Threads_FOUND=${Threads_FOUND}") diff --git a/src/libs/libssh-0.12.2/src/agent.c b/src/libs/libssh-0.12.2/src/agent.c new file mode 100644 index 000000000000..bb1669c34557 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/agent.c @@ -0,0 +1,637 @@ +/* + * agent.c - ssh agent functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* This file is based on authfd.c from OpenSSH */ + +/* + * How does the ssh-agent work? + * + * a) client sends a request to get a list of all keys + * the agent returns the count and all public keys + * b) iterate over them to check if the server likes one + * c) the client sends a sign request to the agent + * type, pubkey as blob, data to sign, flags + * the agent returns the signed data + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifndef _WIN32 +#include +#include +#include +#else +#include +#include +#endif + +#include "libssh/agent.h" +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/poll.h" +#include "libssh/pki.h" +#include "libssh/bytearray.h" + +/* macro to check for "agent failure" message */ +#define agent_failed(x) \ + (((x) == SSH_AGENT_FAILURE) || ((x) == SSH_COM_AGENT2_FAILURE) || \ + ((x) == SSH2_AGENT_FAILURE)) + +static uint32_t +atomicio(struct ssh_agent_struct *agent, void *buf, uint32_t n, int do_read) +{ + char *b = buf; + uint32_t pos = 0; + ssize_t res; + ssh_pollfd_t pfd; + ssh_channel channel = agent->channel; + socket_t fd; + + /* Using a socket ? */ + if (channel == NULL) { + fd = ssh_socket_get_fd(agent->sock); + pfd.fd = fd; + pfd.events = do_read ? POLLIN : POLLOUT; + + while (n > pos) { + if (do_read) { + res = recv(fd, b + pos, n - pos, 0); + } else { + res = send(fd, b + pos, n - pos, 0); + } + switch (res) { + case -1: + if (errno == EINTR) { + continue; + } +#ifdef EWOULDBLOCK + if (errno == EAGAIN || errno == EWOULDBLOCK) { +#else + if (errno == EAGAIN) { +#endif + (void)ssh_poll(&pfd, 1, -1); + continue; + } + return 0; + case 0: + /* read returns 0 on end-of-file */ + errno = do_read ? 0 : EPIPE; + return pos; + default: + pos += (uint32_t)res; + } + } + return pos; + } else { + /* using an SSH channel */ + while (n > pos) { + if (do_read) { + res = ssh_channel_read(channel, b + pos, n - pos, 0); + } else { + res = ssh_channel_write(channel, b + pos, n - pos); + } + if (res == SSH_AGAIN) { + continue; + } + if (res == SSH_ERROR) { + return 0; + } + pos += (uint32_t)res; + } + return pos; + } +} + +ssh_agent ssh_agent_new(struct ssh_session_struct *session) +{ + ssh_agent agent = NULL; + + agent = calloc(1, sizeof(struct ssh_agent_struct)); + if (agent == NULL) { + return NULL; + } + + agent->count = 0; + agent->sock = ssh_socket_new(session); + if (agent->sock == NULL) { + SAFE_FREE(agent); + return NULL; + } + agent->channel = NULL; + return agent; +} + +static void agent_set_channel(struct ssh_agent_struct *agent, + ssh_channel channel) +{ + agent->channel = channel; +} + +/** + * @addtogroup libssh_auth + * + * @{ + */ + +/** @brief sets the SSH agent channel. + * The SSH agent channel will be used to authenticate this client using + * an agent through a channel, from another session. The most likely use + * is to implement SSH Agent forwarding into a SSH proxy. + * + * @param session the session + * + * @param[in] channel a SSH channel from another session. + * + * @returns SSH_OK in case of success + * SSH_ERROR in case of an error + */ +int ssh_set_agent_channel(ssh_session session, ssh_channel channel) +{ + if (!session) { + return SSH_ERROR; + } + if (!session->agent) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Session has no active agent"); + return SSH_ERROR; + } + agent_set_channel(session->agent, channel); + return SSH_OK; +} + +/** @brief sets the SSH agent socket. + * The SSH agent will be used to authenticate this client using + * the given socket to communicate with the ssh-agent. The caller + * is responsible for connecting to the socket prior to calling + * this function. + * @returns SSH_OK in case of success + * SSH_ERROR in case of an error + */ +int ssh_set_agent_socket(ssh_session session, socket_t fd) +{ + if (!session) { + return SSH_ERROR; + } + if (!session->agent) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Session has no active agent"); + return SSH_ERROR; + } + + return ssh_socket_set_fd(session->agent->sock, fd); +} + +/** + * @} + */ + +void ssh_agent_close(struct ssh_agent_struct *agent) +{ + if (agent == NULL) { + return; + } + + ssh_socket_close(agent->sock); +} + +void ssh_agent_free(ssh_agent agent) +{ + if (agent) { + if (agent->ident) { + SSH_BUFFER_FREE(agent->ident); + } + if (agent->sock) { + ssh_agent_close(agent); + ssh_socket_free(agent->sock); + } + SAFE_FREE(agent); + } +} + +static int agent_connect(ssh_session session) +{ + const char *auth_sock = NULL; + + if (session == NULL || session->agent == NULL) { + return -1; + } + + if (session->agent->channel != NULL) { + return 0; + } + + auth_sock = session->opts.agent_socket ? session->opts.agent_socket + : getenv("SSH_AUTH_SOCK"); + + if (auth_sock && *auth_sock) { + if (ssh_socket_unix(session->agent->sock, auth_sock) < 0) { + return -1; + } + return 0; + } + + return -1; +} + +#if 0 +static int agent_decode_reply(struct ssh_session_struct *session, int type) { + switch (type) { + case SSH_AGENT_FAILURE: + case SSH2_AGENT_FAILURE: + case SSH_COM_AGENT2_FAILURE: + ssh_log(session, SSH_LOG_RARE, "SSH_AGENT_FAILURE"); + return 0; + case SSH_AGENT_SUCCESS: + return 1; + default: + ssh_set_error(session, SSH_FATAL, + "Bad response from authentication agent: %d", type); + break; + } + + return -1; +} +#endif + +static int agent_talk(struct ssh_session_struct *session, + struct ssh_buffer_struct *request, + struct ssh_buffer_struct *reply) +{ + uint32_t len = 0; + uint8_t tmpbuf[4]; + uint8_t *payload = tmpbuf; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + len = ssh_buffer_get_len(request); + SSH_LOG(SSH_LOG_TRACE, "Request length: %" PRIu32, len); + PUSH_BE_U32(payload, 0, len); + + /* send length and then the request packet */ + if (atomicio(session->agent, payload, 4, 0) == 4) { + if (atomicio(session->agent, ssh_buffer_get(request), len, 0) != len) { + SSH_LOG(SSH_LOG_TRACE, + "atomicio sending request failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "atomicio sending request length failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + /* wait for response, read the length of the response packet */ + if (atomicio(session->agent, payload, 4, 1) != 4) { + SSH_LOG(SSH_LOG_TRACE, + "atomicio read response length failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + len = PULL_BE_U32(payload, 0); + if (len > 256 * 1024) { + ssh_set_error(session, + SSH_FATAL, + "Authentication response too long: %" PRIu32, + len); + return -1; + } + SSH_LOG(SSH_LOG_TRACE, "Response length: %" PRIu32, len); + + payload = ssh_buffer_allocate(reply, len); + if (payload == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Not enough space"); + return -1; + } + + if (atomicio(session->agent, payload, len, 1) != len) { + SSH_LOG(SSH_LOG_DEBUG, + "Error reading response from authentication socket."); + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(reply, len); + return -1; + } + + return 0; +} + +uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) +{ + ssh_buffer request = NULL; + ssh_buffer reply = NULL; + unsigned int type = 0; + uint32_t count = 0; + uint32_t rc; + + /* send message to the agent requesting the list of identities */ + request = ssh_buffer_new(); + if (request == NULL) { + ssh_set_error_oom(session); + return 0; + } + if (ssh_buffer_add_u8(request, SSH2_AGENTC_REQUEST_IDENTITIES) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(request); + return 0; + } + + reply = ssh_buffer_new(); + if (reply == NULL) { + SSH_BUFFER_FREE(request); + ssh_set_error(session, SSH_FATAL, "Not enough space"); + return 0; + } + + if (agent_talk(session, request, reply) < 0) { + SSH_BUFFER_FREE(request); + SSH_BUFFER_FREE(reply); + return 0; + } + SSH_BUFFER_FREE(request); + + /* get message type and verify the answer */ + rc = ssh_buffer_get_u8(reply, (uint8_t *) &type); + if (rc != sizeof(uint8_t)) { + ssh_set_error(session, SSH_FATAL, + "Bad authentication reply size: %" PRIu32, rc); + SSH_BUFFER_FREE(reply); + return 0; + } +#ifdef WORDS_BIGENDIAN + type = bswap_32(type); +#endif + + SSH_LOG(SSH_LOG_TRACE, + "Answer type: %d, expected answer: %d", + type, SSH2_AGENT_IDENTITIES_ANSWER); + + if (agent_failed(type)) { + SSH_BUFFER_FREE(reply); + return 0; + } else if (type != SSH2_AGENT_IDENTITIES_ANSWER) { + ssh_set_error(session, SSH_FATAL, + "Bad authentication reply message type: %u", type); + SSH_BUFFER_FREE(reply); + return 0; + } + + rc = ssh_buffer_get_u32(reply, &count); + if (rc != 4) { + ssh_set_error(session, + SSH_FATAL, + "Failed to read count"); + SSH_BUFFER_FREE(reply); + return 0; + } + session->agent->count = ntohl(count); + SSH_LOG(SSH_LOG_DEBUG, "Agent count: %d", + session->agent->count); + if (session->agent->count > 1024) { + ssh_set_error(session, SSH_FATAL, + "Too many identities in authentication reply: %d", + session->agent->count); + SSH_BUFFER_FREE(reply); + return 0; + } + + ssh_buffer_free(session->agent->ident); + session->agent->ident = reply; + + return session->agent->count; +} + +/* caller has to free comment */ +ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, + char **comment) { + if (ssh_agent_get_ident_count(session) > 0) { + return ssh_agent_get_next_ident(session, comment); + } + + return NULL; +} + +/* caller has to free comment */ +ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, + char **comment) +{ + struct ssh_key_struct *key = NULL; + struct ssh_string_struct *blob = NULL; + struct ssh_string_struct *tmp = NULL; + int rc; + + if (session->agent->count == 0) { + return NULL; + } + + /* get the blob */ + blob = ssh_buffer_get_ssh_string(session->agent->ident); + if (blob == NULL) { + return NULL; + } + + /* get the comment */ + tmp = ssh_buffer_get_ssh_string(session->agent->ident); + if (tmp == NULL) { + SSH_STRING_FREE(blob); + + return NULL; + } + + if (comment) { + *comment = ssh_string_to_char(tmp); + } else { + SSH_STRING_FREE(blob); + SSH_STRING_FREE(tmp); + + return NULL; + } + SSH_STRING_FREE(tmp); + + /* get key from blob */ + rc = ssh_pki_import_pubkey_blob(blob, &key); + if (rc == SSH_ERROR) { + /* Try again as a cert. */ + rc = ssh_pki_import_cert_blob(blob, &key); + } + SSH_STRING_FREE(blob); + if (rc == SSH_ERROR) { + return NULL; + } + + return key; +} + +int ssh_agent_is_running(ssh_session session) +{ + if (session == NULL || session->agent == NULL) { + return 0; + } + + if (ssh_socket_is_open(session->agent->sock)) { + return 1; + } else { + if (agent_connect(session) < 0) { + return 0; + } else { + return 1; + } + } + + return 0; +} + +ssh_string ssh_agent_sign_data(ssh_session session, + const ssh_key pubkey, + struct ssh_buffer_struct *data) +{ + ssh_buffer request = NULL; + ssh_buffer reply = NULL; + ssh_string key_blob = NULL; + ssh_string sig_blob = NULL; + unsigned int type = 0; + unsigned int flags = 0; + uint32_t dlen; + size_t request_len; + int rc; + + request = ssh_buffer_new(); + if (request == NULL) { + return NULL; + } + + /* create request */ + if (ssh_buffer_add_u8(request, SSH2_AGENTC_SIGN_REQUEST) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &key_blob); + if (rc < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* + * make sure it already can contain all the expected content: + * - 1 x uint8_t + * - 2 x uint32_t + * - 1 x ssh_string (uint8_t + data) + */ + request_len = sizeof(uint8_t) * 2 + + sizeof(uint32_t) * 2 + + ssh_string_len(key_blob); + /* this can't overflow the uint32_t as the + * STRING_SIZE_MAX is (UINT32_MAX >> 8) + 1 */ + rc = ssh_buffer_allocate_size(request, (uint32_t)request_len); + if (rc < 0) { + SSH_STRING_FREE(key_blob); + SSH_BUFFER_FREE(request); + return NULL; + } + + /* adds len + blob */ + rc = ssh_buffer_add_ssh_string(request, key_blob); + SSH_STRING_FREE(key_blob); + if (rc < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* Add data */ + dlen = ssh_buffer_get_len(data); + if (ssh_buffer_add_u32(request, htonl(dlen)) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + if (ssh_buffer_add_data(request, ssh_buffer_get(data), dlen) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* Add Flags: SHA2 extension (RFC 8332) if negotiated */ + if (ssh_key_type_plain(pubkey->type) == SSH_KEYTYPE_RSA) { + if (session->extensions & SSH_EXT_SIG_RSA_SHA512) { + flags |= SSH_AGENT_RSA_SHA2_512; + } else if (session->extensions & SSH_EXT_SIG_RSA_SHA256) { + flags |= SSH_AGENT_RSA_SHA2_256; + } + } + if (ssh_buffer_add_u32(request, htonl(flags)) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + reply = ssh_buffer_new(); + if (reply == NULL) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* send the request */ + if (agent_talk(session, request, reply) < 0) { + SSH_BUFFER_FREE(request); + SSH_BUFFER_FREE(reply); + return NULL; + } + SSH_BUFFER_FREE(request); + + /* check if reply is valid */ + if (ssh_buffer_get_u8(reply, (uint8_t *) &type) != sizeof(uint8_t)) { + SSH_BUFFER_FREE(reply); + return NULL; + } +#ifdef WORDS_BIGENDIAN + type = bswap_32(type); +#endif + + if (agent_failed(type)) { + SSH_LOG(SSH_LOG_DEBUG, "Agent reports failure in signing the key"); + SSH_BUFFER_FREE(reply); + return NULL; + } else if (type != SSH2_AGENT_SIGN_RESPONSE) { + ssh_set_error(session, + SSH_FATAL, + "Bad authentication response: %u", + type); + SSH_BUFFER_FREE(reply); + return NULL; + } + + sig_blob = ssh_buffer_get_ssh_string(reply); + SSH_BUFFER_FREE(reply); + + return sig_blob; +} diff --git a/src/libs/libssh-0.12.2/src/auth.c b/src/libs/libssh-0.12.2/src/auth.c new file mode 100644 index 000000000000..b4b8fc4ed3b9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/auth.c @@ -0,0 +1,2580 @@ +/* + * auth.c - Authentication with SSH protocols + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * Copyright (c) 2008-2013 Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/agent.h" +#include "libssh/auth.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/gssapi.h" +#include "libssh/keys.h" +#include "libssh/legacy.h" +#include "libssh/misc.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +/** + * @defgroup libssh_auth The SSH authentication functions + * @ingroup libssh + * + * Functions to authenticate with a server. + * + * @{ + */ + +/** + * @internal + * + * @brief Ask for access to the ssh-userauth service. + * + * @param[in] session The SSH session handle. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN on nonblocking mode, if calling that function + * again is necessary + */ +static int ssh_userauth_request_service(ssh_session session) +{ + int rc; + + rc = ssh_service_request(session, "ssh-userauth"); + if ((rc != SSH_OK) && (rc != SSH_AGAIN)) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to request \"ssh-userauth\" service"); + } + + return rc; +} + +static int ssh_auth_response_termination(void *user) +{ + ssh_session session = (ssh_session)user; + switch (session->auth.state) { + case SSH_AUTH_STATE_NONE: + case SSH_AUTH_STATE_KBDINT_SENT: + case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: + case SSH_AUTH_STATE_GSSAPI_TOKEN: + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT: + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: + case SSH_AUTH_STATE_AUTH_NONE_SENT: + return 0; + default: + return 1; + } +} + +static const char *ssh_auth_get_current_method(ssh_session session) +{ + const char *method = "unknown"; + + switch (session->auth.current_method) { + case SSH_AUTH_METHOD_NONE: + method = "none"; + break; + case SSH_AUTH_METHOD_PASSWORD: + method = "password"; + break; + case SSH_AUTH_METHOD_PUBLICKEY: + method = "publickey"; + break; + case SSH_AUTH_METHOD_HOSTBASED: + method = "hostbased"; + break; + case SSH_AUTH_METHOD_INTERACTIVE: + method = "keyboard interactive"; + break; +#ifdef WITH_GSSAPI + case SSH_AUTH_METHOD_GSSAPI_MIC: + method = "gssapi"; + break; + case SSH_AUTH_METHOD_GSSAPI_KEYEX: + method = "gssapi-keyex"; + break; +#endif + default: + break; + } + + return method; +} + +/** + * @internal + * @brief Wait for a response of an authentication function. + * + * @param[in] session The SSH session. + * + * @returns SSH_AUTH_SUCCESS Authentication success, or pubkey accepted + * SSH_AUTH_PARTIAL Authentication succeeded but another mean + * of authentication is needed. + * SSH_AUTH_INFO Data for keyboard-interactive + * SSH_AUTH_AGAIN In nonblocking mode, call has to be made again + * SSH_AUTH_ERROR Error during the process. + */ +static int ssh_userauth_get_response(ssh_session session) +{ + int rc = SSH_AUTH_ERROR; + + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_auth_response_termination, session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + if (!ssh_auth_response_termination(session)) { + return SSH_AUTH_AGAIN; + } + + switch(session->auth.state) { + case SSH_AUTH_STATE_ERROR: + rc = SSH_AUTH_ERROR; + break; + case SSH_AUTH_STATE_FAILED: + rc = SSH_AUTH_DENIED; + break; + case SSH_AUTH_STATE_INFO: + rc = SSH_AUTH_INFO; + break; + case SSH_AUTH_STATE_PARTIAL: + rc = SSH_AUTH_PARTIAL; + break; + case SSH_AUTH_STATE_PK_OK: + case SSH_AUTH_STATE_SUCCESS: + rc = SSH_AUTH_SUCCESS; + break; + case SSH_AUTH_STATE_KBDINT_SENT: + case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: + case SSH_AUTH_STATE_GSSAPI_TOKEN: + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT: + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: + case SSH_AUTH_STATE_AUTH_NONE_SENT: + case SSH_AUTH_STATE_NONE: + /* not reached */ + rc = SSH_AUTH_ERROR; + break; + } + + return rc; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_BANNER packet. + * + * This banner should be shown to user prior to authentication + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner) +{ + ssh_string banner = NULL; + (void)type; + (void)user; + + banner = ssh_buffer_get_ssh_string(packet); + if (banner == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid SSH_USERAUTH_BANNER packet"); + } else { + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_USERAUTH_BANNER packet"); + if (session->banner != NULL) + SSH_STRING_FREE(session->banner); + session->banner = banner; + } + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_FAILURE packet. + * + * This handles the complete or partial authentication failure. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { + const char *current_method = ssh_auth_get_current_method(session); + char *auth_methods = NULL; + uint8_t partial = 0; + int rc; + (void) type; + (void) user; + + rc = ssh_buffer_unpack(packet, "sb", &auth_methods, &partial); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, + "Invalid SSH_MSG_USERAUTH_FAILURE message"); + session->auth.state = SSH_AUTH_STATE_ERROR; + goto end; + } + + if (partial) { + session->auth.state = SSH_AUTH_STATE_PARTIAL; + SSH_LOG(SSH_LOG_DEBUG, + "Partial success for '%s'. Authentication that can continue: %s", + current_method, + auth_methods); + } else { + session->auth.state = SSH_AUTH_STATE_FAILED; + ssh_set_error(session, SSH_REQUEST_DENIED, + "Access denied for '%s'. Authentication that can continue: %s", + current_method, + auth_methods); + SSH_LOG(SSH_LOG_DEBUG, + "%s", + ssh_get_error(session)); + + } + session->auth.supported_methods = 0; + if (strstr(auth_methods, "password") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_PASSWORD; + } + if (strstr(auth_methods, "keyboard-interactive") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_INTERACTIVE; + } + if (strstr(auth_methods, "publickey") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_PUBLICKEY; + } + if (strstr(auth_methods, "hostbased") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_HOSTBASED; + } +#ifdef WITH_GSSAPI + if (strstr(auth_methods, "gssapi-with-mic") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_MIC; + } + if (strstr(auth_methods, "gssapi-keyex") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_KEYEX; + } +#endif + +end: + session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN; + SAFE_FREE(auth_methods); + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_SUCCESS packet. + * + * It is also used to communicate the new to the upper levels. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_success) +{ + struct ssh_crypto_struct *crypto = NULL; + + (void)packet; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_DEBUG, "Authentication successful"); + SSH_LOG(SSH_LOG_TRACE, "Received SSH_USERAUTH_SUCCESS"); + + session->auth.state = SSH_AUTH_STATE_SUCCESS; + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto != NULL && crypto->delayed_compress_out) { + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression OUT"); + crypto->do_compress_out = 1; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL && crypto->delayed_compress_in) { + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression IN"); + crypto->do_compress_in = 1; + } + + /* Reset errors by previous authentication methods. */ + ssh_reset_error(session); + session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN; + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_PK_OK or SSH_USERAUTH_INFO_REQUEST packet. + * + * Since the two types of packets share the same code, additional work is done + * to understand if we are in a public key or keyboard-interactive context. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok) { + int rc; + + SSH_LOG(SSH_LOG_TRACE, + "Received SSH_USERAUTH_PK_OK/INFO_REQUEST/GSSAPI_RESPONSE"); + + if (session->auth.state == SSH_AUTH_STATE_KBDINT_SENT) { + /* Assuming we are in keyboard-interactive context */ + SSH_LOG(SSH_LOG_TRACE, + "keyboard-interactive context, " + "assuming SSH_USERAUTH_INFO_REQUEST"); + rc = ssh_packet_userauth_info_request(session,type,packet,user); +#ifdef WITH_GSSAPI + } else if (session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT) { + rc = ssh_packet_userauth_gssapi_response(session, type, packet, user); +#endif + } else if (session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT) { + session->auth.state = SSH_AUTH_STATE_PK_OK; + SSH_LOG(SSH_LOG_TRACE, "Assuming SSH_USERAUTH_PK_OK"); + rc = SSH_PACKET_USED; + } else { + session->auth.state = SSH_AUTH_STATE_ERROR; + SSH_LOG(SSH_LOG_TRACE, "SSH_USERAUTH_PK_OK received in wrong state"); + rc = SSH_PACKET_USED; + } + + return rc; +} + +/** + * @brief Get available authentication methods from the server. + * + * This requires the function ssh_userauth_none() to be called before the + * methods are available. The server MAY return a list of methods that may + * continue. + * + * @param[in] session The SSH session. + * + * @param[in] username Deprecated, set to NULL. + * + * @returns A bitfield of the following values: + * - SSH_AUTH_METHOD_PASSWORD + * - SSH_AUTH_METHOD_PUBLICKEY + * - SSH_AUTH_METHOD_HOSTBASED + * - SSH_AUTH_METHOD_INTERACTIVE + * + * @warning Other reserved flags may appear in future versions. + * @see ssh_userauth_none() + */ +int ssh_userauth_list(ssh_session session, const char *username) +{ + (void) username; /* unused */ + + if (session == NULL) { + return 0; + } + + return session->auth.supported_methods; +} + +/** + * @brief Try to authenticate through the "none" method. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @returns SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: Authentication failed: use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + */ +int ssh_userauth_none(ssh_session session, const char *username) +{ + int rc; + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_NONE: + goto pending; + default: + ssh_set_error(session, SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsss", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "none" + ); + if (rc < 0) { + goto fail; + } + + session->auth.current_method = SSH_AUTH_METHOD_NONE; + session->auth.state = SSH_AUTH_STATE_AUTH_NONE_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_NONE; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * + * @brief Adds the server's public key to the authentication request. + * + * This function is used internally when the hostbound public key authentication + * extension is enabled. It export the server's public key and adds it to the + * authentication buffer. + * + * @param[in] session The SSH session. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +static int add_hostbound_pubkey(ssh_session session) +{ + int rc; + ssh_string server_pubkey_s = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Invalid session or server public key"); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(session->current_crypto->server_pubkey, + &server_pubkey_s); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_s); + if (rc < 0) { + goto error; + } + +error: + SSH_STRING_FREE(server_pubkey_s); + return rc; +} + +/** + * @internal + * + * @brief Build a public key authentication request. + * + * This helper function creates a SSH2_MSG_USERAUTH_REQUEST message for public + * key authentication and adds the server's public key if the hostbound + * extension is enabled. + * + * @param[in] session The SSH session. + * @param[in] username The username, may be NULL. + * @param[in] auth_type Authentication type (0 for key offer, 1 for actual + * auth). + * @param[in] sig_type_c The signature algorithm name. + * @param[in] pubkey_s The public key string. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + */ +static int build_pubkey_auth_request(ssh_session session, + const char *username, + int has_signature, + const char *sig_type_c, + ssh_string pubkey_s) +{ + int rc; + const char *auth_method = "publickey"; + + if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND && + session->current_crypto->server_pubkey != NULL) { + auth_method = "publickey-hostbound-v00@openssh.com"; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, + "bsssbsS", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + auth_method, + has_signature, /* private key? */ + sig_type_c, /* algo */ + pubkey_s /* public key */ + ); + if (rc < 0) { + return SSH_ERROR; + } + + if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND && + session->current_crypto->server_pubkey != NULL) { + rc = add_hostbound_pubkey(session); + if (rc < 0) { + return SSH_ERROR; + } + } + + return SSH_OK; +} + +/** + * @brief Try to authenticate with the given public key. + * + * To avoid unnecessary processing and user interaction, the following method + * is provided for querying whether authentication using the 'pubkey' would + * be possible. + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] pubkey The public key to try. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use + * ssh_userauth_publickey().\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + */ +int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey) +{ + ssh_string pubkey_s = NULL; + const char *sig_type_c = NULL; + bool allowed; + int rc; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (pubkey == NULL || !ssh_key_is_public(pubkey)) { + ssh_set_error(session, SSH_FATAL, "Invalid pubkey"); + return SSH_AUTH_ERROR; + } + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_OFFER_PUBKEY: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_AUTH_ERROR; + } + + /* Note, that this is intentionally before checking the signature type + * compatibility to make sure the possible EXT_INFO packet is processed, + * extensions recorded and the right signature type is used below + */ + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* Check if the given public key algorithm is allowed */ + sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type); + if (sig_type_c == NULL) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Invalid key type (unknown)"); + return SSH_AUTH_DENIED; + } + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The key algorithm '%s' is not allowed to be used by" + " PUBLICKEY_ACCEPTED_TYPES configuration option", + sig_type_c); + return SSH_AUTH_DENIED; + } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", + sig_type_c, + ssh_key_size(pubkey)); + return SSH_AUTH_DENIED; + } + + /* public key */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_s); + if (rc < 0) { + goto fail; + } + + SSH_LOG(SSH_LOG_TRACE, "Trying signature type %s", sig_type_c); + rc = build_pubkey_auth_request(session, username, 0, sig_type_c, pubkey_s); + if (rc < 0) { + goto fail; + } + SSH_STRING_FREE(pubkey_s); + + session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; + session->auth.state = SSH_AUTH_STATE_PUBKEY_OFFER_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_OFFER_PUBKEY; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + SSH_STRING_FREE(pubkey_s); + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @brief Authenticate with public/private key or certificate. + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] privkey The private key for authentication. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted.\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + */ +int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey) +{ + ssh_string str = NULL; + bool allowed; + int rc; + const char *sig_type_c = NULL; + enum ssh_keytypes_e key_type; + enum ssh_digest_e hash_type; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + ssh_set_error(session, SSH_FATAL, "Invalid private key"); + return SSH_AUTH_ERROR; + } + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_PUBKEY: + goto pending; + default: + ssh_set_error( + session, + SSH_FATAL, + "Bad call during pending SSH call in ssh_userauth_try_publickey"); + return SSH_AUTH_ERROR; + } + + /* Note, that this is intentionally before checking the signature type + * compatibility to make sure the possible EXT_INFO packet is processed, + * extensions recorded and the right signature type is used below + */ + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* Cert auth requires presenting the cert type name (*-cert@openssh.com) */ + key_type = privkey->cert != NULL ? privkey->cert_type : privkey->type; + + /* Check if the given public key algorithm is allowed */ + sig_type_c = ssh_key_get_signature_algorithm(session, key_type); + if (sig_type_c == NULL) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Invalid key type (unknown)"); + return SSH_AUTH_DENIED; + } + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The key algorithm '%s' is not allowed to be used by" + " PUBLICKEY_ACCEPTED_TYPES configuration option", + sig_type_c); + return SSH_AUTH_DENIED; + } + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", + sig_type_c, + ssh_key_size(privkey)); + return SSH_AUTH_DENIED; + } + + /* get public key or cert */ + rc = ssh_pki_export_pubkey_blob(privkey, &str); + if (rc < 0) { + goto fail; + } + + SSH_LOG(SSH_LOG_TRACE, "Sending signature type %s", sig_type_c); + rc = build_pubkey_auth_request(session, username, 1, sig_type_c, str); + if (rc < 0) { + goto fail; + } + SSH_STRING_FREE(str); + + /* Get the hash type to be used in the signature based on the key type */ + hash_type = ssh_key_type_to_hash(session, privkey->type); + + /* sign the buffer with the private key */ + str = ssh_pki_do_sign(session, session->out_buffer, privkey, hash_type); + if (str == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, str); + SSH_STRING_FREE(str); + str = NULL; + if (rc < 0) { + goto fail; + } + + session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; + session->auth.state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_PUBKEY; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + SSH_STRING_FREE(str); + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +static int ssh_userauth_agent_publickey(ssh_session session, + const char *username, + ssh_key pubkey) +{ + ssh_string pubkey_s = NULL; + ssh_string sig_blob = NULL; + const char *sig_type_c = NULL; + bool allowed; + int rc; + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_AGENT: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Bad call during pending SSH call in %s", + __func__); + return SSH_ERROR; + } + + /* Note, that this is intentionally before checking the signature type + * compatibility to make sure the possible EXT_INFO packet is processed, + * extensions recorded and the right signature type is used below + */ + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* public key */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_s); + if (rc < 0) { + goto fail; + } + + /* Check if the given public key algorithm is allowed */ + sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type); + if (sig_type_c == NULL) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Invalid key type (unknown)"); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The key algorithm '%s' is not allowed to be used by" + " PUBLICKEY_ACCEPTED_TYPES configuration option", + sig_type_c); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", + sig_type_c, + ssh_key_size(pubkey)); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } + + rc = build_pubkey_auth_request(session, username, 1, sig_type_c, pubkey_s); + if (rc < 0) { + goto fail; + } + SSH_STRING_FREE(pubkey_s); + + /* sign the buffer with the private key */ + sig_blob = ssh_pki_do_sign_agent(session, session->out_buffer, pubkey); + if (sig_blob == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc < 0) { + goto fail; + } + + session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; + session->auth.state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_AGENT; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + SSH_STRING_FREE(pubkey_s); + + return SSH_AUTH_ERROR; +} + +enum ssh_agent_state_e { + SSH_AGENT_STATE_NONE = 0, + SSH_AGENT_STATE_PUBKEY, + SSH_AGENT_STATE_CERT, + SSH_AGENT_STATE_AUTH +}; + +struct ssh_agent_state_struct { + enum ssh_agent_state_e state; + ssh_key pubkey; + char *comment; +}; + +/* Internal function */ +void ssh_agent_state_free(void *data) +{ + struct ssh_agent_state_struct *state = data; + + if (state) { + SSH_STRING_FREE_CHAR(state->comment); + ssh_key_free(state->pubkey); + free(state); + } +} + +/** + * @brief Try to do public key authentication with ssh agent. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use + * ssh_userauth_publickey().\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + */ +int ssh_userauth_agent(ssh_session session, const char *username) +{ + int rc = SSH_AUTH_ERROR; + struct ssh_agent_state_struct *state = NULL; + ssh_key *configKeys = NULL; + ssh_key *configCerts = NULL; + size_t configKeysCount = 0; + size_t configCertsCount = 0; + size_t i; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (!ssh_agent_is_running(session)) { + return SSH_AUTH_DENIED; + } + + if (!session->agent_state) { + session->agent_state = calloc(1, sizeof(struct ssh_agent_state_struct)); + if (!session->agent_state) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + session->agent_state->state = SSH_AGENT_STATE_NONE; + } + + state = session->agent_state; + if (state->pubkey == NULL) { + state->pubkey = ssh_agent_get_first_ident(session, &state->comment); + } + + if (state->pubkey == NULL) { + return SSH_AUTH_DENIED; + } + + if (session->opts.identities_only) { + /* + * Read keys mentioned in the config, so we can check if key from agent + * is in there. + */ + size_t identityLen = ssh_list_count(session->opts.identity); + size_t certsLen = ssh_list_count(session->opts.certificate); + struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); + + configKeys = malloc(identityLen * sizeof(ssh_key)); + configCerts = malloc((certsLen + identityLen) * sizeof(ssh_key)); + if (configKeys == NULL || configCerts == NULL) { + free(configKeys); + free(configCerts); + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + while (it != NULL && configKeysCount < identityLen) { + const char *privkeyFile = it->data; + size_t certPathLen; + char *certFile = NULL; + ssh_key pubkey = NULL; + ssh_key cert = NULL; + + /* + * Read the private key file listed in the config, but we're only + * interested in the public key. Don't try to decrypt private key. + */ + rc = ssh_pki_import_pubkey_file(privkeyFile, &pubkey); + if (rc == SSH_OK) { + configKeys[configKeysCount++] = pubkey; + } else { + char *pubkeyFile = NULL; + size_t pubkeyPathLen = strlen(privkeyFile) + sizeof(".pub"); + + SSH_KEY_FREE(pubkey); + + /* + * If we couldn't get the public key from the private key file, + * try a .pub file instead. + */ + pubkeyFile = malloc(pubkeyPathLen); + if (!pubkeyFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(pubkeyFile, pubkeyPathLen, "%s.pub", privkeyFile); + rc = ssh_pki_import_pubkey_file(pubkeyFile, &pubkey); + free(pubkeyFile); + if (rc == SSH_OK) { + configKeys[configKeysCount++] = pubkey; + } else if (pubkey) { + SSH_KEY_FREE(pubkey); + } + } + /* Now try to see if there is a certificate with default name + * do not merge it yet with the key as we need to try first the + * non-certified key */ + certPathLen = strlen(privkeyFile) + sizeof("-cert.pub"); + certFile = malloc(certPathLen); + if (!certFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(certFile, certPathLen, "%s-cert.pub", privkeyFile); + rc = ssh_pki_import_cert_file(certFile, &cert); + free(certFile); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + /* And now load separately-listed certificates. */ + it = ssh_list_get_iterator(session->opts.certificate); + while (it != NULL && configCertsCount < certsLen + identityLen) { + const char *certFile = it->data; + ssh_key cert = NULL; + + rc = ssh_pki_import_cert_file(certFile, &cert); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + } + + while (state->pubkey != NULL) { + if (state->state == SSH_AGENT_STATE_NONE) { + SSH_LOG(SSH_LOG_DEBUG, + "Trying identity %s", + state->comment); + if (session->opts.identities_only) { + /* Check if this key is one of the keys listed in the config */ + bool found_key = false; + for (i = 0; i < configKeysCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configKeys[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } + /* or in separate certificates */ + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } + + if (!found_key) { + SSH_LOG(SSH_LOG_DEBUG, + "Identities only is enabled and identity %s was " + "not listed in config, skipping", + state->comment); + SSH_STRING_FREE_CHAR(state->comment); + state->comment = NULL; + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident( + session, &state->comment); + + if (state->pubkey == NULL) { + rc = SSH_AUTH_DENIED; + } + continue; + } + } + } + if (state->state == SSH_AGENT_STATE_NONE || + state->state == SSH_AGENT_STATE_PUBKEY || + state->state == SSH_AGENT_STATE_CERT) { + rc = ssh_userauth_try_publickey(session, username, state->pubkey); + if (rc == SSH_AUTH_ERROR) { + ssh_agent_state_free(state); + session->agent_state = NULL; + goto done; + } else if (rc == SSH_AUTH_AGAIN) { + state->state = (state->state == SSH_AGENT_STATE_NONE ? + SSH_AGENT_STATE_PUBKEY : state->state); + goto done; + } else if (rc != SSH_AUTH_SUCCESS) { + SSH_LOG(SSH_LOG_DEBUG, + "Public key of %s refused by server", + state->comment); + if (state->state == SSH_AGENT_STATE_PUBKEY) { + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Retry with matching certificate"); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_key_dup(configCerts[i]); + state->state = SSH_AGENT_STATE_CERT; + continue; + } + } + } + SSH_STRING_FREE_CHAR(state->comment); + state->comment = NULL; + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); + state->state = SSH_AGENT_STATE_NONE; + continue; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Public key of %s accepted by server", + state->comment); + state->state = SSH_AGENT_STATE_AUTH; + } + if (state->state == SSH_AGENT_STATE_AUTH) { + rc = ssh_userauth_agent_publickey(session, username, state->pubkey); + if (rc == SSH_AUTH_AGAIN) { + goto done; + } + SSH_STRING_FREE_CHAR(state->comment); + state->comment = NULL; + if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_PARTIAL) { + ssh_agent_state_free(session->agent_state); + session->agent_state = NULL; + goto done; + } else if (rc != SSH_AUTH_SUCCESS) { + SSH_LOG(SSH_LOG_DEBUG, + "Server accepted public key but refused the signature"); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); + state->state = SSH_AGENT_STATE_NONE; + continue; + } + ssh_agent_state_free (session->agent_state); + session->agent_state = NULL; + rc = SSH_AUTH_SUCCESS; + goto done; + } + } + + ssh_agent_state_free (session->agent_state); + session->agent_state = NULL; +done: + for (i = 0; i < configKeysCount; i++) { + ssh_key_free(configKeys[i]); + } + free(configKeys); + for (i = 0; i < configCertsCount; i++) { + ssh_key_free(configCerts[i]); + } + free(configCerts); + return rc; +} + +enum ssh_auth_auto_state_e { + SSH_AUTH_AUTO_STATE_NONE = 0, + SSH_AUTH_AUTO_STATE_PUBKEY, + SSH_AUTH_AUTO_STATE_KEY_IMPORTED, + SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE, + SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT, + SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION, + SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED +}; + +struct ssh_auth_auto_state_struct { + enum ssh_auth_auto_state_e state; + struct ssh_iterator *it; + ssh_key privkey; + ssh_key pubkey; + ssh_key cert; + struct ssh_iterator *cert_it; +}; + +/** + * @brief Get the identity that is currently being processed by + * ssh_userauth_publickey_auto() + * + * This is meant to be used by a callback that happens as part of the + * execution of ssh_userauth_publickey_auto(). The auth_function + * callback might want to know which key a passphrase is needed for, + * for example. + * + * @param[in] session The SSH session. + * + * @param[out] value The value to get into. As a char**, space will be + * allocated by the function for the value, it is + * your responsibility to free the memory using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, + char** value) +{ + const char *id = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (value == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + if (session->auth.auto_state != NULL && + session->auth.auto_state->it != NULL) { + id = session->auth.auto_state->it->data; + } + + if (id == NULL) { + return SSH_ERROR; + } + + *value = strdup(id); + if (*value == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Tries to automatically authenticate with public key and "none" + * + * It may fail, for instance it doesn't ask for a password and uses a default + * asker for passphrases (in case the private key is encrypted). + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] passphrase Use this passphrase to unlock the privatekey. Use NULL + * if you don't want to use a passphrase or the user + * should be asked. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + * + * The OpenSSH iterates over the identities and first try the plain public key + * and then the certificate if it is in place. + */ +int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase) +{ + ssh_auth_callback auth_fn = NULL; + void *auth_data = NULL; + struct ssh_auth_auto_state_struct *state = NULL; + int rc; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + SSH_LOG(SSH_LOG_INFO, + "Starting authentication as a user %s", + username ? username : session->opts.username); + + if (! (session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH)) { + session->auth.supported_methods &= ~SSH_AUTH_METHOD_PUBLICKEY; + return SSH_AUTH_DENIED; + } + if (session->common.callbacks) { + auth_fn = session->common.callbacks->auth_function; + auth_data = session->common.callbacks->userdata; + } + if (!session->auth.auto_state) { + session->auth.auto_state = + calloc(1, sizeof(struct ssh_auth_auto_state_struct)); + if (!session->auth.auto_state) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + /* Set state explicitly */ + session->auth.auto_state->state = SSH_AUTH_AUTO_STATE_NONE; + } + state = session->auth.auto_state; + if (state->state == SSH_AUTH_AUTO_STATE_NONE) { + /* Try authentication with ssh-agent first */ + rc = ssh_userauth_agent(session, username); + if (rc == SSH_AUTH_SUCCESS || + rc == SSH_AUTH_PARTIAL || + rc == SSH_AUTH_AGAIN) { + return rc; + } + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + } + if (state->it == NULL) { + state->it = ssh_list_get_iterator(session->opts.identity); + } + + while (state->it != NULL) { + const char *privkey_file = state->it->data; + char pubkey_file[PATH_MAX] = {0}; + + if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY) { + SSH_LOG(SSH_LOG_DEBUG, + "Trying to authenticate with %s", + privkey_file); + state->cert = NULL; + state->privkey = NULL; + state->pubkey = NULL; + +#ifdef WITH_PKCS11_URI + if (ssh_pki_is_uri(privkey_file)) { + char *pub_uri_from_priv = NULL; + SSH_LOG(SSH_LOG_INFO, + "Authenticating with PKCS #11 URI."); + pub_uri_from_priv = ssh_pki_export_pub_uri_from_priv_uri(privkey_file); + if (pub_uri_from_priv == NULL) { + return SSH_ERROR; + } else { + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s", + pub_uri_from_priv); + SAFE_FREE(pub_uri_from_priv); + } + } else +#endif /* WITH_PKCS11_URI */ + { + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s.pub", + privkey_file); + } + + rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to import public key: %s", + pubkey_file); + SAFE_FREE(session->auth.auto_state); + return SSH_AUTH_ERROR; + } else if (rc == SSH_EOF) { + /* Read the private key and save the public key to file */ + rc = ssh_pki_import_privkey_file(privkey_file, + passphrase, + auth_fn, + auth_data, + &state->privkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; + continue; + } else if (rc == SSH_EOF) { + /* If the file doesn't exist, continue */ + SSH_LOG(SSH_LOG_DEBUG, + "Private key %s doesn't exist.", + privkey_file); + state->it = state->it->next; + continue; + } + + rc = ssh_pki_export_privkey_to_pubkey(state->privkey, + &state->pubkey); + if (rc == SSH_ERROR) { + SSH_KEY_FREE(state->privkey); + SAFE_FREE(session->auth.auto_state); + return SSH_AUTH_ERROR; + } + + rc = ssh_pki_export_pubkey_file(state->pubkey, pubkey_file); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Could not write public key to file: %s", + pubkey_file); + } + } + state->state = SSH_AUTH_AUTO_STATE_KEY_IMPORTED; + } + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + ssh_key k = state->pubkey; + if (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + k = state->cert; + } + rc = ssh_userauth_try_publickey(session, username, k); + if (rc == SSH_AUTH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Public key authentication error for %s", + privkey_file); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + SAFE_FREE(session->auth.auto_state); + return rc; + } else if (rc == SSH_AUTH_AGAIN) { + return rc; + } else if (rc != SSH_AUTH_SUCCESS) { + int r; /* do not reuse `rc` as it is used to return from here */ + SSH_KEY_FREE(state->cert); + SSH_LOG(SSH_LOG_DEBUG, + "Public key for %s%s refused by server", + privkey_file, + (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED + ? " (with certificate)" : "")); + /* Try certificate file by appending -cert.pub (if present) */ + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + char cert_file[PATH_MAX] = {0}; + ssh_key cert = NULL; + + snprintf(cert_file, + sizeof(cert_file), + "%s-cert.pub", + privkey_file); + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (default path)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + /* TODO check the pubkey and certs match */ + SSH_LOG(SSH_LOG_TRACE, + "Certificate loaded %s. Retry the authentication.", + cert_file); + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE; + SSH_KEY_FREE(state->cert); + state->cert = cert; + /* try to authenticate with this certificate */ + continue; + } + /* if the file does not exists, try configuration options */ + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT; + } + /* Try certificate files loaded through options */ + if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT) { + state->cert_it = ssh_list_get_iterator(session->opts.certificate); + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + } + if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + SSH_KEY_FREE(state->cert); + while (state->cert_it != NULL) { + const char *cert_file = state->cert_it->data; + ssh_key cert = NULL; + + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (options)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + int cmp = ssh_key_cmp(cert, + state->pubkey, + SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + state->cert_it = state->cert_it->next; + SSH_KEY_FREE(cert); + continue; /* with next cert */ + } + SSH_LOG(SSH_LOG_TRACE, + "Found matching certificate %s in options. Retry the authentication.", + cert_file); + state->cert = cert; + cert = NULL; + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + state->cert_it = state->cert_it->next; + /* try to authenticate with this identity */ + break; /* try this cert */ + } + /* continue with next identity */ + state->cert_it = state->cert_it->next; + } + if (state->cert != NULL) { + continue; /* retry with the certificate */ + } + } + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + state->state = SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED; + } + if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED) { + /* Public key has been accepted by the server */ + if (state->privkey == NULL) { + rc = ssh_pki_import_privkey_file(privkey_file, + passphrase, + auth_fn, + auth_data, + &state->privkey); + if (rc == SSH_ERROR) { + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); + ssh_set_error(session, + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } else if (rc == SSH_EOF) { + /* If the file doesn't exist, continue */ + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); + SSH_LOG(SSH_LOG_DEBUG, + "Private key %s doesn't exist.", + privkey_file); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + } + if (state->cert != NULL && !is_cert_type(state->privkey->cert_type)) { + rc = ssh_pki_copy_cert_to_privkey(state->cert, state->privkey); + if (rc != SSH_OK) { + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + ssh_set_error(session, + SSH_FATAL, + "Failed to copy cert to private key"); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + } + + rc = ssh_userauth_publickey(session, username, state->privkey); + if (rc != SSH_AUTH_AGAIN && rc != SSH_AUTH_DENIED) { + bool cert_used = (state->cert != NULL); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + SAFE_FREE(session->auth.auto_state); + if (rc == SSH_AUTH_SUCCESS) { + SSH_LOG(SSH_LOG_DEBUG, + "Successfully authenticated using %s%s", + privkey_file, + (cert_used ? " and certificate" : "")); + } + return rc; + } + if (rc == SSH_AUTH_AGAIN) { + return rc; + } + + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + + SSH_LOG(SSH_LOG_DEBUG, + "The server accepted the public key but refused the signature"); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + /* continue */ + } + } + SSH_LOG(SSH_LOG_WARN, + "Access denied: Tried every public key, none matched"); + SAFE_FREE(session->auth.auto_state); + return SSH_AUTH_DENIED; +} + +/** + * @brief Try to authenticate by password. + * + * This authentication method is normally disabled on SSHv2 server. You should + * use keyboard-interactive mode. + * + * The 'password' value MUST be encoded UTF-8. It is up to the server how to + * interpret the password and validate it against the password database. + * However, if you read the password in some other encoding, you MUST convert + * the password to UTF-8. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] password The password to authenticate in UTF-8. + * + * @returns SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: Authentication failed: use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + * + * @see ssh_userauth_none() + * @see ssh_userauth_kbdint() + */ +int ssh_userauth_password(ssh_session session, + const char *username, + const char *password) +{ + int rc; + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_PASSWORD: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsssbs", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "password", + 0, /* false */ + password + ); + if (rc < 0) { + goto fail; + } + + /* Set the buffer as secure to be explicitly zeroed when freed */ + ssh_buffer_set_secure(session->out_buffer); + + session->auth.current_method = SSH_AUTH_METHOD_PASSWORD; + session->auth.state = SSH_AUTH_STATE_PASSWORD_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_PASSWORD; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/* LEGACY */ +int ssh_userauth_agent_pubkey(ssh_session session, + const char *username, + ssh_public_key publickey) +{ + ssh_key key = NULL; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_AUTH_ERROR; + } + + key->type = publickey->type; + key->type_c = ssh_key_type_to_char(key->type); + key->flags = SSH_KEY_FLAG_PUBLIC; +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = publickey->rsa_pub; +#elif defined(HAVE_LIBCRYPTO) + key->key = publickey->key_pub; +#else + key->rsa = publickey->rsa_pub; +#endif /* HAVE_LIBCRYPTO */ + + rc = ssh_userauth_agent_publickey(session, username, key); + +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + key->key = NULL; +#else + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + ssh_key_free(key); + + return rc; +} + +ssh_kbdint ssh_kbdint_new(void) +{ + ssh_kbdint kbd; + + kbd = calloc(1, sizeof(struct ssh_kbdint_struct)); + if (kbd == NULL) { + return NULL; + } + + return kbd; +} + + +void ssh_kbdint_free(ssh_kbdint kbd) +{ + size_t i, n; + + if (kbd == NULL) { + return; + } + + SAFE_FREE(kbd->name); + SAFE_FREE(kbd->instruction); + SAFE_FREE(kbd->echo); + + n = kbd->nprompts; + if (kbd->prompts) { + for (i = 0; i < n; i++) { + if (kbd->prompts[i] != NULL) { + ssh_burn(kbd->prompts[i], strlen(kbd->prompts[i])); + } + SAFE_FREE(kbd->prompts[i]); + } + SAFE_FREE(kbd->prompts); + } + + n = kbd->nanswers; + if (kbd->answers) { + for (i = 0; i < n; i++) { + if (kbd->answers[i] != NULL) { + ssh_burn(kbd->answers[i], strlen(kbd->answers[i])); + } + SAFE_FREE(kbd->answers[i]); + } + SAFE_FREE(kbd->answers); + } + + SAFE_FREE(kbd); +} + +void ssh_kbdint_clean(ssh_kbdint kbd) +{ + size_t i, n; + + if (kbd == NULL) { + return; + } + + SAFE_FREE(kbd->name); + SAFE_FREE(kbd->instruction); + SAFE_FREE(kbd->echo); + + n = kbd->nprompts; + if (kbd->prompts) { + for (i = 0; i < n; i++) { + ssh_burn(kbd->prompts[i], strlen(kbd->prompts[i])); + SAFE_FREE(kbd->prompts[i]); + } + SAFE_FREE(kbd->prompts); + } + + n = kbd->nanswers; + + if (kbd->answers) { + for (i = 0; i < n; i++) { + ssh_burn(kbd->answers[i], strlen(kbd->answers[i])); + SAFE_FREE(kbd->answers[i]); + } + SAFE_FREE(kbd->answers); + } + + kbd->nprompts = 0; + kbd->nanswers = 0; +} + +/* + * This function sends the first packet as explained in RFC 3066 section 3.1. + */ +static int ssh_userauth_kbdint_init(ssh_session session, + const char *username, + const char *submethods) +{ + int rc; + + if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT) { + goto pending; + } + if (session->pending_call_state != SSH_PENDING_CALL_NONE) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } + if (rc != SSH_OK) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsssss", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "keyboard-interactive", + "", /* lang (ignore it) */ + submethods ? submethods : "" + ); + if (rc < 0) { + goto fail; + } + + + session->auth.state = SSH_AUTH_STATE_KBDINT_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_INIT; + + SSH_LOG(SSH_LOG_DEBUG, + "Sending keyboard-interactive init request"); + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * + * @brief Send the current challenge response and wait for a reply from the + * server. + * + * @returns SSH_AUTH_INFO if more info is needed + * @returns SSH_AUTH_SUCCESS + * @returns SSH_AUTH_FAILURE + * @returns SSH_AUTH_PARTIAL + */ +static int ssh_userauth_kbdint_send(ssh_session session) +{ + uint32_t i; + int rc; + if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND) + goto pending; + if (session->pending_call_state != SSH_PENDING_CALL_NONE) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + rc = ssh_buffer_pack(session->out_buffer, "bd", + SSH2_MSG_USERAUTH_INFO_RESPONSE, + session->kbdint->nprompts); + if (rc < 0) { + goto fail; + } + + for (i = 0; i < session->kbdint->nprompts; i++) { + rc = ssh_buffer_pack(session->out_buffer, "s", + session->kbdint->answers && session->kbdint->answers[i] ? + session->kbdint->answers[i]:""); + if (rc < 0) { + goto fail; + } + } + + session->auth.current_method = SSH_AUTH_METHOD_INTERACTIVE; + session->auth.state = SSH_AUTH_STATE_KBDINT_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_SEND; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + SSH_LOG(SSH_LOG_DEBUG, + "Sending keyboard-interactive response packet"); + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * @brief handles a SSH_USERAUTH_INFO_REQUEST packet, as used in + * keyboard-interactive authentication, and changes the + * authentication state. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { + ssh_string tmp = NULL; + uint32_t nprompts; + uint32_t i; + int rc; + (void)user; + (void)type; + + + if (session->kbdint == NULL) { + session->kbdint = ssh_kbdint_new(); + if (session->kbdint == NULL) { + ssh_set_error_oom(session); + return SSH_PACKET_USED; + } + } else { + ssh_kbdint_clean(session->kbdint); + } + + rc = ssh_buffer_unpack(packet, "ssSd", + &session->kbdint->name, /* name of the "asking" window shown to client */ + &session->kbdint->instruction, + &tmp, /* to ignore */ + &nprompts + ); + + /* We don't care about tmp */ + SSH_STRING_FREE(tmp); + + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid USERAUTH_INFO_REQUEST msg"); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_DEBUG, + "%" PRIu32 " keyboard-interactive prompts", nprompts); + if (nprompts > KBDINT_MAX_PROMPT) { + ssh_set_error(session, SSH_FATAL, + "Too much prompts requested by the server: %" PRIu32 " (0x%.4" PRIx32 ")", + nprompts, nprompts); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + + session->kbdint->nprompts = nprompts; + session->kbdint->nanswers = nprompts; + session->kbdint->prompts = calloc(nprompts, sizeof(char *)); + if (session->kbdint->prompts == NULL) { + session->kbdint->nprompts = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + + session->kbdint->echo = calloc(nprompts, sizeof(unsigned char)); + if (session->kbdint->echo == NULL) { + session->kbdint->nprompts = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + + for (i = 0; i < nprompts; i++) { + rc = ssh_buffer_unpack(packet, "sb", + &session->kbdint->prompts[i], + &session->kbdint->echo[i]); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Short INFO_REQUEST packet"); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + } + session->auth.state=SSH_AUTH_STATE_INFO; + + return SSH_PACKET_USED; +} + +/** + * @brief Try to authenticate through the "keyboard-interactive" method. + * + * @param[in] session The ssh session to use. + * + * @param[in] user The username to authenticate. You can specify NULL if + * ssh_option_set_username() has been used. You cannot try + * two different logins in a row. + * + * @param[in] submethods Undocumented. Set it to NULL. + * + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_INFO: The server asked some questions. Use + * ssh_userauth_kbdint_getnprompts() and such.\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @see ssh_userauth_kbdint_getnprompts() + * @see ssh_userauth_kbdint_getname() + * @see ssh_userauth_kbdint_getinstruction() + * @see ssh_userauth_kbdint_getprompt() + * @see ssh_userauth_kbdint_setanswer() + */ +int ssh_userauth_kbdint(ssh_session session, const char *user, + const char *submethods) +{ + int rc = SSH_AUTH_ERROR; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if ((session->pending_call_state == SSH_PENDING_CALL_NONE && session->kbdint == NULL) || + session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT) + rc = ssh_userauth_kbdint_init(session, user, submethods); + else if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND || + session->kbdint != NULL) { + /* + * If we are at this point, it is because session->kbdint exists. + * It means the user has set some information there we need to send + * the server and then we need to ack the status (new questions or ok + * pass in). + * It is possible that session->kbdint is NULL while we're waiting for + * a reply, hence the test for the pending call. + */ + rc = ssh_userauth_kbdint_send(session); + } else { + /* We are here because session->kbdint == NULL & state != NONE. + * This should not happen + */ + rc = SSH_AUTH_ERROR; + ssh_set_error(session, SSH_FATAL, "Invalid state in %s", __func__); + } + return rc; +} + +/** + * @brief Get the number of prompts (questions) the server has given. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The number of prompts. + */ +int ssh_userauth_kbdint_getnprompts(ssh_session session) +{ + if (session == NULL) { + return SSH_ERROR; + } + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + return session->kbdint->nprompts; +} + +/** + * @brief Get the "name" of the message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The name of the message block. Do not free it. + */ +const char *ssh_userauth_kbdint_getname(ssh_session session) +{ + if (session == NULL) { + return NULL; + } + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + return session->kbdint->name; +} + +/** + * @brief Get the "instruction" of the message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The instruction of the message block. + */ + +const char *ssh_userauth_kbdint_getinstruction(ssh_session session) +{ + if (session == NULL) + return NULL; + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + return session->kbdint->instruction; +} + +/** + * @brief Get a prompt from a message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @param[in] i The index number of the i'th prompt. + * + * @param[out] echo This is an optional variable. You can obtain a + * boolean if the user input should be echoed or + * hidden. For passwords it is usually hidden. + * + * @returns A pointer to the prompt. Do not free it. + * + * @code + * const char prompt; + * char echo; + * + * prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + * if (echo) ... + * @endcode + */ +const char * +ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo) +{ + if (session == NULL) + return NULL; + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + if (i >= session->kbdint->nprompts) { + ssh_set_error_invalid(session); + return NULL; + } + + if (echo) { + *echo = (char)session->kbdint->echo[i]; + } + + return session->kbdint->prompts[i]; +} + +#ifdef WITH_SERVER +/** + * @brief Get the number of answers the client has given. + * + * @param[in] session The ssh session to use. + * + * @returns The number of answers. + */ +int ssh_userauth_kbdint_getnanswers(ssh_session session) +{ + if (session == NULL || session->kbdint == NULL) { + return SSH_ERROR; + } + return session->kbdint->nanswers; +} + +/** + * @brief Get the answer to a question from a message block. + * + * @param[in] session The ssh session to use. + * + * @param[in] i index The number of the ith answer. + * + * @return The answer string, or NULL if the answer is not + * available. Do not free the string. + */ +const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) +{ + if (session == NULL || session->kbdint == NULL + || session->kbdint->answers == NULL) { + return NULL; + } + if (i >= session->kbdint->nanswers) { + return NULL; + } + + return session->kbdint->answers[i]; +} +#endif + +/** + * @brief Set the answer for a question from a message block. + * + * If you have called ssh_userauth_kbdint() and got SSH_AUTH_INFO, this + * function returns the questions from the server. + * + * @param[in] session The ssh session to use. + * + * @param[in] i index The number of the ith prompt. + * + * @param[in] answer The answer to give to the server. The answer MUST be + * encoded UTF-8. It is up to the server how to interpret + * the value and validate it. However, if you read the + * answer in some other encoding, you MUST convert it to + * UTF-8. + * + * @return 0 on success, < 0 on error. + */ +int +ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer) +{ + if (session == NULL) { + return -1; + } + if (answer == NULL || session->kbdint == NULL || + i >= session->kbdint->nprompts) { + ssh_set_error_invalid(session); + return -1; + } + + if (session->kbdint->answers == NULL) { + session->kbdint->answers = calloc(session->kbdint->nprompts, sizeof(char *)); + if (session->kbdint->answers == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (session->kbdint->answers[i]) { + ssh_burn(session->kbdint->answers[i], + strlen(session->kbdint->answers[i])); + SAFE_FREE(session->kbdint->answers[i]); + } + + session->kbdint->answers[i] = strdup(answer); + if (session->kbdint->answers[i] == NULL) { + ssh_set_error_oom(session); + return -1; + } + + return 0; +} + +/** + * @brief Try to authenticate through the "gssapi-with-mic" method. + * + * @param[in] session The ssh session to use. + * + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_userauth_gssapi(ssh_session session) +{ + int rc = SSH_AUTH_DENIED; +#ifdef WITH_GSSAPI + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_GSSAPI_MIC: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-with-mic"); + + session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_MIC; + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_GSSAPI_MIC; + rc = ssh_gssapi_auth_mic(session); + + if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_DENIED) { + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } +#else + (void) session; /* unused */ +#endif + return rc; +} + +/** + * @brief Try to authenticate through the "gssapi-keyex" method. + * + * @param[in] session The ssh session to use. + * + * @returns + * - `SSH_AUTH_ERROR`: A serious error happened. + * - `SSH_AUTH_DENIED`: Authentication failed : use another method. + * - `SSH_AUTH_PARTIAL`: You've been partially authenticated, you still + * have to use another method. + * - `SSH_AUTH_SUCCESS`: Authentication success. + * - `SSH_AUTH_AGAIN`: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_userauth_gssapi_keyex(ssh_session session) +{ + int rc = SSH_AUTH_DENIED; +#ifdef WITH_GSSAPI + OM_uint32 min_stat; + gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + /* Check if GSSAPI Key exchange was performed */ + if (!ssh_session_kex_is_gss(session)) { + ssh_set_error(session, + SSH_FATAL, + "Attempt to authenticate with gssapi-keyex without " + "doing GSSAPI Key exchange."); + return SSH_ERROR; + } + + if (session->gssapi == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-keyex"); + + session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_KEYEX; + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX; + + SAFE_FREE(session->gssapi->user); + session->gssapi->user = strdup(session->opts.username); + if (session->gssapi->user == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_gssapi_auth_keyex_mic(session, &mic_token_buf); + if (rc != SSH_OK) { + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bsssdP", + SSH2_MSG_USERAUTH_REQUEST, + session->opts.username, + "ssh-connection", + "gssapi-keyex", + mic_token_buf.length, + (size_t)mic_token_buf.length, + mic_token_buf.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + gss_release_buffer(&min_stat, &mic_token_buf); + return rc; + } + + gss_release_buffer(&min_stat, &mic_token_buf); + + session->auth.state = SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT; + + ssh_packet_send(session); + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } +#else + (void)session; /* unused */ +#endif + return rc; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/base64.c b/src/libs/libssh-0.12.2/src/base64.c new file mode 100644 index 000000000000..81dc7a1b6a5e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/base64.c @@ -0,0 +1,314 @@ +/*#pragma GCC error "ERROR"*/ +/* + * base64.c - support for base64 alphabet system, described in RFC1521 + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2005 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* just the dirtiest part of code i ever made */ +#include "config.h" + +#include + +#include "libssh/priv.h" +#include "libssh/buffer.h" + +/* Do not allow encoding more than 256MB of data */ +#define BASE64_MAX_INPUT_LEN 256 * 1024 * 1024 + +static +const uint8_t alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* Transformations */ +#define SET_A(n, i) do { (n) |= ((i) & 63) <<18; } while (0) +#define SET_B(n, i) do { (n) |= ((i) & 63) <<12; } while (0) +#define SET_C(n, i) do { (n) |= ((i) & 63) << 6; } while (0) +#define SET_D(n, i) do { (n) |= ((i) & 63); } while (0) + +#define GET_A(n) (unsigned char) (((n) & 0xff0000) >> 16) +#define GET_B(n) (unsigned char) (((n) & 0xff00) >> 8) +#define GET_C(n) (unsigned char) ((n) & 0xff) + +static int _base64_to_bin(unsigned char dest[3], const char *source, int num); +static int get_equals(char *string); + +/* First part: base64 to binary */ + +/** + * @internal + * + * @brief Translates a base64 string into a binary one. + * + * @returns A buffer containing the decoded string, NULL if something went + * wrong (e.g. incorrect char). + */ +ssh_buffer base64_to_bin(const char *source) +{ + ssh_buffer buffer = NULL; + unsigned char block[3]; + char *base64 = NULL; + char *ptr = NULL; + size_t len; + int equals; + + base64 = strdup(source); + if (base64 == NULL) { + return NULL; + } + ptr = base64; + + /* Get the number of equals signs, which mirrors the padding */ + equals = get_equals(ptr); + if (equals > 2) { + SAFE_FREE(base64); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + SAFE_FREE(base64); + return NULL; + } + /* + * The base64 buffer often contains sensitive data. Make sure we don't leak + * sensitive data + */ + ssh_buffer_set_secure(buffer); + + len = strlen(ptr); + while (len > 4) { + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + len -= 4; + ptr += 4; + } + + /* + * Depending on the number of bytes resting, there are 3 possibilities + * from the RFC. + */ + switch (len) { + /* + * (1) The final quantum of encoding input is an integral multiple of + * 24 bits. Here, the final unit of encoded output will be an integral + * multiple of 4 characters with no "=" padding + */ + case 4: + if (equals != 0) { + goto error; + } + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + /* + * (2) The final quantum of encoding input is exactly 8 bits; here, the + * final unit of encoded output will be two characters followed by + * two "=" padding characters. + */ + case 2: + if (equals != 2) { + goto error; + } + + if (_base64_to_bin(block, ptr, 1) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 1) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + /* + * The final quantum of encoding input is exactly 16 bits. Here, the final + * unit of encoded output will be three characters followed by one "=" + * padding character. + */ + case 3: + if (equals != 1) { + goto error; + } + if (_base64_to_bin(block, ptr, 2) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 2) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + default: + /* 4,3,2 are the only padding size allowed */ + goto error; + } + +error: + SAFE_FREE(base64); + SSH_BUFFER_FREE(buffer); + return NULL; +} + +#define BLOCK(letter, n) do {ptr = strchr((const char *)alphabet, source[n]); \ + if(!ptr) return -1; \ + i = ptr - (const char *)alphabet; \ + SET_##letter(*block, i); \ + } while(0) + +/* Returns 0 if ok, -1 if not (ie invalid char into the stuff) */ +static int to_block4(unsigned long *block, const char *source, int num) +{ + const char *ptr = NULL; + size_t i; + + *block = 0; + if (num < 1) { + return 0; + } + + BLOCK(A, 0); /* 6 bit */ + BLOCK(B, 1); /* 12 bit */ + + if (num < 2) { + return 0; + } + + BLOCK(C, 2); /* 18 bit */ + + if (num < 3) { + return 0; + } + + BLOCK(D, 3); /* 24 bit */ + + return 0; +} + +/* num = numbers of final bytes to be decoded */ +static int _base64_to_bin(unsigned char dest[3], const char *source, int num) +{ + unsigned long block; + + if (to_block4(&block, source, num) < 0) { + return -1; + } + dest[0] = GET_A(block); + dest[1] = GET_B(block); + dest[2] = GET_C(block); + + return 0; +} + +/* Count the number of "=" signs and replace them by zeroes */ +static int get_equals(char *string) +{ + char *ptr = string; + int num = 0; + + while ((ptr = strchr(ptr, '=')) != NULL) { + num++; + *ptr = '\0'; + ptr++; + } + + return num; +} + +/* thanks sysk for debugging my mess :) */ +static void _bin_to_base64(uint8_t *dest, + const uint8_t source[3], + size_t len) +{ +#define BITS(n) ((1 << (n)) - 1) + switch (len) { + case 1: + dest[0] = alphabet[(source[0] >> 2)]; + dest[1] = alphabet[((source[0] & BITS(2)) << 4)]; + dest[2] = '='; + dest[3] = '='; + break; + case 2: + dest[0] = alphabet[source[0] >> 2]; + dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)]; + dest[2] = alphabet[(source[1] & BITS(4)) << 2]; + dest[3] = '='; + break; + case 3: + dest[0] = alphabet[(source[0] >> 2)]; + dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)]; + dest[2] = alphabet[(source[2] >> 6) | (source[1] & BITS(4)) << 2]; + dest[3] = alphabet[source[2] & BITS(6)]; + break; + } +#undef BITS +} + +/** + * @internal + * + * @brief Converts binary data to a base64 string. + * + * @returns the converted string + */ +uint8_t *bin_to_base64(const uint8_t *source, size_t len) +{ + uint8_t *base64 = NULL; + uint8_t *ptr = NULL; + size_t flen = 0; + + /* Set the artificial upper limit for the input. Otherwise on 32b arch, the + * following line could overflow for sizes larger than SIZE_MAX / 4 */ + if (len > BASE64_MAX_INPUT_LEN) { + return NULL; + } + + flen = len + (3 - (len % 3)); /* round to upper 3 multiple */ + flen = (4 * flen) / 3 + 1; + + base64 = malloc(flen); + if (base64 == NULL) { + return NULL; + } + ptr = base64; + + while (len > 0) { + _bin_to_base64(ptr, source, len > 3 ? 3 : len); + ptr += 4; + if (len < 3) { + break; + } + source += 3; + len -= 3; + } + ptr[0] = '\0'; + + return base64; +} diff --git a/src/libs/libssh-0.12.2/src/bignum.c b/src/libs/libssh-0.12.2/src/bignum.c new file mode 100644 index 000000000000..b18c1162ce14 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/bignum.c @@ -0,0 +1,107 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "libssh/priv.h" +#include "libssh/bignum.h" +#include "libssh/string.h" + +static ssh_string make_bignum_string(bignum num, size_t pad_to_len) +{ + ssh_string ptr = NULL; + size_t pad = 0; + size_t len = bignum_num_bytes(num); + size_t bits = bignum_num_bits(num); + + if (pad_to_len == 0) { + /* If the first bit is set we have a negative number */ + if (!(bits % 8) && bignum_is_bit_set(num, bits - 1)) { + pad++; + } + } else { + if (len > pad_to_len) { + return NULL; + } + pad = pad_to_len - len; + } + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_TRACE, "%zu bits, %zu bytes, %zu padding", bits, len, pad); +#endif /* DEBUG_CRYPTO */ + + ptr = ssh_string_new(len + pad); + if (ptr == NULL) { + return NULL; + } + + /* We have a negative number so we need a leading zero */ + if (pad) { + memset(ptr->data, 0, pad); + } + + bignum_bn2bin(num, len, ptr->data + pad); + + return ptr; +} + +ssh_string ssh_make_bignum_string(bignum num) +{ + return make_bignum_string(num, 0); +} + +ssh_string ssh_make_padded_bignum_string(bignum num, size_t pad_len) +{ + return make_bignum_string(num, pad_len); +} + +bignum ssh_make_string_bn(ssh_string string) +{ + bignum bn = NULL; + size_t len = ssh_string_len(string); + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_TRACE, + "Importing a %zu bits, %zu bytes object ...", + len * 8, + len); +#endif /* DEBUG_CRYPTO */ + + bignum_bin2bn(string->data, (int)len, &bn); + + return bn; +} + +/* prints the bignum on stderr */ +void ssh_print_bignum(const char *name, const_bignum num) +{ + unsigned char *hex = NULL; + if (num != NULL) { + bignum_bn2hex(num, &hex); + } + SSH_LOG(SSH_LOG_DEBUG, + "%s value: %s", + name, + (hex == NULL) ? "(null)" : (char *)hex); + ssh_crypto_free(hex); +} diff --git a/src/libs/libssh-0.12.2/src/bind.c b/src/libs/libssh-0.12.2/src/bind.c new file mode 100644 index 000000000000..97feaac26849 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/bind.c @@ -0,0 +1,611 @@ +/* + * bind.c : all ssh_bind functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2004-2005 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/bind.h" +#include "libssh/libssh.h" +#include "libssh/server.h" +#include "libssh/pki.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/token.h" + +/** + * @addtogroup libssh_server + * + * @{ + */ + + +#ifdef _WIN32 +#include +#include +#include + +/* + * is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. + */ +#ifdef HAVE_WSPIAPI_H +# include +#endif + +#define SOCKOPT_TYPE_ARG4 char + +#else /* _WIN32 */ + +#include +#include +#include +#define SOCKOPT_TYPE_ARG4 int + +#endif /* _WIN32 */ + +static socket_t bind_socket(ssh_bind sshbind, const char *hostname, + int port) { + char port_c[6]; + struct addrinfo *ai = NULL; + struct addrinfo hints; + int opt = 1; + socket_t s; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + ZERO_STRUCT(hints); + + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + + snprintf(port_c, 6, "%d", port); + rc = getaddrinfo(hostname, port_c, &hints, &ai); + if (rc != 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Resolving %s: %s", hostname, gai_strerror(rc)); + return -1; + } + + s = socket (ai->ai_family, + ai->ai_socktype, + ai->ai_protocol); + if (s == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + freeaddrinfo (ai); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *)&opt, sizeof(opt)) < 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Setting socket options failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + freeaddrinfo (ai); + CLOSE_SOCKET(s); + return -1; + } + + if (bind(s, ai->ai_addr, ai->ai_addrlen) != 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Binding to %s:%d: %s", + hostname, + port, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + freeaddrinfo (ai); + CLOSE_SOCKET(s); + return -1; + } + + freeaddrinfo (ai); + return s; +} + +ssh_bind ssh_bind_new(void) +{ + ssh_bind ptr = NULL; + + ptr = calloc(1, sizeof(struct ssh_bind_struct)); + if (ptr == NULL) { + return NULL; + } + ptr->bindfd = SSH_INVALID_SOCKET; + ptr->bindport = 22; + ptr->common.log_verbosity = 0; + + return ptr; +} + +static int ssh_bind_import_keys(ssh_bind sshbind) { + int rc; + +#ifdef HAVE_ECC + if (sshbind->ecdsa == NULL && sshbind->ecdsakey != NULL) { + rc = ssh_pki_import_privkey_file(sshbind->ecdsakey, + NULL, + NULL, + NULL, + &sshbind->ecdsa); + if (rc == SSH_ERROR || rc == SSH_EOF) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private ECDSA host key"); + return SSH_ERROR; + } + + if (!is_ecdsa_key_type(ssh_key_type(sshbind->ecdsa))) { + ssh_set_error(sshbind, SSH_FATAL, + "The ECDSA host key has the wrong type"); + ssh_key_free(sshbind->ecdsa); + sshbind->ecdsa = NULL; + return SSH_ERROR; + } + } +#endif + + if (sshbind->rsa == NULL && sshbind->rsakey != NULL) { + rc = ssh_pki_import_privkey_file(sshbind->rsakey, + NULL, + NULL, + NULL, + &sshbind->rsa); + if (rc == SSH_ERROR || rc == SSH_EOF) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private RSA host key"); + return SSH_ERROR; + } + + if (ssh_key_type(sshbind->rsa) != SSH_KEYTYPE_RSA) { + ssh_set_error(sshbind, SSH_FATAL, + "The RSA host key has the wrong type"); + ssh_key_free(sshbind->rsa); + sshbind->rsa = NULL; + return SSH_ERROR; + } + } + + if (sshbind->ed25519 == NULL && sshbind->ed25519key != NULL) { + rc = ssh_pki_import_privkey_file(sshbind->ed25519key, + NULL, + NULL, + NULL, + &sshbind->ed25519); + if (rc == SSH_ERROR || rc == SSH_EOF) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private ED25519 host key"); + return SSH_ERROR; + } + + if (ssh_key_type(sshbind->ed25519) != SSH_KEYTYPE_ED25519) { + ssh_set_error(sshbind, SSH_FATAL, + "The ED25519 host key has the wrong type"); + ssh_key_free(sshbind->ed25519); + sshbind->ed25519 = NULL; + return SSH_ERROR; + } + } + + return SSH_OK; +} + +int ssh_bind_listen(ssh_bind sshbind) +{ + const char *host = NULL; + socket_t fd; + int rc; + + /* Apply global bind configurations, if it hasn't been applied before */ + rc = ssh_bind_options_parse_config(sshbind, NULL); + if (rc != 0) { + ssh_set_error(sshbind, SSH_FATAL, "Could not parse global config"); + return SSH_ERROR; + } + + /* Set default hostkey paths if no hostkey was found before */ + if (sshbind->ecdsakey == NULL && + sshbind->rsakey == NULL && + sshbind->ed25519key == NULL) { + + sshbind->ecdsakey = strdup("/etc/ssh/ssh_host_ecdsa_key"); + sshbind->rsakey = strdup("/etc/ssh/ssh_host_rsa_key"); + sshbind->ed25519key = strdup("/etc/ssh/ssh_host_ed25519_key"); + } + + if (sshbind->rsa == NULL && + sshbind->ecdsa == NULL && + sshbind->ed25519 == NULL) { + rc = ssh_bind_import_keys(sshbind); + if (rc == SSH_ERROR) { + if (!sshbind->gssapi_key_exchange) { + ssh_set_error(sshbind, SSH_FATAL, "No usable hostkeys found"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, + "No usable hostkeys found: Using \"null\" hostkey algorithm"); + } + } + + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + host = sshbind->bindaddr; + if (host == NULL) { + host = "0.0.0.0"; + } + + fd = bind_socket(sshbind, host, sshbind->bindport); + if (fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + + if (listen(fd, 10) < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(sshbind, + SSH_FATAL, + "Listening to socket %d: %s", + fd, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); + return SSH_ERROR; + } + + sshbind->bindfd = fd; + } else { + SSH_LOG(SSH_LOG_DEBUG, "Using app-provided bind socket"); + } + return 0; +} + +int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, void *userdata) +{ + if (sshbind == NULL) { + return SSH_ERROR; + } + if (callbacks == NULL) { + ssh_set_error_invalid(sshbind); + return SSH_ERROR; + } + if (callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)) { + ssh_set_error(sshbind, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + } + sshbind->bind_callbacks = callbacks; + sshbind->bind_callbacks_userdata = userdata; + return 0; +} + +/** @internal + * @brief callback being called by poll when an event happens + * + */ +static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, socket_t fd, int revents, void *user) +{ + ssh_bind sshbind = (ssh_bind)user; + (void)sshpoll; + (void)fd; + + if (revents & POLLIN) { + /* new incoming connection */ + if (ssh_callbacks_exists(sshbind->bind_callbacks, incoming_connection)) { + sshbind->bind_callbacks->incoming_connection(sshbind, + sshbind->bind_callbacks_userdata); + } + } + return 0; +} + +/** @internal + * @brief returns the current poll handle, or creates it + * @param sshbind the ssh_bind object + * @returns a ssh_poll handle suitable for operation + */ +ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind) +{ + short events = POLLIN; + + if (sshbind->poll) { + return sshbind->poll; + } + +#ifdef POLLRDHUP + events |= POLLRDHUP; +#endif /* POLLRDHUP */ + + sshbind->poll = ssh_poll_new(sshbind->bindfd, + events, + ssh_bind_poll_callback, + sshbind); + + return sshbind->poll; +} + +void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) +{ + sshbind->blocking = blocking ? 1 : 0; +} + +socket_t ssh_bind_get_fd(ssh_bind sshbind) +{ + return sshbind->bindfd; +} + +void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) +{ + sshbind->bindfd = fd; +} + +void ssh_bind_fd_toaccept(ssh_bind sshbind) +{ + sshbind->toaccept = 1; +} + +void ssh_bind_free(ssh_bind sshbind){ + int i; + + if (sshbind == NULL) { + return; + } + + if (sshbind->bindfd >= 0) { + CLOSE_SOCKET(sshbind->bindfd); + } + sshbind->bindfd = SSH_INVALID_SOCKET; + + /* options */ + SAFE_FREE(sshbind->banner); + SAFE_FREE(sshbind->moduli_file); + SAFE_FREE(sshbind->bindaddr); + SAFE_FREE(sshbind->config_dir); + SAFE_FREE(sshbind->pubkey_accepted_key_types); + + SAFE_FREE(sshbind->rsakey); + SAFE_FREE(sshbind->ecdsakey); + SAFE_FREE(sshbind->ed25519key); + SAFE_FREE(sshbind->gssapi_key_exchange_algs); + + ssh_key_free(sshbind->rsa); + sshbind->rsa = NULL; + ssh_key_free(sshbind->ecdsa); + sshbind->ecdsa = NULL; + ssh_key_free(sshbind->ed25519); + sshbind->ed25519 = NULL; + + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (sshbind->wanted_methods[i]) { + SAFE_FREE(sshbind->wanted_methods[i]); + } + } + + SAFE_FREE(sshbind); +} + +int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) +{ + ssh_poll_handle handle = NULL; + int i, rc; + + if (sshbind == NULL) { + return SSH_ERROR; + } + + if (session == NULL){ + ssh_set_error(sshbind, SSH_FATAL,"session is null"); + return SSH_ERROR; + } + + session->server = 1; + + /* Copy options from bind to session */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (sshbind->wanted_methods[i]) { + session->opts.wanted_methods[i] = strdup(sshbind->wanted_methods[i]); + if (session->opts.wanted_methods[i] == NULL) { + return SSH_ERROR; + } + } + } + + if (sshbind->bindaddr == NULL) + session->opts.bindaddr = NULL; + else { + SAFE_FREE(session->opts.bindaddr); + session->opts.bindaddr = strdup(sshbind->bindaddr); + if (session->opts.bindaddr == NULL) { + return SSH_ERROR; + } + } + + if (sshbind->pubkey_accepted_key_types != NULL) { + if (session->opts.pubkey_accepted_types == NULL) { + session->opts.pubkey_accepted_types = strdup(sshbind->pubkey_accepted_key_types); + if (session->opts.pubkey_accepted_types == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } else { + char *p = NULL; + /* If something was set to the session prior to calling this + * function, keep only what is allowed by the options set in + * sshbind */ + p = ssh_find_all_matching(sshbind->pubkey_accepted_key_types, + session->opts.pubkey_accepted_types); + if (p == NULL) { + return SSH_ERROR; + } + + SAFE_FREE(session->opts.pubkey_accepted_types); + session->opts.pubkey_accepted_types = p; + } + } + + session->common.log_verbosity = sshbind->common.log_verbosity; + session->opts.gssapi_key_exchange = sshbind->gssapi_key_exchange; + + if (sshbind->gssapi_key_exchange_algs != NULL) { + SAFE_FREE(session->opts.gssapi_key_exchange_algs); + session->opts.gssapi_key_exchange_algs = + strdup(sshbind->gssapi_key_exchange_algs); + if (session->opts.gssapi_key_exchange_algs == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + if (sshbind->banner != NULL) { + session->server_opts.custombanner = strdup(sshbind->banner); + if (session->server_opts.custombanner == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + if (sshbind->moduli_file != NULL) { + session->server_opts.moduli_file = strdup(sshbind->moduli_file); + if (session->server_opts.moduli_file == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + session->opts.rsa_min_size = sshbind->rsa_min_size; + + ssh_socket_free(session->socket); + session->socket = ssh_socket_new(session); + if (session->socket == NULL) { + /* perhaps it may be better to copy the error from session to sshbind */ + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + rc = ssh_socket_set_fd(session->socket, fd); + if (rc != SSH_OK) { + return rc; + } + handle = ssh_socket_get_poll_handle(session->socket); + if (handle == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + ssh_socket_set_connected(session->socket, handle); + + /* We must try to import any keys that could be imported in case + * we are not using ssh_bind_listen (which is the other place + * where keys can be imported) on this ssh_bind and are instead + * only using ssh_bind_accept_fd to manage sockets ourselves. + */ + if (sshbind->rsa == NULL && + sshbind->ecdsa == NULL && + sshbind->ed25519 == NULL) { + rc = ssh_bind_import_keys(sshbind); + if (rc == SSH_ERROR) { + if (!sshbind->gssapi_key_exchange) { + ssh_set_error(sshbind, SSH_FATAL, "No usable hostkeys found"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, + "No usable hostkeys found: Using \"null\" hostkey algorithm"); + } + } + +#ifdef HAVE_ECC + if (sshbind->ecdsa) { + session->srv.ecdsa_key = ssh_key_dup(sshbind->ecdsa); + if (session->srv.ecdsa_key == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } +#endif + if (sshbind->rsa) { + session->srv.rsa_key = ssh_key_dup(sshbind->rsa); + if (session->srv.rsa_key == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + if (sshbind->ed25519 != NULL) { + session->srv.ed25519_key = ssh_key_dup(sshbind->ed25519); + if (session->srv.ed25519_key == NULL){ + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + /* force PRNG to change state in case we fork after ssh_bind_accept */ + ssh_reseed(); + return SSH_OK; +} + +int ssh_bind_accept(ssh_bind sshbind, ssh_session session) +{ + socket_t fd = SSH_INVALID_SOCKET; + int rc; + + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, + "Can't accept new clients on a not bound socket."); + return SSH_ERROR; + } + + if (session == NULL) { + ssh_set_error(sshbind, SSH_FATAL, "session is null"); + return SSH_ERROR; + } + + fd = accept(sshbind->bindfd, NULL, NULL); + if (fd == SSH_INVALID_SOCKET) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + if (errno == EINTR) { + ssh_set_error(sshbind, SSH_EINTR, + "Accepting a new connection (child signal error): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } else { + ssh_set_error(sshbind, SSH_FATAL, + "Accepting a new connection: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } + return SSH_ERROR; + } + rc = ssh_bind_accept_fd(sshbind, session, fd); + + if (rc == SSH_ERROR) { + CLOSE_SOCKET(fd); + ssh_socket_free(session->socket); + } + + return rc; +} + + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/bind_config.c b/src/libs/libssh-0.12.2/src/bind_config.c new file mode 100644 index 000000000000..a8bf37039eca --- /dev/null +++ b/src/libs/libssh-0.12.2/src/bind_config.c @@ -0,0 +1,741 @@ +/* + * bind_config.c - Parse the SSH server configuration file + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#ifdef HAVE_GLOB_H +# include +#endif + +#include "libssh/bind.h" +#include "libssh/bind_config.h" +#include "libssh/config_parser.h" +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/options.h" + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 1024 +#endif + +/* Flags used for the parser state */ +#define PARSING 1 +#define IN_MATCH (1<<1) + +struct ssh_bind_config_keyword_table_s { + const char *name; + enum ssh_bind_config_opcode_e opcode; + bool allowed_in_match; +}; + +static struct ssh_bind_config_keyword_table_s +ssh_bind_config_keyword_table[] = { + { + .name = "include", + .opcode = BIND_CFG_INCLUDE + }, + { + .name = "hostkey", + .opcode = BIND_CFG_HOSTKEY + }, + { + .name = "listenaddress", + .opcode = BIND_CFG_LISTENADDRESS + }, + { + .name = "port", + .opcode = BIND_CFG_PORT + }, + { + .name = "loglevel", + .opcode = BIND_CFG_LOGLEVEL, + .allowed_in_match = true, + }, + { + .name = "ciphers", + .opcode = BIND_CFG_CIPHERS + }, + { + .name = "macs", + .opcode = BIND_CFG_MACS + }, + { + .name = "kexalgorithms", + .opcode = BIND_CFG_KEXALGORITHMS + }, + { + .name = "match", + .opcode = BIND_CFG_MATCH, + .allowed_in_match = true + }, + { + .name = "pubkeyacceptedkeytypes", + .opcode = BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES, + .allowed_in_match = true + }, + { + .name = "hostkeyalgorithms", + .opcode = BIND_CFG_HOSTKEY_ALGORITHMS, + .allowed_in_match = true + }, + { + .name = "requiredrsasize", + .opcode = BIND_CFG_REQUIRED_RSA_SIZE, + .allowed_in_match = true + }, + { + .opcode = BIND_CFG_UNKNOWN, + } +}; + +enum ssh_bind_config_match_e { + BIND_MATCH_UNKNOWN = -1, + BIND_MATCH_ALL, + BIND_MATCH_USER, + BIND_MATCH_GROUP, + BIND_MATCH_HOST, + BIND_MATCH_LOCALADDRESS, + BIND_MATCH_LOCALPORT, + BIND_MATCH_RDOMAIN, + BIND_MATCH_ADDRESS, +}; + +struct ssh_bind_config_match_keyword_table_s { + const char *name; + enum ssh_bind_config_match_e opcode; +}; + +static struct ssh_bind_config_match_keyword_table_s +ssh_bind_config_match_keyword_table[] = { + { + .name = "all", + .opcode = BIND_MATCH_ALL + }, + { + .name = "user", + .opcode = BIND_MATCH_USER + }, + { + .name = "group", + .opcode = BIND_MATCH_GROUP + }, + { + .name = "host", + .opcode = BIND_MATCH_HOST + }, + { + .name = "localaddress", + .opcode = BIND_MATCH_LOCALADDRESS + }, + { + .name = "localport", + .opcode = BIND_MATCH_LOCALPORT + }, + { + .name = "rdomain", + .opcode = BIND_MATCH_RDOMAIN + }, + { + .name = "address", + .opcode = BIND_MATCH_ADDRESS + }, + { + .opcode = BIND_MATCH_UNKNOWN + }, +}; + +static enum ssh_bind_config_opcode_e +ssh_bind_config_get_opcode(char *keyword, uint32_t *parser_flags) +{ + int i; + + for (i = 0; ssh_bind_config_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_bind_config_keyword_table[i].name) == 0) { + if ((*parser_flags & IN_MATCH) && + !(ssh_bind_config_keyword_table[i].allowed_in_match)) + { + return BIND_CFG_NOT_ALLOWED_IN_MATCH; + } + return ssh_bind_config_keyword_table[i].opcode; + } + } + + return BIND_CFG_UNKNOWN; +} + +static int +ssh_bind_config_parse_line(ssh_bind bind, + const char *line, + unsigned int count, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth); + +#define LIBSSH_BIND_CONF_MAX_DEPTH 16 +static void +local_parse_file(ssh_bind bind, + const char *filename, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth) +{ + FILE *f = NULL; + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + int rv; + + if (depth > LIBSSH_BIND_CONF_MAX_DEPTH) { + ssh_set_error(bind, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { + SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", + filename); + return; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", + filename); + + while (fgets(line, sizeof(line), f)) { + count++; + rv = ssh_bind_config_parse_line(bind, line, count, parser_flags, seen, depth); + if (rv < 0) { + fclose(f); + return; + } + } + + fclose(f); + return; +} + +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) +static void local_parse_glob(ssh_bind bind, + const char *fileglob, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth) +{ + glob_t globbuf = { + .gl_flags = 0, + }; + int rt; + u_int i; + + rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf); + if (rt == GLOB_NOMATCH) { + globfree(&globbuf); + return; + } else if (rt != 0) { + SSH_LOG(SSH_LOG_RARE, "Glob error: %s", + fileglob); + globfree(&globbuf); + return; + } + + for (i = 0; i < globbuf.gl_pathc; i++) { + local_parse_file(bind, globbuf.gl_pathv[i], parser_flags, seen, depth); + } + + globfree(&globbuf); +} +#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */ + +static enum ssh_bind_config_match_e +ssh_bind_config_get_match_opcode(const char *keyword) +{ + size_t i; + + for (i = 0; ssh_bind_config_match_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_bind_config_match_keyword_table[i].name) == 0) { + return ssh_bind_config_match_keyword_table[i].opcode; + } + } + + return BIND_MATCH_UNKNOWN; +} + +static int +ssh_bind_config_parse_line(ssh_bind bind, + const char *line, + unsigned int count, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth) +{ + enum ssh_bind_config_opcode_e opcode; + const char *p = NULL; + char *s = NULL, *x = NULL; + char *keyword = NULL; + long l; + size_t len; + + int rc = 0; + + if (bind == NULL) { + return -1; + } + + /* Ignore empty lines */ + if (line == NULL || *line == '\0') { + return 0; + } + + if (parser_flags == NULL) { + ssh_set_error_invalid(bind); + return -1; + } + + x = s = strdup(line); + if (s == NULL) { + ssh_set_error_oom(bind); + return -1; + } + + /* Remove trailing spaces */ + for (len = strlen(s) - 1; len > 0; len--) { + if (! isspace(s[len])) { + break; + } + s[len] = '\0'; + } + + keyword = ssh_config_get_token(&s); + if (keyword == NULL || *keyword == '#' || + *keyword == '\0' || *keyword == '\n') { + SAFE_FREE(x); + return 0; + } + + opcode = ssh_bind_config_get_opcode(keyword, parser_flags); + if ((*parser_flags & PARSING) && + opcode != BIND_CFG_HOSTKEY && + opcode != BIND_CFG_INCLUDE && + opcode != BIND_CFG_MATCH && + opcode > BIND_CFG_UNSUPPORTED) { /* Ignore all unknown types here */ + /* Skip all the options that were already applied */ + if (seen[opcode] != 0) { + SAFE_FREE(x); + return 0; + } + seen[opcode] = 1; + } + + switch (opcode) { + case BIND_CFG_INCLUDE: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + local_parse_glob(bind, p, parser_flags, seen, depth + 1); +#else + local_parse_file(bind, p, parser_flags, seen, depth + 1); +#endif /* HAVE_GLOB */ + } + break; + + case BIND_CFG_HOSTKEY: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set Hostkey value '%s'", + count, p); + } + } + break; + case BIND_CFG_LISTENADDRESS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set ListenAddress value '%s'", + count, p); + } + } + break; + case BIND_CFG_PORT: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set Port value '%s'", + count, p); + } + } + break; + case BIND_CFG_CIPHERS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set C->S Ciphers value '%s'", + count, p); + break; + } + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set S->C Ciphers value '%s'", + count, p); + } + } + break; + case BIND_CFG_MACS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set C->S MAC value '%s'", + count, p); + break; + } + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set S->C MAC value '%s'", + count, p); + } + } + break; + case BIND_CFG_LOGLEVEL: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + int value = -1; + + if (strcasecmp(p, "quiet") == 0) { + value = SSH_LOG_NONE; + } else if (strcasecmp(p, "fatal") == 0 || + strcasecmp(p, "error")== 0) { + value = SSH_LOG_WARN; + } else if (strcasecmp(p, "verbose") == 0 || + strcasecmp(p, "info") == 0) { + value = SSH_LOG_INFO; + } else if (strcasecmp(p, "DEBUG") == 0 || + strcasecmp(p, "DEBUG1") == 0) { + value = SSH_LOG_DEBUG; + } else if (strcasecmp(p, "DEBUG2") == 0 || + strcasecmp(p, "DEBUG3") == 0) { + value = SSH_LOG_TRACE; + } + if (value != -1) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, + &value); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set LogLevel value '%s'", + count, p); + } + } + } + break; + case BIND_CFG_KEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_KEY_EXCHANGE, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set KexAlgorithms value '%s'", + count, p); + } + } + break; + case BIND_CFG_MATCH: { + bool negate; + int result = PARSING; + size_t args = 0; + enum ssh_bind_config_match_e opt; + const char *p2 = NULL; + + /* The options set in Match blocks should be applied when a connection + * is accepted, and not right away when parsing the file (as it is + * currently done). This means the configuration files should be parsed + * again or the options set in the Match blocks should be stored and + * applied as necessary. */ + + /* If this is the first Match block, erase the seen table to allow + * options to be overridden. Erasing the seen table was the easiest way + * to allow overriding an option, but only for the first occurrence of + * an option in a Match block. This is sufficient for the current + * implementation which supports only the 'All' criterion, meaning the + * options can be applied right away. */ + if (!(*parser_flags & IN_MATCH)) { + memset(seen, 0x00, BIND_CFG_MAX * sizeof(uint8_t)); + } + + /* In this line the PARSING bit is cleared from the flags */ + *parser_flags = IN_MATCH; + do { + p = p2 = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + break; + } + args++; + SSH_LOG(SSH_LOG_TRACE, "line %d: Processing Match keyword '%s'", + count, p); + + /* If the option is prefixed with ! the result should be negated */ + negate = false; + if (p[0] == '!') { + negate = true; + p++; + } + + opt = ssh_bind_config_get_match_opcode(p); + switch (opt) { + case BIND_MATCH_ALL: + p = ssh_config_get_str_tok(&s, NULL); + if ((args == 1) && (p == NULL || p[0] == '\0')) { + /* The "all" keyword does not accept arguments or modifiers + */ + if (negate == true) { + result = 0; + } + break; + } + ssh_set_error(bind, SSH_FATAL, + "line %d: ERROR - Match all cannot be combined with " + "other Match attributes", count); + SAFE_FREE(x); + return -1; + case BIND_MATCH_USER: + case BIND_MATCH_GROUP: + case BIND_MATCH_HOST: + case BIND_MATCH_LOCALADDRESS: + case BIND_MATCH_LOCALPORT: + case BIND_MATCH_RDOMAIN: + case BIND_MATCH_ADDRESS: + /* Only "All" is supported for now */ + /* Skip one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " + "'%s' requires argument\n", count, p2); + SAFE_FREE(x); + return -1; + } + args++; + SSH_LOG(SSH_LOG_DEBUG, + "line %d: Unsupported Match keyword '%s', ignoring\n", + count, + p2); + result = 0; + break; + case BIND_MATCH_UNKNOWN: + default: + ssh_set_error(bind, SSH_FATAL, + "ERROR - Unknown argument '%s' for Match keyword", p); + SAFE_FREE(x); + return -1; + } + } while (p != NULL && p[0] != '\0'); + if (args == 0) { + ssh_set_error(bind, SSH_FATAL, + "ERROR - Match keyword requires an argument"); + SAFE_FREE(x); + return -1; + } + /* This line only sets the PARSING flag if all checks passed */ + *parser_flags |= result; + break; + } + case BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set PubKeyAcceptedKeyTypes value '%s'", + count, p); + } + } + break; + case BIND_CFG_HOSTKEY_ALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set HostkeyAlgorithms value '%s'", + count, p); + } + } + break; + case BIND_CFG_REQUIRED_RSA_SIZE: + l = ssh_config_get_long(&s, -1); + if (l >= 0 && l <= INT_MAX && (*parser_flags & PARSING)) { + int i = (int)l; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &i); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set RequiredRSASize value '%ld'", + count, + l); + } + } + break; + case BIND_CFG_NOT_ALLOWED_IN_MATCH: + SSH_LOG(SSH_LOG_DEBUG, "Option not allowed in Match block: %s, line: %d", + keyword, count); + break; + case BIND_CFG_UNKNOWN: + SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d", + keyword, count); + break; + case BIND_CFG_UNSUPPORTED: + SSH_LOG(SSH_LOG_TRACE, "Unsupported option: %s, line: %d", + keyword, count); + break; + case BIND_CFG_NA: + SSH_LOG(SSH_LOG_TRACE, "Option not applicable: %s, line: %d", + keyword, count); + break; + default: + ssh_set_error(bind, SSH_FATAL, "ERROR - unimplemented opcode: %d", + opcode); + SAFE_FREE(x); + return -1; + break; + } + + SAFE_FREE(x); + return rc; +} + +int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) +{ + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + FILE *f = NULL; + uint32_t parser_flags; + int rv; + + /* This local table is used during the parsing of the current file (and + * files included recursively in this file) to prevent an option to be + * redefined, i.e. the first value set is kept. But this DO NOT prevent the + * option to be redefined later by another file. */ + uint8_t seen[BIND_CFG_MAX] = {0}; + + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { + return 0; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename); + + parser_flags = PARSING; + while (fgets(line, sizeof(line), f)) { + count++; + rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen, 0); + if (rv) { + fclose(f); + return -1; + } + } + + fclose(f); + return 0; +} + +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input) +{ + char line[MAX_LINE_SIZE] = {0}; + const char *c = input, *line_start = input; + unsigned int line_num = 0; + size_t line_len; + uint32_t parser_flags; + int rv; + + /* This local table is used during the parsing of the current file (and + * files included recursively in this file) to prevent an option to be + * redefined, i.e. the first value set is kept. But this DO NOT prevent the + * option to be redefined later by another file. */ + uint8_t seen[BIND_CFG_MAX] = {0}; + + SSH_LOG(SSH_LOG_DEBUG, "Reading bind configuration data from string:"); + SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input); + + parser_flags = PARSING; + while (1) { + line_num++; + line_start = c; + c = strchr(line_start, '\n'); + if (c == NULL) { + /* if there is no newline at the end of the string */ + c = strchr(line_start, '\0'); + } + if (c == NULL) { + /* should not happen, would mean a string without trailing '\0' */ + SSH_LOG(SSH_LOG_WARN, "No trailing '\\0' in config string"); + return SSH_ERROR; + } + line_len = c - line_start; + if (line_len > MAX_LINE_SIZE - 1) { + SSH_LOG(SSH_LOG_WARN, + "Line %u too long: %zu characters", + line_num, + line_len); + return SSH_ERROR; + } + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); + rv = ssh_bind_config_parse_line(bind, line, line_num, &parser_flags, seen, 0); + if (rv < 0) { + return SSH_ERROR; + } + if (*c == '\0') { + break; + } + c++; + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/buffer.c b/src/libs/libssh-0.12.2/src/buffer.c new file mode 100644 index 000000000000..601191755f2b --- /dev/null +++ b/src/libs/libssh-0.12.2/src/buffer.c @@ -0,0 +1,1450 @@ +/* + * buffer.c - buffer functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/bignum.h" + +/* + * Describes a buffer state + * [XXXXXXXXXXXXDATA PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXX] + * ^ ^ ^ ^] + * \_data points\_pos points here \_used points here | / + * here Allocated + */ +struct ssh_buffer_struct { + bool secure; + uint32_t used; + uint32_t allocated; + uint32_t pos; + uint8_t *data; +}; + +/* Buffer size maximum is 256M */ +#define BUFFER_SIZE_MAX 0x10000000 + +/** + * @defgroup libssh_buffer The SSH buffer functions + * @ingroup libssh + * + * Functions to handle SSH buffers. + * + * @{ + */ + + +#ifdef DEBUG_BUFFER +/** + * @internal + * + * @brief Check that preconditions and postconditions are valid. + * + * @param[in] buf The buffer to check. + */ +static void buffer_verify(ssh_buffer buf) +{ + bool do_abort = false; + + if (buf->data == NULL) { + return; + } + + if (buf->used > buf->allocated) { + fprintf(stderr, + "BUFFER ERROR: allocated %u, used %u\n", + buf->allocated, + buf->used); + do_abort = true; + } + if (buf->pos > buf->used) { + fprintf(stderr, + "BUFFER ERROR: position %u, used %u\n", + buf->pos, + buf->used); + do_abort = true; + } + if (buf->pos > buf->allocated) { + fprintf(stderr, + "BUFFER ERROR: position %u, allocated %u\n", + buf->pos, + buf->allocated); + do_abort = true; + } + if (do_abort) { + abort(); + } +} + +#else +#define buffer_verify(x) +#endif + +/** + * @brief Create a new SSH buffer. + * + * @return A newly initialized SSH buffer, NULL on error. + */ +struct ssh_buffer_struct *ssh_buffer_new(void) +{ + struct ssh_buffer_struct *buf = NULL; + int rc; + + buf = calloc(1, sizeof(struct ssh_buffer_struct)); + if (buf == NULL) { + return NULL; + } + + /* + * Always preallocate 64 bytes. + * + * -1 for realloc_buffer magic. + */ + rc = ssh_buffer_allocate_size(buf, 64 - 1); + if (rc != 0) { + SAFE_FREE(buf); + return NULL; + } + buffer_verify(buf); + + return buf; +} + +/** + * @brief Deallocate a SSH buffer. + * + * \param[in] buffer The buffer to free. + */ +void ssh_buffer_free(struct ssh_buffer_struct *buffer) +{ + if (buffer == NULL) { + return; + } + buffer_verify(buffer); + + if (buffer->secure && buffer->allocated > 0) { + /* burn the data */ + ssh_burn(buffer->data, buffer->allocated); + SAFE_FREE(buffer->data); + + ssh_burn(buffer, sizeof(struct ssh_buffer_struct)); + } else { + SAFE_FREE(buffer->data); + } + SAFE_FREE(buffer); +} + +/** + * @brief Sets the buffer as secure. + * + * A secure buffer will never leave cleartext data in the heap + * after being reallocated or freed. + * + * @param[in] buffer buffer to set secure. + */ +void ssh_buffer_set_secure(ssh_buffer buffer) +{ + buffer->secure = true; +} + +static int realloc_buffer(struct ssh_buffer_struct *buffer, uint32_t needed) +{ + uint32_t smallest = 1; + uint8_t *new = NULL; + + buffer_verify(buffer); + + /* Find the smallest power of two which is greater or equal to needed */ + while(smallest <= needed) { + if (smallest == 0) { + return -1; + } + smallest <<= 1; + } + needed = smallest; + + if (needed > BUFFER_SIZE_MAX) { + return -1; + } + + if (buffer->secure) { + new = malloc(needed); + if (new == NULL) { + return -1; + } + memcpy(new, buffer->data, buffer->used); + ssh_burn(buffer->data, buffer->used); + SAFE_FREE(buffer->data); + } else { + new = realloc(buffer->data, needed); + if (new == NULL) { + return -1; + } + } + buffer->data = new; + buffer->allocated = needed; + + buffer_verify(buffer); + return 0; +} + +/** @internal + * @brief shifts a buffer to remove unused data in the beginning + * @param buffer SSH buffer + */ +static void buffer_shift(ssh_buffer buffer) +{ + size_t burn_pos = buffer->pos; + + buffer_verify(buffer); + + if (buffer->pos == 0) { + return; + } + memmove(buffer->data, + buffer->data + buffer->pos, + buffer->used - buffer->pos); + buffer->used -= buffer->pos; + buffer->pos = 0; + + if (buffer->secure) { + void *ptr = buffer->data + buffer->used; + ssh_burn(ptr, burn_pos); + } + + buffer_verify(buffer); +} + +/** + * @brief Reinitialize a SSH buffer. + * + * In case the buffer has exceeded 64K in size, the buffer will be reallocated + * to 64K. + * + * @param[in] buffer The buffer to reinitialize. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_reinit(struct ssh_buffer_struct *buffer) +{ + if (buffer == NULL) { + return -1; + } + + buffer_verify(buffer); + + if (buffer->secure && buffer->allocated > 0) { + ssh_burn(buffer->data, buffer->allocated); + } + buffer->used = 0; + buffer->pos = 0; + + /* If the buffer is bigger then 64K, reset it to 64K */ + if (buffer->allocated > 65536) { + int rc; + + /* -1 for realloc_buffer magic */ + rc = realloc_buffer(buffer, 65536 - 1); + if (rc != 0) { + return -1; + } + } + + buffer_verify(buffer); + + return 0; +} + +/** + * @brief Add data at the tail of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] data A pointer to the data to add. + * + * @param[in] len The length of the data to add. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_add_data(struct ssh_buffer_struct *buffer, const void *data, uint32_t len) +{ + if (buffer == NULL) { + return -1; + } + + buffer_verify(buffer); + + if (data == NULL) { + return -1; + } + + if (buffer->used + len < len) { + return -1; + } + + if (buffer->allocated < (buffer->used + len)) { + if (buffer->pos > 0) { + buffer_shift(buffer); + } + if (realloc_buffer(buffer, buffer->used + len) < 0) { + return -1; + } + } + + memcpy(buffer->data + buffer->used, data, len); + buffer->used += len; + buffer_verify(buffer); + return 0; +} + +/** + * @brief Ensure the buffer has at least a certain preallocated size. + * + * @param[in] buffer The buffer to enlarge. + * + * @param[in] len The length to ensure as allocated. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_allocate_size(struct ssh_buffer_struct *buffer, + uint32_t len) +{ + buffer_verify(buffer); + + if (buffer->allocated < len) { + if (buffer->pos > 0) { + buffer_shift(buffer); + } + if (realloc_buffer(buffer, len) < 0) { + return -1; + } + } + + buffer_verify(buffer); + + return 0; +} + +/** + * @internal + * + * @brief Allocate space for data at the tail of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] len The length of the data to add. + * + * @return Pointer on the allocated space + * NULL on error. + */ +void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len) +{ + void *ptr = NULL; + + buffer_verify(buffer); + + if (buffer->used + len < len) { + return NULL; + } + + if (buffer->allocated < (buffer->used + len)) { + if (buffer->pos > 0) { + buffer_shift(buffer); + } + + if (realloc_buffer(buffer, buffer->used + len) < 0) { + return NULL; + } + } + + ptr = buffer->data + buffer->used; + buffer->used+=len; + buffer_verify(buffer); + + return ptr; +} + +/** + * @internal + * + * @brief Add a SSH string to the tail of a buffer. + * + * @param[in] buffer The buffer to add the string. + * + * @param[in] string The SSH String to add. + * + * @return 0 on success, < 0 on error. + */ +int +ssh_buffer_add_ssh_string(struct ssh_buffer_struct *buffer, + struct ssh_string_struct *string) +{ + size_t len; + int rc; + + if (string == NULL) { + return -1; + } + + len = ssh_string_len(string) + sizeof(uint32_t); + /* this can't overflow the uint32_t as the + * STRING_SIZE_MAX is (UINT32_MAX >> 8) + 1 */ + rc = ssh_buffer_add_data(buffer, string, (uint32_t)len); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 32 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 32 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u32(struct ssh_buffer_struct *buffer,uint32_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(data)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 16 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 16 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u16(struct ssh_buffer_struct *buffer,uint16_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(data)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 64 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 64 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u64(struct ssh_buffer_struct *buffer, uint64_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(data)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 8 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 8 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u8(struct ssh_buffer_struct *buffer,uint8_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(uint8_t)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add data at the head of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] data The data to prepend. + * + * @param[in] len The length of data to prepend. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_prepend_data(struct ssh_buffer_struct *buffer, const void *data, + uint32_t len) { + buffer_verify(buffer); + + if(len <= buffer->pos){ + /* It's possible to insert data between begin and pos */ + memcpy(buffer->data + (buffer->pos - len), data, len); + buffer->pos -= len; + buffer_verify(buffer); + return 0; + } + /* pos isn't high enough */ + if (buffer->used - buffer->pos + len < len) { + return -1; + } + + if (buffer->allocated < (buffer->used - buffer->pos + len)) { + if (realloc_buffer(buffer, buffer->used - buffer->pos + len) < 0) { + return -1; + } + } + memmove(buffer->data + len, buffer->data + buffer->pos, buffer->used - buffer->pos); + memcpy(buffer->data, data, len); + buffer->used += len - buffer->pos; + buffer->pos = 0; + buffer_verify(buffer); + return 0; +} + +/** + * @internal + * + * @brief Append data from a buffer to the tail of another buffer. + * + * @param[in] buffer The destination buffer. + * + * @param[in] source The source buffer to append. It doesn't take the + * position of the buffer into account. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_buffer(struct ssh_buffer_struct *buffer, + struct ssh_buffer_struct *source) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, + ssh_buffer_get(source), + ssh_buffer_get_len(source)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @brief Get a pointer to the head of a buffer at the current position. + * + * @param[in] buffer The buffer to get the head pointer. + * + * @return A pointer to the data from current position. + * + * @see ssh_buffer_get_len() + */ +void *ssh_buffer_get(struct ssh_buffer_struct *buffer){ + return buffer->data + buffer->pos; +} + +/** + * @brief Get the length of the buffer from the current position. + * + * @param[in] buffer The buffer to get the length from. + * + * @return The length of the buffer. + * + * @see ssh_buffer_get() + */ +uint32_t ssh_buffer_get_len(struct ssh_buffer_struct *buffer){ + buffer_verify(buffer); + return buffer->used - buffer->pos; +} + +/** + * @internal + * + * @brief Duplicate an existing buffer. + * + * Creates a new ssh_buffer and copies all data from the source buffer. + * The new buffer preserves the secure flag setting of the source. + * + * @param[in] buffer The buffer to duplicate. Can be NULL. + * + * @return A new buffer containing a copy of the data on success, + * NULL on failure or if buffer is NULL. + * + * @see ssh_buffer_free() + */ +ssh_buffer ssh_buffer_dup(const ssh_buffer buffer) +{ + ssh_buffer new_buffer = NULL; + int rc; + + if (buffer == NULL) { + return NULL; + } + + buffer_verify(buffer); + + new_buffer = ssh_buffer_new(); + if (new_buffer == NULL) { + return NULL; + } + + new_buffer->secure = buffer->secure; + + if (ssh_buffer_get_len(buffer) > 0) { + rc = ssh_buffer_add_data(new_buffer, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc != SSH_OK) { + ssh_buffer_free(new_buffer); + return NULL; + } + } + + buffer_verify(new_buffer); + return new_buffer; +} + +/** + * @internal + * + * @brief Advance the position in the buffer. + * + * This has effect to "eat" bytes at head of the buffer. + * + * @param[in] buffer The buffer to advance the position. + * + * @param[in] len The number of bytes to eat. + * + * @return The new size of the buffer. + */ +uint32_t ssh_buffer_pass_bytes(struct ssh_buffer_struct *buffer, uint32_t len){ + buffer_verify(buffer); + + if (buffer->pos + len < len || buffer->used < buffer->pos + len) { + return 0; + } + + buffer->pos+=len; + /* if the buffer is empty after having passed the whole bytes into it, we can clean it */ + if(buffer->pos==buffer->used){ + buffer->pos=0; + buffer->used=0; + } + buffer_verify(buffer); + return len; +} + +/** + * @internal + * + * @brief Cut the end of the buffer. + * + * @param[in] buffer The buffer to cut. + * + * @param[in] len The number of bytes to remove from the tail. + * + * @return The new size of the buffer. + */ +uint32_t ssh_buffer_pass_bytes_end(struct ssh_buffer_struct *buffer, uint32_t len){ + buffer_verify(buffer); + + if (buffer->used < len) { + return 0; + } + + buffer->used-=len; + buffer_verify(buffer); + return len; +} + +/** + * @brief Get the remaining data out of the buffer and adjust the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data The data buffer where to store the data. + * + * @param[in] len The length to read from the buffer. + * + * @returns 0 if there is not enough data in buffer, len otherwise. + */ +uint32_t ssh_buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint32_t len) +{ + int rc; + + /* + * Check for a integer overflow first, then check if not enough data is in + * the buffer. + */ + rc = ssh_buffer_validate_length(buffer, len); + if (rc != SSH_OK) { + return 0; + } + memcpy(data,buffer->data+buffer->pos,len); + buffer->pos+=len; + return len; /* no yet support for partial reads (is it really needed ?? ) */ +} + +/** + * @internal + * + * @brief Get a 8 bits unsigned int out of the buffer and adjust the read + * pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint8_t where to store the data. + * + * @returns 0 if there is not enough data in buffer, 1 otherwise. + */ +uint32_t ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ + return ssh_buffer_get_data(buffer,data,sizeof(uint8_t)); +} + +/** + * @internal + * + * @brief gets a 32 bits unsigned int out of the buffer. Adjusts the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint32_t where to store the data. + * + * @returns 0 if there is not enough data in buffer, 4 otherwise. + */ +uint32_t ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ + return ssh_buffer_get_data(buffer,data,sizeof(uint32_t)); +} +/** + * @internal + * + * @brief Get a 64 bits unsigned int out of the buffer and adjusts the read + * pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint64_t where to store the data. + * + * @returns 0 if there is not enough data in buffer, 8 otherwise. + */ +uint32_t ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ + return ssh_buffer_get_data(buffer,data,sizeof(uint64_t)); +} + +/** + * @brief Validates that the given length can be obtained from the buffer. + * + * @param[in] buffer The buffer to read from. + * + * @param[in] len The length to be checked. + * + * @return SSH_OK if the length is valid, SSH_ERROR otherwise. + */ +int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) +{ + if (buffer == NULL || buffer->pos + len < len || + buffer->pos + len > buffer->used) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief Get an SSH String out of the buffer and adjust the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @returns The SSH String, NULL on error. + */ +struct ssh_string_struct * +ssh_buffer_get_ssh_string(struct ssh_buffer_struct *buffer) +{ + uint32_t stringlen; + uint32_t hostlen; + struct ssh_string_struct *str = NULL; + int rc; + + rc = ssh_buffer_get_u32(buffer, &stringlen); + if (rc == 0) { + return NULL; + } + hostlen = ntohl(stringlen); + /* verify if there is enough space in buffer to get it */ + rc = ssh_buffer_validate_length(buffer, hostlen); + if (rc != SSH_OK) { + return NULL; /* it is indeed */ + } + str = ssh_string_new(hostlen); + if (str == NULL) { + return NULL; + } + + stringlen = ssh_buffer_get_data(buffer, ssh_string_data(str), hostlen); + if (stringlen != hostlen) { + /* should never happen */ + SAFE_FREE(str); + return NULL; + } + + return str; +} + +/** + * @brief Pre-calculate the size we need for packing the buffer. + * + * This makes sure that enough memory is allocated for packing the buffer and + * we only have to do one memory allocation. + * + * @param[in] buffer The buffer to allocate + * + * @param[in] format A format string of arguments. + * + * @param[in] argc The number of arguments. + * + * @param[in] ap The va_list of arguments. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) +{ + const char *p = NULL; + ssh_string string = NULL; + char *cstring = NULL; + bignum b = NULL; + size_t needed_size = 0; + size_t len; + size_t count; + int rc = SSH_OK; + + for (p = format, count = 0; *p != '\0'; p++, count++) { + /* Invalid number of arguments passed */ + if (count > argc) { + return SSH_ERROR; + } + + switch(*p) { + case 'b': + va_arg(ap, unsigned int); + needed_size += sizeof(uint8_t); + break; + case 'w': + va_arg(ap, unsigned int); + needed_size += sizeof(uint16_t); + break; + case 'd': + va_arg(ap, uint32_t); + needed_size += sizeof(uint32_t); + break; + case 'q': + va_arg(ap, uint64_t); + needed_size += sizeof(uint64_t); + break; + case 'S': + string = va_arg(ap, ssh_string); + needed_size += sizeof(uint32_t) + ssh_string_len(string); + string = NULL; + break; + case 's': + cstring = va_arg(ap, char *); + needed_size += sizeof(uint32_t) + strlen(cstring); + cstring = NULL; + break; + case 'P': + len = va_arg(ap, size_t); + needed_size += len; + va_arg(ap, void *); + count++; /* increase argument count */ + break; + case 'F': + case 'B': + b = va_arg(ap, bignum); + if (*p == 'F') { + /* For padded bignum, we know the exact length */ + len = va_arg(ap, size_t); + count++; /* increase argument count */ + needed_size += sizeof(uint32_t) + len; + } else { + /* The bignum bytes + 1 for possible padding */ + needed_size += sizeof(uint32_t) + bignum_num_bytes(b) + 1; + } + break; + case 't': + cstring = va_arg(ap, char *); + needed_size += strlen(cstring); + cstring = NULL; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); + rc = SSH_ERROR; + } + if (rc != SSH_OK){ + break; + } + } + + if (argc != count) { + return SSH_ERROR; + } + + if (rc != SSH_ERROR){ + /* + * Check if our canary is intact, if not, something really bad happened. + */ + uint32_t canary = va_arg(ap, uint32_t); + if (canary != SSH_BUFFER_PACK_END) { + abort(); + } + } + + rc = ssh_buffer_allocate_size(buffer, (uint32_t)needed_size); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** @internal + * @brief Add multiple values in a buffer on a single function call + * @param[in] buffer The buffer to add to + * @param[in] format A format string of arguments. + * @param[in] ap A va_list of arguments. + * @returns SSH_OK on success + * SSH_ERROR on error + * @see ssh_buffer_add_format() for format list values. + */ +static int +ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) +{ + int rc = SSH_ERROR; + const char *p = NULL; + union { + uint8_t byte; + uint16_t word; + uint32_t dword; + uint64_t qword; + ssh_string string; + void *data; + } o; + char *cstring = NULL; + bignum b; + size_t len; + size_t count; + + if (argc > 256) { + return SSH_ERROR; + } + + for (p = format, count = 0; *p != '\0'; p++, count++) { + /* Invalid number of arguments passed */ + if (count > argc) { + return SSH_ERROR; + } + + switch(*p) { + case 'b': + o.byte = (uint8_t)va_arg(ap, unsigned int); + rc = ssh_buffer_add_u8(buffer, o.byte); + break; + case 'w': + o.word = (uint16_t)va_arg(ap, unsigned int); + o.word = htons(o.word); + rc = ssh_buffer_add_u16(buffer, o.word); + break; + case 'd': + o.dword = va_arg(ap, uint32_t); + o.dword = htonl(o.dword); + rc = ssh_buffer_add_u32(buffer, o.dword); + break; + case 'q': + o.qword = va_arg(ap, uint64_t); + o.qword = htonll(o.qword); + rc = ssh_buffer_add_u64(buffer, o.qword); + break; + case 'S': + o.string = va_arg(ap, ssh_string); + rc = ssh_buffer_add_ssh_string(buffer, o.string); + o.string = NULL; + break; + case 's': + cstring = va_arg(ap, char *); + len = strlen(cstring); + if (len > UINT32_MAX) { + rc = SSH_ERROR; + break; + } + o.dword = (uint32_t)len; + rc = ssh_buffer_add_u32(buffer, htonl(o.dword)); + if (rc == SSH_OK){ + rc = ssh_buffer_add_data(buffer, cstring, o.dword); + } + cstring = NULL; + break; + case 'P': + len = va_arg(ap, size_t); + if (len > UINT32_MAX) { + rc = SSH_ERROR; + break; + } + + o.data = va_arg(ap, void *); + count++; /* increase argument count */ + + rc = ssh_buffer_add_data(buffer, o.data, (uint32_t)len); + o.data = NULL; + break; + case 'F': + case 'B': + b = va_arg(ap, bignum); + if (*p == 'F') { + len = va_arg(ap, size_t); + count++; /* increase argument count */ + o.string = ssh_make_padded_bignum_string(b, len); + } else { + o.string = ssh_make_bignum_string(b); + } + if(o.string == NULL){ + rc = SSH_ERROR; + break; + } + rc = ssh_buffer_add_ssh_string(buffer, o.string); + SAFE_FREE(o.string); + break; + case 't': + cstring = va_arg(ap, char *); + len = strlen(cstring); + if (len > UINT32_MAX) { + rc = SSH_ERROR; + break; + } + rc = ssh_buffer_add_data(buffer, cstring, (uint32_t)len); + cstring = NULL; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); + rc = SSH_ERROR; + } + if (rc != SSH_OK){ + break; + } + } + + if (argc != count) { + return SSH_ERROR; + } + + if (rc != SSH_ERROR){ + /* Check if our canary is intact, if not something really bad happened */ + uint32_t canary = va_arg(ap, uint32_t); + if (canary != SSH_BUFFER_PACK_END) { + abort(); + } + } + return rc; +} + +/** @internal + * @brief Add multiple values in a buffer on a single function call + * @param[in] buffer The buffer to add to + * @param[in] format A format string of arguments. This string contains single + * letters describing the order and type of arguments: + * 'b': uint8_t (pushed in network byte order) + * 'w': uint16_t (pushed in network byte order) + * 'd': uint32_t (pushed in network byte order) + * 'q': uint64_t (pushed in network byte order) + * 'S': ssh_string + * 's': char * (C string, pushed as SSH string) + * 't': char * (C string, pushed as free text) + * 'P': size_t, void * (len of data, pointer to data) + * only pushes data. + * 'B': bignum (pushed as SSH string) + * 'F': bignum, size_t (bignum, padded to fixed length, + * pushed as SSH string) + * @returns SSH_OK on success + * SSH_ERROR on error + * @warning when using 'P' with a constant size (e.g. 8), do not + * forget to cast to (size_t). + */ +int _ssh_buffer_pack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...) +{ + va_list ap; + int rc; + + if (argc > 256) { + return SSH_ERROR; + } + + va_start(ap, argc); + rc = ssh_buffer_pack_allocate_va(buffer, format, argc, ap); + va_end(ap); + + if (rc != SSH_OK) { + return rc; + } + + va_start(ap, argc); + rc = ssh_buffer_pack_va(buffer, format, argc, ap); + va_end(ap); + + return rc; +} + +/** @internal + * @brief Get multiple values from a buffer on a single function call + * @param[in] buffer The buffer to get from + * @param[in] format A format string of arguments. + * @param[in] ap A va_list of arguments. + * @returns SSH_OK on success + * SSH_ERROR on error + * @see ssh_buffer_get_format() for format list values. + */ +int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) +{ + int rc = SSH_ERROR; + const char *p = format, *last = NULL; + union { + uint8_t *byte; + uint16_t *word; + uint32_t *dword; + uint64_t *qword; + ssh_string *string; + char **cstring; + bignum *bignum; + void **data; + } o; + size_t len; + uint32_t rlen, max_len; + ssh_string tmp_string = NULL; + va_list ap_copy; + size_t count; + + max_len = ssh_buffer_get_len(buffer); + + /* copy the argument list in case a rollback is needed */ + va_copy(ap_copy, ap); + + if (argc > 256) { + rc = SSH_ERROR; + goto cleanup; + } + + for (count = 0; *p != '\0'; p++, count++) { + /* Invalid number of arguments passed */ + if (count > argc) { + rc = SSH_ERROR; + goto cleanup; + } + + rc = SSH_ERROR; + switch (*p) { + case 'b': + o.byte = va_arg(ap, uint8_t *); + rlen = ssh_buffer_get_u8(buffer, o.byte); + rc = rlen==1 ? SSH_OK : SSH_ERROR; + break; + case 'w': + o.word = va_arg(ap, uint16_t *); + rlen = ssh_buffer_get_data(buffer, o.word, sizeof(uint16_t)); + if (rlen == 2) { + *o.word = ntohs(*o.word); + rc = SSH_OK; + } + break; + case 'd': + o.dword = va_arg(ap, uint32_t *); + rlen = ssh_buffer_get_u32(buffer, o.dword); + if (rlen == 4) { + *o.dword = ntohl(*o.dword); + rc = SSH_OK; + } + break; + case 'q': + o.qword = va_arg(ap, uint64_t*); + rlen = ssh_buffer_get_u64(buffer, o.qword); + if (rlen == 8) { + *o.qword = ntohll(*o.qword); + rc = SSH_OK; + } + break; + case 'B': + o.bignum = va_arg(ap, bignum *); + *o.bignum = NULL; + tmp_string = ssh_buffer_get_ssh_string(buffer); + if (tmp_string == NULL) { + break; + } + *o.bignum = ssh_make_string_bn(tmp_string); + ssh_string_burn(tmp_string); + SSH_STRING_FREE(tmp_string); + rc = (*o.bignum != NULL) ? SSH_OK : SSH_ERROR; + break; + case 'S': + o.string = va_arg(ap, ssh_string *); + *o.string = ssh_buffer_get_ssh_string(buffer); + rc = *o.string != NULL ? SSH_OK : SSH_ERROR; + o.string = NULL; + break; + case 's': { + uint32_t u32len = 0; + + o.cstring = va_arg(ap, char **); + *o.cstring = NULL; + rlen = ssh_buffer_get_u32(buffer, &u32len); + if (rlen != 4){ + break; + } + u32len = ntohl(u32len); + if (u32len > max_len - 1) { + break; + } + + rc = ssh_buffer_validate_length(buffer, u32len); + if (rc != SSH_OK) { + break; + } + + *o.cstring = malloc(u32len + 1); + if (*o.cstring == NULL){ + rc = SSH_ERROR; + break; + } + rlen = ssh_buffer_get_data(buffer, *o.cstring, u32len); + if (rlen != u32len) { + SAFE_FREE(*o.cstring); + rc = SSH_ERROR; + break; + } + (*o.cstring)[u32len] = '\0'; + o.cstring = NULL; + rc = SSH_OK; + break; + } + case 'P': + len = va_arg(ap, size_t); + if (len > max_len - 1) { + rc = SSH_ERROR; + break; + } + + rc = ssh_buffer_validate_length(buffer, len); + if (rc != SSH_OK) { + break; + } + + o.data = va_arg(ap, void **); + count++; + + *o.data = malloc(len); + if(*o.data == NULL){ + rc = SSH_ERROR; + break; + } + rlen = ssh_buffer_get_data(buffer, *o.data, (uint32_t)len); + if (rlen != len){ + SAFE_FREE(*o.data); + rc = SSH_ERROR; + break; + } + o.data = NULL; + rc = SSH_OK; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); + } + if (rc != SSH_OK) { + break; + } + } + + if (argc != count) { + rc = SSH_ERROR; + } + +cleanup: + if (rc != SSH_ERROR){ + /* Check if our canary is intact, if not something really bad happened */ + uint32_t canary = va_arg(ap, uint32_t); + if (canary != SSH_BUFFER_PACK_END){ + abort(); + } + } + + if (rc != SSH_OK){ + /* Reset the format string and erase everything that was allocated */ + last = p; + for(p=format;psecure) { + ssh_burn(o.byte, sizeof(uint8_t)); + break; + } + break; + case 'w': + o.word = va_arg(ap_copy, uint16_t *); + if (buffer->secure) { + ssh_burn(o.word, sizeof(uint16_t)); + break; + } + break; + case 'd': + o.dword = va_arg(ap_copy, uint32_t *); + if (buffer->secure) { + ssh_burn(o.dword, sizeof(uint32_t)); + break; + } + break; + case 'q': + o.qword = va_arg(ap_copy, uint64_t *); + if (buffer->secure) { + ssh_burn(o.qword, sizeof(uint64_t)); + break; + } + break; + case 'B': + o.bignum = va_arg(ap_copy, bignum *); + bignum_safe_free(*o.bignum); + break; + case 'S': + o.string = va_arg(ap_copy, ssh_string *); + if (buffer->secure) { + ssh_string_burn(*o.string); + } + SAFE_FREE(*o.string); + break; + case 's': + o.cstring = va_arg(ap_copy, char **); + if (buffer->secure) { + ssh_burn(*o.cstring, strlen(*o.cstring)); + } + SAFE_FREE(*o.cstring); + break; + case 'P': + len = va_arg(ap_copy, size_t); + o.data = va_arg(ap_copy, void **); + if (buffer->secure) { + ssh_burn(*o.data, len); + } + SAFE_FREE(*o.data); + break; + default: + (void)va_arg(ap_copy, void *); + break; + } + } + } + va_end(ap_copy); + + return rc; +} + +/** @internal + * @brief Get multiple values from a buffer on a single function call + * @param[in] buffer The buffer to get from + * @param[in] format A format string of arguments. This string contains single + * letters describing the order and type of arguments: + * 'b': uint8_t * (pulled in network byte order) + * 'w': uint16_t * (pulled in network byte order) + * 'd': uint32_t * (pulled in network byte order) + * 'q': uint64_t * (pulled in network byte order) + * 'S': ssh_string * + * 's': char ** (C string, pulled as SSH string) + * 'P': size_t, void ** (len of data, pointer to data) + * only pulls data. + * 'B': bignum * (pulled as SSH string) + * @returns SSH_OK on success + * SSH_ERROR on error + * @warning when using 'P' with a constant size (e.g. 8), do not + * forget to cast to (size_t). + */ +int _ssh_buffer_unpack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...) +{ + va_list ap; + int rc; + + va_start(ap, argc); + rc = ssh_buffer_unpack_va(buffer, format, argc, ap); + va_end(ap); + return rc; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/callbacks.c b/src/libs/libssh-0.12.2/src/callbacks.c new file mode 100644 index 000000000000..94ea729eaad5 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/callbacks.c @@ -0,0 +1,156 @@ +/* + * callbacks.c - callback functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/callbacks.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +#define is_callback_valid(session, cb) \ + (cb->size > 0 || cb->size <= 1024 * sizeof(void *)) + +/* LEGACY */ +static void ssh_legacy_log_callback(int priority, + const char *function, + const char *buffer, + void *userdata) +{ + ssh_session session = (ssh_session)userdata; + ssh_log_callback log_fn = session->common.callbacks->log_function; + void *log_data = session->common.callbacks->userdata; + + (void)function; /* unused */ + + log_fn(session, priority, buffer, log_data); +} + +void _ssh_remove_legacy_log_cb(void) +{ + if (ssh_get_log_callback() == ssh_legacy_log_callback) { + _ssh_reset_log_cb(); + ssh_set_log_userdata(NULL); + } +} + +int ssh_set_callbacks(ssh_session session, ssh_callbacks cb) +{ + if (session == NULL || cb == NULL) { + return SSH_ERROR; + } + + if (!is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + session->common.callbacks = cb; + + /* LEGACY */ + if (ssh_get_log_callback() == NULL && cb->log_function) { + ssh_set_log_callback(ssh_legacy_log_callback); + ssh_set_log_userdata(session); + } + + return 0; +} + +static int ssh_add_set_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb, + int prepend) +{ + ssh_session session = NULL; + int rc; + + if (channel == NULL || cb == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if (!is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + if (channel->callbacks == NULL) { + channel->callbacks = ssh_list_new(); + if (channel->callbacks == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + if (prepend) { + rc = ssh_list_prepend(channel->callbacks, cb); + } else { + rc = ssh_list_append(channel->callbacks, cb); + } + + return rc; +} + +int ssh_set_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) +{ + return ssh_add_set_channel_callbacks(channel, cb, 1); +} + +int ssh_add_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) +{ + return ssh_add_set_channel_callbacks(channel, cb, 0); +} + +int ssh_remove_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) +{ + struct ssh_iterator *it = NULL; + + if (channel == NULL || channel->callbacks == NULL) { + return SSH_ERROR; + } + + it = ssh_list_find(channel->callbacks, cb); + if (it == NULL) { + return SSH_ERROR; + } + + ssh_list_remove(channel->callbacks, it); + + return SSH_OK; +} + +int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb) +{ + if (session == NULL || cb == NULL) { + return SSH_ERROR; + } + + if (!is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + session->server_callbacks = cb; + + return 0; +} diff --git a/src/libs/libssh-0.12.2/src/chachapoly.c b/src/libs/libssh-0.12.2/src/chachapoly.c new file mode 100644 index 000000000000..354a0d266487 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/chachapoly.c @@ -0,0 +1,205 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2015 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/libssh.h" +#include "libssh/crypto.h" +#include "libssh/chacha.h" +#include "libssh/poly1305.h" +#include "libssh/misc.h" +#include "libssh/chacha20-poly1305-common.h" + +struct chacha20_poly1305_keysched { + /* key used for encrypting the length field*/ + struct chacha_ctx k1; + /* key used for encrypting the packets */ + struct chacha_ctx k2; +}; + +static const uint8_t zero_block_counter[8] = {0, 0, 0, 0, 0, 0, 0, 0}; +static const uint8_t payload_block_counter[8] = {1, 0, 0, 0, 0, 0, 0, 0}; + +static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + struct chacha20_poly1305_keysched *sched = NULL; + uint8_t *u8key = key; + (void)IV; + + if (cipher->chacha20_schedule == NULL) { + sched = malloc(sizeof *sched); + if (sched == NULL){ + return -1; + } + } else { + sched = cipher->chacha20_schedule; + } + + chacha_keysetup(&sched->k2, u8key, CHACHA20_KEYLEN * 8); + chacha_keysetup(&sched->k1, u8key + CHACHA20_KEYLEN, CHACHA20_KEYLEN * 8); + cipher->chacha20_schedule = sched; + + return 0; +} + +/** + * @internal + * + * @brief encrypts an outgoing packet with chacha20 and authenticates it + * with poly1305. + */ +static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + uint8_t poly1305_ctx[POLY1305_KEYLEN] = {0}; + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + + seq = htonll(seq); + /* step 1, prepare the poly1305 key */ + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k2, + poly1305_ctx, + poly1305_ctx, + POLY1305_KEYLEN); + + /* step 2, encrypt length field */ + chacha_ivsetup(&keys->k1, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k1, + (uint8_t *)&in_packet->length, + (uint8_t *)&out_packet->length, + sizeof(uint32_t)); + + /* step 3, encrypt packet payload */ + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, payload_block_counter); + chacha_encrypt_bytes(&keys->k2, + in_packet->payload, + out_packet->payload, + len - sizeof(uint32_t)); + + /* ssh_log_hexdump("poly1305_ctx", poly1305_ctx, sizeof(poly1305_ctx)); */ + /* step 4, compute the MAC */ + poly1305_auth(tag, (uint8_t *)out_packet, len, poly1305_ctx); + /* ssh_log_hexdump("poly1305 src", (uint8_t *)out_packet, len); + ssh_log_hexdump("poly1305 tag", tag, POLY1305_TAGLEN); */ +} + +static int chacha20_poly1305_aead_decrypt_length( + struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + seq = htonll(seq); + + chacha_ivsetup(&keys->k1, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k1, + in, + (uint8_t *)out, + sizeof(uint32_t)); + return SSH_OK; +} + +static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + uint8_t poly1305_ctx[POLY1305_KEYLEN] = {0}; + uint8_t tag[POLY1305_TAGLEN] = {0}; + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + encrypted_size; + int cmp; + + seq = htonll(seq); + + ZERO_STRUCT(poly1305_ctx); + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k2, + poly1305_ctx, + poly1305_ctx, + POLY1305_KEYLEN); +#if 0 + ssh_log_hexdump("poly1305_ctx", poly1305_ctx, sizeof(poly1305_ctx)); +#endif + + poly1305_auth(tag, (uint8_t *)complete_packet, encrypted_size + + sizeof(uint32_t), poly1305_ctx); +#if 0 + ssh_log_hexdump("poly1305 src", + (uint8_t*)complete_packet, + encrypted_size + 4); + ssh_log_hexdump("poly1305 tag", tag, POLY1305_TAGLEN); + ssh_log_hexdump("received tag", mac, POLY1305_TAGLEN); +#endif + + cmp = secure_memcmp(tag, mac, POLY1305_TAGLEN); + if(cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET,"poly1305 verify error"); + return SSH_ERROR; + } + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, payload_block_counter); + chacha_encrypt_bytes(&keys->k2, + (uint8_t *)complete_packet + sizeof(uint32_t), + out, + encrypted_size); + + return SSH_OK; +} + +static void chacha20_cleanup(struct ssh_cipher_struct *cipher) { + SAFE_FREE(cipher->chacha20_schedule); +} + +const struct ssh_cipher_struct chacha20poly1305_cipher = { + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 512, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_set_encrypt_key, + .set_decrypt_key = chacha20_set_encrypt_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_cleanup +}; + +const struct ssh_cipher_struct *ssh_get_chacha20poly1305_cipher(void) +{ + return &chacha20poly1305_cipher; +} diff --git a/src/libs/libssh-0.12.2/src/channels.c b/src/libs/libssh-0.12.2/src/channels.c new file mode 100644 index 000000000000..ea64221ffe50 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/channels.c @@ -0,0 +1,4162 @@ +/* + * channels.c - SSH channel functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/messages.h" +#if WITH_SERVER +#include "libssh/server.h" +#endif + +/* + * All implementations MUST be able to process packets with an + * uncompressed payload length of 32768 bytes or less and a total packet + * size of 35000 bytes or less. + */ +#define CHANNEL_MAX_PACKET 32768 + +/* + * WINDOW_DEFAULT matches the default OpenSSH session window size. + * This controls how much data the peer can send before needing to receive + * a round-trip SSH2_MSG_CHANNEL_WINDOW_ADJUST message that increases the window. + */ +#define WINDOW_DEFAULT (64*CHANNEL_MAX_PACKET) + +/** + * @defgroup libssh_channel The SSH channel functions + * @ingroup libssh + * + * Functions that manage a SSH channel. + * + * @{ + */ + +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet); + +/** + * @brief Allocate a new channel. + * + * @param[in] session The ssh session to use. + * + * @return A pointer to a newly allocated channel, NULL on error. + * The channel needs to be freed with ssh_channel_free(). + * + * @see ssh_channel_free() + */ +ssh_channel ssh_channel_new(ssh_session session) +{ + ssh_channel channel = NULL; + + if (session == NULL) { + return NULL; + } + + /* Check if we have an authenticated session */ + if (!(session->flags & SSH_SESSION_FLAG_AUTHENTICATED)) { + return NULL; + } + + channel = calloc(1, sizeof(struct ssh_channel_struct)); + if (channel == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(channel); + return NULL; + } + + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + SAFE_FREE(channel); + return NULL; + } + + channel->session = session; + channel->exit.code = (uint32_t)-1; + channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND; + + if (session->channels == NULL) { + session->channels = ssh_list_new(); + if (session->channels == NULL) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + SSH_BUFFER_FREE(channel->stderr_buffer); + SAFE_FREE(channel); + return NULL; + } + } + + ssh_list_prepend(session->channels, channel); + + /* Set states explicitly */ + channel->state = SSH_CHANNEL_STATE_NOT_OPEN; + channel->request_state = SSH_CHANNEL_REQ_STATE_NONE; + + return channel; +} + +/** + * @internal + * + * @brief Create a new channel identifier. + * + * @param[in] session The SSH session to use. + * + * @return The new channel identifier. + */ +uint32_t ssh_channel_new_id(ssh_session session) +{ + return ++(session->maxchannel); +} + +/** + * @internal + * + * @brief Handle a SSH_PACKET_CHANNEL_OPEN_CONFIRMATION packet. + * + * Constructs the channel object. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf) +{ + uint32_t channelid = 0; + ssh_channel channel = NULL; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); + + rc = ssh_buffer_unpack(packet, "d", &channelid); + if (rc != SSH_OK) + goto error; + channel = ssh_channel_from_local(session, channelid); + if (channel == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Unknown channel id %" PRIu32, + (uint32_t)channelid); + /* TODO: Set error marking in channel object */ + + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, + "ddd", + &channel->remote_channel, + &channel->remote_window, + &channel->remote_maxpacket); + if (rc != SSH_OK) + goto error; + + if (channel->remote_maxpacket == 0) { + SSH_LOG(SSH_LOG_RARE, + "Invalid maximum packet size 0 in " + "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %" PRIu32 + ":%" PRIu32, + channel->local_channel, + channel->remote_channel); + + if (channel->state != SSH_CHANNEL_STATE_OPENING) { + SSH_LOG(SSH_LOG_RARE, + "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION received in incorrect " + "channel state %d", + channel->state); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Remote window : %" PRIu32 ", maxpacket : %" PRIu32, + channel->remote_window, + channel->remote_maxpacket); + + channel->state = SSH_CHANNEL_STATE_OPEN; + channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + true /* is_success */); + + return SSH_PACKET_USED; + +error: + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_OPEN_FAILURE and set the state of the channel. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail) +{ + ssh_channel channel = NULL; + char *error = NULL; + uint32_t code; + int rc; + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_RARE, "Invalid channel in packet"); + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, "ds", &code, &error); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; + } + + if (channel->state != SSH_CHANNEL_STATE_OPENING) { + SSH_LOG(SSH_LOG_RARE, + "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel " + "state %d", + channel->state); + SAFE_FREE(error); + goto error; + } + + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Channel opening failure: channel %" PRIu32 " error (%" PRIu32 + ") %s", + channel->local_channel, + code, + error); + SAFE_FREE(error); + channel->state = SSH_CHANNEL_STATE_OPEN_DENIED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + false /* is_success */); + + return SSH_PACKET_USED; + +error: + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; +} + +static int ssh_channel_open_termination(void *c) +{ + ssh_channel channel = (ssh_channel) c; + if (channel->state != SSH_CHANNEL_STATE_OPENING || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @internal + * + * @brief Open a channel by sending a SSH_OPEN_CHANNEL message and + * wait for the reply. + * + * @param[in] channel The current channel. + * + * @param[in] type A C string describing the kind of channel (e.g. "exec"). + * + * @param[in] window The receiving window of the channel. The window is the + * maximum size of data that can stay in buffers and + * network. + * + * @param[in] maxpacket The maximum packet size allowed (like MTU). + * + * @param[in] payload The buffer containing additional payload for the query. + * + * @return `SSH_OK` if successful; `SSH_ERROR` otherwise. + */ +static int +channel_open(ssh_channel channel, + const char *type, + uint32_t window, + uint32_t maxpacket, + ssh_buffer payload) +{ + ssh_session session = channel->session; + int err = SSH_ERROR; + int rc; + + switch (channel->state) { + case SSH_CHANNEL_STATE_NOT_OPEN: + break; + case SSH_CHANNEL_STATE_OPENING: + goto pending; + case SSH_CHANNEL_STATE_OPEN: + case SSH_CHANNEL_STATE_CLOSED: + case SSH_CHANNEL_STATE_OPEN_DENIED: + goto end; + default: + ssh_set_error(session, SSH_FATAL, "Bad state in channel_open: %d", + channel->state); + } + + channel->local_channel = ssh_channel_new_id(session); + channel->local_maxpacket = maxpacket; + channel->local_window = window; + + SSH_LOG(SSH_LOG_DEBUG, + "Creating a channel %" PRIu32 " with %" PRIu32 " window and %" PRIu32 " max packet", + channel->local_channel, window, maxpacket); + + rc = ssh_buffer_pack(session->out_buffer, + "bsddd", + SSH2_MSG_CHANNEL_OPEN, + type, + channel->local_channel, + channel->local_window, + channel->local_maxpacket); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return err; + } + + if (payload != NULL) { + if (ssh_buffer_add_buffer(session->out_buffer, payload) < 0) { + ssh_set_error_oom(session); + + return err; + } + } + channel->state = SSH_CHANNEL_STATE_OPENING; + if (ssh_packet_send(session) == SSH_ERROR) { + return err; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %" PRIu32, + type, channel->local_channel); + +pending: + /* wait until channel is opened by server */ + err = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_open_termination, + channel); + + if (session->session_state == SSH_SESSION_STATE_ERROR) { + err = SSH_ERROR; + } + +end: + /* This needs to pass the SSH_AGAIN from the above, + * but needs to catch failed channel states */ + if (channel->state == SSH_CHANNEL_STATE_OPEN) { + err = SSH_OK; + } else if (err != SSH_AGAIN) { + /* Messages were handled correctly, but the channel state is invalid */ + err = SSH_ERROR; + } + + return err; +} + +/* return channel with corresponding local id, or NULL if not found */ +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) +{ + struct ssh_iterator *it = NULL; + ssh_channel channel = NULL; + + for (it = ssh_list_get_iterator(session->channels); it != NULL; + it = it->next) { + channel = ssh_iterator_value(ssh_channel, it); + if (channel == NULL) { + continue; + } + if (channel->local_channel == id) { + return channel; + } + } + + return NULL; +} + +/** + * @internal + * @brief grows the local window and sends a packet to the other party + * @param session SSH session + * @param channel SSH channel + * @return `SSH_OK` if successful; `SSH_ERROR` otherwise. + */ +static int grow_window(ssh_session session, + ssh_channel channel) +{ + uint32_t used; + uint32_t increment; + int rc; + + /* Calculate the increment taking into account what the peer may still send + * (local_window) and what we've already buffered (stdout_buffer and + * stderr_buffer). + */ + used = channel->local_window; + if (channel->stdout_buffer != NULL) { + used += ssh_buffer_get_len(channel->stdout_buffer); + } + if (channel->stderr_buffer != NULL) { + used += ssh_buffer_get_len(channel->stderr_buffer); + } + /* Avoid a negative increment in case the peer sent more than the window allowed */ + increment = WINDOW_DEFAULT > used ? WINDOW_DEFAULT - used : 0; + /* Don't grow until we can request at least half a window */ + if (increment < (WINDOW_DEFAULT / 2)) { + SSH_LOG(SSH_LOG_DEBUG, + "growing window (channel %" PRIu32 ":%" PRIu32 ") to %" PRIu32 " bytes : not needed (%" PRIu32 " bytes)", + channel->local_channel, channel->remote_channel, WINDOW_DEFAULT, + channel->local_window); + + return SSH_OK; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdd", + SSH2_MSG_CHANNEL_WINDOW_ADJUST, + channel->remote_channel, + increment); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + if (ssh_packet_send(session) == SSH_ERROR) { + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "growing window (channel %" PRIu32 ":%" PRIu32 ") by %" PRIu32 " bytes", + channel->local_channel, + channel->remote_channel, + increment); + + channel->local_window += increment; + + return SSH_OK; +error: + ssh_buffer_reinit(session->out_buffer); + + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Parse a channel-related packet to resolve it to a ssh_channel. + * + * @param[in] session The current SSH session. + * + * @param[in] packet The buffer to parse packet from. The read pointer will + * be moved after the call. + * + * @return The related ssh_channel, or NULL if the channel is + * unknown or the packet is invalid. + */ +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) +{ + ssh_channel channel = NULL; + uint32_t chan; + int rc; + + rc = ssh_buffer_unpack(packet, "d", &chan); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Getting channel from message: short read"); + return NULL; + } + + channel = ssh_channel_from_local(session, chan); + if (channel == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Server specified invalid channel %" PRIu32, + (uint32_t)chan); + } + + return channel; +} + +SSH_PACKET_CALLBACK(channel_rcv_change_window) +{ + ssh_channel channel = NULL; + uint32_t bytes; + int rc; + bool was_empty; + + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + } + + rc = ssh_buffer_unpack(packet, "d", &bytes); + if (channel == NULL || rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, + "Error getting a window adjust message: invalid packet"); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Adding %" PRIu32 " bytes to channel (%" PRIu32 ":%" PRIu32 + ") (from %" PRIu32 " bytes)", + bytes, + channel->local_channel, + channel->remote_channel, + channel->remote_window); + + was_empty = channel->remote_window == 0; + + if (UINT32_MAX - channel->remote_window < bytes) { + ssh_set_error(session, + SSH_FATAL, + "Window adjust %" PRIu32 " overflows remote window.", + bytes); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; + } + + channel->remote_window += bytes; + + /* Writing to the channel is non-blocking until the receive window is empty. + * When the receive window becomes non-zero again, call + * channel_write_wontblock_function. */ + if (was_empty && bytes > 0) { + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_write_wontblock_function, + session, + channel, + channel->remote_window); + } + + return SSH_PACKET_USED; +} + +/* is_stderr is set to 1 if the data are extended, ie stderr */ +SSH_PACKET_CALLBACK(channel_rcv_data) +{ + ssh_channel channel = NULL; + ssh_string str = NULL; + ssh_buffer buf = NULL; + void *data = NULL; + uint32_t len; + int extended, is_stderr = 0; + int rest; + + (void)user; + + if (type == SSH2_MSG_CHANNEL_DATA) { + extended = 0; + } else { /* SSH_MSG_CHANNEL_EXTENDED_DATA */ + extended = 1; + } + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + if (extended) { + uint32_t data_type_code, rc; + rc = ssh_buffer_get_u32(packet, &data_type_code); + if (rc != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_PACKET, + "Failed to read data type code: rc = %" PRIu32, rc); + + return SSH_PACKET_USED; + } + is_stderr = 1; + data_type_code = ntohl(data_type_code); + if (data_type_code != SSH2_EXTENDED_DATA_STDERR) { + SSH_LOG(SSH_LOG_PACKET, "Invalid data type code %" PRIu32 "!", + data_type_code); + } + } + + str = ssh_buffer_get_ssh_string(packet); + if (str == NULL) { + SSH_LOG(SSH_LOG_PACKET, "Invalid data packet!"); + + return SSH_PACKET_USED; + } + /* STRING_SIZE_MAX < UINT32_MAX */ + len = (uint32_t)ssh_string_len(str); + + SSH_LOG(SSH_LOG_PACKET, + "Channel receiving %" PRIu32 " bytes data%s (local win=%" PRIu32 + " remote win=%" PRIu32 ") on channel %" PRIu32 ":%" PRIu32, + len, + is_stderr ? " in stderr" : "", + channel->local_window, + channel->remote_window, + channel->local_channel, + channel->remote_channel); + + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) { + SSH_LOG(SSH_LOG_WARNING, "Received data on (remotely) closed channel"); + ssh_set_error(session, SSH_FATAL, "Received data on (remotely) closed channel"); + SSH_STRING_FREE(str); + return SSH_PACKET_USED; + } + + if (len > channel->local_window) { + SSH_LOG(SSH_LOG_RARE, + "Data packet too big for our window(%" PRIu32 " vs %" PRIu32 ")", + len, + channel->local_window); + + SSH_STRING_FREE(str); + + ssh_set_error(session, SSH_FATAL, "Window exceeded"); + + return SSH_PACKET_USED; + } + + data = ssh_string_data(str); + if (channel_default_bufferize(channel, data, len, is_stderr) < 0) { + SSH_STRING_FREE(str); + + return SSH_PACKET_USED; + } + + channel->local_window -= len; + + SSH_LOG(SSH_LOG_PACKET, + "Channel windows are now (local win=%" PRIu32 " remote win=%" PRIu32 ")", + channel->local_window, + channel->remote_window); + + SSH_STRING_FREE(str); + + if (is_stderr) { + buf = channel->stderr_buffer; + } else { + buf = channel->stdout_buffer; + } + + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_data_function) { + if (ssh_buffer_get(buf) == NULL) { + break; + } + rest = ssh_callbacks_iterate_exec(channel_data_function, + channel->session, + channel, + ssh_buffer_get(buf), + ssh_buffer_get_len(buf), + is_stderr); + if (rest > 0) { + int rc; + if (channel->counter != NULL) { + channel->counter->in_bytes += rest; + } + ssh_buffer_pass_bytes(buf, rest); + + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; + } + } + } + ssh_callbacks_iterate_end(); + + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_eof) +{ + ssh_channel channel = NULL; + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received eof on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + /* channel->remote_window = 0; */ + channel->remote_eof = 1; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_eof_function, + channel->session, + channel); + + return SSH_PACKET_USED; +} + +static bool ssh_channel_has_unread_data(ssh_channel channel) +{ + if (channel == NULL) { + return false; + } + + if ((channel->stdout_buffer && + ssh_buffer_get_len(channel->stdout_buffer) > 0) || + (channel->stderr_buffer && + ssh_buffer_get_len(channel->stderr_buffer) > 0)) + { + return true; + } + + return false; +} + +SSH_PACKET_CALLBACK(channel_rcv_close) +{ + ssh_channel channel = NULL; + (void)user; + (void)type; + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received close on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + + if (!ssh_channel_has_unread_data(channel)) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } else { + channel->delayed_close = 1; + } + + if (channel->remote_eof == 0) { + SSH_LOG(SSH_LOG_PACKET, + "Remote host not polite enough to send an eof before close"); + } + /* + * The remote eof doesn't break things if there was still data into read + * buffer because the eof is ignored until the buffer is empty. + */ + channel->remote_eof = 1; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_close_function, + channel->session, + channel); + + channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE; + if(channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL) + ssh_channel_do_free(channel); + + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_request) +{ + ssh_channel channel = NULL; + char *request = NULL; + uint8_t want_reply; + int rc; + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, "sb", &request, &want_reply); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-status") == 0) { + SAFE_FREE(request); + rc = ssh_buffer_unpack(packet, "d", &channel->exit.code); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid exit-status packet"); + return SSH_PACKET_USED; + } + channel->exit.status = true; + + SSH_LOG(SSH_LOG_PACKET, + "received exit-status %u on channel %" PRIu32 ":%" PRIu32, + channel->exit.code, + channel->local_channel, + channel->remote_channel); + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_exit_status_function, + channel->session, + channel, + channel->exit.code); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "signal") == 0) { + char *sig = NULL; + + SAFE_FREE(request); + SSH_LOG(SSH_LOG_PACKET, "received signal"); + + rc = ssh_buffer_unpack(packet, "s", &sig); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, "Remote connection sent a signal SIG %s", sig); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_signal_function, + channel->session, + channel, + sig); + SAFE_FREE(sig); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-signal") == 0) { + const char *core = "(core dumped)"; + char *sig = NULL; + char *errmsg = NULL; + char *lang = NULL; + uint8_t core_dumped; + + SAFE_FREE(request); + + rc = ssh_buffer_unpack(packet, + "sbss", + &sig, /* signal name */ + &core_dumped, /* core dumped */ + &errmsg, /* error message */ + &lang); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (core_dumped == 0) { + core = ""; + } + + SSH_LOG(SSH_LOG_PACKET, + "Remote connection closed by signal SIG %s %s", + sig, + core); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_exit_signal_function, + channel->session, + channel, + sig, + core_dumped, + errmsg, + lang); + + channel->exit.core_dumped = core_dumped; + if (sig != NULL) { + SAFE_FREE(channel->exit.signal); + channel->exit.signal = sig; + } + channel->exit.status = true; + + SAFE_FREE(lang); + SAFE_FREE(errmsg); + + return SSH_PACKET_USED; + } + if (strcmp(request, "keepalive@openssh.com") == 0) { + SAFE_FREE(request); + SSH_LOG(SSH_LOG_DEBUG, "Responding to Openssh's keepalive"); + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_FAILURE, + channel->remote_channel); + if (rc != SSH_OK) { + return SSH_PACKET_USED; + } + ssh_packet_send(session); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "auth-agent-req@openssh.com") == 0) { + int status; + + SAFE_FREE(request); + SSH_LOG(SSH_LOG_DEBUG, "Received an auth-agent-req request"); + + status = SSH2_MSG_CHANNEL_FAILURE; + ssh_callbacks_iterate (channel->callbacks, + ssh_channel_callbacks, + channel_auth_agent_req_function) { + ssh_callbacks_iterate_exec(channel_auth_agent_req_function, + channel->session, + channel); + /* in lieu of a return value, if the callback exists it's supported + */ + status = SSH2_MSG_CHANNEL_SUCCESS; + break; + } + ssh_callbacks_iterate_end(); + + if (want_reply) { + rc = ssh_buffer_pack(session->out_buffer, + "bd", + status, + channel->remote_channel); + if (rc != SSH_OK) { + return SSH_PACKET_USED; + } + ssh_packet_send(session); + } + + return SSH_PACKET_USED; + } +#ifdef WITH_SERVER + /* If we are here, that means we have a request that is not in the + * understood client requests. That means we need to create a ssh message to + * be passed to the user code handling ssh messages + */ + ssh_message_handle_channel_request(session, + channel, + packet, + request, + want_reply); +#else + SSH_LOG(SSH_LOG_DEBUG, "Unhandled channel request %s", request); +#endif + + SAFE_FREE(request); + + return SSH_PACKET_USED; +} + +/* + * When data has been received from the ssh server, it can be applied to the + * known user function, with help of the callback, or inserted here + * + * FIXME is the window changed? + */ +int channel_default_bufferize(ssh_channel channel, + void *data, uint32_t len, + bool is_stderr) +{ + ssh_session session = NULL; + + if (channel == NULL) { + return -1; + } + + session = channel->session; + + if (data == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, + "placing %" PRIu32 " bytes into channel buffer (%s)", + len, + is_stderr ? "stderr" : "stdout"); + if (!is_stderr) { + /* stdout */ + if (channel->stdout_buffer == NULL) { + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (ssh_buffer_add_data(channel->stdout_buffer, data, len) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + channel->stdout_buffer = NULL; + return -1; + } + } else { + /* stderr */ + if (channel->stderr_buffer == NULL) { + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (ssh_buffer_add_data(channel->stderr_buffer, data, len) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stderr_buffer); + channel->stderr_buffer = NULL; + return -1; + } + } + + return 0; +} + +/** + * @brief Open a session channel (suited for a shell, not TCP forwarding). + * + * @param[in] channel An allocated channel. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @see ssh_channel_open_forward() + * @see ssh_channel_request_env() + * @see ssh_channel_request_shell() + * @see ssh_channel_request_exec() + */ +int ssh_channel_open_session(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + + return channel_open(channel, + "session", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + NULL); +} + +/** + * @brief Open an agent authentication forwarding channel. This type of channel + * can be opened by a server towards a client in order to provide SSH-Agent + * services to the server-side process. This channel can only be opened if the + * client claimed support by sending a channel request beforehand. + * + * @param[in] channel An allocated channel. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @see ssh_channel_open_forward() + */ +int ssh_channel_open_auth_agent(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + + return channel_open(channel, + "auth-agent@openssh.com", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + NULL); +} + +/** + * @brief Open a TCP/IP forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotehost The remote host to connected (host name or IP). + * + * @param[in] remoteport The remote port. + * + * @param[in] sourcehost The numeric IP address of the machine from where the + * connection request originates. This is mostly for + * logging purposes. + * + * @param[in] localport The port on the host from where the connection + * originated. This is mostly for logging purposes. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the local port and does not + * automatically forward the content of a socket to the channel. You still have + * to use ssh_channel_read and ssh_channel_write for this. + */ +int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return rc; + } + + session = channel->session; + + if (remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(session); + return rc; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, + "sdsd", + remotehost, + remoteport, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_open(channel, + "direct-tcpip", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + SSH_STRING_FREE(str); + + return rc; +} + +/** + * @brief Open a TCP/IP - UNIX domain socket forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotepath The UNIX socket path on the remote machine + * + * @param[in] sourcehost The numeric IP address of the machine from where the + * connection request originates. This is mostly for + * logging purposes. + * + * @param[in] localport The port on the host from where the connection + * originated. This is mostly for logging purposes. + * + * @return `SSH_OK on` success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the local port and does not + * automatically forward the content of a socket to the channel. + * You still have to use ssh_channel_read and ssh_channel_write for this. + * @warning Requires support of OpenSSH for UNIX domain socket forwarding. + */ +int ssh_channel_open_forward_unix(ssh_channel channel, + const char *remotepath, + const char *sourcehost, + int localport) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + int rc = SSH_ERROR; + int version; + + if (channel == NULL) { + return rc; + } + + session = channel->session; + + version = ssh_get_openssh_version(session); + if (version == 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "We're not connected to an OpenSSH server!"); + return SSH_ERROR; + } + + if (remotepath == NULL || sourcehost == NULL) { + ssh_set_error_invalid(session); + return rc; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, + "ssd", + remotepath, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_open(channel, + "direct-streamlocal@openssh.com", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + + return rc; +} + +/** + * @brief Open a TCP/IP - VPN tunnel channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remoteunit The remote interface number, or may be 0x7fffffff + * to allow the server to automatically choose + * an interface. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the channel to a local interface + * and does not automatically forward packets from a local interface + * to the channel. + * You still have to use channel_read and channel_write for this. + * @warning Requires support of OpenSSH for VPN tunneling. + */ +int ssh_channel_open_tunnel(ssh_channel channel, + int remoteunit) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + int version; + + if (channel == NULL) { + return rc; + } + + session = channel->session; + + version = ssh_get_openssh_version(session); + if (version == 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "We're not connected to an OpenSSH server!"); + return SSH_ERROR; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, + "dd", + 2, // SSH_TUNMODE_ETHERNET + remoteunit); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_open(channel, + "tun@openssh.com", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + SSH_STRING_FREE(str); + + return rc; +} + +/** + * @brief Close and free a channel. + * + * @param[in] channel The channel to free. + * + * @warning Any data unread on this channel will be lost. + */ +void ssh_channel_free(ssh_channel channel) +{ + ssh_session session = NULL; + + if (channel == NULL) { + return; + } + + session = channel->session; + if (session->alive) { + bool send_close = false; + + switch (channel->state) { + case SSH_CHANNEL_STATE_OPEN: + send_close = true; + break; + case SSH_CHANNEL_STATE_CLOSED: + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) { + send_close = true; + } + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) { + send_close = false; + } + break; + default: + send_close = false; + break; + } + + if (send_close) { + ssh_channel_close(channel); + } + } + channel->flags |= SSH_CHANNEL_FLAG_FREED_LOCAL; + + if (channel->callbacks != NULL) { + ssh_list_free(channel->callbacks); + channel->callbacks = NULL; + } + + /* The idea behind the flags is the following : it is well possible + * that a client closes a channel that still exists on the server side. + * We definitively close the channel when we receive a close message *and* + * the user closed it. + */ + if ((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) { + ssh_channel_do_free(channel); + } +} + +/** + * @internal + * @brief Effectively free a channel, without caring about flags + */ + +void ssh_channel_do_free(ssh_channel channel) +{ + struct ssh_iterator *it = NULL; + ssh_session session = channel->session; + + it = ssh_list_find(session->channels, channel); + if (it != NULL) { + ssh_list_remove(session->channels, it); + } + + SSH_BUFFER_FREE(channel->stdout_buffer); + SSH_BUFFER_FREE(channel->stderr_buffer); + + if (channel->callbacks != NULL) { + ssh_list_free(channel->callbacks); + channel->callbacks = NULL; + } + SAFE_FREE(channel->exit.signal); + + channel->session = NULL; + SAFE_FREE(channel); +} + +/** + * @brief Send an end of file on the channel. + * + * This doesn't close the channel. You may still read from it but not write. + * + * @param[in] channel The channel to send the eof to. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + * + * Example: +@code + rc = ssh_channel_send_eof(channel); + if (rc == SSH_ERROR) { + return -1; + } + while(!ssh_channel_is_eof(channel)) { + rc = ssh_channel_read(channel, buf, sizeof(buf), 0); + if (rc == SSH_ERROR) { + return -1; + } + } + ssh_channel_close(channel); +@endcode + * + * @see ssh_channel_close() + * @see ssh_channel_free() + * @see ssh_channel_is_eof() + */ +int ssh_channel_send_eof(ssh_channel channel) +{ + ssh_session session = NULL; + int rc = SSH_ERROR; + int err; + + if (channel == NULL || channel->session == NULL) { + return rc; + } + + /* If the EOF has already been sent we're done here. */ + if (channel->local_eof != 0) { + return SSH_OK; + } + + session = channel->session; + + err = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_EOF, + channel->remote_channel); + if (err != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_packet_send(session); + SSH_LOG(SSH_LOG_PACKET, + "Sent a EOF on client channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_channel_flush(channel); + if (rc == SSH_ERROR) { + goto error; + } + channel->local_eof = 1; + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/** + * @brief Close a channel. + * + * This sends an end of file and then closes the channel. You won't be able + * to recover any data the server was going to send or was in buffers. + * + * @param[in] channel The channel to close. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + * + * @see ssh_channel_free() + * @see ssh_channel_is_eof() + */ +int ssh_channel_close(ssh_channel channel) +{ + ssh_session session = NULL; + int rc = 0; + + if(channel == NULL) { + return SSH_ERROR; + } + + /* If the channel close has already been sent we're done here. */ + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) { + return SSH_OK; + } + + session = channel->session; + + rc = ssh_channel_send_eof(channel); + if (rc != SSH_OK) { + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_CLOSE, + channel->remote_channel); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_packet_send(session); + SSH_LOG(SSH_LOG_PACKET, + "Sent a close on client channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + + if (rc == SSH_OK) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + channel->flags |= SSH_CHANNEL_FLAG_CLOSED_LOCAL; + } + + rc = ssh_channel_flush(channel); + if(rc == SSH_ERROR) { + goto error; + } + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/* this termination function waits for a window growing condition */ +static int ssh_channel_waitwindow_termination(void *c) +{ + ssh_channel channel = (ssh_channel) c; + if (channel->remote_window > 0 || + channel->session->session_state == SSH_SESSION_STATE_ERROR || + channel->state == SSH_CHANNEL_STATE_CLOSED) + return 1; + else + return 0; +} + +/* This termination function waits until the session is not in blocked status + * anymore, e.g. because of a key re-exchange. + */ +static int ssh_waitsession_unblocked(void *s) +{ + ssh_session session = (ssh_session)s; + switch (session->session_state){ + case SSH_SESSION_STATE_DH: + case SSH_SESSION_STATE_INITIAL_KEX: + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + return 0; + default: + return 1; + } +} +/** + * @internal + * @brief Flushes a channel (and its session) until the output buffer + * is empty, or timeout elapsed. + * @param channel SSH channel + * @return `SSH_OK` On success, + * `SSH_ERROR` On error. + * `SSH_AGAIN` Timeout elapsed (or in nonblocking mode). + */ +int ssh_channel_flush(ssh_channel channel) +{ + return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT); +} + +static int channel_write_common(ssh_channel channel, + const void *data, + uint32_t len, int is_stderr) +{ + ssh_session session = NULL; + uint32_t origlen = len; + uint32_t effectivelen; + int rc; + + if (channel == NULL) { + return -1; + } + session = channel->session; + if (data == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + if (len > INT_MAX) { + SSH_LOG(SSH_LOG_TRACE, + "Length (%" PRIu32 ") is bigger than INT_MAX", + len); + return SSH_ERROR; + } + + if (channel->local_eof) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't write to channel %" PRIu32 ":%" PRIu32 + " after EOF was sent", + channel->local_channel, + channel->remote_channel); + return -1; + } + + if (channel->state != SSH_CHANNEL_STATE_OPEN || + channel->delayed_close != 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed"); + + return -1; + } + + if (session->session_state == SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + + if (ssh_waitsession_unblocked(session) == 0) { + rc = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_waitsession_unblocked, + session); + if (rc == SSH_ERROR || !ssh_waitsession_unblocked(session)) + goto out; + } + while (len > 0) { + if (channel->remote_window < len) { + SSH_LOG(SSH_LOG_DEBUG, + "Remote window is %" PRIu32 + " bytes. going to write %" PRIu32 " bytes", + channel->remote_window, + len); + /* When the window is zero, wait for it to grow */ + if (channel->remote_window == 0) { + /* nothing can be written */ + SSH_LOG(SSH_LOG_DEBUG, "Wait for a growing window message..."); + rc = ssh_handle_packets_termination( + session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_waitwindow_termination, + channel); + if (rc == SSH_ERROR || + !ssh_channel_waitwindow_termination(channel) || + session->session_state == SSH_SESSION_STATE_ERROR || + channel->state == SSH_CHANNEL_STATE_CLOSED) + goto out; + continue; + } + /* When the window is non-zero, accept data up to the window size */ + effectivelen = MIN(len, channel->remote_window); + } else { + effectivelen = len; + } + + /* + * Like OpenSSH, don't subtract bytes for the header fields + * and allow to send a payload of remote_maxpacket length. + */ + effectivelen = MIN(effectivelen, channel->remote_maxpacket); + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + is_stderr ? SSH2_MSG_CHANNEL_EXTENDED_DATA + : SSH2_MSG_CHANNEL_DATA, + channel->remote_channel); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + /* stderr message has an extra field */ + if (is_stderr) { + rc = ssh_buffer_pack(session->out_buffer, + "d", + SSH2_EXTENDED_DATA_STDERR); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + } + + /* append payload data */ + rc = ssh_buffer_pack(session->out_buffer, + "dP", + effectivelen, + (size_t)effectivelen, + data); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "ssh_channel_write wrote %" PRIu32 " bytes", + effectivelen); + + channel->remote_window -= effectivelen; + len -= effectivelen; + data = ((uint8_t *)data + effectivelen); + if (channel->counter != NULL) { + channel->counter->out_bytes += effectivelen; + } + } + + /* it's a good idea to flush the socket now */ + rc = ssh_channel_flush(channel); + if (rc == SSH_ERROR) { + goto error; + } + +out: + return (int)(origlen - len); + +error: + ssh_buffer_reinit(session->out_buffer); + + return SSH_ERROR; +} + +/** + * @brief Get the remote window size. + * + * This is the maximum amount of bytes the remote side expects us to send + * before growing the window again. + * + * @param[in] channel The channel to query. + * + * @return The remote window size + * + * @warning A nonzero return value does not guarantee the socket is ready + * to send that much data. Buffering may happen in the local SSH + * packet buffer, so beware of really big window sizes. + * + * @warning A zero return value means ssh_channel_write (default settings) + * will block until the window grows back. + */ +uint32_t ssh_channel_window_size(ssh_channel channel) +{ + return channel->remote_window; +} + +/** + * @brief Blocking write on a channel. + * + * @param[in] channel The channel to write to. + * + * @param[in] data A pointer to the data to write. + * + * @param[in] len The length of the buffer to write to. + * + * @return The number of bytes written, `SSH_ERROR` on error. + * + * @see ssh_channel_read() + */ +int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 0); +} + +/** + * @brief Check if the channel is open or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is closed, nonzero otherwise. + * + * @see ssh_channel_is_closed() + */ +int ssh_channel_is_open(ssh_channel channel) +{ + if (channel == NULL || channel->session == NULL) { + return 0; + } + return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0); +} + +/** + * @brief Check if the channel is closed or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is opened, nonzero otherwise. + * + * @see ssh_channel_is_open() + */ +int ssh_channel_is_closed(ssh_channel channel) +{ + if (channel == NULL || channel->session == NULL) { + return SSH_ERROR; + } + return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0); +} + +/** + * @brief Check if remote has sent an EOF. + * + * @param[in] channel The channel to check. + * + * @return 0 if there is no EOF, nonzero otherwise. + */ +int ssh_channel_is_eof(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + if (ssh_channel_has_unread_data(channel)) { + return 0; + } + + return (channel->remote_eof != 0); +} + +/** + * @brief Put the channel into blocking or nonblocking mode. + * + * @param[in] channel The channel to use. + * + * @param[in] blocking A boolean for blocking or nonblocking. + * + * @warning A side-effect of this is to put the whole session + * in non-blocking mode. + * @see ssh_set_blocking() + */ +void ssh_channel_set_blocking(ssh_channel channel, int blocking) +{ + if (channel == NULL) { + return; + } + ssh_set_blocking(channel->session, blocking); +} + +/** + * @internal + * + * @brief handle a SSH_CHANNEL_SUCCESS packet and set the channel state. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_success) +{ + ssh_channel channel = NULL; + (void)type; + (void)user; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_CHANNEL_SUCCESS on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + if (channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING) { + SSH_LOG(SSH_LOG_RARE, + "SSH_CHANNEL_SUCCESS received in incorrect state %d", + channel->request_state); + } else { + channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); + } + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_FAILURE packet and set the channel state. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_failure) +{ + ssh_channel channel = NULL; + (void)type; + (void)user; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_CHANNEL_FAILURE on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + if (channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING) { + SSH_LOG(SSH_LOG_RARE, + "SSH_CHANNEL_FAILURE received in incorrect state %d", + channel->request_state); + } else { + channel->request_state = SSH_CHANNEL_REQ_STATE_DENIED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); + } + + return SSH_PACKET_USED; +} + +static int ssh_channel_request_termination(void *c) +{ + ssh_channel channel = (ssh_channel)c; + if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +static int channel_request(ssh_channel channel, const char *request, + ssh_buffer buffer, int reply) +{ + ssh_session session = channel->session; + int rc = SSH_ERROR; + int ret; + + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + ret = ssh_buffer_pack(session->out_buffer, + "bdsb", + SSH2_MSG_CHANNEL_REQUEST, + channel->remote_channel, + request, + reply == 0 ? 0 : 1); + if (ret != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer != NULL) { + if (ssh_buffer_add_data(session->out_buffer, ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)) < 0) { + ssh_set_error_oom(session); + goto error; + } + } + channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING; + if (ssh_packet_send(session) == SSH_ERROR) { + return rc; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_REQUEST %s on channel %" PRIu32 ":%" PRIu32, + request, + channel->local_channel, + channel->remote_channel); + if (reply == 0) { + channel->request_state = SSH_CHANNEL_REQ_STATE_NONE; + return SSH_OK; + } +pending: + rc = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_request_termination, + channel); + + if(session->session_state == SSH_SESSION_STATE_ERROR || rc == SSH_ERROR) { + channel->request_state = SSH_CHANNEL_REQ_STATE_ERROR; + } + /* we received something */ + switch (channel->request_state){ + case SSH_CHANNEL_REQ_STATE_ERROR: + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Channel request %s failed on channel %" PRIu32 ":%" PRIu32, + request, + channel->local_channel, + channel->remote_channel); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + SSH_LOG(SSH_LOG_DEBUG, + "Channel request %s success on channel %" PRIu32 ":%" PRIu32, + request, + channel->local_channel, + channel->remote_channel); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_PENDING: + rc = SSH_AGAIN; + return rc; + case SSH_CHANNEL_REQ_STATE_NONE: + /* Never reached */ + ssh_set_error(session, SSH_FATAL, "Invalid state in channel_request()"); + rc=SSH_ERROR; + break; + } + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/** + * @brief Request a pty with a specific type and size. + * + * @param[in] channel The channel to send the request. + * + * @param[in] terminal The terminal type ("vt100, xterm,..."). + * + * @param[in] col The number of columns. + * + * @param[in] row The number of rows. + * + * @param[in] modes Encoded SSH terminal modes for the PTY + * + * @param[in] modes_len Number of bytes in 'modes' + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *terminal, + int col, int row, const unsigned char* modes, size_t modes_len) +{ + ssh_session session = NULL; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if (terminal == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + switch (channel->request_state) { + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "sdddddP", + terminal, + col, + row, + 0, /* pix */ + 0, /* pix */ + (uint32_t)modes_len, + (size_t)modes_len, + modes); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_request(channel, "pty-req", buffer, 1); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Request a PTY with a specific size using current TTY modes. + * + * Encodes @p terminal modes from the current TTY and sends a PTY request + * for the given channel, terminal type, and size in columns/rows. + * + * @param[in] channel The channel to send the request on. + * @param[in] terminal The terminal type (e.g. "xterm"). + * @param[in] col Number of columns. + * @param[in] row Number of rows. + * + * @return `SSH_OK` on success; `SSH_ERROR` on failure. + */ +int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, + int col, int row) +{ + /* use modes from the current TTY */ + unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + int rc = encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + if (rc < 0) { + return rc; + } + return ssh_channel_request_pty_size_modes(channel, + terminal, + col, + row, + modes_buf, + (size_t)rc); +} + +/** + * @brief Request a PTY. + * + * @param[in] channel The channel to send the request. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @see ssh_channel_request_pty_size() + */ +int ssh_channel_request_pty(ssh_channel channel) +{ + return ssh_channel_request_pty_size(channel, "xterm", 80, 24); +} + +/** + * @brief Change the size of the terminal associated to a channel. + * + * @param[in] channel The channel to change the size. + * + * @param[in] cols The new number of columns. + * + * @param[in] rows The new number of rows. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + * + * @warning Do not call it from a signal handler if you are not sure any other + * libssh function using the same channel/session is running at the + * same time (not 100% threadsafe). + */ +int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) +{ + ssh_session session = channel->session; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "dddd", + cols, + rows, + 0, /* pix */ + 0 /* pix */); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_request(channel, "window-change", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Request a shell. + * + * @param[in] channel The channel to send the request. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_request_shell(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + + return channel_request(channel, "shell", NULL, 1); +} + +/** + * @brief Request a subsystem (for example "sftp"). + * + * @param[in] channel The channel to send the request. + * + * @param[in] subsys The subsystem to request (for example "sftp"). + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @warning You normally don't have to call it for sftp, see sftp_new(). + */ +int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(subsys == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "s", subsys); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "subsystem", buffer, 1); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Request sftp subsystem on the channel + * + * @param[in] channel The channel to request the sftp subsystem. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @note You should use sftp_new() which does this for you. + */ +int ssh_channel_request_sftp( ssh_channel channel) +{ + if(channel == NULL) { + return SSH_ERROR; + } + return ssh_channel_request_subsystem(channel, "sftp"); +} + +static char *generate_cookie(void) +{ + static const char *hex = "0123456789abcdef"; + char s[36]; + unsigned char rnd[16]; + int ok; + int i; + + ok = ssh_get_random(rnd, sizeof(rnd), 0); + if (!ok) { + return NULL; + } + + for (i = 0; i < 16; i++) { + s[i*2] = hex[rnd[i] & 0x0f]; + s[i*2+1] = hex[rnd[i] >> 4]; + } + s[32] = '\0'; + return strdup(s); +} + +/** + * @brief Sends the "x11-req" channel request over an existing session channel. + * + * This will enable redirecting the display of the remote X11 applications to + * local X server over a secure tunnel. + * + * @param[in] channel An existing session channel where the remote X11 + * applications are going to be executed. + * + * @param[in] single_connection A boolean to mark only one X11 app will be + * redirected. + * + * @param[in] protocol A x11 authentication protocol. Pass NULL to use the + * default value MIT-MAGIC-COOKIE-1. + * + * @param[in] cookie A x11 authentication cookie. Pass NULL to generate + * a random cookie. + * + * @param[in] screen_number The screen number. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number) +{ + ssh_buffer buffer = NULL; + char *c = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (cookie == NULL) { + c = generate_cookie(); + if (c == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + } + + rc = ssh_buffer_pack(buffer, + "bssd", + single_connection == 0 ? 0 : 1, + protocol ? protocol : "MIT-MAGIC-COOKIE-1", + cookie ? cookie : c, + screen_number); + if (c != NULL){ + SAFE_FREE(c); + } + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "x11-req", buffer, 1); + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, + int timeout_ms, int *destination_port, char **originator, int *originator_port) +{ +#ifndef _WIN32 + static const struct timespec ts = { + .tv_sec = 0, + .tv_nsec = 50000000 /* 50ms */ + }; +#endif + ssh_message msg = NULL; + ssh_channel channel = NULL; + struct ssh_iterator *iterator = NULL; + int t; + + /* + * We sleep for 50 ms in ssh_handle_packets() and later sleep for + * 50 ms. So we need to decrement by 100 ms. + */ + for (t = timeout_ms; t >= 0; t -= 100) { + if (timeout_ms == 0) { + ssh_handle_packets(session, 0); + } else { + ssh_handle_packets(session, 50); + } + + if (session->ssh_message_list) { + iterator = ssh_list_get_iterator(session->ssh_message_list); + while (iterator) { + msg = (ssh_message)iterator->data; + if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(msg) == channeltype) { + ssh_list_remove(session->ssh_message_list, iterator); + channel = ssh_message_channel_request_open_reply_accept(msg); + if(destination_port) { + *destination_port=msg->channel_request_open.destination_port; + } + if(originator) { + *originator=strdup(msg->channel_request_open.originator); + } + if(originator_port) { + *originator_port=msg->channel_request_open.originator_port; + } + + ssh_message_free(msg); + return channel; + } + iterator = iterator->next; + } + } + if(t>0){ +#ifdef _WIN32 + Sleep(50); /* 50ms */ +#else + nanosleep(&ts, NULL); +#endif + } + } + + ssh_set_error(session, SSH_NO_ERROR, "No channel request of this type from server"); + return NULL; +} + +/** + * @brief Accept an X11 forwarding channel. + * + * @param[in] channel An x11-enabled session channel. + * + * @param[in] timeout_ms Timeout in milliseconds. + * + * @return A newly created channel, or NULL if no X11 request from + * the server. + */ +ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) +{ + return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL, NULL, NULL); +} + +/** + * @brief Send an "auth-agent-req" channel request over an existing session + * channel. + * + * This client-side request will enable forwarding the agent over an secure + * tunnel. When the server is ready to open one authentication agent channel, an + * ssh_channel_open_request_auth_agent_callback event will be generated. + * + * @param[in] channel The channel to send signal. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred + */ +int ssh_channel_request_auth_agent(ssh_channel channel) { + if (channel == NULL) { + return SSH_ERROR; + } + + return channel_request(channel, "auth-agent-req@openssh.com", NULL, 0); +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_SUCCESS packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_success){ + (void)type; + (void)user; + (void)packet; + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_REQUEST_SUCCESS"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + SSH_LOG(SSH_LOG_RARE, "SSH_REQUEST_SUCCESS received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + } + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_DENIED packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_denied){ + (void)type; + (void)user; + (void)packet; + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_REQUEST_FAILURE"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + SSH_LOG(SSH_LOG_RARE, "SSH_REQUEST_DENIED received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_DENIED; + } + + return SSH_PACKET_USED; + +} + +static int ssh_global_request_termination(void *s) +{ + ssh_session session = (ssh_session) s; + if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING || + session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @internal + * + * @brief Send a global request (needed for forward listening) and wait for the + * result. + * + * @param[in] session The SSH session handle. + * + * @param[in] request The type of request (defined in RFC). + * + * @param[in] buffer Additional data to put in packet. + * + * @param[in] reply Set if you expect a reply from server. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_global_request(ssh_session session, + const char *request, + ssh_buffer buffer, + int reply) +{ + int rc; + + switch (session->global_req_state) { + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bsb", + SSH2_MSG_GLOBAL_REQUEST, + request, + reply == 0 ? 0 : 1); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + + if (buffer != NULL) { + rc = ssh_buffer_add_data(session->out_buffer, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc < 0) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + } + + session->global_req_state = SSH_CHANNEL_REQ_STATE_PENDING; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return rc; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a SSH_MSG_GLOBAL_REQUEST %s", request); + + if (reply == 0) { + session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE; + + return SSH_OK; + } +pending: + rc = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_global_request_termination, + session); + + if(rc==SSH_ERROR || session->session_state == SSH_SESSION_STATE_ERROR){ + session->global_req_state = SSH_CHANNEL_REQ_STATE_ERROR; + } + switch(session->global_req_state){ + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + SSH_LOG(SSH_LOG_DEBUG, "Global request %s success",request); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + SSH_LOG(SSH_LOG_PACKET, + "Global request %s failed", request); + ssh_set_error(session, SSH_REQUEST_DENIED, + "Global request %s failed", request); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ERROR: + case SSH_CHANNEL_REQ_STATE_NONE: + rc = SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_PENDING: + return SSH_AGAIN; + } + session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE; + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/** + * @brief Sends the "tcpip-forward" global request to ask the server to begin + * listening for inbound connections. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The address to bind to on the server. Pass NULL to bind + * to all available addresses on all protocol families + * supported by the server. + * + * @param[in] port The port to bind to on the server. Pass 0 to ask the + * server to allocate the next available unprivileged port + * number + * + * @param[in] bound_port The pointer to get actual bound port. Pass NULL to + * ignore. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + **/ +int ssh_channel_listen_forward(ssh_session session, + const char *address, + int port, + int *bound_port) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE) + goto pending; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "sd", + address ? address : "", + port); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } +pending: + rc = ssh_global_request(session, "tcpip-forward", buffer, 1); + + /* TODO: FIXME no guarantee the last packet we received contains + * that info */ + if (rc == SSH_OK && port == 0 && bound_port != NULL) { + rc = ssh_buffer_unpack(session->in_buffer, "d", bound_port); + if (rc != SSH_OK) + *bound_port = 0; + } + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/* DEPRECATED */ +int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) +{ + return ssh_channel_listen_forward(session, address, port, bound_port); +} + +/* DEPRECATED */ +ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) +{ + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL, NULL, NULL); +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel and get some information + * about incoming connection + * + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @param[in] destination_port A pointer to destination port or NULL. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + */ +ssh_channel ssh_channel_accept_forward(ssh_session session, int timeout_ms, int* destination_port) { + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, NULL, NULL); +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel and get information + * about incoming connection + * + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @param[out] destination_port A pointer to destination port or NULL. + * + * @param[out] originator A pointer to a pointer to a string of originator host or NULL. + * That the caller is responsible for to ssh_string_free_char(). + * + * @param[out] originator_port A pointer to originator port or NULL. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + * + * @see ssh_string_free_char() + */ +ssh_channel ssh_channel_open_forward_port(ssh_session session, int timeout_ms, int *destination_port, char **originator, int *originator_port) { + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, originator, originator_port); +} + +/** + * @brief Sends the "cancel-tcpip-forward" global request to ask the server to + * cancel the tcpip-forward request. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The bound address on the server. + * + * @param[in] port The bound port on the server. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_cancel_forward(ssh_session session, + const char *address, + int port) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE) + goto pending; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "sd", + address ? address : "", + port); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } +pending: + rc = ssh_global_request(session, "cancel-tcpip-forward", buffer, 1); + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/* DEPRECATED */ +int ssh_forward_cancel(ssh_session session, const char *address, int port) +{ + return ssh_channel_cancel_forward(session, address, port); +} + +/** + * @brief Set environment variables. + * + * @param[in] channel The channel to set the environment variables. + * + * @param[in] name The name of the variable. + * + * @param[in] value The value to set. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * @warning Some environment variables may be refused by security reasons. + */ +int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(name == NULL || value == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "ss", + name, + value); + if (rc != SSH_OK){ + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "env", buffer,1); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Run a shell command without an interactive shell. + * + * This is similar to 'sh -c command'. + * + * @param[in] channel The channel to execute the command. + * + * @param[in] cmd The command to execute + * (e.g. "ls ~/ -al | grep -i reports"). + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * Example: +@code + rc = ssh_channel_request_exec(channel, "ps aux"); + if (rc > 0) { + return -1; + } + + while ((rc = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + if (fwrite(buffer, 1, rc, stdout) != (unsigned int) rc) { + return -1; + } + } +@endcode + * + * @warning In a single channel, only ONE command can be executed! + * If you want to executed multiple commands, allocate separate channels for + * them or consider opening interactive shell. + * Attempting to run multiple consecutive commands in one channel will fail. + * See RFC 4254 Section 6.5. + * + * @see ssh_channel_request_shell() + */ +int ssh_channel_request_exec(ssh_channel channel, const char *cmd) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(cmd == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "s", cmd); + + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "exec", buffer, 1); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/** + * @brief Send a signal to remote process (as described in RFC 4254, + * section 6.9). + * + * Sends a signal 'sig' to the remote process. + * Note, that remote system may not support signals concept. + * In such a case this request will be silently ignored. + * + * @param[in] channel The channel to send signal. + * + * @param[in] sig The signal to send (without SIG prefix) + * \n\n + * SIGABRT -> ABRT \n + * SIGALRM -> ALRM \n + * SIGFPE -> FPE \n + * SIGHUP -> HUP \n + * SIGILL -> ILL \n + * SIGINT -> INT \n + * SIGKILL -> KILL \n + * SIGPIPE -> PIPE \n + * SIGQUIT -> QUIT \n + * SIGSEGV -> SEGV \n + * SIGTERM -> TERM \n + * SIGUSR1 -> USR1 \n + * SIGUSR2 -> USR2 \n + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + */ +int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return SSH_ERROR; + } + if (sig == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "s", sig); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "signal", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/** + * @brief Send a break signal to the server (as described in RFC 4335). + * + * Sends a break signal to the remote process. + * Note, that remote system may not support breaks. + * In such a case this request will be silently ignored. + * + * @param[in] channel The channel to send the break to. + * + * @param[in] length The break-length in milliseconds to send. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred + */ +int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "d", length); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "break", buffer, 0); + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/** + * @brief Read data from a channel into a buffer. + * + * @param[in] channel The channel to read from. + * + * @param[out] buffer The buffer which will get the data. + * + * @param[in] count The count of bytes to be read. If it is bigger than 0, + * the exact size will be read, else (bytes=0) it will return once anything is + * available. + * + * @param is_stderr A boolean value to mark reading from the stderr stream. + * + * @return The number of bytes read, 0 on end of file, `SSH_AGAIN` + * on timeout and `SSH_ERROR` on error. + * + * @deprecated Please use ssh_channel_read instead + * @warning This function doesn't work in nonblocking/timeout mode + * @see ssh_channel_read + */ +int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr) +{ + ssh_session session = NULL; + char *buffer_tmp = NULL; + int r; + uint32_t total = 0; + + if (channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if (buffer == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + ssh_buffer_reinit(buffer); + if (count == 0) { + do { + r = ssh_channel_poll(channel, is_stderr); + if (r < 0) { + return r; + } + if (r > 0) { + count = r; + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + r = ssh_channel_read(channel, buffer_tmp, r, is_stderr); + if (r < 0) { + ssh_buffer_pass_bytes_end(buffer, count); + return r; + } + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - r); + + return r; + } + if (ssh_channel_is_eof(channel)) { + return 0; + } + ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE); + } while (r == 0); + } + + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + while (total < count) { + r = ssh_channel_read(channel, buffer_tmp, count - total, is_stderr); + if (r < 0) { + ssh_buffer_pass_bytes_end(buffer, count); + return r; + } + if (r == 0) { + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - total); + return total; + } + total += r; + } + + return total; +} + +struct ssh_channel_read_termination_struct { + ssh_channel channel; + ssh_buffer buffer; +}; + +static int ssh_channel_read_termination(void *s) +{ + struct ssh_channel_read_termination_struct *ctx = s; + if (ssh_buffer_get_len(ctx->buffer) >= 1 || + ctx->channel->remote_eof || + ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @brief Reads data from a channel. + * + * @param[in] channel The channel to read from. + * + * @param[out] dest The destination buffer which will get the data. + * + * @param[in] count The count of bytes to be read. + * + * @param[in] is_stderr A boolean value to mark reading from the stderr flow. + * + * @return The number of bytes read, 0 on end of file, `SSH_AGAIN` + * on timeout and `SSH_ERROR` on error. + * + * @warning This function may return less than count bytes of data, and won't + * block until count bytes have been read. + */ +int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr) +{ + return ssh_channel_read_timeout(channel, + dest, + count, + is_stderr, + SSH_TIMEOUT_DEFAULT); +} + +/** + * @brief Reads data from a channel. + * + * @param[in] channel The channel to read from. + * + * @param[out] dest The destination buffer which will get the data. + * + * @param[in] count The count of bytes to be read. + * + * @param[in] is_stderr A boolean value to mark reading from the stderr flow. + * + * @param[in] timeout_ms A timeout in milliseconds. A value of -1 means + * infinite timeout. + * + * @return The number of bytes read, 0 on end of file, `SSH_AGAIN` + * on timeout, `SSH_ERROR` on error. + * + * @warning This function may return less than count bytes of data, and won't + * block until count bytes have been read. + */ +int ssh_channel_read_timeout(ssh_channel channel, + void *dest, + uint32_t count, + int is_stderr, + int timeout_ms) +{ + ssh_session session = NULL; + ssh_buffer stdbuf = NULL; + uint32_t len; + struct ssh_channel_read_termination_struct ctx; + int rc; + + if (channel == NULL) { + return SSH_ERROR; + } + if (dest == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + session = channel->session; + stdbuf = channel->stdout_buffer; + + if (count == 0) { + return 0; + } + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + + SSH_LOG(SSH_LOG_PACKET, + "Read (%" PRIu32 ") buffered : %" PRIu32 " bytes. Window: %" PRIu32, + count, + ssh_buffer_get_len(stdbuf), + channel->local_window); + + /* block reading until at least one byte has been read + * and ignore the trivial case count=0 + */ + ctx.channel = channel; + ctx.buffer = stdbuf; + + if (timeout_ms < SSH_TIMEOUT_DEFAULT) { + timeout_ms = SSH_TIMEOUT_INFINITE; + } + + rc = ssh_handle_packets_termination(session, + timeout_ms, + ssh_channel_read_termination, + &ctx); + if (rc == SSH_ERROR || rc == SSH_AGAIN) { + return rc; + } + + /* + * If the channel is closed or in an error state, reading from it is an + * error + */ + if (session->session_state == SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + /* If the server closed the channel properly, there is nothing to do */ + if (channel->remote_eof && ssh_buffer_get_len(stdbuf) == 0) { + return 0; + } + if (channel->state == SSH_CHANNEL_STATE_CLOSED) { + ssh_set_error(session, SSH_FATAL, "Remote channel is closed."); + return SSH_ERROR; + } + len = ssh_buffer_get_len(stdbuf); + /* Read count bytes if len is greater, everything otherwise */ + len = (len > count ? count : len); + memcpy(dest, ssh_buffer_get(stdbuf), len); + ssh_buffer_pass_bytes(stdbuf, len); + if (channel->counter != NULL) { + channel->counter->in_bytes += len; + } + /* Try completing the delayed_close */ + if (channel->delayed_close && !ssh_channel_has_unread_data(channel)) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } + + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; + } + + return len; +} + +/** + * @brief Do a nonblocking read on the channel. + * + * A nonblocking read on the specified channel. it will return <= count bytes of + * data read atomically. It will also trigger any callbacks set on the channel. + * + * @param[in] channel The channel to read from. + * + * @param[out] dest A pointer to a destination buffer. + * + * @param[in] count The count of bytes of data to be read. + * + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes read, `SSH_AGAIN` if nothing is + * available, `SSH_ERROR` on error, and `SSH_EOF` if the channel is EOF. + * + * @see ssh_channel_is_eof() + */ +int ssh_channel_read_nonblocking(ssh_channel channel, + void *dest, + uint32_t count, + int is_stderr) +{ + ssh_session session = NULL; + uint32_t to_read; + int rc; + int blocking; + + if(channel == NULL) { + return SSH_ERROR; + } + if(dest == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + session = channel->session; + + rc = ssh_channel_poll(channel, is_stderr); + + if (rc <= 0) { + if (session->session_state == SSH_SESSION_STATE_ERROR){ + return SSH_ERROR; + } + + return rc; /* may be an error code */ + } + + to_read = (unsigned int)rc; + + if (to_read > count) { + to_read = count; + } + blocking = ssh_is_blocking(session); + ssh_set_blocking(session, 0); + rc = ssh_channel_read(channel, dest, to_read, is_stderr); + ssh_set_blocking(session,blocking); + + return rc; +} + +/** + * @brief Polls a channel for data to read. + * + * If callbacks are set on the channel, they will be called. + * + * @param[in] channel The channel to poll. + * + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes available for reading, 0 if nothing + * is available or `SSH_ERROR` on error. + * When a channel is freed the function returns + * `SSH_ERROR` immediately. + * + * @warning When the channel is in EOF state, the function returns `SSH_EOF`. + * + * @see ssh_channel_is_eof() + */ +int ssh_channel_poll(ssh_channel channel, int is_stderr) +{ + ssh_buffer stdbuf; + + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { + return SSH_ERROR; + } + + stdbuf = channel->stdout_buffer; + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + + if (channel->remote_eof == 0) { + if (channel->session->session_state == SSH_SESSION_STATE_ERROR){ + return SSH_ERROR; + } + if (ssh_handle_packets(channel->session, SSH_TIMEOUT_NONBLOCKING)==SSH_ERROR) { + return SSH_ERROR; + } + } + + if (ssh_buffer_get_len(stdbuf) > 0){ + return ssh_buffer_get_len(stdbuf); + } + + if (channel->remote_eof) { + return SSH_EOF; + } + + return ssh_buffer_get_len(stdbuf); +} + +/** + * @brief Polls a channel for data to read, waiting for a certain timeout. + * + * @param[in] channel The channel to poll. + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying a negative + * value means an infinite timeout. This parameter is + * passed to the poll() function. + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes available for reading, + * 0 if nothing is available (timeout elapsed), + * `SSH_EOF` on end of file, + * `SSH_ERROR` on error. + * + * @warning When the channel is in EOF state, the function returns `SSH_EOF`. + * When a channel is freed the function returns `SSH_ERROR` + * immediately. + * + * @see ssh_channel_is_eof() + */ +int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) +{ + ssh_session session = NULL; + ssh_buffer stdbuf = NULL; + struct ssh_channel_read_termination_struct ctx; + size_t len; + int rc; + + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { + return SSH_ERROR; + } + + session = channel->session; + stdbuf = channel->stdout_buffer; + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + ctx.buffer = stdbuf; + ctx.channel = channel; + rc = ssh_handle_packets_termination(channel->session, + timeout, + ssh_channel_read_termination, + &ctx); + if (rc == SSH_ERROR || + session->session_state == SSH_SESSION_STATE_ERROR) { + rc = SSH_ERROR; + goto out; + } else if (rc == SSH_AGAIN) { + /* If the above timeout expired, it is ok and we do not need to + * attempt to check the read buffer. The calling functions do not + * expect us to return SSH_AGAIN either here. */ + rc = SSH_OK; + goto out; + } + len = ssh_buffer_get_len(stdbuf); + if (len > 0) { + if (len > INT_MAX) { + rc = SSH_ERROR; + } else { + rc = (int)len; + } + goto out; + } + if (channel->remote_eof) { + rc = SSH_EOF; + } + +out: + return rc; +} + +/** + * @brief Recover the session in which belongs a channel. + * + * @param[in] channel The channel to recover the session from. + * + * @return The session pointer. + */ +ssh_session ssh_channel_get_session(ssh_channel channel) +{ + if (channel == NULL) { + return NULL; + } + + return channel->session; +} + +static int ssh_channel_exit_status_termination(void *c) +{ + ssh_channel channel = c; + if (channel->exit.status || + /* When a channel is closed, no exit status message can + * come anymore */ + (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + { + return 1; + } + return 0; +} + +/** + * @brief Get the exit state of the channel (error code from the executed + * instruction or signal). + * + * @param[in] channel The channel to get the status from. + * + * @param[out] pexit_code A pointer to an uint32_t to store the exit status. + * + * @param[out] pexit_signal A pointer to store the exit signal as a string. + * The signal is without the SIG prefix, e.g. "TERM" or + * "KILL"). The caller has to free the memory. + * + * @param[out] pcore_dumped A pointer to store a boolean value if it dumped a + * core. + * + * @return `SSH_OK` on success, `SSH_AGAIN` if we don't have a + * status or an SSH error. + * @warning This function may block until a timeout (or never) + * if the other side is not willing to close the channel. + * When a channel is freed the function returns + * `SSH_ERROR` immediately. + * + * If you're looking for an async handling of this register a callback for the + * exit status! + * + * @see ssh_channel_exit_status_callback + * @see ssh_channel_exit_signal_callback + */ +int ssh_channel_get_exit_state(ssh_channel channel, + uint32_t *pexit_code, + char **pexit_signal, + int *pcore_dumped) +{ + ssh_session session = NULL; + int rc; + + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { + return SSH_ERROR; + } + session = channel->session; + + rc = ssh_handle_packets_termination(channel->session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_exit_status_termination, + channel); + if (rc == SSH_ERROR || channel->session->session_state == + SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + + /* If we don't have any kind of exit state, return SSH_AGAIN */ + if (!channel->exit.status) { + return SSH_AGAIN; + } + + if (pexit_code != NULL) { + *pexit_code = channel->exit.code; + } + + if (pexit_signal != NULL) { + *pexit_signal = NULL; + if (channel->exit.signal != NULL) { + *pexit_signal = strdup(channel->exit.signal); + if (*pexit_signal == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + } + + if (pcore_dumped != NULL) { + *pcore_dumped = channel->exit.core_dumped; + } + + return SSH_OK; +} + +/** + * @brief Get the exit status of the channel (error code from the executed + * instruction). + * + * @param[in] channel The channel to get the status from. + * + * @return The exit status, -1 if no exit status has been returned + * (yet), or `SSH_ERROR` on error. + * @warning This function may block until a timeout (or never) + * if the other side is not willing to close the channel. + * When a channel is freed the function returns + * `SSH_ERROR` immediately. + * + * If you're looking for an async handling of this register a callback for the + * exit status. + * + * @see ssh_channel_exit_status_callback + * @deprecated Please use ssh_channel_exit_state() + */ +int ssh_channel_get_exit_status(ssh_channel channel) +{ + uint32_t exit_status = (uint32_t)-1; + int rc; + + rc = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + return exit_status; +} + +/* + * This function acts as a meta select. + * + * First, channels are analyzed to seek potential can-write or can-read ones, + * then if no channel has been elected, it goes in a loop with the posix + * select(2). + * This is made in two parts: protocol select and network select. The protocol + * select does not use the network functions at all + */ +static int +channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, + ssh_channel *echans, ssh_channel *rout, + ssh_channel *wout, ssh_channel *eout) +{ + ssh_channel chan = NULL; + int i; + int j = 0; + + for (i = 0; rchans[i] != NULL; i++) { + chan = rchans[i]; + + while (ssh_channel_is_open(chan) && + ssh_socket_data_available(chan->session->socket)) { + ssh_handle_packets(chan->session, SSH_TIMEOUT_NONBLOCKING); + } + + if ((chan->stdout_buffer && + ssh_buffer_get_len(chan->stdout_buffer) > 0) || + (chan->stderr_buffer && + ssh_buffer_get_len(chan->stderr_buffer) > 0) || + chan->remote_eof) { + rout[j] = chan; + j++; + } + } + rout[j] = NULL; + + j = 0; + for (i = 0; wchans[i] != NULL; i++) { + chan = wchans[i]; + /* It's not our business to seek if the file descriptor is writable */ + if (ssh_socket_data_writable(chan->session->socket) && + ssh_channel_is_open(chan) && (chan->remote_window > 0)) { + wout[j] = chan; + j++; + } + } + wout[j] = NULL; + + j = 0; + for (i = 0; echans[i] != NULL; i++) { + chan = echans[i]; + + if (!ssh_socket_is_open(chan->session->socket) || + ssh_channel_is_closed(chan)) { + eout[j] = chan; + j++; + } + } + eout[j] = NULL; + + return 0; +} + +/* Just count number of pointers in the array */ +static size_t count_ptrs(ssh_channel *ptrs) +{ + size_t c; + for (c = 0; ptrs[c] != NULL; c++) + ; + + return c; +} + +/** + * @brief Act like the standard select(2) on channels. + * + * The list of pointers are then actualized and will only contain pointers to + * channels that are respectively readable, writable or have an exception to + * trap. + * + * @param[in] readchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] writechans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] exceptchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] timeout Timeout as defined by select(2). + * + * @return `SSH_OK` on a successful operation, `SSH_EINTR` if the + * select(2) syscall was interrupted, then relaunch the + * function, or `SSH_ERROR` on error. + */ +int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, + ssh_channel *exceptchans, struct timeval * timeout) +{ + ssh_channel *rchans = NULL, *wchans = NULL, *echans = NULL; + ssh_channel dummy = NULL; + ssh_event event = NULL; + int rc; + int i; + int tm, tm_base; + int firstround = 1; + struct ssh_timestamp ts; + + if (timeout != NULL) + tm_base = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; + else + tm_base = SSH_TIMEOUT_INFINITE; + ssh_timestamp_init(&ts); + tm = tm_base; + /* don't allow NULL pointers */ + if (readchans == NULL) { + readchans = &dummy; + } + + if (writechans == NULL) { + writechans = &dummy; + } + + if (exceptchans == NULL) { + exceptchans = &dummy; + } + + if (readchans[0] == NULL && writechans[0] == NULL && + exceptchans[0] == NULL) { + /* No channel to poll?? Go away! */ + return 0; + } + + /* Prepare the outgoing temporary arrays */ + rchans = calloc(count_ptrs(readchans) + 1, sizeof(ssh_channel)); + if (rchans == NULL) { + return SSH_ERROR; + } + + wchans = calloc(count_ptrs(writechans) + 1, sizeof(ssh_channel)); + if (wchans == NULL) { + SAFE_FREE(rchans); + return SSH_ERROR; + } + + echans = calloc(count_ptrs(exceptchans) + 1, sizeof(ssh_channel)); + if (echans == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + return SSH_ERROR; + } + + /* + * First, try without doing network stuff then, use the ssh_poll + * infrastructure to poll on all sessions. + */ + do { + channel_protocol_select(readchans, + writechans, + exceptchans, + rchans, + wchans, + echans); + if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) { + /* At least one channel has an event */ + break; + } + /* Add all channels' sessions right into an event object */ + if (event == NULL) { + event = ssh_event_new(); + if (event == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + + return SSH_ERROR; + } + for (i = 0; readchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(readchans[i]->session); + ssh_event_add_session(event, readchans[i]->session); + } + for (i = 0; writechans[i] != NULL; i++) { + ssh_poll_get_default_ctx(writechans[i]->session); + ssh_event_add_session(event, writechans[i]->session); + } + for (i = 0; exceptchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(exceptchans[i]->session); + ssh_event_add_session(event, exceptchans[i]->session); + } + } + /* Get out if the timeout has elapsed */ + if (!firstround && ssh_timeout_elapsed(&ts, tm_base)) { + break; + } + /* Here we go */ + rc = ssh_event_dopoll(event, tm); + if (rc != SSH_OK) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + ssh_event_free(event); + return rc; + } + tm = ssh_timeout_update(&ts, tm_base); + firstround = 0; + } while (1); + + if (readchans != &dummy) { + memcpy(readchans, + rchans, + (count_ptrs(rchans) + 1) * sizeof(ssh_channel)); + } + if (writechans != &dummy) { + memcpy(writechans, + wchans, + (count_ptrs(wchans) + 1) * sizeof(ssh_channel)); + } + if (exceptchans != &dummy) { + memcpy(exceptchans, + echans, + (count_ptrs(echans) + 1) * sizeof(ssh_channel)); + } + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + if (event) + ssh_event_free(event); + return 0; +} + +/** + * @brief Set the channel data counter. + * + * @code + * struct ssh_counter_struct counter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * ssh_channel_set_counter(channel, &counter); + * @endcode + * + * @param[in] channel The SSH channel. + * + * @param[in] counter Counter for bytes handled by the channel. + */ +void ssh_channel_set_counter(ssh_channel channel, + ssh_counter counter) +{ + if (channel != NULL) { + channel->counter = counter; + } +} + +/** + * @brief Blocking write on a channel stderr. + * + * @param[in] channel The channel to write to. + * + * @param[in] data A pointer to the data to write. + * + * @param[in] len The length of the buffer to write to. + * + * @return The number of bytes written, SSH_ERROR on error. + * + * @see ssh_channel_read() + */ +int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 1); +} + +#if WITH_SERVER + +/** + * @brief Open a TCP/IP reverse forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotehost The remote host to connected (host name or IP). + * + * @param[in] remoteport The remote port. + * + * @param[in] sourcehost The source host (your local computer). It's optional + * and for logging purpose. + * + * @param[in] localport The source port (your local computer). It's optional + * and for logging purpose. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the local port and does not + * automatically forward the content of a socket to the channel. You + * still have to use ssh_channel_read and ssh_channel_write for this. + */ +int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return rc; + } + if (remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + session = channel->session; + + if (channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_pack(payload, + "sdsd", + remotehost, + remoteport, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_open(channel, + "forwarded-tcpip", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + + return rc; +} + +/** + * @brief Open a X11 channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] orig_addr The source host (the local server). + * + * @param[in] orig_port The source port (the local server). + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * @warning This function does not bind the local port and does not + * automatically forward the content of a socket to the channel. You + * still have to use shh_channel_read and ssh_channel_write for this. + */ +int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return rc; + } + if (orig_addr == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + session = channel->session; + + if (channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, "sd", orig_addr, orig_port); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_open(channel, + "x11", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + + return rc; +} + +/** + * @brief Send the exit status to the remote process + * + * Sends the exit status to the remote process (as described in RFC 4254, + * section 6.10). + * + * @param[in] channel The channel to send exit status. + * + * @param[in] exit_status The exit status to send + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + */ +int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "d", exit_status); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "exit-status", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/** + * @brief Send an exit signal to remote process (RFC 4254, section 6.10). + * + * This sends the exit status of the remote process. + * Note, that remote system may not support signals concept. + * In such a case this request will be silently ignored. + * + * @param[in] channel The channel to send signal. + * + * @param[in] sig The signal to send (without SIG prefix) + * (e.g. "TERM" or "KILL"). + * @param[in] core A boolean to tell if a core was dumped + * @param[in] errmsg A CRLF explanation text about the error condition + * @param[in] lang The language used in the message (format: RFC 3066) + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred + */ +int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig, + int core, const char *errmsg, const char *lang) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return rc; + } + if(sig == NULL || errmsg == NULL || lang == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "sbss", + sig, + core ? 1 : 0, + errmsg, + lang); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "exit-signal", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +#endif + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/client.c b/src/libs/libssh-0.12.2/src/client.c new file mode 100644 index 000000000000..323a7a93c4a9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/client.c @@ -0,0 +1,936 @@ +/* + * client.c - SSH client functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/buffer.h" +#include "libssh/kex-gss.h" +#include "libssh/dh.h" +#include "libssh/options.h" +#include "libssh/packet.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#ifdef WITH_GEX +#include "libssh/dh-gex.h" +#endif /* WITH_GEX */ +#include "libssh/ecdh.h" +#include "libssh/threads.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/kex.h" +#include "libssh/hybrid_mlkem.h" + +#ifndef _WIN32 +#ifdef HAVE_PTHREAD +extern int proxy_disconnect; +#endif /* HAVE_PTHREAD */ +#endif /* _WIN32 */ + +#define set_status(session, status) do {\ + if (session->common.callbacks && session->common.callbacks->connect_status_function) \ + session->common.callbacks->connect_status_function(session->common.callbacks->userdata, status); \ + } while (0) + +/** + * @internal + * @brief Callback to be called when the socket is connected or had a + * connection error. Changes the state of the session and updates the error + * message. + * @param code one of SSH_SOCKET_CONNECTED_OK or SSH_SOCKET_CONNECTED_ERROR + * @param user is a pointer to session + */ +static void socket_callback_connected(int code, int errno_code, void *user) +{ + ssh_session session=(ssh_session)user; + + if (session->session_state != SSH_SESSION_STATE_CONNECTING && + session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) + { + ssh_set_error(session,SSH_FATAL, "Wrong state in socket_callback_connected : %d", + session->session_state); + + return; + } + + SSH_LOG(SSH_LOG_TRACE,"Socket connection callback: %d (%d)",code, errno_code); + if(code == SSH_SOCKET_CONNECTED_OK) + session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; + else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session,SSH_FATAL,"%s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); + } + session->ssh_connection_callback(session); +} + +/** + * @internal + * + * @brief Gets the banner from socket and saves it in session. + * Updates the session state + * + * @param data pointer to the beginning of header + * @param len size of the banner + * @param user is a pointer to session + * @returns Number of bytes processed, or zero if the banner is not complete. + */ +static size_t callback_receive_banner(const void *data, size_t len, void *user) +{ + char *buffer = (char *)data; + ssh_session session = (ssh_session) user; + char *str = NULL; + uint32_t i; + int ret=0; + + if (session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) { + ssh_set_error(session,SSH_FATAL, + "Wrong state in callback_receive_banner : %d", + session->session_state); + + return 0; + } + for (i = 0; i < len; ++i) { +#ifdef WITH_PCAP + if (session->pcap_ctx && buffer[i] == '\n') { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + buffer,i+1, + i+1); + } +#endif + if (buffer[i] == '\r') { + buffer[i] = '\0'; + } + if (buffer[i] == '\n') { + int cmp; + + buffer[i] = '\0'; + + /* The server MAY send other lines of data... */ + cmp = strncmp(buffer, "SSH-", 4); + if (cmp == 0) { + str = strdup(buffer); + if (str == NULL) { + return SSH_ERROR; + } + /* number of bytes read */ + ret = i + 1; + session->serverbanner = str; + session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED; + SSH_LOG(SSH_LOG_PACKET, "Received banner: %s", str); + session->ssh_connection_callback(session); + + return ret; + } else { + SSH_LOG(SSH_LOG_DEBUG, + "ssh_protocol_version_exchange: %s", + buffer); + ret = i + 1; + break; + } + } + /* According to RFC 4253 the max banner length is 255 */ + if (i > 255) { + /* Too big banner */ + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session, + SSH_FATAL, + "Receiving banner: too large banner"); + + return 0; + } + } + + return ret; +} + +/** @internal + * @brief Sends a SSH banner to the server. + * + * @param session The SSH session to use. + * + * @param server Send client or server banner. + * + * @return 0 on success, < 0 on error. + */ +int ssh_send_banner(ssh_session session, int server) +{ + const char *banner = CLIENT_BANNER_SSH2; + const char *terminator = "\r\n"; + /* The maximum banner length is 255 for SSH2 */ + char buffer[256] = {0}; + size_t len; + int rc = SSH_ERROR; + + if (server == 1) { + if (session->server_opts.custombanner == NULL) { + session->serverbanner = strdup(banner); + if (session->serverbanner == NULL) { + goto end; + } + } else { + len = strlen(session->server_opts.custombanner); + session->serverbanner = malloc(len + 8 + 1); + if(session->serverbanner == NULL) { + goto end; + } + snprintf(session->serverbanner, + len + 8 + 1, + "SSH-2.0-%s", + session->server_opts.custombanner); + } + + snprintf(buffer, + sizeof(buffer), + "%s%s", + session->serverbanner, + terminator); + } else { + session->clientbanner = strdup(banner); + if (session->clientbanner == NULL) { + goto end; + } + + snprintf(buffer, + sizeof(buffer), + "%s%s", + session->clientbanner, + terminator); + } + + rc = ssh_socket_write(session->socket, buffer, (uint32_t)strlen(buffer)); + if (rc == SSH_ERROR) { + goto end; + } +#ifdef WITH_PCAP + if (session->pcap_ctx != NULL) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_OUT, + buffer, + (uint32_t)strlen(buffer), + (uint32_t)strlen(buffer)); + } +#endif + + rc = SSH_OK; +end: + return rc; +} + +/** @internal + * @brief launches the DH handshake state machine + * @param session session handle + * @returns SSH_OK or SSH_ERROR + * @warning this function returning is no proof that DH handshake is + * completed + */ +int dh_handshake(ssh_session session) +{ + int rc = SSH_AGAIN; + + SSH_LOG(SSH_LOG_TRACE, + "dh_handshake_state = %d, kex_type = %d", + session->dh_handshake_state, + session->next_crypto->kex_type); + + switch (session->dh_handshake_state) { + case DH_STATE_INIT: + switch (session->next_crypto->kex_type) { +#ifdef WITH_GSSAPI + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_client_gss_kex_init(session); + break; +#endif + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_client_dh_init(session); + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + rc = ssh_client_dhgex_init(session); + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + rc = ssh_client_ecdh_init(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + rc = ssh_client_curve25519_init(session); + break; +#endif +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + rc = ssh_client_sntrup761x25519_init(session); + break; +#endif + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_client_hybrid_mlkem_init(session); + break; + default: + rc = SSH_ERROR; + } + + break; + case DH_STATE_INIT_SENT: + /* wait until ssh_packet_dh_reply is called */ + break; + case DH_STATE_NEWKEYS_SENT: + /* wait until ssh_packet_newkeys is called */ + break; + case DH_STATE_FINISHED: + return SSH_OK; + default: + ssh_set_error(session, + SSH_FATAL, + "Invalid state in dh_handshake(): %d", + session->dh_handshake_state); + + return SSH_ERROR; + } + + return rc; +} + +static int ssh_service_request_termination(void *s) +{ + ssh_session session = (ssh_session)s; + + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->auth.service_state != SSH_AUTH_SERVICE_SENT) + return 1; + else + return 0; +} + +/** + * @addtogroup libssh_session + * + * @{ + */ + +/** + * @internal + * @brief Request a service from the SSH server. + * + * Service requests are for example: ssh-userauth, ssh-connection, etc. + * + * @param session The session to use to ask for a service request. + * @param service The service request. + * + * @return SSH_OK on success + * @return SSH_ERROR on error + * @return SSH_AGAIN No response received yet + * @bug actually only works with ssh-userauth + */ +int ssh_service_request(ssh_session session, const char *service) +{ + int rc = SSH_ERROR; + + if(session->auth.service_state != SSH_AUTH_SERVICE_NONE) + goto pending; + + rc = ssh_buffer_pack(session->out_buffer, + "bs", + SSH2_MSG_SERVICE_REQUEST, + service); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + session->auth.service_state = SSH_AUTH_SERVICE_SENT; + if (ssh_packet_send(session) == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, + "Sending SSH2_MSG_SERVICE_REQUEST failed."); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent SSH_MSG_SERVICE_REQUEST (service %s)", service); +pending: + rc=ssh_handle_packets_termination(session,SSH_TIMEOUT_USER, + ssh_service_request_termination, session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + switch(session->auth.service_state) { + case SSH_AUTH_SERVICE_DENIED: + ssh_set_error(session,SSH_FATAL,"ssh_auth_service request denied"); + break; + case SSH_AUTH_SERVICE_ACCEPTED: + rc=SSH_OK; + break; + case SSH_AUTH_SERVICE_SENT: + rc=SSH_AGAIN; + break; + case SSH_AUTH_SERVICE_NONE: + rc=SSH_ERROR; + break; + } + + return rc; +} + +/** + * @internal + * + * @brief A function to be called each time a step has been done in the + * connection. + */ +static void ssh_client_connection_callback(ssh_session session) +{ + int rc; + + SSH_LOG(SSH_LOG_DEBUG, "session_state=%d", session->session_state); + + switch (session->session_state) { + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + break; + case SSH_SESSION_STATE_SOCKET_CONNECTED: + ssh_set_fd_towrite(session); + ssh_send_banner(session, 0); + + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->serverbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + SSH_LOG(SSH_LOG_DEBUG, "SSH server banner: %s", session->serverbanner); + + /* Here we analyze the different protocols the server allows. */ + rc = ssh_analyze_banner(session, 0); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "No version of SSH protocol usable (banner: %s)", + session->serverbanner); + goto error; + } + + ssh_packet_register_socket_callback(session, session->socket); + + ssh_packet_set_default_callbacks(session); + session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + set_status(session, 0.5f); + + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session, 0.6f); + ssh_list_kex(&session->next_crypto->server_kex); + if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + /* in rekeying state if next_crypto client_kex might be empty */ + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + } + if (ssh_kex_select_methods(session) == SSH_ERROR) + goto error; + set_status(session, 0.8f); + session->session_state = SSH_SESSION_STATE_DH; + + /* If the init packet was already sent in previous step, this will be no + * operation */ + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } + FALL_THROUGH; + case SSH_SESSION_STATE_DH: + if (session->dh_handshake_state == DH_STATE_FINISHED) { + set_status(session, 1.0f); + session->connected = 1; + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + } else { + session->session_state = SSH_SESSION_STATE_AUTHENTICATING; + } + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state %d", + session->session_state); + } + + return; +error: + ssh_session_socket_close(session); + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(session)); +} + +/** @internal + * @brief describe under which conditions the ssh_connect function may stop + */ +static int ssh_connect_termination(void *user) +{ + ssh_session session = (ssh_session)user; + + switch (session->session_state) { + case SSH_SESSION_STATE_ERROR: + case SSH_SESSION_STATE_AUTHENTICATING: + case SSH_SESSION_STATE_DISCONNECTED: + return 1; + default: + return 0; + } +} + +/** + * @brief Connect to the ssh server. + * + * @param[in] session The ssh session to connect. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN, if the session is in nonblocking mode, + * and call must be done again. + * + * @see ssh_new() + * @see ssh_disconnect() + */ +int ssh_connect(ssh_session session) +{ + int ret; + + if (!is_ssh_initialized()) { + ssh_set_error(session, SSH_FATAL, + "Library not initialized."); + + return SSH_ERROR; + } + + if (session == NULL) { + return SSH_ERROR; + } + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_CONNECT: + goto pending; + default: + ssh_set_error(session, SSH_FATAL, + "Bad call during pending SSH call in ssh_connect"); + + return SSH_ERROR; + } + session->alive = 0; + session->client = 1; + + if (session->opts.fd == SSH_INVALID_SOCKET && + session->opts.host == NULL && + session->opts.ProxyCommand == NULL) + { + ssh_set_error(session, SSH_FATAL, "Hostname required"); + return SSH_ERROR; + } + + /* If the system configuration files were not yet processed, do it now */ + if (!session->opts.config_processed) { + ret = ssh_options_parse_config(session, NULL); + if (ret != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to process system configuration files"); + return SSH_ERROR; + } + } + + ret = ssh_options_apply(session); + if (ret < 0) { + ssh_set_error(session, SSH_FATAL, "Couldn't apply options"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, + "libssh %s, using threading %s", + ssh_copyright(), + ssh_threads_get_type()); + + session->ssh_connection_callback = ssh_client_connection_callback; + session->session_state = SSH_SESSION_STATE_CONNECTING; + ssh_socket_set_callbacks(session->socket, &session->socket_callbacks); + session->socket_callbacks.connected = socket_callback_connected; + session->socket_callbacks.data = callback_receive_banner; + session->socket_callbacks.exception = ssh_socket_exception_callback; + session->socket_callbacks.userdata = session; + + if (session->opts.fd != SSH_INVALID_SOCKET) { + session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; + ret = ssh_socket_set_fd(session->socket, session->opts.fd); + } else if (session->opts.ProxyCommand != NULL && strncmp(VBOX_PROXY_PREFIX, session->opts.ProxyCommand, VBOX_PROXY_PREFIX_LENGTH) == 0) { + ret = ssh_socket_connect_proxycommand_vbox(session->socket, + session->opts.host, + session->opts.port > 0 ? session->opts.port : 22, + session->opts.ProxyCommand + VBOX_PROXY_PREFIX_LENGTH); +#ifndef _WIN32 +#ifdef HAVE_PTHREAD + } else if (ssh_libssh_proxy_jumps() && + ssh_list_count(session->opts.proxy_jumps) != 0) { + ret = ssh_socket_connect_proxyjump(session->socket); +#endif /* HAVE_PTHREAD */ +#endif /* _WIN32 */ + } else if (session->opts.ProxyCommand != NULL) { +#ifdef WITH_EXEC + ret = ssh_socket_connect_proxycommand(session->socket, + session->opts.ProxyCommand); +#else + ssh_set_error(session, + SSH_FATAL, + "The libssh is built without support for proxy commands."); + ret = SSH_ERROR; +#endif /* WITH_EXEC */ + } else { + ret = ssh_socket_connect(session->socket, + session->opts.host, + session->opts.port > 0 ? session->opts.port : 22, + session->opts.bindaddr); + } + if (ret == SSH_ERROR) { + return SSH_ERROR; + } + + set_status(session, 0.2f); + + session->alive = 1; + SSH_LOG(SSH_LOG_DEBUG, + "Socket connecting, now waiting for the callbacks to work"); + +pending: + session->pending_call_state = SSH_PENDING_CALL_CONNECT; + if(ssh_is_blocking(session)) { + int timeout = (session->opts.timeout * 1000) + + (session->opts.timeout_usec / 1000); + if (timeout == 0) { + timeout = 10 * 1000; + } + SSH_LOG(SSH_LOG_PACKET, "Actual timeout : %d", timeout); + ret = ssh_handle_packets_termination(session, timeout, + ssh_connect_termination, session); + if (session->session_state != SSH_SESSION_STATE_ERROR && + (ret == SSH_ERROR || !ssh_connect_termination(session))) + { + ssh_set_error(session, SSH_FATAL, + "Timeout connecting to %s", session->opts.host); + session->session_state = SSH_SESSION_STATE_ERROR; + } + } else { + ret = ssh_handle_packets_termination(session, + SSH_TIMEOUT_NONBLOCKING, + ssh_connect_termination, + session); + if (ret == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + } + + SSH_LOG(SSH_LOG_PACKET, "current state : %d", session->session_state); + if (!ssh_is_blocking(session) && !ssh_connect_termination(session)) { + return SSH_AGAIN; + } + + session->pending_call_state = SSH_PENDING_CALL_NONE; + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->session_state == SSH_SESSION_STATE_DISCONNECTED) + { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Get the issue banner from the server. + * + * This is the banner showing a disclaimer to users who log in, + * typically their right or the fact that they will be monitored. + * + * @param[in] session The SSH session to use. + * + * @return A newly allocated string with the banner, NULL on error. + */ +char *ssh_get_issue_banner(ssh_session session) +{ + if (session == NULL || session->banner == NULL) { + return NULL; + } + + return ssh_string_to_char(session->banner); +} + +/** + * @brief Get the version of the OpenSSH server, if it is not an OpenSSH server + * then 0 will be returned. + * + * You can use the SSH_VERSION_INT macro to compare version numbers. + * + * @param[in] session The SSH session to use. + * + * @return The version number if available, 0 otherwise. + * + * @code + * int openssh = ssh_get_openssh_version(); + * + * if (openssh == SSH_INT_VERSION(6, 1, 0)) { + * printf("Version match!\m"); + * } + * @endcode + */ +int ssh_get_openssh_version(ssh_session session) +{ + if (session == NULL) { + return 0; + } + + return session->openssh; +} + +/** + * @brief Most SSH connections will only ever request a single session, but an + * attacker may abuse a running ssh client to surreptitiously open + * additional sessions under their control. OpenSSH provides a global + * request "no-more-sessions@openssh.com" to mitigate this attack. + * + * @param[in] session The SSH session to use. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN, if the session is in nonblocking mode, + * and call must be done again. + */ +int ssh_request_no_more_sessions(ssh_session session) +{ + if (session == NULL) { + return SSH_ERROR; + } + + return ssh_global_request(session, "no-more-sessions@openssh.com", NULL, 1); +} + +/** + * @brief Add disconnect message when ssh_session is disconnected + * To add a disconnect message to give peer a better hint. + * @param session The SSH session to use. + * @param message The message to send after the session is disconnected. + * If no message is passed then a default message i.e + * "Bye Bye" will be sent. + */ +int +ssh_session_set_disconnect_message(ssh_session session, const char *message) +{ + if (session == NULL) { + return SSH_ERROR; + } + + if (message == NULL || strlen(message) == 0) { + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; + } + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup(message); + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Disconnect from a session (client or server). + * + * The session can then be reused to open a new session. + * + * @note Note that this function won't close the socket if it was set with + * ssh_options_set and SSH_OPTIONS_FD. You're responsible for closing the + * socket. This is new behavior in libssh 0.10. + * + * @param[in] session The SSH session to use. + */ +void +ssh_disconnect(ssh_session session) +{ + struct ssh_iterator *it = NULL; + int rc; + + if (session == NULL) { + return; + } + +#ifndef _WIN32 +#ifdef HAVE_PTHREAD + /* Only send the disconnect to all other threads when the root session calls + * ssh_disconnect() */ + if (session->proxy_root) { + proxy_disconnect = 1; + } +#endif /* HAVE_PTHREAD */ +#endif /* _WIN32 */ + + if (session->disconnect_message == NULL) { + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + goto error; + } + } + + if (session->socket != NULL && ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bdss", + SSH2_MSG_DISCONNECT, + SSH2_DISCONNECT_BY_APPLICATION, + session->disconnect_message, + ""); /* language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + ssh_packet_send(session); + ssh_session_socket_close(session); + } + +error: + session->recv_seq = 0; + session->send_seq = 0; + session->alive = 0; + if (session->socket != NULL){ + ssh_socket_reset(session->socket); + } + session->opts.fd = SSH_INVALID_SOCKET; + session->session_state = SSH_SESSION_STATE_DISCONNECTED; + session->pending_call_state = SSH_PENDING_CALL_NONE; + session->packet_state = PACKET_STATE_INIT; + + while ((it = ssh_list_get_iterator(session->channels)) != NULL) { + ssh_channel_do_free(ssh_iterator_value(ssh_channel, it)); + ssh_list_remove(session->channels, it); + } + if (session->current_crypto) { + crypto_free(session->current_crypto); + session->current_crypto = NULL; + } + if (session->next_crypto) { + crypto_free(session->next_crypto); + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + ssh_set_error_oom(session); + } + } + if (session->in_buffer) { + ssh_buffer_reinit(session->in_buffer); + } + if (session->out_buffer) { + ssh_buffer_reinit(session->out_buffer); + } + if (session->in_hashbuf) { + ssh_buffer_reinit(session->in_hashbuf); + } + if (session->out_hashbuf) { + ssh_buffer_reinit(session->out_hashbuf); + } + session->auth.supported_methods = 0; + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + SAFE_FREE(session->disconnect_message); + + if (session->ssh_message_list) { + ssh_message msg = NULL; + + while ((msg = ssh_list_pop_head(ssh_message, + session->ssh_message_list)) != NULL) { + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + session->ssh_message_list = NULL; + } + + if (session->packet_callbacks) { + ssh_list_free(session->packet_callbacks); + session->packet_callbacks = NULL; + } +} + +/** + * @brief Copyright information + * + * Returns copyright information + * + * @returns SSH_STRING copyright + */ +const char *ssh_copyright(void) +{ + return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2026 " + "Aris Adamantiadis, Andreas Schneider " + "and libssh contributors. " + "Distributed under the LGPL, please refer to COPYING " + "file for information about your rights"; +} +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/config.c b/src/libs/libssh-0.12.2/src/config.c new file mode 100644 index 000000000000..84fb12384426 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/config.c @@ -0,0 +1,1790 @@ +/* + * config.c - parse the ssh config file + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#ifdef HAVE_GLOB_H +# include +#endif +#include +#include +#ifndef _WIN32 +# include +# include +# include +# include +# include +# include +# include +# include +#endif +#ifdef HAVE_IFADDRS_H +#include +#endif + +#include "libssh/config_parser.h" +#include "libssh/config.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/options.h" + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 1024 +#endif + +struct ssh_config_keyword_table_s { + const char *name; + enum ssh_config_opcode_e opcode; + bool cli_supported; +}; + +static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { + {"host", SOC_HOST, true}, + {"match", SOC_MATCH, false}, + {"hostname", SOC_HOSTNAME, true}, + {"port", SOC_PORT, true}, + {"user", SOC_USERNAME, true}, + {"identityfile", SOC_IDENTITY, true}, + {"ciphers", SOC_CIPHERS, true}, + {"macs", SOC_MACS, true}, + {"compression", SOC_COMPRESSION, true}, + {"connecttimeout", SOC_TIMEOUT, true}, + {"stricthostkeychecking", SOC_STRICTHOSTKEYCHECK, true}, + {"userknownhostsfile", SOC_KNOWNHOSTS, true}, + {"proxycommand", SOC_PROXYCOMMAND, true}, + {"gssapiserveridentity", SOC_GSSAPISERVERIDENTITY, false}, + {"gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY, false}, + {"gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS, true}, + {"include", SOC_INCLUDE, true}, + {"bindaddress", SOC_BINDADDRESS, true}, + {"globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE, true}, + {"loglevel", SOC_LOGLEVEL, true}, + {"hostkeyalgorithms", SOC_HOSTKEYALGORITHMS, true}, + {"kexalgorithms", SOC_KEXALGORITHMS, true}, + {"gssapiauthentication", SOC_GSSAPIAUTHENTICATION, true}, + {"kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION, true}, + {"passwordauthentication", SOC_PASSWORDAUTHENTICATION, true}, + {"pubkeyauthentication", SOC_PUBKEYAUTHENTICATION, true}, + {"addkeystoagent", SOC_UNSUPPORTED, true}, + {"addressfamily", SOC_ADDRESSFAMILY, true}, + {"batchmode", SOC_UNSUPPORTED, true}, + {"canonicaldomains", SOC_UNSUPPORTED, true}, + {"canonicalizefallbacklocal", SOC_UNSUPPORTED, true}, + {"canonicalizehostname", SOC_UNSUPPORTED, true}, + {"canonicalizemaxdots", SOC_UNSUPPORTED, true}, + {"canonicalizepermittedcnames", SOC_UNSUPPORTED, true}, + {"certificatefile", SOC_CERTIFICATE, true}, + {"kbdinteractiveauthentication", SOC_UNSUPPORTED, true}, + {"checkhostip", SOC_UNSUPPORTED, true}, + {"connectionattempts", SOC_UNSUPPORTED, true}, + {"enablesshkeysign", SOC_UNSUPPORTED, true}, + {"fingerprinthash", SOC_UNSUPPORTED, true}, + {"forwardagent", SOC_UNSUPPORTED, true}, + {"hashknownhosts", SOC_UNSUPPORTED, true}, + {"hostbasedauthentication", SOC_UNSUPPORTED, true}, + {"hostbasedacceptedalgorithms", SOC_UNSUPPORTED, true}, + {"hostkeyalias", SOC_UNSUPPORTED, true}, + {"identitiesonly", SOC_IDENTITIESONLY, true}, + {"identityagent", SOC_IDENTITYAGENT, true}, + {"ipqos", SOC_UNSUPPORTED, true}, + {"kbdinteractivedevices", SOC_UNSUPPORTED, true}, + {"nohostauthenticationforlocalhost", SOC_UNSUPPORTED, true}, + {"numberofpasswordprompts", SOC_UNSUPPORTED, true}, + {"pkcs11provider", SOC_UNSUPPORTED, true}, + {"preferredauthentications", SOC_UNSUPPORTED, true}, + {"proxyjump", SOC_PROXYJUMP, true}, + {"proxyusefdpass", SOC_UNSUPPORTED, true}, + {"pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES, true}, + {"rekeylimit", SOC_REKEYLIMIT, true}, + {"remotecommand", SOC_UNSUPPORTED, true}, + {"revokedhostkeys", SOC_UNSUPPORTED, true}, + {"serveralivecountmax", SOC_UNSUPPORTED, true}, + {"serveraliveinterval", SOC_UNSUPPORTED, true}, + {"streamlocalbindmask", SOC_UNSUPPORTED, true}, + {"streamlocalbindunlink", SOC_UNSUPPORTED, true}, + {"syslogfacility", SOC_UNSUPPORTED, true}, + {"tcpkeepalive", SOC_UNSUPPORTED, true}, + {"updatehostkeys", SOC_UNSUPPORTED, true}, + {"verifyhostkeydns", SOC_UNSUPPORTED, true}, + {"visualhostkey", SOC_UNSUPPORTED, true}, + {"clearallforwardings", SOC_NA, true}, + {"controlmaster", SOC_NA, true}, + {"controlpersist", SOC_NA, true}, + {"controlpath", SOC_NA, true}, + {"dynamicforward", SOC_NA, true}, + {"escapechar", SOC_NA, true}, + {"exitonforwardfailure", SOC_NA, true}, + {"forwardx11", SOC_NA, true}, + {"forwardx11timeout", SOC_NA, true}, + {"forwardx11trusted", SOC_NA, true}, + {"gatewayports", SOC_NA, true}, + {"ignoreunknown", SOC_NA, true}, + {"localcommand", SOC_NA, true}, + {"localforward", SOC_NA, true}, + {"permitlocalcommand", SOC_NA, true}, + {"remoteforward", SOC_NA, true}, + {"requesttty", SOC_NA, true}, + {"sendenv", SOC_NA, true}, + {"tunnel", SOC_NA, true}, + {"tunneldevice", SOC_NA, true}, + {"xauthlocation", SOC_NA, true}, + {"pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES, true}, + {"requiredrsasize", SOC_REQUIRED_RSA_SIZE, true}, + {"gssapikeyexchange", SOC_GSSAPIKEYEXCHANGE, true}, + {"gssapikexalgorithms", SOC_GSSAPIKEXALGORITHMS, true}, + {NULL, SOC_UNKNOWN, false}, +}; + +enum ssh_config_match_e { + MATCH_UNKNOWN = -1, + MATCH_ALL, + MATCH_FINAL, + MATCH_CANONICAL, + MATCH_EXEC, + MATCH_HOST, + MATCH_ORIGINALHOST, + MATCH_USER, + MATCH_LOCALUSER, + MATCH_LOCALNETWORK +}; + +struct ssh_config_match_keyword_table_s { + const char *name; + enum ssh_config_match_e opcode; +}; + +static struct ssh_config_match_keyword_table_s + ssh_config_match_keyword_table[] = { + {"all", MATCH_ALL}, + {"canonical", MATCH_CANONICAL}, + {"final", MATCH_FINAL}, + {"exec", MATCH_EXEC}, + {"host", MATCH_HOST}, + {"originalhost", MATCH_ORIGINALHOST}, + {"user", MATCH_USER}, + {"localuser", MATCH_LOCALUSER}, + {"localnetwork", MATCH_LOCALNETWORK}, + {NULL, MATCH_UNKNOWN}, +}; + +int ssh_config_parse_line(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global); + +static int ssh_config_parse_line_internal(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global, + bool is_cli, + bool fail_on_unknown); + +int ssh_config_parse_line_cli(ssh_session session, const char *line); + +enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) +{ + int i; + + for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) { + return ssh_config_keyword_table[i].opcode; + } + } + + return SOC_UNKNOWN; +} + +static bool ssh_config_is_cli_supported(enum ssh_config_opcode_e opcode) +{ + int i; + + for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) { + if (opcode == ssh_config_keyword_table[i].opcode) { + return ssh_config_keyword_table[i].cli_supported; + } + } + + return false; +} + +#define LIBSSH_CONF_MAX_DEPTH 16 +static void +local_parse_file(ssh_session session, + const char *filename, + int *parsing, + unsigned int depth, + bool global) +{ + FILE *f = NULL; + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + int rv; + + if (depth > LIBSSH_CONF_MAX_DEPTH) { + ssh_set_error(session, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { + /* The underlying function logs the reasons */ + return; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename); + while (fgets(line, sizeof(line), f)) { + count++; + rv = ssh_config_parse_line(session, line, count, parsing, depth, global); + if (rv < 0) { + fclose(f); + return; + } + } + + fclose(f); + return; +} + +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) +static void local_parse_glob(ssh_session session, + const char *fileglob, + int *parsing, + unsigned int depth, + bool global) +{ + glob_t globbuf = { + .gl_flags = 0, + }; + int rt; + size_t i; + + rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf); + if (rt == GLOB_NOMATCH) { + globfree(&globbuf); + return; + } else if (rt != 0) { + SSH_LOG(SSH_LOG_RARE, "Glob error: %s", + fileglob); + globfree(&globbuf); + return; + } + + for (i = 0; i < globbuf.gl_pathc; i++) { + local_parse_file(session, globbuf.gl_pathv[i], parsing, depth, global); + } + + globfree(&globbuf); +} +#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */ + +static enum ssh_config_match_e +ssh_config_get_match_opcode(const char *keyword) +{ + size_t i; + + for (i = 0; ssh_config_match_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_config_match_keyword_table[i].name) == 0) { + return ssh_config_match_keyword_table[i].opcode; + } + } + + return MATCH_UNKNOWN; +} + +static int +ssh_config_match(char *value, const char *pattern, bool negate) +{ + int ok, result = 0; + + ok = match_pattern_list(value, pattern, strlen(pattern), 0); + if (ok <= 0 && negate == true) { + result = 1; + } else if (ok > 0 && negate == false) { + result = 1; + } + SSH_LOG(SSH_LOG_TRACE, "%s '%s' against pattern '%s'%s (ok=%d)", + result == 1 ? "Matched" : "Not matched", value, pattern, + negate == true ? " (negated)" : "", ok); + return result; +} + +#ifdef WITH_EXEC +/* FIXME reuse the ssh_execute_command() from socket.c */ +static int +ssh_exec_shell(char *cmd) +{ + char *shell = NULL; + pid_t pid; + int status, devnull, rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + shell = getenv("SHELL"); + if (shell == NULL || shell[0] == '\0') { + shell = (char *)"/bin/sh"; + } + + rc = access(shell, X_OK); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, "The shell '%s' is not executable", shell); + return -1; + } + + /* Need this to redirect subprocess stdin/out */ + devnull = open("/dev/null", O_RDWR); + if (devnull == -1) { + SSH_LOG(SSH_LOG_WARN, "Failed to open(/dev/null): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + SSH_LOG(SSH_LOG_DEBUG, "Running command '%s'", cmd); + pid = fork(); + if (pid == 0) { /* Child */ + char *argv[4]; + + /* Redirect child stdin and stdout. Leave stderr */ + rc = dup2(devnull, STDIN_FILENO); + if (rc == -1) { + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + exit(1); + } + rc = dup2(devnull, STDOUT_FILENO); + if (rc == -1) { + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + exit(1); + } + if (devnull > STDERR_FILENO) { + close(devnull); + } + + argv[0] = shell; + argv[1] = (char *) "-c"; + argv[2] = strdup(cmd); + argv[3] = NULL; + + rc = execv(argv[0], argv); + if (rc == -1) { + SSH_LOG(SSH_LOG_WARN, "Failed to execute command '%s': %s", cmd, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + /* Die with signal to make this error apparent to parent. */ + signal(SIGTERM, SIG_DFL); + kill(getpid(), SIGTERM); + _exit(1); + } + } + + /* Parent */ + close(devnull); + if (pid == -1) { /* Error */ + SSH_LOG(SSH_LOG_WARN, "Failed to fork child: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + + } + + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + } + if (!WIFEXITED(status)) { + SSH_LOG(SSH_LOG_WARN, "Command %s exited abnormally", cmd); + return -1; + } + SSH_LOG(SSH_LOG_TRACE, "Command '%s' returned %d", cmd, WEXITSTATUS(status)); + return WEXITSTATUS(status); +} + +static int +ssh_match_exec(ssh_session session, const char *command, bool negate) +{ + int rv, result = 0; + char *cmd = NULL; + + /* TODO There should be more supported expansions */ + cmd = ssh_path_expand_escape(session, command); + if (cmd == NULL) { + return 0; + } + rv = ssh_exec_shell(cmd); + if (rv > 0 && negate == true) { + result = 1; + } else if (rv == 0 && negate == false) { + result = 1; + } + SSH_LOG(SSH_LOG_TRACE, "%s 'exec' command '%s'%s (rv=%d)", + result == 1 ? "Matched" : "Not matched", cmd, + negate == true ? " (negated)" : "", rv); + free(cmd); + return result; +} +#else +static int +ssh_match_exec(ssh_session session, const char *command, bool negate) +{ + (void)session; + (void)command; + (void)negate; + + SSH_LOG(SSH_LOG_TRACE, + "Unsupported 'exec' command on Windows '%s'", + command); + return 0; +} +#endif /* WITH_EXEC */ + +/** + * @brief: Parse the ProxyJump configuration line and if parsing, + * stores the result in the configuration option + * + * @param[in] session The ssh session + * @param[in] s The string to be parsed. + * @param[in] do_parsing Whether to parse or not. + * + * @returns SSH_OK if the provided string is formatted and parsed correctly + * SSH_ERROR on failure + */ +int +ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) +{ + char *c = NULL, *cp = NULL, *endp = NULL; + char *username = NULL; + char *hostname = NULL; + char *port = NULL; + char *next = NULL; + int cmp, rv = SSH_ERROR; + struct ssh_jump_info_struct *jump_host = NULL; + bool parse_entry = do_parsing; + bool libssh_proxy_jump = ssh_libssh_proxy_jumps(); + + if (do_parsing) { + SAFE_FREE(session->opts.proxy_jumps_str); + ssh_proxyjumps_free(session->opts.proxy_jumps); + } + /* Special value none disables the proxy */ + cmp = strcasecmp(s, "none"); + if (cmp == 0) { + if (!libssh_proxy_jump && do_parsing) { + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s); + } + return SSH_OK; + } + + /* This is comma-separated list of [user@]host[:port] entries */ + c = strdup(s); + if (c == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (do_parsing) { + /* Store the whole string in session */ + SAFE_FREE(session->opts.proxy_jumps_str); + session->opts.proxy_jumps_str = strdup(s); + if (session->opts.proxy_jumps_str == NULL) { + free(c); + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + + cp = c; + do { + endp = strchr(cp, ','); + if (endp != NULL) { + /* Split out the token */ + *endp = '\0'; + } + if (parse_entry && libssh_proxy_jump) { + jump_host = calloc(1, sizeof(struct ssh_jump_info_struct)); + if (jump_host == NULL) { + ssh_set_error_oom(session); + rv = SSH_ERROR; + goto out; + } + + rv = ssh_config_parse_uri(cp, + &jump_host->username, + &jump_host->hostname, + &port, + false); + if (rv != SSH_OK) { + ssh_set_error_invalid(session); + SAFE_FREE(jump_host); + goto out; + } + /* Leave the port at 0 when it is not given, so that the jump + * host's own configuration can supply it later. */ + if (port != NULL) { + jump_host->port = strtol(port, NULL, 10); + SAFE_FREE(port); + } + + /* Prepend because we will recursively proxy jump */ + rv = ssh_list_prepend(session->opts.proxy_jumps, jump_host); + if (rv != SSH_OK) { + ssh_set_error_oom(session); + SAFE_FREE(jump_host); + goto out; + } + } else if (parse_entry) { + /* We actually care only about the first item */ + rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false); + if (rv != SSH_OK) { + ssh_set_error_invalid(session); + goto out; + } + /* The rest of the list needs to be passed on */ + if (endp != NULL) { + next = strdup(endp + 1); + if (next == NULL) { + ssh_set_error_oom(session); + rv = SSH_ERROR; + goto out; + } + } + } else { + /* The rest is just sanity-checked to avoid failures later */ + rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false); + if (rv != SSH_OK) { + ssh_set_error_invalid(session); + goto out; + } + } + if (!libssh_proxy_jump) { + parse_entry = 0; + } + if (endp != NULL) { + cp = endp + 1; + } else { + cp = NULL; /* end */ + } + } while (cp != NULL); + + if (!libssh_proxy_jump && hostname != NULL && do_parsing) { + char com[512] = {0}; + + rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W '[%%h]:%%p' %s", + username ? " -l " : "", + username ? username : "", + port ? " -p " : "", + port ? port : "", + next ? " -J " : "", + next ? next : "", + hostname); + if (rv < 0 || rv >= (int)sizeof(com)) { + SSH_LOG(SSH_LOG_TRACE, "Too long ProxyJump configuration line"); + rv = SSH_ERROR; + goto out; + } + rv = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com); + if (rv != SSH_OK) { + ssh_set_error_oom(session); + goto out; + } + } + + rv = SSH_OK; + +out: + if (rv != SSH_OK) { + ssh_proxyjumps_free(session->opts.proxy_jumps); + } + SAFE_FREE(username); + SAFE_FREE(hostname); + SAFE_FREE(port); + SAFE_FREE(next); + SAFE_FREE(c); + return rv; +} + +static char * +ssh_config_make_absolute(ssh_session session, + const char *path, + bool global) +{ + size_t outlen = 0; + char *out = NULL; + int rv; + + /* Looks like absolute path */ + if (path[0] == '/') { + return strdup(path); + } + + /* relative path */ + if (global) { + /* Parsing global config */ + outlen = strlen(path) + strlen("/etc/ssh/") + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "/etc/ssh/%s", path); + if (rv < 1) { + free(out); + return NULL; + } + return out; + } + + /* paths starting with tilde are already absolute */ + if (path[0] == '~') { + return ssh_path_expand_tilde(path); + } + + /* Parsing user config relative to home directory (generally ~/.ssh) */ + if (session->opts.sshdir == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + outlen = strlen(path) + strlen(session->opts.sshdir) + 1 + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "%s/%s", session->opts.sshdir, path); + if (rv < 1) { + free(out); + return NULL; + } + return out; +} + +#ifdef HAVE_IFADDRS_H +/** + * @brief Checks if host address matches the local network specified. + * + * Verify whether a local network interface address matches any of the CIDR + * patterns. + * + * @param addrlist The CIDR pattern-list to be checked, can contain both + * IPv4 and IPv6 addresses and has to be comma separated + * (',' only, space after comma not allowed). + * + * @param negate The negate condition. The return value is negated + * (returns 1 instead of 0 and vice versa). + * + * @return 1 if match found. + * @return 0 if no match found. + * @return -1 on errors. + */ +static int +ssh_match_localnetwork(const char *addrlist, bool negate) +{ + struct ifaddrs *ifa = NULL, *ifaddrs = NULL; + int r, found = 0; + char address[NI_MAXHOST], err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socklen_t sa_len; + + r = getifaddrs(&ifaddrs); + if (r != 0) { + SSH_LOG(SSH_LOG_WARN, + "Match localnetwork: getifaddrs() failed: %s", + ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) { + continue; + } + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + sa_len = sizeof(struct sockaddr_in6); + break; + default: + SSH_LOG(SSH_LOG_TRACE, + "Interface %s: unsupported address family %d", + ifa->ifa_name, + ifa->ifa_addr->sa_family); + continue; + } + + r = getnameinfo(ifa->ifa_addr, + sa_len, + address, + sizeof(address), + NULL, + 0, + NI_NUMERICHOST); + if (r != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Interface %s getnameinfo failed: %s", + ifa->ifa_name, + gai_strerror(r)); + continue; + } + SSH_LOG(SSH_LOG_TRACE, + "Interface %s address %s", + ifa->ifa_name, + address); + + r = match_cidr_address_list(address, + addrlist, + ifa->ifa_addr->sa_family); + if (r == 1) { + SSH_LOG(SSH_LOG_TRACE, + "Matched interface %s: address %s in %s", + ifa->ifa_name, + address, + addrlist); + found = 1; + break; + } + } + + freeifaddrs(ifaddrs); + + return (found == (negate ? 0 : 1)); +} +#endif /* HAVE_IFADDRS_H */ + +static enum ssh_options_e +ssh_config_get_auth_option(enum ssh_config_opcode_e opcode) +{ + struct auth_option_map { + enum ssh_config_opcode_e opcode; + const char *name; + enum ssh_options_e option; + }; + + static struct auth_option_map auth_options[] = { + { + SOC_GSSAPIAUTHENTICATION, + "GSSAPIAuthentication", + SSH_OPTIONS_GSSAPI_AUTH, + }, + { + SOC_KBDINTERACTIVEAUTHENTICATION, + "KbdInteractiveAuthentication", + SSH_OPTIONS_KBDINT_AUTH, + }, + { + SOC_PASSWORDAUTHENTICATION, + "PasswordAuthentication", + SSH_OPTIONS_PASSWORD_AUTH, + }, + { + SOC_PUBKEYAUTHENTICATION, + "PubkeyAuthentication", + SSH_OPTIONS_PUBKEY_AUTH, + }, + {0, NULL, 0}, + }; + + for (struct auth_option_map *map = auth_options; map->name != NULL; map++) { + if (map->opcode == opcode) { + return map->option; + } + } + return -1; +} + +#define CHECK_COND_OR_FAIL(cond, error_message) \ + if ((cond)) { \ + SSH_LOG(SSH_LOG_DEBUG, \ + "line %d: %s: %s", \ + count, \ + error_message, \ + keyword); \ + if (fail_on_unknown) { \ + ssh_set_error(session, \ + SSH_FATAL, \ + is_cli ? "%s '%s' value on CLI" \ + : "%s '%s' value at line %d", \ + error_message, \ + keyword, \ + is_cli ? 0 : count); \ + SAFE_FREE(x); \ + return SSH_ERROR; \ + } \ + break; \ + } + +static int ssh_config_parse_line_internal(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global, + bool is_cli, + bool fail_on_unknown) +{ + enum ssh_config_opcode_e opcode; + const char *p = NULL, *p2 = NULL; + char *s = NULL, *x = NULL; + char *keyword = NULL; + char *lowerhost = NULL; + size_t len; + int i, rv; + uint8_t *seen = session->opts.options_seen; + long l; + int64_t ll; + + /* Ignore empty lines */ + if (line == NULL || *line == '\0') { + if (is_cli) { + return SSH_ERROR; + } + return 0; + } + + x = s = strdup(line); + if (s == NULL) { + ssh_set_error_oom(session); + return -1; + } + + /* Remove trailing spaces */ + for (len = strlen(s) - 1; len > 0; len--) { + if (! isspace(s[len])) { + break; + } + s[len] = '\0'; + } + + keyword = ssh_config_get_token(&s); + if (keyword == NULL || *keyword == '#' || + *keyword == '\0' || *keyword == '\n') { + SAFE_FREE(x); + return 0; + } + + opcode = ssh_config_get_opcode(keyword); + if (is_cli && !ssh_config_is_cli_supported(opcode)) { + ssh_set_error( + session, + SSH_FATAL, + "Option '%s' is not supported in command-line configuration", + keyword); + SAFE_FREE(x); + return SSH_ERROR; + } + + if (*parsing == 1 && + opcode != SOC_HOST && + opcode != SOC_MATCH && + opcode != SOC_INCLUDE && + opcode != SOC_IDENTITY && + opcode != SOC_CERTIFICATE && + opcode > SOC_UNSUPPORTED && + opcode < SOC_MAX) { /* Ignore all unknown types here */ + /* Skip all the options that were already applied */ + if (seen[opcode] != 0) { + SAFE_FREE(x); + return 0; + } + seen[opcode] = 1; + } + + switch (opcode) { + case SOC_INCLUDE: /* recursive include of other files */ + + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + char *path = ssh_config_make_absolute(session, p, global); + if (path == NULL) { + SSH_LOG(SSH_LOG_WARN, "line %d: Failed to allocate memory " + "for the include path expansion", count); + SAFE_FREE(x); + return -1; + } +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + local_parse_glob(session, path, parsing, depth + 1, global); +#else + local_parse_file(session, path, parsing, depth + 1, global); +#endif /* HAVE_GLOB */ + free(path); + } + break; + + case SOC_MATCH: { + bool negate; + int result = 1; + size_t args = 0; + enum ssh_config_match_e opt; + char *localuser = NULL; + + *parsing = 0; + do { + p = p2 = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + break; + } + args++; + SSH_LOG(SSH_LOG_DEBUG, "line %d: Processing Match keyword '%s'", + count, p); + + /* If the option is prefixed with ! the result should be negated */ + negate = false; + if (p[0] == '!') { + negate = true; + p++; + } + + opt = ssh_config_get_match_opcode(p); + switch (opt) { + case MATCH_ALL: + p = ssh_config_get_str_tok(&s, NULL); + if (args <= 2 && (p == NULL || p[0] == '\0')) { + /* The first or second, but last argument. The "all" keyword + * can be prefixed with either "final" or "canonical" + * keywords which do not have any effect here. */ + if (negate == true) { + result = 0; + } + break; + } + + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match all cannot be combined with " + "other Match attributes", count); + SAFE_FREE(x); + return -1; + + case MATCH_FINAL: + case MATCH_CANONICAL: + SSH_LOG(SSH_LOG_DEBUG, + "line %d: Unsupported Match keyword '%s', skipping", + count, + p); + /* Not set any result here -- the result is dependent on the + * following matches after this keyword */ + break; + + case MATCH_EXEC: + /* Skip one argument (including in quotes) */ + p = ssh_config_get_token(&s); + if (p == NULL || p[0] == '\0') { + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " + "'%s' requires argument", count, p2); + SAFE_FREE(x); + return -1; + } + if (result != 1) { + SSH_LOG(SSH_LOG_DEBUG, "line %d: Skipped match exec " + "'%s' as previous conditions already failed.", + count, p2); + continue; + } + result &= ssh_match_exec(session, p, negate); + args++; + break; + + case MATCH_LOCALUSER: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Match localuser keyword " + "requires argument", + count); + SAFE_FREE(x); + return -1; + } + localuser = ssh_get_local_username(); + if (localuser == NULL) { + SSH_LOG(SSH_LOG_TRACE, "line %d: Can not get local username " + "for conditional matching.", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(localuser, p, negate); + SAFE_FREE(localuser); + args++; + break; + + case MATCH_ORIGINALHOST: + /* Skip one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " + "'%s' requires argument", count, p2); + SAFE_FREE(x); + return -1; + } + args++; + SSH_LOG(SSH_LOG_TRACE, + "line %d: Unsupported Match keyword '%s', ignoring", + count, + p2); + result = 0; + break; + + case MATCH_HOST: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match host keyword " + "requires argument", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(session->opts.host, p, negate); + args++; + break; + + case MATCH_USER: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match user keyword " + "requires argument", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(session->opts.username, p, negate); + args++; + break; + + case MATCH_LOCALNETWORK: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Match local network keyword" + "requires argument", + count); + SAFE_FREE(x); + return -1; + } +#ifdef HAVE_IFADDRS_H + rv = match_cidr_address_list(NULL, p, -1); + if (rv == -1) { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - List invalid entry: %s", + count, + p); + SAFE_FREE(x); + return -1; + } + rv = ssh_match_localnetwork(p, negate); + if (rv == -1) { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Error while retrieving " + "network interface information -" + " List entry: %s", + count, + p); + SAFE_FREE(x); + return -1; + } + + result &= rv; +#else /* HAVE_IFADDRS_H */ + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - match localnetwork " + "not supported on this platform", + count); + SAFE_FREE(x); + return -1; +#endif /* HAVE_IFADDRS_H */ + args++; + break; + + case MATCH_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, + "Unknown argument '%s' for Match keyword. Not matching", + p); + result = 0; + break; + } + } while (p != NULL && p[0] != '\0'); + if (args == 0) { + SSH_LOG(SSH_LOG_WARN, + "ERROR - Match keyword requires an argument. Not matching"); + result = 0; + } + *parsing = result; + break; + } + case SOC_HOST: { + int ok = 0, result = -1; + + *parsing = 0; + lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL; + for (p = ssh_config_get_str_tok(&s, NULL); + p != NULL && p[0] != '\0'; + p = ssh_config_get_str_tok(&s, NULL)) { + if (ok >= 0) { + ok = match_hostname(lowerhost, p, strlen(p)); + if (result == -1 && ok < 0) { + result = 0; + } else if (result == -1 && ok > 0) { + result = 1; + } + } + } + SAFE_FREE(lowerhost); + if (result != -1) { + *parsing = result; + } + break; + } + case SOC_HOSTNAME: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + char *z = ssh_path_expand_escape(session, p); + if (z == NULL) { + z = strdup(p); + } + ssh_options_set(session, SSH_OPTIONS_HOST, z); + free(z); + } + break; + case SOC_PORT: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_PORT_STR, p); + } + break; + case SOC_USERNAME: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_USER, p); + } + break; + case SOC_IDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p); + } + break; + case SOC_CIPHERS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p); + ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p); + } + break; + case SOC_MACS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p); + ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p); + } + break; + case SOC_COMPRESSION: + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + if (i) { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); + } else { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); + } + } + break; + case SOC_TIMEOUT: + l = ssh_config_get_long(&s, -1); + CHECK_COND_OR_FAIL(l < 0, "Invalid argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l); + } + break; + case SOC_STRICTHOSTKEYCHECK: + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i); + } + break; + case SOC_KNOWNHOSTS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p); + } + break; + case SOC_PROXYCOMMAND: + p = ssh_config_get_cmd(&s); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + /* We share the seen value with the ProxyJump */ + if (*parsing && !seen[SOC_PROXYJUMP]) { + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p); + } + break; + case SOC_PROXYJUMP: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SAFE_FREE(x); + return -1; + } + /* We share the seen value with the ProxyCommand */ + rv = ssh_config_parse_proxy_jump(session, + p, + (*parsing && !seen[SOC_PROXYCOMMAND])); + if (rv != SSH_OK) { + SAFE_FREE(x); + return -1; + } + break; + case SOC_GSSAPISERVERIDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p); + } + break; + case SOC_GSSAPICLIENTIDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p); + } + break; + case SOC_GSSAPIDELEGATECREDENTIALS: + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i); + } + break; + case SOC_BINDADDRESS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_BINDADDR, p); + } + break; + case SOC_GLOBALKNOWNHOSTSFILE: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p); + } + break; + case SOC_LOGLEVEL: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + int value = -1; + + if (strcasecmp(p, "quiet") == 0) { + value = SSH_LOG_NONE; + } else if (strcasecmp(p, "fatal") == 0 || + strcasecmp(p, "error")== 0) { + value = SSH_LOG_WARN; + } else if (strcasecmp(p, "verbose") == 0 || + strcasecmp(p, "info") == 0) { + value = SSH_LOG_INFO; + } else if (strcasecmp(p, "DEBUG") == 0 || + strcasecmp(p, "DEBUG1") == 0) { + value = SSH_LOG_DEBUG; + } else if (strcasecmp(p, "DEBUG2") == 0 || + strcasecmp(p, "DEBUG3") == 0) { + value = SSH_LOG_TRACE; + } + CHECK_COND_OR_FAIL(value == -1, "Invalid value"); + if (value != -1) { + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value); + } + } + break; + case SOC_HOSTKEYALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p); + } + break; + case SOC_PUBKEYACCEPTEDKEYTYPES: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p); + } + break; + case SOC_KEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p); + } + break; + case SOC_REKEYLIMIT: + /* Parse the data limit */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + CHECK_COND_OR_FAIL(1, "Missing data limit"); + break; + } else if (strcmp(p, "default") == 0) { + /* Default rekey limits enforced automatically */ + ll = 0; + } else { + char *endp = NULL; + ll = strtoll(p, &endp, 10); + if (p == endp || ll < 0) { + CHECK_COND_OR_FAIL(1, "Invalid data limit"); + break; + } + switch (*endp) { + case 'G': + if (ll > LLONG_MAX / 1024) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 1024; + FALL_THROUGH; + case 'M': + if (ll > LLONG_MAX / 1024) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 1024; + FALL_THROUGH; + case 'K': + if (ll > LLONG_MAX / 1024) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 1024; + endp++; + FALL_THROUGH; + case '\0': + /* just the number */ + break; + default: + /* Invalid suffix */ + ll = -1; + break; + } + if (*endp != ' ' && *endp != '\0') { + CHECK_COND_OR_FAIL(1, "Invalid trailing characters"); + break; + } + } + CHECK_COND_OR_FAIL(ll < 0, "Invalid data limit"); + if (*parsing) { + uint64_t v = (uint64_t)ll; + ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v); + } + /* Parse the time limit */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + CHECK_COND_OR_FAIL(1, "Missing time limit"); + break; + } else if (strcmp(p, "none") == 0) { + ll = 0; + } else { + char *endp = NULL; + ll = strtoll(p, &endp, 10); + if (p == endp || ll < 0) { + /* No number or negative */ + CHECK_COND_OR_FAIL(1, "Invalid time limit"); + break; + } + switch (*endp) { + case 'w': + case 'W': + if (ll > LLONG_MAX / 7) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 7; + FALL_THROUGH; + case 'd': + case 'D': + if (ll > LLONG_MAX / 24) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 24; + FALL_THROUGH; + case 'h': + case 'H': + if (ll > LLONG_MAX / 60) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 60; + FALL_THROUGH; + case 'm': + case 'M': + if (ll > LLONG_MAX / 60) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 60; + FALL_THROUGH; + case 's': + case 'S': + endp++; + FALL_THROUGH; + case '\0': + /* just the number */ + break; + default: + /* Invalid suffix */ + ll = -1; + break; + } + if (*endp != '\0') { + CHECK_COND_OR_FAIL(1, "Invalid trailing characters"); + break; + } + } + CHECK_COND_OR_FAIL(ll < 0, "Invalid time limit"); + if (ll > -1 && *parsing) { + uint32_t v = (uint32_t)ll; + ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v); + } + break; + case SOC_GSSAPIAUTHENTICATION: + case SOC_KBDINTERACTIVEAUTHENTICATION: + case SOC_PASSWORDAUTHENTICATION: + case SOC_PUBKEYAUTHENTICATION: { + enum ssh_options_e option = ssh_config_get_auth_option(opcode); + i = ssh_config_get_yesno(&s, 0); + + CHECK_COND_OR_FAIL(i < 0, "Authentication option"); + if (*parsing) { + ssh_options_set(session, option, &i); + } + break; + } + case SOC_NA: + CHECK_COND_OR_FAIL(1, "Unapplicable option"); + break; + case SOC_UNSUPPORTED: + CHECK_COND_OR_FAIL(1, "Unsupported option"); + break; + case SOC_UNKNOWN: + CHECK_COND_OR_FAIL(1, "Unknown option"); + break; + case SOC_IDENTITYAGENT: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p); + } + break; + case SOC_IDENTITIESONLY: + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + bool b = i; + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b); + } + break; + case SOC_CONTROLMASTER: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "ControlMaster"); + if (*parsing) { + int value = -1; + + if (strcasecmp(p, "auto") == 0) { + value = SSH_CONTROL_MASTER_AUTO; + } else if (strcasecmp(p, "yes") == 0) { + value = SSH_CONTROL_MASTER_YES; + } else if (strcasecmp(p, "no") == 0) { + value = SSH_CONTROL_MASTER_NO; + } else if (strcasecmp(p, "autoask") == 0) { + value = SSH_CONTROL_MASTER_AUTOASK; + } else if (strcasecmp(p, "ask") == 0) { + value = SSH_CONTROL_MASTER_ASK; + } + + CHECK_COND_OR_FAIL(value == -1, "Invalid argument"); + if (value != -1) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value); + } + } + break; + case SOC_CONTROLPATH: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SAFE_FREE(x); + return -1; + } + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p); + } + break; + case SOC_CERTIFICATE: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p); + } + break; + case SOC_GSSAPIKEYEXCHANGE: { + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + bool b = (i == 1) ? true : false; + ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &b); + } + break; + } + case SOC_GSSAPIKEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, p); + } + break; + case SOC_REQUIRED_RSA_SIZE: + l = ssh_config_get_long(&s, -1); + CHECK_COND_OR_FAIL(l < 0 || l > INT_MAX, "Invalid argument"); + if (*parsing) { + i = (int)l; + ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &i); + } + break; + case SOC_ADDRESSFAMILY: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "line %d: no argument after keyword \"addressfamily\"", + count); + SAFE_FREE(x); + return SSH_ERROR; + } + if (*parsing) { + int value = -1; + + if (strcasecmp(p, "any") == 0) { + value = SSH_ADDRESS_FAMILY_ANY; + } else if (strcasecmp(p, "inet") == 0) { + value = SSH_ADDRESS_FAMILY_INET; + } else if (strcasecmp(p, "inet6") == 0) { + value = SSH_ADDRESS_FAMILY_INET6; + } else { + SSH_LOG(SSH_LOG_WARNING, + "line %d: invalid argument \"%s\"", + count, + p); + SAFE_FREE(x); + return SSH_ERROR; + } + ssh_options_set(session, SSH_OPTIONS_ADDRESS_FAMILY, &value); + } + break; + default: + ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d", + opcode); + SAFE_FREE(x); + return -1; + break; + } + + SAFE_FREE(x); + return 0; +} + +#undef CHECK_COND_OR_FAIL + +int ssh_config_parse_line(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global) +{ + return ssh_config_parse_line_internal(session, + line, + count, + parsing, + depth, + global, + false, + false); +} + +int ssh_config_parse_line_cli(ssh_session session, const char *line) +{ + int parsing = 1; + return ssh_config_parse_line_internal(session, + line, + 0, + &parsing, + 0, + false, + true, + true); +} + +/* @brief Parse configuration from a file pointer + * + * @params[in] session The ssh session + * @params[in] fp A valid file pointer + * @params[in] global Whether the config is global or not + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_config_parse(ssh_session session, FILE *fp, bool global) +{ + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + int parsing, rv; + + parsing = 1; + while (fgets(line, sizeof(line), fp)) { + count++; + rv = ssh_config_parse_line(session, line, count, &parsing, 0, global); + if (rv < 0) { + return -1; + } + } + + return 0; +} + +/* @brief Parse configuration file and set the options to the given session + * + * @params[in] session The ssh session + * @params[in] filename The path to the ssh configuration file + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_config_parse_file(ssh_session session, const char *filename) +{ + FILE *fp = NULL; + int rv; + bool global = 0; + + fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (fp == NULL) { + /* The underlying function logs the reasons */ + return 0; + } + + rv = strcmp(filename, GLOBAL_CLIENT_CONFIG); +#ifdef USR_GLOBAL_CLIENT_CONFIG + if (rv != 0) { + rv = strcmp(filename, USR_GLOBAL_CLIENT_CONFIG); + } +#endif + + if (rv == 0) { + global = true; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename); + + rv = ssh_config_parse(session, fp, global); + + fclose(fp); + return rv; +} + +/* @brief Parse configuration string and set the options to the given session + * + * @params[in] session The ssh session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_config_parse_string(ssh_session session, const char *input) +{ + char line[MAX_LINE_SIZE] = {0}; + const char *c = input, *line_start = input; + unsigned int line_num = 0; + size_t line_len; + int parsing, rv; + + SSH_LOG(SSH_LOG_DEBUG, "Reading configuration data from string:"); + SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input); + + parsing = 1; + while (1) { + line_num++; + line_start = c; + c = strchr(line_start, '\n'); + if (c == NULL) { + /* if there is no newline at the end of the string */ + c = strchr(line_start, '\0'); + } + if (c == NULL) { + /* should not happen, would mean a string without trailing '\0' */ + SSH_LOG(SSH_LOG_TRACE, "No trailing '\\0' in config string"); + return SSH_ERROR; + } + line_len = c - line_start; + if (line_len > MAX_LINE_SIZE - 1) { + SSH_LOG(SSH_LOG_TRACE, + "Line %u too long: %zu characters", + line_num, + line_len); + return SSH_ERROR; + } + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); + rv = ssh_config_parse_line(session, line, line_num, &parsing, 0, false); + if (rv < 0) { + return SSH_ERROR; + } + if (*c == '\0') { + break; + } + c++; + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/config.h b/src/libs/libssh-0.12.2/src/config.h new file mode 100644 index 000000000000..37186b0f1bd9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/config.h @@ -0,0 +1,334 @@ +/* Name of package */ +#define PACKAGE "libssh" + +/* Version number of package */ +#define VERSION "0.11.4" + +#define SYSCONFDIR "etc" +//#define BINARYDIR "C:/Src/libssh-0.9.5/build" +//#define SOURCEDIR "C:/Src/libssh-0.9.5" + +/* Global configuration directory */ +/* #undef USR_GLOBAL_CONF_DIR */ +#ifndef RT_OS_WINDOWS +#define GLOBAL_CONF_DIR "/etc/ssh" +#else +#define GLOBAL_CONF_DIR "C:/ProgramData/ssh" +#endif + +/* Global bind configuration file path */ +#define GLOBAL_BIND_CONFIG "/etc/ssh/libssh_server_config" + +/* Global client configuration file path */ +#define GLOBAL_CLIENT_CONFIG "/etc/ssh/ssh_config" + +/************************** HEADER FILES *************************/ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_ARGP_H */ + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_GLOB_H 1 +#endif +/* Define to 1 if you have the header file. */ +/* #undef HAVE_VALGRIND_VALGRIND_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_PTY_H */ + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_UTMP_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UTIL_H 1 +#endif + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LIBUTIL_H */ + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H +#endif + +/* Define to 1 if you have the header file. */ +//#define HAVE_SYS_UTIME_H 1 + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_IO_H 1 +#else +/* Define to 1 if you have the header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_OPENSSL_AES_H 1 + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_WSPIAPI_H 1 +#endif +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OPENSSL_BLOWFISH_H */ + +/* Define to 1 if you have the header file. */ +//#define HAVE_OPENSSL_DES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_OPENSSL_ECDH_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_OPENSSL_EC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_OPENSSL_ECDSA_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_PTHREAD_H */ + +/* Define to 1 if you have eliptic curve cryptography in openssl */ +#define HAVE_OPENSSL_ECC 1 + +/* Define to 1 if you have eliptic curve cryptography in gcrypt */ +/* #undef HAVE_GCRYPT_ECC */ + +/* Define to 1 if you have eliptic curve cryptography */ +#define HAVE_ECC 1 + +/* Define to 1 if you have DSA */ +#define HAVE_DSA 1 + +/* Define to 1 if you have gl_flags as a glob_t sturct member */ +/* #undef HAVE_GLOB_GL_FLAGS_MEMBER */ + +/* Define to 1 if you have OpenSSL with Ed25519 support */ +//#define HAVE_OPENSSL_ED25519 1 + +/* Define to 1 if you have OpenSSL with X25519 support */ +//#define HAVE_OPENSSL_X25519 1 + +/*************************** FUNCTIONS ***************************/ + +/* Define to 1 if you have the `EVP_aes128_ctr' function. */ +#define HAVE_OPENSSL_EVP_AES_CTR 1 + +/* Define to 1 if you have the `EVP_aes128_cbc' function. */ +#define HAVE_OPENSSL_EVP_AES_CBC 1 + +/* Define to 1 if you have the `EVP_aes128_gcm' function. */ +#define HAVE_OPENSSL_EVP_AES_GCM 1 + +/* Define to 1 if you have the `CRYPTO_THREADID_set_callback' function. */ +/* #undef HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK */ + +/* Define to 1 if you have the `CRYPTO_ctr128_encrypt' function. */ +#define HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT 1 + +/* Define to 1 if you have the `EVP_CIPHER_CTX_new' function. */ +#define HAVE_OPENSSL_EVP_CIPHER_CTX_NEW 1 + +/* Define to 1 if you have the `EVP_KDF_CTX_new_id' function. */ +/* #undef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID */ + +#ifndef VBOX +/* Define to 1 if you have the `FIPS_mode' function. */ +#define HAVE_OPENSSL_FIPS_MODE 1 +#endif + +/* Define to 1 if you have the `EVP_DigestSign' function. */ +//#define HAVE_OPENSSL_EVP_DIGESTSIGN 1 + +/* Define to 1 if you have the `EVP_DigestVerify' function. */ +//#define HAVE_OPENSSL_EVP_DIGESTVERIFY 1 + +/* Define to 1 if you have the `OPENSSL_ia32cap_loc' function. */ +/* #undef HAVE_OPENSSL_IA32CAP_LOC */ + +/* Define to 1 if you have the `snprintf' function. */ +#define HAVE_SNPRINTF 1 + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the `_snprintf' function. */ +#define HAVE__SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf_s' function. */ +#define HAVE__SNPRINTF_S 1 +#endif +/* Define to 1 if you have the `vsnprintf' function. */ +#define HAVE_VSNPRINTF 1 + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the `_vsnprintf' function. */ +#define HAVE__VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf_s' function. */ +#define HAVE__VSNPRINTF_S 1 +#endif + +/* Define to 1 if you have the `isblank' function. */ +#define HAVE_ISBLANK 1 + +/* Define to 1 if you have the `strncpy' function. */ +#define HAVE_STRNCPY 1 + +#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) +/* Define to 1 if you have the `strndup' function. */ +#define HAVE_STRNDUP 1 +#endif + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the `cfmakeraw' function. */ +#define HAVE_CFMAKERAW 1 +#endif + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define HAVE_GETADDRINFO 1 + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the `poll' function. */ +#define HAVE_POLL 1 +#endif + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +/* #undef HAVE_CLOCK_GETTIME */ + +#if defined(RT_OS_WINDOWS) || (defined(RT_OS_DARWIN) && MAC_OS_X_VERSION_MIN_REQUIRED > 1090) +/* Define to 1 if you have the `ntohll' function. */ +#define HAVE_NTOHLL 1 + +/* Define to 1 if you have the `htonll' function. */ +#define HAVE_HTONLL 1 +#endif + +/* Define to 1 if you have the `strtoull' function. */ +#define HAVE_STRTOULL 1 + +/* Define to 1 if you have the `__strtoull' function. */ +/* #undef HAVE___STRTOULL */ + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the `_strtoui64' function. */ +#define HAVE__STRTOUI64 1 +#else +/* Define to 1 if you have the `glob' function. */ +#define HAVE_GLOB 1 +#endif + +/* Define to 1 if you have the `explicit_bzero' function. */ +/* #undef HAVE_EXPLICIT_BZERO */ + +#ifdef RT_OS_DARWIN +/* Define to 1 if you have the `memset_s' function. */ +#define HAVE_MEMSET_S 1 +#endif + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the `SecureZeroMemory' function. */ +#define HAVE_SECURE_ZERO_MEMORY 1 +#endif + +/* Define to 1 if you have the `cmocka_set_test_filter' function. */ +/* #undef HAVE_CMOCKA_SET_TEST_FILTER */ + +/*************************** LIBRARIES ***************************/ + +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#define HAVE_LIBCRYPTO 1 + +/* Define to 1 if you have the `gcrypt' library (-lgcrypt). */ +/* #undef HAVE_LIBGCRYPT */ + +/* Define to 1 if you have the 'mbedTLS' library (-lmbedtls). */ +/* #undef HAVE_LIBMBEDCRYPTO */ + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#define HAVE_PTHREAD 1 +#endif + +/* Define to 1 if you have the `cmocka' library (-lcmocka). */ +/* #undef HAVE_CMOCKA */ + +/**************************** OPTIONS ****************************/ + +#ifndef RT_OS_WINDOWS +#define HAVE_GCC_THREAD_LOCAL_STORAGE 1 +#else +#define HAVE_MSC_THREAD_LOCAL_STORAGE 1 +#endif + +/* #undef HAVE_FALLTHROUGH_ATTRIBUTE */ +/* #undef HAVE_UNUSED_ATTRIBUTE */ + +/* #undef HAVE_CONSTRUCTOR_ATTRIBUTE */ +/* #undef HAVE_DESTRUCTOR_ATTRIBUTE */ + +/* #undef HAVE_GCC_VOLATILE_MEMORY_PROTECTION */ + +#define HAVE_COMPILER__FUNC__ 1 +#define HAVE_COMPILER__FUNCTION__ 1 + +/* #undef HAVE_GCC_BOUNDED_ATTRIBUTE */ + +/* Define to 1 if you want to enable GSSAPI */ +/* #undef WITH_GSSAPI */ + +/* Define to 1 if you want to enable ZLIB */ +#define WITH_ZLIB 1 + +/* Define to 1 if you want to enable SFTP */ +#define WITH_SFTP 1 + +/* Define to 1 if you want to enable server support */ +//#define WITH_SERVER 1 + +/* Define to 1 if you want to enable DH group exchange algorithms */ +//#define WITH_GEX 1 + +/* Define to 1 if you want to enable blowfish cipher support */ +/* #undef WITH_BLOWFISH_CIPHER */ + +/* Define to 1 if you want to enable debug output for crypto functions */ +/* #undef DEBUG_CRYPTO */ + +/* Define to 1 if you want to enable debug output for packet functions */ +/* #undef DEBUG_PACKET */ + +/* Define to 1 if you want to enable pcap output support (experimental) */ +#define WITH_PCAP 1 + +/* Define to 1 if you want to enable calltrace debug output */ +#define DEBUG_CALLTRACE 1 + +/* Define to 1 if you want to enable NaCl support */ +/* #undef WITH_NACL */ + +/*************************** ENDIAN *****************************/ + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +/* #undef WORDS_BIGENDIAN */ + +#ifdef RT_OS_WINDOWS +unsigned __int64 htonll(unsigned __int64 Value); +unsigned __int64 ntohll(unsigned __int64 Value); +#endif + +#if defined(RT_OS_LINUX) && !defined(LLONG_MAX) +#define LLONG_MAX 9223372036854775807LL +#endif diff --git a/src/libs/libssh-0.12.2/src/config_parser.c b/src/libs/libssh-0.12.2/src/config_parser.c new file mode 100644 index 000000000000..876f0396a1be --- /dev/null +++ b/src/libs/libssh-0.12.2/src/config_parser.c @@ -0,0 +1,293 @@ +/* + * config_parser.c - Common configuration file parser functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/config_parser.h" +#include "libssh/priv.h" +#include "libssh/misc.h" + +/* Returns the original string after skipping the leading whitespace + * until finding LF. + * This is useful in case we need to get the rest of the line (for example + * external command). + */ +char *ssh_config_get_cmd(char **str) +{ + register char *c = NULL; + char *r = NULL; + + /* Ignore leading spaces */ + for (c = *str; *c; c++) { + if (! isblank(*c)) { + break; + } + } + + for (r = c; *c; c++) { + if (*c == '\n') { + *c = '\0'; + goto out; + } + } + +out: + *str = c + 1; + + return r; +} + +/* Returns the next token delimited by whitespace or equal sign (=) + * respecting the quotes creating separate token (including whitespaces). + */ +char *ssh_config_get_token(char **str) +{ + register char *c = NULL; + bool had_equal = false; + char *r = NULL; + + /* Ignore leading spaces */ + for (c = *str; *c; c++) { + if (! isblank(*c)) { + break; + } + } + + /* If we start with quote, return the whole quoted block */ + if (*c == '\"') { + for (r = ++c; *c; c++) { + if (*c == '\"' || *c == '\n') { + if (*c == '\"' && r != c && *(c - 1) == '\\') { + /* Escaped quote: Move the remaining one char left */ + int remaining_len = strlen(c); + memmove(c - 1, c, remaining_len); + c[remaining_len - 1] = '\0'; + continue; + } + *c = '\0'; + c++; + break; + } + /* XXX Unmatched quotes extend to the end of line */ + } + } else { + /* Otherwise terminate on space, equal or newline */ + for (r = c; *c; c++) { + if (*c == '\0') { + goto out; + } else if (isblank(*c) || *c == '=' || *c == '\n') { + had_equal = (*c == '='); + *c = '\0'; + c++; + break; + } + } + } + + /* Skip any other remaining whitespace */ + while (isblank(*c) || *c == '\n' || (!had_equal && *c == '=')) { + if (*c == '=') { + had_equal = true; + } + c++; + } +out: + *str = c; + return r; +} + +long ssh_config_get_long(char **str, long notfound) +{ + char *p = NULL, *endp = NULL; + long i; + + p = ssh_config_get_token(str); + if (p && *p) { + i = strtol(p, &endp, 10); + if (p == endp) { + return notfound; + } + return i; + } + + return notfound; +} + +const char *ssh_config_get_str_tok(char **str, const char *def) +{ + char *p = NULL; + + p = ssh_config_get_token(str); + if (p && *p) { + return p; + } + + return def; +} + +int ssh_config_get_yesno(char **str, int notfound) +{ + const char *p = NULL; + + p = ssh_config_get_str_tok(str, NULL); + if (p == NULL) { + return notfound; + } + + if (strncasecmp(p, "yes", 3) == 0) { + return 1; + } else if (strncasecmp(p, "no", 2) == 0) { + return 0; + } + + return notfound; +} + +int ssh_config_parse_uri(const char *tok, + char **username, + char **hostname, + char **port, + bool ignore_port) +{ + const char *endp = NULL; + long port_n; + int rc; + + /* Sanitize inputs */ + if (username != NULL) { + *username = NULL; + } + if (hostname != NULL) { + *hostname = NULL; + } + if (port != NULL) { + *port = NULL; + } + + /* Username part (optional) */ + endp = strrchr(tok, '@'); + if (endp != NULL) { + /* Zero-length username is not valid */ + if (tok == endp) { + goto error; + } + if (username != NULL) { + *username = strndup(tok, endp - tok); + if (*username == NULL) { + goto error; + } + rc = ssh_check_username_syntax(*username); + if (rc != SSH_OK) { + goto error; + } + } + tok = endp + 1; + /* If there is second @ character, this does not look like our URI */ + endp = strchr(tok, '@'); + if (endp != NULL) { + goto error; + } + } + + /* Hostname */ + if (*tok == '[') { + /* IPv6 address is enclosed with square brackets */ + tok++; + endp = strchr(tok, ']'); + if (endp == NULL) { + goto error; + } + } else if (!ignore_port) { + /* Hostnames or aliases expand to the last colon (if port is requested) + * or to the end */ + endp = strrchr(tok, ':'); + if (endp == NULL) { + endp = strchr(tok, '\0'); + } + } else { + /* If no port is requested, expand to the end of line + * (to accommodate the IPv6 addresses) */ + endp = strchr(tok, '\0'); + } + if (tok == endp) { + /* Zero-length hostnames are not valid */ + goto error; + } + if (hostname != NULL) { + *hostname = strndup(tok, endp - tok); + if (*hostname == NULL) { + goto error; + } + /* if not an ip, check syntax */ + rc = ssh_is_ipaddr(*hostname); + if (rc == 0) { + rc = ssh_check_hostname_syntax(*hostname); + if (rc != SSH_OK) { + goto error; + } + } + } + /* Skip also the closing bracket */ + if (*endp == ']') { + endp++; + } + + /* Port (optional) */ + if (*endp != '\0') { + char *port_end = NULL; + + /* Verify the port is valid positive number */ + port_n = strtol(endp + 1, &port_end, 10); + if (port_n < 1 || *port_end != '\0') { + SSH_LOG(SSH_LOG_TRACE, "Failed to parse port number." + " The value '%ld' is invalid or there are some" + " trailing characters: '%s'", port_n, port_end); + goto error; + } + if (port != NULL) { + *port = strdup(endp + 1); + if (*port == NULL) { + goto error; + } + } + } + + return SSH_OK; + +error: + if (username != NULL) { + SAFE_FREE(*username); + } + if (hostname != NULL) { + SAFE_FREE(*hostname); + } + if (port != NULL) { + SAFE_FREE(*port); + } + return SSH_ERROR; +} diff --git a/src/libs/libssh-0.12.2/src/connect.c b/src/libs/libssh-0.12.2/src/connect.c new file mode 100644 index 000000000000..0758e468e378 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/connect.c @@ -0,0 +1,472 @@ +/* + * connect.c - handles connections to ssh servers + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ + +#include "libssh/libssh.h" +#include "libssh/misc.h" + +#ifdef _WIN32 +/* + * Only use Windows API functions available on Windows 2000 SP4 or later. + * The available constants are in . + * http://msdn.microsoft.com/en-us/library/aa383745.aspx + * http://blogs.msdn.com/oldnewthing/archive/2007/04/11/2079137.aspx + */ +#undef _WIN32_WINNT +#ifdef HAVE_WSPIAPI_H +#define _WIN32_WINNT 0x0500 /* _WIN32_WINNT_WIN2K */ +#undef NTDDI_VERSION +#define NTDDI_VERSION 0x05000400 /* NTDDI_WIN2KSP4 */ +#else +#define _WIN32_WINNT 0x0501 /* _WIN32_WINNT_WINXP */ +#undef NTDDI_VERSION +#define NTDDI_VERSION 0x05010000 /* NTDDI_WINXP */ +#endif + +#include +#include + +/* is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. */ +#ifdef HAVE_WSPIAPI_H +#include +#endif + +#ifndef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#endif + +#else /* _WIN32 */ + +#include +#include +#include +#include +#include + +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/poll.h" + +#ifndef HAVE_GETADDRINFO +#error "Your system must have getaddrinfo()" +#endif + +#ifdef _WIN32 +#ifndef gai_strerror +char WSAAPI *gai_strerrorA(int code) +{ + static char buf[256]; + + snprintf(buf, sizeof(buf), "Undetermined error code (%d)", code); + + return buf; +} +#endif /* gai_strerror */ +#endif /* _WIN32 */ + +static int ssh_connect_socket_close(socket_t s) +{ +#ifdef _WIN32 + return closesocket(s); +#else + return close(s); +#endif +} + +static int +getai(const char *host, int port, int ai_family, struct addrinfo **ai) +{ + const char *service = NULL; + struct addrinfo hints; + char s_port[10]; + + ZERO_STRUCT(hints); + + hints.ai_protocol = IPPROTO_TCP; + hints.ai_family = ai_family; + hints.ai_socktype = SOCK_STREAM; + + if (port == 0) { + hints.ai_flags = AI_PASSIVE; + } else { + snprintf(s_port, sizeof(s_port), "%hu", (unsigned short)port); + service = s_port; +#ifdef AI_NUMERICSERV + hints.ai_flags = AI_NUMERICSERV; +#endif + } + + if (ssh_is_ipaddr(host) == 1) { + /* this is an IP address */ + SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host); + hints.ai_flags |= AI_NUMERICHOST; + } + + return getaddrinfo(host, service, &hints, ai); +} + +static int set_tcp_nodelay(socket_t socket) +{ + int opt = 1; + + return setsockopt(socket, + IPPROTO_TCP, + TCP_NODELAY, + (void *)&opt, + sizeof(opt)); +} + +/** + * @internal + * + * @brief Launches a nonblocking connect to an IPv4 or IPv6 host + * specified by its IP address or hostname. + * + * @returns A file descriptor, < 0 on error. + * @warning very ugly !!! + */ +socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + const char *bind_addr, int port) +{ + socket_t s = -1, first = -1; + int rc; + int ai_family; + static const char *ai_family_str = NULL; + struct addrinfo *ai = NULL; + struct addrinfo *itr = NULL; + char addrname[NI_MAXHOST], portname[NI_MAXSERV]; + + switch (session->opts.address_family) { + case SSH_ADDRESS_FAMILY_INET: + ai_family = PF_INET; + ai_family_str = "inet"; + break; + case SSH_ADDRESS_FAMILY_INET6: + ai_family = PF_INET6; + ai_family_str = "inet6"; + break; + case SSH_ADDRESS_FAMILY_ANY: + default: + ai_family = PF_UNSPEC; + ai_family_str = "any"; + } + SSH_LOG(SSH_LOG_PACKET, + "Resolve target hostname %s port %d (%s)", + host, + port, + ai_family_str); + rc = getai(host, port, ai_family, &ai); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Failed to resolve hostname %s (%s): %s", + host, + ai_family_str, + gai_strerror(rc)); + + return -1; + } + + for (itr = ai; itr != NULL; itr = itr->ai_next) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + /* create socket */ + s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol); + if (s < 0) { + ssh_set_error(session, SSH_FATAL, + "Socket create failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + continue; + } + + if (bind_addr) { + struct addrinfo *bind_ai = NULL; + struct addrinfo *bind_itr = NULL; + + SSH_LOG(SSH_LOG_PACKET, + "Resolving bind address %s (%s)", + bind_addr, + ai_family_str); + + rc = getai(bind_addr, 0, ai_family, &bind_ai); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Failed to resolve bind address %s (%s): %s", + bind_addr, + ai_family_str, + gai_strerror(rc)); + ssh_connect_socket_close(s); + s = -1; + break; + } + + for (bind_itr = bind_ai; + bind_itr != NULL; + bind_itr = bind_itr->ai_next) + { + rc = bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Binding local address: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + continue; + } else { + break; + } + } + freeaddrinfo(bind_ai); + + /* Cannot bind to any local addresses */ + if (bind_itr == NULL) { + ssh_connect_socket_close(s); + s = -1; + continue; + } + } + + rc = ssh_socket_set_nonblocking(s); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to set socket non-blocking for %s:%d", + host, port); + ssh_connect_socket_close(s); + s = -1; + continue; + } + + if (session->opts.nodelay) { + /* For winsock, socket options are only effective before connect */ + rc = set_tcp_nodelay(s); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to set TCP_NODELAY on socket: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + ssh_connect_socket_close(s); + s = -1; + continue; + } + } + + rc = getnameinfo(itr->ai_addr, + itr->ai_addrlen, + addrname, + sizeof(addrname), + portname, + sizeof(portname), + NI_NUMERICHOST | NI_NUMERICSERV); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "getnameinfo failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + ssh_connect_socket_close(s); + s = -1; + continue; + } + + errno = 0; + SSH_LOG(SSH_LOG_PACKET, + "Connecting to host %s [%s] port %s", + host, + addrname, + portname); + rc = connect(s, itr->ai_addr, itr->ai_addrlen); + if (rc == -1) { + if ((errno != 0) && (errno != EINPROGRESS)) { + ssh_set_error(session, SSH_FATAL, + "Failed to connect: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + ssh_connect_socket_close(s); + s = -1; + } else { + if (first == -1) { + SSH_LOG(SSH_LOG_PACKET, "EINPROGRESS => Store for later."); + first = s; + } else { /* errno == EINPROGRESS */ + /* save only the first "working" socket */ + ssh_connect_socket_close(s); + s = -1; + } + } + continue; + } + + break; + } + + freeaddrinfo(ai); + + /* first let's go through all the addresses looking for immediate + * connection, otherwise return the first address without error or error */ + if (s == -1) { + s = first; + } else if (s != first && first != -1) { + /* Clean up the saved socket if any */ + ssh_connect_socket_close(first); + } + + return s; +} + +/** + * @addtogroup libssh_session + * + * @{ + */ + +static int ssh_select_cb (socket_t fd, int revents, void *userdata) +{ + fd_set *set = (fd_set *)userdata; + if (revents & POLLIN) { + FD_SET(fd, set); + } + return 0; +} + +/** + * @brief A wrapper for the select syscall + * + * This function acts more or less like the select(2) syscall.\n + * There is no support for writing or exceptions.\n + * + * @param[in] channels Arrays of channels pointers terminated by a NULL. + * It is never rewritten. + * + * @param[out] outchannels Arrays of the same size as "channels", there is no + * need to initialize it. + * + * @param[in] maxfd Maximum +1 file descriptor from readfds. + * + * @param[in] readfds A fd_set of file descriptors to be select'ed for + * reading. + * + * @param[in] timeout The timeout in milliseconds. + * + * @return SSH_OK on success, + * SSH_ERROR on error, + * SSH_EINTR if it was interrupted. In that case, + * just restart it. + * + * @warning libssh is not reentrant here. That means that if a signal is caught + * during the processing of this function, you cannot call libssh + * functions on sessions that are busy with ssh_select(). + * + * @see select(2) + */ +int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout) +{ + fd_set origfds; + socket_t fd; + size_t i, j; + int rc; + int base_tm, tm; + struct ssh_timestamp ts; + ssh_event event = ssh_event_new(); + int firstround = 1; + + base_tm = tm = (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000); + for (i = 0 ; channels[i] != NULL; ++i) { + ssh_event_add_session(event, channels[i]->session); + } + + ZERO_STRUCT(origfds); + FD_ZERO(&origfds); + for (fd = 0; fd < maxfd ; fd++) { + if (FD_ISSET(fd, readfds)) { + ssh_event_add_fd(event, fd, POLLIN, ssh_select_cb, readfds); + FD_SET(fd, &origfds); + } + } + outchannels[0] = NULL; + FD_ZERO(readfds); + ssh_timestamp_init(&ts); + do { + /* Poll every channel */ + j = 0; + for (i = 0; channels[i]; i++) { + rc = ssh_channel_poll(channels[i], 0); + if (rc != 0) { + outchannels[j] = channels[i]; + j++; + } else { + rc = ssh_channel_poll(channels[i], 1); + if (rc != 0) { + outchannels[j] = channels[i]; + j++; + } + } + } + + outchannels[j] = NULL; + if (j != 0) { + break; + } + + /* watch if a user socket was triggered */ + for (fd = 0; fd < maxfd; fd++) { + if (FD_ISSET(fd, readfds)) { + goto out; + } + } + + /* If the timeout is elapsed, we should go out */ + if (!firstround && ssh_timeout_elapsed(&ts, base_tm)) { + goto out; + } + + /* since there's nothing, let's fire the polling */ + rc = ssh_event_dopoll(event,tm); + if (rc == SSH_ERROR) { + goto out; + } + + tm = ssh_timeout_update(&ts, base_tm); + firstround = 0; + } while (1); +out: + for (fd = 0; fd < maxfd; fd++) { + if (FD_ISSET(fd, &origfds)) { + ssh_event_remove_fd(event, fd); + } + } + ssh_event_free(event); + return SSH_OK; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/connector.c b/src/libs/libssh-0.12.2/src/connector.c new file mode 100644 index 000000000000..2cdfea475508 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/connector.c @@ -0,0 +1,919 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2015 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/poll.h" +#include "libssh/callbacks.h" +#include "libssh/session.h" +#include +#include +#include +#include + +#ifndef CHUNKSIZE +#define CHUNKSIZE 4096 +#endif + +#ifndef _WIN32 +# include +# include +#endif + +struct ssh_connector_struct { + ssh_session session; + + ssh_channel in_channel; + ssh_channel out_channel; + + socket_t in_fd; + socket_t out_fd; + + bool fd_is_socket; + + ssh_poll_handle in_poll; + ssh_poll_handle out_poll; + + ssh_event event; + + int in_available; + int out_wontblock; + + struct ssh_channel_callbacks_struct in_channel_cb; + struct ssh_channel_callbacks_struct out_channel_cb; + + enum ssh_connector_flags_e in_flags; + enum ssh_connector_flags_e out_flags; +}; + +static int ssh_connector_channel_data_cb(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); +static int ssh_connector_channel_write_wontblock_cb(ssh_session session, + ssh_channel channel, + uint32_t bytes, + void *userdata); +static ssize_t ssh_connector_fd_read(ssh_connector connector, + void *buffer, + uint32_t len); +static ssize_t ssh_connector_fd_write(ssh_connector connector, + const void *buffer, + uint32_t len); +static bool ssh_connector_fd_is_socket(socket_t socket); + +/** + * @brief Create a new SSH connector. + * + * Allocates and initializes a new connector object for moving data between + * an SSH session and file descriptors. The connector is created with invalid + * file descriptors and callback structures initialized, but not yet attached + * to any channels or sockets. + * + * @param[in] session The SSH session to associate with the connector. + * + * @return A newly allocated connector on success, or NULL if an + * error occurred. On error, an out-of-memory error is + * set on the session. + */ +ssh_connector ssh_connector_new(ssh_session session) +{ + ssh_connector connector; + + connector = calloc(1, sizeof(struct ssh_connector_struct)); + if (connector == NULL){ + ssh_set_error_oom(session); + return NULL; + } + + connector->session = session; + connector->in_fd = SSH_INVALID_SOCKET; + connector->out_fd = SSH_INVALID_SOCKET; + + connector->fd_is_socket = false; + + ssh_callbacks_init(&connector->in_channel_cb); + ssh_callbacks_init(&connector->out_channel_cb); + + connector->in_channel_cb.userdata = connector; + connector->in_channel_cb.channel_data_function = ssh_connector_channel_data_cb; + + connector->out_channel_cb.userdata = connector; + connector->out_channel_cb.channel_write_wontblock_function = + ssh_connector_channel_write_wontblock_cb; + + return connector; +} + +/** + * @brief Free an SSH connector. + * + * Cleans up and deallocates a connector created by ssh_connector_new(). + * Any channel callbacks and poll objects associated with the @p connector + * are removed and freed before the connector structure itself is released. + * + * @param[in] connector The connector to free. + */ +void ssh_connector_free (ssh_connector connector) +{ + if (connector == NULL) { + return; + } + if (connector->in_channel != NULL) { + ssh_remove_channel_callbacks(connector->in_channel, + &connector->in_channel_cb); + } + if (connector->out_channel != NULL) { + ssh_remove_channel_callbacks(connector->out_channel, + &connector->out_channel_cb); + } + + if (connector->event != NULL){ + ssh_connector_remove_event(connector); + } + + if (connector->in_poll != NULL) { + ssh_poll_free(connector->in_poll); + connector->in_poll = NULL; + } + + if (connector->out_poll != NULL) { + ssh_poll_free(connector->out_poll); + connector->out_poll = NULL; + } + + free(connector); +} + +/** + * @brief Set the input channel for a connector. + * + * Associates an SSH channel with the @p connector as its input source and + * installs the internal channel callbacks used for reading data. Any + * configured input file descriptor is disabled and the connector will + * receive data from the given channel only. + * + * If neither `SSH_CONNECTOR_STDOUT` nor `SSH_CONNECTOR_STDERR` is specified + * in @p flags, `SSH_CONNECTOR_STDOUT` is used as the default. + * + * @param[in] connector The connector to configure. + * @param[in] channel The SSH channel to use as input. + * @param[in] flags A combination of ssh_connector_flags_e values + * selecting which channel streams to read from. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure. + */ +int ssh_connector_set_in_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags) +{ + connector->in_channel = channel; + connector->in_fd = SSH_INVALID_SOCKET; + connector->in_flags = flags; + + /* Fallback to default value for invalid flags */ + if (!(flags & SSH_CONNECTOR_STDOUT) && !(flags & SSH_CONNECTOR_STDERR)) { + connector->in_flags = SSH_CONNECTOR_STDOUT; + } + + return ssh_add_channel_callbacks(channel, &connector->in_channel_cb); +} + +/** + * @brief Set the output channel for a connector. + * + * Associates an SSH channel with the @p connector as its output target and + * installs the internal channel callbacks used for writing data. Any + * configured output file descriptor is disabled and the connector will + * send data to the given channel only. + * + * If neither `SSH_CONNECTOR_STDOUT` nor `SSH_CONNECTOR_STDERR` is specified + * in @p flags, `SSH_CONNECTOR_STDOUT` is used as the default. + * + * @param[in] connector The connector to configure. + * @param[in] channel The SSH channel to use as output. + * @param[in] flags A combination of ssh_connector_flags_e values + * selecting which channel streams to write to. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure. + */ +int ssh_connector_set_out_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags) +{ + connector->out_channel = channel; + connector->out_fd = SSH_INVALID_SOCKET; + connector->out_flags = flags; + + /* Fallback to default value for invalid flags */ + if (!(flags & SSH_CONNECTOR_STDOUT) && !(flags & SSH_CONNECTOR_STDERR)) { + connector->out_flags = SSH_CONNECTOR_STDOUT; + } + + return ssh_add_channel_callbacks(channel, &connector->out_channel_cb); +} + +/** + * @brief Set the connector's input file descriptor. + * + * Sets the @p fd (file descriptor) to be used as the input source for the + * @p connector , replacing any previously configured input channel. + * + * @param[in] connector The connector to configure. + * @param[in] fd The file descriptor (socket or regular). + */ +void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd) +{ + connector->in_fd = fd; + connector->fd_is_socket = ssh_connector_fd_is_socket(fd); + connector->in_channel = NULL; +} + +/** + * @brief Set the connector's output file descriptor. + * + * Sets the @p fd (file descriptor) to be used as the output target for the + * @p connector , replacing any previously configured output channel. + * + * @param[in] connector The connector to configure. + * @param[in] fd The file descriptor (socket or regular). + */ +void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd) +{ + connector->out_fd = fd; + connector->fd_is_socket = ssh_connector_fd_is_socket(fd); + connector->out_channel = NULL; +} + +/* TODO */ +static void ssh_connector_except(ssh_connector connector, socket_t fd) +{ + (void) connector; + (void) fd; +} + +/* TODO */ +static void ssh_connector_except_channel(ssh_connector connector, + ssh_channel channel) +{ + (void) connector; + (void) channel; +} + +/** + * @internal + * + * @brief Reset the poll events to be followed for each file descriptor. + */ +static void ssh_connector_reset_pollevents(ssh_connector connector) +{ + if (connector->in_fd != SSH_INVALID_SOCKET) { + if (connector->in_available) { + ssh_poll_remove_events(connector->in_poll, POLLIN); + } else { + ssh_poll_add_events(connector->in_poll, POLLIN); + } + } + + if (connector->out_fd != SSH_INVALID_SOCKET) { + if (connector->out_wontblock) { + ssh_poll_remove_events(connector->out_poll, POLLOUT); + } else { + ssh_poll_add_events(connector->out_poll, POLLOUT); + } + } +} + +/** + * @internal + * + * @brief Update the connector's flags after a read-write io + * operation + * + * This should be called after some data is successfully read from + * connector's input and written to connector's output. + * + * @param[in, out] connector Connector for which the io operation occurred. + * + * @warning This does not consider the case when the io indicated failure + * + * @warning This does not consider the case when the input indicated that + * EOF was encountered. + */ +static void ssh_connector_update_flags_after_io(ssh_connector connector) +{ + /* + * With fds we can afford to mark: + * - in_available as 0 after an fd read (even if more pending data can be + * immediately read from the fd) + * + * - out_wontblock as 0 after an fd write (even if more data can + * be written to the fd without blocking) + * + * since poll events set on the fd will get raised to indicate + * possibility of read/write in case existing situation is apt + * (i.e can read/write occur right now) or if situation becomes + * apt in future (read data becomes available, write becomes + * possible) + */ + + /* + * On the other hand, with channels we need to be more careful + * before claiming read/write not possible because channel callbacks + * are called in limited scenarios. + * + * (e.g. connector callback to indicate read data available on input + * channel is called only when new data is received on channel. It is + * not called when we have some pending data in channel's buffers but + * don't receive any new data on the channel) + * + * Hence, in case of channels, blindly setting flag associated with + * read/write input/output to 0 after a read/write may not be a good + * idea as the callback that sets it back to 1 again may not be ever + * called again. + */ + + uint32_t window_size; + + /* update in_available based on input source (fd or channel) */ + if (connector->in_fd != SSH_INVALID_SOCKET) { + connector->in_available = 0; + } else if (connector->in_channel != NULL) { + if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0) { + connector->in_available = 1; + } else { + connector->in_available = 0; + } + } else { + /* connector input is invalid ! */ + return; + } + + /* update out_wontblock based on output source (fd or channel) */ + if (connector->out_fd != SSH_INVALID_SOCKET) { + connector->out_wontblock = 0; + } else if (connector->out_channel != NULL) { + window_size = ssh_channel_window_size(connector->out_channel); + if (window_size > 0) { + connector->out_wontblock = 1; + } else { + connector->out_wontblock = 0; + } + } else { + /* connector output is invalid ! */ + return; + } +} + +/** + * @internal + * + * @brief Callback called when a poll event is received on an input fd. + */ +static void ssh_connector_fd_in_cb(ssh_connector connector) +{ + unsigned char buffer[CHUNKSIZE]; + uint32_t toread = CHUNKSIZE; + ssize_t r; + ssize_t w; + ssize_t total = 0; + int rc; + + SSH_LOG(SSH_LOG_TRACE, "connector POLLIN event for fd %d", connector->in_fd); + + if (connector->out_wontblock) { + if (connector->out_channel != NULL) { + uint32_t size = ssh_channel_window_size(connector->out_channel); + + /* Don't attempt reading more than the window */ + toread = MIN(size, CHUNKSIZE); + } + + r = ssh_connector_fd_read(connector, buffer, toread); + /* Sanity: Make sure we do not get too large return value to make static + * analysis tools happy */ + if (r < 0 || r > (ssize_t)toread) { + ssh_connector_except(connector, connector->in_fd); + return; + } + + if (connector->out_channel != NULL) { + if (r == 0) { + SSH_LOG(SSH_LOG_TRACE, "input fd %d is EOF", connector->in_fd); + if (connector->out_channel->local_eof == 0) { + rc = ssh_channel_send_eof(connector->out_channel); + (void)rc; /* TODO Handle rc? */ + } + connector->in_available = 1; /* Don't poll on it */ + return; + } else if (r > 0) { + /* loop around ssh_channel_write in case our window reduced due to a race */ + while (total != r){ + if (connector->out_flags & SSH_CONNECTOR_STDOUT) { + w = ssh_channel_write(connector->out_channel, + buffer + total, + (uint32_t)(r - total)); + } else { + w = ssh_channel_write_stderr(connector->out_channel, + buffer + total, + (uint32_t)(r - total)); + } + if (w == SSH_ERROR) { + return; + } + total += w; + } + } + } else if (connector->out_fd != SSH_INVALID_SOCKET) { + if (r == 0){ + close(connector->out_fd); + connector->out_fd = SSH_INVALID_SOCKET; + } else { + /* + * Loop around write in case the write blocks even for CHUNKSIZE + * bytes + */ + while (total < r) { + w = ssh_connector_fd_write(connector, + buffer + total, + (uint32_t)(r - total)); + /* Sanity: Make sure we do not get too large return value + * to make static analysis tools happy */ + if (w < 0 || w > (r - total)) { + ssh_connector_except(connector, connector->out_fd); + return; + } + total += w; + } + } + } else { + ssh_set_error(connector->session, SSH_FATAL, "output socket or channel closed"); + return; + } + + ssh_connector_update_flags_after_io(connector); + } else { + connector->in_available = 1; + } +} + +/** @internal + * @brief Callback called when a poll event is received on an output fd + */ +static void +ssh_connector_fd_out_cb(ssh_connector connector) +{ + unsigned char buffer[CHUNKSIZE]; + ssize_t r; + ssize_t w; + ssize_t total = 0; + SSH_LOG(SSH_LOG_TRACE, "connector POLLOUT event for fd %d", + connector->out_fd); + + if (connector->in_available) { + if (connector->in_channel != NULL) { + r = ssh_channel_read_nonblocking(connector->in_channel, buffer, + CHUNKSIZE, 0); + if (r == SSH_ERROR) { + ssh_connector_except_channel(connector, connector->in_channel); + return; + } else if (r == 0 && ssh_channel_is_eof(connector->in_channel)) { + close(connector->out_fd); + connector->out_fd = SSH_INVALID_SOCKET; + return; + } else if (r > 0) { + /* loop around write in case the write blocks even for CHUNKSIZE bytes */ + while (total != r) { + w = ssh_connector_fd_write(connector, + buffer + total, + (uint32_t)(r - total)); + if (w < 0) { + ssh_connector_except(connector, connector->out_fd); + return; + } + total += w; + } + } + } else if (connector->in_fd != SSH_INVALID_SOCKET) { + /* fallback on the socket input callback */ + connector->out_wontblock = 1; + ssh_connector_fd_in_cb(connector); + } else { + ssh_set_error(connector->session, + SSH_FATAL, + "Output socket or channel closed"); + return; + } + + ssh_connector_update_flags_after_io(connector); + } else { + connector->out_wontblock = 1; + } +} + +/** + * @internal + * + * @brief Callback called when a poll event is received on a file descriptor. + * + * This is for input or output. + * + * @param[in] fd file descriptor receiving the event + * + * @param[in] revents received Poll(2) events + * + * @param[in] userdata connector + * + * @returns 0 + */ +static int ssh_connector_fd_cb(UNUSED_PARAM(ssh_poll_handle p), + socket_t fd, + int revents, + void *userdata) +{ + ssh_connector connector = userdata; + + if (revents & POLLERR) { + ssh_connector_except(connector, fd); + } else if((revents & (POLLIN|POLLHUP)) && fd == connector->in_fd) { + ssh_connector_fd_in_cb(connector); + } else if(((revents & POLLOUT) || (revents & POLLHUP)) && + fd == connector->out_fd) { + ssh_connector_fd_out_cb(connector); + } + ssh_connector_reset_pollevents(connector); + + return 0; +} + +/** + * @internal + * + * @brief Callback called when data is received on channel. + * + * @param[in] session The SSH session + * + * @param[in] channel The channel data came from + * + * @param[in] data Pointer to the data + * + * @param[in] len Length of data + * + * @param[in] is_stderr Set to 1 if the data are out of band + * + * @param[in] userdata The ssh connector + * + * @returns Amount of data bytes consumed + */ +static int ssh_connector_channel_data_cb(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + ssh_connector connector = userdata; + int w; + uint32_t window; + + SSH_LOG(SSH_LOG_TRACE, + "Received data (%" PRIu32 ") on channel (%" PRIu32 ":%" PRIu32 ")", + len, + channel->local_channel, + channel->remote_channel); + + if (is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDERR)) { + /* ignore stderr */ + return 0; + } else if (!is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDOUT)) { + /* ignore stdout */ + return 0; + } else if (len == 0) { + /* ignore empty data */ + return 0; + } + + if (connector->out_wontblock) { + SSH_LOG(SSH_LOG_TRACE, "Writing won't block"); + if (connector->out_channel != NULL) { + uint32_t window_len; + + window = ssh_channel_window_size(connector->out_channel); + window_len = MIN(window, len); + + /* Route the data to the right exception channel */ + if (connector->out_flags & SSH_CONNECTOR_STDOUT && + !(is_stderr && (connector->out_flags & SSH_CONNECTOR_STDERR))) { + w = ssh_channel_write(connector->out_channel, + data, + window_len); + } else { + w = ssh_channel_write_stderr(connector->out_channel, + data, + window_len); + } + if (w == SSH_ERROR) { + ssh_connector_except_channel(connector, connector->out_channel); + } + } else if (connector->out_fd != SSH_INVALID_SOCKET) { + ssize_t ws = ssh_connector_fd_write(connector, data, len); + if (ws < 0) { + ssh_connector_except(connector, connector->out_fd); + } + w = (int)ws; + } else { + ssh_set_error(session, SSH_FATAL, "output socket or channel closed"); + return SSH_ERROR; + } + + ssh_connector_update_flags_after_io(connector); + ssh_connector_reset_pollevents(connector); + + return w; + } else { + SSH_LOG(SSH_LOG_TRACE, "Writing would block: wait?"); + connector->in_available = 1; + + return 0; + } +} + +/** + * @internal + * + * @brief Callback called when the channel is free to write. + * + * @param[in] bytes Amount of bytes that can be written without blocking + * + * @param[in] userdata The ssh connector + * + * @returns Amount of data bytes consumed + */ +static int +ssh_connector_channel_write_wontblock_cb(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + uint32_t bytes, + void *userdata) +{ + ssh_connector connector = userdata; + uint8_t buffer[CHUNKSIZE]; + int r, w; + + (void) channel; + + SSH_LOG(SSH_LOG_TRACE, + "Write won't block (%" PRIu32 ") on channel (%" PRIu32 ":%" PRIu32 ")", + bytes, + channel->local_channel, + channel->remote_channel); + + if (connector->in_available) { + if (connector->in_channel != NULL) { + uint32_t len = MIN(CHUNKSIZE, bytes); + + r = ssh_channel_read_nonblocking(connector->in_channel, + buffer, + len, + 0); + if (r == SSH_ERROR) { + ssh_connector_except_channel(connector, connector->in_channel); + } else if (r == 0 && ssh_channel_is_eof(connector->in_channel)) { + ssh_channel_send_eof(connector->out_channel); + } else if (r > 0) { + w = ssh_channel_write(connector->out_channel, buffer, r); + if (w == SSH_ERROR) { + ssh_connector_except_channel(connector, + connector->out_channel); + } + } + } else if (connector->in_fd != SSH_INVALID_SOCKET) { + /* fallback on on the socket input callback */ + connector->out_wontblock = 1; + ssh_connector_fd_in_cb(connector); + ssh_connector_reset_pollevents(connector); + } else { + ssh_set_error(session, + SSH_FATAL, + "Output socket or channel closed"); + + return 0; + } + + ssh_connector_update_flags_after_io(connector); + } else { + connector->out_wontblock = 1; + } + + return 0; +} + +int ssh_connector_set_event(ssh_connector connector, ssh_event event) +{ + int rc = SSH_OK; + + if ((connector->in_fd == SSH_INVALID_SOCKET && + connector->in_channel == NULL) + || (connector->out_fd == SSH_INVALID_SOCKET && + connector->out_channel == NULL)) { + rc = SSH_ERROR; + ssh_set_error(connector->session,SSH_FATAL,"Connector not complete"); + goto error; + } + + connector->event = event; + if (connector->in_fd != SSH_INVALID_SOCKET) { + if (connector->in_poll == NULL) { + connector->in_poll = ssh_poll_new(connector->in_fd, + POLLIN|POLLERR, + ssh_connector_fd_cb, + connector); + } + rc = ssh_event_add_poll(event, connector->in_poll); + if (rc != SSH_OK) { + goto error; + } + } + + if (connector->out_fd != SSH_INVALID_SOCKET) { + if (connector->out_poll == NULL) { + connector->out_poll = ssh_poll_new(connector->out_fd, + POLLOUT|POLLERR, + ssh_connector_fd_cb, + connector); + } + + rc = ssh_event_add_poll(event, connector->out_poll); + if (rc != SSH_OK) { + goto error; + } + } + if (connector->in_channel != NULL) { + ssh_session session = ssh_channel_get_session(connector->in_channel); + rc = ssh_event_add_session(event, session); + if (rc != SSH_OK) + goto error; + if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0){ + connector->in_available = 1; + } + } + if (connector->out_channel != NULL) { + ssh_session session = ssh_channel_get_session(connector->out_channel); + + rc = ssh_event_add_session(event, session); + if (rc != SSH_OK) { + goto error; + } + if (ssh_channel_window_size(connector->out_channel) > 0) { + connector->out_wontblock = 1; + } + } + +error: + return rc; +} + +int ssh_connector_remove_event(ssh_connector connector) +{ + ssh_session session = NULL; + + if (connector->in_poll != NULL) { + ssh_event_remove_poll(connector->event, connector->in_poll); + ssh_poll_free(connector->in_poll); + connector->in_poll = NULL; + } + + if (connector->out_poll != NULL) { + ssh_event_remove_poll(connector->event, connector->out_poll); + ssh_poll_free(connector->out_poll); + connector->out_poll = NULL; + } + + if (connector->in_channel != NULL) { + session = ssh_channel_get_session(connector->in_channel); + + ssh_event_remove_session(connector->event, session); + } + + if (connector->out_channel != NULL) { + session = ssh_channel_get_session(connector->out_channel); + + ssh_event_remove_session(connector->event, session); + } + connector->event = NULL; + + return SSH_OK; +} + +/** + * @internal + * + * @brief Check the file descriptor to check if it is a Windows socket handle. + * + */ +static bool ssh_connector_fd_is_socket(socket_t s) +{ +#ifdef _WIN32 + struct sockaddr_storage ss; + int len = sizeof(struct sockaddr_storage); + int rc; + + rc = getsockname(s, (struct sockaddr *)&ss, &len); + if (rc == 0) { + return true; + } + + SSH_LOG(SSH_LOG_TRACE, + "Error %i in getsockname() for fd %d", + WSAGetLastError(), + s); + + return false; +#else + struct stat sb; + int rc; + + rc = fstat(s, &sb); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "error %i in fstat() for fd %d", + errno, + s); + return false; + } + + /* The descriptor is a socket */ + if (S_ISSOCK(sb.st_mode)) { + return true; + } + + return false; +#endif /* _WIN32 */ +} + +/** + * @internal + * + * @brief read len bytes from socket into buffer + * + */ +static ssize_t ssh_connector_fd_read(ssh_connector connector, + void *buffer, + uint32_t len) +{ + ssize_t nread = -1; + + if (connector->fd_is_socket) { + nread = recv(connector->in_fd,buffer, len, 0); + } else { + nread = read(connector->in_fd,buffer, len); + } + + return nread; +} + +/** + * @internal + * + * @brief brief writes len bytes from buffer to socket + * + */ +static ssize_t ssh_connector_fd_write(ssh_connector connector, + const void *buffer, + uint32_t len) +{ + ssize_t bwritten = -1; + int flags = 0; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + if (connector->fd_is_socket) { + bwritten = send(connector->out_fd,buffer, len, flags); + } else { + bwritten = write(connector->out_fd, buffer, len); + } + + return bwritten; +} diff --git a/src/libs/libssh-0.12.2/src/crypto_common.c b/src/libs/libssh-0.12.2/src/crypto_common.c new file mode 100644 index 000000000000..5dc883f63b01 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/crypto_common.c @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "libssh/crypto.h" + +int secure_memcmp(const void *s1, const void *s2, size_t n) +{ + size_t i; + uint8_t status = 0; + const uint8_t *p1 = s1; + const uint8_t *p2 = s2; + + for (i = 0; i < n; i++) { + status |= (p1[i] ^ p2[i]); + } + + return (status != 0); +} diff --git a/src/libs/libssh-0.12.2/src/curve25519.c b/src/libs/libssh-0.12.2/src/curve25519.c new file mode 100644 index 000000000000..c68fbc794e12 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519.c @@ -0,0 +1,370 @@ +/* + * curve25519.c - Curve25519 ECDH functions for key exchange + * curve25519-sha256@libssh.org and curve25519-sha256 + * + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/curve25519.h" +#ifdef HAVE_CURVE25519 + +#include "libssh/bignum.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply); + +static ssh_packet_callback dh_client_callbacks[] = { + ssh_packet_client_curve25519_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_curve25519_client_callbacks = { + .start = SSH2_MSG_KEX_ECDH_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL, +}; + +int ssh_curve25519_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + int rc; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + session->next_crypto->server_kex.cookie, + 16); + ssh_log_hexdump("Session client cookie", + session->next_crypto->client_kex.cookie, + 16); +#endif + + rc = curve25519_do_create_k(session, k); + return rc; +} + +/** @internal + * @brief Starts curve25519-sha256@libssh.org / curve25519-sha256 key exchange + */ +int ssh_client_curve25519_init(ssh_session session) +{ + int rc; + + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_KEX_ECDH_INIT, + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_curve25519_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + rc = ssh_packet_send(session); + + return rc; +} + +void ssh_client_curve25519_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); +} + +int ssh_curve25519_build_k(ssh_session session) +{ + ssh_curve25519_pubkey k; + int rc; + + rc = ssh_curve25519_create_k(session, k); + if (rc != SSH_OK) { + return rc; + } + + bignum_bin2bn(k, + CURVE25519_PUBKEY_SIZE, + &session->next_crypto->shared_secret); + if (session->next_crypto->shared_secret == NULL) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); +#endif + + return SSH_OK; +} + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply) +{ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_client_curve25519_remove_callbacks(session); + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "No public key in packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Failed to import next public key"); + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_S ECC point in packet"); + goto error; + } + if (ssh_string_len(q_s_string) != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server Curve25519 public key: %zu", + ssh_string_len(q_s_string)); + SSH_STRING_FREE(q_s_string); + goto error; + } + memcpy(session->next_crypto->curve25519_server_pubkey, + ssh_string_data(q_s_string), + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_s_string); + + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature = NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ssh_curve25519_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; + +error: + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_curve25519_init, +}; + +static struct ssh_packet_callbacks_struct ssh_curve25519_server_callbacks = { + .start = SSH2_MSG_KEX_ECDH_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL, +}; + +/** @internal + * @brief sets up the curve25519-sha256@libssh.org kex callbacks + */ +void ssh_server_curve25519_init(ssh_session session) +{ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_curve25519_server_callbacks); +} + +/** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init) +{ + /* ECDH keys */ + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; + ssh_string server_pubkey_blob = NULL; + + /* SSH host keys (rsa, ed25519 and ecdsa) */ + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_curve25519_server_callbacks); + + /* Extract the client pubkey from the init packet */ + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); + goto error; + } + if (ssh_string_len(q_c_string) != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server Curve25519 public key: %zu", + ssh_string_len(q_c_string)); + goto error; + } + + memcpy(session->next_crypto->curve25519_client_pubkey, + ssh_string_data(q_c_string), + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_c_string); + + /* Build server's key pair */ + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate curve25519 keys"); + goto error; + } + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_REPLY); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* build k and session_id */ + rc = ssh_curve25519_build_k(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + goto error; + } + + /* add host's public key */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob); + SSH_STRING_FREE(server_pubkey_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* add ecdh public key */ + q_s_string = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (q_s_string == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_string_fill(q_s_string, + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Could not copy public key"); + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, q_s_string); + SSH_STRING_FREE(q_s_string); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + /* add signature blob */ + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_ECDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + return SSH_PACKET_USED; +error: + SSH_STRING_FREE(q_c_string); + SSH_STRING_FREE(q_s_string); + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_CURVE25519 */ diff --git a/src/libs/libssh-0.12.2/src/curve25519_crypto.c b/src/libs/libssh-0.12.2/src/curve25519_crypto.c new file mode 100644 index 000000000000..3314f70ed9dd --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519_crypto.c @@ -0,0 +1,164 @@ +/* + * curve25519_crypto.c - Curve25519 ECDH functions for key exchange (OpenSSL) + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + size_t pubkey_len = CURVE25519_PUBKEY_SIZE; + int rc; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 context: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen_init(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen(pctx, &pkey); + EVP_PKEY_CTX_free(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate X25519 keys: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + rc = EVP_PKEY_get_raw_public_key(pkey, *pubkey_loc, &pubkey_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get X25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_free(pkey); + return SSH_ERROR; + } + + /* Free any previously allocated privkey */ + if (session->next_crypto->curve25519_privkey != NULL) { + EVP_PKEY_free(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + } + + session->next_crypto->curve25519_privkey = pkey; + pkey = NULL; + + return SSH_OK; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + int rc, ret = SSH_ERROR; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL, *pubkey = NULL; + size_t shared_key_len = CURVE25519_PUBKEY_SIZE; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + pkey = session->next_crypto->curve25519_privkey; + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create X25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + pctx = EVP_PKEY_CTX_new(pkey, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive_init(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 key derivation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + pubkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, + NULL, + *peer_pubkey_loc, + CURVE25519_PUBKEY_SIZE); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create X25519 public key EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive_set_peer(pctx, pubkey); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set peer X25519 public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive(pctx, k, &shared_key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to derive X25519 shared secret: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + ret = SSH_OK; + +out: + EVP_PKEY_free(pubkey); + EVP_PKEY_CTX_free(pctx); + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/curve25519_fallback.c b/src/libs/libssh-0.12.2/src/curve25519_fallback.c new file mode 100644 index 000000000000..e05331d596ef --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519_fallback.c @@ -0,0 +1,73 @@ +/* + * curve25519_fallback.c - Curve25519 ECDH functions for key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#ifdef WITH_NACL +#include "nacl/crypto_scalarmult_curve25519.h" +#endif + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + int rc; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + rc = ssh_get_random(session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE, + 1); + if (rc != 1) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return SSH_ERROR; + } + + crypto_scalarmult_base(*pubkey_loc, + session->next_crypto->curve25519_privkey); + + return SSH_OK; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + crypto_scalarmult(k, + session->next_crypto->curve25519_privkey, + *peer_pubkey_loc); + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/curve25519_gcrypt.c b/src/libs/libssh-0.12.2/src/curve25519_gcrypt.c new file mode 100644 index 000000000000..cd522a1d7da9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519_gcrypt.c @@ -0,0 +1,205 @@ +/* + * curve25519_gcrypt.c - Curve25519 ECDH functions for key exchange (Gcrypt) + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + gcry_error_t gcry_err; + gcry_sexp_t param = NULL, keypair_sexp = NULL; + ssh_string pubkey = NULL; + const char *pubkey_data = NULL; + int ret = SSH_ERROR; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + gcry_err = + gcry_sexp_build(¶m, NULL, "(genkey (ecdh (curve Curve25519)))"); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create keypair sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + gcry_err = gcry_pk_genkey(&keypair_sexp, param); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate keypair: %s", + gcry_strerror(gcry_err)); + goto out; + } + + /* Extract the public key */ + pubkey = ssh_sexp_extract_mpi(keypair_sexp, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to extract public key: %s", + gcry_strerror(gcry_err)); + goto out; + } + + /* Store the public key in the session */ + /* The first byte should be 0x40 indicating that the point is compressed, so + * we skip storing it */ + pubkey_data = (char *)ssh_string_data(pubkey); + if (ssh_string_len(pubkey) != CURVE25519_PUBKEY_SIZE + 1 || + pubkey_data[0] != 0x40) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid public key with length: %zu", + ssh_string_len(pubkey)); + goto out; + } + + memcpy(*pubkey_loc, pubkey_data + 1, CURVE25519_PUBKEY_SIZE); + + /* Free any previously allocated privkey */ + if (session->next_crypto->curve25519_privkey != NULL) { + gcry_sexp_release(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + } + + /* Store the private key */ + session->next_crypto->curve25519_privkey = keypair_sexp; + keypair_sexp = NULL; + ret = SSH_OK; + +out: + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + gcry_sexp_release(param); + gcry_sexp_release(keypair_sexp); + return ret; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + gcry_error_t gcry_err; + gcry_sexp_t pubkey_sexp = NULL, privkey_data_sexp = NULL, + result_sexp = NULL; + ssh_string shared_secret = NULL, privkey = NULL; + char *shared_secret_data = NULL; + int ret = SSH_ERROR; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + gcry_err = gcry_sexp_build( + &pubkey_sexp, + NULL, + "(key-data(public-key (ecdh (curve Curve25519) (q %b))))", + CURVE25519_PUBKEY_SIZE, + *peer_pubkey_loc); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create peer public key sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + privkey = ssh_sexp_extract_mpi(session->next_crypto->curve25519_privkey, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (privkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to extract private key"); + goto out; + } + + gcry_err = gcry_sexp_build(&privkey_data_sexp, + NULL, + "(data(flags raw)(value %b))", + ssh_string_len(privkey), + ssh_string_data(privkey)); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create private key sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + gcry_err = gcry_pk_encrypt(&result_sexp, privkey_data_sexp, pubkey_sexp); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to compute shared secret: %s", + gcry_strerror(gcry_err)); + goto out; + } + + shared_secret = ssh_sexp_extract_mpi(result_sexp, + "s", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_USG); + if (shared_secret == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to extract shared secret"); + goto out; + } + + /* Copy the shared secret to the output buffer */ + /* The first byte should be 0x40 indicating that it is a compressed point, + * so we skip it */ + shared_secret_data = (char *)ssh_string_data(shared_secret); + if (ssh_string_len(shared_secret) != CURVE25519_PUBKEY_SIZE + 1 || + shared_secret_data[0] != 0x40) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid shared secret with length: %zu", + ssh_string_len(shared_secret)); + goto out; + } + + memcpy(k, shared_secret_data + 1, CURVE25519_PUBKEY_SIZE); + + ret = SSH_OK; + gcry_sexp_release(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + +out: + ssh_string_burn(shared_secret); + SSH_STRING_FREE(shared_secret); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + gcry_sexp_release(privkey_data_sexp); + gcry_sexp_release(pubkey_sexp); + gcry_sexp_release(result_sexp); + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/curve25519_mbedcrypto.c b/src/libs/libssh-0.12.2/src/curve25519_mbedcrypto.c new file mode 100644 index 000000000000..f328f7b71305 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519_mbedcrypto.c @@ -0,0 +1,189 @@ +/* + * curve25519_mbedcrypto.c - Curve25519 ECDH functions for key exchange + * (MbedTLS) + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "mbedcrypto-compat.h" + +#include +#include + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + mbedtls_ecdh_context ecdh_ctx; + mbedtls_ecdh_params *ecdh_params = NULL; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + int rc, ret = SSH_ERROR; + char error_buf[128]; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + mbedtls_ecdh_init(&ecdh_ctx); + rc = mbedtls_ecdh_setup(&ecdh_ctx, MBEDTLS_ECP_DP_CURVE25519); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup X25519 context: %s", error_buf); + goto out; + } + + ecdh_params = &MBEDTLS_ECDH_PARAMS(ecdh_ctx); + + rc = mbedtls_ecdh_gen_public(&ecdh_params->MBEDTLS_ECDH_PRIVATE(grp), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate X25519 keypair: %s", + error_buf); + goto out; + } + + rc = mbedtls_mpi_write_binary_le(&ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to write X25519 private key: %s", + error_buf); + goto out; + } + + rc = mbedtls_mpi_write_binary_le( + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Q).MBEDTLS_ECDH_PRIVATE(X), + *pubkey_loc, + CURVE25519_PUBKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to write X25519 public key: %s", + error_buf); + goto out; + } + + ret = SSH_OK; + +out: + mbedtls_ecdh_free(&ecdh_ctx); + return ret; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + int rc, ret = SSH_ERROR; + mbedtls_ecdh_context ecdh_ctx; + mbedtls_ecdh_params *ecdh_params = NULL; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + char error_buf[128]; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + mbedtls_ecdh_init(&ecdh_ctx); + rc = mbedtls_ecdh_setup(&ecdh_ctx, MBEDTLS_ECP_DP_CURVE25519); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup X25519 context: %s", error_buf); + goto out; + } + + ecdh_params = &MBEDTLS_ECDH_PARAMS(ecdh_ctx); + + rc = mbedtls_mpi_read_binary_le(&ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to read private key: %s", error_buf); + goto out; + } + + rc = mbedtls_mpi_read_binary_le( + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Qp).MBEDTLS_ECDH_PRIVATE(X), + *peer_pubkey_loc, + CURVE25519_PUBKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to read peer public key: %s", error_buf); + goto out; + } + + rc = mbedtls_mpi_lset( + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Qp).MBEDTLS_ECDH_PRIVATE(Z), + 1); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to set Z coordinate: %s", error_buf); + goto out; + } + + rc = mbedtls_ecdh_compute_shared(&ecdh_params->MBEDTLS_ECDH_PRIVATE(grp), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(z), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Qp), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to compute shared secret: %s", + error_buf); + goto out; + } + + rc = mbedtls_mpi_write_binary_le(&ecdh_params->MBEDTLS_ECDH_PRIVATE(z), + k, + CURVE25519_PUBKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to write shared secret: %s", error_buf); + goto out; + } + + ret = SSH_OK; + +out: + mbedtls_ecdh_free(&ecdh_ctx); + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/dh-gex.c b/src/libs/libssh-0.12.2/src/dh-gex.c new file mode 100644 index 000000000000..888d2b2a4aa5 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/dh-gex.c @@ -0,0 +1,716 @@ +/* + * dh-gex.c - diffie-hellman group exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2016 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/dh-gex.h" +#include "libssh/libssh.h" +#include "libssh/ssh2.h" +#include "libssh/callbacks.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/session.h" + +/* Minimum, recommended and maximum size of DH group */ +#define DH_PMIN 2048 +#define DH_PREQ 2048 +#define DH_PMAX 8192 + +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group); +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply); + +static ssh_packet_callback dhgex_client_callbacks[] = { + ssh_packet_client_dhgex_group, /* SSH_MSG_KEX_DH_GEX_GROUP */ + NULL, /* SSH_MSG_KEX_DH_GEX_INIT */ + ssh_packet_client_dhgex_reply /* SSH_MSG_KEX_DH_GEX_REPLY */ +}; + +static struct ssh_packet_callbacks_struct ssh_dhgex_client_callbacks = { + .start = SSH2_MSG_KEX_DH_GEX_GROUP, + .n_callbacks = 3, + .callbacks = dhgex_client_callbacks, + .user = NULL +}; + +/** @internal + * @brief initiates a diffie-hellman-group-exchange kex + */ +int ssh_client_dhgex_init(ssh_session session) +{ + int rc; + + rc = ssh_dh_init_common(session->next_crypto); + if (rc != SSH_OK){ + goto error; + } + + session->next_crypto->dh_pmin = DH_PMIN; + session->next_crypto->dh_pn = DH_PREQ; + session->next_crypto->dh_pmax = DH_PMAX; + /* Minimum group size, preferred group size, maximum group size */ + rc = ssh_buffer_pack(session->out_buffer, + "bddd", + SSH2_MSG_KEX_DH_GEX_REQUEST, + session->next_crypto->dh_pmin, + session->next_crypto->dh_pn, + session->next_crypto->dh_pmax); + if (rc != SSH_OK) { + goto error; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dhgex_client_callbacks); + session->dh_handshake_state = DH_STATE_REQUEST_SENT; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + return rc; +error: + ssh_dh_cleanup(session->next_crypto); + return SSH_ERROR; +} + +/** @internal + * @brief handle a DH_GEX_GROUP packet, client side. This packet contains + * the group parameters. + */ +SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) +{ + int rc; + int blen; + bignum pmin1 = NULL, one = NULL; + bignum_CTX ctx = bignum_ctx_new(); + bignum modulus = NULL, generator = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + (void) type; + (void) user; + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_GROUP received"); + + if (bignum_ctx_invalid(ctx)) { + goto error; + } + + if (session->dh_handshake_state != DH_STATE_REQUEST_SENT) { + ssh_set_error(session, + SSH_FATAL, + "Received DH_GEX_GROUP in invalid state"); + goto error; + } + one = bignum_new(); + pmin1 = bignum_new(); + if (one == NULL || pmin1 == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_unpack(packet, + "BB", + &modulus, + &generator); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_GROUP packet"); + goto error; + } + /* basic checks */ + if (ssh_fips_mode() && + !ssh_dh_is_known_group(modulus, generator)) { + ssh_set_error(session, + SSH_FATAL, + "The received DH group is not FIPS approved"); + goto error; + } + rc = bignum_set_word(one, 1); + if (rc != 1) { + goto error; + } + blen = bignum_num_bits(modulus); + if (blen < DH_PMIN || blen > DH_PMAX) { + ssh_set_error(session, + SSH_FATAL, + "Invalid dh group parameter p: %d not in [%d:%d]", + blen, + DH_PMIN, + DH_PMAX); + goto error; + } + if (bignum_cmp(modulus, one) <= 0) { + /* p must be positive and preferably bigger than one */ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); + goto error; + } + if (!bignum_is_bit_set(modulus, 0)) { + /* p must be a prime and therefore not divisible by 2 */ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); + goto error; + } + bignum_sub(pmin1, modulus, one); + if (bignum_cmp(generator, one) <= 0 || + bignum_cmp(generator, pmin1) > 0) { + /* generator must be at least 2 and smaller than p-1*/ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter g"); + goto error; + } + bignum_ctx_free(ctx); + ctx = NULL; + + /* all checks passed, set parameters (the BNs are copied in openssl backend) */ + rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx, + modulus, generator); + if (rc != SSH_OK) { + goto error; + } +#ifdef HAVE_LIBCRYPTO + bignum_safe_free(modulus); + bignum_safe_free(generator); +#endif + modulus = NULL; + generator = NULL; + + /* compute and send DH public parameter */ + rc = ssh_dh_keypair_gen_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, NULL, &pubkey); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bB", + SSH2_MSG_KEX_DH_GEX_INIT, + pubkey); + if (rc != SSH_OK) { + goto error; + } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ + + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_PACKET_USED; + +error: + bignum_safe_free(modulus); + bignum_safe_free(generator); + bignum_safe_free(one); + bignum_safe_free(pmin1); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ + if(!bignum_ctx_invalid(ctx)) { + bignum_ctx_free(ctx); + } + ssh_dh_cleanup(session->next_crypto); + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +void ssh_client_dhgex_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) +{ + struct ssh_crypto_struct *crypto=session->next_crypto; + int rc; + ssh_string pubkey_blob = NULL; + bignum server_pubkey = NULL; + (void)type; + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_REPLY received"); + + ssh_client_dhgex_remove_callbacks(session); + rc = ssh_buffer_unpack(packet, + "SBS", + &pubkey_blob, &server_pubkey, + &crypto->dh_server_signature); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_REPLY packet"); + goto error; + } + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, + NULL, server_pubkey); + if (rc != SSH_OK) { + bignum_safe_free(server_pubkey); + goto error; + } + /* The ownership was passed to the crypto structure */ + server_pubkey = NULL; + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, DH_SERVER_KEYPAIR, + &session->next_crypto->shared_secret); + ssh_dh_debug_crypto(session->next_crypto); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; +error: + SSH_STRING_FREE(pubkey_blob); + ssh_dh_cleanup(session->next_crypto); + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +#define MODULI_FILE "/etc/ssh/moduli" +/* 2 "Safe" prime; (p-1)/2 is also prime. */ +#define SAFE_PRIME 2 +/* 0x04 Probabilistic Miller-Rabin primality tests. */ +#define PRIM_TEST_REQUIRED 0x04 + +/** + * @internal + * + * @brief Determines if the proposed modulus size is more appropriate than the + * current one. + * + * @returns 1 if it's more appropriate. Returns 0 if same or less appropriate + */ +static bool dhgroup_better_size(uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t current_size, + size_t proposed_size) +{ + if (current_size == proposed_size) { + return false; + } + + if (current_size == pn) { + /* can't do better */ + return false; + } + + if (current_size == 0 && proposed_size >= pmin && proposed_size <= pmax) { + return true; + } + + if (proposed_size < pmin || proposed_size > pmax) { + /* out of bounds */ + return false; + } + + if (current_size == 0) { + /* not in the allowed window */ + return false; + } + + if (proposed_size >= pn && proposed_size < current_size) { + return true; + } + + if (proposed_size <= pn && proposed_size > current_size) { + return true; + } + + if (proposed_size >= pn && current_size < pn) { + return true; + } + + /* We're in the allowed window but a better match already exists. */ + return false; +} + +/** @internal + * @brief returns 1 with 1/n probability + * @returns 1 on with P(1/n), 0 with P(n-1/n). + */ +static bool invn_chance(size_t n) +{ + size_t nounce = 0; + int ok; + + ok = ssh_get_random(&nounce, sizeof(nounce), 0); + if (!ok) { + return false; + } + return (nounce % n) == 0; +} + +/** @internal + * @brief retrieves a DH group from an open moduli file. + */ +static int ssh_retrieve_dhgroup_file(FILE *moduli, + uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t *best_size, + char **best_generator, + char **best_modulus) +{ + char timestamp[32] = {0}; + char generator[32] = {0}; + char modulus[4096] = {0}; + size_t type, tests, tries, size, proposed_size; + int firstbyte; + int rc; + size_t line = 0; + size_t best_nlines = 0; + + *best_size = 0; + for(;;) { + line++; + firstbyte = getc(moduli); + if (firstbyte == '#'){ + do { + firstbyte = getc(moduli); + } while(firstbyte != '\n' && firstbyte != EOF); + if (firstbyte == EOF) { + break; + } + continue; + } + if (firstbyte == EOF) { + break; + } + ungetc(firstbyte, moduli); + rc = fscanf(moduli, + "%31s %zu %zu %zu %zu %31s %4095s\n", + timestamp, + &type, + &tests, + &tries, + &size, + generator, + modulus); + if (rc != 7){ + if (rc == EOF) { + break; + } + SSH_LOG(SSH_LOG_DEBUG, "Invalid moduli entry line %zu", line); + do { + firstbyte = getc(moduli); + } while(firstbyte != '\n' && firstbyte != EOF); + if (firstbyte == EOF) { + break; + } + continue; + } + + /* we only want safe primes that were tested */ + if (type != SAFE_PRIME || !(tests & PRIM_TEST_REQUIRED)) { + continue; + } + + proposed_size = size + 1; + if (proposed_size != *best_size && + dhgroup_better_size(pmin, pn, pmax, *best_size, proposed_size)) { + best_nlines = 1; + *best_size = proposed_size; + } else if (proposed_size == *best_size) { + best_nlines++; + } + + /* Use reservoir sampling algorithm */ + if (proposed_size == *best_size && invn_chance(best_nlines)) { + SAFE_FREE(*best_generator); + SAFE_FREE(*best_modulus); + *best_generator = strdup(generator); + if (*best_generator == NULL) { + return SSH_ERROR; + } + *best_modulus = strdup(modulus); + if (*best_modulus == NULL) { + SAFE_FREE(*best_generator); + return SSH_ERROR; + } + } + } + if (*best_size != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Selected %zu bits modulus out of %zu candidates in %zu lines", + *best_size, + best_nlines - 1, + line); + } else { + SSH_LOG(SSH_LOG_DEBUG, + "No moduli found for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", + pmin, + pn, + pmax); + } + + return SSH_OK; +} + +/** @internal + * @brief retrieves a DH group from the moduli file based on bits len parameters + * @param[in] pmin minimum group size in bits + * @param[in] pn preferred group size + * @param[in] pmax maximum group size + * @param[out] size size of the chosen modulus + * @param[out] p modulus + * @param[out] g generator + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +static int ssh_retrieve_dhgroup(char *moduli_file, + uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t *size, + bignum *p, + bignum *g) +{ + FILE *moduli = NULL; + char *generator = NULL; + char *modulus = NULL; + int rc; + + /* In FIPS mode, we can not negotiate arbitrary primes, + * but just the approved ones */ + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "In FIPS mode, using built-in primes"); + return ssh_fallback_group(pmax, p, g); + } + + if (moduli_file != NULL) + moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE); + else + moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE); + + if (moduli == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_DEBUG, + "Unable to open moduli file: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return ssh_fallback_group(pmax, p, g); + } + + *size = 0; + *p = NULL; + *g = NULL; + + rc = ssh_retrieve_dhgroup_file(moduli, + pmin, + pn, + pmax, + size, + &generator, + &modulus); + fclose(moduli); + if (rc == SSH_ERROR || *size == 0) { + goto error; + } + rc = bignum_hex2bn(generator, g); + if (rc == 0) { + goto error; + } + rc = bignum_hex2bn(modulus, p); + if (rc == 0) { + goto error; + } + SAFE_FREE(generator); + SAFE_FREE(modulus); + + return SSH_OK; + +error: + bignum_safe_free(*g); + bignum_safe_free(*p); + SAFE_FREE(generator); + SAFE_FREE(modulus); + + return SSH_ERROR; +} + +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request); +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init); + +static ssh_packet_callback dhgex_server_callbacks[] = { + NULL, /* SSH_MSG_KEX_DH_GEX_REQUEST_OLD */ + NULL, /* SSH_MSG_KEX_DH_GEX_GROUP */ + ssh_packet_server_dhgex_init, /* SSH_MSG_KEX_DH_GEX_INIT */ + NULL, /* SSH_MSG_KEX_DH_GEX_REPLY */ + ssh_packet_server_dhgex_request /* SSH_MSG_KEX_DH_GEX_REQUEST */ + +}; + +static struct ssh_packet_callbacks_struct ssh_dhgex_server_callbacks = { + .start = SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, + .n_callbacks = 5, + .callbacks = dhgex_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the diffie-hellman-groupx kex callbacks + */ +void ssh_server_dhgex_init(ssh_session session){ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dhgex_server_callbacks); + ssh_dh_init_common(session->next_crypto); + session->dh_handshake_state = DH_STATE_INIT; +} + +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) +{ + bignum modulus = NULL, generator = NULL; + uint32_t pmin, pn, pmax; + size_t size = 0; + int rc; + + (void) type; + (void) user; + + if (session->dh_handshake_state != DH_STATE_INIT) { + ssh_set_error(session, + SSH_FATAL, + "Received DH_GEX_REQUEST in invalid state"); + goto error; + } + + /* Minimum group size, preferred group size, maximum group size */ + rc = ssh_buffer_unpack(packet, "ddd", &pmin, &pn, &pmax); + if (rc != SSH_OK){ + ssh_set_error_invalid(session); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "dh-gex: DHGEX_REQUEST[%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); + + if (pmin > pn || pn > pmax || pn > DH_PMAX || pmax < DH_PMIN) { + ssh_set_error(session, + SSH_FATAL, + "Invalid dh-gex arguments [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", + pmin, + pn, + pmax); + goto error; + } + session->next_crypto->dh_pmin = pmin; + session->next_crypto->dh_pn = pn; + session->next_crypto->dh_pmax = pmax; + + /* ensure safe parameters */ + if (pmin < DH_PMIN) { + pmin = DH_PMIN; + if (pn < pmin) { + pn = pmin; + } + } + rc = ssh_retrieve_dhgroup(session->server_opts.moduli_file, + pmin, + pn, + pmax, + &size, + &modulus, + &generator); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Couldn't find DH group for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", + pmin, + pn, + pmax); + goto error; + } + rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx, + modulus, generator); + if (rc != SSH_OK) { + bignum_safe_free(generator); + bignum_safe_free(modulus); + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, + "bBB", + SSH2_MSG_KEX_DH_GEX_GROUP, + modulus, + generator); + +#ifdef HAVE_LIBCRYPTO + bignum_safe_free(generator); + bignum_safe_free(modulus); +#endif + + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + goto error; + } + + session->dh_handshake_state = DH_STATE_GROUP_SENT; + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + +error: + return SSH_PACKET_USED; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEX_DH_GEX_INIT packet and complete + * Diffie-Hellman key exchange + **/ +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init){ + (void) type; + (void) user; + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEX_DHGEX_INIT"); + ssh_packet_remove_callbacks(session, &ssh_dhgex_server_callbacks); + ssh_server_dh_process_init(session, packet); + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/src/dh.c b/src/libs/libssh-0.12.2/src/dh.c new file mode 100644 index 000000000000..c04418dd59bd --- /dev/null +++ b/src/libs/libssh-0.12.2/src/dh.c @@ -0,0 +1,832 @@ +/* + * dh.c - Diffie-Helman algorithm code against SSH 2 + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2018 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2012 by Dmitriy Kuznetsov + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#ifdef WITH_GSSAPI +#include "libssh/gssapi.h" +#include +#endif + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/string.h" + +static unsigned char p_group1_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#define P_GROUP1_LEN 128 /* Size in bytes of the p number */ + +static unsigned char p_group14_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP14_LEN 256 /* Size in bytes of the p number for group 14 */ + +static unsigned char p_group16_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, + 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, + 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, + 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, + 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, + 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, + 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, + 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, + 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, + 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, + 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, + 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, + 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, + 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, + 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, + 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, + 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, + 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, + 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, + 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, + 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x06, 0x31, 0x99, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP16_LEN 512 /* Size in bytes of the p number for group 16 */ + +static unsigned char p_group18_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, + 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, + 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, + 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, + 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, + 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, + 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, + 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, + 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, + 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, + 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, + 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, + 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, + 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, + 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, + 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, + 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, + 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, + 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, + 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, + 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x02, 0x84, 0x92, + 0x36, 0xC3, 0xFA, 0xB4, 0xD2, 0x7C, 0x70, 0x26, 0xC1, 0xD4, 0xDC, 0xB2, + 0x60, 0x26, 0x46, 0xDE, 0xC9, 0x75, 0x1E, 0x76, 0x3D, 0xBA, 0x37, 0xBD, + 0xF8, 0xFF, 0x94, 0x06, 0xAD, 0x9E, 0x53, 0x0E, 0xE5, 0xDB, 0x38, 0x2F, + 0x41, 0x30, 0x01, 0xAE, 0xB0, 0x6A, 0x53, 0xED, 0x90, 0x27, 0xD8, 0x31, + 0x17, 0x97, 0x27, 0xB0, 0x86, 0x5A, 0x89, 0x18, 0xDA, 0x3E, 0xDB, 0xEB, + 0xCF, 0x9B, 0x14, 0xED, 0x44, 0xCE, 0x6C, 0xBA, 0xCE, 0xD4, 0xBB, 0x1B, + 0xDB, 0x7F, 0x14, 0x47, 0xE6, 0xCC, 0x25, 0x4B, 0x33, 0x20, 0x51, 0x51, + 0x2B, 0xD7, 0xAF, 0x42, 0x6F, 0xB8, 0xF4, 0x01, 0x37, 0x8C, 0xD2, 0xBF, + 0x59, 0x83, 0xCA, 0x01, 0xC6, 0x4B, 0x92, 0xEC, 0xF0, 0x32, 0xEA, 0x15, + 0xD1, 0x72, 0x1D, 0x03, 0xF4, 0x82, 0xD7, 0xCE, 0x6E, 0x74, 0xFE, 0xF6, + 0xD5, 0x5E, 0x70, 0x2F, 0x46, 0x98, 0x0C, 0x82, 0xB5, 0xA8, 0x40, 0x31, + 0x90, 0x0B, 0x1C, 0x9E, 0x59, 0xE7, 0xC9, 0x7F, 0xBE, 0xC7, 0xE8, 0xF3, + 0x23, 0xA9, 0x7A, 0x7E, 0x36, 0xCC, 0x88, 0xBE, 0x0F, 0x1D, 0x45, 0xB7, + 0xFF, 0x58, 0x5A, 0xC5, 0x4B, 0xD4, 0x07, 0xB2, 0x2B, 0x41, 0x54, 0xAA, + 0xCC, 0x8F, 0x6D, 0x7E, 0xBF, 0x48, 0xE1, 0xD8, 0x14, 0xCC, 0x5E, 0xD2, + 0x0F, 0x80, 0x37, 0xE0, 0xA7, 0x97, 0x15, 0xEE, 0xF2, 0x9B, 0xE3, 0x28, + 0x06, 0xA1, 0xD5, 0x8B, 0xB7, 0xC5, 0xDA, 0x76, 0xF5, 0x50, 0xAA, 0x3D, + 0x8A, 0x1F, 0xBF, 0xF0, 0xEB, 0x19, 0xCC, 0xB1, 0xA3, 0x13, 0xD5, 0x5C, + 0xDA, 0x56, 0xC9, 0xEC, 0x2E, 0xF2, 0x96, 0x32, 0x38, 0x7F, 0xE8, 0xD7, + 0x6E, 0x3C, 0x04, 0x68, 0x04, 0x3E, 0x8F, 0x66, 0x3F, 0x48, 0x60, 0xEE, + 0x12, 0xBF, 0x2D, 0x5B, 0x0B, 0x74, 0x74, 0xD6, 0xE6, 0x94, 0xF9, 0x1E, + 0x6D, 0xBE, 0x11, 0x59, 0x74, 0xA3, 0x92, 0x6F, 0x12, 0xFE, 0xE5, 0xE4, + 0x38, 0x77, 0x7C, 0xB6, 0xA9, 0x32, 0xDF, 0x8C, 0xD8, 0xBE, 0xC4, 0xD0, + 0x73, 0xB9, 0x31, 0xBA, 0x3B, 0xC8, 0x32, 0xB6, 0x8D, 0x9D, 0xD3, 0x00, + 0x74, 0x1F, 0xA7, 0xBF, 0x8A, 0xFC, 0x47, 0xED, 0x25, 0x76, 0xF6, 0x93, + 0x6B, 0xA4, 0x24, 0x66, 0x3A, 0xAB, 0x63, 0x9C, 0x5A, 0xE4, 0xF5, 0x68, + 0x34, 0x23, 0xB4, 0x74, 0x2B, 0xF1, 0xC9, 0x78, 0x23, 0x8F, 0x16, 0xCB, + 0xE3, 0x9D, 0x65, 0x2D, 0xE3, 0xFD, 0xB8, 0xBE, 0xFC, 0x84, 0x8A, 0xD9, + 0x22, 0x22, 0x2E, 0x04, 0xA4, 0x03, 0x7C, 0x07, 0x13, 0xEB, 0x57, 0xA8, + 0x1A, 0x23, 0xF0, 0xC7, 0x34, 0x73, 0xFC, 0x64, 0x6C, 0xEA, 0x30, 0x6B, + 0x4B, 0xCB, 0xC8, 0x86, 0x2F, 0x83, 0x85, 0xDD, 0xFA, 0x9D, 0x4B, 0x7F, + 0xA2, 0xC0, 0x87, 0xE8, 0x79, 0x68, 0x33, 0x03, 0xED, 0x5B, 0xDD, 0x3A, + 0x06, 0x2B, 0x3C, 0xF5, 0xB3, 0xA2, 0x78, 0xA6, 0x6D, 0x2A, 0x13, 0xF8, + 0x3F, 0x44, 0xF8, 0x2D, 0xDF, 0x31, 0x0E, 0xE0, 0x74, 0xAB, 0x6A, 0x36, + 0x45, 0x97, 0xE8, 0x99, 0xA0, 0x25, 0x5D, 0xC1, 0x64, 0xF3, 0x1C, 0xC5, + 0x08, 0x46, 0x85, 0x1D, 0xF9, 0xAB, 0x48, 0x19, 0x5D, 0xED, 0x7E, 0xA1, + 0xB1, 0xD5, 0x10, 0xBD, 0x7E, 0xE7, 0x4D, 0x73, 0xFA, 0xF3, 0x6B, 0xC3, + 0x1E, 0xCF, 0xA2, 0x68, 0x35, 0x90, 0x46, 0xF4, 0xEB, 0x87, 0x9F, 0x92, + 0x40, 0x09, 0x43, 0x8B, 0x48, 0x1C, 0x6C, 0xD7, 0x88, 0x9A, 0x00, 0x2E, + 0xD5, 0xEE, 0x38, 0x2B, 0xC9, 0x19, 0x0D, 0xA6, 0xFC, 0x02, 0x6E, 0x47, + 0x95, 0x58, 0xE4, 0x47, 0x56, 0x77, 0xE9, 0xAA, 0x9E, 0x30, 0x50, 0xE2, + 0x76, 0x56, 0x94, 0xDF, 0xC8, 0x1F, 0x56, 0xE8, 0x80, 0xB9, 0x6E, 0x71, + 0x60, 0xC9, 0x80, 0xDD, 0x98, 0xED, 0xD3, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP18_LEN 1024 /* Size in bytes of the p number for group 18 */ + +bignum ssh_dh_generator; +bignum ssh_dh_group1; +bignum ssh_dh_group14; +bignum ssh_dh_group16; +bignum ssh_dh_group18; +static int dh_crypto_initialized; + +/** + * @internal + * @brief Initialize global constants used in DH key agreement + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_dh_init(void) +{ + unsigned long g_int = 2 ; /* G is defined as 2 by the ssh2 standards */ + int rc; + if (dh_crypto_initialized) { + return SSH_OK; + } + dh_crypto_initialized = 1; + + ssh_dh_generator = bignum_new(); + if (ssh_dh_generator == NULL) { + goto error; + } + rc = bignum_set_word(ssh_dh_generator, g_int); + if (rc != 1) { + goto error; + } + + bignum_bin2bn(p_group1_value, P_GROUP1_LEN, &ssh_dh_group1); + if (ssh_dh_group1 == NULL) { + goto error; + } + bignum_bin2bn(p_group14_value, P_GROUP14_LEN, &ssh_dh_group14); + if (ssh_dh_group14 == NULL) { + goto error; + } + bignum_bin2bn(p_group16_value, P_GROUP16_LEN, &ssh_dh_group16); + if (ssh_dh_group16 == NULL) { + goto error; + } + bignum_bin2bn(p_group18_value, P_GROUP18_LEN, &ssh_dh_group18); + if (ssh_dh_group18 == NULL) { + goto error; + } + + return 0; +error: + ssh_dh_finalize(); + return SSH_ERROR; +} + +/** + * @internal + * @brief Finalize and free global constants used in DH key agreement + */ +void ssh_dh_finalize(void) +{ + if (!dh_crypto_initialized) { + return; + } + + bignum_safe_free(ssh_dh_generator); + bignum_safe_free(ssh_dh_group1); + bignum_safe_free(ssh_dh_group14); + bignum_safe_free(ssh_dh_group16); + bignum_safe_free(ssh_dh_group18); + + dh_crypto_initialized = 0; +} + +int ssh_dh_import_next_pubkey_blob(ssh_session session, ssh_string pubkey_blob) +{ + return ssh_pki_import_pubkey_blob(pubkey_blob, + &session->next_crypto->server_pubkey); + +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply); + +static ssh_packet_callback dh_client_callbacks[]= { + ssh_packet_client_dh_reply +}; + +static struct ssh_packet_callbacks_struct ssh_dh_client_callbacks = { + .start = SSH2_MSG_KEXDH_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL +}; + +/** @internal + * @brief Starts diffie-hellman-group1 key exchange + */ +int ssh_client_dh_init(ssh_session session){ + struct ssh_crypto_struct *crypto = session->next_crypto; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + int rc; + + rc = ssh_dh_init_common(crypto); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR); + if (rc == SSH_ERROR){ + goto error; + } + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR, + NULL, &pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, "bB", SSH2_MSG_KEXDH_INIT, pubkey); + if (rc != SSH_OK) { + goto error; + } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + return rc; +error: +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif + ssh_dh_cleanup(crypto); + return SSH_ERROR; +} + +void ssh_client_dh_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); +} + +SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ + struct ssh_crypto_struct *crypto=session->next_crypto; + ssh_string pubkey_blob = NULL; + bignum server_pubkey; + int rc; + + (void)type; + (void)user; + + ssh_client_dh_remove_callbacks(session); + + rc = ssh_buffer_unpack(packet, "SBS", &pubkey_blob, &server_pubkey, + &crypto->dh_server_signature); + if (rc == SSH_ERROR) { + goto error; + } + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, + NULL, server_pubkey); + if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey_blob); + bignum_safe_free(server_pubkey); + goto error; + } + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, DH_SERVER_KEYPAIR, + &session->next_crypto->shared_secret); + ssh_dh_debug_crypto(session->next_crypto); + if (rc == SSH_ERROR){ + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + return SSH_PACKET_USED; +error: + ssh_dh_cleanup(session->next_crypto); + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_dh_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_dh_init, +}; + +static struct ssh_packet_callbacks_struct ssh_dh_server_callbacks = { + .start = SSH2_MSG_KEXDH_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the diffie-hellman-groupx kex callbacks + */ +void ssh_server_dh_init(ssh_session session){ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dh_server_callbacks); + + ssh_dh_init_common(session->next_crypto); +} + +/** @internal + * @brief processes a SSH_MSG_KEXDH_INIT or SSH_MSG_KEX_DH_GEX_INIT packet and sends + * the appropriate SSH_MSG_KEXDH_REPLY or SSH_MSG_KEX_DH_GEX_REPLY + */ +int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + bignum client_pubkey; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum server_pubkey; +#else + bignum server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + int packet_type; + int rc; + + rc = ssh_buffer_unpack(packet, "B", &client_pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No e number in client request"); + goto error; + } + + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR, + NULL, client_pubkey); + if (rc != SSH_OK) { + bignum_safe_free(client_pubkey); + goto error; + } + + rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_compute_shared_secret(crypto->dh_ctx, + DH_SERVER_KEYPAIR, DH_CLIENT_KEYPAIR, + &crypto->shared_secret); + ssh_dh_debug_crypto(crypto); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + switch (crypto->kex_type){ + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + packet_type = SSH2_MSG_KEXDH_REPLY; + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + packet_type = SSH2_MSG_KEX_DH_GEX_REPLY; + break; +#endif /* WITH_GEX */ + default: + ssh_set_error(session, SSH_FATAL, "Invalid kex type"); + goto error; + } + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, + NULL, &server_pubkey); + if (rc != SSH_OK){ + goto error; + } + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, + "bSBS", + packet_type, + pubkey_blob, + server_pubkey, + sig_blob); + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif + if(rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + goto error; + } + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Sent KEX_DH_[GEX]_REPLY"); + + session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + return SSH_OK; +error: + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif + + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_dh_cleanup(session->next_crypto); + return SSH_ERROR; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEXDH_INIT packet and complete + * Diffie-Hellman key exchange + **/ +static SSH_PACKET_CALLBACK(ssh_packet_server_dh_init){ + (void)type; + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEXDH_INIT"); + ssh_packet_remove_callbacks(session, &ssh_dh_server_callbacks); + ssh_server_dh_process_init(session, packet); + return SSH_PACKET_USED; +} + +/** @internal + * @brief Choose a fallback group for the DH Group exchange if the + * moduli file is not readable + * @param[in] pmax maximum requestsd group size + * @param[out] modulus + * @param[out] generator + * @returns SSH_OK on success, SSH_ERROR otherwise + */ +int ssh_fallback_group(uint32_t pmax, + bignum *modulus, + bignum *generator) +{ + *modulus = NULL; + *generator = NULL; + + if (pmax < 3072) { + bignum_dup(ssh_dh_group14, modulus); + } else if (pmax < 6144) { + bignum_dup(ssh_dh_group16, modulus); + } else { + bignum_dup(ssh_dh_group18, modulus); + } + if (*modulus == NULL) { + return SSH_ERROR; + } + + bignum_dup(ssh_dh_generator, generator); + if (*generator == NULL) { + bignum_safe_free((*modulus)); + return SSH_ERROR; + } + + return SSH_OK; +} + +#endif /* WITH_SERVER */ + +/** + * @addtogroup libssh_session + * + * @{ + */ + +bool ssh_dh_is_known_group(bignum modulus, bignum generator) +{ + int cmp, bits; + bignum m = NULL; + + bits = bignum_num_bits(modulus); + if (bits < 3072) { + m = ssh_dh_group14; + } else if (bits < 6144) { + m = ssh_dh_group16; + } else { + m = ssh_dh_group18; + } + + cmp = bignum_cmp(m, modulus); + if (cmp != 0) { + return false; + } + + cmp = bignum_cmp(ssh_dh_generator, generator); + if (cmp != 0) { + return false; + } + + SSH_LOG(SSH_LOG_TRACE, "The received primes in FIPS are known"); + return true; +} + +ssh_key ssh_dh_get_current_server_publickey(ssh_session session) +{ + if (session->current_crypto == NULL) { + return NULL; + } + + return session->current_crypto->server_pubkey; +} + +/* Caller needs to free the blob */ +int ssh_dh_get_current_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob) +{ + const ssh_key pubkey = ssh_dh_get_current_server_publickey(session); + + return ssh_pki_export_pubkey_blob(pubkey, pubkey_blob); +} + +ssh_key ssh_dh_get_next_server_publickey(ssh_session session) +{ + return session->next_crypto->server_pubkey; +} + +/* Caller needs to free the blob */ +int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob) +{ + const ssh_key pubkey = ssh_dh_get_next_server_publickey(session); + + return ssh_pki_export_pubkey_blob(pubkey, pubkey_blob); +} + +/** + * @internal + * + * @brief Convert a buffer into an unpadded base64 string. + * The caller has to free the memory. + * + * @param hash What should be converted to a base64 string. + * + * @param len Length of the buffer to convert. + * + * @return The base64 string or NULL on error. + * + * @see ssh_string_free_char() + */ +static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) +{ + char *b64_padded = NULL; + char *b64_unpadded = NULL; + size_t k; + + b64_padded = (char *)bin_to_base64(hash, len); + if (b64_padded == NULL) { + return NULL; + } + for (k = strlen(b64_padded); k != 0 && b64_padded[k-1] == '='; k--); + + b64_unpadded = strndup(b64_padded, k); + SAFE_FREE(b64_padded); + + return b64_unpadded; +} + +/** + * @brief Get a hash as a human-readable hex- or base64-string. + * + * This gets an allocated fingerprint hash. If it is a SHA sum, it will + * return an unpadded base64 string. If it is a MD5 sum, it will return a hex + * string. Either way, the output is prepended by the hash-type. + * + * @warning Do NOT use MD5 or SHA1! Those hash functions are being deprecated. + * + * @param type Which sort of hash is given, use + * SSH_PUBLICKEY_HASH_SHA256 or better. + * + * @param hash The hash to be converted to fingerprint. + * + * @param len Length of the buffer to convert. + * + * @return Returns the allocated fingerprint hash or NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len) +{ + const char *prefix = "UNKNOWN"; + char *fingerprint = NULL; + char *str = NULL; + size_t str_len; + int rc; + + switch (type) { + case SSH_PUBLICKEY_HASH_SHA1: + case SSH_PUBLICKEY_HASH_SHA256: + fingerprint = ssh_get_b64_unpadded(hash, len); + break; + case SSH_PUBLICKEY_HASH_MD5: + fingerprint = ssh_get_hexa(hash, len); + break; + } + if (fingerprint == NULL) { + return NULL; + } + + switch (type) { + case SSH_PUBLICKEY_HASH_MD5: + prefix = "MD5"; + break; + case SSH_PUBLICKEY_HASH_SHA1: + prefix = "SHA1"; + break; + case SSH_PUBLICKEY_HASH_SHA256: + prefix = "SHA256"; + break; + } + + str_len = strlen(prefix); + if (str_len + 1 + strlen(fingerprint) + 1 < str_len) { + SAFE_FREE(fingerprint); + return NULL; + } + str_len += 1 + strlen(fingerprint) + 1; + + str = malloc(str_len); + if (str == NULL) { + SAFE_FREE(fingerprint); + return NULL; + } + rc = snprintf(str, str_len, "%s:%s", prefix, fingerprint); + SAFE_FREE(fingerprint); + if (rc < 0 || rc < (int)(str_len - 1)) { + SAFE_FREE(str); + } + + return str; +} + +/** + * @brief Print a hash as a human-readable hex- or base64-string. + * + * This prints an unpadded base64 strings for SHA sums and hex strings for MD5 + * sum. Either way, the output is prepended by the hash-type. + * + * @param type Which sort of hash is given. Use + * SSH_PUBLICKEY_HASH_SHA256 or better. + * + * @param hash The hash to be converted to fingerprint. + * + * @param len Length of the buffer to convert. + * + * @see ssh_get_publickey_hash() + * @see ssh_get_fingerprint_hash() + */ +void ssh_print_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len) +{ + char *fingerprint = NULL; + + fingerprint = ssh_get_fingerprint_hash(type, + hash, + len); + if (fingerprint == NULL) { + return; + } + + fprintf(stderr, "%s\n", fingerprint); + + SAFE_FREE(fingerprint); +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/dh_crypto.c b/src/libs/libssh-0.12.2/src/dh_crypto.c new file mode 100644 index 000000000000..647e3bdd45ca --- /dev/null +++ b/src/libs/libssh-0.12.2/src/dh_crypto.c @@ -0,0 +1,615 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Simo Sorce - Red Hat, Inc. + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/session.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#include "openssl/crypto.h" +#include "openssl/dh.h" +#include "libcrypto-compat.h" +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#include +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ + +extern bignum ssh_dh_generator; +extern bignum ssh_dh_group1; +extern bignum ssh_dh_group14; +extern bignum ssh_dh_group16; +extern bignum ssh_dh_group18; + +struct dh_ctx { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DH *keypair[2]; +#else + EVP_PKEY *keypair[2]; +#endif /* OPENSSL_VERSION_NUMBER */ +}; + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) +{ +#ifdef DEBUG_CRYPTO +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum x = NULL, y = NULL, e = NULL, f = NULL; +#else + bignum x = NULL, y = NULL, e = NULL, f = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + ssh_dh_keypair_get_keys(c->dh_ctx, DH_CLIENT_KEYPAIR, &x, &e); + ssh_dh_keypair_get_keys(c->dh_ctx, DH_SERVER_KEYPAIR, &y, &f); + ssh_print_bignum("x", x); + ssh_print_bignum("y", y); + ssh_print_bignum("e", e); + ssh_print_bignum("f", f); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(x); + bignum_safe_free(y); + bignum_safe_free(e); + bignum_safe_free(f); +#endif /* OPENSSL_VERSION_NUMBER */ + + ssh_log_hexdump("Session server cookie", c->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", c->client_kex.cookie, 16); + ssh_print_bignum("k", c->shared_secret); + +#else + (void)c; /* UNUSED_PARAM */ +#endif /* DEBUG_CRYPTO */ +} + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub) +{ + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + + DH_get0_key(ctx->keypair[peer], pub, priv); + + if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + return SSH_ERROR; + } + if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + return SSH_ERROR; + } + + return SSH_OK; +} + +#else +/* If set *priv and *pub should be initialized + * to NULL before calling this function*/ +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub) +{ + int rc; + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + + if (priv) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PRIV_KEY, + priv); + if (rc != 1) { + return SSH_ERROR; + } + } + if (pub) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PUB_KEY, + pub); + if (rc != 1) { + return SSH_ERROR; + } + } + if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + if (pub && (*pub != NULL && bignum_num_bits(*pub) != 0)) { + bignum_safe_free(*pub); + *pub = NULL; + } + return SSH_ERROR; + } + if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + if (priv) { + bignum_safe_free(*priv); + *priv = NULL; + } + return SSH_ERROR; + } + + return SSH_OK; +} +#endif /* OPENSSL_VERSION_NUMBER */ + +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + bignum priv, bignum pub) +{ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + int rc; + OSSL_PARAM *params = NULL, *out_params = NULL, *merged_params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + (void)DH_set0_key(ctx->keypair[peer], pub, priv); + + return SSH_OK; +#else + rc = EVP_PKEY_todata(ctx->keypair[peer], EVP_PKEY_KEYPAIR, &out_params); + if (rc != 1) { + return SSH_ERROR; + } + + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + rc = SSH_ERROR; + goto out; + } + + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + + if (priv) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, priv); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + } + if (pub) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PUB_KEY, pub); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + rc = SSH_ERROR; + goto out; + } + OSSL_PARAM_BLD_free(param_bld); + + merged_params = OSSL_PARAM_merge(out_params, params); + if (merged_params == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[peer]), + EVP_PKEY_PUBLIC_KEY, + merged_params); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + + rc = SSH_OK; +out: + bignum_safe_free(priv); + bignum_safe_free(pub); + EVP_PKEY_CTX_free(evp_ctx); + OSSL_PARAM_free(out_params); + OSSL_PARAM_free(params); + OSSL_PARAM_free(merged_params); + + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ +} + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator) +{ + if (ctx == NULL || ctx->keypair[0] == NULL) { + return SSH_ERROR; + } + DH_get0_pqg(ctx->keypair[0], modulus, NULL, generator); + return SSH_OK; +} +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator) +{ + int rc; + + if (ctx == NULL || ctx->keypair[0] == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_P, (BIGNUM**)modulus); + if (rc != 1) { + return SSH_ERROR; + } + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_G, (BIGNUM**)generator); + if (rc != 1) { + bignum_safe_free(*modulus); + return SSH_ERROR; + } + + return SSH_OK; +} +#endif /* OPENSSL_VERSION_NUMBER */ + +int ssh_dh_set_parameters(struct dh_ctx *ctx, + const bignum modulus, const bignum generator) +{ + size_t i; + int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + if ((ctx == NULL) || (modulus == NULL) || (generator == NULL)) { + return SSH_ERROR; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + evp_ctx = EVP_PKEY_CTX_new_from_name(NULL, "DHX", NULL); +#endif + + for (i = 0; i < 2; i++) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + bignum p = NULL; + bignum g = NULL; + + /* when setting modulus or generator, + * make sure to invalidate existing keys */ + DH_free(ctx->keypair[i]); + ctx->keypair[i] = DH_new(); + if (ctx->keypair[i] == NULL) { + rc = SSH_ERROR; + goto done; + } + + p = BN_dup(modulus); + g = BN_dup(generator); + rc = DH_set0_pqg(ctx->keypair[i], p, NULL, g); + if (rc != 1) { + BN_free(p); + BN_free(g); + rc = SSH_ERROR; + goto done; + } +#else + param_bld = OSSL_PARAM_BLD_new(); + + if (param_bld == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, modulus); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, generator); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + OSSL_PARAM_BLD_free(param_bld); + rc = SSH_ERROR; + goto done; + } + OSSL_PARAM_BLD_free(param_bld); + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + /* make sure to invalidate existing keys */ + EVP_PKEY_free(ctx->keypair[i]); + ctx->keypair[i] = NULL; + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[i]), + EVP_PKEY_KEY_PARAMETERS, + params); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ + } + + rc = SSH_OK; +#if OPENSSL_VERSION_NUMBER < 0x30000000L +done: + if (rc != SSH_OK) { + DH_free(ctx->keypair[0]); + DH_free(ctx->keypair[1]); + } +#else +done: + EVP_PKEY_CTX_free(evp_ctx); + + if (rc != SSH_OK) { + EVP_PKEY_free(ctx->keypair[0]); + EVP_PKEY_free(ctx->keypair[1]); + } +#endif /* OPENSSL_VERSION_NUMBER */ + if (rc != SSH_OK) { + ctx->keypair[0] = NULL; + ctx->keypair[1] = NULL; + } + + return rc; +} + +/** + * @internal + * @brief allocate and initialize ephemeral values used in dh kex + */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto) +{ + struct dh_ctx *ctx = NULL; + int rc; + + /* Cleanup any previously allocated dh_ctx */ + if (crypto->dh_ctx != NULL) { + ssh_dh_cleanup(crypto); + } + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return SSH_ERROR; + } + crypto->dh_ctx = ctx; + + switch (crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group1, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group18, ssh_dh_generator); + break; + default: + rc = SSH_OK; + break; + } + + if (rc != SSH_OK) { + ssh_dh_cleanup(crypto); + } + return rc; +} + +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) +{ + if (crypto->dh_ctx != NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DH_free(crypto->dh_ctx->keypair[0]); + DH_free(crypto->dh_ctx->keypair[1]); +#else + EVP_PKEY_free(crypto->dh_ctx->keypair[0]); + EVP_PKEY_free(crypto->dh_ctx->keypair[1]); +#endif /* OPENSSL_VERSION_NUMBER */ + free(crypto->dh_ctx); + crypto->dh_ctx = NULL; + } +} + +/** @internal + * @brief generates a secret DH parameter of at least DH_SECURITY_BITS + * security as well as the corresponding public key. + * + * @param[out] params a dh_ctx that will hold the new keys. + * @param peer Select either client or server key storage. Valid values are: + * DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) +{ + int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *evp_ctx = NULL; +#endif + + if ((dh_ctx == NULL) || (dh_ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + rc = DH_generate_key(dh_ctx->keypair[peer]); + if (rc != 1) { + return SSH_ERROR; + } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen_init(evp_ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_generate(evp_ctx, &(dh_ctx->keypair[peer])); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate DH: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + EVP_PKEY_CTX_free(evp_ctx); +#endif /* OPENSSL_VERSION_NUMBER */ + + return SSH_OK; +} + +/** @internal + * @brief generates a shared secret between the local peer and the remote + * peer. The local peer must have been initialized using either the + * ssh_dh_keypair_gen_keys() function or by seetting manually both + * the private and public keys. The remote peer only needs to have + * the remote's peer public key set. + * @param[in] local peer identifier (DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR) + * @param[in] remote peer identifier (DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR) + * @param[out] dest a new bignum with the shared secret value is returned. + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, + bignum *dest) +{ + unsigned char *kstring = NULL; + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum pub_key = NULL; + int klen; +#else + size_t klen; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + if ((dh_ctx == NULL) || + (dh_ctx->keypair[local] == NULL) || + (dh_ctx->keypair[remote] == NULL)) { + return SSH_ERROR; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + kstring = malloc(DH_size(dh_ctx->keypair[local])); + if (kstring == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = ssh_dh_keypair_get_keys(dh_ctx, remote, NULL, &pub_key); + if (rc != SSH_OK) { + rc = SSH_ERROR; + goto done; + } + + klen = DH_compute_key(kstring, pub_key, dh_ctx->keypair[local]); + if (klen == -1) { + rc = SSH_ERROR; + goto done; + } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[local], NULL); + + rc = EVP_PKEY_derive_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive_set_peer(evp_ctx, dh_ctx->keypair[remote]); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set peer key: %s", + ERR_error_string(ERR_get_error(), NULL)); + rc = SSH_ERROR; + goto done; + } + + /* getting the size of the secret */ + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + kstring = malloc(klen); + if (kstring == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + *dest = BN_bin2bn(kstring, (int)klen, NULL); + if (*dest == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = SSH_OK; +done: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX_free(evp_ctx); +#endif + free(kstring); + return rc; +} diff --git a/src/libs/libssh-0.12.2/src/dh_key.c b/src/libs/libssh-0.12.2/src/dh_key.c new file mode 100644 index 000000000000..cd8b2a85def9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/dh_key.c @@ -0,0 +1,411 @@ +/* + * dh-int.c - Diffie-Helman algorithm code against SSH 2 + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2018 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2012 by Dmitriy Kuznetsov + * Copyright (c) 2019 by Simo Sorce + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +extern bignum ssh_dh_generator; +extern bignum ssh_dh_group1; +extern bignum ssh_dh_group14; +extern bignum ssh_dh_group16; +extern bignum ssh_dh_group18; + +/* + * How many bits of security we want for fast DH. DH private key size must be + * twice that size. + */ +#define DH_SECURITY_BITS 512 + +struct dh_keypair { + bignum priv_key; + bignum pub_key; +}; + +struct dh_ctx { + /* 0 is client, 1 is server */ + struct dh_keypair keypair[2]; + bignum generator; + bignum modulus; +}; + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) +{ +#ifdef DEBUG_CRYPTO + const_bignum x = NULL, y = NULL, e = NULL, f = NULL; + + ssh_dh_keypair_get_keys(c->dh_ctx, DH_CLIENT_KEYPAIR, &x, &e); + ssh_dh_keypair_get_keys(c->dh_ctx, DH_SERVER_KEYPAIR, &y, &f); + ssh_print_bignum("p", c->dh_ctx->modulus); + ssh_print_bignum("g", c->dh_ctx->generator); + ssh_print_bignum("x", x); + ssh_print_bignum("y", y); + ssh_print_bignum("e", e); + ssh_print_bignum("f", f); + + ssh_log_hexdump("Session server cookie", c->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", c->client_kex.cookie, 16); + ssh_print_bignum("k", c->shared_secret); +#else + (void)c; /* UNUSED_PARAM */ +#endif +} + +static void ssh_dh_free_modulus(struct dh_ctx *ctx) +{ + if ((ctx->modulus != ssh_dh_group1) && + (ctx->modulus != ssh_dh_group14) && + (ctx->modulus != ssh_dh_group16) && + (ctx->modulus != ssh_dh_group18)) { + bignum_safe_free(ctx->modulus); + } + ctx->modulus = NULL; +} + +static void ssh_dh_free_generator(struct dh_ctx *ctx) +{ + if (ctx->generator != ssh_dh_generator) { + bignum_safe_free(ctx->generator); + } +} + +static void ssh_dh_free_dh_keypair(struct dh_keypair *keypair) +{ + bignum_safe_free(keypair->priv_key); + bignum_safe_free(keypair->pub_key); +} + +static int ssh_dh_init_dh_keypair(struct dh_keypair *keypair) +{ + int rc; + + keypair->priv_key = bignum_new(); + if (keypair->priv_key == NULL) { + rc = SSH_ERROR; + goto done; + } + keypair->pub_key = bignum_new(); + if (keypair->pub_key == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = SSH_OK; +done: + if (rc != SSH_OK) { + ssh_dh_free_dh_keypair(keypair); + } + return rc; +} + +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub) +{ + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL)) { + return SSH_ERROR; + } + + if (priv) { + /* check that we have something in it */ + if (bignum_num_bits(ctx->keypair[peer].priv_key)) { + *priv = ctx->keypair[peer].priv_key; + } else { + return SSH_ERROR; + } + } + + if (pub) { + /* check that we have something in it */ + if (bignum_num_bits(ctx->keypair[peer].pub_key)) { + *pub = ctx->keypair[peer].pub_key; + } else { + return SSH_ERROR; + } + } + + return SSH_OK; +} + +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + bignum priv, bignum pub) +{ + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL)) { + return SSH_ERROR; + } + + if (priv) { + bignum_safe_free(ctx->keypair[peer].priv_key); + ctx->keypair[peer].priv_key = priv; + } + if (pub) { + int rc; + bignum one = bignum_new(); + bignum pmin1 = bignum_new(); + if (one == NULL || pmin1 == NULL) { + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_ERROR; + } + rc = bignum_set_word(one, 1); + if (rc != 1) { + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_ERROR; + } + bignum_sub(pmin1, ctx->modulus, one); + + /* Validate the peer public key `x` is 1 < x < (modulus - 1) */ + if (bignum_cmp(pub, one) <= 0 || + bignum_cmp(pub, pmin1) >= 0) { + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_ERROR; + } + bignum_safe_free(one); + bignum_safe_free(pmin1); + + bignum_safe_free(ctx->keypair[peer].pub_key); + ctx->keypair[peer].pub_key = pub; + } + return SSH_OK; +} + +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator) +{ + if (ctx == NULL) { + return SSH_ERROR; + } + if (modulus) { + *modulus = ctx->modulus; + } + if (generator) { + *generator = ctx->generator; + } + + return SSH_OK; +} + +int ssh_dh_set_parameters(struct dh_ctx *ctx, + bignum modulus, bignum generator) +{ + int rc; + + if ((ctx == NULL) || ((modulus == NULL) && (generator == NULL))) { + return SSH_ERROR; + } + /* when setting modulus or generator, + * make sure to invalidate existing keys */ + ssh_dh_free_dh_keypair(&ctx->keypair[DH_CLIENT_KEYPAIR]); + ssh_dh_free_dh_keypair(&ctx->keypair[DH_SERVER_KEYPAIR]); + + rc = ssh_dh_init_dh_keypair(&ctx->keypair[DH_CLIENT_KEYPAIR]); + if (rc != SSH_OK) { + goto done; + } + rc = ssh_dh_init_dh_keypair(&ctx->keypair[DH_SERVER_KEYPAIR]); + if (rc != SSH_OK) { + goto done; + } + + if (modulus) { + ssh_dh_free_modulus(ctx); + ctx->modulus = modulus; + } + if (generator) { + ssh_dh_free_generator(ctx); + ctx->generator = generator; + } + +done: + return rc; +} + +/** + * @internal + * @brief allocate and initialize ephemeral values used in dh kex + */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto) +{ + struct dh_ctx *ctx = NULL; + int rc; + + /* Cleanup any previously allocated dh_ctx */ + if (crypto->dh_ctx != NULL) { + ssh_dh_cleanup(crypto); + } + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return SSH_ERROR; + } + + switch (crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group1, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group18, ssh_dh_generator); + break; + default: + rc = SSH_OK; + break; + } + + crypto->dh_ctx = ctx; + + if (rc != SSH_OK) { + ssh_dh_cleanup(crypto); + } + return rc; +} + +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) +{ + struct dh_ctx *ctx = crypto->dh_ctx; + + if (ctx == NULL) { + return; + } + + ssh_dh_free_dh_keypair(&ctx->keypair[DH_CLIENT_KEYPAIR]); + ssh_dh_free_dh_keypair(&ctx->keypair[DH_SERVER_KEYPAIR]); + + ssh_dh_free_modulus(ctx); + ssh_dh_free_generator(ctx); + free(ctx); + crypto->dh_ctx = NULL; +} + +/** @internal + * @brief generates a secret DH parameter of at least DH_SECURITY_BITS + * security as well as the corresponding public key. + * + * @param[out] params a dh_kex parameters structure with preallocated bignum + * where to store the parameters + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) +{ + bignum tmp = NULL; + bignum_CTX ctx = NULL; + int rc = 0; + int bits = 0; + int p_bits = 0; + + ctx = bignum_ctx_new(); + if (bignum_ctx_invalid(ctx)){ + goto error; + } + tmp = bignum_new(); + if (tmp == NULL) { + goto error; + } + p_bits = bignum_num_bits(dh_ctx->modulus); + /* we need at most DH_SECURITY_BITS */ + bits = MIN(DH_SECURITY_BITS * 2, p_bits); + /* ensure we're not too close of p so rnd()%p stays uniform */ + if (bits <= p_bits && bits + 64 > p_bits) { + bits += 64; + } + rc = bignum_rand(tmp, bits); + if (rc != 1) { + goto error; + } + rc = bignum_mod(dh_ctx->keypair[peer].priv_key, tmp, dh_ctx->modulus, ctx); + if (rc != 1) { + goto error; + } + /* Now compute the corresponding public key */ + rc = bignum_mod_exp(dh_ctx->keypair[peer].pub_key, dh_ctx->generator, + dh_ctx->keypair[peer].priv_key, dh_ctx->modulus, ctx); + if (rc != 1) { + goto error; + } + bignum_safe_free(tmp); + bignum_ctx_free(ctx); + return SSH_OK; +error: + bignum_safe_free(tmp); + bignum_ctx_free(ctx); + return SSH_ERROR; +} + +/** @internal + * @brief generates a shared secret between the local peer and the remote peer + * @param[in] local peer identifier + * @param[in] remote peer identifier + * @param[out] dest a preallocated bignum where to store parameter + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, + bignum *dest) +{ + int rc; + bignum_CTX ctx = bignum_ctx_new(); + if (bignum_ctx_invalid(ctx)) { + return -1; + } + + if (*dest == NULL) { + *dest = bignum_new(); + if (*dest == NULL) { + rc = 0; + goto done; + } + } + + rc = bignum_mod_exp(*dest, dh_ctx->keypair[remote].pub_key, + dh_ctx->keypair[local].priv_key, + dh_ctx->modulus, ctx); + +done: + bignum_ctx_free(ctx); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/ecdh.c b/src/libs/libssh-0.12.2/src/ecdh.c new file mode 100644 index 000000000000..af80beecd53f --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ecdh.c @@ -0,0 +1,130 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#ifdef HAVE_ECDH + +static SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply); + +static ssh_packet_callback ecdh_client_callbacks[]= { + ssh_packet_client_ecdh_reply +}; + +struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks = { + .start = SSH2_MSG_KEX_ECDH_REPLY, + .n_callbacks = 1, + .callbacks = ecdh_client_callbacks, + .user = NULL +}; + +void ssh_client_ecdh_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); +} + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_client_ecdh_remove_callbacks(session); + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session,SSH_FATAL, "No public key in packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session,SSH_FATAL, "No Q_S ECC point in packet"); + goto error; + } + session->next_crypto->ecdh_server_pubkey = q_s_string; + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature=NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ecdh_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; + +error: + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static ssh_packet_callback ecdh_server_callbacks[] = { + ssh_packet_server_ecdh_init +}; + +struct ssh_packet_callbacks_struct ssh_ecdh_server_callbacks = { + .start = SSH2_MSG_KEX_ECDH_INIT, + .n_callbacks = 1, + .callbacks = ecdh_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the ecdh kex callbacks + */ +void ssh_server_ecdh_init(ssh_session session){ + ssh_packet_set_callbacks(session, &ssh_ecdh_server_callbacks); +} + +#endif /* WITH_SERVER */ +#endif /* HAVE_ECDH */ diff --git a/src/libs/libssh-0.12.2/src/ecdh_crypto.c b/src/libs/libssh-0.12.2/src/ecdh_crypto.c new file mode 100644 index 000000000000..6b4e7706cd7d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ecdh_crypto.c @@ -0,0 +1,579 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#ifdef HAVE_ECDH +#include +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#define NISTP256 NID_X9_62_prime256v1 +#define NISTP384 NID_secp384r1 +#define NISTP521 NID_secp521r1 +#else +#include +#include +#include +#include +#include +#include "libcrypto-compat.h" +#endif /* OPENSSL_VERSION_NUMBER */ + +/** @internal + * @brief Map the given key exchange enum value to its curve name. + */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L +static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#else +static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#endif /* OPENSSL_VERSION_NUMBER */ + switch (kex_type) { + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + return NISTP256; + case SSH_KEX_ECDH_SHA2_NISTP384: +#if HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + return NISTP384; + case SSH_KEX_ECDH_SHA2_NISTP521: + return NISTP521; + default: +#if OPENSSL_VERSION_NUMBER < 0x30000000L + return SSH_ERROR; +#else + return NULL; +#endif + } +} + +/* @internal + * @brief Generate ECDH key pair for ecdh key exchange and store it in the + * session->next_crypto structure + */ +static ssh_string ssh_ecdh_generate(ssh_session session) +{ + ssh_string pubkey_string = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const EC_POINT *point = NULL; + const EC_GROUP *group = NULL; + EC_KEY *key = NULL; + int curve; +#else + EC_POINT *point = NULL; + EC_GROUP *group = NULL; + const char *curve = NULL; + EVP_PKEY *key = NULL; + OSSL_PARAM *out_params = NULL; + const OSSL_PARAM *pubkey_param = NULL; + const void *pubkey = NULL; + size_t pubkey_len; + int nid; + int rc; +#endif /* OPENSSL_VERSION_NUMBER */ + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (curve == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Failed to get curve name"); + return NULL; + } + + key = EC_KEY_new_by_curve_name(curve); +#else + if (curve == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to get curve name"); + return NULL; + } + + key = EVP_EC_gen(curve); +#endif /* OPENSSL_VERSION_NUMBER */ + if (key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to generate key"); + return NULL; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + group = EC_KEY_get0_group(key); + + EC_KEY_generate_key(key); + + point = EC_KEY_get0_public_key(key); + + pubkey_string = pki_key_make_ecpoint_string(group, point); +#else + rc = EVP_PKEY_todata(key, EVP_PKEY_PUBLIC_KEY, &out_params); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export public key"); + EVP_PKEY_free(key); + return NULL; + } + + pubkey_param = OSSL_PARAM_locate_const(out_params, OSSL_PKEY_PARAM_PUB_KEY); + if (pubkey_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to find public key"); + EVP_PKEY_free(key); + OSSL_PARAM_free(out_params); + return NULL; + } + + rc = OSSL_PARAM_get_octet_string_ptr(pubkey_param, + (const void**)&pubkey, + &pubkey_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to read public key"); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + + /* Convert the data to low-level representation */ + nid = pki_key_ecgroup_name_to_nid(curve); + group = EC_GROUP_new_by_curve_name_ex(NULL, NULL, nid); + if (group == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Could not create group: %s", + ERR_error_string(ERR_get_error(), NULL)); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + point = EC_POINT_new(group); + if (point == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Could not create point: %s", + ERR_error_string(ERR_get_error(), NULL)); + EC_GROUP_free(group); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + rc = EC_POINT_oct2point(group, point, pubkey, pubkey_len, NULL); + OSSL_PARAM_free(out_params); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export public key"); + EC_GROUP_free(group); + EC_POINT_free(point); + EVP_PKEY_free(key); + return NULL; + } + + pubkey_string = pki_key_make_ecpoint_string(group, point); + EC_GROUP_free(group); + EC_POINT_free(point); +#endif /* OPENSSL_VERSION_NUMBER */ + if (pubkey_string == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to convert public key"); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(key); +#else + EVP_PKEY_free(key); +#endif /* OPENSSL_VERSION_NUMBER */ + return NULL; + } + + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(session->next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(session->next_crypto->ecdh_privkey); +#endif + session->next_crypto->ecdh_privkey = NULL; + } + + session->next_crypto->ecdh_privkey = key; + return pubkey_string; +} + +/** @internal + * @brief Set up a nistp{256,384,521} key pair for ECDH key exchange. + */ +int ssh_ecdh_init(ssh_session session) +{ + ssh_string pubkey = NULL; + ssh_string *pubkey_loc = NULL; + + pubkey = ssh_ecdh_generate(session); + if (pubkey == NULL) { + return SSH_ERROR; + } + + if (session->server) { + pubkey_loc = &session->next_crypto->ecdh_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->ecdh_client_pubkey; + } + + ssh_string_free(*pubkey_loc); + *pubkey_loc = pubkey; + + return SSH_OK; +} + +/** @internal + * @brief Starts ecdh-sha2-nistp256 key exchange + */ +int ssh_client_ecdh_init(ssh_session session) +{ + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, + session->next_crypto->ecdh_client_pubkey); + if (rc < 0) { + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + + return rc; +} + +int ecdh_build_k(ssh_session session) +{ + struct ssh_crypto_struct *next_crypto = session->next_crypto; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const EC_GROUP *group = EC_KEY_get0_group(next_crypto->ecdh_privkey); + EC_POINT *pubkey = NULL; + void *buffer = NULL; + int rc; + int len = (EC_GROUP_get_degree(group) + 7) / 8; + bignum_CTX ctx = bignum_ctx_new(); + if (ctx == NULL) { + return -1; + } + pubkey = EC_POINT_new(group); + if (pubkey == NULL) { + bignum_ctx_free(ctx); + return -1; + } + + if (session->server) { + rc = EC_POINT_oct2point(group, + pubkey, + ssh_string_data(next_crypto->ecdh_client_pubkey), + ssh_string_len(next_crypto->ecdh_client_pubkey), + ctx); + } else { + rc = EC_POINT_oct2point(group, + pubkey, + ssh_string_data(next_crypto->ecdh_server_pubkey), + ssh_string_len(next_crypto->ecdh_server_pubkey), + ctx); + } + bignum_ctx_free(ctx); + if (rc <= 0) { + EC_POINT_clear_free(pubkey); + return -1; + } + + buffer = malloc(len); + if (buffer == NULL) { + EC_POINT_clear_free(pubkey); + return -1; + } + + rc = ECDH_compute_key(buffer, + len, + pubkey, + next_crypto->ecdh_privkey, + NULL); + EC_POINT_clear_free(pubkey); + if (rc <= 0) { + free(buffer); + return -1; + } + + bignum_bin2bn(buffer, len, &next_crypto->shared_secret); + free(buffer); +#else + const char *curve = NULL; + EVP_PKEY *pubkey = NULL; + void *secret = NULL; + size_t secret_len; + int rc; + ssh_string peer_pubkey = NULL; + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + EVP_PKEY_CTX *dh_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, + next_crypto->ecdh_privkey, + NULL); + + if (dh_ctx == NULL || param_bld == NULL) { + ssh_set_error_oom(session); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + rc = EVP_PKEY_derive_init(dh_ctx); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not init PKEY derive: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + if (session->server) { + peer_pubkey = next_crypto->ecdh_client_pubkey; + } else { + peer_pubkey = next_crypto->ecdh_server_pubkey; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(peer_pubkey), + ssh_string_len(peer_pubkey)); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not push the pub key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + curve = ecdh_kex_type_to_curve(next_crypto->kex_type); + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_PKEY_PARAM_GROUP_NAME, + (char *)curve, + strlen(curve)); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not push the group name: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + rc = evp_build_pkey("EC", param_bld, &pubkey, EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Could not build the pkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + rc = EVP_PKEY_derive_set_peer(dh_ctx, pubkey); + EVP_PKEY_free(pubkey); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not set peer pubkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + /* get the max length of the secret */ + rc = EVP_PKEY_derive(dh_ctx, NULL, &secret_len); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not set peer pubkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + secret = malloc(secret_len); + if (secret == NULL) { + ssh_set_error_oom(session); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + rc = EVP_PKEY_derive(dh_ctx, secret, &secret_len); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not derive shared key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + free(secret); + return -1; + } + + EVP_PKEY_CTX_free(dh_ctx); + + bignum_bin2bn(secret, secret_len, &next_crypto->shared_secret); + free(secret); +#endif /* OPENSSL_VERSION_NUMBER */ + if (next_crypto->shared_secret == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; + return -1; + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + next_crypto->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", + next_crypto->client_kex.cookie, 16); + ssh_print_bignum("Shared secret key", next_crypto->shared_secret); +#endif /* DEBUG_CRYPTO */ + + return 0; +} + +#ifdef WITH_SERVER + +/** @brief Handle a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init) +{ + /* ECDH keys */ + ssh_string q_c_string = NULL; + /* SSH host keys (rsa, ed25519 and ecdsa) */ + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "Processing SSH_MSG_KEXDH_INIT"); + + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); + /* Extract the client pubkey from the init packet */ + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); + goto error; + } + session->next_crypto->ecdh_client_pubkey = q_c_string; + + rc = ssh_ecdh_init(session); + if (rc < 0) { + goto error; + } + + /* build k and session_id */ + rc = ecdh_build_k(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + SSH_STRING_FREE(sig_blob); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bSSS", + SSH2_MSG_KEXDH_REPLY, + pubkey_blob, /* host's pubkey */ + session->next_crypto->ecdh_server_pubkey, /* ecdh public key */ + sig_blob); /* signature blob */ + + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + return SSH_PACKET_USED; +error: + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_ECDH */ diff --git a/src/libs/libssh-0.12.2/src/ecdh_gcrypt.c b/src/libs/libssh-0.12.2/src/ecdh_gcrypt.c new file mode 100644 index 000000000000..ec7dccfd3cc8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ecdh_gcrypt.c @@ -0,0 +1,396 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/libgcrypt.h" + +#ifdef HAVE_ECDH +#include + +/** @internal + * @brief Map the given key exchange enum value to its curve name. + */ +static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) +{ + switch (kex_type) { + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + return "NIST P-256"; + case SSH_KEX_ECDH_SHA2_NISTP384: +#if HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + return "NIST P-384"; + case SSH_KEX_ECDH_SHA2_NISTP521: + return "NIST P-521"; + default: + return NULL; + } +} + +/** @internal + * @brief Set up a nistp{256,384,521} key pair for ECDH key exchange. + */ +int ssh_ecdh_init(ssh_session session) +{ + int rc = SSH_OK; + const char *curve = NULL; + const char *genstring = NULL; + gpg_error_t err; + gcry_sexp_t param = NULL; + gcry_sexp_t key = NULL; + ssh_string pubkey = NULL; + ssh_string *pubkey_loc = NULL; + + if (session->server) { + pubkey_loc = &session->next_crypto->ecdh_server_pubkey; + genstring = "(genkey(ecdh(curve %s) (flags transient-key)))"; + } else { + pubkey_loc = &session->next_crypto->ecdh_client_pubkey; + genstring = "(genkey(ecdh(curve %s)))"; + } + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == NULL) { + rc = SSH_ERROR; + goto out; + } + + err = gcry_sexp_build(¶m, NULL, genstring, curve); + if (err) { + rc = SSH_ERROR; + goto out; + } + + err = gcry_pk_genkey(&key, param); + if (err) { + rc = SSH_ERROR; + goto out; + } + + pubkey = ssh_sexp_extract_mpi(key, "q", GCRYMPI_FMT_USG, GCRYMPI_FMT_STD); + if (pubkey == NULL) { + rc = SSH_ERROR; + goto out; + } + + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { + gcry_sexp_release(session->next_crypto->ecdh_privkey); + session->next_crypto->ecdh_privkey = NULL; + } + session->next_crypto->ecdh_privkey = key; + key = NULL; + + SSH_STRING_FREE(*pubkey_loc); + *pubkey_loc = pubkey; + pubkey = NULL; + +out: + gcry_sexp_release(param); + gcry_sexp_release(key); + SSH_STRING_FREE(pubkey); + return rc; +} + +/** @internal + * @brief Starts ecdh-sha2-nistp{256,384,521} key exchange. + */ +int ssh_client_ecdh_init(ssh_session session) +{ + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, + session->next_crypto->ecdh_client_pubkey); + if (rc < 0) { + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + return rc; +} + +int ecdh_build_k(ssh_session session) +{ + gpg_error_t err; + gcry_sexp_t data = NULL; + gcry_sexp_t result = NULL; + /* We need to get the x coordinate. Libgcrypt 1.7 and above + offers a suitable API for that. */ +#if (GCRYPT_VERSION_NUMBER >= 0x010700) + gcry_mpi_t s = NULL; + gcry_mpi_point_t point; +#else + size_t k_len = 0; + enum ssh_key_exchange_e kex_type = session->next_crypto->kex_type; + ssh_string s = NULL; +#endif + ssh_string pubkey_raw = NULL; + gcry_sexp_t pubkey = NULL; + ssh_string privkey = NULL; + int rc = SSH_ERROR; + const char *curve = NULL; + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == NULL) { + goto out; + } + + pubkey_raw = session->server + ? session->next_crypto->ecdh_client_pubkey + : session->next_crypto->ecdh_server_pubkey; + + err = gcry_sexp_build(&pubkey, + NULL, + "(key-data(public-key(ecdh(curve %s)(q %b))))", + curve, + ssh_string_len(pubkey_raw), + ssh_string_data(pubkey_raw)); + if (err) { + goto out; + } + + privkey = ssh_sexp_extract_mpi(session->next_crypto->ecdh_privkey, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (privkey == NULL) { + goto out; + } + + err = gcry_sexp_build(&data, NULL, + "(data(flags raw)(value %b))", + ssh_string_len(privkey), + ssh_string_data(privkey)); + if (err) { + goto out; + } + + err = gcry_pk_encrypt(&result, data, pubkey); + if (err) { + goto out; + } + +#if (GCRYPT_VERSION_NUMBER >= 0x010700) + err = gcry_sexp_extract_param(result, "", "s", &s, NULL); + if (err) { + goto out; + } + + point = gcry_mpi_point_new(0); + if (point == NULL) { + gcry_mpi_release(s); + goto out; + } + + err = gcry_mpi_ec_decode_point(point, s, NULL); + gcry_mpi_release(s); + if (err) { + goto out; + } + + session->next_crypto->shared_secret = gcry_mpi_new(0); + gcry_mpi_point_snatch_get(session->next_crypto->shared_secret, + NULL, NULL, point); +#else + s = ssh_sexp_extract_mpi(result, "s", GCRYMPI_FMT_USG, GCRYMPI_FMT_USG); + if (s == NULL) { + goto out; + } + + if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { + k_len = 65; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { + k_len = 97; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { + k_len = 133; + } else { + ssh_string_burn(s); + SSH_STRING_FREE(s); + goto out; + } + + if (ssh_string_len(s) != k_len) { + ssh_string_burn(s); + SSH_STRING_FREE(s); + goto out; + } + + err = gcry_mpi_scan(&session->next_crypto->shared_secret, + GCRYMPI_FMT_USG, + (const char *)ssh_string_data(s) + 1, + k_len / 2, + NULL); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (err) { + goto out; + } +#endif + + rc = SSH_OK; + gcry_sexp_release(session->next_crypto->ecdh_privkey); + session->next_crypto->ecdh_privkey = NULL; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + session->next_crypto->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", + session->next_crypto->client_kex.cookie, 16); + ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); +#endif + + out: + gcry_sexp_release(pubkey); + gcry_sexp_release(data); + gcry_sexp_release(result); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + return rc; +} + +#ifdef WITH_SERVER + + +/** @brief Handle a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ + ssh_string q_c_string = NULL; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + int rc = SSH_ERROR; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); + + /* Extract the client pubkey from the init packet */ + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); + goto out; + } + session->next_crypto->ecdh_client_pubkey = q_c_string; + + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate a key pair"); + goto out; + } + + /* build k and session_id */ + rc = ecdh_build_k(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto out; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto out; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + rc = SSH_ERROR; + goto out; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + SSH_STRING_FREE(sig_blob); + goto out; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bSSS", + SSH2_MSG_KEXDH_REPLY, + pubkey_blob, /* host's pubkey */ + session->next_crypto->ecdh_server_pubkey, /* ecdh public key */ + sig_blob); /* signature blob */ + + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + goto out; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto out; + } + + out: + if (rc == SSH_ERROR) { + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + } + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_ECDH */ diff --git a/src/libs/libssh-0.12.2/src/ecdh_mbedcrypto.c b/src/libs/libssh-0.12.2/src/ecdh_mbedcrypto.c new file mode 100644 index 000000000000..7d013b74e433 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ecdh_mbedcrypto.c @@ -0,0 +1,326 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/session.h" +#include "libssh/ecdh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/libmbedcrypto.h" + +#include +#include +#include "mbedcrypto-compat.h" + +#ifdef HAVE_ECDH + +static mbedtls_ecp_group_id +ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) +{ + switch (kex_type) { + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + return MBEDTLS_ECP_DP_SECP256R1; + case SSH_KEX_ECDH_SHA2_NISTP384: + return MBEDTLS_ECP_DP_SECP384R1; + case SSH_KEX_ECDH_SHA2_NISTP521: + return MBEDTLS_ECP_DP_SECP521R1; + default: + return MBEDTLS_ECP_DP_NONE; + } + return MBEDTLS_ECP_DP_NONE; +} + +int ssh_ecdh_init(ssh_session session) +{ + int rc; + mbedtls_ecp_group grp; + mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + ssh_string pubkey = NULL; + ssh_string *pubkey_loc = NULL; + + if (session->server) { + pubkey_loc = &session->next_crypto->ecdh_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->ecdh_client_pubkey; + } + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == MBEDTLS_ECP_DP_NONE) { + return SSH_ERROR; + } + + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { + mbedtls_ecp_keypair_free(session->next_crypto->ecdh_privkey); + SAFE_FREE(session->next_crypto->ecdh_privkey); + } + + session->next_crypto->ecdh_privkey = malloc(sizeof(mbedtls_ecp_keypair)); + if (session->next_crypto->ecdh_privkey == NULL) { + return SSH_ERROR; + } + + ecdh_privkey = session->next_crypto->ecdh_privkey; + + mbedtls_ecp_keypair_init(ecdh_privkey); + mbedtls_ecp_group_init(&grp); + + rc = mbedtls_ecp_group_load(&grp, curve); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + rc = mbedtls_ecp_gen_keypair(&grp, + &ecdh_privkey->MBEDTLS_PRIVATE(d), + &ecdh_privkey->MBEDTLS_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + pubkey = make_ecpoint_string(&grp, &ecdh_privkey->MBEDTLS_PRIVATE(Q)); + if (pubkey == NULL) { + rc = SSH_ERROR; + goto out; + } + + SSH_STRING_FREE(*pubkey_loc); + *pubkey_loc = pubkey; + pubkey = NULL; + +out: + mbedtls_ecp_group_free(&grp); + SSH_STRING_FREE(pubkey); + return rc; +} + +int ssh_client_ecdh_init(ssh_session session) +{ + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, + session->next_crypto->ecdh_client_pubkey); + if (rc < 0) { + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + rc = ssh_packet_send(session); + + return rc; +} + +int ecdh_build_k(ssh_session session) +{ + mbedtls_ecp_group grp; + mbedtls_ecp_point pubkey; + int rc; + mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == MBEDTLS_ECP_DP_NONE) { + return SSH_ERROR; + } + + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_point_init(&pubkey); + + rc = mbedtls_ecp_group_load(&grp, curve); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + if (session->server) { + rc = mbedtls_ecp_point_read_binary(&grp, &pubkey, + ssh_string_data(session->next_crypto->ecdh_client_pubkey), + ssh_string_len(session->next_crypto->ecdh_client_pubkey)); + } else { + rc = mbedtls_ecp_point_read_binary(&grp, &pubkey, + ssh_string_data(session->next_crypto->ecdh_server_pubkey), + ssh_string_len(session->next_crypto->ecdh_server_pubkey)); + } + + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + session->next_crypto->shared_secret = malloc(sizeof(mbedtls_mpi)); + if (session->next_crypto->shared_secret == NULL) { + rc = SSH_ERROR; + goto out; + } + + mbedtls_mpi_init(session->next_crypto->shared_secret); + + ecdh_privkey = session->next_crypto->ecdh_privkey; + rc = mbedtls_ecdh_compute_shared(&grp, + session->next_crypto->shared_secret, + &pubkey, + &ecdh_privkey->MBEDTLS_PRIVATE(d), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + +out: + mbedtls_ecp_keypair_free(ecdh_privkey); + SAFE_FREE(session->next_crypto->ecdh_privkey); + mbedtls_ecp_group_free(&grp); + mbedtls_ecp_point_free(&pubkey); + return rc; +} + +#ifdef WITH_SERVER + +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ + ssh_string q_c_string = NULL; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); + + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); + return SSH_ERROR; + } + + session->next_crypto->ecdh_client_pubkey = q_c_string; + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; + } + + /* build k and session_id */ + rc = ecdh_build_k(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto out; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + rc = SSH_ERROR; + goto out; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + rc = SSH_ERROR; + goto out; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + rc = SSH_ERROR; + goto out; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + SSH_STRING_FREE(sig_blob); + goto out; + } + + rc = ssh_buffer_pack(session->out_buffer, "bSSS", + SSH2_MSG_KEXDH_REPLY, + pubkey_blob, /* host's pubkey */ + session->next_crypto->ecdh_server_pubkey, /* ecdh public key */ + sig_blob); /* signature blob */ + + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + rc = SSH_ERROR; + goto out; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto out; + } + +out: + if (rc == SSH_ERROR) { + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + } + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ +#endif diff --git a/src/libs/libssh-0.12.2/src/error.c b/src/libs/libssh-0.12.2/src/error.c new file mode 100644 index 000000000000..3f8d78cd8f9d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/error.c @@ -0,0 +1,154 @@ +/* + * error.c - functions for ssh error handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include "libssh/priv.h" +#include "libssh/session.h" + +/** + * @defgroup libssh_error The SSH error functions + * @ingroup libssh + * + * Functions for error handling. + * + * @{ + */ + +/** + * @internal + * + * @brief Registers an error with a description. + * + * @param error The place to store the error. + * + * @param code The class of error. + * + * @param descr The description, which can be a format string. + * + * @param ... The arguments for the format string. + */ +void _ssh_set_error(void *error, + int code, + const char *function, + const char *descr, ...) +{ + struct ssh_common_struct *err = error; + va_list va; + + va_start(va, descr); + vsnprintf(err->error.error_buffer, ERROR_BUFFERLEN, descr, va); + va_end(va); + + err->error.error_code = code; + if (ssh_get_log_level() == SSH_LOG_TRACE) { + ssh_log_function(SSH_LOG_TRACE, + function, + err->error.error_buffer); + } +} + +/** + * @internal + * + * @brief Registers an out of memory error + * + * @param error The place to store the error. + * + */ +void _ssh_set_error_oom(void *error, const char *function) +{ + struct error_struct *err = error; + + snprintf(err->error_buffer, sizeof(err->error_buffer), + "%s: Out of memory", function); + err->error_code = SSH_FATAL; +} + +/** + * @internal + * + * @brief Registers an invalid argument error + * + * @param error The place to store the error. + * + * @param function The function the error happened in. + * + */ +void _ssh_set_error_invalid(void *error, const char *function) +{ + _ssh_set_error(error, SSH_FATAL, function, + "Invalid argument in %s", function); +} + +/** + * @internal + * + * @brief Reset the error code and message + * + * @param error The place to reset the error. + */ +void ssh_reset_error(void *error) +{ + struct ssh_common_struct *err = error; + + ZERO_STRUCT(err->error.error_buffer); + err->error.error_code = 0; +} + +/** + * @brief Retrieve the error text message from the last error. + * + * @param error An ssh_session or ssh_bind. + * + * @return A static string describing the error. + */ +const char *ssh_get_error(void *error) { + struct error_struct *err = error; + + return err->error_buffer; +} + +/** + * @brief Retrieve the error code from the last error. + * + * @param error An ssh_session or ssh_bind. + * + * \return SSH_NO_ERROR No error occurred\n + * SSH_REQUEST_DENIED The last request was denied but situation is + * recoverable\n + * SSH_FATAL A fatal error occurred. This could be an unexpected + * disconnection\n + * + * Other error codes are internal but can be considered the same as + * SSH_FATAL. + */ +int ssh_get_error_code(void *error) { + struct error_struct *err = error; + + return err->error_code; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/external/bcrypt_pbkdf.c b/src/libs/libssh-0.12.2/src/external/bcrypt_pbkdf.c new file mode 100644 index 000000000000..b05b87f21e52 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/bcrypt_pbkdf.c @@ -0,0 +1,191 @@ +/* $OpenBSD: bcrypt_pbkdf.c,v 1.4 2013/07/29 00:55:53 tedu Exp $ */ +/* + * Copyright (c) 2013 Ted Unangst + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +//#include "includes.h" + +#ifndef HAVE_BCRYPT_PBKDF + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/wrapper.h" +#include +#include +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include "libssh/blf.h" +#include "libssh/pki_priv.h" +#ifndef SHA512_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH SHA512_DIGEST_LEN +#endif + +/* + * pkcs #5 pbkdf2 implementation using the "bcrypt" hash + * + * The bcrypt hash function is derived from the bcrypt password hashing + * function with the following modifications: + * 1. The input password and salt are preprocessed with SHA512. + * 2. The output length is expanded to 256 bits. + * 3. Subsequently the magic string to be encrypted is lengthened and modified + * to "OxychromaticBlowfishSwatDynamite" + * 4. The hash function is defined to perform 64 rounds of initial state + * expansion. (More rounds are performed by iterating the hash.) + * + * Note that this implementation pulls the SHA512 operations into the caller + * as a performance optimization. + * + * One modification from official pbkdf2. Instead of outputting key material + * linearly, we mix it. pbkdf2 has a known weakness where if one uses it to + * generate (i.e.) 512 bits of key material for use as two 256 bit keys, an + * attacker can merely run once through the outer loop below, but the user + * always runs it twice. Shuffling output bytes requires computing the + * entirety of the key material to assemble any subkey. This is something a + * wise caller could do; we just do it for you. + */ + +#define BCRYPT_BLOCKS 8 +#define BCRYPT_HASHSIZE (BCRYPT_BLOCKS * 4) + +static void +bcrypt_hash(ssh_blf_ctx *state, uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) +{ + uint8_t ciphertext[BCRYPT_HASHSIZE] = + "OxychromaticBlowfishSwatDynamite"; + uint32_t cdata[BCRYPT_BLOCKS]; + int i; + uint16_t j; + uint16_t shalen = SHA512_DIGEST_LENGTH; + + /* key expansion */ + Blowfish_initstate(state); + Blowfish_expandstate(state, sha2salt, shalen, sha2pass, shalen); + for (i = 0; i < 64; i++) { + Blowfish_expand0state(state, sha2salt, shalen); + Blowfish_expand0state(state, sha2pass, shalen); + } + + /* encryption */ + j = 0; + for (i = 0; i < BCRYPT_BLOCKS; i++) + cdata[i] = Blowfish_stream2word(ciphertext, sizeof(ciphertext), + &j); + for (i = 0; i < 64; i++) + ssh_blf_enc(state, cdata, BCRYPT_BLOCKS/2); + + /* copy out */ + for (i = 0; i < BCRYPT_BLOCKS; i++) { + out[4 * i + 3] = (cdata[i] >> 24) & 0xff; + out[4 * i + 2] = (cdata[i] >> 16) & 0xff; + out[4 * i + 1] = (cdata[i] >> 8) & 0xff; + out[4 * i + 0] = cdata[i] & 0xff; + } + + /* zap */ + ssh_burn(ciphertext, sizeof(ciphertext)); + ssh_burn(cdata, sizeof(cdata)); +} + +int +bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltlen, + uint8_t *key, size_t keylen, unsigned int rounds) +{ + uint8_t sha2pass[SHA512_DIGEST_LENGTH]; + uint8_t sha2salt[SHA512_DIGEST_LENGTH]; + uint8_t out[BCRYPT_HASHSIZE]; + uint8_t tmpout[BCRYPT_HASHSIZE]; + uint8_t *countsalt; + size_t i, j, amt, stride; + uint32_t count; + size_t origkeylen = keylen; + ssh_blf_ctx *state; + SHA512CTX ctx; + + /* nothing crazy */ + if (rounds < 1) + return -1; + if (passlen == 0 || saltlen == 0 || keylen == 0 || + keylen > sizeof(out) * sizeof(out) || saltlen > 1<<20) + return -1; + if ((countsalt = calloc(1, saltlen + 4)) == NULL) + return -1; + stride = (keylen + sizeof(out) - 1) / sizeof(out); + amt = (keylen + stride - 1) / stride; + + memcpy(countsalt, salt, saltlen); + + state = malloc(sizeof(*state)); + if (state == NULL) { + free(countsalt); + return -1; + } + + /* collapse password */ + ctx = sha512_init(); + sha512_update(ctx, pass, passlen); + sha512_final(sha2pass, ctx); + + /* generate key, sizeof(out) at a time */ + for (count = 1; keylen > 0; count++) { + countsalt[saltlen + 0] = (count >> 24) & 0xff; + countsalt[saltlen + 1] = (count >> 16) & 0xff; + countsalt[saltlen + 2] = (count >> 8) & 0xff; + countsalt[saltlen + 3] = count & 0xff; + + /* first round, salt is salt */ + ctx = sha512_init(); + sha512_update(ctx, countsalt, saltlen + 4); + sha512_final(sha2salt, ctx); + + bcrypt_hash(state, sha2pass, sha2salt, tmpout); + memcpy(out, tmpout, sizeof(out)); + + for (i = 1; i < rounds; i++) { + /* subsequent rounds, salt is previous output */ + ctx = sha512_init(); + sha512_update(ctx, tmpout, sizeof(tmpout)); + sha512_final(sha2salt, ctx); + bcrypt_hash(state, sha2pass, sha2salt, tmpout); + for (j = 0; j < sizeof(out); j++) + out[j] ^= tmpout[j]; + } + + /* + * pbkdf2 deviation: output the key material non-linearly. + */ + amt = MIN(amt, keylen); + for (i = 0; i < amt; i++) { + size_t dest = i * stride + (count - 1); + if (dest >= origkeylen) { + break; + } + key[dest] = out[i]; + } + keylen -= i; + } + + /* zap */ + ssh_burn(out, sizeof(out)); + ssh_burn(state, sizeof(*state)); + + free(state); + free(countsalt); + + return 0; +} +#endif /* HAVE_BCRYPT_PBKDF */ diff --git a/src/libs/libssh-0.12.2/src/external/blowfish.c b/src/libs/libssh-0.12.2/src/external/blowfish.c new file mode 100644 index 000000000000..42d5df1eeb3d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/blowfish.c @@ -0,0 +1,691 @@ +/* $OpenBSD: blowfish.c,v 1.20 2021/11/29 01:04:45 djm Exp $ */ +/* + * Blowfish block cipher for OpenBSD + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Implementation advice by David Mazieres . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code is derived from section 14.3 and the given source + * in section V of Applied Cryptography, second edition. + * Blowfish is an unpatented fast block cipher designed by + * Bruce Schneier. + */ + + +#if !defined(HAVE_BCRYPT_PBKDF) && (!defined(HAVE_BLOWFISH_INITSTATE) || \ + !defined(HAVE_BLOWFISH_EXPAND0STATE) || !defined(HAVE_BLF_ENC)) + +#if 0 +#include /* used for debugging */ +#include +#endif + +#include +#include + +#include "libssh/blf.h" + +#undef inline +#ifdef __GNUC__ +#define inline __inline +#else /* !__GNUC__ */ +#define inline +#endif /* !__GNUC__ */ + +/* Function for Feistel Networks */ + +#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \ + + (s)[0x100 + (((x)>>16)&0xFF)]) \ + ^ (s)[0x200 + (((x)>> 8)&0xFF)]) \ + + (s)[0x300 + ( (x) &0xFF)]) + +#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n]) + +void +Blowfish_encipher(ssh_blf_ctx *c, uint32_t *xl, uint32_t *xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t *s = c->S[0]; + uint32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[0]; + BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2); + BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4); + BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6); + BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8); + BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10); + BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12); + BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14); + BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16); + + *xl = Xr ^ p[17]; + *xr = Xl; +} + +void +Blowfish_decipher(ssh_blf_ctx *c, uint32_t *xl, uint32_t *xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t *s = c->S[0]; + uint32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[17]; + BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15); + BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13); + BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11); + BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9); + BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7); + BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5); + BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3); + BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1); + + *xl = Xr ^ p[0]; + *xr = Xl; +} + +void +Blowfish_initstate(ssh_blf_ctx *c) +{ + /* P-box and S-box tables initialized with digits of Pi */ + + static const ssh_blf_ctx initstate = + { { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a}, + { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7}, + { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0}, + { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6} + }, + { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } }; + + *c = initstate; +} + +uint32_t +Blowfish_stream2word(const uint8_t *data, uint16_t databytes, + uint16_t *current) +{ + uint8_t i; + uint16_t j; + uint32_t temp; + + temp = 0x00000000; + j = *current; + + for (i = 0; i < 4; i++, j++) { + if (j >= databytes) + j = 0; + temp = (temp << 8) | data[j]; + } + + *current = j; + return temp; +} + +void +Blowfish_expand0state(ssh_blf_ctx *c, const uint8_t *key, uint16_t keybytes) +{ + uint16_t i; + uint16_t j; + uint16_t k; + uint32_t temp; + uint32_t datal; + uint32_t datar; + + j = 0; + for (i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for (i = 0; i < BLF_N + 2; i += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for (i = 0; i < 4; i++) { + for (k = 0; k < 256; k += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } +} + + +void +Blowfish_expandstate(ssh_blf_ctx *c, const uint8_t *data, uint16_t databytes, + const uint8_t *key, uint16_t keybytes) +{ + uint16_t i; + uint16_t j; + uint16_t k; + uint32_t temp; + uint32_t datal; + uint32_t datar; + + j = 0; + for (i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for (i = 0; i < BLF_N + 2; i += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for (i = 0; i < 4; i++) { + for (k = 0; k < 256; k += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } + +} + +void +ssh_blf_key(ssh_blf_ctx *c, const uint8_t *k, uint16_t len) +{ + /* Initialize S-boxes and subkeys with Pi */ + Blowfish_initstate(c); + + /* Transform S-boxes and subkeys with key */ + Blowfish_expand0state(c, k, len); +} + +void +ssh_blf_enc(ssh_blf_ctx *c, uint32_t *data, uint16_t blocks) +{ + uint32_t *d; + uint16_t i; + + d = data; + for (i = 0; i < blocks; i++) { + Blowfish_encipher(c, d, d + 1); + d += 2; + } +} + +void +ssh_blf_dec(ssh_blf_ctx *c, uint32_t *data, uint16_t blocks) +{ + uint32_t *d; + uint16_t i; + + d = data; + for (i = 0; i < blocks; i++) { + Blowfish_decipher(c, d, d + 1); + d += 2; + } +} + +void +ssh_blf_ecb_encrypt(ssh_blf_ctx *c, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i; + + for (i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +ssh_blf_ecb_decrypt(ssh_blf_ctx *c, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i; + + for (i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +ssh_blf_cbc_encrypt(ssh_blf_ctx *c, uint8_t *iv, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i, j; + + for (i = 0; i < len; i += 8) { + for (j = 0; j < 8; j++) + data[j] ^= iv[j]; + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + iv = data; + data += 8; + } +} + +void +ssh_blf_cbc_decrypt(ssh_blf_ctx *c, uint8_t *iva, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint8_t *iv; + uint32_t i, j; + + iv = data + len - 16; + data = data + len - 8; + for (i = len - 8; i >= 8; i -= 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for (j = 0; j < 8; j++) + data[j] ^= iv[j]; + iv -= 8; + data -= 8; + } + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for (j = 0; j < 8; j++) + data[j] ^= iva[j]; +} + +#if 0 +void +report(uint32_t data[], uint16_t len) +{ + uint16_t i; + for (i = 0; i < len; i += 2) + printf("Block %0hd: %08lx %08lx.\n", + i / 2, data[i], data[i + 1]); +} +void +main(void) +{ + + ssh_blf_ctx c; + char key[] = "AAAAA"; + char key2[] = "abcdefghijklmnopqrstuvwxyz"; + + uint32_t data[10]; + uint32_t data2[] = + {0x424c4f57l, 0x46495348l}; + + uint16_t i; + + /* First test */ + for (i = 0; i < 10; i++) + data[i] = i; + + ssh_blf_key(&c, (uint8_t *) key, 5); + ssh_blf_enc(&c, data, 5); + ssh_blf_dec(&c, data, 1); + ssh_blf_dec(&c, data + 2, 4); + printf("Should read as 0 - 9.\n"); + report(data, 10); + + /* Second test */ + ssh_blf_key(&c, (uint8_t *) key2, strlen(key2)); + ssh_blf_enc(&c, data2, 1); + printf("\nShould read as: 0x324ed0fe 0xf413a203.\n"); + report(data2, 2); + ssh_blf_dec(&c, data2, 1); + report(data2, 2); +} +#endif + +#endif /* !defined(HAVE_BCRYPT_PBKDF) && (!defined(HAVE_BLOWFISH_INITSTATE) || \ + !defined(HAVE_BLOWFISH_EXPAND0STATE) || !defined(HAVE_BLF_ENC)) */ diff --git a/src/libs/libssh-0.12.2/src/external/chacha.c b/src/libs/libssh-0.12.2/src/external/chacha.c new file mode 100644 index 000000000000..8d1ccca6ed57 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/chacha.c @@ -0,0 +1,216 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. + */ + +#include +#include +#include + +#include "libssh/chacha.h" + +typedef struct chacha_ctx chacha_ctx; + +#define U8C(v) (v##U) +#define U32C(v) (v##U) + +#define U8V(v) ((uint8_t)(v) & U8C(0xFF)) +#define U32V(v) ((uint32_t)(v) & U32C(0xFFFFFFFF)) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#define U8TO32_LITTLE(p) \ + (((uint32_t)((p)[0]) ) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +void +chacha_keysetup(chacha_ctx *x,const uint8_t *k,uint32_t kbits) +{ + const char *constants; + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +void +chacha_ivsetup(chacha_ctx *x, const uint8_t *iv, const uint8_t *counter) +{ + x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0); + x->input[13] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 4); + x->input[14] = U8TO32_LITTLE(iv + 0); + x->input[15] = U8TO32_LITTLE(iv + 4); +} + +void +chacha_encrypt_bytes(chacha_ctx *x,const uint8_t *m,uint8_t *c,uint32_t bytes) +{ + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + uint8_t *ctarget = NULL; + uint8_t tmp[64]; + uint32_t i; + + if (!bytes) return; + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = 20;i > 0;i -= 2) { + QUARTERROUND( x0, x4, x8,x12) + QUARTERROUND( x1, x5, x9,x13) + QUARTERROUND( x2, x6,x10,x14) + QUARTERROUND( x3, x7,x11,x15) + QUARTERROUND( x0, x5,x10,x15) + QUARTERROUND( x1, x6,x11,x12) + QUARTERROUND( x2, x7, x8,x13) + QUARTERROUND( x3, x4, x9,x14) + } + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + + x0 = XOR(x0,U8TO32_LITTLE(m + 0)); + x1 = XOR(x1,U8TO32_LITTLE(m + 4)); + x2 = XOR(x2,U8TO32_LITTLE(m + 8)); + x3 = XOR(x3,U8TO32_LITTLE(m + 12)); + x4 = XOR(x4,U8TO32_LITTLE(m + 16)); + x5 = XOR(x5,U8TO32_LITTLE(m + 20)); + x6 = XOR(x6,U8TO32_LITTLE(m + 24)); + x7 = XOR(x7,U8TO32_LITTLE(m + 28)); + x8 = XOR(x8,U8TO32_LITTLE(m + 32)); + x9 = XOR(x9,U8TO32_LITTLE(m + 36)); + x10 = XOR(x10,U8TO32_LITTLE(m + 40)); + x11 = XOR(x11,U8TO32_LITTLE(m + 44)); + x12 = XOR(x12,U8TO32_LITTLE(m + 48)); + x13 = XOR(x13,U8TO32_LITTLE(m + 52)); + x14 = XOR(x14,U8TO32_LITTLE(m + 56)); + x15 = XOR(x15,U8TO32_LITTLE(m + 60)); + + j12 = PLUSONE(j12); + if (!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0,x0); + U32TO8_LITTLE(c + 4,x1); + U32TO8_LITTLE(c + 8,x2); + U32TO8_LITTLE(c + 12,x3); + U32TO8_LITTLE(c + 16,x4); + U32TO8_LITTLE(c + 20,x5); + U32TO8_LITTLE(c + 24,x6); + U32TO8_LITTLE(c + 28,x7); + U32TO8_LITTLE(c + 32,x8); + U32TO8_LITTLE(c + 36,x9); + U32TO8_LITTLE(c + 40,x10); + U32TO8_LITTLE(c + 44,x11); + U32TO8_LITTLE(c + 48,x12); + U32TO8_LITTLE(c + 52,x13); + U32TO8_LITTLE(c + 56,x14); + U32TO8_LITTLE(c + 60,x15); + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) ctarget[i] = c[i]; + } + x->input[12] = j12; + x->input[13] = j13; + return; + } + bytes -= 64; + c += 64; + m += 64; + } +} diff --git a/src/libs/libssh-0.12.2/src/external/curve25519_ref.c b/src/libs/libssh-0.12.2/src/external/curve25519_ref.c new file mode 100644 index 000000000000..6ffb14a87fe0 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/curve25519_ref.c @@ -0,0 +1,271 @@ +/* +version 20081011 +Matthew Dempsky +Public domain. +Derived from public domain code by D. J. Bernstein. +*/ + +#include "libssh/curve25519.h" +static const unsigned char base[32] = {9}; + +int crypto_scalarmult_base(unsigned char *q, + const unsigned char *n) +{ + return crypto_scalarmult(q,n,base); +} + +static void add(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 31;++j) { u += a[j] + b[j]; out[j] = u & 255; u >>= 8; } + u += a[31] + b[31]; out[31] = u; +} + +static void sub(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int j; + unsigned int u; + u = 218; + for (j = 0;j < 31;++j) { + u += a[j] + 65280 - b[j]; + out[j] = u & 255; + u >>= 8; + } + u += a[31] - b[31]; + out[31] = u; +} + +static void squeeze(unsigned int a[32]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; } + u += a[31]; a[31] = u & 127; + u = 19 * (u >> 7); + for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; } + u += a[31]; a[31] = u; +} + +static const unsigned int minusp[32] = { + 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128 +} ; + +static void freeze(unsigned int a[32]) +{ + unsigned int aorig[32]; + unsigned int j; + unsigned int negative; + + for (j = 0;j < 32;++j) aorig[j] = a[j]; + add(a,a,minusp); + negative = -((a[31] >> 7) & 1); + for (j = 0;j < 32;++j) a[j] ^= negative & (aorig[j] ^ a[j]); +} + +static void mult(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int i; + unsigned int j; + unsigned int u; + + for (i = 0;i < 32;++i) { + u = 0; + for (j = 0;j <= i;++j) u += a[j] * b[i - j]; + for (j = i + 1;j < 32;++j) u += 38 * a[j] * b[i + 32 - j]; + out[i] = u; + } + squeeze(out); +} + +static void mult121665(unsigned int out[32],const unsigned int a[32]) +{ + unsigned int j; + unsigned int u; + + u = 0; + for (j = 0;j < 31;++j) { u += 121665 * a[j]; out[j] = u & 255; u >>= 8; } + u += 121665 * a[31]; out[31] = u & 127; + u = 19 * (u >> 7); + for (j = 0;j < 31;++j) { u += out[j]; out[j] = u & 255; u >>= 8; } + u += out[j]; out[j] = u; +} + +static void square(unsigned int out[32],const unsigned int a[32]) +{ + unsigned int i; + unsigned int j; + unsigned int u; + + for (i = 0;i < 32;++i) { + u = 0; + for (j = 0;j < i - j;++j) u += a[j] * a[i - j]; + for (j = i + 1;j < i + 32 - j;++j) u += 38 * a[j] * a[i + 32 - j]; + u *= 2; + if ((i & 1) == 0) { + u += a[i / 2] * a[i / 2]; + u += 38 * a[i / 2 + 16] * a[i / 2 + 16]; + } + out[i] = u; + } + squeeze(out); +} + +static void c_select(unsigned int p[64],unsigned int q[64],const unsigned int r[64],const unsigned int s[64],unsigned int b) +{ + unsigned int j; + unsigned int t; + unsigned int bminus1; + + bminus1 = b - 1; + for (j = 0;j < 64;++j) { + t = bminus1 & (r[j] ^ s[j]); + p[j] = s[j] ^ t; + q[j] = r[j] ^ t; + } +} + +static void mainloop(unsigned int work[64],const unsigned char e[32]) +{ + unsigned int xzm1[64]; + unsigned int xzm[64]; + unsigned int xzmb[64]; + unsigned int xzm1b[64]; + unsigned int xznb[64]; + unsigned int xzn1b[64]; + unsigned int a0[64]; + unsigned int a1[64]; + unsigned int b0[64]; + unsigned int b1[64]; + unsigned int c1[64]; + unsigned int r[32]; + unsigned int s[32]; + unsigned int t[32]; + unsigned int u[32]; + unsigned int j; + unsigned int b; + int pos; + + for (j = 0;j < 32;++j) xzm1[j] = work[j]; + xzm1[32] = 1; + for (j = 33;j < 64;++j) xzm1[j] = 0; + + xzm[0] = 1; + for (j = 1;j < 64;++j) xzm[j] = 0; + + for (pos = 254;pos >= 0;--pos) { + b = e[pos / 8] >> (pos & 7); + b &= 1; + c_select(xzmb,xzm1b,xzm,xzm1,b); + add(a0,xzmb,xzmb + 32); + sub(a0 + 32,xzmb,xzmb + 32); + add(a1,xzm1b,xzm1b + 32); + sub(a1 + 32,xzm1b,xzm1b + 32); + square(b0,a0); + square(b0 + 32,a0 + 32); + mult(b1,a1,a0 + 32); + mult(b1 + 32,a1 + 32,a0); + add(c1,b1,b1 + 32); + sub(c1 + 32,b1,b1 + 32); + square(r,c1 + 32); + sub(s,b0,b0 + 32); + mult121665(t,s); + add(u,t,b0); + mult(xznb,b0,b0 + 32); + mult(xznb + 32,s,u); + square(xzn1b,c1); + mult(xzn1b + 32,r,work); + c_select(xzm,xzm1,xznb,xzn1b,b); + } + + for (j = 0;j < 64;++j) work[j] = xzm[j]; +} + +static void recip(unsigned int out[32],const unsigned int z[32]) +{ + unsigned int z2[32]; + unsigned int z9[32]; + unsigned int z11[32]; + unsigned int z2_5_0[32]; + unsigned int z2_10_0[32]; + unsigned int z2_20_0[32]; + unsigned int z2_50_0[32]; + unsigned int z2_100_0[32]; + unsigned int t0[32]; + unsigned int t1[32]; + int i; + + /* 2 */ square(z2,z); + /* 4 */ square(t1,z2); + /* 8 */ square(t0,t1); + /* 9 */ mult(z9,t0,z); + /* 11 */ mult(z11,z9,z2); + /* 22 */ square(t0,z11); + /* 2^5 - 2^0 = 31 */ mult(z2_5_0,t0,z9); + + /* 2^6 - 2^1 */ square(t0,z2_5_0); + /* 2^7 - 2^2 */ square(t1,t0); + /* 2^8 - 2^3 */ square(t0,t1); + /* 2^9 - 2^4 */ square(t1,t0); + /* 2^10 - 2^5 */ square(t0,t1); + /* 2^10 - 2^0 */ mult(z2_10_0,t0,z2_5_0); + + /* 2^11 - 2^1 */ square(t0,z2_10_0); + /* 2^12 - 2^2 */ square(t1,t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^20 - 2^0 */ mult(z2_20_0,t1,z2_10_0); + + /* 2^21 - 2^1 */ square(t0,z2_20_0); + /* 2^22 - 2^2 */ square(t1,t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^40 - 2^0 */ mult(t0,t1,z2_20_0); + + /* 2^41 - 2^1 */ square(t1,t0); + /* 2^42 - 2^2 */ square(t0,t1); + /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t1,t0); square(t0,t1); } + /* 2^50 - 2^0 */ mult(z2_50_0,t0,z2_10_0); + + /* 2^51 - 2^1 */ square(t0,z2_50_0); + /* 2^52 - 2^2 */ square(t1,t0); + /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^100 - 2^0 */ mult(z2_100_0,t1,z2_50_0); + + /* 2^101 - 2^1 */ square(t1,z2_100_0); + /* 2^102 - 2^2 */ square(t0,t1); + /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { square(t1,t0); square(t0,t1); } + /* 2^200 - 2^0 */ mult(t1,t0,z2_100_0); + + /* 2^201 - 2^1 */ square(t0,t1); + /* 2^202 - 2^2 */ square(t1,t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^250 - 2^0 */ mult(t0,t1,z2_50_0); + + /* 2^251 - 2^1 */ square(t1,t0); + /* 2^252 - 2^2 */ square(t0,t1); + /* 2^253 - 2^3 */ square(t1,t0); + /* 2^254 - 2^4 */ square(t0,t1); + /* 2^255 - 2^5 */ square(t1,t0); + /* 2^255 - 21 */ mult(out,t1,z11); +} + +int crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p) +{ + unsigned int work[96]; + unsigned char e[32]; + unsigned int i; + for (i = 0;i < 32;++i) e[i] = n[i]; + e[0] &= 248; + e[31] &= 127; + e[31] |= 64; + for (i = 0;i < 32;++i) work[i] = p[i]; + mainloop(work,e); + recip(work + 32,work + 32); + mult(work + 64,work,work + 32); + freeze(work + 64); + for (i = 0;i < 32;++i) q[i] = work[64 + i]; + return 0; +} diff --git a/src/libs/libssh-0.12.2/src/external/ed25519.c b/src/libs/libssh-0.12.2/src/external/ed25519.c new file mode 100644 index 000000000000..41b5f289854e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/ed25519.c @@ -0,0 +1,222 @@ +/* $OpenBSD: ed25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ed25519.c + */ + +#include "config.h" + +#include "libssh/libcrypto.h" +#include "libssh/wrapper.h" +#include "libssh/ge25519.h" +#include "libssh/sc25519.h" +#include "libssh/ed25519.h" + +/* + * Public Domain, Author: Daniel J. Bernstein + * Copied from nacl-20110221/crypto_verify/32/ref/verify.c + */ + +static int crypto_verify_32(const unsigned char *x,const unsigned char *y) +{ + unsigned int differentbits = 0; +#define F(i) differentbits |= x[i] ^ y[i]; + F(0) + F(1) + F(2) + F(3) + F(4) + F(5) + F(6) + F(7) + F(8) + F(9) + F(10) + F(11) + F(12) + F(13) + F(14) + F(15) + F(16) + F(17) + F(18) + F(19) + F(20) + F(21) + F(22) + F(23) + F(24) + F(25) + F(26) + F(27) + F(28) + F(29) + F(30) + F(31) + + return (1 & ((differentbits - 1) >> 8)) - 1; +} + +static void get_hram(unsigned char *hram, + const unsigned char *sm, + const unsigned char *pk, + unsigned char *playground, + uint64_t smlen) +{ + uint64_t i; + SHA512CTX ctx; + for (i = 0;i < 32;++i) playground[i] = sm[i]; + for (i = 32;i < 64;++i) playground[i] = pk[i-32]; + for (i = 64;i < smlen;++i) playground[i] = sm[i]; + + ctx = sha512_init(); + sha512_update(ctx, playground, smlen); + sha512_final(hram, ctx); +} + + +int crypto_sign_ed25519_keypair(ed25519_pubkey pk, + ed25519_privkey sk) +{ + sc25519 scsk; + ge25519 gepk; + SHA512CTX ctx; + unsigned char extsk[64]; + int i; + int ok; + + ok = ssh_get_random(sk, 32, 0); + if (!ok) { + return -1; + } + + ctx = sha512_init(); + sha512_update(ctx, sk, 32); + sha512_final(extsk, ctx); + extsk[0] &= 248; + extsk[31] &= 127; + extsk[31] |= 64; + + sc25519_from32bytes(&scsk,extsk); + + ge25519_scalarmult_base(&gepk, &scsk); + ge25519_pack(pk, &gepk); + for(i=0;i<32;i++) { + sk[32 + i] = pk[i]; + } + + return 0; +} + +int crypto_sign_ed25519(unsigned char *sm, + uint64_t *smlen, + const unsigned char *m, + uint64_t mlen, + const ed25519_privkey sk) +{ + sc25519 sck, scs, scsk; + ge25519 ger; + SHA512CTX ctx; + unsigned char r[32]; + unsigned char s[32]; + unsigned char extsk[64]; + uint64_t i; + unsigned char hmg[SHA512_DIGEST_LEN]; + unsigned char hram[SHA512_DIGEST_LEN]; + + ctx = sha512_init(); + sha512_update(ctx, sk, 32); + sha512_final(extsk, ctx); + + extsk[0] &= 248; + extsk[31] &= 127; + extsk[31] |= 64; + + *smlen = mlen + 64; + for (i = 0;i < mlen; i++) { + sm[64 + i] = m[i]; + } + for (i = 0;i < 32; i++) { + sm[32 + i] = extsk[32+i]; + } + + /* Generate k as h(extsk[32],...,extsk[63],m) */ + ctx = sha512_init(); + sha512_update(ctx, sm + 32, mlen + 32); + sha512_final(hmg, ctx); + + /* Computation of R */ + sc25519_from64bytes(&sck, hmg); + ge25519_scalarmult_base(&ger, &sck); + ge25519_pack(r, &ger); + + /* Computation of s */ + for (i = 0; i < 32; i++) { + sm[i] = r[i]; + } + + get_hram(hram, sm, sk+32, sm, mlen+64); + + sc25519_from64bytes(&scs, hram); + sc25519_from32bytes(&scsk, extsk); + sc25519_mul(&scs, &scs, &scsk); + + sc25519_add(&scs, &scs, &sck); + + sc25519_to32bytes(s,&scs); /* cat s */ + for (i = 0;i < 32; i++) { + sm[32 + i] = s[i]; + } + + return 0; +} + +int crypto_sign_ed25519_open(unsigned char *m, + uint64_t *mlen, + const unsigned char *sm, + uint64_t smlen, + const ed25519_pubkey pk) +{ + unsigned int i; + int ret; + unsigned char t2[32]; + ge25519 get1, get2; + sc25519 schram, scs; + unsigned char hram[SHA512_DIGEST_LEN]; + + *mlen = (uint64_t) -1; + if (smlen < 64) return -1; + + if (ge25519_unpackneg_vartime(&get1, pk)) { + return -1; + } + + get_hram(hram,sm,pk,m,smlen); + + sc25519_from64bytes(&schram, hram); + + sc25519_from32bytes(&scs, sm+32); + + ge25519_double_scalarmult_vartime(&get2, + &get1, + &schram, + &ge25519_base, + &scs); + ge25519_pack(t2, &get2); + + ret = crypto_verify_32(sm, t2); + if (ret != 0) { + for (i = 0; i < smlen - 64; i++) { + m[i] = sm[i + 64]; + } + *mlen = smlen-64; + } else { + for (i = 0; i < smlen - 64; i++) { + m[i] = 0; + } + } + + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/external/fe25519.c b/src/libs/libssh-0.12.2/src/external/fe25519.c new file mode 100644 index 000000000000..a7f26c9fcd4e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/fe25519.c @@ -0,0 +1,418 @@ +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/fe25519.c + */ + +#include "config.h" + +#define WINDOWSIZE 1 /* Should be 1,2, or 4 */ +#define WINDOWMASK ((1<>= 31; /* 1: yes; 0: no */ + return x; +} + +static uint32_t ge(uint32_t a,uint32_t b) /* 16-bit inputs */ +{ + unsigned int x = a; + + x -= (unsigned int) b; /* 0..65535: yes; 4294901761..4294967295: no */ + x >>= 31; /* 0: yes; 1: no */ + x ^= 1; /* 1: yes; 0: no */ + + return x; +} + +static uint32_t times19(uint32_t a) +{ + return (a << 4) + (a << 1) + a; +} + +static uint32_t times38(uint32_t a) +{ + return (a << 5) + (a << 2) + (a << 1); +} + +static void reduce_add_sub(fe25519 *r) +{ + uint32_t t; + int i,rep; + + for(rep = 0; rep < 4; rep++) { + t = r->v[31] >> 7; + r->v[31] &= 127; + t = times19(t); + r->v[0] += t; + for(i = 0; i < 31; i++) { + t = r->v[i] >> 8; + r->v[i+1] += t; + r->v[i] &= 255; + } + } +} + +static void reduce_mul(fe25519 *r) +{ + uint32_t t; + int i,rep; + + for(rep = 0; rep < 2; rep++) { + t = r->v[31] >> 7; + r->v[31] &= 127; + t = times19(t); + r->v[0] += t; + for(i = 0; i < 31; i++) { + t = r->v[i] >> 8; + r->v[i+1] += t; + r->v[i] &= 255; + } + } +} + +/* reduction modulo 2^255-19 */ +void fe25519_freeze(fe25519 *r) +{ + int i; + uint32_t m = equal(r->v[31],127); + + for (i = 30; i > 0; i--) { + m &= equal(r->v[i],255); + } + m &= ge(r->v[0],237); + + m = -m; + + r->v[31] -= m&127; + for (i = 30; i > 0; i--) { + r->v[i] -= m&255; + } + r->v[0] -= m&237; +} + +void fe25519_unpack(fe25519 *r, const unsigned char x[32]) +{ + int i; + + for (i = 0;i < 32; i++) { + r->v[i] = x[i]; + } + + r->v[31] &= 127; +} + +/* Assumes input x being reduced below 2^255 */ +void fe25519_pack(unsigned char r[32], const fe25519 *x) +{ + int i; + + fe25519 y = *x; + fe25519_freeze(&y); + + for (i = 0; i < 32; i++) { + r[i] = y.v[i]; + } +} + +uint32_t fe25519_iszero(const fe25519 *x) +{ + int i; + uint32_t r; + + fe25519 t = *x; + fe25519_freeze(&t); + + r = equal(t.v[0],0); + for (i = 1; i < 32; i++) { + r &= equal(t.v[i],0); + } + + return r; +} + +int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y) +{ + int i; + + fe25519 t1 = *x; + fe25519 t2 = *y; + fe25519_freeze(&t1); + fe25519_freeze(&t2); + + for (i = 0; i < 32; i++) { + if(t1.v[i] != t2.v[i]) { + return 0; + } + } + + return 1; +} + +void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b) +{ + int i; + uint32_t mask = b; + + mask = -mask; + + for (i = 0; i < 32; i++) { + r->v[i] ^= mask & (x->v[i] ^ r->v[i]); + } +} + +unsigned char fe25519_getparity(const fe25519 *x) +{ + fe25519 t = *x; + fe25519_freeze(&t); + + return t.v[0] & 1; +} + +void fe25519_setone(fe25519 *r) +{ + int i; + + r->v[0] = 1; + for (i = 1; i < 32; i++) { + r->v[i]=0; + } +} + +void fe25519_setzero(fe25519 *r) +{ + int i; + + for (i = 0; i < 32; i++) { + r->v[i]=0; + } +} + +void fe25519_neg(fe25519 *r, const fe25519 *x) +{ + fe25519 t; + int i; + + for (i = 0; i < 32; i++) { + t.v[i]=x->v[i]; + } + + fe25519_setzero(r); + fe25519_sub(r, r, &t); +} + +void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i; + + for (i = 0; i < 32; i++) { + r->v[i] = x->v[i] + y->v[i]; + } + + reduce_add_sub(r); +} + +void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i; + uint32_t t[32]; + + t[0] = x->v[0] + 0x1da; + t[31] = x->v[31] + 0xfe; + + for (i = 1; i < 31; i++) { + t[i] = x->v[i] + 0x1fe; + } + + for (i = 0; i < 32; i++) { + r->v[i] = t[i] - y->v[i]; + } + + reduce_add_sub(r); +} + +void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i,j; + uint32_t t[63]; + + for (i = 0; i < 63; i++) { + t[i] = 0; + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 32; j++) { + t[i+j] += x->v[i] * y->v[j]; + } + } + + for (i = 32; i < 63; i++) { + r->v[i-32] = t[i-32] + times38(t[i]); + } + r->v[31] = t[31]; /* result now in r[0]...r[31] */ + + reduce_mul(r); +} + +void fe25519_square(fe25519 *r, const fe25519 *x) +{ + fe25519_mul(r, x, x); +} + +void fe25519_invert(fe25519 *r, const fe25519 *x) +{ + fe25519 z2; + fe25519 z9; + fe25519 z11; + fe25519 z2_5_0; + fe25519 z2_10_0; + fe25519 z2_20_0; + fe25519 z2_50_0; + fe25519 z2_100_0; + fe25519 t0; + fe25519 t1; + int i; + + /* 2 */ fe25519_square(&z2, x); + /* 4 */ fe25519_square(&t1, &z2); + /* 8 */ fe25519_square(&t0, &t1); + /* 9 */ fe25519_mul(&z9, &t0, x); + /* 11 */ fe25519_mul(&z11, &z9, &z2); + /* 22 */ fe25519_square(&t0, &z11); + /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0, &t0, &z9); + + /* 2^6 - 2^1 */ fe25519_square(&t0, &z2_5_0); + /* 2^7 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^8 - 2^3 */ fe25519_square(&t0, &t1); + /* 2^9 - 2^4 */ fe25519_square(&t1, &t0); + /* 2^10 - 2^5 */ fe25519_square(&t0, &t1); + /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0, &t0, &z2_5_0); + + /* 2^11 - 2^1 */ fe25519_square(&t0, &z2_10_0); + /* 2^12 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1, &t0); + } + /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0, &t1, &z2_10_0); + + /* 2^21 - 2^1 */ fe25519_square(&t0, &z2_20_0); + /* 2^22 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1,&t0); + } + /* 2^40 - 2^0 */ fe25519_mul(&t0, &t1, &z2_20_0); + + /* 2^41 - 2^1 */ fe25519_square(&t1, &t0); + /* 2^42 - 2^2 */ fe25519_square(&t0, &t1); + /* 2^50 - 2^10 */ for (i = 2; i < 10;i += 2) { + fe25519_square(&t1, &t0); + fe25519_square(&t0, &t1); + } + /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0,&t0,&z2_10_0); + + /* 2^51 - 2^1 */ fe25519_square(&t0, &z2_50_0); + /* 2^52 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^100 - 2^50 */ for (i = 2; i < 50; i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1,&t0); + } + /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0, &t1, &z2_50_0); + + /* 2^101 - 2^1 */ fe25519_square(&t1, &z2_100_0); + /* 2^102 - 2^2 */ fe25519_square(&t0, &t1); + /* 2^200 - 2^100 */ for (i = 2; i < 100; i += 2) { + fe25519_square(&t1, &t0); + fe25519_square(&t0,&t1); + } + /* 2^200 - 2^0 */ fe25519_mul(&t1, &t0, &z2_100_0); + + /* 2^201 - 2^1 */ fe25519_square(&t0, &t1); + /* 2^202 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1,&t0); + } + /* 2^250 - 2^0 */ fe25519_mul(&t0, &t1, &z2_50_0); + + /* 2^251 - 2^1 */ fe25519_square(&t1, &t0); + /* 2^252 - 2^2 */ fe25519_square(&t0, &t1); + /* 2^253 - 2^3 */ fe25519_square(&t1, &t0); + /* 2^254 - 2^4 */ fe25519_square(&t0, &t1); + /* 2^255 - 2^5 */ fe25519_square(&t1, &t0); + /* 2^255 - 21 */ fe25519_mul(r, &t1, &z11); +} + +void fe25519_pow2523(fe25519 *r, const fe25519 *x) +{ + fe25519 z2; + fe25519 z9; + fe25519 z11; + fe25519 z2_5_0; + fe25519 z2_10_0; + fe25519 z2_20_0; + fe25519 z2_50_0; + fe25519 z2_100_0; + fe25519 t; + int i; + + /* 2 */ fe25519_square(&z2, x); + /* 4 */ fe25519_square(&t, &z2); + /* 8 */ fe25519_square(&t, &t); + /* 9 */ fe25519_mul(&z9, &t, x); + /* 11 */ fe25519_mul(&z11, &z9, &z2); + /* 22 */ fe25519_square(&t, &z11); + /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0, &t, &z9); + + /* 2^6 - 2^1 */ fe25519_square(&t, &z2_5_0); + /* 2^10 - 2^5 */ for (i = 1; i < 5; i++) { + fe25519_square(&t,&t); + } + /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0, &t, &z2_5_0); + + /* 2^11 - 2^1 */ fe25519_square(&t, &z2_10_0); + /* 2^20 - 2^10 */ for (i = 1; i < 10; i++) { + fe25519_square(&t, &t); + } + /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0, &t, &z2_10_0); + + /* 2^21 - 2^1 */ fe25519_square(&t, &z2_20_0); + /* 2^40 - 2^20 */ for (i = 1; i < 20; i++) { + fe25519_square(&t,&t); + } + /* 2^40 - 2^0 */ fe25519_mul(&t, &t, &z2_20_0); + + /* 2^41 - 2^1 */ fe25519_square(&t, &t); + /* 2^50 - 2^10 */ for (i = 1; i < 10; i++) { + fe25519_square(&t,&t); + } + /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0, &t, &z2_10_0); + + /* 2^51 - 2^1 */ fe25519_square(&t, &z2_50_0); + /* 2^100 - 2^50 */ for (i = 1; i < 50; i++) { + fe25519_square(&t, &t); + } + /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0, &t, &z2_50_0); + + /* 2^101 - 2^1 */ fe25519_square(&t, &z2_100_0); + /* 2^200 - 2^100 */ for (i = 1; i < 100; i++) { + fe25519_square(&t, &t); + } + /* 2^200 - 2^0 */ fe25519_mul(&t, &t, &z2_100_0); + + /* 2^201 - 2^1 */ fe25519_square(&t, &t); + /* 2^250 - 2^50 */ for (i = 1; i < 50; i++) { + fe25519_square(&t, &t); + } + /* 2^250 - 2^0 */ fe25519_mul(&t, &t, &z2_50_0); + + /* 2^251 - 2^1 */ fe25519_square(&t, &t); + /* 2^252 - 2^2 */ fe25519_square(&t, &t); + /* 2^252 - 3 */ fe25519_mul(r, &t, x); +} diff --git a/src/libs/libssh-0.12.2/src/external/ge25519.c b/src/libs/libssh-0.12.2/src/external/ge25519.c new file mode 100644 index 000000000000..ffeb1d58a4f0 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/ge25519.c @@ -0,0 +1,369 @@ +/* $OpenBSD: ge25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519.c + */ + +#include "config.h" + +#include "libssh/fe25519.h" +#include "libssh/sc25519.h" +#include "libssh/ge25519.h" + +/* + * Arithmetic on the twisted Edwards curve -x^2 + y^2 = 1 + dx^2y^2 + * with d = -(121665/121666) = 37095705934669439343138083508754565189542113879843219016388785533085940283555 + * Base point: (15112221349535400772501151409588531511454012693041857206046113283949847762202,46316835694926478169428394003475163141307993866256225615783033603165251855960); + */ + +/* d */ +static const fe25519 ge25519_ecd = { + {0xA3, 0x78, 0x59, 0x13, 0xCA, 0x4D, 0xEB, 0x75, + 0xAB, 0xD8, 0x41, 0x41, 0x4D, 0x0A, 0x70, 0x00, + 0x98, 0xE8, 0x79, 0x77, 0x79, 0x40, 0xC7, 0x8C, + 0x73, 0xFE, 0x6F, 0x2B, 0xEE, 0x6C, 0x03, 0x52} +}; + +/* 2*d */ +static const fe25519 ge25519_ec2d = { + {0x59, 0xF1, 0xB2, 0x26, 0x94, 0x9B, 0xD6, 0xEB, + 0x56, 0xB1, 0x83, 0x82, 0x9A, 0x14, 0xE0, 0x00, + 0x30, 0xD1, 0xF3, 0xEE, 0xF2, 0x80, 0x8E, 0x19, + 0xE7, 0xFC, 0xDF, 0x56, 0xDC, 0xD9, 0x06, 0x24} +}; + +/* sqrt(-1) */ +static const fe25519 ge25519_sqrtm1 = { + {0xB0, 0xA0, 0x0E, 0x4A, 0x27, 0x1B, 0xEE, 0xC4, + 0x78, 0xE4, 0x2F, 0xAD, 0x06, 0x18, 0x43, 0x2F, + 0xA7, 0xD7, 0xFB, 0x3D, 0x99, 0x00, 0x4D, 0x2B, + 0x0B, 0xDF, 0xC1, 0x4F, 0x80, 0x24, 0x83, 0x2B} +}; + +#define ge25519_p3 ge25519 + +typedef struct { + fe25519 x; + fe25519 z; + fe25519 y; + fe25519 t; +} ge25519_p1p1; + +typedef struct { + fe25519 x; + fe25519 y; + fe25519 z; +} ge25519_p2; + +typedef struct { + fe25519 x; + fe25519 y; +} ge25519_aff; + + +/* Packed coordinates of the base point */ +const ge25519 ge25519_base = { + {{0x1A, 0xD5, 0x25, 0x8F, 0x60, 0x2D, 0x56, 0xC9, + 0xB2, 0xA7, 0x25, 0x95, 0x60, 0xC7, 0x2C, 0x69, + 0x5C, 0xDC, 0xD6, 0xFD, 0x31, 0xE2, 0xA4, 0xC0, + 0xFE, 0x53, 0x6E, 0xCD, 0xD3, 0x36, 0x69, 0x21}}, + {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0xA3, 0xDD, 0xB7, 0xA5, 0xB3, 0x8A, 0xDE, 0x6D, + 0xF5, 0x52, 0x51, 0x77, 0x80, 0x9F, 0xF0, 0x20, + 0x7D, 0xE3, 0xAB, 0x64, 0x8E, 0x4E, 0xEA, 0x66, + 0x65, 0x76, 0x8B, 0xD7, 0x0F, 0x5F, 0x87, 0x67}} +}; + +/* Multiples of the base point in affine representation */ +static const ge25519_aff ge25519_base_multiples_affine[425] = { +#include "ge25519_base.data" +}; + +static void p1p1_to_p2(ge25519_p2 *r, const ge25519_p1p1 *p) +{ + fe25519_mul(&r->x, &p->x, &p->t); + fe25519_mul(&r->y, &p->y, &p->z); + fe25519_mul(&r->z, &p->z, &p->t); +} + +static void p1p1_to_p3(ge25519_p3 *r, const ge25519_p1p1 *p) +{ + p1p1_to_p2((ge25519_p2 *)r, p); + fe25519_mul(&r->t, &p->x, &p->y); +} + +static void ge25519_mixadd2(ge25519_p3 *r, const ge25519_aff *q) +{ + fe25519 a,b,t1,t2,c,d,e,f,g,h,qt; + fe25519_mul(&qt, &q->x, &q->y); + fe25519_sub(&a, &r->y, &r->x); /* A = (Y1-X1)*(Y2-X2) */ + fe25519_add(&b, &r->y, &r->x); /* B = (Y1+X1)*(Y2+X2) */ + fe25519_sub(&t1, &q->y, &q->x); + fe25519_add(&t2, &q->y, &q->x); + fe25519_mul(&a, &a, &t1); + fe25519_mul(&b, &b, &t2); + fe25519_sub(&e, &b, &a); /* E = B-A */ + fe25519_add(&h, &b, &a); /* H = B+A */ + fe25519_mul(&c, &r->t, &qt); /* C = T1*k*T2 */ + fe25519_mul(&c, &c, &ge25519_ec2d); + fe25519_add(&d, &r->z, &r->z); /* D = Z1*2 */ + fe25519_sub(&f, &d, &c); /* F = D-C */ + fe25519_add(&g, &d, &c); /* G = D+C */ + fe25519_mul(&r->x, &e, &f); + fe25519_mul(&r->y, &h, &g); + fe25519_mul(&r->z, &g, &f); + fe25519_mul(&r->t, &e, &h); +} + +static void add_p1p1(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_p3 *q) +{ + fe25519 a, b, c, d, t; + + fe25519_sub(&a, &p->y, &p->x); /* A = (Y1-X1)*(Y2-X2) */ + fe25519_sub(&t, &q->y, &q->x); + fe25519_mul(&a, &a, &t); + fe25519_add(&b, &p->x, &p->y); /* B = (Y1+X1)*(Y2+X2) */ + fe25519_add(&t, &q->x, &q->y); + fe25519_mul(&b, &b, &t); + fe25519_mul(&c, &p->t, &q->t); /* C = T1*k*T2 */ + fe25519_mul(&c, &c, &ge25519_ec2d); + fe25519_mul(&d, &p->z, &q->z); /* D = Z1*2*Z2 */ + fe25519_add(&d, &d, &d); + fe25519_sub(&r->x, &b, &a); /* E = B-A */ + fe25519_sub(&r->t, &d, &c); /* F = D-C */ + fe25519_add(&r->z, &d, &c); /* G = D+C */ + fe25519_add(&r->y, &b, &a); /* H = B+A */ +} + +/* See http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#doubling-dbl-2008-hwcd */ +static void dbl_p1p1(ge25519_p1p1 *r, const ge25519_p2 *p) +{ + fe25519 a,b,c,d; + fe25519_square(&a, &p->x); + fe25519_square(&b, &p->y); + fe25519_square(&c, &p->z); + fe25519_add(&c, &c, &c); + fe25519_neg(&d, &a); + + fe25519_add(&r->x, &p->x, &p->y); + fe25519_square(&r->x, &r->x); + fe25519_sub(&r->x, &r->x, &a); + fe25519_sub(&r->x, &r->x, &b); + fe25519_add(&r->z, &d, &b); + fe25519_sub(&r->t, &r->z, &c); + fe25519_sub(&r->y, &d, &b); +} + +/* Constant-time version of: if(b) r = p */ +static void cmov_aff(ge25519_aff *r, const ge25519_aff *p, unsigned char b) +{ + fe25519_cmov(&r->x, &p->x, b); + fe25519_cmov(&r->y, &p->y, b); +} + +static unsigned char equal(signed char b,signed char c) +{ + unsigned char ub = b; + unsigned char uc = c; + unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ + uint32_t y = x; /* 0: yes; 1..255: no */ + + y -= 1; /* 4294967295: yes; 0..254: no */ + y >>= 31; /* 1: yes; 0: no */ + + return y; +} + +static unsigned char negative(signed char b) +{ + unsigned long long x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + + x >>= 63; /* 1: yes; 0: no */ + + return x; +} + +static void choose_t(ge25519_aff *t, unsigned long long pos, signed char b) +{ + /* constant time */ + fe25519 v; + + *t = ge25519_base_multiples_affine[5 * pos + 0]; + + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 1], + equal(b,1) | equal(b,-1)); + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 2], + equal(b,2) | equal(b,-2)); + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 3], + equal(b,3) | equal(b,-3)); + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 4], + equal(b,-4)); + + fe25519_neg(&v, &t->x); + fe25519_cmov(&t->x, &v, negative(b)); +} + +static void setneutral(ge25519 *r) +{ + fe25519_setzero(&r->x); + fe25519_setone(&r->y); + fe25519_setone(&r->z); + fe25519_setzero(&r->t); +} + +/* ******************************************************************** + * EXPORTED FUNCTIONS + ******************************************************************** */ + +/* return 0 on success, -1 otherwise */ +int ge25519_unpackneg_vartime(ge25519_p3 *r, const unsigned char p[32]) +{ + unsigned char par; + + fe25519 t, chk, num, den, den2, den4, den6; + fe25519_setone(&r->z); + par = p[31] >> 7; + fe25519_unpack(&r->y, p); + fe25519_square(&num, &r->y); /* x = y^2 */ + fe25519_mul(&den, &num, &ge25519_ecd); /* den = dy^2 */ + fe25519_sub(&num, &num, &r->z); /* x = y^2-1 */ + fe25519_add(&den, &r->z, &den); /* den = dy^2+1 */ + + /* Computation of sqrt(num/den) */ + /* 1.: computation of num^((p-5)/8)*den^((7p-35)/8) = (num*den^7)^((p-5)/8) */ + fe25519_square(&den2, &den); + fe25519_square(&den4, &den2); + fe25519_mul(&den6, &den4, &den2); + fe25519_mul(&t, &den6, &num); + fe25519_mul(&t, &t, &den); + + fe25519_pow2523(&t, &t); + /* 2. computation of r->x = t * num * den^3 */ + fe25519_mul(&t, &t, &num); + fe25519_mul(&t, &t, &den); + fe25519_mul(&t, &t, &den); + fe25519_mul(&r->x, &t, &den); + + /* 3. Check whether sqrt computation gave correct result, multiply by sqrt(-1) if not: */ + fe25519_square(&chk, &r->x); + fe25519_mul(&chk, &chk, &den); + if (!fe25519_iseq_vartime(&chk, &num)) { + fe25519_mul(&r->x, &r->x, &ge25519_sqrtm1); + } + + /* 4. Now we have one of the two square roots, except if input was not a square */ + fe25519_square(&chk, &r->x); + fe25519_mul(&chk, &chk, &den); + if (!fe25519_iseq_vartime(&chk, &num)) { + return -1; + } + + /* 5. Choose the desired square root according to parity: */ + if(fe25519_getparity(&r->x) != (1-par)) { + fe25519_neg(&r->x, &r->x); + } + + fe25519_mul(&r->t, &r->x, &r->y); + + return 0; +} + +void ge25519_pack(unsigned char r[32], const ge25519_p3 *p) +{ + fe25519 tx, ty, zi; + + fe25519_invert(&zi, &p->z); + fe25519_mul(&tx, &p->x, &zi); + fe25519_mul(&ty, &p->y, &zi); + fe25519_pack(r, &ty); + + r[31] ^= fe25519_getparity(&tx) << 7; +} + +int ge25519_isneutral_vartime(const ge25519_p3 *p) +{ + int ret = 1; + + if (!fe25519_iszero(&p->x)) { + ret = 0; + } + + if (!fe25519_iseq_vartime(&p->y, &p->z)) { + ret = 0; + } + + return ret; +} + +/* computes [s1]p1 + [s2]p2 */ +void ge25519_double_scalarmult_vartime(ge25519_p3 *r, const ge25519_p3 *p1, const sc25519 *s1, const ge25519_p3 *p2, const sc25519 *s2) +{ + ge25519_p1p1 tp1p1; + ge25519_p3 pre[16]; + unsigned char b[127]; + int i; + + /* precomputation s2 s1 */ + setneutral(pre); /* 00 00 */ + pre[1] = *p1; /* 00 01 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)p1); p1p1_to_p3( &pre[2], &tp1p1); /* 00 10 */ + add_p1p1(&tp1p1,&pre[1], &pre[2]); p1p1_to_p3( &pre[3], &tp1p1); /* 00 11 */ + pre[4] = *p2; /* 01 00 */ + add_p1p1(&tp1p1,&pre[1], &pre[4]); p1p1_to_p3( &pre[5], &tp1p1); /* 01 01 */ + add_p1p1(&tp1p1,&pre[2], &pre[4]); p1p1_to_p3( &pre[6], &tp1p1); /* 01 10 */ + add_p1p1(&tp1p1,&pre[3], &pre[4]); p1p1_to_p3( &pre[7], &tp1p1); /* 01 11 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)p2); p1p1_to_p3( &pre[8], &tp1p1); /* 10 00 */ + add_p1p1(&tp1p1,&pre[1], &pre[8]); p1p1_to_p3( &pre[9], &tp1p1); /* 10 01 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)&pre[5]); p1p1_to_p3(&pre[10], &tp1p1); /* 10 10 */ + add_p1p1(&tp1p1,&pre[3], &pre[8]); p1p1_to_p3(&pre[11], &tp1p1); /* 10 11 */ + add_p1p1(&tp1p1,&pre[4], &pre[8]); p1p1_to_p3(&pre[12], &tp1p1); /* 11 00 */ + add_p1p1(&tp1p1,&pre[1],&pre[12]); p1p1_to_p3(&pre[13], &tp1p1); /* 11 01 */ + add_p1p1(&tp1p1,&pre[2],&pre[12]); p1p1_to_p3(&pre[14], &tp1p1); /* 11 10 */ + add_p1p1(&tp1p1,&pre[3],&pre[12]); p1p1_to_p3(&pre[15], &tp1p1); /* 11 11 */ + + sc25519_2interleave2(b,s1,s2); + + /* scalar multiplication */ + *r = pre[b[126]]; + + for (i = 125; i >= 0; i--) { + dbl_p1p1(&tp1p1, (ge25519_p2 *)r); + p1p1_to_p2((ge25519_p2 *) r, &tp1p1); + dbl_p1p1(&tp1p1, (ge25519_p2 *)r); + if(b[i] != 0) { + p1p1_to_p3(r, &tp1p1); + add_p1p1(&tp1p1, r, &pre[b[i]]); + } + if (i != 0) { + p1p1_to_p2((ge25519_p2 *)r, &tp1p1); + } else { + p1p1_to_p3(r, &tp1p1); + } + } +} + +void ge25519_scalarmult_base(ge25519_p3 *r, const sc25519 *s) +{ + signed char b[85]; + int i; + ge25519_aff t; + + sc25519_window3(b,s); + + choose_t((ge25519_aff *)r, 0, b[0]); + fe25519_setone(&r->z); + fe25519_mul(&r->t, &r->x, &r->y); + for (i = 1; i < 85; i++) { + choose_t(&t, (unsigned long long) i, b[i]); + ge25519_mixadd2(r, &t); + } +} diff --git a/src/libs/libssh-0.12.2/src/external/ge25519_base.data b/src/libs/libssh-0.12.2/src/external/ge25519_base.data new file mode 100644 index 000000000000..e6a4227e9ced --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/ge25519_base.data @@ -0,0 +1,858 @@ +/* $OpenBSD: ge25519_base.data,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519_base.data + */ + +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x1a, 0xd5, 0x25, 0x8f, 0x60, 0x2d, 0x56, 0xc9, 0xb2, 0xa7, 0x25, 0x95, 0x60, 0xc7, 0x2c, 0x69, 0x5c, 0xdc, 0xd6, 0xfd, 0x31, 0xe2, 0xa4, 0xc0, 0xfe, 0x53, 0x6e, 0xcd, 0xd3, 0x36, 0x69, 0x21}} , + {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}}}, +{{{0x0e, 0xce, 0x43, 0x28, 0x4e, 0xa1, 0xc5, 0x83, 0x5f, 0xa4, 0xd7, 0x15, 0x45, 0x8e, 0x0d, 0x08, 0xac, 0xe7, 0x33, 0x18, 0x7d, 0x3b, 0x04, 0x3d, 0x6c, 0x04, 0x5a, 0x9f, 0x4c, 0x38, 0xab, 0x36}} , + {{0xc9, 0xa3, 0xf8, 0x6a, 0xae, 0x46, 0x5f, 0x0e, 0x56, 0x51, 0x38, 0x64, 0x51, 0x0f, 0x39, 0x97, 0x56, 0x1f, 0xa2, 0xc9, 0xe8, 0x5e, 0xa2, 0x1d, 0xc2, 0x29, 0x23, 0x09, 0xf3, 0xcd, 0x60, 0x22}}}, +{{{0x5c, 0xe2, 0xf8, 0xd3, 0x5f, 0x48, 0x62, 0xac, 0x86, 0x48, 0x62, 0x81, 0x19, 0x98, 0x43, 0x63, 0x3a, 0xc8, 0xda, 0x3e, 0x74, 0xae, 0xf4, 0x1f, 0x49, 0x8f, 0x92, 0x22, 0x4a, 0x9c, 0xae, 0x67}} , + {{0xd4, 0xb4, 0xf5, 0x78, 0x48, 0x68, 0xc3, 0x02, 0x04, 0x03, 0x24, 0x67, 0x17, 0xec, 0x16, 0x9f, 0xf7, 0x9e, 0x26, 0x60, 0x8e, 0xa1, 0x26, 0xa1, 0xab, 0x69, 0xee, 0x77, 0xd1, 0xb1, 0x67, 0x12}}}, +{{{0x70, 0xf8, 0xc9, 0xc4, 0x57, 0xa6, 0x3a, 0x49, 0x47, 0x15, 0xce, 0x93, 0xc1, 0x9e, 0x73, 0x1a, 0xf9, 0x20, 0x35, 0x7a, 0xb8, 0xd4, 0x25, 0x83, 0x46, 0xf1, 0xcf, 0x56, 0xdb, 0xa8, 0x3d, 0x20}} , + {{0x2f, 0x11, 0x32, 0xca, 0x61, 0xab, 0x38, 0xdf, 0xf0, 0x0f, 0x2f, 0xea, 0x32, 0x28, 0xf2, 0x4c, 0x6c, 0x71, 0xd5, 0x80, 0x85, 0xb8, 0x0e, 0x47, 0xe1, 0x95, 0x15, 0xcb, 0x27, 0xe8, 0xd0, 0x47}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc8, 0x84, 0xa5, 0x08, 0xbc, 0xfd, 0x87, 0x3b, 0x99, 0x8b, 0x69, 0x80, 0x7b, 0xc6, 0x3a, 0xeb, 0x93, 0xcf, 0x4e, 0xf8, 0x5c, 0x2d, 0x86, 0x42, 0xb6, 0x71, 0xd7, 0x97, 0x5f, 0xe1, 0x42, 0x67}} , + {{0xb4, 0xb9, 0x37, 0xfc, 0xa9, 0x5b, 0x2f, 0x1e, 0x93, 0xe4, 0x1e, 0x62, 0xfc, 0x3c, 0x78, 0x81, 0x8f, 0xf3, 0x8a, 0x66, 0x09, 0x6f, 0xad, 0x6e, 0x79, 0x73, 0xe5, 0xc9, 0x00, 0x06, 0xd3, 0x21}}}, +{{{0xf8, 0xf9, 0x28, 0x6c, 0x6d, 0x59, 0xb2, 0x59, 0x74, 0x23, 0xbf, 0xe7, 0x33, 0x8d, 0x57, 0x09, 0x91, 0x9c, 0x24, 0x08, 0x15, 0x2b, 0xe2, 0xb8, 0xee, 0x3a, 0xe5, 0x27, 0x06, 0x86, 0xa4, 0x23}} , + {{0xeb, 0x27, 0x67, 0xc1, 0x37, 0xab, 0x7a, 0xd8, 0x27, 0x9c, 0x07, 0x8e, 0xff, 0x11, 0x6a, 0xb0, 0x78, 0x6e, 0xad, 0x3a, 0x2e, 0x0f, 0x98, 0x9f, 0x72, 0xc3, 0x7f, 0x82, 0xf2, 0x96, 0x96, 0x70}}}, +{{{0x81, 0x6b, 0x88, 0xe8, 0x1e, 0xc7, 0x77, 0x96, 0x0e, 0xa1, 0xa9, 0x52, 0xe0, 0xd8, 0x0e, 0x61, 0x9e, 0x79, 0x2d, 0x95, 0x9c, 0x8d, 0x96, 0xe0, 0x06, 0x40, 0x5d, 0x87, 0x28, 0x5f, 0x98, 0x70}} , + {{0xf1, 0x79, 0x7b, 0xed, 0x4f, 0x44, 0xb2, 0xe7, 0x08, 0x0d, 0xc2, 0x08, 0x12, 0xd2, 0x9f, 0xdf, 0xcd, 0x93, 0x20, 0x8a, 0xcf, 0x33, 0xca, 0x6d, 0x89, 0xb9, 0x77, 0xc8, 0x93, 0x1b, 0x4e, 0x60}}}, +{{{0x26, 0x4f, 0x7e, 0x97, 0xf6, 0x40, 0xdd, 0x4f, 0xfc, 0x52, 0x78, 0xf9, 0x90, 0x31, 0x03, 0xe6, 0x7d, 0x56, 0x39, 0x0b, 0x1d, 0x56, 0x82, 0x85, 0xf9, 0x1a, 0x42, 0x17, 0x69, 0x6c, 0xcf, 0x39}} , + {{0x69, 0xd2, 0x06, 0x3a, 0x4f, 0x39, 0x2d, 0xf9, 0x38, 0x40, 0x8c, 0x4c, 0xe7, 0x05, 0x12, 0xb4, 0x78, 0x8b, 0xf8, 0xc0, 0xec, 0x93, 0xde, 0x7a, 0x6b, 0xce, 0x2c, 0xe1, 0x0e, 0xa9, 0x34, 0x44}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0b, 0xa4, 0x3c, 0xb0, 0x0f, 0x7a, 0x51, 0xf1, 0x78, 0xd6, 0xd9, 0x6a, 0xfd, 0x46, 0xe8, 0xb8, 0xa8, 0x79, 0x1d, 0x87, 0xf9, 0x90, 0xf2, 0x9c, 0x13, 0x29, 0xf8, 0x0b, 0x20, 0x64, 0xfa, 0x05}} , + {{0x26, 0x09, 0xda, 0x17, 0xaf, 0x95, 0xd6, 0xfb, 0x6a, 0x19, 0x0d, 0x6e, 0x5e, 0x12, 0xf1, 0x99, 0x4c, 0xaa, 0xa8, 0x6f, 0x79, 0x86, 0xf4, 0x72, 0x28, 0x00, 0x26, 0xf9, 0xea, 0x9e, 0x19, 0x3d}}}, +{{{0x87, 0xdd, 0xcf, 0xf0, 0x5b, 0x49, 0xa2, 0x5d, 0x40, 0x7a, 0x23, 0x26, 0xa4, 0x7a, 0x83, 0x8a, 0xb7, 0x8b, 0xd2, 0x1a, 0xbf, 0xea, 0x02, 0x24, 0x08, 0x5f, 0x7b, 0xa9, 0xb1, 0xbe, 0x9d, 0x37}} , + {{0xfc, 0x86, 0x4b, 0x08, 0xee, 0xe7, 0xa0, 0xfd, 0x21, 0x45, 0x09, 0x34, 0xc1, 0x61, 0x32, 0x23, 0xfc, 0x9b, 0x55, 0x48, 0x53, 0x99, 0xf7, 0x63, 0xd0, 0x99, 0xce, 0x01, 0xe0, 0x9f, 0xeb, 0x28}}}, +{{{0x47, 0xfc, 0xab, 0x5a, 0x17, 0xf0, 0x85, 0x56, 0x3a, 0x30, 0x86, 0x20, 0x28, 0x4b, 0x8e, 0x44, 0x74, 0x3a, 0x6e, 0x02, 0xf1, 0x32, 0x8f, 0x9f, 0x3f, 0x08, 0x35, 0xe9, 0xca, 0x16, 0x5f, 0x6e}} , + {{0x1c, 0x59, 0x1c, 0x65, 0x5d, 0x34, 0xa4, 0x09, 0xcd, 0x13, 0x9c, 0x70, 0x7d, 0xb1, 0x2a, 0xc5, 0x88, 0xaf, 0x0b, 0x60, 0xc7, 0x9f, 0x34, 0x8d, 0xd6, 0xb7, 0x7f, 0xea, 0x78, 0x65, 0x8d, 0x77}}}, +{{{0x56, 0xa5, 0xc2, 0x0c, 0xdd, 0xbc, 0xb8, 0x20, 0x6d, 0x57, 0x61, 0xb5, 0xfb, 0x78, 0xb5, 0xd4, 0x49, 0x54, 0x90, 0x26, 0xc1, 0xcb, 0xe9, 0xe6, 0xbf, 0xec, 0x1d, 0x4e, 0xed, 0x07, 0x7e, 0x5e}} , + {{0xc7, 0xf6, 0x6c, 0x56, 0x31, 0x20, 0x14, 0x0e, 0xa8, 0xd9, 0x27, 0xc1, 0x9a, 0x3d, 0x1b, 0x7d, 0x0e, 0x26, 0xd3, 0x81, 0xaa, 0xeb, 0xf5, 0x6b, 0x79, 0x02, 0xf1, 0x51, 0x5c, 0x75, 0x55, 0x0f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0a, 0x34, 0xcd, 0x82, 0x3c, 0x33, 0x09, 0x54, 0xd2, 0x61, 0x39, 0x30, 0x9b, 0xfd, 0xef, 0x21, 0x26, 0xd4, 0x70, 0xfa, 0xee, 0xf9, 0x31, 0x33, 0x73, 0x84, 0xd0, 0xb3, 0x81, 0xbf, 0xec, 0x2e}} , + {{0xe8, 0x93, 0x8b, 0x00, 0x64, 0xf7, 0x9c, 0xb8, 0x74, 0xe0, 0xe6, 0x49, 0x48, 0x4d, 0x4d, 0x48, 0xb6, 0x19, 0xa1, 0x40, 0xb7, 0xd9, 0x32, 0x41, 0x7c, 0x82, 0x37, 0xa1, 0x2d, 0xdc, 0xd2, 0x54}}}, +{{{0x68, 0x2b, 0x4a, 0x5b, 0xd5, 0xc7, 0x51, 0x91, 0x1d, 0xe1, 0x2a, 0x4b, 0xc4, 0x47, 0xf1, 0xbc, 0x7a, 0xb3, 0xcb, 0xc8, 0xb6, 0x7c, 0xac, 0x90, 0x05, 0xfd, 0xf3, 0xf9, 0x52, 0x3a, 0x11, 0x6b}} , + {{0x3d, 0xc1, 0x27, 0xf3, 0x59, 0x43, 0x95, 0x90, 0xc5, 0x96, 0x79, 0xf5, 0xf4, 0x95, 0x65, 0x29, 0x06, 0x9c, 0x51, 0x05, 0x18, 0xda, 0xb8, 0x2e, 0x79, 0x7e, 0x69, 0x59, 0x71, 0x01, 0xeb, 0x1a}}}, +{{{0x15, 0x06, 0x49, 0xb6, 0x8a, 0x3c, 0xea, 0x2f, 0x34, 0x20, 0x14, 0xc3, 0xaa, 0xd6, 0xaf, 0x2c, 0x3e, 0xbd, 0x65, 0x20, 0xe2, 0x4d, 0x4b, 0x3b, 0xeb, 0x9f, 0x4a, 0xc3, 0xad, 0xa4, 0x3b, 0x60}} , + {{0xbc, 0x58, 0xe6, 0xc0, 0x95, 0x2a, 0x2a, 0x81, 0x9a, 0x7a, 0xf3, 0xd2, 0x06, 0xbe, 0x48, 0xbc, 0x0c, 0xc5, 0x46, 0xe0, 0x6a, 0xd4, 0xac, 0x0f, 0xd9, 0xcc, 0x82, 0x34, 0x2c, 0xaf, 0xdb, 0x1f}}}, +{{{0xf7, 0x17, 0x13, 0xbd, 0xfb, 0xbc, 0xd2, 0xec, 0x45, 0xb3, 0x15, 0x31, 0xe9, 0xaf, 0x82, 0x84, 0x3d, 0x28, 0xc6, 0xfc, 0x11, 0xf5, 0x41, 0xb5, 0x8b, 0xd3, 0x12, 0x76, 0x52, 0xe7, 0x1a, 0x3c}} , + {{0x4e, 0x36, 0x11, 0x07, 0xa2, 0x15, 0x20, 0x51, 0xc4, 0x2a, 0xc3, 0x62, 0x8b, 0x5e, 0x7f, 0xa6, 0x0f, 0xf9, 0x45, 0x85, 0x6c, 0x11, 0x86, 0xb7, 0x7e, 0xe5, 0xd7, 0xf9, 0xc3, 0x91, 0x1c, 0x05}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xea, 0xd6, 0xde, 0x29, 0x3a, 0x00, 0xb9, 0x02, 0x59, 0xcb, 0x26, 0xc4, 0xba, 0x99, 0xb1, 0x97, 0x2f, 0x8e, 0x00, 0x92, 0x26, 0x4f, 0x52, 0xeb, 0x47, 0x1b, 0x89, 0x8b, 0x24, 0xc0, 0x13, 0x7d}} , + {{0xd5, 0x20, 0x5b, 0x80, 0xa6, 0x80, 0x20, 0x95, 0xc3, 0xe9, 0x9f, 0x8e, 0x87, 0x9e, 0x1e, 0x9e, 0x7a, 0xc7, 0xcc, 0x75, 0x6c, 0xa5, 0xf1, 0x91, 0x1a, 0xa8, 0x01, 0x2c, 0xab, 0x76, 0xa9, 0x59}}}, +{{{0xde, 0xc9, 0xb1, 0x31, 0x10, 0x16, 0xaa, 0x35, 0x14, 0x6a, 0xd4, 0xb5, 0x34, 0x82, 0x71, 0xd2, 0x4a, 0x5d, 0x9a, 0x1f, 0x53, 0x26, 0x3c, 0xe5, 0x8e, 0x8d, 0x33, 0x7f, 0xff, 0xa9, 0xd5, 0x17}} , + {{0x89, 0xaf, 0xf6, 0xa4, 0x64, 0xd5, 0x10, 0xe0, 0x1d, 0xad, 0xef, 0x44, 0xbd, 0xda, 0x83, 0xac, 0x7a, 0xa8, 0xf0, 0x1c, 0x07, 0xf9, 0xc3, 0x43, 0x6c, 0x3f, 0xb7, 0xd3, 0x87, 0x22, 0x02, 0x73}}}, +{{{0x64, 0x1d, 0x49, 0x13, 0x2f, 0x71, 0xec, 0x69, 0x87, 0xd0, 0x42, 0xee, 0x13, 0xec, 0xe3, 0xed, 0x56, 0x7b, 0xbf, 0xbd, 0x8c, 0x2f, 0x7d, 0x7b, 0x9d, 0x28, 0xec, 0x8e, 0x76, 0x2f, 0x6f, 0x08}} , + {{0x22, 0xf5, 0x5f, 0x4d, 0x15, 0xef, 0xfc, 0x4e, 0x57, 0x03, 0x36, 0x89, 0xf0, 0xeb, 0x5b, 0x91, 0xd6, 0xe2, 0xca, 0x01, 0xa5, 0xee, 0x52, 0xec, 0xa0, 0x3c, 0x8f, 0x33, 0x90, 0x5a, 0x94, 0x72}}}, +{{{0x8a, 0x4b, 0xe7, 0x38, 0xbc, 0xda, 0xc2, 0xb0, 0x85, 0xe1, 0x4a, 0xfe, 0x2d, 0x44, 0x84, 0xcb, 0x20, 0x6b, 0x2d, 0xbf, 0x11, 0x9c, 0xd7, 0xbe, 0xd3, 0x3e, 0x5f, 0xbf, 0x68, 0xbc, 0xa8, 0x07}} , + {{0x01, 0x89, 0x28, 0x22, 0x6a, 0x78, 0xaa, 0x29, 0x03, 0xc8, 0x74, 0x95, 0x03, 0x3e, 0xdc, 0xbd, 0x07, 0x13, 0xa8, 0xa2, 0x20, 0x2d, 0xb3, 0x18, 0x70, 0x42, 0xfd, 0x7a, 0xc4, 0xd7, 0x49, 0x72}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x02, 0xff, 0x32, 0x2b, 0x5c, 0x93, 0x54, 0x32, 0xe8, 0x57, 0x54, 0x1a, 0x8b, 0x33, 0x60, 0x65, 0xd3, 0x67, 0xa4, 0xc1, 0x26, 0xc4, 0xa4, 0x34, 0x1f, 0x9b, 0xa7, 0xa9, 0xf4, 0xd9, 0x4f, 0x5b}} , + {{0x46, 0x8d, 0xb0, 0x33, 0x54, 0x26, 0x5b, 0x68, 0xdf, 0xbb, 0xc5, 0xec, 0xc2, 0xf9, 0x3c, 0x5a, 0x37, 0xc1, 0x8e, 0x27, 0x47, 0xaa, 0x49, 0x5a, 0xf8, 0xfb, 0x68, 0x04, 0x23, 0xd1, 0xeb, 0x40}}}, +{{{0x65, 0xa5, 0x11, 0x84, 0x8a, 0x67, 0x9d, 0x9e, 0xd1, 0x44, 0x68, 0x7a, 0x34, 0xe1, 0x9f, 0xa3, 0x54, 0xcd, 0x07, 0xca, 0x79, 0x1f, 0x54, 0x2f, 0x13, 0x70, 0x4e, 0xee, 0xa2, 0xfa, 0xe7, 0x5d}} , + {{0x36, 0xec, 0x54, 0xf8, 0xce, 0xe4, 0x85, 0xdf, 0xf6, 0x6f, 0x1d, 0x90, 0x08, 0xbc, 0xe8, 0xc0, 0x92, 0x2d, 0x43, 0x6b, 0x92, 0xa9, 0x8e, 0xab, 0x0a, 0x2e, 0x1c, 0x1e, 0x64, 0x23, 0x9f, 0x2c}}}, +{{{0xa7, 0xd6, 0x2e, 0xd5, 0xcc, 0xd4, 0xcb, 0x5a, 0x3b, 0xa7, 0xf9, 0x46, 0x03, 0x1d, 0xad, 0x2b, 0x34, 0x31, 0x90, 0x00, 0x46, 0x08, 0x82, 0x14, 0xc4, 0xe0, 0x9c, 0xf0, 0xe3, 0x55, 0x43, 0x31}} , + {{0x60, 0xd6, 0xdd, 0x78, 0xe6, 0xd4, 0x22, 0x42, 0x1f, 0x00, 0xf9, 0xb1, 0x6a, 0x63, 0xe2, 0x92, 0x59, 0xd1, 0x1a, 0xb7, 0x00, 0x54, 0x29, 0xc9, 0xc1, 0xf6, 0x6f, 0x7a, 0xc5, 0x3c, 0x5f, 0x65}}}, +{{{0x27, 0x4f, 0xd0, 0x72, 0xb1, 0x11, 0x14, 0x27, 0x15, 0x94, 0x48, 0x81, 0x7e, 0x74, 0xd8, 0x32, 0xd5, 0xd1, 0x11, 0x28, 0x60, 0x63, 0x36, 0x32, 0x37, 0xb5, 0x13, 0x1c, 0xa0, 0x37, 0xe3, 0x74}} , + {{0xf1, 0x25, 0x4e, 0x11, 0x96, 0x67, 0xe6, 0x1c, 0xc2, 0xb2, 0x53, 0xe2, 0xda, 0x85, 0xee, 0xb2, 0x9f, 0x59, 0xf3, 0xba, 0xbd, 0xfa, 0xcf, 0x6e, 0xf9, 0xda, 0xa4, 0xb3, 0x02, 0x8f, 0x64, 0x08}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x34, 0x94, 0xf2, 0x64, 0x54, 0x47, 0x37, 0x07, 0x40, 0x8a, 0x20, 0xba, 0x4a, 0x55, 0xd7, 0x3f, 0x47, 0xba, 0x25, 0x23, 0x14, 0xb0, 0x2c, 0xe8, 0x55, 0xa8, 0xa6, 0xef, 0x51, 0xbd, 0x6f, 0x6a}} , + {{0x71, 0xd6, 0x16, 0x76, 0xb2, 0x06, 0xea, 0x79, 0xf5, 0xc4, 0xc3, 0x52, 0x7e, 0x61, 0xd1, 0xe1, 0xad, 0x70, 0x78, 0x1d, 0x16, 0x11, 0xf8, 0x7c, 0x2b, 0xfc, 0x55, 0x9f, 0x52, 0xf8, 0xf5, 0x16}}}, +{{{0x34, 0x96, 0x9a, 0xf6, 0xc5, 0xe0, 0x14, 0x03, 0x24, 0x0e, 0x4c, 0xad, 0x9e, 0x9a, 0x70, 0x23, 0x96, 0xb2, 0xf1, 0x2e, 0x9d, 0xc3, 0x32, 0x9b, 0x54, 0xa5, 0x73, 0xde, 0x88, 0xb1, 0x3e, 0x24}} , + {{0xf6, 0xe2, 0x4c, 0x1f, 0x5b, 0xb2, 0xaf, 0x82, 0xa5, 0xcf, 0x81, 0x10, 0x04, 0xef, 0xdb, 0xa2, 0xcc, 0x24, 0xb2, 0x7e, 0x0b, 0x7a, 0xeb, 0x01, 0xd8, 0x52, 0xf4, 0x51, 0x89, 0x29, 0x79, 0x37}}}, +{{{0x74, 0xde, 0x12, 0xf3, 0x68, 0xb7, 0x66, 0xc3, 0xee, 0x68, 0xdc, 0x81, 0xb5, 0x55, 0x99, 0xab, 0xd9, 0x28, 0x63, 0x6d, 0x8b, 0x40, 0x69, 0x75, 0x6c, 0xcd, 0x5c, 0x2a, 0x7e, 0x32, 0x7b, 0x29}} , + {{0x02, 0xcc, 0x22, 0x74, 0x4d, 0x19, 0x07, 0xc0, 0xda, 0xb5, 0x76, 0x51, 0x2a, 0xaa, 0xa6, 0x0a, 0x5f, 0x26, 0xd4, 0xbc, 0xaf, 0x48, 0x88, 0x7f, 0x02, 0xbc, 0xf2, 0xe1, 0xcf, 0xe9, 0xdd, 0x15}}}, +{{{0xed, 0xb5, 0x9a, 0x8c, 0x9a, 0xdd, 0x27, 0xf4, 0x7f, 0x47, 0xd9, 0x52, 0xa7, 0xcd, 0x65, 0xa5, 0x31, 0x22, 0xed, 0xa6, 0x63, 0x5b, 0x80, 0x4a, 0xad, 0x4d, 0xed, 0xbf, 0xee, 0x49, 0xb3, 0x06}} , + {{0xf8, 0x64, 0x8b, 0x60, 0x90, 0xe9, 0xde, 0x44, 0x77, 0xb9, 0x07, 0x36, 0x32, 0xc2, 0x50, 0xf5, 0x65, 0xdf, 0x48, 0x4c, 0x37, 0xaa, 0x68, 0xab, 0x9a, 0x1f, 0x3e, 0xff, 0x89, 0x92, 0xa0, 0x07}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x7d, 0x4f, 0x9c, 0x19, 0xc0, 0x4a, 0x31, 0xec, 0xf9, 0xaa, 0xeb, 0xb2, 0x16, 0x9c, 0xa3, 0x66, 0x5f, 0xd1, 0xd4, 0xed, 0xb8, 0x92, 0x1c, 0xab, 0xda, 0xea, 0xd9, 0x57, 0xdf, 0x4c, 0x2a, 0x48}} , + {{0x4b, 0xb0, 0x4e, 0x6e, 0x11, 0x3b, 0x51, 0xbd, 0x6a, 0xfd, 0xe4, 0x25, 0xa5, 0x5f, 0x11, 0x3f, 0x98, 0x92, 0x51, 0x14, 0xc6, 0x5f, 0x3c, 0x0b, 0xa8, 0xf7, 0xc2, 0x81, 0x43, 0xde, 0x91, 0x73}}}, +{{{0x3c, 0x8f, 0x9f, 0x33, 0x2a, 0x1f, 0x43, 0x33, 0x8f, 0x68, 0xff, 0x1f, 0x3d, 0x73, 0x6b, 0xbf, 0x68, 0xcc, 0x7d, 0x13, 0x6c, 0x24, 0x4b, 0xcc, 0x4d, 0x24, 0x0d, 0xfe, 0xde, 0x86, 0xad, 0x3b}} , + {{0x79, 0x51, 0x81, 0x01, 0xdc, 0x73, 0x53, 0xe0, 0x6e, 0x9b, 0xea, 0x68, 0x3f, 0x5c, 0x14, 0x84, 0x53, 0x8d, 0x4b, 0xc0, 0x9f, 0x9f, 0x89, 0x2b, 0x8c, 0xba, 0x86, 0xfa, 0xf2, 0xcd, 0xe3, 0x2d}}}, +{{{0x06, 0xf9, 0x29, 0x5a, 0xdb, 0x3d, 0x84, 0x52, 0xab, 0xcc, 0x6b, 0x60, 0x9d, 0xb7, 0x4a, 0x0e, 0x36, 0x63, 0x91, 0xad, 0xa0, 0x95, 0xb0, 0x97, 0x89, 0x4e, 0xcf, 0x7d, 0x3c, 0xe5, 0x7c, 0x28}} , + {{0x2e, 0x69, 0x98, 0xfd, 0xc6, 0xbd, 0xcc, 0xca, 0xdf, 0x9a, 0x44, 0x7e, 0x9d, 0xca, 0x89, 0x6d, 0xbf, 0x27, 0xc2, 0xf8, 0xcd, 0x46, 0x00, 0x2b, 0xb5, 0x58, 0x4e, 0xb7, 0x89, 0x09, 0xe9, 0x2d}}}, +{{{0x54, 0xbe, 0x75, 0xcb, 0x05, 0xb0, 0x54, 0xb7, 0xe7, 0x26, 0x86, 0x4a, 0xfc, 0x19, 0xcf, 0x27, 0x46, 0xd4, 0x22, 0x96, 0x5a, 0x11, 0xe8, 0xd5, 0x1b, 0xed, 0x71, 0xc5, 0x5d, 0xc8, 0xaf, 0x45}} , + {{0x40, 0x7b, 0x77, 0x57, 0x49, 0x9e, 0x80, 0x39, 0x23, 0xee, 0x81, 0x0b, 0x22, 0xcf, 0xdb, 0x7a, 0x2f, 0x14, 0xb8, 0x57, 0x8f, 0xa1, 0x39, 0x1e, 0x77, 0xfc, 0x0b, 0xa6, 0xbf, 0x8a, 0x0c, 0x6c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x77, 0x3a, 0xd4, 0xd8, 0x27, 0xcf, 0xe8, 0xa1, 0x72, 0x9d, 0xca, 0xdd, 0x0d, 0x96, 0xda, 0x79, 0xed, 0x56, 0x42, 0x15, 0x60, 0xc7, 0x1c, 0x6b, 0x26, 0x30, 0xf6, 0x6a, 0x95, 0x67, 0xf3, 0x0a}} , + {{0xc5, 0x08, 0xa4, 0x2b, 0x2f, 0xbd, 0x31, 0x81, 0x2a, 0xa6, 0xb6, 0xe4, 0x00, 0x91, 0xda, 0x3d, 0xb2, 0xb0, 0x96, 0xce, 0x8a, 0xd2, 0x8d, 0x70, 0xb3, 0xd3, 0x34, 0x01, 0x90, 0x8d, 0x10, 0x21}}}, +{{{0x33, 0x0d, 0xe7, 0xba, 0x4f, 0x07, 0xdf, 0x8d, 0xea, 0x7d, 0xa0, 0xc5, 0xd6, 0xb1, 0xb0, 0xe5, 0x57, 0x1b, 0x5b, 0xf5, 0x45, 0x13, 0x14, 0x64, 0x5a, 0xeb, 0x5c, 0xfc, 0x54, 0x01, 0x76, 0x2b}} , + {{0x02, 0x0c, 0xc2, 0xaf, 0x96, 0x36, 0xfe, 0x4a, 0xe2, 0x54, 0x20, 0x6a, 0xeb, 0xb2, 0x9f, 0x62, 0xd7, 0xce, 0xa2, 0x3f, 0x20, 0x11, 0x34, 0x37, 0xe0, 0x42, 0xed, 0x6f, 0xf9, 0x1a, 0xc8, 0x7d}}}, +{{{0xd8, 0xb9, 0x11, 0xe8, 0x36, 0x3f, 0x42, 0xc1, 0xca, 0xdc, 0xd3, 0xf1, 0xc8, 0x23, 0x3d, 0x4f, 0x51, 0x7b, 0x9d, 0x8d, 0xd8, 0xe4, 0xa0, 0xaa, 0xf3, 0x04, 0xd6, 0x11, 0x93, 0xc8, 0x35, 0x45}} , + {{0x61, 0x36, 0xd6, 0x08, 0x90, 0xbf, 0xa7, 0x7a, 0x97, 0x6c, 0x0f, 0x84, 0xd5, 0x33, 0x2d, 0x37, 0xc9, 0x6a, 0x80, 0x90, 0x3d, 0x0a, 0xa2, 0xaa, 0xe1, 0xb8, 0x84, 0xba, 0x61, 0x36, 0xdd, 0x69}}}, +{{{0x6b, 0xdb, 0x5b, 0x9c, 0xc6, 0x92, 0xbc, 0x23, 0xaf, 0xc5, 0xb8, 0x75, 0xf8, 0x42, 0xfa, 0xd6, 0xb6, 0x84, 0x94, 0x63, 0x98, 0x93, 0x48, 0x78, 0x38, 0xcd, 0xbb, 0x18, 0x34, 0xc3, 0xdb, 0x67}} , + {{0x96, 0xf3, 0x3a, 0x09, 0x56, 0xb0, 0x6f, 0x7c, 0x51, 0x1e, 0x1b, 0x39, 0x48, 0xea, 0xc9, 0x0c, 0x25, 0xa2, 0x7a, 0xca, 0xe7, 0x92, 0xfc, 0x59, 0x30, 0xa3, 0x89, 0x85, 0xdf, 0x6f, 0x43, 0x38}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x79, 0x84, 0x44, 0x19, 0xbd, 0xe9, 0x54, 0xc4, 0xc0, 0x6e, 0x2a, 0xa8, 0xa8, 0x9b, 0x43, 0xd5, 0x71, 0x22, 0x5f, 0xdc, 0x01, 0xfa, 0xdf, 0xb3, 0xb8, 0x47, 0x4b, 0x0a, 0xa5, 0x44, 0xea, 0x29}} , + {{0x05, 0x90, 0x50, 0xaf, 0x63, 0x5f, 0x9d, 0x9e, 0xe1, 0x9d, 0x38, 0x97, 0x1f, 0x6c, 0xac, 0x30, 0x46, 0xb2, 0x6a, 0x19, 0xd1, 0x4b, 0xdb, 0xbb, 0x8c, 0xda, 0x2e, 0xab, 0xc8, 0x5a, 0x77, 0x6c}}}, +{{{0x2b, 0xbe, 0xaf, 0xa1, 0x6d, 0x2f, 0x0b, 0xb1, 0x8f, 0xe3, 0xe0, 0x38, 0xcd, 0x0b, 0x41, 0x1b, 0x4a, 0x15, 0x07, 0xf3, 0x6f, 0xdc, 0xb8, 0xe9, 0xde, 0xb2, 0xa3, 0x40, 0x01, 0xa6, 0x45, 0x1e}} , + {{0x76, 0x0a, 0xda, 0x8d, 0x2c, 0x07, 0x3f, 0x89, 0x7d, 0x04, 0xad, 0x43, 0x50, 0x6e, 0xd2, 0x47, 0xcb, 0x8a, 0xe6, 0x85, 0x1a, 0x24, 0xf3, 0xd2, 0x60, 0xfd, 0xdf, 0x73, 0xa4, 0x0d, 0x73, 0x0e}}}, +{{{0xfd, 0x67, 0x6b, 0x71, 0x9b, 0x81, 0x53, 0x39, 0x39, 0xf4, 0xb8, 0xd5, 0xc3, 0x30, 0x9b, 0x3b, 0x7c, 0xa3, 0xf0, 0xd0, 0x84, 0x21, 0xd6, 0xbf, 0xb7, 0x4c, 0x87, 0x13, 0x45, 0x2d, 0xa7, 0x55}} , + {{0x5d, 0x04, 0xb3, 0x40, 0x28, 0x95, 0x2d, 0x30, 0x83, 0xec, 0x5e, 0xe4, 0xff, 0x75, 0xfe, 0x79, 0x26, 0x9d, 0x1d, 0x36, 0xcd, 0x0a, 0x15, 0xd2, 0x24, 0x14, 0x77, 0x71, 0xd7, 0x8a, 0x1b, 0x04}}}, +{{{0x5d, 0x93, 0xc9, 0xbe, 0xaa, 0x90, 0xcd, 0x9b, 0xfb, 0x73, 0x7e, 0xb0, 0x64, 0x98, 0x57, 0x44, 0x42, 0x41, 0xb1, 0xaf, 0xea, 0xc1, 0xc3, 0x22, 0xff, 0x60, 0x46, 0xcb, 0x61, 0x81, 0x70, 0x61}} , + {{0x0d, 0x82, 0xb9, 0xfe, 0x21, 0xcd, 0xc4, 0xf5, 0x98, 0x0c, 0x4e, 0x72, 0xee, 0x87, 0x49, 0xf8, 0xa1, 0x95, 0xdf, 0x8f, 0x2d, 0xbd, 0x21, 0x06, 0x7c, 0x15, 0xe8, 0x12, 0x6d, 0x93, 0xd6, 0x38}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x91, 0xf7, 0x51, 0xd9, 0xef, 0x7d, 0x42, 0x01, 0x13, 0xe9, 0xb8, 0x7f, 0xa6, 0x49, 0x17, 0x64, 0x21, 0x80, 0x83, 0x2c, 0x63, 0x4c, 0x60, 0x09, 0x59, 0x91, 0x92, 0x77, 0x39, 0x51, 0xf4, 0x48}} , + {{0x60, 0xd5, 0x22, 0x83, 0x08, 0x2f, 0xff, 0x99, 0x3e, 0x69, 0x6d, 0x88, 0xda, 0xe7, 0x5b, 0x52, 0x26, 0x31, 0x2a, 0xe5, 0x89, 0xde, 0x68, 0x90, 0xb6, 0x22, 0x5a, 0xbd, 0xd3, 0x85, 0x53, 0x31}}}, +{{{0xd8, 0xce, 0xdc, 0xf9, 0x3c, 0x4b, 0xa2, 0x1d, 0x2c, 0x2f, 0x36, 0xbe, 0x7a, 0xfc, 0xcd, 0xbc, 0xdc, 0xf9, 0x30, 0xbd, 0xff, 0x05, 0xc7, 0xe4, 0x8e, 0x17, 0x62, 0xf8, 0x4d, 0xa0, 0x56, 0x79}} , + {{0x82, 0xe7, 0xf6, 0xba, 0x53, 0x84, 0x0a, 0xa3, 0x34, 0xff, 0x3c, 0xa3, 0x6a, 0xa1, 0x37, 0xea, 0xdd, 0xb6, 0x95, 0xb3, 0x78, 0x19, 0x76, 0x1e, 0x55, 0x2f, 0x77, 0x2e, 0x7f, 0xc1, 0xea, 0x5e}}}, +{{{0x83, 0xe1, 0x6e, 0xa9, 0x07, 0x33, 0x3e, 0x83, 0xff, 0xcb, 0x1c, 0x9f, 0xb1, 0xa3, 0xb4, 0xc9, 0xe1, 0x07, 0x97, 0xff, 0xf8, 0x23, 0x8f, 0xce, 0x40, 0xfd, 0x2e, 0x5e, 0xdb, 0x16, 0x43, 0x2d}} , + {{0xba, 0x38, 0x02, 0xf7, 0x81, 0x43, 0x83, 0xa3, 0x20, 0x4f, 0x01, 0x3b, 0x8a, 0x04, 0x38, 0x31, 0xc6, 0x0f, 0xc8, 0xdf, 0xd7, 0xfa, 0x2f, 0x88, 0x3f, 0xfc, 0x0c, 0x76, 0xc4, 0xa6, 0x45, 0x72}}}, +{{{0xbb, 0x0c, 0xbc, 0x6a, 0xa4, 0x97, 0x17, 0x93, 0x2d, 0x6f, 0xde, 0x72, 0x10, 0x1c, 0x08, 0x2c, 0x0f, 0x80, 0x32, 0x68, 0x27, 0xd4, 0xab, 0xdd, 0xc5, 0x58, 0x61, 0x13, 0x6d, 0x11, 0x1e, 0x4d}} , + {{0x1a, 0xb9, 0xc9, 0x10, 0xfb, 0x1e, 0x4e, 0xf4, 0x84, 0x4b, 0x8a, 0x5e, 0x7b, 0x4b, 0xe8, 0x43, 0x8c, 0x8f, 0x00, 0xb5, 0x54, 0x13, 0xc5, 0x5c, 0xb6, 0x35, 0x4e, 0x9d, 0xe4, 0x5b, 0x41, 0x6d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x15, 0x7d, 0x12, 0x48, 0x82, 0x14, 0x42, 0xcd, 0x32, 0xd4, 0x4b, 0xc1, 0x72, 0x61, 0x2a, 0x8c, 0xec, 0xe2, 0xf8, 0x24, 0x45, 0x94, 0xe3, 0xbe, 0xdd, 0x67, 0xa8, 0x77, 0x5a, 0xae, 0x5b, 0x4b}} , + {{0xcb, 0x77, 0x9a, 0x20, 0xde, 0xb8, 0x23, 0xd9, 0xa0, 0x0f, 0x8c, 0x7b, 0xa5, 0xcb, 0xae, 0xb6, 0xec, 0x42, 0x67, 0x0e, 0x58, 0xa4, 0x75, 0x98, 0x21, 0x71, 0x84, 0xb3, 0xe0, 0x76, 0x94, 0x73}}}, +{{{0xdf, 0xfc, 0x69, 0x28, 0x23, 0x3f, 0x5b, 0xf8, 0x3b, 0x24, 0x37, 0xf3, 0x1d, 0xd5, 0x22, 0x6b, 0xd0, 0x98, 0xa8, 0x6c, 0xcf, 0xff, 0x06, 0xe1, 0x13, 0xdf, 0xb9, 0xc1, 0x0c, 0xa9, 0xbf, 0x33}} , + {{0xd9, 0x81, 0xda, 0xb2, 0x4f, 0x82, 0x9d, 0x43, 0x81, 0x09, 0xf1, 0xd2, 0x01, 0xef, 0xac, 0xf4, 0x2d, 0x7d, 0x01, 0x09, 0xf1, 0xff, 0xa5, 0x9f, 0xe5, 0xca, 0x27, 0x63, 0xdb, 0x20, 0xb1, 0x53}}}, +{{{0x67, 0x02, 0xe8, 0xad, 0xa9, 0x34, 0xd4, 0xf0, 0x15, 0x81, 0xaa, 0xc7, 0x4d, 0x87, 0x94, 0xea, 0x75, 0xe7, 0x4c, 0x94, 0x04, 0x0e, 0x69, 0x87, 0xe7, 0x51, 0x91, 0x10, 0x03, 0xc7, 0xbe, 0x56}} , + {{0x32, 0xfb, 0x86, 0xec, 0x33, 0x6b, 0x2e, 0x51, 0x2b, 0xc8, 0xfa, 0x6c, 0x70, 0x47, 0x7e, 0xce, 0x05, 0x0c, 0x71, 0xf3, 0xb4, 0x56, 0xa6, 0xdc, 0xcc, 0x78, 0x07, 0x75, 0xd0, 0xdd, 0xb2, 0x6a}}}, +{{{0xc6, 0xef, 0xb9, 0xc0, 0x2b, 0x22, 0x08, 0x1e, 0x71, 0x70, 0xb3, 0x35, 0x9c, 0x7a, 0x01, 0x92, 0x44, 0x9a, 0xf6, 0xb0, 0x58, 0x95, 0xc1, 0x9b, 0x02, 0xed, 0x2d, 0x7c, 0x34, 0x29, 0x49, 0x44}} , + {{0x45, 0x62, 0x1d, 0x2e, 0xff, 0x2a, 0x1c, 0x21, 0xa4, 0x25, 0x7b, 0x0d, 0x8c, 0x15, 0x39, 0xfc, 0x8f, 0x7c, 0xa5, 0x7d, 0x1e, 0x25, 0xa3, 0x45, 0xd6, 0xab, 0xbd, 0xcb, 0xc5, 0x5e, 0x78, 0x77}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0xd3, 0x42, 0xed, 0x1d, 0x00, 0x3c, 0x15, 0x2c, 0x9c, 0x77, 0x81, 0xd2, 0x73, 0xd1, 0x06, 0xd5, 0xc4, 0x7f, 0x94, 0xbb, 0x92, 0x2d, 0x2c, 0x4b, 0x45, 0x4b, 0xe9, 0x2a, 0x89, 0x6b, 0x2b}} , + {{0xd2, 0x0c, 0x88, 0xc5, 0x48, 0x4d, 0xea, 0x0d, 0x4a, 0xc9, 0x52, 0x6a, 0x61, 0x79, 0xe9, 0x76, 0xf3, 0x85, 0x52, 0x5c, 0x1b, 0x2c, 0xe1, 0xd6, 0xc4, 0x0f, 0x18, 0x0e, 0x4e, 0xf6, 0x1c, 0x7f}}}, +{{{0xb4, 0x04, 0x2e, 0x42, 0xcb, 0x1f, 0x2b, 0x11, 0x51, 0x7b, 0x08, 0xac, 0xaa, 0x3e, 0x9e, 0x52, 0x60, 0xb7, 0xc2, 0x61, 0x57, 0x8c, 0x84, 0xd5, 0x18, 0xa6, 0x19, 0xfc, 0xb7, 0x75, 0x91, 0x1b}} , + {{0xe8, 0x68, 0xca, 0x44, 0xc8, 0x38, 0x38, 0xcc, 0x53, 0x0a, 0x32, 0x35, 0xcc, 0x52, 0xcb, 0x0e, 0xf7, 0xc5, 0xe7, 0xec, 0x3d, 0x85, 0xcc, 0x58, 0xe2, 0x17, 0x47, 0xff, 0x9f, 0xa5, 0x30, 0x17}}}, +{{{0xe3, 0xae, 0xc8, 0xc1, 0x71, 0x75, 0x31, 0x00, 0x37, 0x41, 0x5c, 0x0e, 0x39, 0xda, 0x73, 0xa0, 0xc7, 0x97, 0x36, 0x6c, 0x5b, 0xf2, 0xee, 0x64, 0x0a, 0x3d, 0x89, 0x1e, 0x1d, 0x49, 0x8c, 0x37}} , + {{0x4c, 0xe6, 0xb0, 0xc1, 0xa5, 0x2a, 0x82, 0x09, 0x08, 0xad, 0x79, 0x9c, 0x56, 0xf6, 0xf9, 0xc1, 0xd7, 0x7c, 0x39, 0x7f, 0x93, 0xca, 0x11, 0x55, 0xbf, 0x07, 0x1b, 0x82, 0x29, 0x69, 0x95, 0x5c}}}, +{{{0x87, 0xee, 0xa6, 0x56, 0x9e, 0xc2, 0x9a, 0x56, 0x24, 0x42, 0x85, 0x4d, 0x98, 0x31, 0x1e, 0x60, 0x4d, 0x87, 0x85, 0x04, 0xae, 0x46, 0x12, 0xf9, 0x8e, 0x7f, 0xe4, 0x7f, 0xf6, 0x1c, 0x37, 0x01}} , + {{0x73, 0x4c, 0xb6, 0xc5, 0xc4, 0xe9, 0x6c, 0x85, 0x48, 0x4a, 0x5a, 0xac, 0xd9, 0x1f, 0x43, 0xf8, 0x62, 0x5b, 0xee, 0x98, 0x2a, 0x33, 0x8e, 0x79, 0xce, 0x61, 0x06, 0x35, 0xd8, 0xd7, 0xca, 0x71}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x72, 0xd3, 0xae, 0xa6, 0xca, 0x8f, 0xcd, 0xcc, 0x78, 0x8e, 0x19, 0x4d, 0xa7, 0xd2, 0x27, 0xe9, 0xa4, 0x3c, 0x16, 0x5b, 0x84, 0x80, 0xf9, 0xd0, 0xcc, 0x6a, 0x1e, 0xca, 0x1e, 0x67, 0xbd, 0x63}} , + {{0x7b, 0x6e, 0x2a, 0xd2, 0x87, 0x48, 0xff, 0xa1, 0xca, 0xe9, 0x15, 0x85, 0xdc, 0xdb, 0x2c, 0x39, 0x12, 0x91, 0xa9, 0x20, 0xaa, 0x4f, 0x29, 0xf4, 0x15, 0x7a, 0xd2, 0xf5, 0x32, 0xcc, 0x60, 0x04}}}, +{{{0xe5, 0x10, 0x47, 0x3b, 0xfa, 0x90, 0xfc, 0x30, 0xb5, 0xea, 0x6f, 0x56, 0x8f, 0xfb, 0x0e, 0xa7, 0x3b, 0xc8, 0xb2, 0xff, 0x02, 0x7a, 0x33, 0x94, 0x93, 0x2a, 0x03, 0xe0, 0x96, 0x3a, 0x6c, 0x0f}} , + {{0x5a, 0x63, 0x67, 0xe1, 0x9b, 0x47, 0x78, 0x9f, 0x38, 0x79, 0xac, 0x97, 0x66, 0x1d, 0x5e, 0x51, 0xee, 0x24, 0x42, 0xe8, 0x58, 0x4b, 0x8a, 0x03, 0x75, 0x86, 0x37, 0x86, 0xe2, 0x97, 0x4e, 0x3d}}}, +{{{0x3f, 0x75, 0x8e, 0xb4, 0xff, 0xd8, 0xdd, 0xd6, 0x37, 0x57, 0x9d, 0x6d, 0x3b, 0xbd, 0xd5, 0x60, 0x88, 0x65, 0x9a, 0xb9, 0x4a, 0x68, 0x84, 0xa2, 0x67, 0xdd, 0x17, 0x25, 0x97, 0x04, 0x8b, 0x5e}} , + {{0xbb, 0x40, 0x5e, 0xbc, 0x16, 0x92, 0x05, 0xc4, 0xc0, 0x4e, 0x72, 0x90, 0x0e, 0xab, 0xcf, 0x8a, 0xed, 0xef, 0xb9, 0x2d, 0x3b, 0xf8, 0x43, 0x5b, 0xba, 0x2d, 0xeb, 0x2f, 0x52, 0xd2, 0xd1, 0x5a}}}, +{{{0x40, 0xb4, 0xab, 0xe6, 0xad, 0x9f, 0x46, 0x69, 0x4a, 0xb3, 0x8e, 0xaa, 0xea, 0x9c, 0x8a, 0x20, 0x16, 0x5d, 0x8c, 0x13, 0xbd, 0xf6, 0x1d, 0xc5, 0x24, 0xbd, 0x90, 0x2a, 0x1c, 0xc7, 0x13, 0x3b}} , + {{0x54, 0xdc, 0x16, 0x0d, 0x18, 0xbe, 0x35, 0x64, 0x61, 0x52, 0x02, 0x80, 0xaf, 0x05, 0xf7, 0xa6, 0x42, 0xd3, 0x8f, 0x2e, 0x79, 0x26, 0xa8, 0xbb, 0xb2, 0x17, 0x48, 0xb2, 0x7a, 0x0a, 0x89, 0x14}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x20, 0xa8, 0x88, 0xe3, 0x91, 0xc0, 0x6e, 0xbb, 0x8a, 0x27, 0x82, 0x51, 0x83, 0xb2, 0x28, 0xa9, 0x83, 0xeb, 0xa6, 0xa9, 0x4d, 0x17, 0x59, 0x22, 0x54, 0x00, 0x50, 0x45, 0xcb, 0x48, 0x4b, 0x18}} , + {{0x33, 0x7c, 0xe7, 0x26, 0xba, 0x4d, 0x32, 0xfe, 0x53, 0xf4, 0xfa, 0x83, 0xe3, 0xa5, 0x79, 0x66, 0x73, 0xef, 0x80, 0x23, 0x68, 0xc2, 0x60, 0xdd, 0xa9, 0x33, 0xdc, 0x03, 0x7a, 0xe0, 0xe0, 0x3e}}}, +{{{0x34, 0x5c, 0x13, 0xfb, 0xc0, 0xe3, 0x78, 0x2b, 0x54, 0x58, 0x22, 0x9b, 0x76, 0x81, 0x7f, 0x93, 0x9c, 0x25, 0x3c, 0xd2, 0xe9, 0x96, 0x21, 0x26, 0x08, 0xf5, 0xed, 0x95, 0x11, 0xae, 0x04, 0x5a}} , + {{0xb9, 0xe8, 0xc5, 0x12, 0x97, 0x1f, 0x83, 0xfe, 0x3e, 0x94, 0x99, 0xd4, 0x2d, 0xf9, 0x52, 0x59, 0x5c, 0x82, 0xa6, 0xf0, 0x75, 0x7e, 0xe8, 0xec, 0xcc, 0xac, 0x18, 0x21, 0x09, 0x67, 0x66, 0x67}}}, +{{{0xb3, 0x40, 0x29, 0xd1, 0xcb, 0x1b, 0x08, 0x9e, 0x9c, 0xb7, 0x53, 0xb9, 0x3b, 0x71, 0x08, 0x95, 0x12, 0x1a, 0x58, 0xaf, 0x7e, 0x82, 0x52, 0x43, 0x4f, 0x11, 0x39, 0xf4, 0x93, 0x1a, 0x26, 0x05}} , + {{0x6e, 0x44, 0xa3, 0xf9, 0x64, 0xaf, 0xe7, 0x6d, 0x7d, 0xdf, 0x1e, 0xac, 0x04, 0xea, 0x3b, 0x5f, 0x9b, 0xe8, 0x24, 0x9d, 0x0e, 0xe5, 0x2e, 0x3e, 0xdf, 0xa9, 0xf7, 0xd4, 0x50, 0x71, 0xf0, 0x78}}}, +{{{0x3e, 0xa8, 0x38, 0xc2, 0x57, 0x56, 0x42, 0x9a, 0xb1, 0xe2, 0xf8, 0x45, 0xaa, 0x11, 0x48, 0x5f, 0x17, 0xc4, 0x54, 0x27, 0xdc, 0x5d, 0xaa, 0xdd, 0x41, 0xbc, 0xdf, 0x81, 0xb9, 0x53, 0xee, 0x52}} , + {{0xc3, 0xf1, 0xa7, 0x6d, 0xb3, 0x5f, 0x92, 0x6f, 0xcc, 0x91, 0xb8, 0x95, 0x05, 0xdf, 0x3c, 0x64, 0x57, 0x39, 0x61, 0x51, 0xad, 0x8c, 0x38, 0x7b, 0xc8, 0xde, 0x00, 0x34, 0xbe, 0xa1, 0xb0, 0x7e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0x24, 0x1d, 0x8a, 0x67, 0x20, 0xee, 0x42, 0xeb, 0x38, 0xed, 0x0b, 0x8b, 0xcd, 0x46, 0x9d, 0x5e, 0x6b, 0x1e, 0x24, 0x9d, 0x12, 0x05, 0x1a, 0xcc, 0x05, 0x4e, 0x92, 0x38, 0xe1, 0x1f, 0x50}} , + {{0x4e, 0xee, 0x1c, 0x91, 0xe6, 0x11, 0xbd, 0x8e, 0x55, 0x1a, 0x18, 0x75, 0x66, 0xaf, 0x4d, 0x7b, 0x0f, 0xae, 0x6d, 0x85, 0xca, 0x82, 0x58, 0x21, 0x9c, 0x18, 0xe0, 0xed, 0xec, 0x22, 0x80, 0x2f}}}, +{{{0x68, 0x3b, 0x0a, 0x39, 0x1d, 0x6a, 0x15, 0x57, 0xfc, 0xf0, 0x63, 0x54, 0xdb, 0x39, 0xdb, 0xe8, 0x5c, 0x64, 0xff, 0xa0, 0x09, 0x4f, 0x3b, 0xb7, 0x32, 0x60, 0x99, 0x94, 0xfd, 0x94, 0x82, 0x2d}} , + {{0x24, 0xf6, 0x5a, 0x44, 0xf1, 0x55, 0x2c, 0xdb, 0xea, 0x7c, 0x84, 0x7c, 0x01, 0xac, 0xe3, 0xfd, 0xc9, 0x27, 0xc1, 0x5a, 0xb9, 0xde, 0x4f, 0x5a, 0x90, 0xdd, 0xc6, 0x67, 0xaa, 0x6f, 0x8a, 0x3a}}}, +{{{0x78, 0x52, 0x87, 0xc9, 0x97, 0x63, 0xb1, 0xdd, 0x54, 0x5f, 0xc1, 0xf8, 0xf1, 0x06, 0xa6, 0xa8, 0xa3, 0x88, 0x82, 0xd4, 0xcb, 0xa6, 0x19, 0xdd, 0xd1, 0x11, 0x87, 0x08, 0x17, 0x4c, 0x37, 0x2a}} , + {{0xa1, 0x0c, 0xf3, 0x08, 0x43, 0xd9, 0x24, 0x1e, 0x83, 0xa7, 0xdf, 0x91, 0xca, 0xbd, 0x69, 0x47, 0x8d, 0x1b, 0xe2, 0xb9, 0x4e, 0xb5, 0xe1, 0x76, 0xb3, 0x1c, 0x93, 0x03, 0xce, 0x5f, 0xb3, 0x5a}}}, +{{{0x1d, 0xda, 0xe4, 0x61, 0x03, 0x50, 0xa9, 0x8b, 0x68, 0x18, 0xef, 0xb2, 0x1c, 0x84, 0x3b, 0xa2, 0x44, 0x95, 0xa3, 0x04, 0x3b, 0xd6, 0x99, 0x00, 0xaf, 0x76, 0x42, 0x67, 0x02, 0x7d, 0x85, 0x56}} , + {{0xce, 0x72, 0x0e, 0x29, 0x84, 0xb2, 0x7d, 0xd2, 0x45, 0xbe, 0x57, 0x06, 0xed, 0x7f, 0xcf, 0xed, 0xcd, 0xef, 0x19, 0xd6, 0xbc, 0x15, 0x79, 0x64, 0xd2, 0x18, 0xe3, 0x20, 0x67, 0x3a, 0x54, 0x0b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x52, 0xfd, 0x04, 0xc5, 0xfb, 0x99, 0xe7, 0xe8, 0xfb, 0x8c, 0xe1, 0x42, 0x03, 0xef, 0x9d, 0xd9, 0x9e, 0x4d, 0xf7, 0x80, 0xcf, 0x2e, 0xcc, 0x9b, 0x45, 0xc9, 0x7b, 0x7a, 0xbc, 0x37, 0xa8, 0x52}} , + {{0x96, 0x11, 0x41, 0x8a, 0x47, 0x91, 0xfe, 0xb6, 0xda, 0x7a, 0x54, 0x63, 0xd1, 0x14, 0x35, 0x05, 0x86, 0x8c, 0xa9, 0x36, 0x3f, 0xf2, 0x85, 0x54, 0x4e, 0x92, 0xd8, 0x85, 0x01, 0x46, 0xd6, 0x50}}}, +{{{0x53, 0xcd, 0xf3, 0x86, 0x40, 0xe6, 0x39, 0x42, 0x95, 0xd6, 0xcb, 0x45, 0x1a, 0x20, 0xc8, 0x45, 0x4b, 0x32, 0x69, 0x04, 0xb1, 0xaf, 0x20, 0x46, 0xc7, 0x6b, 0x23, 0x5b, 0x69, 0xee, 0x30, 0x3f}} , + {{0x70, 0x83, 0x47, 0xc0, 0xdb, 0x55, 0x08, 0xa8, 0x7b, 0x18, 0x6d, 0xf5, 0x04, 0x5a, 0x20, 0x0c, 0x4a, 0x8c, 0x60, 0xae, 0xae, 0x0f, 0x64, 0x55, 0x55, 0x2e, 0xd5, 0x1d, 0x53, 0x31, 0x42, 0x41}}}, +{{{0xca, 0xfc, 0x88, 0x6b, 0x96, 0x78, 0x0a, 0x8b, 0x83, 0xdc, 0xbc, 0xaf, 0x40, 0xb6, 0x8d, 0x7f, 0xef, 0xb4, 0xd1, 0x3f, 0xcc, 0xa2, 0x74, 0xc9, 0xc2, 0x92, 0x55, 0x00, 0xab, 0xdb, 0xbf, 0x4f}} , + {{0x93, 0x1c, 0x06, 0x2d, 0x66, 0x65, 0x02, 0xa4, 0x97, 0x18, 0xfd, 0x00, 0xe7, 0xab, 0x03, 0xec, 0xce, 0xc1, 0xbf, 0x37, 0xf8, 0x13, 0x53, 0xa5, 0xe5, 0x0c, 0x3a, 0xa8, 0x55, 0xb9, 0xff, 0x68}}}, +{{{0xe4, 0xe6, 0x6d, 0x30, 0x7d, 0x30, 0x35, 0xc2, 0x78, 0x87, 0xf9, 0xfc, 0x6b, 0x5a, 0xc3, 0xb7, 0x65, 0xd8, 0x2e, 0xc7, 0xa5, 0x0c, 0xc6, 0xdc, 0x12, 0xaa, 0xd6, 0x4f, 0xc5, 0x38, 0xbc, 0x0e}} , + {{0xe2, 0x3c, 0x76, 0x86, 0x38, 0xf2, 0x7b, 0x2c, 0x16, 0x78, 0x8d, 0xf5, 0xa4, 0x15, 0xda, 0xdb, 0x26, 0x85, 0xa0, 0x56, 0xdd, 0x1d, 0xe3, 0xb3, 0xfd, 0x40, 0xef, 0xf2, 0xd9, 0xa1, 0xb3, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xdb, 0x49, 0x0e, 0xe6, 0x58, 0x10, 0x7a, 0x52, 0xda, 0xb5, 0x7d, 0x37, 0x6a, 0x3e, 0xa1, 0x78, 0xce, 0xc7, 0x1c, 0x24, 0x23, 0xdb, 0x7d, 0xfb, 0x8c, 0x8d, 0xdc, 0x30, 0x67, 0x69, 0x75, 0x3b}} , + {{0xa9, 0xea, 0x6d, 0x16, 0x16, 0x60, 0xf4, 0x60, 0x87, 0x19, 0x44, 0x8c, 0x4a, 0x8b, 0x3e, 0xfb, 0x16, 0x00, 0x00, 0x54, 0xa6, 0x9e, 0x9f, 0xef, 0xcf, 0xd9, 0xd2, 0x4c, 0x74, 0x31, 0xd0, 0x34}}}, +{{{0xa4, 0xeb, 0x04, 0xa4, 0x8c, 0x8f, 0x71, 0x27, 0x95, 0x85, 0x5d, 0x55, 0x4b, 0xb1, 0x26, 0x26, 0xc8, 0xae, 0x6a, 0x7d, 0xa2, 0x21, 0xca, 0xce, 0x38, 0xab, 0x0f, 0xd0, 0xd5, 0x2b, 0x6b, 0x00}} , + {{0xe5, 0x67, 0x0c, 0xf1, 0x3a, 0x9a, 0xea, 0x09, 0x39, 0xef, 0xd1, 0x30, 0xbc, 0x33, 0xba, 0xb1, 0x6a, 0xc5, 0x27, 0x08, 0x7f, 0x54, 0x80, 0x3d, 0xab, 0xf6, 0x15, 0x7a, 0xc2, 0x40, 0x73, 0x72}}}, +{{{0x84, 0x56, 0x82, 0xb6, 0x12, 0x70, 0x7f, 0xf7, 0xf0, 0xbd, 0x5b, 0xa9, 0xd5, 0xc5, 0x5f, 0x59, 0xbf, 0x7f, 0xb3, 0x55, 0x22, 0x02, 0xc9, 0x44, 0x55, 0x87, 0x8f, 0x96, 0x98, 0x64, 0x6d, 0x15}} , + {{0xb0, 0x8b, 0xaa, 0x1e, 0xec, 0xc7, 0xa5, 0x8f, 0x1f, 0x92, 0x04, 0xc6, 0x05, 0xf6, 0xdf, 0xa1, 0xcc, 0x1f, 0x81, 0xf5, 0x0e, 0x9c, 0x57, 0xdc, 0xe3, 0xbb, 0x06, 0x87, 0x1e, 0xfe, 0x23, 0x6c}}}, +{{{0xd8, 0x2b, 0x5b, 0x16, 0xea, 0x20, 0xf1, 0xd3, 0x68, 0x8f, 0xae, 0x5b, 0xd0, 0xa9, 0x1a, 0x19, 0xa8, 0x36, 0xfb, 0x2b, 0x57, 0x88, 0x7d, 0x90, 0xd5, 0xa6, 0xf3, 0xdc, 0x38, 0x89, 0x4e, 0x1f}} , + {{0xcc, 0x19, 0xda, 0x9b, 0x3b, 0x43, 0x48, 0x21, 0x2e, 0x23, 0x4d, 0x3d, 0xae, 0xf8, 0x8c, 0xfc, 0xdd, 0xa6, 0x74, 0x37, 0x65, 0xca, 0xee, 0x1a, 0x19, 0x8e, 0x9f, 0x64, 0x6f, 0x0c, 0x8b, 0x5a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0xb9, 0xc2, 0xf0, 0x72, 0xb8, 0x15, 0x16, 0xcc, 0x8d, 0x3c, 0x6f, 0x25, 0xed, 0xf4, 0x46, 0x2e, 0x0c, 0x60, 0x0f, 0xe2, 0x84, 0x34, 0x55, 0x89, 0x59, 0x34, 0x1b, 0xf5, 0x8d, 0xfe, 0x08}} , + {{0xf8, 0xab, 0x93, 0xbc, 0x44, 0xba, 0x1b, 0x75, 0x4b, 0x49, 0x6f, 0xd0, 0x54, 0x2e, 0x63, 0xba, 0xb5, 0xea, 0xed, 0x32, 0x14, 0xc9, 0x94, 0xd8, 0xc5, 0xce, 0xf4, 0x10, 0x68, 0xe0, 0x38, 0x27}}}, +{{{0x74, 0x1c, 0x14, 0x9b, 0xd4, 0x64, 0x61, 0x71, 0x5a, 0xb6, 0x21, 0x33, 0x4f, 0xf7, 0x8e, 0xba, 0xa5, 0x48, 0x9a, 0xc7, 0xfa, 0x9a, 0xf0, 0xb4, 0x62, 0xad, 0xf2, 0x5e, 0xcc, 0x03, 0x24, 0x1a}} , + {{0xf5, 0x76, 0xfd, 0xe4, 0xaf, 0xb9, 0x03, 0x59, 0xce, 0x63, 0xd2, 0x3b, 0x1f, 0xcd, 0x21, 0x0c, 0xad, 0x44, 0xa5, 0x97, 0xac, 0x80, 0x11, 0x02, 0x9b, 0x0c, 0xe5, 0x8b, 0xcd, 0xfb, 0x79, 0x77}}}, +{{{0x15, 0xbe, 0x9a, 0x0d, 0xba, 0x38, 0x72, 0x20, 0x8a, 0xf5, 0xbe, 0x59, 0x93, 0x79, 0xb7, 0xf6, 0x6a, 0x0c, 0x38, 0x27, 0x1a, 0x60, 0xf4, 0x86, 0x3b, 0xab, 0x5a, 0x00, 0xa0, 0xce, 0x21, 0x7d}} , + {{0x6c, 0xba, 0x14, 0xc5, 0xea, 0x12, 0x9e, 0x2e, 0x82, 0x63, 0xce, 0x9b, 0x4a, 0xe7, 0x1d, 0xec, 0xf1, 0x2e, 0x51, 0x1c, 0xf4, 0xd0, 0x69, 0x15, 0x42, 0x9d, 0xa3, 0x3f, 0x0e, 0xbf, 0xe9, 0x5c}}}, +{{{0xe4, 0x0d, 0xf4, 0xbd, 0xee, 0x31, 0x10, 0xed, 0xcb, 0x12, 0x86, 0xad, 0xd4, 0x2f, 0x90, 0x37, 0x32, 0xc3, 0x0b, 0x73, 0xec, 0x97, 0x85, 0xa4, 0x01, 0x1c, 0x76, 0x35, 0xfe, 0x75, 0xdd, 0x71}} , + {{0x11, 0xa4, 0x88, 0x9f, 0x3e, 0x53, 0x69, 0x3b, 0x1b, 0xe0, 0xf7, 0xba, 0x9b, 0xad, 0x4e, 0x81, 0x5f, 0xb5, 0x5c, 0xae, 0xbe, 0x67, 0x86, 0x37, 0x34, 0x8e, 0x07, 0x32, 0x45, 0x4a, 0x67, 0x39}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x90, 0x70, 0x58, 0x20, 0x03, 0x1e, 0x67, 0xb2, 0xc8, 0x9b, 0x58, 0xc5, 0xb1, 0xeb, 0x2d, 0x4a, 0xde, 0x82, 0x8c, 0xf2, 0xd2, 0x14, 0xb8, 0x70, 0x61, 0x4e, 0x73, 0xd6, 0x0b, 0x6b, 0x0d, 0x30}} , + {{0x81, 0xfc, 0x55, 0x5c, 0xbf, 0xa7, 0xc4, 0xbd, 0xe2, 0xf0, 0x4b, 0x8f, 0xe9, 0x7d, 0x99, 0xfa, 0xd3, 0xab, 0xbc, 0xc7, 0x83, 0x2b, 0x04, 0x7f, 0x0c, 0x19, 0x43, 0x03, 0x3d, 0x07, 0xca, 0x40}}}, +{{{0xf9, 0xc8, 0xbe, 0x8c, 0x16, 0x81, 0x39, 0x96, 0xf6, 0x17, 0x58, 0xc8, 0x30, 0x58, 0xfb, 0xc2, 0x03, 0x45, 0xd2, 0x52, 0x76, 0xe0, 0x6a, 0x26, 0x28, 0x5c, 0x88, 0x59, 0x6a, 0x5a, 0x54, 0x42}} , + {{0x07, 0xb5, 0x2e, 0x2c, 0x67, 0x15, 0x9b, 0xfb, 0x83, 0x69, 0x1e, 0x0f, 0xda, 0xd6, 0x29, 0xb1, 0x60, 0xe0, 0xb2, 0xba, 0x69, 0xa2, 0x9e, 0xbd, 0xbd, 0xe0, 0x1c, 0xbd, 0xcd, 0x06, 0x64, 0x70}}}, +{{{0x41, 0xfa, 0x8c, 0xe1, 0x89, 0x8f, 0x27, 0xc8, 0x25, 0x8f, 0x6f, 0x5f, 0x55, 0xf8, 0xde, 0x95, 0x6d, 0x2f, 0x75, 0x16, 0x2b, 0x4e, 0x44, 0xfd, 0x86, 0x6e, 0xe9, 0x70, 0x39, 0x76, 0x97, 0x7e}} , + {{0x17, 0x62, 0x6b, 0x14, 0xa1, 0x7c, 0xd0, 0x79, 0x6e, 0xd8, 0x8a, 0xa5, 0x6d, 0x8c, 0x93, 0xd2, 0x3f, 0xec, 0x44, 0x8d, 0x6e, 0x91, 0x01, 0x8c, 0x8f, 0xee, 0x01, 0x8f, 0xc0, 0xb4, 0x85, 0x0e}}}, +{{{0x02, 0x3a, 0x70, 0x41, 0xe4, 0x11, 0x57, 0x23, 0xac, 0xe6, 0xfc, 0x54, 0x7e, 0xcd, 0xd7, 0x22, 0xcb, 0x76, 0x9f, 0x20, 0xce, 0xa0, 0x73, 0x76, 0x51, 0x3b, 0xa4, 0xf8, 0xe3, 0x62, 0x12, 0x6c}} , + {{0x7f, 0x00, 0x9c, 0x26, 0x0d, 0x6f, 0x48, 0x7f, 0x3a, 0x01, 0xed, 0xc5, 0x96, 0xb0, 0x1f, 0x4f, 0xa8, 0x02, 0x62, 0x27, 0x8a, 0x50, 0x8d, 0x9a, 0x8b, 0x52, 0x0f, 0x1e, 0xcf, 0x41, 0x38, 0x19}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf5, 0x6c, 0xd4, 0x2f, 0x0f, 0x69, 0x0f, 0x87, 0x3f, 0x61, 0x65, 0x1e, 0x35, 0x34, 0x85, 0xba, 0x02, 0x30, 0xac, 0x25, 0x3d, 0xe2, 0x62, 0xf1, 0xcc, 0xe9, 0x1b, 0xc2, 0xef, 0x6a, 0x42, 0x57}} , + {{0x34, 0x1f, 0x2e, 0xac, 0xd1, 0xc7, 0x04, 0x52, 0x32, 0x66, 0xb2, 0x33, 0x73, 0x21, 0x34, 0x54, 0xf7, 0x71, 0xed, 0x06, 0xb0, 0xff, 0xa6, 0x59, 0x6f, 0x8a, 0x4e, 0xfb, 0x02, 0xb0, 0x45, 0x6b}}}, +{{{0xf5, 0x48, 0x0b, 0x03, 0xc5, 0x22, 0x7d, 0x80, 0x08, 0x53, 0xfe, 0x32, 0xb1, 0xa1, 0x8a, 0x74, 0x6f, 0xbd, 0x3f, 0x85, 0xf4, 0xcf, 0xf5, 0x60, 0xaf, 0x41, 0x7e, 0x3e, 0x46, 0xa3, 0x5a, 0x20}} , + {{0xaa, 0x35, 0x87, 0x44, 0x63, 0x66, 0x97, 0xf8, 0x6e, 0x55, 0x0c, 0x04, 0x3e, 0x35, 0x50, 0xbf, 0x93, 0x69, 0xd2, 0x8b, 0x05, 0x55, 0x99, 0xbe, 0xe2, 0x53, 0x61, 0xec, 0xe8, 0x08, 0x0b, 0x32}}}, +{{{0xb3, 0x10, 0x45, 0x02, 0x69, 0x59, 0x2e, 0x97, 0xd9, 0x64, 0xf8, 0xdb, 0x25, 0x80, 0xdc, 0xc4, 0xd5, 0x62, 0x3c, 0xed, 0x65, 0x91, 0xad, 0xd1, 0x57, 0x81, 0x94, 0xaa, 0xa1, 0x29, 0xfc, 0x68}} , + {{0xdd, 0xb5, 0x7d, 0xab, 0x5a, 0x21, 0x41, 0x53, 0xbb, 0x17, 0x79, 0x0d, 0xd1, 0xa8, 0x0c, 0x0c, 0x20, 0x88, 0x09, 0xe9, 0x84, 0xe8, 0x25, 0x11, 0x67, 0x7a, 0x8b, 0x1a, 0xe4, 0x5d, 0xe1, 0x5d}}}, +{{{0x37, 0xea, 0xfe, 0x65, 0x3b, 0x25, 0xe8, 0xe1, 0xc2, 0xc5, 0x02, 0xa4, 0xbe, 0x98, 0x0a, 0x2b, 0x61, 0xc1, 0x9b, 0xe2, 0xd5, 0x92, 0xe6, 0x9e, 0x7d, 0x1f, 0xca, 0x43, 0x88, 0x8b, 0x2c, 0x59}} , + {{0xe0, 0xb5, 0x00, 0x1d, 0x2a, 0x6f, 0xaf, 0x79, 0x86, 0x2f, 0xa6, 0x5a, 0x93, 0xd1, 0xfe, 0xae, 0x3a, 0xee, 0xdb, 0x7c, 0x61, 0xbe, 0x7c, 0x01, 0xf9, 0xfe, 0x52, 0xdc, 0xd8, 0x52, 0xa3, 0x42}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x22, 0xaf, 0x13, 0x37, 0xbd, 0x37, 0x71, 0xac, 0x04, 0x46, 0x63, 0xac, 0xa4, 0x77, 0xed, 0x25, 0x38, 0xe0, 0x15, 0xa8, 0x64, 0x00, 0x0d, 0xce, 0x51, 0x01, 0xa9, 0xbc, 0x0f, 0x03, 0x1c, 0x04}} , + {{0x89, 0xf9, 0x80, 0x07, 0xcf, 0x3f, 0xb3, 0xe9, 0xe7, 0x45, 0x44, 0x3d, 0x2a, 0x7c, 0xe9, 0xe4, 0x16, 0x5c, 0x5e, 0x65, 0x1c, 0xc7, 0x7d, 0xc6, 0x7a, 0xfb, 0x43, 0xee, 0x25, 0x76, 0x46, 0x72}}}, +{{{0x02, 0xa2, 0xed, 0xf4, 0x8f, 0x6b, 0x0b, 0x3e, 0xeb, 0x35, 0x1a, 0xd5, 0x7e, 0xdb, 0x78, 0x00, 0x96, 0x8a, 0xa0, 0xb4, 0xcf, 0x60, 0x4b, 0xd4, 0xd5, 0xf9, 0x2d, 0xbf, 0x88, 0xbd, 0x22, 0x62}} , + {{0x13, 0x53, 0xe4, 0x82, 0x57, 0xfa, 0x1e, 0x8f, 0x06, 0x2b, 0x90, 0xba, 0x08, 0xb6, 0x10, 0x54, 0x4f, 0x7c, 0x1b, 0x26, 0xed, 0xda, 0x6b, 0xdd, 0x25, 0xd0, 0x4e, 0xea, 0x42, 0xbb, 0x25, 0x03}}}, +{{{0x51, 0x16, 0x50, 0x7c, 0xd5, 0x5d, 0xf6, 0x99, 0xe8, 0x77, 0x72, 0x4e, 0xfa, 0x62, 0xcb, 0x76, 0x75, 0x0c, 0xe2, 0x71, 0x98, 0x92, 0xd5, 0xfa, 0x45, 0xdf, 0x5c, 0x6f, 0x1e, 0x9e, 0x28, 0x69}} , + {{0x0d, 0xac, 0x66, 0x6d, 0xc3, 0x8b, 0xba, 0x16, 0xb5, 0xe2, 0xa0, 0x0d, 0x0c, 0xbd, 0xa4, 0x8e, 0x18, 0x6c, 0xf2, 0xdc, 0xf9, 0xdc, 0x4a, 0x86, 0x25, 0x95, 0x14, 0xcb, 0xd8, 0x1a, 0x04, 0x0f}}}, +{{{0x97, 0xa5, 0xdb, 0x8b, 0x2d, 0xaa, 0x42, 0x11, 0x09, 0xf2, 0x93, 0xbb, 0xd9, 0x06, 0x84, 0x4e, 0x11, 0xa8, 0xa0, 0x25, 0x2b, 0xa6, 0x5f, 0xae, 0xc4, 0xb4, 0x4c, 0xc8, 0xab, 0xc7, 0x3b, 0x02}} , + {{0xee, 0xc9, 0x29, 0x0f, 0xdf, 0x11, 0x85, 0xed, 0xce, 0x0d, 0x62, 0x2c, 0x8f, 0x4b, 0xf9, 0x04, 0xe9, 0x06, 0x72, 0x1d, 0x37, 0x20, 0x50, 0xc9, 0x14, 0xeb, 0xec, 0x39, 0xa7, 0x97, 0x2b, 0x4d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x69, 0xd1, 0x39, 0xbd, 0xfb, 0x33, 0xbe, 0xc4, 0xf0, 0x5c, 0xef, 0xf0, 0x56, 0x68, 0xfc, 0x97, 0x47, 0xc8, 0x72, 0xb6, 0x53, 0xa4, 0x0a, 0x98, 0xa5, 0xb4, 0x37, 0x71, 0xcf, 0x66, 0x50, 0x6d}} , + {{0x17, 0xa4, 0x19, 0x52, 0x11, 0x47, 0xb3, 0x5c, 0x5b, 0xa9, 0x2e, 0x22, 0xb4, 0x00, 0x52, 0xf9, 0x57, 0x18, 0xb8, 0xbe, 0x5a, 0xe3, 0xab, 0x83, 0xc8, 0x87, 0x0a, 0x2a, 0xd8, 0x8c, 0xbb, 0x54}}}, +{{{0xa9, 0x62, 0x93, 0x85, 0xbe, 0xe8, 0x73, 0x4a, 0x0e, 0xb0, 0xb5, 0x2d, 0x94, 0x50, 0xaa, 0xd3, 0xb2, 0xea, 0x9d, 0x62, 0x76, 0x3b, 0x07, 0x34, 0x4e, 0x2d, 0x70, 0xc8, 0x9a, 0x15, 0x66, 0x6b}} , + {{0xc5, 0x96, 0xca, 0xc8, 0x22, 0x1a, 0xee, 0x5f, 0xe7, 0x31, 0x60, 0x22, 0x83, 0x08, 0x63, 0xce, 0xb9, 0x32, 0x44, 0x58, 0x5d, 0x3a, 0x9b, 0xe4, 0x04, 0xd5, 0xef, 0x38, 0xef, 0x4b, 0xdd, 0x19}}}, +{{{0x4d, 0xc2, 0x17, 0x75, 0xa1, 0x68, 0xcd, 0xc3, 0xc6, 0x03, 0x44, 0xe3, 0x78, 0x09, 0x91, 0x47, 0x3f, 0x0f, 0xe4, 0x92, 0x58, 0xfa, 0x7d, 0x1f, 0x20, 0x94, 0x58, 0x5e, 0xbc, 0x19, 0x02, 0x6f}} , + {{0x20, 0xd6, 0xd8, 0x91, 0x54, 0xa7, 0xf3, 0x20, 0x4b, 0x34, 0x06, 0xfa, 0x30, 0xc8, 0x6f, 0x14, 0x10, 0x65, 0x74, 0x13, 0x4e, 0xf0, 0x69, 0x26, 0xce, 0xcf, 0x90, 0xf4, 0xd0, 0xc5, 0xc8, 0x64}}}, +{{{0x26, 0xa2, 0x50, 0x02, 0x24, 0x72, 0xf1, 0xf0, 0x4e, 0x2d, 0x93, 0xd5, 0x08, 0xe7, 0xae, 0x38, 0xf7, 0x18, 0xa5, 0x32, 0x34, 0xc2, 0xf0, 0xa6, 0xec, 0xb9, 0x61, 0x7b, 0x64, 0x99, 0xac, 0x71}} , + {{0x25, 0xcf, 0x74, 0x55, 0x1b, 0xaa, 0xa9, 0x38, 0x41, 0x40, 0xd5, 0x95, 0x95, 0xab, 0x1c, 0x5e, 0xbc, 0x41, 0x7e, 0x14, 0x30, 0xbe, 0x13, 0x89, 0xf4, 0xe5, 0xeb, 0x28, 0xc0, 0xc2, 0x96, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2b, 0x77, 0x45, 0xec, 0x67, 0x76, 0x32, 0x4c, 0xb9, 0xdf, 0x25, 0x32, 0x6b, 0xcb, 0xe7, 0x14, 0x61, 0x43, 0xee, 0xba, 0x9b, 0x71, 0xef, 0xd2, 0x48, 0x65, 0xbb, 0x1b, 0x8a, 0x13, 0x1b, 0x22}} , + {{0x84, 0xad, 0x0c, 0x18, 0x38, 0x5a, 0xba, 0xd0, 0x98, 0x59, 0xbf, 0x37, 0xb0, 0x4f, 0x97, 0x60, 0x20, 0xb3, 0x9b, 0x97, 0xf6, 0x08, 0x6c, 0xa4, 0xff, 0xfb, 0xb7, 0xfa, 0x95, 0xb2, 0x51, 0x79}}}, +{{{0x28, 0x5c, 0x3f, 0xdb, 0x6b, 0x18, 0x3b, 0x5c, 0xd1, 0x04, 0x28, 0xde, 0x85, 0x52, 0x31, 0xb5, 0xbb, 0xf6, 0xa9, 0xed, 0xbe, 0x28, 0x4f, 0xb3, 0x7e, 0x05, 0x6a, 0xdb, 0x95, 0x0d, 0x1b, 0x1c}} , + {{0xd5, 0xc5, 0xc3, 0x9a, 0x0a, 0xd0, 0x31, 0x3e, 0x07, 0x36, 0x8e, 0xc0, 0x8a, 0x62, 0xb1, 0xca, 0xd6, 0x0e, 0x1e, 0x9d, 0xef, 0xab, 0x98, 0x4d, 0xbb, 0x6c, 0x05, 0xe0, 0xe4, 0x5d, 0xbd, 0x57}}}, +{{{0xcc, 0x21, 0x27, 0xce, 0xfd, 0xa9, 0x94, 0x8e, 0xe1, 0xab, 0x49, 0xe0, 0x46, 0x26, 0xa1, 0xa8, 0x8c, 0xa1, 0x99, 0x1d, 0xb4, 0x27, 0x6d, 0x2d, 0xc8, 0x39, 0x30, 0x5e, 0x37, 0x52, 0xc4, 0x6e}} , + {{0xa9, 0x85, 0xf4, 0xe7, 0xb0, 0x15, 0x33, 0x84, 0x1b, 0x14, 0x1a, 0x02, 0xd9, 0x3b, 0xad, 0x0f, 0x43, 0x6c, 0xea, 0x3e, 0x0f, 0x7e, 0xda, 0xdd, 0x6b, 0x4c, 0x7f, 0x6e, 0xd4, 0x6b, 0xbf, 0x0f}}}, +{{{0x47, 0x9f, 0x7c, 0x56, 0x7c, 0x43, 0x91, 0x1c, 0xbb, 0x4e, 0x72, 0x3e, 0x64, 0xab, 0xa0, 0xa0, 0xdf, 0xb4, 0xd8, 0x87, 0x3a, 0xbd, 0xa8, 0x48, 0xc9, 0xb8, 0xef, 0x2e, 0xad, 0x6f, 0x84, 0x4f}} , + {{0x2d, 0x2d, 0xf0, 0x1b, 0x7e, 0x2a, 0x6c, 0xf8, 0xa9, 0x6a, 0xe1, 0xf0, 0x99, 0xa1, 0x67, 0x9a, 0xd4, 0x13, 0xca, 0xca, 0xba, 0x27, 0x92, 0xaa, 0xa1, 0x5d, 0x50, 0xde, 0xcc, 0x40, 0x26, 0x0a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9f, 0x3e, 0xf2, 0xb2, 0x90, 0xce, 0xdb, 0x64, 0x3e, 0x03, 0xdd, 0x37, 0x36, 0x54, 0x70, 0x76, 0x24, 0xb5, 0x69, 0x03, 0xfc, 0xa0, 0x2b, 0x74, 0xb2, 0x05, 0x0e, 0xcc, 0xd8, 0x1f, 0x6a, 0x1f}} , + {{0x19, 0x5e, 0x60, 0x69, 0x58, 0x86, 0xa0, 0x31, 0xbd, 0x32, 0xe9, 0x2c, 0x5c, 0xd2, 0x85, 0xba, 0x40, 0x64, 0xa8, 0x74, 0xf8, 0x0e, 0x1c, 0xb3, 0xa9, 0x69, 0xe8, 0x1e, 0x40, 0x64, 0x99, 0x77}}}, +{{{0x6c, 0x32, 0x4f, 0xfd, 0xbb, 0x5c, 0xbb, 0x8d, 0x64, 0x66, 0x4a, 0x71, 0x1f, 0x79, 0xa3, 0xad, 0x8d, 0xf9, 0xd4, 0xec, 0xcf, 0x67, 0x70, 0xfa, 0x05, 0x4a, 0x0f, 0x6e, 0xaf, 0x87, 0x0a, 0x6f}} , + {{0xc6, 0x36, 0x6e, 0x6c, 0x8c, 0x24, 0x09, 0x60, 0xbe, 0x26, 0xd2, 0x4c, 0x5e, 0x17, 0xca, 0x5f, 0x1d, 0xcc, 0x87, 0xe8, 0x42, 0x6a, 0xcb, 0xcb, 0x7d, 0x92, 0x05, 0x35, 0x81, 0x13, 0x60, 0x6b}}}, +{{{0xf4, 0x15, 0xcd, 0x0f, 0x0a, 0xaf, 0x4e, 0x6b, 0x51, 0xfd, 0x14, 0xc4, 0x2e, 0x13, 0x86, 0x74, 0x44, 0xcb, 0x66, 0x6b, 0xb6, 0x9d, 0x74, 0x56, 0x32, 0xac, 0x8d, 0x8e, 0x8c, 0x8c, 0x8c, 0x39}} , + {{0xca, 0x59, 0x74, 0x1a, 0x11, 0xef, 0x6d, 0xf7, 0x39, 0x5c, 0x3b, 0x1f, 0xfa, 0xe3, 0x40, 0x41, 0x23, 0x9e, 0xf6, 0xd1, 0x21, 0xa2, 0xbf, 0xad, 0x65, 0x42, 0x6b, 0x59, 0x8a, 0xe8, 0xc5, 0x7f}}}, +{{{0x64, 0x05, 0x7a, 0x84, 0x4a, 0x13, 0xc3, 0xf6, 0xb0, 0x6e, 0x9a, 0x6b, 0x53, 0x6b, 0x32, 0xda, 0xd9, 0x74, 0x75, 0xc4, 0xba, 0x64, 0x3d, 0x3b, 0x08, 0xdd, 0x10, 0x46, 0xef, 0xc7, 0x90, 0x1f}} , + {{0x7b, 0x2f, 0x3a, 0xce, 0xc8, 0xa1, 0x79, 0x3c, 0x30, 0x12, 0x44, 0x28, 0xf6, 0xbc, 0xff, 0xfd, 0xf4, 0xc0, 0x97, 0xb0, 0xcc, 0xc3, 0x13, 0x7a, 0xb9, 0x9a, 0x16, 0xe4, 0xcb, 0x4c, 0x34, 0x63}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x07, 0x4e, 0xd3, 0x2d, 0x09, 0x33, 0x0e, 0xd2, 0x0d, 0xbe, 0x3e, 0xe7, 0xe4, 0xaa, 0xb7, 0x00, 0x8b, 0xe8, 0xad, 0xaa, 0x7a, 0x8d, 0x34, 0x28, 0xa9, 0x81, 0x94, 0xc5, 0xe7, 0x42, 0xac, 0x47}} , + {{0x24, 0x89, 0x7a, 0x8f, 0xb5, 0x9b, 0xf0, 0xc2, 0x03, 0x64, 0xd0, 0x1e, 0xf5, 0xa4, 0xb2, 0xf3, 0x74, 0xe9, 0x1a, 0x16, 0xfd, 0xcb, 0x15, 0xea, 0xeb, 0x10, 0x6c, 0x35, 0xd1, 0xc1, 0xa6, 0x28}}}, +{{{0xcc, 0xd5, 0x39, 0xfc, 0xa5, 0xa4, 0xad, 0x32, 0x15, 0xce, 0x19, 0xe8, 0x34, 0x2b, 0x1c, 0x60, 0x91, 0xfc, 0x05, 0xa9, 0xb3, 0xdc, 0x80, 0x29, 0xc4, 0x20, 0x79, 0x06, 0x39, 0xc0, 0xe2, 0x22}} , + {{0xbb, 0xa8, 0xe1, 0x89, 0x70, 0x57, 0x18, 0x54, 0x3c, 0xf6, 0x0d, 0x82, 0x12, 0x05, 0x87, 0x96, 0x06, 0x39, 0xe3, 0xf8, 0xb3, 0x95, 0xe5, 0xd7, 0x26, 0xbf, 0x09, 0x5a, 0x94, 0xf9, 0x1c, 0x63}}}, +{{{0x2b, 0x8c, 0x2d, 0x9a, 0x8b, 0x84, 0xf2, 0x56, 0xfb, 0xad, 0x2e, 0x7f, 0xb7, 0xfc, 0x30, 0xe1, 0x35, 0x89, 0xba, 0x4d, 0xa8, 0x6d, 0xce, 0x8c, 0x8b, 0x30, 0xe0, 0xda, 0x29, 0x18, 0x11, 0x17}} , + {{0x19, 0xa6, 0x5a, 0x65, 0x93, 0xc3, 0xb5, 0x31, 0x22, 0x4f, 0xf3, 0xf6, 0x0f, 0xeb, 0x28, 0xc3, 0x7c, 0xeb, 0xce, 0x86, 0xec, 0x67, 0x76, 0x6e, 0x35, 0x45, 0x7b, 0xd8, 0x6b, 0x92, 0x01, 0x65}}}, +{{{0x3d, 0xd5, 0x9a, 0x64, 0x73, 0x36, 0xb1, 0xd6, 0x86, 0x98, 0x42, 0x3f, 0x8a, 0xf1, 0xc7, 0xf5, 0x42, 0xa8, 0x9c, 0x52, 0xa8, 0xdc, 0xf9, 0x24, 0x3f, 0x4a, 0xa1, 0xa4, 0x5b, 0xe8, 0x62, 0x1a}} , + {{0xc5, 0xbd, 0xc8, 0x14, 0xd5, 0x0d, 0xeb, 0xe1, 0xa5, 0xe6, 0x83, 0x11, 0x09, 0x00, 0x1d, 0x55, 0x83, 0x51, 0x7e, 0x75, 0x00, 0x81, 0xb9, 0xcb, 0xd8, 0xc5, 0xe5, 0xa1, 0xd9, 0x17, 0x6d, 0x1f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xea, 0xf9, 0xe4, 0xe9, 0xe1, 0x52, 0x3f, 0x51, 0x19, 0x0d, 0xdd, 0xd9, 0x9d, 0x93, 0x31, 0x87, 0x23, 0x09, 0xd5, 0x83, 0xeb, 0x92, 0x09, 0x76, 0x6e, 0xe3, 0xf8, 0xc0, 0xa2, 0x66, 0xb5, 0x36}} , + {{0x3a, 0xbb, 0x39, 0xed, 0x32, 0x02, 0xe7, 0x43, 0x7a, 0x38, 0x14, 0x84, 0xe3, 0x44, 0xd2, 0x5e, 0x94, 0xdd, 0x78, 0x89, 0x55, 0x4c, 0x73, 0x9e, 0xe1, 0xe4, 0x3e, 0x43, 0xd0, 0x4a, 0xde, 0x1b}}}, +{{{0xb2, 0xe7, 0x8f, 0xe3, 0xa3, 0xc5, 0xcb, 0x72, 0xee, 0x79, 0x41, 0xf8, 0xdf, 0xee, 0x65, 0xc5, 0x45, 0x77, 0x27, 0x3c, 0xbd, 0x58, 0xd3, 0x75, 0xe2, 0x04, 0x4b, 0xbb, 0x65, 0xf3, 0xc8, 0x0f}} , + {{0x24, 0x7b, 0x93, 0x34, 0xb5, 0xe2, 0x74, 0x48, 0xcd, 0xa0, 0x0b, 0x92, 0x97, 0x66, 0x39, 0xf4, 0xb0, 0xe2, 0x5d, 0x39, 0x6a, 0x5b, 0x45, 0x17, 0x78, 0x1e, 0xdb, 0x91, 0x81, 0x1c, 0xf9, 0x16}}}, +{{{0x16, 0xdf, 0xd1, 0x5a, 0xd5, 0xe9, 0x4e, 0x58, 0x95, 0x93, 0x5f, 0x51, 0x09, 0xc3, 0x2a, 0xc9, 0xd4, 0x55, 0x48, 0x79, 0xa4, 0xa3, 0xb2, 0xc3, 0x62, 0xaa, 0x8c, 0xe8, 0xad, 0x47, 0x39, 0x1b}} , + {{0x46, 0xda, 0x9e, 0x51, 0x3a, 0xe6, 0xd1, 0xa6, 0xbb, 0x4d, 0x7b, 0x08, 0xbe, 0x8c, 0xd5, 0xf3, 0x3f, 0xfd, 0xf7, 0x44, 0x80, 0x2d, 0x53, 0x4b, 0xd0, 0x87, 0x68, 0xc1, 0xb5, 0xd8, 0xf7, 0x07}}}, +{{{0xf4, 0x10, 0x46, 0xbe, 0xb7, 0xd2, 0xd1, 0xce, 0x5e, 0x76, 0xa2, 0xd7, 0x03, 0xdc, 0xe4, 0x81, 0x5a, 0xf6, 0x3c, 0xde, 0xae, 0x7a, 0x9d, 0x21, 0x34, 0xa5, 0xf6, 0xa9, 0x73, 0xe2, 0x8d, 0x60}} , + {{0xfa, 0x44, 0x71, 0xf6, 0x41, 0xd8, 0xc6, 0x58, 0x13, 0x37, 0xeb, 0x84, 0x0f, 0x96, 0xc7, 0xdc, 0xc8, 0xa9, 0x7a, 0x83, 0xb2, 0x2f, 0x31, 0xb1, 0x1a, 0xd8, 0x98, 0x3f, 0x11, 0xd0, 0x31, 0x3b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x81, 0xd5, 0x34, 0x16, 0x01, 0xa3, 0x93, 0xea, 0x52, 0x94, 0xec, 0x93, 0xb7, 0x81, 0x11, 0x2d, 0x58, 0xf9, 0xb5, 0x0a, 0xaa, 0x4f, 0xf6, 0x2e, 0x3f, 0x36, 0xbf, 0x33, 0x5a, 0xe7, 0xd1, 0x08}} , + {{0x1a, 0xcf, 0x42, 0xae, 0xcc, 0xb5, 0x77, 0x39, 0xc4, 0x5b, 0x5b, 0xd0, 0x26, 0x59, 0x27, 0xd0, 0x55, 0x71, 0x12, 0x9d, 0x88, 0x3d, 0x9c, 0xea, 0x41, 0x6a, 0xf0, 0x50, 0x93, 0x93, 0xdd, 0x47}}}, +{{{0x6f, 0xc9, 0x51, 0x6d, 0x1c, 0xaa, 0xf5, 0xa5, 0x90, 0x3f, 0x14, 0xe2, 0x6e, 0x8e, 0x64, 0xfd, 0xac, 0xe0, 0x4e, 0x22, 0xe5, 0xc1, 0xbc, 0x29, 0x0a, 0x6a, 0x9e, 0xa1, 0x60, 0xcb, 0x2f, 0x0b}} , + {{0xdc, 0x39, 0x32, 0xf3, 0xa1, 0x44, 0xe9, 0xc5, 0xc3, 0x78, 0xfb, 0x95, 0x47, 0x34, 0x35, 0x34, 0xe8, 0x25, 0xde, 0x93, 0xc6, 0xb4, 0x76, 0x6d, 0x86, 0x13, 0xc6, 0xe9, 0x68, 0xb5, 0x01, 0x63}}}, +{{{0x1f, 0x9a, 0x52, 0x64, 0x97, 0xd9, 0x1c, 0x08, 0x51, 0x6f, 0x26, 0x9d, 0xaa, 0x93, 0x33, 0x43, 0xfa, 0x77, 0xe9, 0x62, 0x9b, 0x5d, 0x18, 0x75, 0xeb, 0x78, 0xf7, 0x87, 0x8f, 0x41, 0xb4, 0x4d}} , + {{0x13, 0xa8, 0x82, 0x3e, 0xe9, 0x13, 0xad, 0xeb, 0x01, 0xca, 0xcf, 0xda, 0xcd, 0xf7, 0x6c, 0xc7, 0x7a, 0xdc, 0x1e, 0x6e, 0xc8, 0x4e, 0x55, 0x62, 0x80, 0xea, 0x78, 0x0c, 0x86, 0xb9, 0x40, 0x51}}}, +{{{0x27, 0xae, 0xd3, 0x0d, 0x4c, 0x8f, 0x34, 0xea, 0x7d, 0x3c, 0xe5, 0x8a, 0xcf, 0x5b, 0x92, 0xd8, 0x30, 0x16, 0xb4, 0xa3, 0x75, 0xff, 0xeb, 0x27, 0xc8, 0x5c, 0x6c, 0xc2, 0xee, 0x6c, 0x21, 0x0b}} , + {{0xc3, 0xba, 0x12, 0x53, 0x2a, 0xaa, 0x77, 0xad, 0x19, 0x78, 0x55, 0x8a, 0x2e, 0x60, 0x87, 0xc2, 0x6e, 0x91, 0x38, 0x91, 0x3f, 0x7a, 0xc5, 0x24, 0x8f, 0x51, 0xc5, 0xde, 0xb0, 0x53, 0x30, 0x56}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x02, 0xfe, 0x54, 0x12, 0x18, 0xca, 0x7d, 0xa5, 0x68, 0x43, 0xa3, 0x6d, 0x14, 0x2a, 0x6a, 0xa5, 0x8e, 0x32, 0xe7, 0x63, 0x4f, 0xe3, 0xc6, 0x44, 0x3e, 0xab, 0x63, 0xca, 0x17, 0x86, 0x74, 0x3f}} , + {{0x1e, 0x64, 0xc1, 0x7d, 0x52, 0xdc, 0x13, 0x5a, 0xa1, 0x9c, 0x4e, 0xee, 0x99, 0x28, 0xbb, 0x4c, 0xee, 0xac, 0xa9, 0x1b, 0x89, 0xa2, 0x38, 0x39, 0x7b, 0xc4, 0x0f, 0x42, 0xe6, 0x89, 0xed, 0x0f}}}, +{{{0xf3, 0x3c, 0x8c, 0x80, 0x83, 0x10, 0x8a, 0x37, 0x50, 0x9c, 0xb4, 0xdf, 0x3f, 0x8c, 0xf7, 0x23, 0x07, 0xd6, 0xff, 0xa0, 0x82, 0x6c, 0x75, 0x3b, 0xe4, 0xb5, 0xbb, 0xe4, 0xe6, 0x50, 0xf0, 0x08}} , + {{0x62, 0xee, 0x75, 0x48, 0x92, 0x33, 0xf2, 0xf4, 0xad, 0x15, 0x7a, 0xa1, 0x01, 0x46, 0xa9, 0x32, 0x06, 0x88, 0xb6, 0x36, 0x47, 0x35, 0xb9, 0xb4, 0x42, 0x85, 0x76, 0xf0, 0x48, 0x00, 0x90, 0x38}}}, +{{{0x51, 0x15, 0x9d, 0xc3, 0x95, 0xd1, 0x39, 0xbb, 0x64, 0x9d, 0x15, 0x81, 0xc1, 0x68, 0xd0, 0xb6, 0xa4, 0x2c, 0x7d, 0x5e, 0x02, 0x39, 0x00, 0xe0, 0x3b, 0xa4, 0xcc, 0xca, 0x1d, 0x81, 0x24, 0x10}} , + {{0xe7, 0x29, 0xf9, 0x37, 0xd9, 0x46, 0x5a, 0xcd, 0x70, 0xfe, 0x4d, 0x5b, 0xbf, 0xa5, 0xcf, 0x91, 0xf4, 0xef, 0xee, 0x8a, 0x29, 0xd0, 0xe7, 0xc4, 0x25, 0x92, 0x8a, 0xff, 0x36, 0xfc, 0xe4, 0x49}}}, +{{{0xbd, 0x00, 0xb9, 0x04, 0x7d, 0x35, 0xfc, 0xeb, 0xd0, 0x0b, 0x05, 0x32, 0x52, 0x7a, 0x89, 0x24, 0x75, 0x50, 0xe1, 0x63, 0x02, 0x82, 0x8e, 0xe7, 0x85, 0x0c, 0xf2, 0x56, 0x44, 0x37, 0x83, 0x25}} , + {{0x8f, 0xa1, 0xce, 0xcb, 0x60, 0xda, 0x12, 0x02, 0x1e, 0x29, 0x39, 0x2a, 0x03, 0xb7, 0xeb, 0x77, 0x40, 0xea, 0xc9, 0x2b, 0x2c, 0xd5, 0x7d, 0x7e, 0x2c, 0xc7, 0x5a, 0xfd, 0xff, 0xc4, 0xd1, 0x62}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x1d, 0x88, 0x98, 0x5b, 0x4e, 0xfc, 0x41, 0x24, 0x05, 0xe6, 0x50, 0x2b, 0xae, 0x96, 0x51, 0xd9, 0x6b, 0x72, 0xb2, 0x33, 0x42, 0x98, 0x68, 0xbb, 0x10, 0x5a, 0x7a, 0x8c, 0x9d, 0x07, 0xb4, 0x05}} , + {{0x2f, 0x61, 0x9f, 0xd7, 0xa8, 0x3f, 0x83, 0x8c, 0x10, 0x69, 0x90, 0xe6, 0xcf, 0xd2, 0x63, 0xa3, 0xe4, 0x54, 0x7e, 0xe5, 0x69, 0x13, 0x1c, 0x90, 0x57, 0xaa, 0xe9, 0x53, 0x22, 0x43, 0x29, 0x23}}}, +{{{0xe5, 0x1c, 0xf8, 0x0a, 0xfd, 0x2d, 0x7e, 0xf5, 0xf5, 0x70, 0x7d, 0x41, 0x6b, 0x11, 0xfe, 0xbe, 0x99, 0xd1, 0x55, 0x29, 0x31, 0xbf, 0xc0, 0x97, 0x6c, 0xd5, 0x35, 0xcc, 0x5e, 0x8b, 0xd9, 0x69}} , + {{0x8e, 0x4e, 0x9f, 0x25, 0xf8, 0x81, 0x54, 0x2d, 0x0e, 0xd5, 0x54, 0x81, 0x9b, 0xa6, 0x92, 0xce, 0x4b, 0xe9, 0x8f, 0x24, 0x3b, 0xca, 0xe0, 0x44, 0xab, 0x36, 0xfe, 0xfb, 0x87, 0xd4, 0x26, 0x3e}}}, +{{{0x0f, 0x93, 0x9c, 0x11, 0xe7, 0xdb, 0xf1, 0xf0, 0x85, 0x43, 0x28, 0x15, 0x37, 0xdd, 0xde, 0x27, 0xdf, 0xad, 0x3e, 0x49, 0x4f, 0xe0, 0x5b, 0xf6, 0x80, 0x59, 0x15, 0x3c, 0x85, 0xb7, 0x3e, 0x12}} , + {{0xf5, 0xff, 0xcc, 0xf0, 0xb4, 0x12, 0x03, 0x5f, 0xc9, 0x84, 0xcb, 0x1d, 0x17, 0xe0, 0xbc, 0xcc, 0x03, 0x62, 0xa9, 0x8b, 0x94, 0xa6, 0xaa, 0x18, 0xcb, 0x27, 0x8d, 0x49, 0xa6, 0x17, 0x15, 0x07}}}, +{{{0xd9, 0xb6, 0xd4, 0x9d, 0xd4, 0x6a, 0xaf, 0x70, 0x07, 0x2c, 0x10, 0x9e, 0xbd, 0x11, 0xad, 0xe4, 0x26, 0x33, 0x70, 0x92, 0x78, 0x1c, 0x74, 0x9f, 0x75, 0x60, 0x56, 0xf4, 0x39, 0xa8, 0xa8, 0x62}} , + {{0x3b, 0xbf, 0x55, 0x35, 0x61, 0x8b, 0x44, 0x97, 0xe8, 0x3a, 0x55, 0xc1, 0xc8, 0x3b, 0xfd, 0x95, 0x29, 0x11, 0x60, 0x96, 0x1e, 0xcb, 0x11, 0x9d, 0xc2, 0x03, 0x8a, 0x1b, 0xc6, 0xd6, 0x45, 0x3d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x7e, 0x0e, 0x50, 0xb2, 0xcc, 0x0d, 0x6b, 0xa6, 0x71, 0x5b, 0x42, 0xed, 0xbd, 0xaf, 0xac, 0xf0, 0xfc, 0x12, 0xa2, 0x3f, 0x4e, 0xda, 0xe8, 0x11, 0xf3, 0x23, 0xe1, 0x04, 0x62, 0x03, 0x1c, 0x4e}} , + {{0xc8, 0xb1, 0x1b, 0x6f, 0x73, 0x61, 0x3d, 0x27, 0x0d, 0x7d, 0x7a, 0x25, 0x5f, 0x73, 0x0e, 0x2f, 0x93, 0xf6, 0x24, 0xd8, 0x4f, 0x90, 0xac, 0xa2, 0x62, 0x0a, 0xf0, 0x61, 0xd9, 0x08, 0x59, 0x6a}}}, +{{{0x6f, 0x2d, 0x55, 0xf8, 0x2f, 0x8e, 0xf0, 0x18, 0x3b, 0xea, 0xdd, 0x26, 0x72, 0xd1, 0xf5, 0xfe, 0xe5, 0xb8, 0xe6, 0xd3, 0x10, 0x48, 0x46, 0x49, 0x3a, 0x9f, 0x5e, 0x45, 0x6b, 0x90, 0xe8, 0x7f}} , + {{0xd3, 0x76, 0x69, 0x33, 0x7b, 0xb9, 0x40, 0x70, 0xee, 0xa6, 0x29, 0x6b, 0xdd, 0xd0, 0x5d, 0x8d, 0xc1, 0x3e, 0x4a, 0xea, 0x37, 0xb1, 0x03, 0x02, 0x03, 0x35, 0xf1, 0x28, 0x9d, 0xff, 0x00, 0x13}}}, +{{{0x7a, 0xdb, 0x12, 0xd2, 0x8a, 0x82, 0x03, 0x1b, 0x1e, 0xaf, 0xf9, 0x4b, 0x9c, 0xbe, 0xae, 0x7c, 0xe4, 0x94, 0x2a, 0x23, 0xb3, 0x62, 0x86, 0xe7, 0xfd, 0x23, 0xaa, 0x99, 0xbd, 0x2b, 0x11, 0x6c}} , + {{0x8d, 0xa6, 0xd5, 0xac, 0x9d, 0xcc, 0x68, 0x75, 0x7f, 0xc3, 0x4d, 0x4b, 0xdd, 0x6c, 0xbb, 0x11, 0x5a, 0x60, 0xe5, 0xbd, 0x7d, 0x27, 0x8b, 0xda, 0xb4, 0x95, 0xf6, 0x03, 0x27, 0xa4, 0x92, 0x3f}}}, +{{{0x22, 0xd6, 0xb5, 0x17, 0x84, 0xbf, 0x12, 0xcc, 0x23, 0x14, 0x4a, 0xdf, 0x14, 0x31, 0xbc, 0xa1, 0xac, 0x6e, 0xab, 0xfa, 0x57, 0x11, 0x53, 0xb3, 0x27, 0xe6, 0xf9, 0x47, 0x33, 0x44, 0x34, 0x1e}} , + {{0x79, 0xfc, 0xa6, 0xb4, 0x0b, 0x35, 0x20, 0xc9, 0x4d, 0x22, 0x84, 0xc4, 0xa9, 0x20, 0xec, 0x89, 0x94, 0xba, 0x66, 0x56, 0x48, 0xb9, 0x87, 0x7f, 0xca, 0x1e, 0x06, 0xed, 0xa5, 0x55, 0x59, 0x29}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x56, 0xe1, 0xf5, 0xf1, 0xd5, 0xab, 0xa8, 0x2b, 0xae, 0x89, 0xf3, 0xcf, 0x56, 0x9f, 0xf2, 0x4b, 0x31, 0xbc, 0x18, 0xa9, 0x06, 0x5b, 0xbe, 0xb4, 0x61, 0xf8, 0xb2, 0x06, 0x9c, 0x81, 0xab, 0x4c}} , + {{0x1f, 0x68, 0x76, 0x01, 0x16, 0x38, 0x2b, 0x0f, 0x77, 0x97, 0x92, 0x67, 0x4e, 0x86, 0x6a, 0x8b, 0xe5, 0xe8, 0x0c, 0xf7, 0x36, 0x39, 0xb5, 0x33, 0xe6, 0xcf, 0x5e, 0xbd, 0x18, 0xfb, 0x10, 0x1f}}}, +{{{0x83, 0xf0, 0x0d, 0x63, 0xef, 0x53, 0x6b, 0xb5, 0x6b, 0xf9, 0x83, 0xcf, 0xde, 0x04, 0x22, 0x9b, 0x2c, 0x0a, 0xe0, 0xa5, 0xd8, 0xc7, 0x9c, 0xa5, 0xa3, 0xf6, 0x6f, 0xcf, 0x90, 0x6b, 0x68, 0x7c}} , + {{0x33, 0x15, 0xd7, 0x7f, 0x1a, 0xd5, 0x21, 0x58, 0xc4, 0x18, 0xa5, 0xf0, 0xcc, 0x73, 0xa8, 0xfd, 0xfa, 0x18, 0xd1, 0x03, 0x91, 0x8d, 0x52, 0xd2, 0xa3, 0xa4, 0xd3, 0xb1, 0xea, 0x1d, 0x0f, 0x00}}}, +{{{0xcc, 0x48, 0x83, 0x90, 0xe5, 0xfd, 0x3f, 0x84, 0xaa, 0xf9, 0x8b, 0x82, 0x59, 0x24, 0x34, 0x68, 0x4f, 0x1c, 0x23, 0xd9, 0xcc, 0x71, 0xe1, 0x7f, 0x8c, 0xaf, 0xf1, 0xee, 0x00, 0xb6, 0xa0, 0x77}} , + {{0xf5, 0x1a, 0x61, 0xf7, 0x37, 0x9d, 0x00, 0xf4, 0xf2, 0x69, 0x6f, 0x4b, 0x01, 0x85, 0x19, 0x45, 0x4d, 0x7f, 0x02, 0x7c, 0x6a, 0x05, 0x47, 0x6c, 0x1f, 0x81, 0x20, 0xd4, 0xe8, 0x50, 0x27, 0x72}}}, +{{{0x2c, 0x3a, 0xe5, 0xad, 0xf4, 0xdd, 0x2d, 0xf7, 0x5c, 0x44, 0xb5, 0x5b, 0x21, 0xa3, 0x89, 0x5f, 0x96, 0x45, 0xca, 0x4d, 0xa4, 0x21, 0x99, 0x70, 0xda, 0xc4, 0xc4, 0xa0, 0xe5, 0xf4, 0xec, 0x0a}} , + {{0x07, 0x68, 0x21, 0x65, 0xe9, 0x08, 0xa0, 0x0b, 0x6a, 0x4a, 0xba, 0xb5, 0x80, 0xaf, 0xd0, 0x1b, 0xc5, 0xf5, 0x4b, 0x73, 0x50, 0x60, 0x2d, 0x71, 0x69, 0x61, 0x0e, 0xc0, 0x20, 0x40, 0x30, 0x19}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0x75, 0x57, 0x3b, 0xeb, 0x5c, 0x14, 0x56, 0x50, 0xc9, 0x4f, 0xb8, 0xb8, 0x1e, 0xa3, 0xf4, 0xab, 0xf5, 0xa9, 0x20, 0x15, 0x94, 0x82, 0xda, 0x96, 0x1c, 0x9b, 0x59, 0x8c, 0xff, 0xf4, 0x51}} , + {{0xc1, 0x3a, 0x86, 0xd7, 0xb0, 0x06, 0x84, 0x7f, 0x1b, 0xbd, 0xd4, 0x07, 0x78, 0x80, 0x2e, 0xb1, 0xb4, 0xee, 0x52, 0x38, 0xee, 0x9a, 0xf9, 0xf6, 0xf3, 0x41, 0x6e, 0xd4, 0x88, 0x95, 0xac, 0x35}}}, +{{{0x41, 0x97, 0xbf, 0x71, 0x6a, 0x9b, 0x72, 0xec, 0xf3, 0xf8, 0x6b, 0xe6, 0x0e, 0x6c, 0x69, 0xa5, 0x2f, 0x68, 0x52, 0xd8, 0x61, 0x81, 0xc0, 0x63, 0x3f, 0xa6, 0x3c, 0x13, 0x90, 0xe6, 0x8d, 0x56}} , + {{0xe8, 0x39, 0x30, 0x77, 0x23, 0xb1, 0xfd, 0x1b, 0x3d, 0x3e, 0x74, 0x4d, 0x7f, 0xae, 0x5b, 0x3a, 0xb4, 0x65, 0x0e, 0x3a, 0x43, 0xdc, 0xdc, 0x41, 0x47, 0xe6, 0xe8, 0x92, 0x09, 0x22, 0x48, 0x4c}}}, +{{{0x85, 0x57, 0x9f, 0xb5, 0xc8, 0x06, 0xb2, 0x9f, 0x47, 0x3f, 0xf0, 0xfa, 0xe6, 0xa9, 0xb1, 0x9b, 0x6f, 0x96, 0x7d, 0xf9, 0xa4, 0x65, 0x09, 0x75, 0x32, 0xa6, 0x6c, 0x7f, 0x47, 0x4b, 0x2f, 0x4f}} , + {{0x34, 0xe9, 0x59, 0x93, 0x9d, 0x26, 0x80, 0x54, 0xf2, 0xcc, 0x3c, 0xc2, 0x25, 0x85, 0xe3, 0x6a, 0xc1, 0x62, 0x04, 0xa7, 0x08, 0x32, 0x6d, 0xa1, 0x39, 0x84, 0x8a, 0x3b, 0x87, 0x5f, 0x11, 0x13}}}, +{{{0xda, 0x03, 0x34, 0x66, 0xc4, 0x0c, 0x73, 0x6e, 0xbc, 0x24, 0xb5, 0xf9, 0x70, 0x81, 0x52, 0xe9, 0xf4, 0x7c, 0x23, 0xdd, 0x9f, 0xb8, 0x46, 0xef, 0x1d, 0x22, 0x55, 0x7d, 0x71, 0xc4, 0x42, 0x33}} , + {{0xc5, 0x37, 0x69, 0x5b, 0xa8, 0xc6, 0x9d, 0xa4, 0xfc, 0x61, 0x6e, 0x68, 0x46, 0xea, 0xd7, 0x1c, 0x67, 0xd2, 0x7d, 0xfa, 0xf1, 0xcc, 0x54, 0x8d, 0x36, 0x35, 0xc9, 0x00, 0xdf, 0x6c, 0x67, 0x50}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9a, 0x4d, 0x42, 0x29, 0x5d, 0xa4, 0x6b, 0x6f, 0xa8, 0x8a, 0x4d, 0x91, 0x7b, 0xd2, 0xdf, 0x36, 0xef, 0x01, 0x22, 0xc5, 0xcc, 0x8d, 0xeb, 0x58, 0x3d, 0xb3, 0x50, 0xfc, 0x8b, 0x97, 0x96, 0x33}} , + {{0x93, 0x33, 0x07, 0xc8, 0x4a, 0xca, 0xd0, 0xb1, 0xab, 0xbd, 0xdd, 0xa7, 0x7c, 0xac, 0x3e, 0x45, 0xcb, 0xcc, 0x07, 0x91, 0xbf, 0x35, 0x9d, 0xcb, 0x7d, 0x12, 0x3c, 0x11, 0x59, 0x13, 0xcf, 0x5c}}}, +{{{0x45, 0xb8, 0x41, 0xd7, 0xab, 0x07, 0x15, 0x00, 0x8e, 0xce, 0xdf, 0xb2, 0x43, 0x5c, 0x01, 0xdc, 0xf4, 0x01, 0x51, 0x95, 0x10, 0x5a, 0xf6, 0x24, 0x24, 0xa0, 0x19, 0x3a, 0x09, 0x2a, 0xaa, 0x3f}} , + {{0xdc, 0x8e, 0xeb, 0xc6, 0xbf, 0xdd, 0x11, 0x7b, 0xe7, 0x47, 0xe6, 0xce, 0xe7, 0xb6, 0xc5, 0xe8, 0x8a, 0xdc, 0x4b, 0x57, 0x15, 0x3b, 0x66, 0xca, 0x89, 0xa3, 0xfd, 0xac, 0x0d, 0xe1, 0x1d, 0x7a}}}, +{{{0x89, 0xef, 0xbf, 0x03, 0x75, 0xd0, 0x29, 0x50, 0xcb, 0x7d, 0xd6, 0xbe, 0xad, 0x5f, 0x7b, 0x00, 0x32, 0xaa, 0x98, 0xed, 0x3f, 0x8f, 0x92, 0xcb, 0x81, 0x56, 0x01, 0x63, 0x64, 0xa3, 0x38, 0x39}} , + {{0x8b, 0xa4, 0xd6, 0x50, 0xb4, 0xaa, 0x5d, 0x64, 0x64, 0x76, 0x2e, 0xa1, 0xa6, 0xb3, 0xb8, 0x7c, 0x7a, 0x56, 0xf5, 0x5c, 0x4e, 0x84, 0x5c, 0xfb, 0xdd, 0xca, 0x48, 0x8b, 0x48, 0xb9, 0xba, 0x34}}}, +{{{0xc5, 0xe3, 0xe8, 0xae, 0x17, 0x27, 0xe3, 0x64, 0x60, 0x71, 0x47, 0x29, 0x02, 0x0f, 0x92, 0x5d, 0x10, 0x93, 0xc8, 0x0e, 0xa1, 0xed, 0xba, 0xa9, 0x96, 0x1c, 0xc5, 0x76, 0x30, 0xcd, 0xf9, 0x30}} , + {{0x95, 0xb0, 0xbd, 0x8c, 0xbc, 0xa7, 0x4f, 0x7e, 0xfd, 0x4e, 0x3a, 0xbf, 0x5f, 0x04, 0x79, 0x80, 0x2b, 0x5a, 0x9f, 0x4f, 0x68, 0x21, 0x19, 0x71, 0xc6, 0x20, 0x01, 0x42, 0xaa, 0xdf, 0xae, 0x2c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x90, 0x6e, 0x7e, 0x4b, 0x71, 0x93, 0xc0, 0x72, 0xed, 0xeb, 0x71, 0x24, 0x97, 0x26, 0x9c, 0xfe, 0xcb, 0x3e, 0x59, 0x19, 0xa8, 0x0f, 0x75, 0x7d, 0xbe, 0x18, 0xe6, 0x96, 0x1e, 0x95, 0x70, 0x60}} , + {{0x89, 0x66, 0x3e, 0x1d, 0x4c, 0x5f, 0xfe, 0xc0, 0x04, 0x43, 0xd6, 0x44, 0x19, 0xb5, 0xad, 0xc7, 0x22, 0xdc, 0x71, 0x28, 0x64, 0xde, 0x41, 0x38, 0x27, 0x8f, 0x2c, 0x6b, 0x08, 0xb8, 0xb8, 0x7b}}}, +{{{0x3d, 0x70, 0x27, 0x9d, 0xd9, 0xaf, 0xb1, 0x27, 0xaf, 0xe3, 0x5d, 0x1e, 0x3a, 0x30, 0x54, 0x61, 0x60, 0xe8, 0xc3, 0x26, 0x3a, 0xbc, 0x7e, 0xf5, 0x81, 0xdd, 0x64, 0x01, 0x04, 0xeb, 0xc0, 0x1e}} , + {{0xda, 0x2c, 0xa4, 0xd1, 0xa1, 0xc3, 0x5c, 0x6e, 0x32, 0x07, 0x1f, 0xb8, 0x0e, 0x19, 0x9e, 0x99, 0x29, 0x33, 0x9a, 0xae, 0x7a, 0xed, 0x68, 0x42, 0x69, 0x7c, 0x07, 0xb3, 0x38, 0x2c, 0xf6, 0x3d}}}, +{{{0x64, 0xaa, 0xb5, 0x88, 0x79, 0x65, 0x38, 0x8c, 0x94, 0xd6, 0x62, 0x37, 0x7d, 0x64, 0xcd, 0x3a, 0xeb, 0xff, 0xe8, 0x81, 0x09, 0xc7, 0x6a, 0x50, 0x09, 0x0d, 0x28, 0x03, 0x0d, 0x9a, 0x93, 0x0a}} , + {{0x42, 0xa3, 0xf1, 0xc5, 0xb4, 0x0f, 0xd8, 0xc8, 0x8d, 0x15, 0x31, 0xbd, 0xf8, 0x07, 0x8b, 0xcd, 0x08, 0x8a, 0xfb, 0x18, 0x07, 0xfe, 0x8e, 0x52, 0x86, 0xef, 0xbe, 0xec, 0x49, 0x52, 0x99, 0x08}}}, +{{{0x0f, 0xa9, 0xd5, 0x01, 0xaa, 0x48, 0x4f, 0x28, 0x66, 0x32, 0x1a, 0xba, 0x7c, 0xea, 0x11, 0x80, 0x17, 0x18, 0x9b, 0x56, 0x88, 0x25, 0x06, 0x69, 0x12, 0x2c, 0xea, 0x56, 0x69, 0x41, 0x24, 0x19}} , + {{0xde, 0x21, 0xf0, 0xda, 0x8a, 0xfb, 0xb1, 0xb8, 0xcd, 0xc8, 0x6a, 0x82, 0x19, 0x73, 0xdb, 0xc7, 0xcf, 0x88, 0xeb, 0x96, 0xee, 0x6f, 0xfb, 0x06, 0xd2, 0xcd, 0x7d, 0x7b, 0x12, 0x28, 0x8e, 0x0c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x93, 0x44, 0x97, 0xce, 0x28, 0xff, 0x3a, 0x40, 0xc4, 0xf5, 0xf6, 0x9b, 0xf4, 0x6b, 0x07, 0x84, 0xfb, 0x98, 0xd8, 0xec, 0x8c, 0x03, 0x57, 0xec, 0x49, 0xed, 0x63, 0xb6, 0xaa, 0xff, 0x98, 0x28}} , + {{0x3d, 0x16, 0x35, 0xf3, 0x46, 0xbc, 0xb3, 0xf4, 0xc6, 0xb6, 0x4f, 0xfa, 0xf4, 0xa0, 0x13, 0xe6, 0x57, 0x45, 0x93, 0xb9, 0xbc, 0xd6, 0x59, 0xe7, 0x77, 0x94, 0x6c, 0xab, 0x96, 0x3b, 0x4f, 0x09}}}, +{{{0x5a, 0xf7, 0x6b, 0x01, 0x12, 0x4f, 0x51, 0xc1, 0x70, 0x84, 0x94, 0x47, 0xb2, 0x01, 0x6c, 0x71, 0xd7, 0xcc, 0x17, 0x66, 0x0f, 0x59, 0x5d, 0x5d, 0x10, 0x01, 0x57, 0x11, 0xf5, 0xdd, 0xe2, 0x34}} , + {{0x26, 0xd9, 0x1f, 0x5c, 0x58, 0xac, 0x8b, 0x03, 0xd2, 0xc3, 0x85, 0x0f, 0x3a, 0xc3, 0x7f, 0x6d, 0x8e, 0x86, 0xcd, 0x52, 0x74, 0x8f, 0x55, 0x77, 0x17, 0xb7, 0x8e, 0xb7, 0x88, 0xea, 0xda, 0x1b}}}, +{{{0xb6, 0xea, 0x0e, 0x40, 0x93, 0x20, 0x79, 0x35, 0x6a, 0x61, 0x84, 0x5a, 0x07, 0x6d, 0xf9, 0x77, 0x6f, 0xed, 0x69, 0x1c, 0x0d, 0x25, 0x76, 0xcc, 0xf0, 0xdb, 0xbb, 0xc5, 0xad, 0xe2, 0x26, 0x57}} , + {{0xcf, 0xe8, 0x0e, 0x6b, 0x96, 0x7d, 0xed, 0x27, 0xd1, 0x3c, 0xa9, 0xd9, 0x50, 0xa9, 0x98, 0x84, 0x5e, 0x86, 0xef, 0xd6, 0xf0, 0xf8, 0x0e, 0x89, 0x05, 0x2f, 0xd9, 0x5f, 0x15, 0x5f, 0x73, 0x79}}}, +{{{0xc8, 0x5c, 0x16, 0xfe, 0xed, 0x9f, 0x26, 0x56, 0xf6, 0x4b, 0x9f, 0xa7, 0x0a, 0x85, 0xfe, 0xa5, 0x8c, 0x87, 0xdd, 0x98, 0xce, 0x4e, 0xc3, 0x58, 0x55, 0xb2, 0x7b, 0x3d, 0xd8, 0x6b, 0xb5, 0x4c}} , + {{0x65, 0x38, 0xa0, 0x15, 0xfa, 0xa7, 0xb4, 0x8f, 0xeb, 0xc4, 0x86, 0x9b, 0x30, 0xa5, 0x5e, 0x4d, 0xea, 0x8a, 0x9a, 0x9f, 0x1a, 0xd8, 0x5b, 0x53, 0x14, 0x19, 0x25, 0x63, 0xb4, 0x6f, 0x1f, 0x5d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xac, 0x8f, 0xbc, 0x1e, 0x7d, 0x8b, 0x5a, 0x0b, 0x8d, 0xaf, 0x76, 0x2e, 0x71, 0xe3, 0x3b, 0x6f, 0x53, 0x2f, 0x3e, 0x90, 0x95, 0xd4, 0x35, 0x14, 0x4f, 0x8c, 0x3c, 0xce, 0x57, 0x1c, 0x76, 0x49}} , + {{0xa8, 0x50, 0xe1, 0x61, 0x6b, 0x57, 0x35, 0xeb, 0x44, 0x0b, 0x0c, 0x6e, 0xf9, 0x25, 0x80, 0x74, 0xf2, 0x8f, 0x6f, 0x7a, 0x3e, 0x7f, 0x2d, 0xf3, 0x4e, 0x09, 0x65, 0x10, 0x5e, 0x03, 0x25, 0x32}}}, +{{{0xa9, 0x60, 0xdc, 0x0f, 0x64, 0xe5, 0x1d, 0xe2, 0x8d, 0x4f, 0x79, 0x2f, 0x0e, 0x24, 0x02, 0x00, 0x05, 0x77, 0x43, 0x25, 0x3d, 0x6a, 0xc7, 0xb7, 0xbf, 0x04, 0x08, 0x65, 0xf4, 0x39, 0x4b, 0x65}} , + {{0x96, 0x19, 0x12, 0x6b, 0x6a, 0xb7, 0xe3, 0xdc, 0x45, 0x9b, 0xdb, 0xb4, 0xa8, 0xae, 0xdc, 0xa8, 0x14, 0x44, 0x65, 0x62, 0xce, 0x34, 0x9a, 0x84, 0x18, 0x12, 0x01, 0xf1, 0xe2, 0x7b, 0xce, 0x50}}}, +{{{0x41, 0x21, 0x30, 0x53, 0x1b, 0x47, 0x01, 0xb7, 0x18, 0xd8, 0x82, 0x57, 0xbd, 0xa3, 0x60, 0xf0, 0x32, 0xf6, 0x5b, 0xf0, 0x30, 0x88, 0x91, 0x59, 0xfd, 0x90, 0xa2, 0xb9, 0x55, 0x93, 0x21, 0x34}} , + {{0x97, 0x67, 0x9e, 0xeb, 0x6a, 0xf9, 0x6e, 0xd6, 0x73, 0xe8, 0x6b, 0x29, 0xec, 0x63, 0x82, 0x00, 0xa8, 0x99, 0x1c, 0x1d, 0x30, 0xc8, 0x90, 0x52, 0x90, 0xb6, 0x6a, 0x80, 0x4e, 0xff, 0x4b, 0x51}}}, +{{{0x0f, 0x7d, 0x63, 0x8c, 0x6e, 0x5c, 0xde, 0x30, 0xdf, 0x65, 0xfa, 0x2e, 0xb0, 0xa3, 0x25, 0x05, 0x54, 0xbd, 0x25, 0xba, 0x06, 0xae, 0xdf, 0x8b, 0xd9, 0x1b, 0xea, 0x38, 0xb3, 0x05, 0x16, 0x09}} , + {{0xc7, 0x8c, 0xbf, 0x64, 0x28, 0xad, 0xf8, 0xa5, 0x5a, 0x6f, 0xc9, 0xba, 0xd5, 0x7f, 0xd5, 0xd6, 0xbd, 0x66, 0x2f, 0x3d, 0xaa, 0x54, 0xf6, 0xba, 0x32, 0x22, 0x9a, 0x1e, 0x52, 0x05, 0xf4, 0x1d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xaa, 0x1f, 0xbb, 0xeb, 0xfe, 0xe4, 0x87, 0xfc, 0xb1, 0x2c, 0xb7, 0x88, 0xf4, 0xc6, 0xb9, 0xf5, 0x24, 0x46, 0xf2, 0xa5, 0x9f, 0x8f, 0x8a, 0x93, 0x70, 0x69, 0xd4, 0x56, 0xec, 0xfd, 0x06, 0x46}} , + {{0x4e, 0x66, 0xcf, 0x4e, 0x34, 0xce, 0x0c, 0xd9, 0xa6, 0x50, 0xd6, 0x5e, 0x95, 0xaf, 0xe9, 0x58, 0xfa, 0xee, 0x9b, 0xb8, 0xa5, 0x0f, 0x35, 0xe0, 0x43, 0x82, 0x6d, 0x65, 0xe6, 0xd9, 0x00, 0x0f}}}, +{{{0x7b, 0x75, 0x3a, 0xfc, 0x64, 0xd3, 0x29, 0x7e, 0xdd, 0x49, 0x9a, 0x59, 0x53, 0xbf, 0xb4, 0xa7, 0x52, 0xb3, 0x05, 0xab, 0xc3, 0xaf, 0x16, 0x1a, 0x85, 0x42, 0x32, 0xa2, 0x86, 0xfa, 0x39, 0x43}} , + {{0x0e, 0x4b, 0xa3, 0x63, 0x8a, 0xfe, 0xa5, 0x58, 0xf1, 0x13, 0xbd, 0x9d, 0xaa, 0x7f, 0x76, 0x40, 0x70, 0x81, 0x10, 0x75, 0x99, 0xbb, 0xbe, 0x0b, 0x16, 0xe9, 0xba, 0x62, 0x34, 0xcc, 0x07, 0x6d}}}, +{{{0xc3, 0xf1, 0xc6, 0x93, 0x65, 0xee, 0x0b, 0xbc, 0xea, 0x14, 0xf0, 0xc1, 0xf8, 0x84, 0x89, 0xc2, 0xc9, 0xd7, 0xea, 0x34, 0xca, 0xa7, 0xc4, 0x99, 0xd5, 0x50, 0x69, 0xcb, 0xd6, 0x21, 0x63, 0x7c}} , + {{0x99, 0xeb, 0x7c, 0x31, 0x73, 0x64, 0x67, 0x7f, 0x0c, 0x66, 0xaa, 0x8c, 0x69, 0x91, 0xe2, 0x26, 0xd3, 0x23, 0xe2, 0x76, 0x5d, 0x32, 0x52, 0xdf, 0x5d, 0xc5, 0x8f, 0xb7, 0x7c, 0x84, 0xb3, 0x70}}}, +{{{0xeb, 0x01, 0xc7, 0x36, 0x97, 0x4e, 0xb6, 0xab, 0x5f, 0x0d, 0x2c, 0xba, 0x67, 0x64, 0x55, 0xde, 0xbc, 0xff, 0xa6, 0xec, 0x04, 0xd3, 0x8d, 0x39, 0x56, 0x5e, 0xee, 0xf8, 0xe4, 0x2e, 0x33, 0x62}} , + {{0x65, 0xef, 0xb8, 0x9f, 0xc8, 0x4b, 0xa7, 0xfd, 0x21, 0x49, 0x9b, 0x92, 0x35, 0x82, 0xd6, 0x0a, 0x9b, 0xf2, 0x79, 0xf1, 0x47, 0x2f, 0x6a, 0x7e, 0x9f, 0xcf, 0x18, 0x02, 0x3c, 0xfb, 0x1b, 0x3e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2f, 0x8b, 0xc8, 0x40, 0x51, 0xd1, 0xac, 0x1a, 0x0b, 0xe4, 0xa9, 0xa2, 0x42, 0x21, 0x19, 0x2f, 0x7b, 0x97, 0xbf, 0xf7, 0x57, 0x6d, 0x3f, 0x3d, 0x4f, 0x0f, 0xe2, 0xb2, 0x81, 0x00, 0x9e, 0x7b}} , + {{0x8c, 0x85, 0x2b, 0xc4, 0xfc, 0xf1, 0xab, 0xe8, 0x79, 0x22, 0xc4, 0x84, 0x17, 0x3a, 0xfa, 0x86, 0xa6, 0x7d, 0xf9, 0xf3, 0x6f, 0x03, 0x57, 0x20, 0x4d, 0x79, 0xf9, 0x6e, 0x71, 0x54, 0x38, 0x09}}}, +{{{0x40, 0x29, 0x74, 0xa8, 0x2f, 0x5e, 0xf9, 0x79, 0xa4, 0xf3, 0x3e, 0xb9, 0xfd, 0x33, 0x31, 0xac, 0x9a, 0x69, 0x88, 0x1e, 0x77, 0x21, 0x2d, 0xf3, 0x91, 0x52, 0x26, 0x15, 0xb2, 0xa6, 0xcf, 0x7e}} , + {{0xc6, 0x20, 0x47, 0x6c, 0xa4, 0x7d, 0xcb, 0x63, 0xea, 0x5b, 0x03, 0xdf, 0x3e, 0x88, 0x81, 0x6d, 0xce, 0x07, 0x42, 0x18, 0x60, 0x7e, 0x7b, 0x55, 0xfe, 0x6a, 0xf3, 0xda, 0x5c, 0x8b, 0x95, 0x10}}}, +{{{0x62, 0xe4, 0x0d, 0x03, 0xb4, 0xd7, 0xcd, 0xfa, 0xbd, 0x46, 0xdf, 0x93, 0x71, 0x10, 0x2c, 0xa8, 0x3b, 0xb6, 0x09, 0x05, 0x70, 0x84, 0x43, 0x29, 0xa8, 0x59, 0xf5, 0x8e, 0x10, 0xe4, 0xd7, 0x20}} , + {{0x57, 0x82, 0x1c, 0xab, 0xbf, 0x62, 0x70, 0xe8, 0xc4, 0xcf, 0xf0, 0x28, 0x6e, 0x16, 0x3c, 0x08, 0x78, 0x89, 0x85, 0x46, 0x0f, 0xf6, 0x7f, 0xcf, 0xcb, 0x7e, 0xb8, 0x25, 0xe9, 0x5a, 0xfa, 0x03}}}, +{{{0xfb, 0x95, 0x92, 0x63, 0x50, 0xfc, 0x62, 0xf0, 0xa4, 0x5e, 0x8c, 0x18, 0xc2, 0x17, 0x24, 0xb7, 0x78, 0xc2, 0xa9, 0xe7, 0x6a, 0x32, 0xd6, 0x29, 0x85, 0xaf, 0xcb, 0x8d, 0x91, 0x13, 0xda, 0x6b}} , + {{0x36, 0x0a, 0xc2, 0xb6, 0x4b, 0xa5, 0x5d, 0x07, 0x17, 0x41, 0x31, 0x5f, 0x62, 0x46, 0xf8, 0x92, 0xf9, 0x66, 0x48, 0x73, 0xa6, 0x97, 0x0d, 0x7d, 0x88, 0xee, 0x62, 0xb1, 0x03, 0xa8, 0x3f, 0x2c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x4a, 0xb1, 0x70, 0x8a, 0xa9, 0xe8, 0x63, 0x79, 0x00, 0xe2, 0x25, 0x16, 0xca, 0x4b, 0x0f, 0xa4, 0x66, 0xad, 0x19, 0x9f, 0x88, 0x67, 0x0c, 0x8b, 0xc2, 0x4a, 0x5b, 0x2b, 0x6d, 0x95, 0xaf, 0x19}} , + {{0x8b, 0x9d, 0xb6, 0xcc, 0x60, 0xb4, 0x72, 0x4f, 0x17, 0x69, 0x5a, 0x4a, 0x68, 0x34, 0xab, 0xa1, 0x45, 0x32, 0x3c, 0x83, 0x87, 0x72, 0x30, 0x54, 0x77, 0x68, 0xae, 0xfb, 0xb5, 0x8b, 0x22, 0x5e}}}, +{{{0xf1, 0xb9, 0x87, 0x35, 0xc5, 0xbb, 0xb9, 0xcf, 0xf5, 0xd6, 0xcd, 0xd5, 0x0c, 0x7c, 0x0e, 0xe6, 0x90, 0x34, 0xfb, 0x51, 0x42, 0x1e, 0x6d, 0xac, 0x9a, 0x46, 0xc4, 0x97, 0x29, 0x32, 0xbf, 0x45}} , + {{0x66, 0x9e, 0xc6, 0x24, 0xc0, 0xed, 0xa5, 0x5d, 0x88, 0xd4, 0xf0, 0x73, 0x97, 0x7b, 0xea, 0x7f, 0x42, 0xff, 0x21, 0xa0, 0x9b, 0x2f, 0x9a, 0xfd, 0x53, 0x57, 0x07, 0x84, 0x48, 0x88, 0x9d, 0x52}}}, +{{{0xc6, 0x96, 0x48, 0x34, 0x2a, 0x06, 0xaf, 0x94, 0x3d, 0xf4, 0x1a, 0xcf, 0xf2, 0xc0, 0x21, 0xc2, 0x42, 0x5e, 0xc8, 0x2f, 0x35, 0xa2, 0x3e, 0x29, 0xfa, 0x0c, 0x84, 0xe5, 0x89, 0x72, 0x7c, 0x06}} , + {{0x32, 0x65, 0x03, 0xe5, 0x89, 0xa6, 0x6e, 0xb3, 0x5b, 0x8e, 0xca, 0xeb, 0xfe, 0x22, 0x56, 0x8b, 0x5d, 0x14, 0x4b, 0x4d, 0xf9, 0xbe, 0xb5, 0xf5, 0xe6, 0x5c, 0x7b, 0x8b, 0xf4, 0x13, 0x11, 0x34}}}, +{{{0x07, 0xc6, 0x22, 0x15, 0xe2, 0x9c, 0x60, 0xa2, 0x19, 0xd9, 0x27, 0xae, 0x37, 0x4e, 0xa6, 0xc9, 0x80, 0xa6, 0x91, 0x8f, 0x12, 0x49, 0xe5, 0x00, 0x18, 0x47, 0xd1, 0xd7, 0x28, 0x22, 0x63, 0x39}} , + {{0xe8, 0xe2, 0x00, 0x7e, 0xf2, 0x9e, 0x1e, 0x99, 0x39, 0x95, 0x04, 0xbd, 0x1e, 0x67, 0x7b, 0xb2, 0x26, 0xac, 0xe6, 0xaa, 0xe2, 0x46, 0xd5, 0xe4, 0xe8, 0x86, 0xbd, 0xab, 0x7c, 0x55, 0x59, 0x6f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x24, 0x64, 0x6e, 0x9b, 0x35, 0x71, 0x78, 0xce, 0x33, 0x03, 0x21, 0x33, 0x36, 0xf1, 0x73, 0x9b, 0xb9, 0x15, 0x8b, 0x2c, 0x69, 0xcf, 0x4d, 0xed, 0x4f, 0x4d, 0x57, 0x14, 0x13, 0x82, 0xa4, 0x4d}} , + {{0x65, 0x6e, 0x0a, 0xa4, 0x59, 0x07, 0x17, 0xf2, 0x6b, 0x4a, 0x1f, 0x6e, 0xf6, 0xb5, 0xbc, 0x62, 0xe4, 0xb6, 0xda, 0xa2, 0x93, 0xbc, 0x29, 0x05, 0xd2, 0xd2, 0x73, 0x46, 0x03, 0x16, 0x40, 0x31}}}, +{{{0x4c, 0x73, 0x6d, 0x15, 0xbd, 0xa1, 0x4d, 0x5c, 0x13, 0x0b, 0x24, 0x06, 0x98, 0x78, 0x1c, 0x5b, 0xeb, 0x1f, 0x18, 0x54, 0x43, 0xd9, 0x55, 0x66, 0xda, 0x29, 0x21, 0xe8, 0xb8, 0x3c, 0x42, 0x22}} , + {{0xb4, 0xcd, 0x08, 0x6f, 0x15, 0x23, 0x1a, 0x0b, 0x22, 0xed, 0xd1, 0xf1, 0xa7, 0xc7, 0x73, 0x45, 0xf3, 0x9e, 0xce, 0x76, 0xb7, 0xf6, 0x39, 0xb6, 0x8e, 0x79, 0xbe, 0xe9, 0x9b, 0xcf, 0x7d, 0x62}}}, +{{{0x92, 0x5b, 0xfc, 0x72, 0xfd, 0xba, 0xf1, 0xfd, 0xa6, 0x7c, 0x95, 0xe3, 0x61, 0x3f, 0xe9, 0x03, 0xd4, 0x2b, 0xd4, 0x20, 0xd9, 0xdb, 0x4d, 0x32, 0x3e, 0xf5, 0x11, 0x64, 0xe3, 0xb4, 0xbe, 0x32}} , + {{0x86, 0x17, 0x90, 0xe7, 0xc9, 0x1f, 0x10, 0xa5, 0x6a, 0x2d, 0x39, 0xd0, 0x3b, 0xc4, 0xa6, 0xe9, 0x59, 0x13, 0xda, 0x1a, 0xe6, 0xa0, 0xb9, 0x3c, 0x50, 0xb8, 0x40, 0x7c, 0x15, 0x36, 0x5a, 0x42}}}, +{{{0xb4, 0x0b, 0x32, 0xab, 0xdc, 0x04, 0x51, 0x55, 0x21, 0x1e, 0x0b, 0x75, 0x99, 0x89, 0x73, 0x35, 0x3a, 0x91, 0x2b, 0xfe, 0xe7, 0x49, 0xea, 0x76, 0xc1, 0xf9, 0x46, 0xb9, 0x53, 0x02, 0x23, 0x04}} , + {{0xfc, 0x5a, 0x1e, 0x1d, 0x74, 0x58, 0x95, 0xa6, 0x8f, 0x7b, 0x97, 0x3e, 0x17, 0x3b, 0x79, 0x2d, 0xa6, 0x57, 0xef, 0x45, 0x02, 0x0b, 0x4d, 0x6e, 0x9e, 0x93, 0x8d, 0x2f, 0xd9, 0x9d, 0xdb, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc0, 0xd7, 0x56, 0x97, 0x58, 0x91, 0xde, 0x09, 0x4f, 0x9f, 0xbe, 0x63, 0xb0, 0x83, 0x86, 0x43, 0x5d, 0xbc, 0xe0, 0xf3, 0xc0, 0x75, 0xbf, 0x8b, 0x8e, 0xaa, 0xf7, 0x8b, 0x64, 0x6e, 0xb0, 0x63}} , + {{0x16, 0xae, 0x8b, 0xe0, 0x9b, 0x24, 0x68, 0x5c, 0x44, 0xc2, 0xd0, 0x08, 0xb7, 0x7b, 0x62, 0xfd, 0x7f, 0xd8, 0xd4, 0xb7, 0x50, 0xfd, 0x2c, 0x1b, 0xbf, 0x41, 0x95, 0xd9, 0x8e, 0xd8, 0x17, 0x1b}}}, +{{{0x86, 0x55, 0x37, 0x8e, 0xc3, 0x38, 0x48, 0x14, 0xb5, 0x97, 0xd2, 0xa7, 0x54, 0x45, 0xf1, 0x35, 0x44, 0x38, 0x9e, 0xf1, 0x1b, 0xb6, 0x34, 0x00, 0x3c, 0x96, 0xee, 0x29, 0x00, 0xea, 0x2c, 0x0b}} , + {{0xea, 0xda, 0x99, 0x9e, 0x19, 0x83, 0x66, 0x6d, 0xe9, 0x76, 0x87, 0x50, 0xd1, 0xfd, 0x3c, 0x60, 0x87, 0xc6, 0x41, 0xd9, 0x8e, 0xdb, 0x5e, 0xde, 0xaa, 0x9a, 0xd3, 0x28, 0xda, 0x95, 0xea, 0x47}}}, +{{{0xd0, 0x80, 0xba, 0x19, 0xae, 0x1d, 0xa9, 0x79, 0xf6, 0x3f, 0xac, 0x5d, 0x6f, 0x96, 0x1f, 0x2a, 0xce, 0x29, 0xb2, 0xff, 0x37, 0xf1, 0x94, 0x8f, 0x0c, 0xb5, 0x28, 0xba, 0x9a, 0x21, 0xf6, 0x66}} , + {{0x02, 0xfb, 0x54, 0xb8, 0x05, 0xf3, 0x81, 0x52, 0x69, 0x34, 0x46, 0x9d, 0x86, 0x76, 0x8f, 0xd7, 0xf8, 0x6a, 0x66, 0xff, 0xe6, 0xa7, 0x90, 0xf7, 0x5e, 0xcd, 0x6a, 0x9b, 0x55, 0xfc, 0x9d, 0x48}}}, +{{{0xbd, 0xaa, 0x13, 0xe6, 0xcd, 0x45, 0x4a, 0xa4, 0x59, 0x0a, 0x64, 0xb1, 0x98, 0xd6, 0x34, 0x13, 0x04, 0xe6, 0x97, 0x94, 0x06, 0xcb, 0xd4, 0x4e, 0xbb, 0x96, 0xcd, 0xd1, 0x57, 0xd1, 0xe3, 0x06}} , + {{0x7a, 0x6c, 0x45, 0x27, 0xc4, 0x93, 0x7f, 0x7d, 0x7c, 0x62, 0x50, 0x38, 0x3a, 0x6b, 0xb5, 0x88, 0xc6, 0xd9, 0xf1, 0x78, 0x19, 0xb9, 0x39, 0x93, 0x3d, 0xc9, 0xe0, 0x9c, 0x3c, 0xce, 0xf5, 0x72}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x24, 0xea, 0x23, 0x7d, 0x56, 0x2c, 0xe2, 0x59, 0x0e, 0x85, 0x60, 0x04, 0x88, 0x5a, 0x74, 0x1e, 0x4b, 0xef, 0x13, 0xda, 0x4c, 0xff, 0x83, 0x45, 0x85, 0x3f, 0x08, 0x95, 0x2c, 0x20, 0x13, 0x1f}} , + {{0x48, 0x5f, 0x27, 0x90, 0x5c, 0x02, 0x42, 0xad, 0x78, 0x47, 0x5c, 0xb5, 0x7e, 0x08, 0x85, 0x00, 0xfa, 0x7f, 0xfd, 0xfd, 0xe7, 0x09, 0x11, 0xf2, 0x7e, 0x1b, 0x38, 0x6c, 0x35, 0x6d, 0x33, 0x66}}}, +{{{0x93, 0x03, 0x36, 0x81, 0xac, 0xe4, 0x20, 0x09, 0x35, 0x4c, 0x45, 0xb2, 0x1e, 0x4c, 0x14, 0x21, 0xe6, 0xe9, 0x8a, 0x7b, 0x8d, 0xfe, 0x1e, 0xc6, 0x3e, 0xc1, 0x35, 0xfa, 0xe7, 0x70, 0x4e, 0x1d}} , + {{0x61, 0x2e, 0xc2, 0xdd, 0x95, 0x57, 0xd1, 0xab, 0x80, 0xe8, 0x63, 0x17, 0xb5, 0x48, 0xe4, 0x8a, 0x11, 0x9e, 0x72, 0xbe, 0x85, 0x8d, 0x51, 0x0a, 0xf2, 0x9f, 0xe0, 0x1c, 0xa9, 0x07, 0x28, 0x7b}}}, +{{{0xbb, 0x71, 0x14, 0x5e, 0x26, 0x8c, 0x3d, 0xc8, 0xe9, 0x7c, 0xd3, 0xd6, 0xd1, 0x2f, 0x07, 0x6d, 0xe6, 0xdf, 0xfb, 0x79, 0xd6, 0x99, 0x59, 0x96, 0x48, 0x40, 0x0f, 0x3a, 0x7b, 0xb2, 0xa0, 0x72}} , + {{0x4e, 0x3b, 0x69, 0xc8, 0x43, 0x75, 0x51, 0x6c, 0x79, 0x56, 0xe4, 0xcb, 0xf7, 0xa6, 0x51, 0xc2, 0x2c, 0x42, 0x0b, 0xd4, 0x82, 0x20, 0x1c, 0x01, 0x08, 0x66, 0xd7, 0xbf, 0x04, 0x56, 0xfc, 0x02}}}, +{{{0x24, 0xe8, 0xb7, 0x60, 0xae, 0x47, 0x80, 0xfc, 0xe5, 0x23, 0xe7, 0xc2, 0xc9, 0x85, 0xe6, 0x98, 0xa0, 0x29, 0x4e, 0xe1, 0x84, 0x39, 0x2d, 0x95, 0x2c, 0xf3, 0x45, 0x3c, 0xff, 0xaf, 0x27, 0x4c}} , + {{0x6b, 0xa6, 0xf5, 0x4b, 0x11, 0xbd, 0xba, 0x5b, 0x9e, 0xc4, 0xa4, 0x51, 0x1e, 0xbe, 0xd0, 0x90, 0x3a, 0x9c, 0xc2, 0x26, 0xb6, 0x1e, 0xf1, 0x95, 0x7d, 0xc8, 0x6d, 0x52, 0xe6, 0x99, 0x2c, 0x5f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x85, 0xe0, 0x24, 0x32, 0xb4, 0xd1, 0xef, 0xfc, 0x69, 0xa2, 0xbf, 0x8f, 0x72, 0x2c, 0x95, 0xf6, 0xe4, 0x6e, 0x7d, 0x90, 0xf7, 0x57, 0x81, 0xa0, 0xf7, 0xda, 0xef, 0x33, 0x07, 0xe3, 0x6b, 0x78}} , + {{0x36, 0x27, 0x3e, 0xc6, 0x12, 0x07, 0xab, 0x4e, 0xbe, 0x69, 0x9d, 0xb3, 0xbe, 0x08, 0x7c, 0x2a, 0x47, 0x08, 0xfd, 0xd4, 0xcd, 0x0e, 0x27, 0x34, 0x5b, 0x98, 0x34, 0x2f, 0x77, 0x5f, 0x3a, 0x65}}}, +{{{0x13, 0xaa, 0x2e, 0x4c, 0xf0, 0x22, 0xb8, 0x6c, 0xb3, 0x19, 0x4d, 0xeb, 0x6b, 0xd0, 0xa4, 0xc6, 0x9c, 0xdd, 0xc8, 0x5b, 0x81, 0x57, 0x89, 0xdf, 0x33, 0xa9, 0x68, 0x49, 0x80, 0xe4, 0xfe, 0x21}} , + {{0x00, 0x17, 0x90, 0x30, 0xe9, 0xd3, 0x60, 0x30, 0x31, 0xc2, 0x72, 0x89, 0x7a, 0x36, 0xa5, 0xbd, 0x39, 0x83, 0x85, 0x50, 0xa1, 0x5d, 0x6c, 0x41, 0x1d, 0xb5, 0x2c, 0x07, 0x40, 0x77, 0x0b, 0x50}}}, +{{{0x64, 0x34, 0xec, 0xc0, 0x9e, 0x44, 0x41, 0xaf, 0xa0, 0x36, 0x05, 0x6d, 0xea, 0x30, 0x25, 0x46, 0x35, 0x24, 0x9d, 0x86, 0xbd, 0x95, 0xf1, 0x6a, 0x46, 0xd7, 0x94, 0x54, 0xf9, 0x3b, 0xbd, 0x5d}} , + {{0x77, 0x5b, 0xe2, 0x37, 0xc7, 0xe1, 0x7c, 0x13, 0x8c, 0x9f, 0x7b, 0x7b, 0x2a, 0xce, 0x42, 0xa3, 0xb9, 0x2a, 0x99, 0xa8, 0xc0, 0xd8, 0x3c, 0x86, 0xb0, 0xfb, 0xe9, 0x76, 0x77, 0xf7, 0xf5, 0x56}}}, +{{{0xdf, 0xb3, 0x46, 0x11, 0x6e, 0x13, 0xb7, 0x28, 0x4e, 0x56, 0xdd, 0xf1, 0xac, 0xad, 0x58, 0xc3, 0xf8, 0x88, 0x94, 0x5e, 0x06, 0x98, 0xa1, 0xe4, 0x6a, 0xfb, 0x0a, 0x49, 0x5d, 0x8a, 0xfe, 0x77}} , + {{0x46, 0x02, 0xf5, 0xa5, 0xaf, 0xc5, 0x75, 0x6d, 0xba, 0x45, 0x35, 0x0a, 0xfe, 0xc9, 0xac, 0x22, 0x91, 0x8d, 0x21, 0x95, 0x33, 0x03, 0xc0, 0x8a, 0x16, 0xf3, 0x39, 0xe0, 0x01, 0x0f, 0x53, 0x3c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x34, 0x75, 0x37, 0x1f, 0x34, 0x4e, 0xa9, 0x1d, 0x68, 0x67, 0xf8, 0x49, 0x98, 0x96, 0xfc, 0x4c, 0x65, 0x97, 0xf7, 0x02, 0x4a, 0x52, 0x6c, 0x01, 0xbd, 0x48, 0xbb, 0x1b, 0xed, 0xa4, 0xe2, 0x53}} , + {{0x59, 0xd5, 0x9b, 0x5a, 0xa2, 0x90, 0xd3, 0xb8, 0x37, 0x4c, 0x55, 0x82, 0x28, 0x08, 0x0f, 0x7f, 0xaa, 0x81, 0x65, 0xe0, 0x0c, 0x52, 0xc9, 0xa3, 0x32, 0x27, 0x64, 0xda, 0xfd, 0x34, 0x23, 0x5a}}}, +{{{0xb5, 0xb0, 0x0c, 0x4d, 0xb3, 0x7b, 0x23, 0xc8, 0x1f, 0x8a, 0x39, 0x66, 0xe6, 0xba, 0x4c, 0x10, 0x37, 0xca, 0x9c, 0x7c, 0x05, 0x9e, 0xff, 0xc0, 0xf8, 0x8e, 0xb1, 0x8f, 0x6f, 0x67, 0x18, 0x26}} , + {{0x4b, 0x41, 0x13, 0x54, 0x23, 0x1a, 0xa4, 0x4e, 0xa9, 0x8b, 0x1e, 0x4b, 0xfc, 0x15, 0x24, 0xbb, 0x7e, 0xcb, 0xb6, 0x1e, 0x1b, 0xf5, 0xf2, 0xc8, 0x56, 0xec, 0x32, 0xa2, 0x60, 0x5b, 0xa0, 0x2a}}}, +{{{0xa4, 0x29, 0x47, 0x86, 0x2e, 0x92, 0x4f, 0x11, 0x4f, 0xf3, 0xb2, 0x5c, 0xd5, 0x3e, 0xa6, 0xb9, 0xc8, 0xe2, 0x33, 0x11, 0x1f, 0x01, 0x8f, 0xb0, 0x9b, 0xc7, 0xa5, 0xff, 0x83, 0x0f, 0x1e, 0x28}} , + {{0x1d, 0x29, 0x7a, 0xa1, 0xec, 0x8e, 0xb5, 0xad, 0xea, 0x02, 0x68, 0x60, 0x74, 0x29, 0x1c, 0xa5, 0xcf, 0xc8, 0x3b, 0x7d, 0x8b, 0x2b, 0x7c, 0xad, 0xa4, 0x40, 0x17, 0x51, 0x59, 0x7c, 0x2e, 0x5d}}}, +{{{0x0a, 0x6c, 0x4f, 0xbc, 0x3e, 0x32, 0xe7, 0x4a, 0x1a, 0x13, 0xc1, 0x49, 0x38, 0xbf, 0xf7, 0xc2, 0xd3, 0x8f, 0x6b, 0xad, 0x52, 0xf7, 0xcf, 0xbc, 0x27, 0xcb, 0x40, 0x67, 0x76, 0xcd, 0x6d, 0x56}} , + {{0xe5, 0xb0, 0x27, 0xad, 0xbe, 0x9b, 0xf2, 0xb5, 0x63, 0xde, 0x3a, 0x23, 0x95, 0xb7, 0x0a, 0x7e, 0xf3, 0x9e, 0x45, 0x6f, 0x19, 0x39, 0x75, 0x8f, 0x39, 0x3d, 0x0f, 0xc0, 0x9f, 0xf1, 0xe9, 0x51}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x88, 0xaa, 0x14, 0x24, 0x86, 0x94, 0x11, 0x12, 0x3e, 0x1a, 0xb5, 0xcc, 0xbb, 0xe0, 0x9c, 0xd5, 0x9c, 0x6d, 0xba, 0x58, 0x72, 0x8d, 0xfb, 0x22, 0x7b, 0x9f, 0x7c, 0x94, 0x30, 0xb3, 0x51, 0x21}} , + {{0xf6, 0x74, 0x3d, 0xf2, 0xaf, 0xd0, 0x1e, 0x03, 0x7c, 0x23, 0x6b, 0xc9, 0xfc, 0x25, 0x70, 0x90, 0xdc, 0x9a, 0xa4, 0xfb, 0x49, 0xfc, 0x3d, 0x0a, 0x35, 0x38, 0x6f, 0xe4, 0x7e, 0x50, 0x01, 0x2a}}}, +{{{0xd6, 0xe3, 0x96, 0x61, 0x3a, 0xfd, 0xef, 0x9b, 0x1f, 0x90, 0xa4, 0x24, 0x14, 0x5b, 0xc8, 0xde, 0x50, 0xb1, 0x1d, 0xaf, 0xe8, 0x55, 0x8a, 0x87, 0x0d, 0xfe, 0xaa, 0x3b, 0x82, 0x2c, 0x8d, 0x7b}} , + {{0x85, 0x0c, 0xaf, 0xf8, 0x83, 0x44, 0x49, 0xd9, 0x45, 0xcf, 0xf7, 0x48, 0xd9, 0x53, 0xb4, 0xf1, 0x65, 0xa0, 0xe1, 0xc3, 0xb3, 0x15, 0xed, 0x89, 0x9b, 0x4f, 0x62, 0xb3, 0x57, 0xa5, 0x45, 0x1c}}}, +{{{0x8f, 0x12, 0xea, 0xaf, 0xd1, 0x1f, 0x79, 0x10, 0x0b, 0xf6, 0xa3, 0x7b, 0xea, 0xac, 0x8b, 0x57, 0x32, 0x62, 0xe7, 0x06, 0x12, 0x51, 0xa0, 0x3b, 0x43, 0x5e, 0xa4, 0x20, 0x78, 0x31, 0xce, 0x0d}} , + {{0x84, 0x7c, 0xc2, 0xa6, 0x91, 0x23, 0xce, 0xbd, 0xdc, 0xf9, 0xce, 0xd5, 0x75, 0x30, 0x22, 0xe6, 0xf9, 0x43, 0x62, 0x0d, 0xf7, 0x75, 0x9d, 0x7f, 0x8c, 0xff, 0x7d, 0xe4, 0x72, 0xac, 0x9f, 0x1c}}}, +{{{0x88, 0xc1, 0x99, 0xd0, 0x3c, 0x1c, 0x5d, 0xb4, 0xef, 0x13, 0x0f, 0x90, 0xb9, 0x36, 0x2f, 0x95, 0x95, 0xc6, 0xdc, 0xde, 0x0a, 0x51, 0xe2, 0x8d, 0xf3, 0xbc, 0x51, 0xec, 0xdf, 0xb1, 0xa2, 0x5f}} , + {{0x2e, 0x68, 0xa1, 0x23, 0x7d, 0x9b, 0x40, 0x69, 0x85, 0x7b, 0x42, 0xbf, 0x90, 0x4b, 0xd6, 0x40, 0x2f, 0xd7, 0x52, 0x52, 0xb2, 0x21, 0xde, 0x64, 0xbd, 0x88, 0xc3, 0x6d, 0xa5, 0xfa, 0x81, 0x3f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xfb, 0xfd, 0x47, 0x7b, 0x8a, 0x66, 0x9e, 0x79, 0x2e, 0x64, 0x82, 0xef, 0xf7, 0x21, 0xec, 0xf6, 0xd8, 0x86, 0x09, 0x31, 0x7c, 0xdd, 0x03, 0x6a, 0x58, 0xa0, 0x77, 0xb7, 0x9b, 0x8c, 0x87, 0x1f}} , + {{0x55, 0x47, 0xe4, 0xa8, 0x3d, 0x55, 0x21, 0x34, 0xab, 0x1d, 0xae, 0xe0, 0xf4, 0xea, 0xdb, 0xc5, 0xb9, 0x58, 0xbf, 0xc4, 0x2a, 0x89, 0x31, 0x1a, 0xf4, 0x2d, 0xe1, 0xca, 0x37, 0x99, 0x47, 0x59}}}, +{{{0xc7, 0xca, 0x63, 0xc1, 0x49, 0xa9, 0x35, 0x45, 0x55, 0x7e, 0xda, 0x64, 0x32, 0x07, 0x50, 0xf7, 0x32, 0xac, 0xde, 0x75, 0x58, 0x9b, 0x11, 0xb2, 0x3a, 0x1f, 0xf5, 0xf7, 0x79, 0x04, 0xe6, 0x08}} , + {{0x46, 0xfa, 0x22, 0x4b, 0xfa, 0xe1, 0xfe, 0x96, 0xfc, 0x67, 0xba, 0x67, 0x97, 0xc4, 0xe7, 0x1b, 0x86, 0x90, 0x5f, 0xee, 0xf4, 0x5b, 0x11, 0xb2, 0xcd, 0xad, 0xee, 0xc2, 0x48, 0x6c, 0x2b, 0x1b}}}, +{{{0xe3, 0x39, 0x62, 0xb4, 0x4f, 0x31, 0x04, 0xc9, 0xda, 0xd5, 0x73, 0x51, 0x57, 0xc5, 0xb8, 0xf3, 0xa3, 0x43, 0x70, 0xe4, 0x61, 0x81, 0x84, 0xe2, 0xbb, 0xbf, 0x4f, 0x9e, 0xa4, 0x5e, 0x74, 0x06}} , + {{0x29, 0xac, 0xff, 0x27, 0xe0, 0x59, 0xbe, 0x39, 0x9c, 0x0d, 0x83, 0xd7, 0x10, 0x0b, 0x15, 0xb7, 0xe1, 0xc2, 0x2c, 0x30, 0x73, 0x80, 0x3a, 0x7d, 0x5d, 0xab, 0x58, 0x6b, 0xc1, 0xf0, 0xf4, 0x22}}}, +{{{0xfe, 0x7f, 0xfb, 0x35, 0x7d, 0xc6, 0x01, 0x23, 0x28, 0xc4, 0x02, 0xac, 0x1f, 0x42, 0xb4, 0x9d, 0xfc, 0x00, 0x94, 0xa5, 0xee, 0xca, 0xda, 0x97, 0x09, 0x41, 0x77, 0x87, 0x5d, 0x7b, 0x87, 0x78}} , + {{0xf5, 0xfb, 0x90, 0x2d, 0x81, 0x19, 0x9e, 0x2f, 0x6d, 0x85, 0x88, 0x8c, 0x40, 0x5c, 0x77, 0x41, 0x4d, 0x01, 0x19, 0x76, 0x60, 0xe8, 0x4c, 0x48, 0xe4, 0x33, 0x83, 0x32, 0x6c, 0xb4, 0x41, 0x03}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xff, 0x10, 0xc2, 0x09, 0x4f, 0x6e, 0xf4, 0xd2, 0xdf, 0x7e, 0xca, 0x7b, 0x1c, 0x1d, 0xba, 0xa3, 0xb6, 0xda, 0x67, 0x33, 0xd4, 0x87, 0x36, 0x4b, 0x11, 0x20, 0x05, 0xa6, 0x29, 0xc1, 0x87, 0x17}} , + {{0xf6, 0x96, 0xca, 0x2f, 0xda, 0x38, 0xa7, 0x1b, 0xfc, 0xca, 0x7d, 0xfe, 0x08, 0x89, 0xe2, 0x47, 0x2b, 0x6a, 0x5d, 0x4b, 0xfa, 0xa1, 0xb4, 0xde, 0xb6, 0xc2, 0x31, 0x51, 0xf5, 0xe0, 0xa4, 0x0b}}}, +{{{0x5c, 0xe5, 0xc6, 0x04, 0x8e, 0x2b, 0x57, 0xbe, 0x38, 0x85, 0x23, 0xcb, 0xb7, 0xbe, 0x4f, 0xa9, 0xd3, 0x6e, 0x12, 0xaa, 0xd5, 0xb2, 0x2e, 0x93, 0x29, 0x9a, 0x4a, 0x88, 0x18, 0x43, 0xf5, 0x01}} , + {{0x50, 0xfc, 0xdb, 0xa2, 0x59, 0x21, 0x8d, 0xbd, 0x7e, 0x33, 0xae, 0x2f, 0x87, 0x1a, 0xd0, 0x97, 0xc7, 0x0d, 0x4d, 0x63, 0x01, 0xef, 0x05, 0x84, 0xec, 0x40, 0xdd, 0xa8, 0x0a, 0x4f, 0x70, 0x0b}}}, +{{{0x41, 0x69, 0x01, 0x67, 0x5c, 0xd3, 0x8a, 0xc5, 0xcf, 0x3f, 0xd1, 0x57, 0xd1, 0x67, 0x3e, 0x01, 0x39, 0xb5, 0xcb, 0x81, 0x56, 0x96, 0x26, 0xb6, 0xc2, 0xe7, 0x5c, 0xfb, 0x63, 0x97, 0x58, 0x06}} , + {{0x0c, 0x0e, 0xf3, 0xba, 0xf0, 0xe5, 0xba, 0xb2, 0x57, 0x77, 0xc6, 0x20, 0x9b, 0x89, 0x24, 0xbe, 0xf2, 0x9c, 0x8a, 0xba, 0x69, 0xc1, 0xf1, 0xb0, 0x4f, 0x2a, 0x05, 0x9a, 0xee, 0x10, 0x7e, 0x36}}}, +{{{0x3f, 0x26, 0xe9, 0x40, 0xe9, 0x03, 0xad, 0x06, 0x69, 0x91, 0xe0, 0xd1, 0x89, 0x60, 0x84, 0x79, 0xde, 0x27, 0x6d, 0xe6, 0x76, 0xbd, 0xea, 0xe6, 0xae, 0x48, 0xc3, 0x67, 0xc0, 0x57, 0xcd, 0x2f}} , + {{0x7f, 0xc1, 0xdc, 0xb9, 0xc7, 0xbc, 0x86, 0x3d, 0x55, 0x4b, 0x28, 0x7a, 0xfb, 0x4d, 0xc7, 0xf8, 0xbc, 0x67, 0x2a, 0x60, 0x4d, 0x8f, 0x07, 0x0b, 0x1a, 0x17, 0xbf, 0xfa, 0xac, 0xa7, 0x3d, 0x1a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x91, 0x3f, 0xed, 0x5e, 0x18, 0x78, 0x3f, 0x23, 0x2c, 0x0d, 0x8c, 0x44, 0x00, 0xe8, 0xfb, 0xe9, 0x8e, 0xd6, 0xd1, 0x36, 0x58, 0x57, 0x9e, 0xae, 0x4b, 0x5c, 0x0b, 0x07, 0xbc, 0x6b, 0x55, 0x2b}} , + {{0x6f, 0x4d, 0x17, 0xd7, 0xe1, 0x84, 0xd9, 0x78, 0xb1, 0x90, 0xfd, 0x2e, 0xb3, 0xb5, 0x19, 0x3f, 0x1b, 0xfa, 0xc0, 0x68, 0xb3, 0xdd, 0x00, 0x2e, 0x89, 0xbd, 0x7e, 0x80, 0x32, 0x13, 0xa0, 0x7b}}}, +{{{0x1a, 0x6f, 0x40, 0xaf, 0x44, 0x44, 0xb0, 0x43, 0x8f, 0x0d, 0xd0, 0x1e, 0xc4, 0x0b, 0x19, 0x5d, 0x8e, 0xfe, 0xc1, 0xf3, 0xc5, 0x5c, 0x91, 0xf8, 0x04, 0x4e, 0xbe, 0x90, 0xb4, 0x47, 0x5c, 0x3f}} , + {{0xb0, 0x3b, 0x2c, 0xf3, 0xfe, 0x32, 0x71, 0x07, 0x3f, 0xaa, 0xba, 0x45, 0x60, 0xa8, 0x8d, 0xea, 0x54, 0xcb, 0x39, 0x10, 0xb4, 0xf2, 0x8b, 0xd2, 0x14, 0x82, 0x42, 0x07, 0x8e, 0xe9, 0x7c, 0x53}}}, +{{{0xb0, 0xae, 0xc1, 0x8d, 0xc9, 0x8f, 0xb9, 0x7a, 0x77, 0xef, 0xba, 0x79, 0xa0, 0x3c, 0xa8, 0xf5, 0x6a, 0xe2, 0x3f, 0x5d, 0x00, 0xe3, 0x4b, 0x45, 0x24, 0x7b, 0x43, 0x78, 0x55, 0x1d, 0x2b, 0x1e}} , + {{0x01, 0xb8, 0xd6, 0x16, 0x67, 0xa0, 0x15, 0xb9, 0xe1, 0x58, 0xa4, 0xa7, 0x31, 0x37, 0x77, 0x2f, 0x8b, 0x12, 0x9f, 0xf4, 0x3f, 0xc7, 0x36, 0x66, 0xd2, 0xa8, 0x56, 0xf7, 0x7f, 0x74, 0xc6, 0x41}}}, +{{{0x5d, 0xf8, 0xb4, 0xa8, 0x30, 0xdd, 0xcc, 0x38, 0xa5, 0xd3, 0xca, 0xd8, 0xd1, 0xf8, 0xb2, 0x31, 0x91, 0xd4, 0x72, 0x05, 0x57, 0x4a, 0x3b, 0x82, 0x4a, 0xc6, 0x68, 0x20, 0xe2, 0x18, 0x41, 0x61}} , + {{0x19, 0xd4, 0x8d, 0x47, 0x29, 0x12, 0x65, 0xb0, 0x11, 0x78, 0x47, 0xb5, 0xcb, 0xa3, 0xa5, 0xfa, 0x05, 0x85, 0x54, 0xa9, 0x33, 0x97, 0x8d, 0x2b, 0xc2, 0xfe, 0x99, 0x35, 0x28, 0xe5, 0xeb, 0x63}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xb1, 0x3f, 0x3f, 0xef, 0xd8, 0xf4, 0xfc, 0xb3, 0xa0, 0x60, 0x50, 0x06, 0x2b, 0x29, 0x52, 0x70, 0x15, 0x0b, 0x24, 0x24, 0xf8, 0x5f, 0x79, 0x18, 0xcc, 0xff, 0x89, 0x99, 0x84, 0xa1, 0xae, 0x13}} , + {{0x44, 0x1f, 0xb8, 0xc2, 0x01, 0xc1, 0x30, 0x19, 0x55, 0x05, 0x60, 0x10, 0xa4, 0x6c, 0x2d, 0x67, 0x70, 0xe5, 0x25, 0x1b, 0xf2, 0xbf, 0xdd, 0xfb, 0x70, 0x2b, 0xa1, 0x8c, 0x9c, 0x94, 0x84, 0x08}}}, +{{{0xe7, 0xc4, 0x43, 0x4d, 0xc9, 0x2b, 0x69, 0x5d, 0x1d, 0x3c, 0xaf, 0xbb, 0x43, 0x38, 0x4e, 0x98, 0x3d, 0xed, 0x0d, 0x21, 0x03, 0xfd, 0xf0, 0x99, 0x47, 0x04, 0xb0, 0x98, 0x69, 0x55, 0x72, 0x0f}} , + {{0x5e, 0xdf, 0x15, 0x53, 0x3b, 0x86, 0x80, 0xb0, 0xf1, 0x70, 0x68, 0x8f, 0x66, 0x7c, 0x0e, 0x49, 0x1a, 0xd8, 0x6b, 0xfe, 0x4e, 0xef, 0xca, 0x47, 0xd4, 0x03, 0xc1, 0x37, 0x50, 0x9c, 0xc1, 0x16}}}, +{{{0xcd, 0x24, 0xc6, 0x3e, 0x0c, 0x82, 0x9b, 0x91, 0x2b, 0x61, 0x4a, 0xb2, 0x0f, 0x88, 0x55, 0x5f, 0x5a, 0x57, 0xff, 0xe5, 0x74, 0x0b, 0x13, 0x43, 0x00, 0xd8, 0x6b, 0xcf, 0xd2, 0x15, 0x03, 0x2c}} , + {{0xdc, 0xff, 0x15, 0x61, 0x2f, 0x4a, 0x2f, 0x62, 0xf2, 0x04, 0x2f, 0xb5, 0x0c, 0xb7, 0x1e, 0x3f, 0x74, 0x1a, 0x0f, 0xd7, 0xea, 0xcd, 0xd9, 0x7d, 0xf6, 0x12, 0x0e, 0x2f, 0xdb, 0x5a, 0x3b, 0x16}}}, +{{{0x1b, 0x37, 0x47, 0xe3, 0xf5, 0x9e, 0xea, 0x2c, 0x2a, 0xe7, 0x82, 0x36, 0xf4, 0x1f, 0x81, 0x47, 0x92, 0x4b, 0x69, 0x0e, 0x11, 0x8c, 0x5d, 0x53, 0x5b, 0x81, 0x27, 0x08, 0xbc, 0xa0, 0xae, 0x25}} , + {{0x69, 0x32, 0xa1, 0x05, 0x11, 0x42, 0x00, 0xd2, 0x59, 0xac, 0x4d, 0x62, 0x8b, 0x13, 0xe2, 0x50, 0x5d, 0xa0, 0x9d, 0x9b, 0xfd, 0xbb, 0x12, 0x41, 0x75, 0x41, 0x9e, 0xcc, 0xdc, 0xc7, 0xdc, 0x5d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd9, 0xe3, 0x38, 0x06, 0x46, 0x70, 0x82, 0x5e, 0x28, 0x49, 0x79, 0xff, 0x25, 0xd2, 0x4e, 0x29, 0x8d, 0x06, 0xb0, 0x23, 0xae, 0x9b, 0x66, 0xe4, 0x7d, 0xc0, 0x70, 0x91, 0xa3, 0xfc, 0xec, 0x4e}} , + {{0x62, 0x12, 0x37, 0x6a, 0x30, 0xf6, 0x1e, 0xfb, 0x14, 0x5c, 0x0d, 0x0e, 0xb7, 0x81, 0x6a, 0xe7, 0x08, 0x05, 0xac, 0xaa, 0x38, 0x46, 0xe2, 0x73, 0xea, 0x4b, 0x07, 0x81, 0x43, 0x7c, 0x9e, 0x5e}}}, +{{{0xfc, 0xf9, 0x21, 0x4f, 0x2e, 0x76, 0x9b, 0x1f, 0x28, 0x60, 0x77, 0x43, 0x32, 0x9d, 0xbe, 0x17, 0x30, 0x2a, 0xc6, 0x18, 0x92, 0x66, 0x62, 0x30, 0x98, 0x40, 0x11, 0xa6, 0x7f, 0x18, 0x84, 0x28}} , + {{0x3f, 0xab, 0xd3, 0xf4, 0x8a, 0x76, 0xa1, 0x3c, 0xca, 0x2d, 0x49, 0xc3, 0xea, 0x08, 0x0b, 0x85, 0x17, 0x2a, 0xc3, 0x6c, 0x08, 0xfd, 0x57, 0x9f, 0x3d, 0x5f, 0xdf, 0x67, 0x68, 0x42, 0x00, 0x32}}}, +{{{0x51, 0x60, 0x1b, 0x06, 0x4f, 0x8a, 0x21, 0xba, 0x38, 0xa8, 0xba, 0xd6, 0x40, 0xf6, 0xe9, 0x9b, 0x76, 0x4d, 0x56, 0x21, 0x5b, 0x0a, 0x9b, 0x2e, 0x4f, 0x3d, 0x81, 0x32, 0x08, 0x9f, 0x97, 0x5b}} , + {{0xe5, 0x44, 0xec, 0x06, 0x9d, 0x90, 0x79, 0x9f, 0xd3, 0xe0, 0x79, 0xaf, 0x8f, 0x10, 0xfd, 0xdd, 0x04, 0xae, 0x27, 0x97, 0x46, 0x33, 0x79, 0xea, 0xb8, 0x4e, 0xca, 0x5a, 0x59, 0x57, 0xe1, 0x0e}}}, +{{{0x1a, 0xda, 0xf3, 0xa5, 0x41, 0x43, 0x28, 0xfc, 0x7e, 0xe7, 0x71, 0xea, 0xc6, 0x3b, 0x59, 0xcc, 0x2e, 0xd3, 0x40, 0xec, 0xb3, 0x13, 0x6f, 0x44, 0xcd, 0x13, 0xb2, 0x37, 0xf2, 0x6e, 0xd9, 0x1c}} , + {{0xe3, 0xdb, 0x60, 0xcd, 0x5c, 0x4a, 0x18, 0x0f, 0xef, 0x73, 0x36, 0x71, 0x8c, 0xf6, 0x11, 0xb4, 0xd8, 0xce, 0x17, 0x5e, 0x4f, 0x26, 0x77, 0x97, 0x5f, 0xcb, 0xef, 0x91, 0xeb, 0x6a, 0x62, 0x7a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x18, 0x4a, 0xa2, 0x97, 0x08, 0x81, 0x2d, 0x83, 0xc4, 0xcc, 0xf0, 0x83, 0x7e, 0xec, 0x0d, 0x95, 0x4c, 0x5b, 0xfb, 0xfa, 0x98, 0x80, 0x4a, 0x66, 0x56, 0x0c, 0x51, 0xb3, 0xf2, 0x04, 0x5d, 0x27}} , + {{0x3b, 0xb9, 0xb8, 0x06, 0x5a, 0x2e, 0xfe, 0xc3, 0x82, 0x37, 0x9c, 0xa3, 0x11, 0x1f, 0x9c, 0xa6, 0xda, 0x63, 0x48, 0x9b, 0xad, 0xde, 0x2d, 0xa6, 0xbc, 0x6e, 0x32, 0xda, 0x27, 0x65, 0xdd, 0x57}}}, +{{{0x84, 0x4f, 0x37, 0x31, 0x7d, 0x2e, 0xbc, 0xad, 0x87, 0x07, 0x2a, 0x6b, 0x37, 0xfc, 0x5f, 0xeb, 0x4e, 0x75, 0x35, 0xa6, 0xde, 0xab, 0x0a, 0x19, 0x3a, 0xb7, 0xb1, 0xef, 0x92, 0x6a, 0x3b, 0x3c}} , + {{0x3b, 0xb2, 0x94, 0x6d, 0x39, 0x60, 0xac, 0xee, 0xe7, 0x81, 0x1a, 0x3b, 0x76, 0x87, 0x5c, 0x05, 0x94, 0x2a, 0x45, 0xb9, 0x80, 0xe9, 0x22, 0xb1, 0x07, 0xcb, 0x40, 0x9e, 0x70, 0x49, 0x6d, 0x12}}}, +{{{0xfd, 0x18, 0x78, 0x84, 0xa8, 0x4c, 0x7d, 0x6e, 0x59, 0xa6, 0xe5, 0x74, 0xf1, 0x19, 0xa6, 0x84, 0x2e, 0x51, 0xc1, 0x29, 0x13, 0xf2, 0x14, 0x6b, 0x5d, 0x53, 0x51, 0xf7, 0xef, 0xbf, 0x01, 0x22}} , + {{0xa4, 0x4b, 0x62, 0x4c, 0xe6, 0xfd, 0x72, 0x07, 0xf2, 0x81, 0xfc, 0xf2, 0xbd, 0x12, 0x7c, 0x68, 0x76, 0x2a, 0xba, 0xf5, 0x65, 0xb1, 0x1f, 0x17, 0x0a, 0x38, 0xb0, 0xbf, 0xc0, 0xf8, 0xf4, 0x2a}}}, +{{{0x55, 0x60, 0x55, 0x5b, 0xe4, 0x1d, 0x71, 0x4c, 0x9d, 0x5b, 0x9f, 0x70, 0xa6, 0x85, 0x9a, 0x2c, 0xa0, 0xe2, 0x32, 0x48, 0xce, 0x9e, 0x2a, 0xa5, 0x07, 0x3b, 0xc7, 0x6c, 0x86, 0x77, 0xde, 0x3c}} , + {{0xf7, 0x18, 0x7a, 0x96, 0x7e, 0x43, 0x57, 0xa9, 0x55, 0xfc, 0x4e, 0xb6, 0x72, 0x00, 0xf2, 0xe4, 0xd7, 0x52, 0xd3, 0xd3, 0xb6, 0x85, 0xf6, 0x71, 0xc7, 0x44, 0x3f, 0x7f, 0xd7, 0xb3, 0xf2, 0x79}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x46, 0xca, 0xa7, 0x55, 0x7b, 0x79, 0xf3, 0xca, 0x5a, 0x65, 0xf6, 0xed, 0x50, 0x14, 0x7b, 0xe4, 0xc4, 0x2a, 0x65, 0x9e, 0xe2, 0xf9, 0xca, 0xa7, 0x22, 0x26, 0x53, 0xcb, 0x21, 0x5b, 0xa7, 0x31}} , + {{0x90, 0xd7, 0xc5, 0x26, 0x08, 0xbd, 0xb0, 0x53, 0x63, 0x58, 0xc3, 0x31, 0x5e, 0x75, 0x46, 0x15, 0x91, 0xa6, 0xf8, 0x2f, 0x1a, 0x08, 0x65, 0x88, 0x2f, 0x98, 0x04, 0xf1, 0x7c, 0x6e, 0x00, 0x77}}}, +{{{0x81, 0x21, 0x61, 0x09, 0xf6, 0x4e, 0xf1, 0x92, 0xee, 0x63, 0x61, 0x73, 0x87, 0xc7, 0x54, 0x0e, 0x42, 0x4b, 0xc9, 0x47, 0xd1, 0xb8, 0x7e, 0x91, 0x75, 0x37, 0x99, 0x28, 0xb8, 0xdd, 0x7f, 0x50}} , + {{0x89, 0x8f, 0xc0, 0xbe, 0x5d, 0xd6, 0x9f, 0xa0, 0xf0, 0x9d, 0x81, 0xce, 0x3a, 0x7b, 0x98, 0x58, 0xbb, 0xd7, 0x78, 0xc8, 0x3f, 0x13, 0xf1, 0x74, 0x19, 0xdf, 0xf8, 0x98, 0x89, 0x5d, 0xfa, 0x5f}}}, +{{{0x9e, 0x35, 0x85, 0x94, 0x47, 0x1f, 0x90, 0x15, 0x26, 0xd0, 0x84, 0xed, 0x8a, 0x80, 0xf7, 0x63, 0x42, 0x86, 0x27, 0xd7, 0xf4, 0x75, 0x58, 0xdc, 0x9c, 0xc0, 0x22, 0x7e, 0x20, 0x35, 0xfd, 0x1f}} , + {{0x68, 0x0e, 0x6f, 0x97, 0xba, 0x70, 0xbb, 0xa3, 0x0e, 0xe5, 0x0b, 0x12, 0xf4, 0xa2, 0xdc, 0x47, 0xf8, 0xe6, 0xd0, 0x23, 0x6c, 0x33, 0xa8, 0x99, 0x46, 0x6e, 0x0f, 0x44, 0xba, 0x76, 0x48, 0x0f}}}, +{{{0xa3, 0x2a, 0x61, 0x37, 0xe2, 0x59, 0x12, 0x0e, 0x27, 0xba, 0x64, 0x43, 0xae, 0xc0, 0x42, 0x69, 0x79, 0xa4, 0x1e, 0x29, 0x8b, 0x15, 0xeb, 0xf8, 0xaf, 0xd4, 0xa2, 0x68, 0x33, 0xb5, 0x7a, 0x24}} , + {{0x2c, 0x19, 0x33, 0xdd, 0x1b, 0xab, 0xec, 0x01, 0xb0, 0x23, 0xf8, 0x42, 0x2b, 0x06, 0x88, 0xea, 0x3d, 0x2d, 0x00, 0x2a, 0x78, 0x45, 0x4d, 0x38, 0xed, 0x2e, 0x2e, 0x44, 0x49, 0xed, 0xcb, 0x33}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xa0, 0x68, 0xe8, 0x41, 0x8f, 0x91, 0xf8, 0x11, 0x13, 0x90, 0x2e, 0xa7, 0xab, 0x30, 0xef, 0xad, 0xa0, 0x61, 0x00, 0x88, 0xef, 0xdb, 0xce, 0x5b, 0x5c, 0xbb, 0x62, 0xc8, 0x56, 0xf9, 0x00, 0x73}} , + {{0x3f, 0x60, 0xc1, 0x82, 0x2d, 0xa3, 0x28, 0x58, 0x24, 0x9e, 0x9f, 0xe3, 0x70, 0xcc, 0x09, 0x4e, 0x1a, 0x3f, 0x11, 0x11, 0x15, 0x07, 0x3c, 0xa4, 0x41, 0xe0, 0x65, 0xa3, 0x0a, 0x41, 0x6d, 0x11}}}, +{{{0x31, 0x40, 0x01, 0x52, 0x56, 0x94, 0x5b, 0x28, 0x8a, 0xaa, 0x52, 0xee, 0xd8, 0x0a, 0x05, 0x8d, 0xcd, 0xb5, 0xaa, 0x2e, 0x38, 0xaa, 0xb7, 0x87, 0xf7, 0x2b, 0xfb, 0x04, 0xcb, 0x84, 0x3d, 0x54}} , + {{0x20, 0xef, 0x59, 0xde, 0xa4, 0x2b, 0x93, 0x6e, 0x2e, 0xec, 0x42, 0x9a, 0xd4, 0x2d, 0xf4, 0x46, 0x58, 0x27, 0x2b, 0x18, 0x8f, 0x83, 0x3d, 0x69, 0x9e, 0xd4, 0x3e, 0xb6, 0xc5, 0xfd, 0x58, 0x03}}}, +{{{0x33, 0x89, 0xc9, 0x63, 0x62, 0x1c, 0x17, 0xb4, 0x60, 0xc4, 0x26, 0x68, 0x09, 0xc3, 0x2e, 0x37, 0x0f, 0x7b, 0xb4, 0x9c, 0xb6, 0xf9, 0xfb, 0xd4, 0x51, 0x78, 0xc8, 0x63, 0xea, 0x77, 0x47, 0x07}} , + {{0x32, 0xb4, 0x18, 0x47, 0x79, 0xcb, 0xd4, 0x5a, 0x07, 0x14, 0x0f, 0xa0, 0xd5, 0xac, 0xd0, 0x41, 0x40, 0xab, 0x61, 0x23, 0xe5, 0x2a, 0x2a, 0x6f, 0xf7, 0xa8, 0xd4, 0x76, 0xef, 0xe7, 0x45, 0x6c}}}, +{{{0xa1, 0x5e, 0x60, 0x4f, 0xfb, 0xe1, 0x70, 0x6a, 0x1f, 0x55, 0x4f, 0x09, 0xb4, 0x95, 0x33, 0x36, 0xc6, 0x81, 0x01, 0x18, 0x06, 0x25, 0x27, 0xa4, 0xb4, 0x24, 0xa4, 0x86, 0x03, 0x4c, 0xac, 0x02}} , + {{0x77, 0x38, 0xde, 0xd7, 0x60, 0x48, 0x07, 0xf0, 0x74, 0xa8, 0xff, 0x54, 0xe5, 0x30, 0x43, 0xff, 0x77, 0xfb, 0x21, 0x07, 0xff, 0xb2, 0x07, 0x6b, 0xe4, 0xe5, 0x30, 0xfc, 0x19, 0x6c, 0xa3, 0x01}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x13, 0xc5, 0x2c, 0xac, 0xd3, 0x83, 0x82, 0x7c, 0x29, 0xf7, 0x05, 0xa5, 0x00, 0xb6, 0x1f, 0x86, 0x55, 0xf4, 0xd6, 0x2f, 0x0c, 0x99, 0xd0, 0x65, 0x9b, 0x6b, 0x46, 0x0d, 0x43, 0xf8, 0x16, 0x28}} , + {{0x1e, 0x7f, 0xb4, 0x74, 0x7e, 0xb1, 0x89, 0x4f, 0x18, 0x5a, 0xab, 0x64, 0x06, 0xdf, 0x45, 0x87, 0xe0, 0x6a, 0xc6, 0xf0, 0x0e, 0xc9, 0x24, 0x35, 0x38, 0xea, 0x30, 0x54, 0xb4, 0xc4, 0x52, 0x54}}}, +{{{0xe9, 0x9f, 0xdc, 0x3f, 0xc1, 0x89, 0x44, 0x74, 0x27, 0xe4, 0xc1, 0x90, 0xff, 0x4a, 0xa7, 0x3c, 0xee, 0xcd, 0xf4, 0x1d, 0x25, 0x94, 0x7f, 0x63, 0x16, 0x48, 0xbc, 0x64, 0xfe, 0x95, 0xc4, 0x0c}} , + {{0x8b, 0x19, 0x75, 0x6e, 0x03, 0x06, 0x5e, 0x6a, 0x6f, 0x1a, 0x8c, 0xe3, 0xd3, 0x28, 0xf2, 0xe0, 0xb9, 0x7a, 0x43, 0x69, 0xe6, 0xd3, 0xc0, 0xfe, 0x7e, 0x97, 0xab, 0x6c, 0x7b, 0x8e, 0x13, 0x42}}}, +{{{0xd4, 0xca, 0x70, 0x3d, 0xab, 0xfb, 0x5f, 0x5e, 0x00, 0x0c, 0xcc, 0x77, 0x22, 0xf8, 0x78, 0x55, 0xae, 0x62, 0x35, 0xfb, 0x9a, 0xc6, 0x03, 0xe4, 0x0c, 0xee, 0xab, 0xc7, 0xc0, 0x89, 0x87, 0x54}} , + {{0x32, 0xad, 0xae, 0x85, 0x58, 0x43, 0xb8, 0xb1, 0xe6, 0x3e, 0x00, 0x9c, 0x78, 0x88, 0x56, 0xdb, 0x9c, 0xfc, 0x79, 0xf6, 0xf9, 0x41, 0x5f, 0xb7, 0xbc, 0x11, 0xf9, 0x20, 0x36, 0x1c, 0x53, 0x2b}}}, +{{{0x5a, 0x20, 0x5b, 0xa1, 0xa5, 0x44, 0x91, 0x24, 0x02, 0x63, 0x12, 0x64, 0xb8, 0x55, 0xf6, 0xde, 0x2c, 0xdb, 0x47, 0xb8, 0xc6, 0x0a, 0xc3, 0x00, 0x78, 0x93, 0xd8, 0xf5, 0xf5, 0x18, 0x28, 0x0a}} , + {{0xd6, 0x1b, 0x9a, 0x6c, 0xe5, 0x46, 0xea, 0x70, 0x96, 0x8d, 0x4e, 0x2a, 0x52, 0x21, 0x26, 0x4b, 0xb1, 0xbb, 0x0f, 0x7c, 0xa9, 0x9b, 0x04, 0xbb, 0x51, 0x08, 0xf1, 0x9a, 0xa4, 0x76, 0x7c, 0x18}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xfa, 0x94, 0xf7, 0x40, 0xd0, 0xd7, 0xeb, 0xa9, 0x82, 0x36, 0xd5, 0x15, 0xb9, 0x33, 0x7a, 0xbf, 0x8a, 0xf2, 0x63, 0xaa, 0x37, 0xf5, 0x59, 0xac, 0xbd, 0xbb, 0x32, 0x36, 0xbe, 0x73, 0x99, 0x38}} , + {{0x2c, 0xb3, 0xda, 0x7a, 0xd8, 0x3d, 0x99, 0xca, 0xd2, 0xf4, 0xda, 0x99, 0x8e, 0x4f, 0x98, 0xb7, 0xf4, 0xae, 0x3e, 0x9f, 0x8e, 0x35, 0x60, 0xa4, 0x33, 0x75, 0xa4, 0x04, 0x93, 0xb1, 0x6b, 0x4d}}}, +{{{0x97, 0x9d, 0xa8, 0xcd, 0x97, 0x7b, 0x9d, 0xb9, 0xe7, 0xa5, 0xef, 0xfd, 0xa8, 0x42, 0x6b, 0xc3, 0x62, 0x64, 0x7d, 0xa5, 0x1b, 0xc9, 0x9e, 0xd2, 0x45, 0xb9, 0xee, 0x03, 0xb0, 0xbf, 0xc0, 0x68}} , + {{0xed, 0xb7, 0x84, 0x2c, 0xf6, 0xd3, 0xa1, 0x6b, 0x24, 0x6d, 0x87, 0x56, 0x97, 0x59, 0x79, 0x62, 0x9f, 0xac, 0xed, 0xf3, 0xc9, 0x89, 0x21, 0x2e, 0x04, 0xb3, 0xcc, 0x2f, 0xbe, 0xd6, 0x0a, 0x4b}}}, +{{{0x39, 0x61, 0x05, 0xed, 0x25, 0x89, 0x8b, 0x5d, 0x1b, 0xcb, 0x0c, 0x55, 0xf4, 0x6a, 0x00, 0x8a, 0x46, 0xe8, 0x1e, 0xc6, 0x83, 0xc8, 0x5a, 0x76, 0xdb, 0xcc, 0x19, 0x7a, 0xcc, 0x67, 0x46, 0x0b}} , + {{0x53, 0xcf, 0xc2, 0xa1, 0xad, 0x6a, 0xf3, 0xcd, 0x8f, 0xc9, 0xde, 0x1c, 0xf8, 0x6c, 0x8f, 0xf8, 0x76, 0x42, 0xe7, 0xfe, 0xb2, 0x72, 0x21, 0x0a, 0x66, 0x74, 0x8f, 0xb7, 0xeb, 0xe4, 0x6f, 0x01}}}, +{{{0x22, 0x8c, 0x6b, 0xbe, 0xfc, 0x4d, 0x70, 0x62, 0x6e, 0x52, 0x77, 0x99, 0x88, 0x7e, 0x7b, 0x57, 0x7a, 0x0d, 0xfe, 0xdc, 0x72, 0x92, 0xf1, 0x68, 0x1d, 0x97, 0xd7, 0x7c, 0x8d, 0x53, 0x10, 0x37}} , + {{0x53, 0x88, 0x77, 0x02, 0xca, 0x27, 0xa8, 0xe5, 0x45, 0xe2, 0xa8, 0x48, 0x2a, 0xab, 0x18, 0xca, 0xea, 0x2d, 0x2a, 0x54, 0x17, 0x37, 0x32, 0x09, 0xdc, 0xe0, 0x4a, 0xb7, 0x7d, 0x82, 0x10, 0x7d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x8a, 0x64, 0x1e, 0x14, 0x0a, 0x57, 0xd4, 0xda, 0x5c, 0x96, 0x9b, 0x01, 0x4c, 0x67, 0xbf, 0x8b, 0x30, 0xfe, 0x08, 0xdb, 0x0d, 0xd5, 0xa8, 0xd7, 0x09, 0x11, 0x85, 0xa2, 0xd3, 0x45, 0xfb, 0x7e}} , + {{0xda, 0x8c, 0xc2, 0xd0, 0xac, 0x18, 0xe8, 0x52, 0x36, 0xd4, 0x21, 0xa3, 0xdd, 0x57, 0x22, 0x79, 0xb7, 0xf8, 0x71, 0x9d, 0xc6, 0x91, 0x70, 0x86, 0x56, 0xbf, 0xa1, 0x11, 0x8b, 0x19, 0xe1, 0x0f}}}, +{{{0x18, 0x32, 0x98, 0x2c, 0x8f, 0x91, 0xae, 0x12, 0xf0, 0x8c, 0xea, 0xf3, 0x3c, 0xb9, 0x5d, 0xe4, 0x69, 0xed, 0xb2, 0x47, 0x18, 0xbd, 0xce, 0x16, 0x52, 0x5c, 0x23, 0xe2, 0xa5, 0x25, 0x52, 0x5d}} , + {{0xb9, 0xb1, 0xe7, 0x5d, 0x4e, 0xbc, 0xee, 0xbb, 0x40, 0x81, 0x77, 0x82, 0x19, 0xab, 0xb5, 0xc6, 0xee, 0xab, 0x5b, 0x6b, 0x63, 0x92, 0x8a, 0x34, 0x8d, 0xcd, 0xee, 0x4f, 0x49, 0xe5, 0xc9, 0x7e}}}, +{{{0x21, 0xac, 0x8b, 0x22, 0xcd, 0xc3, 0x9a, 0xe9, 0x5e, 0x78, 0xbd, 0xde, 0xba, 0xad, 0xab, 0xbf, 0x75, 0x41, 0x09, 0xc5, 0x58, 0xa4, 0x7d, 0x92, 0xb0, 0x7f, 0xf2, 0xa1, 0xd1, 0xc0, 0xb3, 0x6d}} , + {{0x62, 0x4f, 0xd0, 0x75, 0x77, 0xba, 0x76, 0x77, 0xd7, 0xb8, 0xd8, 0x92, 0x6f, 0x98, 0x34, 0x3d, 0xd6, 0x4e, 0x1c, 0x0f, 0xf0, 0x8f, 0x2e, 0xf1, 0xb3, 0xbd, 0xb1, 0xb9, 0xec, 0x99, 0xb4, 0x07}}}, +{{{0x60, 0x57, 0x2e, 0x9a, 0x72, 0x1d, 0x6b, 0x6e, 0x58, 0x33, 0x24, 0x8c, 0x48, 0x39, 0x46, 0x8e, 0x89, 0x6a, 0x88, 0x51, 0x23, 0x62, 0xb5, 0x32, 0x09, 0x36, 0xe3, 0x57, 0xf5, 0x98, 0xde, 0x6f}} , + {{0x8b, 0x2c, 0x00, 0x48, 0x4a, 0xf9, 0x5b, 0x87, 0x69, 0x52, 0xe5, 0x5b, 0xd1, 0xb1, 0xe5, 0x25, 0x25, 0xe0, 0x9c, 0xc2, 0x13, 0x44, 0xe8, 0xb9, 0x0a, 0x70, 0xad, 0xbd, 0x0f, 0x51, 0x94, 0x69}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xa2, 0xdc, 0xab, 0xa9, 0x25, 0x2d, 0xac, 0x5f, 0x03, 0x33, 0x08, 0xe7, 0x7e, 0xfe, 0x95, 0x36, 0x3c, 0x5b, 0x3a, 0xd3, 0x05, 0x82, 0x1c, 0x95, 0x2d, 0xd8, 0x77, 0x7e, 0x02, 0xd9, 0x5b, 0x70}} , + {{0xc2, 0xfe, 0x1b, 0x0c, 0x67, 0xcd, 0xd6, 0xe0, 0x51, 0x8e, 0x2c, 0xe0, 0x79, 0x88, 0xf0, 0xcf, 0x41, 0x4a, 0xad, 0x23, 0xd4, 0x46, 0xca, 0x94, 0xa1, 0xc3, 0xeb, 0x28, 0x06, 0xfa, 0x17, 0x14}}}, +{{{0x7b, 0xaa, 0x70, 0x0a, 0x4b, 0xfb, 0xf5, 0xbf, 0x80, 0xc5, 0xcf, 0x08, 0x7a, 0xdd, 0xa1, 0xf4, 0x9d, 0x54, 0x50, 0x53, 0x23, 0x77, 0x23, 0xf5, 0x34, 0xa5, 0x22, 0xd1, 0x0d, 0x96, 0x2e, 0x47}} , + {{0xcc, 0xb7, 0x32, 0x89, 0x57, 0xd0, 0x98, 0x75, 0xe4, 0x37, 0x99, 0xa9, 0xe8, 0xba, 0xed, 0xba, 0xeb, 0xc7, 0x4f, 0x15, 0x76, 0x07, 0x0c, 0x4c, 0xef, 0x9f, 0x52, 0xfc, 0x04, 0x5d, 0x58, 0x10}}}, +{{{0xce, 0x82, 0xf0, 0x8f, 0x79, 0x02, 0xa8, 0xd1, 0xda, 0x14, 0x09, 0x48, 0xee, 0x8a, 0x40, 0x98, 0x76, 0x60, 0x54, 0x5a, 0xde, 0x03, 0x24, 0xf5, 0xe6, 0x2f, 0xe1, 0x03, 0xbf, 0x68, 0x82, 0x7f}} , + {{0x64, 0xe9, 0x28, 0xc7, 0xa4, 0xcf, 0x2a, 0xf9, 0x90, 0x64, 0x72, 0x2c, 0x8b, 0xeb, 0xec, 0xa0, 0xf2, 0x7d, 0x35, 0xb5, 0x90, 0x4d, 0x7f, 0x5b, 0x4a, 0x49, 0xe4, 0xb8, 0x3b, 0xc8, 0xa1, 0x2f}}}, +{{{0x8b, 0xc5, 0xcc, 0x3d, 0x69, 0xa6, 0xa1, 0x18, 0x44, 0xbc, 0x4d, 0x77, 0x37, 0xc7, 0x86, 0xec, 0x0c, 0xc9, 0xd6, 0x44, 0xa9, 0x23, 0x27, 0xb9, 0x03, 0x34, 0xa7, 0x0a, 0xd5, 0xc7, 0x34, 0x37}} , + {{0xf9, 0x7e, 0x3e, 0x66, 0xee, 0xf9, 0x99, 0x28, 0xff, 0xad, 0x11, 0xd8, 0xe2, 0x66, 0xc5, 0xcd, 0x0f, 0x0d, 0x0b, 0x6a, 0xfc, 0x7c, 0x24, 0xa8, 0x4f, 0xa8, 0x5e, 0x80, 0x45, 0x8b, 0x6c, 0x41}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xef, 0x1e, 0xec, 0xf7, 0x8d, 0x77, 0xf2, 0xea, 0xdb, 0x60, 0x03, 0x21, 0xc0, 0xff, 0x5e, 0x67, 0xc3, 0x71, 0x0b, 0x21, 0xb4, 0x41, 0xa0, 0x68, 0x38, 0xc6, 0x01, 0xa3, 0xd3, 0x51, 0x3c, 0x3c}} , + {{0x92, 0xf8, 0xd6, 0x4b, 0xef, 0x42, 0x13, 0xb2, 0x4a, 0xc4, 0x2e, 0x72, 0x3f, 0xc9, 0x11, 0xbd, 0x74, 0x02, 0x0e, 0xf5, 0x13, 0x9d, 0x83, 0x1a, 0x1b, 0xd5, 0x54, 0xde, 0xc4, 0x1e, 0x16, 0x6c}}}, +{{{0x27, 0x52, 0xe4, 0x63, 0xaa, 0x94, 0xe6, 0xc3, 0x28, 0x9c, 0xc6, 0x56, 0xac, 0xfa, 0xb6, 0xbd, 0xe2, 0xcc, 0x76, 0xc6, 0x27, 0x27, 0xa2, 0x8e, 0x78, 0x2b, 0x84, 0x72, 0x10, 0xbd, 0x4e, 0x2a}} , + {{0xea, 0xa7, 0x23, 0xef, 0x04, 0x61, 0x80, 0x50, 0xc9, 0x6e, 0xa5, 0x96, 0xd1, 0xd1, 0xc8, 0xc3, 0x18, 0xd7, 0x2d, 0xfd, 0x26, 0xbd, 0xcb, 0x7b, 0x92, 0x51, 0x0e, 0x4a, 0x65, 0x57, 0xb8, 0x49}}}, +{{{0xab, 0x55, 0x36, 0xc3, 0xec, 0x63, 0x55, 0x11, 0x55, 0xf6, 0xa5, 0xc7, 0x01, 0x5f, 0xfe, 0x79, 0xd8, 0x0a, 0xf7, 0x03, 0xd8, 0x98, 0x99, 0xf5, 0xd0, 0x00, 0x54, 0x6b, 0x66, 0x28, 0xf5, 0x25}} , + {{0x7a, 0x8d, 0xa1, 0x5d, 0x70, 0x5d, 0x51, 0x27, 0xee, 0x30, 0x65, 0x56, 0x95, 0x46, 0xde, 0xbd, 0x03, 0x75, 0xb4, 0x57, 0x59, 0x89, 0xeb, 0x02, 0x9e, 0xcc, 0x89, 0x19, 0xa7, 0xcb, 0x17, 0x67}}}, +{{{0x6a, 0xeb, 0xfc, 0x9a, 0x9a, 0x10, 0xce, 0xdb, 0x3a, 0x1c, 0x3c, 0x6a, 0x9d, 0xea, 0x46, 0xbc, 0x45, 0x49, 0xac, 0xe3, 0x41, 0x12, 0x7c, 0xf0, 0xf7, 0x4f, 0xf9, 0xf7, 0xff, 0x2c, 0x89, 0x04}} , + {{0x30, 0x31, 0x54, 0x1a, 0x46, 0xca, 0xe6, 0xc6, 0xcb, 0xe2, 0xc3, 0xc1, 0x8b, 0x75, 0x81, 0xbe, 0xee, 0xf8, 0xa3, 0x11, 0x1c, 0x25, 0xa3, 0xa7, 0x35, 0x51, 0x55, 0xe2, 0x25, 0xaa, 0xe2, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xb4, 0x48, 0x10, 0x9f, 0x8a, 0x09, 0x76, 0xfa, 0xf0, 0x7a, 0xb0, 0x70, 0xf7, 0x83, 0x80, 0x52, 0x84, 0x2b, 0x26, 0xa2, 0xc4, 0x5d, 0x4f, 0xba, 0xb1, 0xc8, 0x40, 0x0d, 0x78, 0x97, 0xc4, 0x60}} , + {{0xd4, 0xb1, 0x6c, 0x08, 0xc7, 0x40, 0x38, 0x73, 0x5f, 0x0b, 0xf3, 0x76, 0x5d, 0xb2, 0xa5, 0x2f, 0x57, 0x57, 0x07, 0xed, 0x08, 0xa2, 0x6c, 0x4f, 0x08, 0x02, 0xb5, 0x0e, 0xee, 0x44, 0xfa, 0x22}}}, +{{{0x0f, 0x00, 0x3f, 0xa6, 0x04, 0x19, 0x56, 0x65, 0x31, 0x7f, 0x8b, 0xeb, 0x0d, 0xe1, 0x47, 0x89, 0x97, 0x16, 0x53, 0xfa, 0x81, 0xa7, 0xaa, 0xb2, 0xbf, 0x67, 0xeb, 0x72, 0x60, 0x81, 0x0d, 0x48}} , + {{0x7e, 0x13, 0x33, 0xcd, 0xa8, 0x84, 0x56, 0x1e, 0x67, 0xaf, 0x6b, 0x43, 0xac, 0x17, 0xaf, 0x16, 0xc0, 0x52, 0x99, 0x49, 0x5b, 0x87, 0x73, 0x7e, 0xb5, 0x43, 0xda, 0x6b, 0x1d, 0x0f, 0x2d, 0x55}}}, +{{{0xe9, 0x58, 0x1f, 0xff, 0x84, 0x3f, 0x93, 0x1c, 0xcb, 0xe1, 0x30, 0x69, 0xa5, 0x75, 0x19, 0x7e, 0x14, 0x5f, 0xf8, 0xfc, 0x09, 0xdd, 0xa8, 0x78, 0x9d, 0xca, 0x59, 0x8b, 0xd1, 0x30, 0x01, 0x13}} , + {{0xff, 0x76, 0x03, 0xc5, 0x4b, 0x89, 0x99, 0x70, 0x00, 0x59, 0x70, 0x9c, 0xd5, 0xd9, 0x11, 0x89, 0x5a, 0x46, 0xfe, 0xef, 0xdc, 0xd9, 0x55, 0x2b, 0x45, 0xa7, 0xb0, 0x2d, 0xfb, 0x24, 0xc2, 0x29}}}, +{{{0x38, 0x06, 0xf8, 0x0b, 0xac, 0x82, 0xc4, 0x97, 0x2b, 0x90, 0xe0, 0xf7, 0xa8, 0xab, 0x6c, 0x08, 0x80, 0x66, 0x90, 0x46, 0xf7, 0x26, 0x2d, 0xf8, 0xf1, 0xc4, 0x6b, 0x4a, 0x82, 0x98, 0x8e, 0x37}} , + {{0x8e, 0xb4, 0xee, 0xb8, 0xd4, 0x3f, 0xb2, 0x1b, 0xe0, 0x0a, 0x3d, 0x75, 0x34, 0x28, 0xa2, 0x8e, 0xc4, 0x92, 0x7b, 0xfe, 0x60, 0x6e, 0x6d, 0xb8, 0x31, 0x1d, 0x62, 0x0d, 0x78, 0x14, 0x42, 0x11}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x5e, 0xa8, 0xd8, 0x04, 0x9b, 0x73, 0xc9, 0xc9, 0xdc, 0x0d, 0x73, 0xbf, 0x0a, 0x0a, 0x73, 0xff, 0x18, 0x1f, 0x9c, 0x51, 0xaa, 0xc6, 0xf1, 0x83, 0x25, 0xfd, 0xab, 0xa3, 0x11, 0xd3, 0x01, 0x24}} , + {{0x4d, 0xe3, 0x7e, 0x38, 0x62, 0x5e, 0x64, 0xbb, 0x2b, 0x53, 0xb5, 0x03, 0x68, 0xc4, 0xf2, 0x2b, 0x5a, 0x03, 0x32, 0x99, 0x4a, 0x41, 0x9a, 0xe1, 0x1a, 0xae, 0x8c, 0x48, 0xf3, 0x24, 0x32, 0x65}}}, +{{{0xe8, 0xdd, 0xad, 0x3a, 0x8c, 0xea, 0xf4, 0xb3, 0xb2, 0xe5, 0x73, 0xf2, 0xed, 0x8b, 0xbf, 0xed, 0xb1, 0x0c, 0x0c, 0xfb, 0x2b, 0xf1, 0x01, 0x48, 0xe8, 0x26, 0x03, 0x8e, 0x27, 0x4d, 0x96, 0x72}} , + {{0xc8, 0x09, 0x3b, 0x60, 0xc9, 0x26, 0x4d, 0x7c, 0xf2, 0x9c, 0xd4, 0xa1, 0x3b, 0x26, 0xc2, 0x04, 0x33, 0x44, 0x76, 0x3c, 0x02, 0xbb, 0x11, 0x42, 0x0c, 0x22, 0xb7, 0xc6, 0xe1, 0xac, 0xb4, 0x0e}}}, +{{{0x6f, 0x85, 0xe7, 0xef, 0xde, 0x67, 0x30, 0xfc, 0xbf, 0x5a, 0xe0, 0x7b, 0x7a, 0x2a, 0x54, 0x6b, 0x5d, 0x62, 0x85, 0xa1, 0xf8, 0x16, 0x88, 0xec, 0x61, 0xb9, 0x96, 0xb5, 0xef, 0x2d, 0x43, 0x4d}} , + {{0x7c, 0x31, 0x33, 0xcc, 0xe4, 0xcf, 0x6c, 0xff, 0x80, 0x47, 0x77, 0xd1, 0xd8, 0xe9, 0x69, 0x97, 0x98, 0x7f, 0x20, 0x57, 0x1d, 0x1d, 0x4f, 0x08, 0x27, 0xc8, 0x35, 0x57, 0x40, 0xc6, 0x21, 0x0c}}}, +{{{0xd2, 0x8e, 0x9b, 0xfa, 0x42, 0x8e, 0xdf, 0x8f, 0xc7, 0x86, 0xf9, 0xa4, 0xca, 0x70, 0x00, 0x9d, 0x21, 0xbf, 0xec, 0x57, 0x62, 0x30, 0x58, 0x8c, 0x0d, 0x35, 0xdb, 0x5d, 0x8b, 0x6a, 0xa0, 0x5a}} , + {{0xc1, 0x58, 0x7c, 0x0d, 0x20, 0xdd, 0x11, 0x26, 0x5f, 0x89, 0x3b, 0x97, 0x58, 0xf8, 0x8b, 0xe3, 0xdf, 0x32, 0xe2, 0xfc, 0xd8, 0x67, 0xf2, 0xa5, 0x37, 0x1e, 0x6d, 0xec, 0x7c, 0x27, 0x20, 0x79}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0xe9, 0xc0, 0xfa, 0x95, 0x45, 0x23, 0x96, 0xf1, 0x2c, 0x79, 0x25, 0x14, 0xce, 0x40, 0x14, 0x44, 0x2c, 0x36, 0x50, 0xd9, 0x63, 0x56, 0xb7, 0x56, 0x3b, 0x9e, 0xa7, 0xef, 0x89, 0xbb, 0x0e}} , + {{0xce, 0x7f, 0xdc, 0x0a, 0xcc, 0x82, 0x1c, 0x0a, 0x78, 0x71, 0xe8, 0x74, 0x8d, 0x01, 0x30, 0x0f, 0xa7, 0x11, 0x4c, 0xdf, 0x38, 0xd7, 0xa7, 0x0d, 0xf8, 0x48, 0x52, 0x00, 0x80, 0x7b, 0x5f, 0x0e}}}, +{{{0x25, 0x83, 0xe6, 0x94, 0x7b, 0x81, 0xb2, 0x91, 0xae, 0x0e, 0x05, 0xc9, 0xa3, 0x68, 0x2d, 0xd9, 0x88, 0x25, 0x19, 0x2a, 0x61, 0x61, 0x21, 0x97, 0x15, 0xa1, 0x35, 0xa5, 0x46, 0xc8, 0xa2, 0x0e}} , + {{0x1b, 0x03, 0x0d, 0x8b, 0x5a, 0x1b, 0x97, 0x4b, 0xf2, 0x16, 0x31, 0x3d, 0x1f, 0x33, 0xa0, 0x50, 0x3a, 0x18, 0xbe, 0x13, 0xa1, 0x76, 0xc1, 0xba, 0x1b, 0xf1, 0x05, 0x7b, 0x33, 0xa8, 0x82, 0x3b}}}, +{{{0xba, 0x36, 0x7b, 0x6d, 0xa9, 0xea, 0x14, 0x12, 0xc5, 0xfa, 0x91, 0x00, 0xba, 0x9b, 0x99, 0xcc, 0x56, 0x02, 0xe9, 0xa0, 0x26, 0x40, 0x66, 0x8c, 0xc4, 0xf8, 0x85, 0x33, 0x68, 0xe7, 0x03, 0x20}} , + {{0x50, 0x5b, 0xff, 0xa9, 0xb2, 0xf1, 0xf1, 0x78, 0xcf, 0x14, 0xa4, 0xa9, 0xfc, 0x09, 0x46, 0x94, 0x54, 0x65, 0x0d, 0x9c, 0x5f, 0x72, 0x21, 0xe2, 0x97, 0xa5, 0x2d, 0x81, 0xce, 0x4a, 0x5f, 0x79}}}, +{{{0x3d, 0x5f, 0x5c, 0xd2, 0xbc, 0x7d, 0x77, 0x0e, 0x2a, 0x6d, 0x22, 0x45, 0x84, 0x06, 0xc4, 0xdd, 0xc6, 0xa6, 0xc6, 0xd7, 0x49, 0xad, 0x6d, 0x87, 0x91, 0x0e, 0x3a, 0x67, 0x1d, 0x2c, 0x1d, 0x56}} , + {{0xfe, 0x7a, 0x74, 0xcf, 0xd4, 0xd2, 0xe5, 0x19, 0xde, 0xd0, 0xdb, 0x70, 0x23, 0x69, 0xe6, 0x6d, 0xec, 0xec, 0xcc, 0x09, 0x33, 0x6a, 0x77, 0xdc, 0x6b, 0x22, 0x76, 0x5d, 0x92, 0x09, 0xac, 0x2d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x23, 0x15, 0x17, 0xeb, 0xd3, 0xdb, 0x12, 0x5e, 0x01, 0xf0, 0x91, 0xab, 0x2c, 0x41, 0xce, 0xac, 0xed, 0x1b, 0x4b, 0x2d, 0xbc, 0xdb, 0x17, 0x66, 0x89, 0x46, 0xad, 0x4b, 0x1e, 0x6f, 0x0b, 0x14}} , + {{0x11, 0xce, 0xbf, 0xb6, 0x77, 0x2d, 0x48, 0x22, 0x18, 0x4f, 0xa3, 0x5d, 0x4a, 0xb0, 0x70, 0x12, 0x3e, 0x54, 0xd7, 0xd8, 0x0e, 0x2b, 0x27, 0xdc, 0x53, 0xff, 0xca, 0x8c, 0x59, 0xb3, 0x4e, 0x44}}}, +{{{0x07, 0x76, 0x61, 0x0f, 0x66, 0xb2, 0x21, 0x39, 0x7e, 0xc0, 0xec, 0x45, 0x28, 0x82, 0xa1, 0x29, 0x32, 0x44, 0x35, 0x13, 0x5e, 0x61, 0x5e, 0x54, 0xcb, 0x7c, 0xef, 0xf6, 0x41, 0xcf, 0x9f, 0x0a}} , + {{0xdd, 0xf9, 0xda, 0x84, 0xc3, 0xe6, 0x8a, 0x9f, 0x24, 0xd2, 0x96, 0x5d, 0x39, 0x6f, 0x58, 0x8c, 0xc1, 0x56, 0x93, 0xab, 0xb5, 0x79, 0x3b, 0xd2, 0xa8, 0x73, 0x16, 0xed, 0xfa, 0xb4, 0x2f, 0x73}}}, +{{{0x8b, 0xb1, 0x95, 0xe5, 0x92, 0x50, 0x35, 0x11, 0x76, 0xac, 0xf4, 0x4d, 0x24, 0xc3, 0x32, 0xe6, 0xeb, 0xfe, 0x2c, 0x87, 0xc4, 0xf1, 0x56, 0xc4, 0x75, 0x24, 0x7a, 0x56, 0x85, 0x5a, 0x3a, 0x13}} , + {{0x0d, 0x16, 0xac, 0x3c, 0x4a, 0x58, 0x86, 0x3a, 0x46, 0x7f, 0x6c, 0xa3, 0x52, 0x6e, 0x37, 0xe4, 0x96, 0x9c, 0xe9, 0x5c, 0x66, 0x41, 0x67, 0xe4, 0xfb, 0x79, 0x0c, 0x05, 0xf6, 0x64, 0xd5, 0x7c}}}, +{{{0x28, 0xc1, 0xe1, 0x54, 0x73, 0xf2, 0xbf, 0x76, 0x74, 0x19, 0x19, 0x1b, 0xe4, 0xb9, 0xa8, 0x46, 0x65, 0x73, 0xf3, 0x77, 0x9b, 0x29, 0x74, 0x5b, 0xc6, 0x89, 0x6c, 0x2c, 0x7c, 0xf8, 0xb3, 0x0f}} , + {{0xf7, 0xd5, 0xe9, 0x74, 0x5d, 0xb8, 0x25, 0x16, 0xb5, 0x30, 0xbc, 0x84, 0xc5, 0xf0, 0xad, 0xca, 0x12, 0x28, 0xbc, 0x9d, 0xd4, 0xfa, 0x82, 0xe6, 0xe3, 0xbf, 0xa2, 0x15, 0x2c, 0xd4, 0x34, 0x10}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x61, 0xb1, 0x46, 0xba, 0x0e, 0x31, 0xa5, 0x67, 0x6c, 0x7f, 0xd6, 0xd9, 0x27, 0x85, 0x0f, 0x79, 0x14, 0xc8, 0x6c, 0x2f, 0x5f, 0x5b, 0x9c, 0x35, 0x3d, 0x38, 0x86, 0x77, 0x65, 0x55, 0x6a, 0x7b}} , + {{0xd3, 0xb0, 0x3a, 0x66, 0x60, 0x1b, 0x43, 0xf1, 0x26, 0x58, 0x99, 0x09, 0x8f, 0x2d, 0xa3, 0x14, 0x71, 0x85, 0xdb, 0xed, 0xf6, 0x26, 0xd5, 0x61, 0x9a, 0x73, 0xac, 0x0e, 0xea, 0xac, 0xb7, 0x0c}}}, +{{{0x5e, 0xf4, 0xe5, 0x17, 0x0e, 0x10, 0x9f, 0xe7, 0x43, 0x5f, 0x67, 0x5c, 0xac, 0x4b, 0xe5, 0x14, 0x41, 0xd2, 0xbf, 0x48, 0xf5, 0x14, 0xb0, 0x71, 0xc6, 0x61, 0xc1, 0xb2, 0x70, 0x58, 0xd2, 0x5a}} , + {{0x2d, 0xba, 0x16, 0x07, 0x92, 0x94, 0xdc, 0xbd, 0x50, 0x2b, 0xc9, 0x7f, 0x42, 0x00, 0xba, 0x61, 0xed, 0xf8, 0x43, 0xed, 0xf5, 0xf9, 0x40, 0x60, 0xb2, 0xb0, 0x82, 0xcb, 0xed, 0x75, 0xc7, 0x65}}}, +{{{0x80, 0xba, 0x0d, 0x09, 0x40, 0xa7, 0x39, 0xa6, 0x67, 0x34, 0x7e, 0x66, 0xbe, 0x56, 0xfb, 0x53, 0x78, 0xc4, 0x46, 0xe8, 0xed, 0x68, 0x6c, 0x7f, 0xce, 0xe8, 0x9f, 0xce, 0xa2, 0x64, 0x58, 0x53}} , + {{0xe8, 0xc1, 0xa9, 0xc2, 0x7b, 0x59, 0x21, 0x33, 0xe2, 0x43, 0x73, 0x2b, 0xac, 0x2d, 0xc1, 0x89, 0x3b, 0x15, 0xe2, 0xd5, 0xc0, 0x97, 0x8a, 0xfd, 0x6f, 0x36, 0x33, 0xb7, 0xb9, 0xc3, 0x88, 0x09}}}, +{{{0xd0, 0xb6, 0x56, 0x30, 0x5c, 0xae, 0xb3, 0x75, 0x44, 0xa4, 0x83, 0x51, 0x6e, 0x01, 0x65, 0xef, 0x45, 0x76, 0xe6, 0xf5, 0xa2, 0x0d, 0xd4, 0x16, 0x3b, 0x58, 0x2f, 0xf2, 0x2f, 0x36, 0x18, 0x3f}} , + {{0xfd, 0x2f, 0xe0, 0x9b, 0x1e, 0x8c, 0xc5, 0x18, 0xa9, 0xca, 0xd4, 0x2b, 0x35, 0xb6, 0x95, 0x0a, 0x9f, 0x7e, 0xfb, 0xc4, 0xef, 0x88, 0x7b, 0x23, 0x43, 0xec, 0x2f, 0x0d, 0x0f, 0x7a, 0xfc, 0x5c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x8d, 0xd2, 0xda, 0xc7, 0x44, 0xd6, 0x7a, 0xdb, 0x26, 0x7d, 0x1d, 0xb8, 0xe1, 0xde, 0x9d, 0x7a, 0x7d, 0x17, 0x7e, 0x1c, 0x37, 0x04, 0x8d, 0x2d, 0x7c, 0x5e, 0x18, 0x38, 0x1e, 0xaf, 0xc7, 0x1b}} , + {{0x33, 0x48, 0x31, 0x00, 0x59, 0xf6, 0xf2, 0xca, 0x0f, 0x27, 0x1b, 0x63, 0x12, 0x7e, 0x02, 0x1d, 0x49, 0xc0, 0x5d, 0x79, 0x87, 0xef, 0x5e, 0x7a, 0x2f, 0x1f, 0x66, 0x55, 0xd8, 0x09, 0xd9, 0x61}}}, +{{{0x54, 0x83, 0x02, 0x18, 0x82, 0x93, 0x99, 0x07, 0xd0, 0xa7, 0xda, 0xd8, 0x75, 0x89, 0xfa, 0xf2, 0xd9, 0xa3, 0xb8, 0x6b, 0x5a, 0x35, 0x28, 0xd2, 0x6b, 0x59, 0xc2, 0xf8, 0x45, 0xe2, 0xbc, 0x06}} , + {{0x65, 0xc0, 0xa3, 0x88, 0x51, 0x95, 0xfc, 0x96, 0x94, 0x78, 0xe8, 0x0d, 0x8b, 0x41, 0xc9, 0xc2, 0x58, 0x48, 0x75, 0x10, 0x2f, 0xcd, 0x2a, 0xc9, 0xa0, 0x6d, 0x0f, 0xdd, 0x9c, 0x98, 0x26, 0x3d}}}, +{{{0x2f, 0x66, 0x29, 0x1b, 0x04, 0x89, 0xbd, 0x7e, 0xee, 0x6e, 0xdd, 0xb7, 0x0e, 0xef, 0xb0, 0x0c, 0xb4, 0xfc, 0x7f, 0xc2, 0xc9, 0x3a, 0x3c, 0x64, 0xef, 0x45, 0x44, 0xaf, 0x8a, 0x90, 0x65, 0x76}} , + {{0xa1, 0x4c, 0x70, 0x4b, 0x0e, 0xa0, 0x83, 0x70, 0x13, 0xa4, 0xaf, 0xb8, 0x38, 0x19, 0x22, 0x65, 0x09, 0xb4, 0x02, 0x4f, 0x06, 0xf8, 0x17, 0xce, 0x46, 0x45, 0xda, 0x50, 0x7c, 0x8a, 0xd1, 0x4e}}}, +{{{0xf7, 0xd4, 0x16, 0x6c, 0x4e, 0x95, 0x9d, 0x5d, 0x0f, 0x91, 0x2b, 0x52, 0xfe, 0x5c, 0x34, 0xe5, 0x30, 0xe6, 0xa4, 0x3b, 0xf3, 0xf3, 0x34, 0x08, 0xa9, 0x4a, 0xa0, 0xb5, 0x6e, 0xb3, 0x09, 0x0a}} , + {{0x26, 0xd9, 0x5e, 0xa3, 0x0f, 0xeb, 0xa2, 0xf3, 0x20, 0x3b, 0x37, 0xd4, 0xe4, 0x9e, 0xce, 0x06, 0x3d, 0x53, 0xed, 0xae, 0x2b, 0xeb, 0xb6, 0x24, 0x0a, 0x11, 0xa3, 0x0f, 0xd6, 0x7f, 0xa4, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xdb, 0x9f, 0x2c, 0xfc, 0xd6, 0xb2, 0x1e, 0x2e, 0x52, 0x7a, 0x06, 0x87, 0x2d, 0x86, 0x72, 0x2b, 0x6d, 0x90, 0x77, 0x46, 0x43, 0xb5, 0x7a, 0xf8, 0x60, 0x7d, 0x91, 0x60, 0x5b, 0x9d, 0x9e, 0x07}} , + {{0x97, 0x87, 0xc7, 0x04, 0x1c, 0x38, 0x01, 0x39, 0x58, 0xc7, 0x85, 0xa3, 0xfc, 0x64, 0x00, 0x64, 0x25, 0xa2, 0xbf, 0x50, 0x94, 0xca, 0x26, 0x31, 0x45, 0x0a, 0x24, 0xd2, 0x51, 0x29, 0x51, 0x16}}}, +{{{0x4d, 0x4a, 0xd7, 0x98, 0x71, 0x57, 0xac, 0x7d, 0x8b, 0x37, 0xbd, 0x63, 0xff, 0x87, 0xb1, 0x49, 0x95, 0x20, 0x7c, 0xcf, 0x7c, 0x59, 0xc4, 0x91, 0x9c, 0xef, 0xd0, 0xdb, 0x60, 0x09, 0x9d, 0x46}} , + {{0xcb, 0x78, 0x94, 0x90, 0xe4, 0x45, 0xb3, 0xf6, 0xd9, 0xf6, 0x57, 0x74, 0xd5, 0xf8, 0x83, 0x4f, 0x39, 0xc9, 0xbd, 0x88, 0xc2, 0x57, 0x21, 0x1f, 0x24, 0x32, 0x68, 0xf8, 0xc7, 0x21, 0x5f, 0x0b}}}, +{{{0x2a, 0x36, 0x68, 0xfc, 0x5f, 0xb6, 0x4f, 0xa5, 0xe3, 0x9d, 0x24, 0x2f, 0xc0, 0x93, 0x61, 0xcf, 0xf8, 0x0a, 0xed, 0xe1, 0xdb, 0x27, 0xec, 0x0e, 0x14, 0x32, 0x5f, 0x8e, 0xa1, 0x62, 0x41, 0x16}} , + {{0x95, 0x21, 0x01, 0xce, 0x95, 0x5b, 0x0e, 0x57, 0xc7, 0xb9, 0x62, 0xb5, 0x28, 0xca, 0x11, 0xec, 0xb4, 0x46, 0x06, 0x73, 0x26, 0xff, 0xfb, 0x66, 0x7d, 0xee, 0x5f, 0xb2, 0x56, 0xfd, 0x2a, 0x08}}}, +{{{0x92, 0x67, 0x77, 0x56, 0xa1, 0xff, 0xc4, 0xc5, 0x95, 0xf0, 0xe3, 0x3a, 0x0a, 0xca, 0x94, 0x4d, 0x9e, 0x7e, 0x3d, 0xb9, 0x6e, 0xb6, 0xb0, 0xce, 0xa4, 0x30, 0x89, 0x99, 0xe9, 0xad, 0x11, 0x59}} , + {{0xf6, 0x48, 0x95, 0xa1, 0x6f, 0x5f, 0xb7, 0xa5, 0xbb, 0x30, 0x00, 0x1c, 0xd2, 0x8a, 0xd6, 0x25, 0x26, 0x1b, 0xb2, 0x0d, 0x37, 0x6a, 0x05, 0xf4, 0x9d, 0x3e, 0x17, 0x2a, 0x43, 0xd2, 0x3a, 0x06}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x32, 0x99, 0x93, 0xd1, 0x9a, 0x72, 0xf3, 0xa9, 0x16, 0xbd, 0xb4, 0x4c, 0xdd, 0xf9, 0xd4, 0xb2, 0x64, 0x9a, 0xd3, 0x05, 0xe4, 0xa3, 0x73, 0x1c, 0xcb, 0x7e, 0x57, 0x67, 0xff, 0x04, 0xb3, 0x10}} , + {{0xb9, 0x4b, 0xa4, 0xad, 0xd0, 0x6d, 0x61, 0x23, 0xb4, 0xaf, 0x34, 0xa9, 0xaa, 0x65, 0xec, 0xd9, 0x69, 0xe3, 0x85, 0xcd, 0xcc, 0xe7, 0xb0, 0x9b, 0x41, 0xc1, 0x1c, 0xf9, 0xa0, 0xfa, 0xb7, 0x13}}}, +{{{0x04, 0xfd, 0x88, 0x3c, 0x0c, 0xd0, 0x09, 0x52, 0x51, 0x4f, 0x06, 0x19, 0xcc, 0xc3, 0xbb, 0xde, 0x80, 0xc5, 0x33, 0xbc, 0xf9, 0xf3, 0x17, 0x36, 0xdd, 0xc6, 0xde, 0xe8, 0x9b, 0x5d, 0x79, 0x1b}} , + {{0x65, 0x0a, 0xbe, 0x51, 0x57, 0xad, 0x50, 0x79, 0x08, 0x71, 0x9b, 0x07, 0x95, 0x8f, 0xfb, 0xae, 0x4b, 0x38, 0xba, 0xcf, 0x53, 0x2a, 0x86, 0x1e, 0xc0, 0x50, 0x5c, 0x67, 0x1b, 0xf6, 0x87, 0x6c}}}, +{{{0x4f, 0x00, 0xb2, 0x66, 0x55, 0xed, 0x4a, 0xed, 0x8d, 0xe1, 0x66, 0x18, 0xb2, 0x14, 0x74, 0x8d, 0xfd, 0x1a, 0x36, 0x0f, 0x26, 0x5c, 0x8b, 0x89, 0xf3, 0xab, 0xf2, 0xf3, 0x24, 0x67, 0xfd, 0x70}} , + {{0xfd, 0x4e, 0x2a, 0xc1, 0x3a, 0xca, 0x8f, 0x00, 0xd8, 0xec, 0x74, 0x67, 0xef, 0x61, 0xe0, 0x28, 0xd0, 0x96, 0xf4, 0x48, 0xde, 0x81, 0xe3, 0xef, 0xdc, 0xaa, 0x7d, 0xf3, 0xb6, 0x55, 0xa6, 0x65}}}, +{{{0xeb, 0xcb, 0xc5, 0x70, 0x91, 0x31, 0x10, 0x93, 0x0d, 0xc8, 0xd0, 0xef, 0x62, 0xe8, 0x6f, 0x82, 0xe3, 0x69, 0x3d, 0x91, 0x7f, 0x31, 0xe1, 0x26, 0x35, 0x3c, 0x4a, 0x2f, 0xab, 0xc4, 0x9a, 0x5e}} , + {{0xab, 0x1b, 0xb5, 0xe5, 0x2b, 0xc3, 0x0e, 0x29, 0xb0, 0xd0, 0x73, 0xe6, 0x4f, 0x64, 0xf2, 0xbc, 0xe4, 0xe4, 0xe1, 0x9a, 0x52, 0x33, 0x2f, 0xbd, 0xcc, 0x03, 0xee, 0x8a, 0xfa, 0x00, 0x5f, 0x50}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf6, 0xdb, 0x0d, 0x22, 0x3d, 0xb5, 0x14, 0x75, 0x31, 0xf0, 0x81, 0xe2, 0xb9, 0x37, 0xa2, 0xa9, 0x84, 0x11, 0x9a, 0x07, 0xb5, 0x53, 0x89, 0x78, 0xa9, 0x30, 0x27, 0xa1, 0xf1, 0x4e, 0x5c, 0x2e}} , + {{0x8b, 0x00, 0x54, 0xfb, 0x4d, 0xdc, 0xcb, 0x17, 0x35, 0x40, 0xff, 0xb7, 0x8c, 0xfe, 0x4a, 0xe4, 0x4e, 0x99, 0x4e, 0xa8, 0x74, 0x54, 0x5d, 0x5c, 0x96, 0xa3, 0x12, 0x55, 0x36, 0x31, 0x17, 0x5c}}}, +{{{0xce, 0x24, 0xef, 0x7b, 0x86, 0xf2, 0x0f, 0x77, 0xe8, 0x5c, 0x7d, 0x87, 0x38, 0x2d, 0xef, 0xaf, 0xf2, 0x8c, 0x72, 0x2e, 0xeb, 0xb6, 0x55, 0x4b, 0x6e, 0xf1, 0x4e, 0x8a, 0x0e, 0x9a, 0x6c, 0x4c}} , + {{0x25, 0xea, 0x86, 0xc2, 0xd1, 0x4f, 0xb7, 0x3e, 0xa8, 0x5c, 0x8d, 0x66, 0x81, 0x25, 0xed, 0xc5, 0x4c, 0x05, 0xb9, 0xd8, 0xd6, 0x70, 0xbe, 0x73, 0x82, 0xe8, 0xa1, 0xe5, 0x1e, 0x71, 0xd5, 0x26}}}, +{{{0x4e, 0x6d, 0xc3, 0xa7, 0x4f, 0x22, 0x45, 0x26, 0xa2, 0x7e, 0x16, 0xf7, 0xf7, 0x63, 0xdc, 0x86, 0x01, 0x2a, 0x71, 0x38, 0x5c, 0x33, 0xc3, 0xce, 0x30, 0xff, 0xf9, 0x2c, 0x91, 0x71, 0x8a, 0x72}} , + {{0x8c, 0x44, 0x09, 0x28, 0xd5, 0x23, 0xc9, 0x8f, 0xf3, 0x84, 0x45, 0xc6, 0x9a, 0x5e, 0xff, 0xd2, 0xc7, 0x57, 0x93, 0xa3, 0xc1, 0x69, 0xdd, 0x62, 0x0f, 0xda, 0x5c, 0x30, 0x59, 0x5d, 0xe9, 0x4c}}}, +{{{0x92, 0x7e, 0x50, 0x27, 0x72, 0xd7, 0x0c, 0xd6, 0x69, 0x96, 0x81, 0x35, 0x84, 0x94, 0x35, 0x8b, 0x6c, 0xaa, 0x62, 0x86, 0x6e, 0x1c, 0x15, 0xf3, 0x6c, 0xb3, 0xff, 0x65, 0x1b, 0xa2, 0x9b, 0x59}} , + {{0xe2, 0xa9, 0x65, 0x88, 0xc4, 0x50, 0xfa, 0xbb, 0x3b, 0x6e, 0x5f, 0x44, 0x01, 0xca, 0x97, 0xd4, 0xdd, 0xf6, 0xcd, 0x3f, 0x3f, 0xe5, 0x97, 0x67, 0x2b, 0x8c, 0x66, 0x0f, 0x35, 0x9b, 0xf5, 0x07}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf1, 0x59, 0x27, 0xd8, 0xdb, 0x5a, 0x11, 0x5e, 0x82, 0xf3, 0x38, 0xff, 0x1c, 0xed, 0xfe, 0x3f, 0x64, 0x54, 0x3f, 0x7f, 0xd1, 0x81, 0xed, 0xef, 0x65, 0xc5, 0xcb, 0xfd, 0xe1, 0x80, 0xcd, 0x11}} , + {{0xe0, 0xdb, 0x22, 0x28, 0xe6, 0xff, 0x61, 0x9d, 0x41, 0x14, 0x2d, 0x3b, 0x26, 0x22, 0xdf, 0xf1, 0x34, 0x81, 0xe9, 0x45, 0xee, 0x0f, 0x98, 0x8b, 0xa6, 0x3f, 0xef, 0xf7, 0x43, 0x19, 0xf1, 0x43}}}, +{{{0xee, 0xf3, 0x00, 0xa1, 0x50, 0xde, 0xc0, 0xb6, 0x01, 0xe3, 0x8c, 0x3c, 0x4d, 0x31, 0xd2, 0xb0, 0x58, 0xcd, 0xed, 0x10, 0x4a, 0x7a, 0xef, 0x80, 0xa9, 0x19, 0x32, 0xf3, 0xd8, 0x33, 0x8c, 0x06}} , + {{0xcb, 0x7d, 0x4f, 0xff, 0x30, 0xd8, 0x12, 0x3b, 0x39, 0x1c, 0x06, 0xf9, 0x4c, 0x34, 0x35, 0x71, 0xb5, 0x16, 0x94, 0x67, 0xdf, 0xee, 0x11, 0xde, 0xa4, 0x1d, 0x88, 0x93, 0x35, 0xa9, 0x32, 0x10}}}, +{{{0xe9, 0xc3, 0xbc, 0x7b, 0x5c, 0xfc, 0xb2, 0xf9, 0xc9, 0x2f, 0xe5, 0xba, 0x3a, 0x0b, 0xab, 0x64, 0x38, 0x6f, 0x5b, 0x4b, 0x93, 0xda, 0x64, 0xec, 0x4d, 0x3d, 0xa0, 0xf5, 0xbb, 0xba, 0x47, 0x48}} , + {{0x60, 0xbc, 0x45, 0x1f, 0x23, 0xa2, 0x3b, 0x70, 0x76, 0xe6, 0x97, 0x99, 0x4f, 0x77, 0x54, 0x67, 0x30, 0x9a, 0xe7, 0x66, 0xd6, 0xcd, 0x2e, 0x51, 0x24, 0x2c, 0x42, 0x4a, 0x11, 0xfe, 0x6f, 0x7e}}}, +{{{0x87, 0xc0, 0xb1, 0xf0, 0xa3, 0x6f, 0x0c, 0x93, 0xa9, 0x0a, 0x72, 0xef, 0x5c, 0xbe, 0x65, 0x35, 0xa7, 0x6a, 0x4e, 0x2c, 0xbf, 0x21, 0x23, 0xe8, 0x2f, 0x97, 0xc7, 0x3e, 0xc8, 0x17, 0xac, 0x1e}} , + {{0x7b, 0xef, 0x21, 0xe5, 0x40, 0xcc, 0x1e, 0xdc, 0xd6, 0xbd, 0x97, 0x7a, 0x7c, 0x75, 0x86, 0x7a, 0x25, 0x5a, 0x6e, 0x7c, 0xe5, 0x51, 0x3c, 0x1b, 0x5b, 0x82, 0x9a, 0x07, 0x60, 0xa1, 0x19, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x96, 0x88, 0xa6, 0xab, 0x8f, 0xe3, 0x3a, 0x49, 0xf8, 0xfe, 0x34, 0xe7, 0x6a, 0xb2, 0xfe, 0x40, 0x26, 0x74, 0x57, 0x4c, 0xf6, 0xd4, 0x99, 0xce, 0x5d, 0x7b, 0x2f, 0x67, 0xd6, 0x5a, 0xe4, 0x4e}} , + {{0x5c, 0x82, 0xb3, 0xbd, 0x55, 0x25, 0xf6, 0x6a, 0x93, 0xa4, 0x02, 0xc6, 0x7d, 0x5c, 0xb1, 0x2b, 0x5b, 0xff, 0xfb, 0x56, 0xf8, 0x01, 0x41, 0x90, 0xc6, 0xb6, 0xac, 0x4f, 0xfe, 0xa7, 0x41, 0x70}}}, +{{{0xdb, 0xfa, 0x9b, 0x2c, 0xd4, 0x23, 0x67, 0x2c, 0x8a, 0x63, 0x6c, 0x07, 0x26, 0x48, 0x4f, 0xc2, 0x03, 0xd2, 0x53, 0x20, 0x28, 0xed, 0x65, 0x71, 0x47, 0xa9, 0x16, 0x16, 0x12, 0xbc, 0x28, 0x33}} , + {{0x39, 0xc0, 0xfa, 0xfa, 0xcd, 0x33, 0x43, 0xc7, 0x97, 0x76, 0x9b, 0x93, 0x91, 0x72, 0xeb, 0xc5, 0x18, 0x67, 0x4c, 0x11, 0xf0, 0xf4, 0xe5, 0x73, 0xb2, 0x5c, 0x1b, 0xc2, 0x26, 0x3f, 0xbf, 0x2b}}}, +{{{0x86, 0xe6, 0x8c, 0x1d, 0xdf, 0xca, 0xfc, 0xd5, 0xf8, 0x3a, 0xc3, 0x44, 0x72, 0xe6, 0x78, 0x9d, 0x2b, 0x97, 0xf8, 0x28, 0x45, 0xb4, 0x20, 0xc9, 0x2a, 0x8c, 0x67, 0xaa, 0x11, 0xc5, 0x5b, 0x2f}} , + {{0x17, 0x0f, 0x86, 0x52, 0xd7, 0x9d, 0xc3, 0x44, 0x51, 0x76, 0x32, 0x65, 0xb4, 0x37, 0x81, 0x99, 0x46, 0x37, 0x62, 0xed, 0xcf, 0x64, 0x9d, 0x72, 0x40, 0x7a, 0x4c, 0x0b, 0x76, 0x2a, 0xfb, 0x56}}}, +{{{0x33, 0xa7, 0x90, 0x7c, 0xc3, 0x6f, 0x17, 0xa5, 0xa0, 0x67, 0x72, 0x17, 0xea, 0x7e, 0x63, 0x14, 0x83, 0xde, 0xc1, 0x71, 0x2d, 0x41, 0x32, 0x7a, 0xf3, 0xd1, 0x2b, 0xd8, 0x2a, 0xa6, 0x46, 0x36}} , + {{0xac, 0xcc, 0x6b, 0x7c, 0xf9, 0xb8, 0x8b, 0x08, 0x5c, 0xd0, 0x7d, 0x8f, 0x73, 0xea, 0x20, 0xda, 0x86, 0xca, 0x00, 0xc7, 0xad, 0x73, 0x4d, 0xe9, 0xe8, 0xa9, 0xda, 0x1f, 0x03, 0x06, 0xdd, 0x24}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9c, 0xb2, 0x61, 0x0a, 0x98, 0x2a, 0xa5, 0xd7, 0xee, 0xa9, 0xac, 0x65, 0xcb, 0x0a, 0x1e, 0xe2, 0xbe, 0xdc, 0x85, 0x59, 0x0f, 0x9c, 0xa6, 0x57, 0x34, 0xa5, 0x87, 0xeb, 0x7b, 0x1e, 0x0c, 0x3c}} , + {{0x2f, 0xbd, 0x84, 0x63, 0x0d, 0xb5, 0xa0, 0xf0, 0x4b, 0x9e, 0x93, 0xc6, 0x34, 0x9a, 0x34, 0xff, 0x73, 0x19, 0x2f, 0x6e, 0x54, 0x45, 0x2c, 0x92, 0x31, 0x76, 0x34, 0xf1, 0xb2, 0x26, 0xe8, 0x74}}}, +{{{0x0a, 0x67, 0x90, 0x6d, 0x0c, 0x4c, 0xcc, 0xc0, 0xe6, 0xbd, 0xa7, 0x5e, 0x55, 0x8c, 0xcd, 0x58, 0x9b, 0x11, 0xa2, 0xbb, 0x4b, 0xb1, 0x43, 0x04, 0x3c, 0x55, 0xed, 0x23, 0xfe, 0xcd, 0xb1, 0x53}} , + {{0x05, 0xfb, 0x75, 0xf5, 0x01, 0xaf, 0x38, 0x72, 0x58, 0xfc, 0x04, 0x29, 0x34, 0x7a, 0x67, 0xa2, 0x08, 0x50, 0x6e, 0xd0, 0x2b, 0x73, 0xd5, 0xb8, 0xe4, 0x30, 0x96, 0xad, 0x45, 0xdf, 0xa6, 0x5c}}}, +{{{0x0d, 0x88, 0x1a, 0x90, 0x7e, 0xdc, 0xd8, 0xfe, 0xc1, 0x2f, 0x5d, 0x67, 0xee, 0x67, 0x2f, 0xed, 0x6f, 0x55, 0x43, 0x5f, 0x87, 0x14, 0x35, 0x42, 0xd3, 0x75, 0xae, 0xd5, 0xd3, 0x85, 0x1a, 0x76}} , + {{0x87, 0xc8, 0xa0, 0x6e, 0xe1, 0xb0, 0xad, 0x6a, 0x4a, 0x34, 0x71, 0xed, 0x7c, 0xd6, 0x44, 0x03, 0x65, 0x4a, 0x5c, 0x5c, 0x04, 0xf5, 0x24, 0x3f, 0xb0, 0x16, 0x5e, 0x8c, 0xb2, 0xd2, 0xc5, 0x20}}}, +{{{0x98, 0x83, 0xc2, 0x37, 0xa0, 0x41, 0xa8, 0x48, 0x5c, 0x5f, 0xbf, 0xc8, 0xfa, 0x24, 0xe0, 0x59, 0x2c, 0xbd, 0xf6, 0x81, 0x7e, 0x88, 0xe6, 0xca, 0x04, 0xd8, 0x5d, 0x60, 0xbb, 0x74, 0xa7, 0x0b}} , + {{0x21, 0x13, 0x91, 0xbf, 0x77, 0x7a, 0x33, 0xbc, 0xe9, 0x07, 0x39, 0x0a, 0xdd, 0x7d, 0x06, 0x10, 0x9a, 0xee, 0x47, 0x73, 0x1b, 0x15, 0x5a, 0xfb, 0xcd, 0x4d, 0xd0, 0xd2, 0x3a, 0x01, 0xba, 0x54}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x48, 0xd5, 0x39, 0x4a, 0x0b, 0x20, 0x6a, 0x43, 0xa0, 0x07, 0x82, 0x5e, 0x49, 0x7c, 0xc9, 0x47, 0xf1, 0x7c, 0x37, 0xb9, 0x23, 0xef, 0x6b, 0x46, 0x45, 0x8c, 0x45, 0x76, 0xdf, 0x14, 0x6b, 0x6e}} , + {{0x42, 0xc9, 0xca, 0x29, 0x4c, 0x76, 0x37, 0xda, 0x8a, 0x2d, 0x7c, 0x3a, 0x58, 0xf2, 0x03, 0xb4, 0xb5, 0xb9, 0x1a, 0x13, 0x2d, 0xde, 0x5f, 0x6b, 0x9d, 0xba, 0x52, 0xc9, 0x5d, 0xb3, 0xf3, 0x30}}}, +{{{0x4c, 0x6f, 0xfe, 0x6b, 0x0c, 0x62, 0xd7, 0x48, 0x71, 0xef, 0xb1, 0x85, 0x79, 0xc0, 0xed, 0x24, 0xb1, 0x08, 0x93, 0x76, 0x8e, 0xf7, 0x38, 0x8e, 0xeb, 0xfe, 0x80, 0x40, 0xaf, 0x90, 0x64, 0x49}} , + {{0x4a, 0x88, 0xda, 0xc1, 0x98, 0x44, 0x3c, 0x53, 0x4e, 0xdb, 0x4b, 0xb9, 0x12, 0x5f, 0xcd, 0x08, 0x04, 0xef, 0x75, 0xe7, 0xb1, 0x3a, 0xe5, 0x07, 0xfa, 0xca, 0x65, 0x7b, 0x72, 0x10, 0x64, 0x7f}}}, +{{{0x3d, 0x81, 0xf0, 0xeb, 0x16, 0xfd, 0x58, 0x33, 0x8d, 0x7c, 0x1a, 0xfb, 0x20, 0x2c, 0x8a, 0xee, 0x90, 0xbb, 0x33, 0x6d, 0x45, 0xe9, 0x8e, 0x99, 0x85, 0xe1, 0x08, 0x1f, 0xc5, 0xf1, 0xb5, 0x46}} , + {{0xe4, 0xe7, 0x43, 0x4b, 0xa0, 0x3f, 0x2b, 0x06, 0xba, 0x17, 0xae, 0x3d, 0xe6, 0xce, 0xbd, 0xb8, 0xed, 0x74, 0x11, 0x35, 0xec, 0x96, 0xfe, 0x31, 0xe3, 0x0e, 0x7a, 0x4e, 0xc9, 0x1d, 0xcb, 0x20}}}, +{{{0xe0, 0x67, 0xe9, 0x7b, 0xdb, 0x96, 0x5c, 0xb0, 0x32, 0xd0, 0x59, 0x31, 0x90, 0xdc, 0x92, 0x97, 0xac, 0x09, 0x38, 0x31, 0x0f, 0x7e, 0xd6, 0x5d, 0xd0, 0x06, 0xb6, 0x1f, 0xea, 0xf0, 0x5b, 0x07}} , + {{0x81, 0x9f, 0xc7, 0xde, 0x6b, 0x41, 0x22, 0x35, 0x14, 0x67, 0x77, 0x3e, 0x90, 0x81, 0xb0, 0xd9, 0x85, 0x4c, 0xca, 0x9b, 0x3f, 0x04, 0x59, 0xd6, 0xaa, 0x17, 0xc3, 0x88, 0x34, 0x37, 0xba, 0x43}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x4c, 0xb6, 0x69, 0xc8, 0x81, 0x95, 0x94, 0x33, 0x92, 0x34, 0xe9, 0x3c, 0x84, 0x0d, 0x3d, 0x5a, 0x37, 0x9c, 0x22, 0xa0, 0xaa, 0x65, 0xce, 0xb4, 0xc2, 0x2d, 0x66, 0x67, 0x02, 0xff, 0x74, 0x10}} , + {{0x22, 0xb0, 0xd5, 0xe6, 0xc7, 0xef, 0xb1, 0xa7, 0x13, 0xda, 0x60, 0xb4, 0x80, 0xc1, 0x42, 0x7d, 0x10, 0x70, 0x97, 0x04, 0x4d, 0xda, 0x23, 0x89, 0xc2, 0x0e, 0x68, 0xcb, 0xde, 0xe0, 0x9b, 0x29}}}, +{{{0x33, 0xfe, 0x42, 0x2a, 0x36, 0x2b, 0x2e, 0x36, 0x64, 0x5c, 0x8b, 0xcc, 0x81, 0x6a, 0x15, 0x08, 0xa1, 0x27, 0xe8, 0x57, 0xe5, 0x78, 0x8e, 0xf2, 0x58, 0x19, 0x12, 0x42, 0xae, 0xc4, 0x63, 0x3e}} , + {{0x78, 0x96, 0x9c, 0xa7, 0xca, 0x80, 0xae, 0x02, 0x85, 0xb1, 0x7c, 0x04, 0x5c, 0xc1, 0x5b, 0x26, 0xc1, 0xba, 0xed, 0xa5, 0x59, 0x70, 0x85, 0x8c, 0x8c, 0xe8, 0x87, 0xac, 0x6a, 0x28, 0x99, 0x35}}}, +{{{0x9f, 0x04, 0x08, 0x28, 0xbe, 0x87, 0xda, 0x80, 0x28, 0x38, 0xde, 0x9f, 0xcd, 0xe4, 0xe3, 0x62, 0xfb, 0x2e, 0x46, 0x8d, 0x01, 0xb3, 0x06, 0x51, 0xd4, 0x19, 0x3b, 0x11, 0xfa, 0xe2, 0xad, 0x1e}} , + {{0xa0, 0x20, 0x99, 0x69, 0x0a, 0xae, 0xa3, 0x70, 0x4e, 0x64, 0x80, 0xb7, 0x85, 0x9c, 0x87, 0x54, 0x43, 0x43, 0x55, 0x80, 0x6d, 0x8d, 0x7c, 0xa9, 0x64, 0xca, 0x6c, 0x2e, 0x21, 0xd8, 0xc8, 0x6c}}}, +{{{0x91, 0x4a, 0x07, 0xad, 0x08, 0x75, 0xc1, 0x4f, 0xa4, 0xb2, 0xc3, 0x6f, 0x46, 0x3e, 0xb1, 0xce, 0x52, 0xab, 0x67, 0x09, 0x54, 0x48, 0x6b, 0x6c, 0xd7, 0x1d, 0x71, 0x76, 0xcb, 0xff, 0xdd, 0x31}} , + {{0x36, 0x88, 0xfa, 0xfd, 0xf0, 0x36, 0x6f, 0x07, 0x74, 0x88, 0x50, 0xd0, 0x95, 0x38, 0x4a, 0x48, 0x2e, 0x07, 0x64, 0x97, 0x11, 0x76, 0x01, 0x1a, 0x27, 0x4d, 0x8e, 0x25, 0x9a, 0x9b, 0x1c, 0x22}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xbe, 0x57, 0xbd, 0x0e, 0x0f, 0xac, 0x5e, 0x76, 0xa3, 0x71, 0xad, 0x2b, 0x10, 0x45, 0x02, 0xec, 0x59, 0xd5, 0x5d, 0xa9, 0x44, 0xcc, 0x25, 0x4c, 0xb3, 0x3c, 0x5b, 0x69, 0x07, 0x55, 0x26, 0x6b}} , + {{0x30, 0x6b, 0xd4, 0xa7, 0x51, 0x29, 0xe3, 0xf9, 0x7a, 0x75, 0x2a, 0x82, 0x2f, 0xd6, 0x1d, 0x99, 0x2b, 0x80, 0xd5, 0x67, 0x1e, 0x15, 0x9d, 0xca, 0xfd, 0xeb, 0xac, 0x97, 0x35, 0x09, 0x7f, 0x3f}}}, +{{{0x35, 0x0d, 0x34, 0x0a, 0xb8, 0x67, 0x56, 0x29, 0x20, 0xf3, 0x19, 0x5f, 0xe2, 0x83, 0x42, 0x73, 0x53, 0xa8, 0xc5, 0x02, 0x19, 0x33, 0xb4, 0x64, 0xbd, 0xc3, 0x87, 0x8c, 0xd7, 0x76, 0xed, 0x25}} , + {{0x47, 0x39, 0x37, 0x76, 0x0d, 0x1d, 0x0c, 0xf5, 0x5a, 0x6d, 0x43, 0x88, 0x99, 0x15, 0xb4, 0x52, 0x0f, 0x2a, 0xb3, 0xb0, 0x3f, 0xa6, 0xb3, 0x26, 0xb3, 0xc7, 0x45, 0xf5, 0x92, 0x5f, 0x9b, 0x17}}}, +{{{0x9d, 0x23, 0xbd, 0x15, 0xfe, 0x52, 0x52, 0x15, 0x26, 0x79, 0x86, 0xba, 0x06, 0x56, 0x66, 0xbb, 0x8c, 0x2e, 0x10, 0x11, 0xd5, 0x4a, 0x18, 0x52, 0xda, 0x84, 0x44, 0xf0, 0x3e, 0xe9, 0x8c, 0x35}} , + {{0xad, 0xa0, 0x41, 0xec, 0xc8, 0x4d, 0xb9, 0xd2, 0x6e, 0x96, 0x4e, 0x5b, 0xc5, 0xc2, 0xa0, 0x1b, 0xcf, 0x0c, 0xbf, 0x17, 0x66, 0x57, 0xc1, 0x17, 0x90, 0x45, 0x71, 0xc2, 0xe1, 0x24, 0xeb, 0x27}}}, +{{{0x2c, 0xb9, 0x42, 0xa4, 0xaf, 0x3b, 0x42, 0x0e, 0xc2, 0x0f, 0xf2, 0xea, 0x83, 0xaf, 0x9a, 0x13, 0x17, 0xb0, 0xbd, 0x89, 0x17, 0xe3, 0x72, 0xcb, 0x0e, 0x76, 0x7e, 0x41, 0x63, 0x04, 0x88, 0x71}} , + {{0x75, 0x78, 0x38, 0x86, 0x57, 0xdd, 0x9f, 0xee, 0x54, 0x70, 0x65, 0xbf, 0xf1, 0x2c, 0xe0, 0x39, 0x0d, 0xe3, 0x89, 0xfd, 0x8e, 0x93, 0x4f, 0x43, 0xdc, 0xd5, 0x5b, 0xde, 0xf9, 0x98, 0xe5, 0x7b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xe7, 0x3b, 0x65, 0x11, 0xdf, 0xb2, 0xf2, 0x63, 0x94, 0x12, 0x6f, 0x5c, 0x9e, 0x77, 0xc1, 0xb6, 0xd8, 0xab, 0x58, 0x7a, 0x1d, 0x95, 0x73, 0xdd, 0xe7, 0xe3, 0x6f, 0xf2, 0x03, 0x1d, 0xdb, 0x76}} , + {{0xae, 0x06, 0x4e, 0x2c, 0x52, 0x1b, 0xbc, 0x5a, 0x5a, 0xa5, 0xbe, 0x27, 0xbd, 0xeb, 0xe1, 0x14, 0x17, 0x68, 0x26, 0x07, 0x03, 0xd1, 0x18, 0x0b, 0xdf, 0xf1, 0x06, 0x5c, 0xa6, 0x1b, 0xb9, 0x24}}}, +{{{0xc5, 0x66, 0x80, 0x13, 0x0e, 0x48, 0x8c, 0x87, 0x31, 0x84, 0xb4, 0x60, 0xed, 0xc5, 0xec, 0xb6, 0xc5, 0x05, 0x33, 0x5f, 0x2f, 0x7d, 0x40, 0xb6, 0x32, 0x1d, 0x38, 0x74, 0x1b, 0xf1, 0x09, 0x3d}} , + {{0xd4, 0x69, 0x82, 0xbc, 0x8d, 0xf8, 0x34, 0x36, 0x75, 0x55, 0x18, 0x55, 0x58, 0x3c, 0x79, 0xaf, 0x26, 0x80, 0xab, 0x9b, 0x95, 0x00, 0xf1, 0xcb, 0xda, 0xc1, 0x9f, 0xf6, 0x2f, 0xa2, 0xf4, 0x45}}}, +{{{0x17, 0xbe, 0xeb, 0x85, 0xed, 0x9e, 0xcd, 0x56, 0xf5, 0x17, 0x45, 0x42, 0xb4, 0x1f, 0x44, 0x4c, 0x05, 0x74, 0x15, 0x47, 0x00, 0xc6, 0x6a, 0x3d, 0x24, 0x09, 0x0d, 0x58, 0xb1, 0x42, 0xd7, 0x04}} , + {{0x8d, 0xbd, 0xa3, 0xc4, 0x06, 0x9b, 0x1f, 0x90, 0x58, 0x60, 0x74, 0xb2, 0x00, 0x3b, 0x3c, 0xd2, 0xda, 0x82, 0xbb, 0x10, 0x90, 0x69, 0x92, 0xa9, 0xb4, 0x30, 0x81, 0xe3, 0x7c, 0xa8, 0x89, 0x45}}}, +{{{0x3f, 0xdc, 0x05, 0xcb, 0x41, 0x3c, 0xc8, 0x23, 0x04, 0x2c, 0x38, 0x99, 0xe3, 0x68, 0x55, 0xf9, 0xd3, 0x32, 0xc7, 0xbf, 0xfa, 0xd4, 0x1b, 0x5d, 0xde, 0xdc, 0x10, 0x42, 0xc0, 0x42, 0xd9, 0x75}} , + {{0x2d, 0xab, 0x35, 0x4e, 0x87, 0xc4, 0x65, 0x97, 0x67, 0x24, 0xa4, 0x47, 0xad, 0x3f, 0x8e, 0xf3, 0xcb, 0x31, 0x17, 0x77, 0xc5, 0xe2, 0xd7, 0x8f, 0x3c, 0xc1, 0xcd, 0x56, 0x48, 0xc1, 0x6c, 0x69}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x14, 0xae, 0x5f, 0x88, 0x7b, 0xa5, 0x90, 0xdf, 0x10, 0xb2, 0x8b, 0x5e, 0x24, 0x17, 0xc3, 0xa3, 0xd4, 0x0f, 0x92, 0x61, 0x1a, 0x19, 0x5a, 0xad, 0x76, 0xbd, 0xd8, 0x1c, 0xdd, 0xe0, 0x12, 0x6d}} , + {{0x8e, 0xbd, 0x70, 0x8f, 0x02, 0xa3, 0x24, 0x4d, 0x5a, 0x67, 0xc4, 0xda, 0xf7, 0x20, 0x0f, 0x81, 0x5b, 0x7a, 0x05, 0x24, 0x67, 0x83, 0x0b, 0x2a, 0x80, 0xe7, 0xfd, 0x74, 0x4b, 0x9e, 0x5c, 0x0d}}}, +{{{0x94, 0xd5, 0x5f, 0x1f, 0xa2, 0xfb, 0xeb, 0xe1, 0x07, 0x34, 0xf8, 0x20, 0xad, 0x81, 0x30, 0x06, 0x2d, 0xa1, 0x81, 0x95, 0x36, 0xcf, 0x11, 0x0b, 0xaf, 0xc1, 0x2b, 0x9a, 0x6c, 0x55, 0xc1, 0x16}} , + {{0x36, 0x4f, 0xf1, 0x5e, 0x74, 0x35, 0x13, 0x28, 0xd7, 0x11, 0xcf, 0xb8, 0xde, 0x93, 0xb3, 0x05, 0xb8, 0xb5, 0x73, 0xe9, 0xeb, 0xad, 0x19, 0x1e, 0x89, 0x0f, 0x8b, 0x15, 0xd5, 0x8c, 0xe3, 0x23}}}, +{{{0x33, 0x79, 0xe7, 0x18, 0xe6, 0x0f, 0x57, 0x93, 0x15, 0xa0, 0xa7, 0xaa, 0xc4, 0xbf, 0x4f, 0x30, 0x74, 0x95, 0x5e, 0x69, 0x4a, 0x5b, 0x45, 0xe4, 0x00, 0xeb, 0x23, 0x74, 0x4c, 0xdf, 0x6b, 0x45}} , + {{0x97, 0x29, 0x6c, 0xc4, 0x42, 0x0b, 0xdd, 0xc0, 0x29, 0x5c, 0x9b, 0x34, 0x97, 0xd0, 0xc7, 0x79, 0x80, 0x63, 0x74, 0xe4, 0x8e, 0x37, 0xb0, 0x2b, 0x7c, 0xe8, 0x68, 0x6c, 0xc3, 0x82, 0x97, 0x57}}}, +{{{0x22, 0xbe, 0x83, 0xb6, 0x4b, 0x80, 0x6b, 0x43, 0x24, 0x5e, 0xef, 0x99, 0x9b, 0xa8, 0xfc, 0x25, 0x8d, 0x3b, 0x03, 0x94, 0x2b, 0x3e, 0xe7, 0x95, 0x76, 0x9b, 0xcc, 0x15, 0xdb, 0x32, 0xe6, 0x66}} , + {{0x84, 0xf0, 0x4a, 0x13, 0xa6, 0xd6, 0xfa, 0x93, 0x46, 0x07, 0xf6, 0x7e, 0x5c, 0x6d, 0x5e, 0xf6, 0xa6, 0xe7, 0x48, 0xf0, 0x06, 0xea, 0xff, 0x90, 0xc1, 0xcc, 0x4c, 0x19, 0x9c, 0x3c, 0x4e, 0x53}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2a, 0x50, 0xe3, 0x07, 0x15, 0x59, 0xf2, 0x8b, 0x81, 0xf2, 0xf3, 0xd3, 0x6c, 0x99, 0x8c, 0x70, 0x67, 0xec, 0xcc, 0xee, 0x9e, 0x59, 0x45, 0x59, 0x7d, 0x47, 0x75, 0x69, 0xf5, 0x24, 0x93, 0x5d}} , + {{0x6a, 0x4f, 0x1b, 0xbe, 0x6b, 0x30, 0xcf, 0x75, 0x46, 0xe3, 0x7b, 0x9d, 0xfc, 0xcd, 0xd8, 0x5c, 0x1f, 0xb4, 0xc8, 0xe2, 0x24, 0xec, 0x1a, 0x28, 0x05, 0x32, 0x57, 0xfd, 0x3c, 0x5a, 0x98, 0x10}}}, +{{{0xa3, 0xdb, 0xf7, 0x30, 0xd8, 0xc2, 0x9a, 0xe1, 0xd3, 0xce, 0x22, 0xe5, 0x80, 0x1e, 0xd9, 0xe4, 0x1f, 0xab, 0xc0, 0x71, 0x1a, 0x86, 0x0e, 0x27, 0x99, 0x5b, 0xfa, 0x76, 0x99, 0xb0, 0x08, 0x3c}} , + {{0x2a, 0x93, 0xd2, 0x85, 0x1b, 0x6a, 0x5d, 0xa6, 0xee, 0xd1, 0xd1, 0x33, 0xbd, 0x6a, 0x36, 0x73, 0x37, 0x3a, 0x44, 0xb4, 0xec, 0xa9, 0x7a, 0xde, 0x83, 0x40, 0xd7, 0xdf, 0x28, 0xba, 0xa2, 0x30}}}, +{{{0xd3, 0xb5, 0x6d, 0x05, 0x3f, 0x9f, 0xf3, 0x15, 0x8d, 0x7c, 0xca, 0xc9, 0xfc, 0x8a, 0x7c, 0x94, 0xb0, 0x63, 0x36, 0x9b, 0x78, 0xd1, 0x91, 0x1f, 0x93, 0xd8, 0x57, 0x43, 0xde, 0x76, 0xa3, 0x43}} , + {{0x9b, 0x35, 0xe2, 0xa9, 0x3d, 0x32, 0x1e, 0xbb, 0x16, 0x28, 0x70, 0xe9, 0x45, 0x2f, 0x8f, 0x70, 0x7f, 0x08, 0x7e, 0x53, 0xc4, 0x7a, 0xbf, 0xf7, 0xe1, 0xa4, 0x6a, 0xd8, 0xac, 0x64, 0x1b, 0x11}}}, +{{{0xb2, 0xeb, 0x47, 0x46, 0x18, 0x3e, 0x1f, 0x99, 0x0c, 0xcc, 0xf1, 0x2c, 0xe0, 0xe7, 0x8f, 0xe0, 0x01, 0x7e, 0x65, 0xb8, 0x0c, 0xd0, 0xfb, 0xc8, 0xb9, 0x90, 0x98, 0x33, 0x61, 0x3b, 0xd8, 0x27}} , + {{0xa0, 0xbe, 0x72, 0x3a, 0x50, 0x4b, 0x74, 0xab, 0x01, 0xc8, 0x93, 0xc5, 0xe4, 0xc7, 0x08, 0x6c, 0xb4, 0xca, 0xee, 0xeb, 0x8e, 0xd7, 0x4e, 0x26, 0xc6, 0x1d, 0xe2, 0x71, 0xaf, 0x89, 0xa0, 0x2a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x98, 0x0b, 0xe4, 0xde, 0xdb, 0xa8, 0xfa, 0x82, 0x74, 0x06, 0x52, 0x6d, 0x08, 0x52, 0x8a, 0xff, 0x62, 0xc5, 0x6a, 0x44, 0x0f, 0x51, 0x8c, 0x1f, 0x6e, 0xb6, 0xc6, 0x2c, 0x81, 0xd3, 0x76, 0x46}} , + {{0xf4, 0x29, 0x74, 0x2e, 0x80, 0xa7, 0x1a, 0x8f, 0xf6, 0xbd, 0xd6, 0x8e, 0xbf, 0xc1, 0x95, 0x2a, 0xeb, 0xa0, 0x7f, 0x45, 0xa0, 0x50, 0x14, 0x05, 0xb1, 0x57, 0x4c, 0x74, 0xb7, 0xe2, 0x89, 0x7d}}}, +{{{0x07, 0xee, 0xa7, 0xad, 0xb7, 0x09, 0x0b, 0x49, 0x4e, 0xbf, 0xca, 0xe5, 0x21, 0xe6, 0xe6, 0xaf, 0xd5, 0x67, 0xf3, 0xce, 0x7e, 0x7c, 0x93, 0x7b, 0x5a, 0x10, 0x12, 0x0e, 0x6c, 0x06, 0x11, 0x75}} , + {{0xd5, 0xfc, 0x86, 0xa3, 0x3b, 0xa3, 0x3e, 0x0a, 0xfb, 0x0b, 0xf7, 0x36, 0xb1, 0x5b, 0xda, 0x70, 0xb7, 0x00, 0xa7, 0xda, 0x88, 0x8f, 0x84, 0xa8, 0xbc, 0x1c, 0x39, 0xb8, 0x65, 0xf3, 0x4d, 0x60}}}, +{{{0x96, 0x9d, 0x31, 0xf4, 0xa2, 0xbe, 0x81, 0xb9, 0xa5, 0x59, 0x9e, 0xba, 0x07, 0xbe, 0x74, 0x58, 0xd8, 0xeb, 0xc5, 0x9f, 0x3d, 0xd1, 0xf4, 0xae, 0xce, 0x53, 0xdf, 0x4f, 0xc7, 0x2a, 0x89, 0x4d}} , + {{0x29, 0xd8, 0xf2, 0xaa, 0xe9, 0x0e, 0xf7, 0x2e, 0x5f, 0x9d, 0x8a, 0x5b, 0x09, 0xed, 0xc9, 0x24, 0x22, 0xf4, 0x0f, 0x25, 0x8f, 0x1c, 0x84, 0x6e, 0x34, 0x14, 0x6c, 0xea, 0xb3, 0x86, 0x5d, 0x04}}}, +{{{0x07, 0x98, 0x61, 0xe8, 0x6a, 0xd2, 0x81, 0x49, 0x25, 0xd5, 0x5b, 0x18, 0xc7, 0x35, 0x52, 0x51, 0xa4, 0x46, 0xad, 0x18, 0x0d, 0xc9, 0x5f, 0x18, 0x91, 0x3b, 0xb4, 0xc0, 0x60, 0x59, 0x8d, 0x66}} , + {{0x03, 0x1b, 0x79, 0x53, 0x6e, 0x24, 0xae, 0x57, 0xd9, 0x58, 0x09, 0x85, 0x48, 0xa2, 0xd3, 0xb5, 0xe2, 0x4d, 0x11, 0x82, 0xe6, 0x86, 0x3c, 0xe9, 0xb1, 0x00, 0x19, 0xc2, 0x57, 0xf7, 0x66, 0x7a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0f, 0xe3, 0x89, 0x03, 0xd7, 0x22, 0x95, 0x9f, 0xca, 0xb4, 0x8d, 0x9e, 0x6d, 0x97, 0xff, 0x8d, 0x21, 0x59, 0x07, 0xef, 0x03, 0x2d, 0x5e, 0xf8, 0x44, 0x46, 0xe7, 0x85, 0x80, 0xc5, 0x89, 0x50}} , + {{0x8b, 0xd8, 0x53, 0x86, 0x24, 0x86, 0x29, 0x52, 0x01, 0xfa, 0x20, 0xc3, 0x4e, 0x95, 0xcb, 0xad, 0x7b, 0x34, 0x94, 0x30, 0xb7, 0x7a, 0xfa, 0x96, 0x41, 0x60, 0x2b, 0xcb, 0x59, 0xb9, 0xca, 0x50}}}, +{{{0xc2, 0x5b, 0x9b, 0x78, 0x23, 0x1b, 0x3a, 0x88, 0x94, 0x5f, 0x0a, 0x9b, 0x98, 0x2b, 0x6e, 0x53, 0x11, 0xf6, 0xff, 0xc6, 0x7d, 0x42, 0xcc, 0x02, 0x80, 0x40, 0x0d, 0x1e, 0xfb, 0xaf, 0x61, 0x07}} , + {{0xb0, 0xe6, 0x2f, 0x81, 0x70, 0xa1, 0x2e, 0x39, 0x04, 0x7c, 0xc4, 0x2c, 0x87, 0x45, 0x4a, 0x5b, 0x69, 0x97, 0xac, 0x6d, 0x2c, 0x10, 0x42, 0x7c, 0x3b, 0x15, 0x70, 0x60, 0x0e, 0x11, 0x6d, 0x3a}}}, +{{{0x9b, 0x18, 0x80, 0x5e, 0xdb, 0x05, 0xbd, 0xc6, 0xb7, 0x3c, 0xc2, 0x40, 0x4d, 0x5d, 0xce, 0x97, 0x8a, 0x34, 0x15, 0xab, 0x28, 0x5d, 0x10, 0xf0, 0x37, 0x0c, 0xcc, 0x16, 0xfa, 0x1f, 0x33, 0x0d}} , + {{0x19, 0xf9, 0x35, 0xaa, 0x59, 0x1a, 0x0c, 0x5c, 0x06, 0xfc, 0x6a, 0x0b, 0x97, 0x53, 0x36, 0xfc, 0x2a, 0xa5, 0x5a, 0x9b, 0x30, 0xef, 0x23, 0xaf, 0x39, 0x5d, 0x9a, 0x6b, 0x75, 0x57, 0x48, 0x0b}}}, +{{{0x26, 0xdc, 0x76, 0x3b, 0xfc, 0xf9, 0x9c, 0x3f, 0x89, 0x0b, 0x62, 0x53, 0xaf, 0x83, 0x01, 0x2e, 0xbc, 0x6a, 0xc6, 0x03, 0x0d, 0x75, 0x2a, 0x0d, 0xe6, 0x94, 0x54, 0xcf, 0xb3, 0xe5, 0x96, 0x25}} , + {{0xfe, 0x82, 0xb1, 0x74, 0x31, 0x8a, 0xa7, 0x6f, 0x56, 0xbd, 0x8d, 0xf4, 0xe0, 0x94, 0x51, 0x59, 0xde, 0x2c, 0x5a, 0xf4, 0x84, 0x6b, 0x4a, 0x88, 0x93, 0xc0, 0x0c, 0x9a, 0xac, 0xa7, 0xa0, 0x68}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0x0d, 0xd6, 0xc7, 0x23, 0x47, 0x10, 0xad, 0xc7, 0x08, 0x5c, 0x87, 0x87, 0x93, 0x98, 0x18, 0xb8, 0xd3, 0x9c, 0xac, 0x5a, 0x3d, 0xc5, 0x75, 0xf8, 0x49, 0x32, 0x14, 0xcc, 0x51, 0x96, 0x24}} , + {{0x65, 0x9c, 0x5d, 0xf0, 0x37, 0x04, 0xf0, 0x34, 0x69, 0x2a, 0xf0, 0xa5, 0x64, 0xca, 0xde, 0x2b, 0x5b, 0x15, 0x10, 0xd2, 0xab, 0x06, 0xdd, 0xc4, 0xb0, 0xb6, 0x5b, 0xc1, 0x17, 0xdf, 0x8f, 0x02}}}, +{{{0xbd, 0x59, 0x3d, 0xbf, 0x5c, 0x31, 0x44, 0x2c, 0x32, 0x94, 0x04, 0x60, 0x84, 0x0f, 0xad, 0x00, 0xb6, 0x8f, 0xc9, 0x1d, 0xcc, 0x5c, 0xa2, 0x49, 0x0e, 0x50, 0x91, 0x08, 0x9a, 0x43, 0x55, 0x05}} , + {{0x5d, 0x93, 0x55, 0xdf, 0x9b, 0x12, 0x19, 0xec, 0x93, 0x85, 0x42, 0x9e, 0x66, 0x0f, 0x9d, 0xaf, 0x99, 0xaf, 0x26, 0x89, 0xbc, 0x61, 0xfd, 0xff, 0xce, 0x4b, 0xf4, 0x33, 0x95, 0xc9, 0x35, 0x58}}}, +{{{0x12, 0x55, 0xf9, 0xda, 0xcb, 0x44, 0xa7, 0xdc, 0x57, 0xe2, 0xf9, 0x9a, 0xe6, 0x07, 0x23, 0x60, 0x54, 0xa7, 0x39, 0xa5, 0x9b, 0x84, 0x56, 0x6e, 0xaa, 0x8b, 0x8f, 0xb0, 0x2c, 0x87, 0xaf, 0x67}} , + {{0x00, 0xa9, 0x4c, 0xb2, 0x12, 0xf8, 0x32, 0xa8, 0x7a, 0x00, 0x4b, 0x49, 0x32, 0xba, 0x1f, 0x5d, 0x44, 0x8e, 0x44, 0x7a, 0xdc, 0x11, 0xfb, 0x39, 0x08, 0x57, 0x87, 0xa5, 0x12, 0x42, 0x93, 0x0e}}}, +{{{0x17, 0xb4, 0xae, 0x72, 0x59, 0xd0, 0xaa, 0xa8, 0x16, 0x8b, 0x63, 0x11, 0xb3, 0x43, 0x04, 0xda, 0x0c, 0xa8, 0xb7, 0x68, 0xdd, 0x4e, 0x54, 0xe7, 0xaf, 0x5d, 0x5d, 0x05, 0x76, 0x36, 0xec, 0x0d}} , + {{0x6d, 0x7c, 0x82, 0x32, 0x38, 0x55, 0x57, 0x74, 0x5b, 0x7d, 0xc3, 0xc4, 0xfb, 0x06, 0x29, 0xf0, 0x13, 0x55, 0x54, 0xc6, 0xa7, 0xdc, 0x4c, 0x9f, 0x98, 0x49, 0x20, 0xa8, 0xc3, 0x8d, 0xfa, 0x48}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x87, 0x47, 0x9d, 0xe9, 0x25, 0xd5, 0xe3, 0x47, 0x78, 0xdf, 0x85, 0xa7, 0x85, 0x5e, 0x7a, 0x4c, 0x5f, 0x79, 0x1a, 0xf3, 0xa2, 0xb2, 0x28, 0xa0, 0x9c, 0xdd, 0x30, 0x40, 0xd4, 0x38, 0xbd, 0x28}} , + {{0xfc, 0xbb, 0xd5, 0x78, 0x6d, 0x1d, 0xd4, 0x99, 0xb4, 0xaa, 0x44, 0x44, 0x7a, 0x1b, 0xd8, 0xfe, 0xb4, 0x99, 0xb9, 0xcc, 0xe7, 0xc4, 0xd3, 0x3a, 0x73, 0x83, 0x41, 0x5c, 0x40, 0xd7, 0x2d, 0x55}}}, +{{{0x26, 0xe1, 0x7b, 0x5f, 0xe5, 0xdc, 0x3f, 0x7d, 0xa1, 0xa7, 0x26, 0x44, 0x22, 0x23, 0xc0, 0x8f, 0x7d, 0xf1, 0xb5, 0x11, 0x47, 0x7b, 0x19, 0xd4, 0x75, 0x6f, 0x1e, 0xa5, 0x27, 0xfe, 0xc8, 0x0e}} , + {{0xd3, 0x11, 0x3d, 0xab, 0xef, 0x2c, 0xed, 0xb1, 0x3d, 0x7c, 0x32, 0x81, 0x6b, 0xfe, 0xf8, 0x1c, 0x3c, 0x7b, 0xc0, 0x61, 0xdf, 0xb8, 0x75, 0x76, 0x7f, 0xaa, 0xd8, 0x93, 0xaf, 0x3d, 0xe8, 0x3d}}}, +{{{0xfd, 0x5b, 0x4e, 0x8d, 0xb6, 0x7e, 0x82, 0x9b, 0xef, 0xce, 0x04, 0x69, 0x51, 0x52, 0xff, 0xef, 0xa0, 0x52, 0xb5, 0x79, 0x17, 0x5e, 0x2f, 0xde, 0xd6, 0x3c, 0x2d, 0xa0, 0x43, 0xb4, 0x0b, 0x19}} , + {{0xc0, 0x61, 0x48, 0x48, 0x17, 0xf4, 0x9e, 0x18, 0x51, 0x2d, 0xea, 0x2f, 0xf2, 0xf2, 0xe0, 0xa3, 0x14, 0xb7, 0x8b, 0x3a, 0x30, 0xf5, 0x81, 0xc1, 0x5d, 0x71, 0x39, 0x62, 0x55, 0x1f, 0x60, 0x5a}}}, +{{{0xe5, 0x89, 0x8a, 0x76, 0x6c, 0xdb, 0x4d, 0x0a, 0x5b, 0x72, 0x9d, 0x59, 0x6e, 0x63, 0x63, 0x18, 0x7c, 0xe3, 0xfa, 0xe2, 0xdb, 0xa1, 0x8d, 0xf4, 0xa5, 0xd7, 0x16, 0xb2, 0xd0, 0xb3, 0x3f, 0x39}} , + {{0xce, 0x60, 0x09, 0x6c, 0xf5, 0x76, 0x17, 0x24, 0x80, 0x3a, 0x96, 0xc7, 0x94, 0x2e, 0xf7, 0x6b, 0xef, 0xb5, 0x05, 0x96, 0xef, 0xd3, 0x7b, 0x51, 0xda, 0x05, 0x44, 0x67, 0xbc, 0x07, 0x21, 0x4e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xe9, 0x73, 0x6f, 0x21, 0xb9, 0xde, 0x22, 0x7d, 0xeb, 0x97, 0x31, 0x10, 0xa3, 0xea, 0xe1, 0xc6, 0x37, 0xeb, 0x8f, 0x43, 0x58, 0xde, 0x41, 0x64, 0x0e, 0x3e, 0x07, 0x99, 0x3d, 0xf1, 0xdf, 0x1e}} , + {{0xf8, 0xad, 0x43, 0xc2, 0x17, 0x06, 0xe2, 0xe4, 0xa9, 0x86, 0xcd, 0x18, 0xd7, 0x78, 0xc8, 0x74, 0x66, 0xd2, 0x09, 0x18, 0xa5, 0xf1, 0xca, 0xa6, 0x62, 0x92, 0xc1, 0xcb, 0x00, 0xeb, 0x42, 0x2e}}}, +{{{0x7b, 0x34, 0x24, 0x4c, 0xcf, 0x38, 0xe5, 0x6c, 0x0a, 0x01, 0x2c, 0x22, 0x0b, 0x24, 0x38, 0xad, 0x24, 0x7e, 0x19, 0xf0, 0x6c, 0xf9, 0x31, 0xf4, 0x35, 0x11, 0xf6, 0x46, 0x33, 0x3a, 0x23, 0x59}} , + {{0x20, 0x0b, 0xa1, 0x08, 0x19, 0xad, 0x39, 0x54, 0xea, 0x3e, 0x23, 0x09, 0xb6, 0xe2, 0xd2, 0xbc, 0x4d, 0xfc, 0x9c, 0xf0, 0x13, 0x16, 0x22, 0x3f, 0xb9, 0xd2, 0x11, 0x86, 0x90, 0x55, 0xce, 0x3c}}}, +{{{0xc4, 0x0b, 0x4b, 0x62, 0x99, 0x37, 0x84, 0x3f, 0x74, 0xa2, 0xf9, 0xce, 0xe2, 0x0b, 0x0f, 0x2a, 0x3d, 0xa3, 0xe3, 0xdb, 0x5a, 0x9d, 0x93, 0xcc, 0xa5, 0xef, 0x82, 0x91, 0x1d, 0xe6, 0x6c, 0x68}} , + {{0xa3, 0x64, 0x17, 0x9b, 0x8b, 0xc8, 0x3a, 0x61, 0xe6, 0x9d, 0xc6, 0xed, 0x7b, 0x03, 0x52, 0x26, 0x9d, 0x3a, 0xb3, 0x13, 0xcc, 0x8a, 0xfd, 0x2c, 0x1a, 0x1d, 0xed, 0x13, 0xd0, 0x55, 0x57, 0x0e}}}, +{{{0x1a, 0xea, 0xbf, 0xfd, 0x4a, 0x3c, 0x8e, 0xec, 0x29, 0x7e, 0x77, 0x77, 0x12, 0x99, 0xd7, 0x84, 0xf9, 0x55, 0x7f, 0xf1, 0x8b, 0xb4, 0xd2, 0x95, 0xa3, 0x8d, 0xf0, 0x8a, 0xa7, 0xeb, 0x82, 0x4b}} , + {{0x2c, 0x28, 0xf4, 0x3a, 0xf6, 0xde, 0x0a, 0xe0, 0x41, 0x44, 0x23, 0xf8, 0x3f, 0x03, 0x64, 0x9f, 0xc3, 0x55, 0x4c, 0xc6, 0xc1, 0x94, 0x1c, 0x24, 0x5d, 0x5f, 0x92, 0x45, 0x96, 0x57, 0x37, 0x14}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc1, 0xcd, 0x90, 0x66, 0xb9, 0x76, 0xa0, 0x5b, 0xa5, 0x85, 0x75, 0x23, 0xf9, 0x89, 0xa5, 0x82, 0xb2, 0x6f, 0xb1, 0xeb, 0xc4, 0x69, 0x6f, 0x18, 0x5a, 0xed, 0x94, 0x3d, 0x9d, 0xd9, 0x2c, 0x1a}} , + {{0x35, 0xb0, 0xe6, 0x73, 0x06, 0xb7, 0x37, 0xe0, 0xf8, 0xb0, 0x22, 0xe8, 0xd2, 0xed, 0x0b, 0xef, 0xe6, 0xc6, 0x5a, 0x99, 0x9e, 0x1a, 0x9f, 0x04, 0x97, 0xe4, 0x4d, 0x0b, 0xbe, 0xba, 0x44, 0x40}}}, +{{{0xc1, 0x56, 0x96, 0x91, 0x5f, 0x1f, 0xbb, 0x54, 0x6f, 0x88, 0x89, 0x0a, 0xb2, 0xd6, 0x41, 0x42, 0x6a, 0x82, 0xee, 0x14, 0xaa, 0x76, 0x30, 0x65, 0x0f, 0x67, 0x39, 0xa6, 0x51, 0x7c, 0x49, 0x24}} , + {{0x35, 0xa3, 0x78, 0xd1, 0x11, 0x0f, 0x75, 0xd3, 0x70, 0x46, 0xdb, 0x20, 0x51, 0xcb, 0x92, 0x80, 0x54, 0x10, 0x74, 0x36, 0x86, 0xa9, 0xd7, 0xa3, 0x08, 0x78, 0xf1, 0x01, 0x29, 0xf8, 0x80, 0x3b}}}, +{{{0xdb, 0xa7, 0x9d, 0x9d, 0xbf, 0xa0, 0xcc, 0xed, 0x53, 0xa2, 0xa2, 0x19, 0x39, 0x48, 0x83, 0x19, 0x37, 0x58, 0xd1, 0x04, 0x28, 0x40, 0xf7, 0x8a, 0xc2, 0x08, 0xb7, 0xa5, 0x42, 0xcf, 0x53, 0x4c}} , + {{0xa7, 0xbb, 0xf6, 0x8e, 0xad, 0xdd, 0xf7, 0x90, 0xdd, 0x5f, 0x93, 0x89, 0xae, 0x04, 0x37, 0xe6, 0x9a, 0xb7, 0xe8, 0xc0, 0xdf, 0x16, 0x2a, 0xbf, 0xc4, 0x3a, 0x3c, 0x41, 0xd5, 0x89, 0x72, 0x5a}}}, +{{{0x1f, 0x96, 0xff, 0x34, 0x2c, 0x13, 0x21, 0xcb, 0x0a, 0x89, 0x85, 0xbe, 0xb3, 0x70, 0x9e, 0x1e, 0xde, 0x97, 0xaf, 0x96, 0x30, 0xf7, 0x48, 0x89, 0x40, 0x8d, 0x07, 0xf1, 0x25, 0xf0, 0x30, 0x58}} , + {{0x1e, 0xd4, 0x93, 0x57, 0xe2, 0x17, 0xe7, 0x9d, 0xab, 0x3c, 0x55, 0x03, 0x82, 0x2f, 0x2b, 0xdb, 0x56, 0x1e, 0x30, 0x2e, 0x24, 0x47, 0x6e, 0xe6, 0xff, 0x33, 0x24, 0x2c, 0x75, 0x51, 0xd4, 0x67}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2b, 0x06, 0xd9, 0xa1, 0x5d, 0xe1, 0xf4, 0xd1, 0x1e, 0x3c, 0x9a, 0xc6, 0x29, 0x2b, 0x13, 0x13, 0x78, 0xc0, 0xd8, 0x16, 0x17, 0x2d, 0x9e, 0xa9, 0xc9, 0x79, 0x57, 0xab, 0x24, 0x91, 0x92, 0x19}} , + {{0x69, 0xfb, 0xa1, 0x9c, 0xa6, 0x75, 0x49, 0x7d, 0x60, 0x73, 0x40, 0x42, 0xc4, 0x13, 0x0a, 0x95, 0x79, 0x1e, 0x04, 0x83, 0x94, 0x99, 0x9b, 0x1e, 0x0c, 0xe8, 0x1f, 0x54, 0xef, 0xcb, 0xc0, 0x52}}}, +{{{0x14, 0x89, 0x73, 0xa1, 0x37, 0x87, 0x6a, 0x7a, 0xcf, 0x1d, 0xd9, 0x2e, 0x1a, 0x67, 0xed, 0x74, 0xc0, 0xf0, 0x9c, 0x33, 0xdd, 0xdf, 0x08, 0xbf, 0x7b, 0xd1, 0x66, 0xda, 0xe6, 0xc9, 0x49, 0x08}} , + {{0xe9, 0xdd, 0x5e, 0x55, 0xb0, 0x0a, 0xde, 0x21, 0x4c, 0x5a, 0x2e, 0xd4, 0x80, 0x3a, 0x57, 0x92, 0x7a, 0xf1, 0xc4, 0x2c, 0x40, 0xaf, 0x2f, 0xc9, 0x92, 0x03, 0xe5, 0x5a, 0xbc, 0xdc, 0xf4, 0x09}}}, +{{{0xf3, 0xe1, 0x2b, 0x7c, 0x05, 0x86, 0x80, 0x93, 0x4a, 0xad, 0xb4, 0x8f, 0x7e, 0x99, 0x0c, 0xfd, 0xcd, 0xef, 0xd1, 0xff, 0x2c, 0x69, 0x34, 0x13, 0x41, 0x64, 0xcf, 0x3b, 0xd0, 0x90, 0x09, 0x1e}} , + {{0x9d, 0x45, 0xd6, 0x80, 0xe6, 0x45, 0xaa, 0xf4, 0x15, 0xaa, 0x5c, 0x34, 0x87, 0x99, 0xa2, 0x8c, 0x26, 0x84, 0x62, 0x7d, 0xb6, 0x29, 0xc0, 0x52, 0xea, 0xf5, 0x81, 0x18, 0x0f, 0x35, 0xa9, 0x0e}}}, +{{{0xe7, 0x20, 0x72, 0x7c, 0x6d, 0x94, 0x5f, 0x52, 0x44, 0x54, 0xe3, 0xf1, 0xb2, 0xb0, 0x36, 0x46, 0x0f, 0xae, 0x92, 0xe8, 0x70, 0x9d, 0x6e, 0x79, 0xb1, 0xad, 0x37, 0xa9, 0x5f, 0xc0, 0xde, 0x03}} , + {{0x15, 0x55, 0x37, 0xc6, 0x1c, 0x27, 0x1c, 0x6d, 0x14, 0x4f, 0xca, 0xa4, 0xc4, 0x88, 0x25, 0x46, 0x39, 0xfc, 0x5a, 0xe5, 0xfe, 0x29, 0x11, 0x69, 0xf5, 0x72, 0x84, 0x4d, 0x78, 0x9f, 0x94, 0x15}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xec, 0xd3, 0xff, 0x57, 0x0b, 0xb0, 0xb2, 0xdc, 0xf8, 0x4f, 0xe2, 0x12, 0xd5, 0x36, 0xbe, 0x6b, 0x09, 0x43, 0x6d, 0xa3, 0x4d, 0x90, 0x2d, 0xb8, 0x74, 0xe8, 0x71, 0x45, 0x19, 0x8b, 0x0c, 0x6a}} , + {{0xb8, 0x42, 0x1c, 0x03, 0xad, 0x2c, 0x03, 0x8e, 0xac, 0xd7, 0x98, 0x29, 0x13, 0xc6, 0x02, 0x29, 0xb5, 0xd4, 0xe7, 0xcf, 0xcc, 0x8b, 0x83, 0xec, 0x35, 0xc7, 0x9c, 0x74, 0xb7, 0xad, 0x85, 0x5f}}}, +{{{0x78, 0x84, 0xe1, 0x56, 0x45, 0x69, 0x68, 0x5a, 0x4f, 0xb8, 0xb1, 0x29, 0xff, 0x33, 0x03, 0x31, 0xb7, 0xcb, 0x96, 0x25, 0xe6, 0xe6, 0x41, 0x98, 0x1a, 0xbb, 0x03, 0x56, 0xf2, 0xb2, 0x91, 0x34}} , + {{0x2c, 0x6c, 0xf7, 0x66, 0xa4, 0x62, 0x6b, 0x39, 0xb3, 0xba, 0x65, 0xd3, 0x1c, 0xf8, 0x11, 0xaa, 0xbe, 0xdc, 0x80, 0x59, 0x87, 0xf5, 0x7b, 0xe5, 0xe3, 0xb3, 0x3e, 0x39, 0xda, 0xbe, 0x88, 0x09}}}, +{{{0x8b, 0xf1, 0xa0, 0xf5, 0xdc, 0x29, 0xb4, 0xe2, 0x07, 0xc6, 0x7a, 0x00, 0xd0, 0x89, 0x17, 0x51, 0xd4, 0xbb, 0xd4, 0x22, 0xea, 0x7e, 0x7d, 0x7c, 0x24, 0xea, 0xf2, 0xe8, 0x22, 0x12, 0x95, 0x06}} , + {{0xda, 0x7c, 0xa4, 0x0c, 0xf4, 0xba, 0x6e, 0xe1, 0x89, 0xb5, 0x59, 0xca, 0xf1, 0xc0, 0x29, 0x36, 0x09, 0x44, 0xe2, 0x7f, 0xd1, 0x63, 0x15, 0x99, 0xea, 0x25, 0xcf, 0x0c, 0x9d, 0xc0, 0x44, 0x6f}}}, +{{{0x1d, 0x86, 0x4e, 0xcf, 0xf7, 0x37, 0x10, 0x25, 0x8f, 0x12, 0xfb, 0x19, 0xfb, 0xe0, 0xed, 0x10, 0xc8, 0xe2, 0xf5, 0x75, 0xb1, 0x33, 0xc0, 0x96, 0x0d, 0xfb, 0x15, 0x6c, 0x0d, 0x07, 0x5f, 0x05}} , + {{0x69, 0x3e, 0x47, 0x97, 0x2c, 0xaf, 0x52, 0x7c, 0x78, 0x83, 0xad, 0x1b, 0x39, 0x82, 0x2f, 0x02, 0x6f, 0x47, 0xdb, 0x2a, 0xb0, 0xe1, 0x91, 0x99, 0x55, 0xb8, 0x99, 0x3a, 0xa0, 0x44, 0x11, 0x51}}} diff --git a/src/libs/libssh-0.12.2/src/external/libcrux_mlkem768_sha3.c b/src/libs/libssh-0.12.2/src/external/libcrux_mlkem768_sha3.c new file mode 100644 index 000000000000..59130fbd40a4 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/libcrux_mlkem768_sha3.c @@ -0,0 +1,8897 @@ +/* $OpenBSD: libcrux_mlkem768_sha3.h,v 1.4 2025/11/13 05:13:06 djm Exp $ */ + +/* Extracted from libcrux revision 026a87ab6d88ad3626b9fbbf3710d1e0483c1849 */ + +/* + * MIT License + * + * Copyright (c) 2024 Cryspen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include "libssh/mlkem_native.h" + +#if !defined(__GNUC__) || (__GNUC__ < 2) +# define __attribute__(x) +#endif +#define KRML_MUSTINLINE inline +#define KRML_NOINLINE __attribute__((noinline, unused)) +#define KRML_HOST_EPRINTF(...) +#define KRML_HOST_EXIT(x) do { \ + fprintf(stderr, "mlkem internal error"); \ + exit(x); \ +} while (0) + +static inline void +store64_le(uint8_t dst[8], uint64_t src) +{ + dst[0] = src & 0xff; + dst[1] = (src >> 8) & 0xff; + dst[2] = (src >> 16) & 0xff; + dst[3] = (src >> 24) & 0xff; + dst[4] = (src >> 32) & 0xff; + dst[5] = (src >> 40) & 0xff; + dst[6] = (src >> 48) & 0xff; + dst[7] = (src >> 56) & 0xff; +} + +static inline uint64_t +load64_le(uint8_t src[8]) +{ + return (uint64_t)(src[0]) | + ((uint64_t)(src[1]) << 8) | + ((uint64_t)(src[2]) << 16) | + ((uint64_t)(src[3]) << 24) | + ((uint64_t)(src[4]) << 32) | + ((uint64_t)(src[5]) << 40) | + ((uint64_t)(src[6]) << 48) | + ((uint64_t)(src[7]) << 56); +} + +#ifdef MISSING_BUILTIN_POPCOUNT +static inline unsigned int +__builtin_popcount(unsigned int num) +{ + const int v[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; + return v[num & 0xf] + v[(num >> 4) & 0xf]; +} +#endif + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/eurydice_glue.h */ + + +#ifdef _MSC_VER +// For __popcnt +#endif + + +// C++ HELPERS + +#if defined(__cplusplus) + +#ifndef KRML_HOST_EPRINTF +#define KRML_HOST_EPRINTF(...) fprintf(stderr, __VA_ARGS__) +#endif + + +#ifndef __cpp_lib_type_identity +template +struct type_identity { + using type = T; +}; + +template +using type_identity_t = typename type_identity::type; +#else +using std::type_identity_t; +#endif + +#define KRML_UNION_CONSTRUCTOR(T) \ + template \ + constexpr T(int t, V U::*m, type_identity_t v) : tag(t) { \ + val.*m = std::move(v); \ + } \ + T() = default; + +#endif + +// GENERAL-PURPOSE STUFF + +#define LowStar_Ignore_ignore(e, t, _ret_t) ((void)e) + +#define EURYDICE_ASSERT(test, msg) \ + do { \ + if (!(test)) { \ + fprintf(stderr, "assertion \"%s\" failed: file \"%s\", line %d\n", msg, \ + __FILE__, __LINE__); \ + exit(255); \ + } \ + } while (0) + +// SLICES, ARRAYS, ETC. + +// We represent a slice as a pair of an (untyped) pointer, along with the length +// of the slice, i.e. the number of elements in the slice (this is NOT the +// number of bytes). This design choice has two important consequences. +// - if you need to use `ptr`, you MUST cast it to a proper type *before* +// performing pointer arithmetic on it (remember that C desugars pointer +// arithmetic based on the type of the address) +// - if you need to use `len` for a C style function (e.g. memcpy, memcmp), you +// need to multiply it by sizeof t, where t is the type of the elements. +// +// Empty slices have `len == 0` and `ptr` always needs to be a valid pointer +// that is not NULL (otherwise the construction in EURYDICE_SLICE computes `NULL +// + start`). +typedef struct { + void *ptr; + size_t len; +} Eurydice_slice; + +#if defined(__cplusplus) +#define KRML_CLITERAL(type) type +#else +#define KRML_CLITERAL(type) (type) +#endif + +#if defined(__cplusplus) && defined(__cpp_designated_initializers) || \ + !(defined(__cplusplus)) +#define EURYDICE_CFIELD(X) X +#else +#define EURYDICE_CFIELD(X) +#endif + +// Helper macro to create a slice out of a pointer x, a start index in x +// (included), and an end index in x (excluded). The argument x must be suitably +// cast to something that can decay (see remark above about how pointer +// arithmetic works in C), meaning either pointer or array type. +#define EURYDICE_SLICE(x, start, end) \ + (KRML_CLITERAL(Eurydice_slice){(void *)(x + start), end - start}) + +// Slice length +#define EURYDICE_SLICE_LEN(s, _) (s).len +#define Eurydice_slice_len(s, _) (s).len + +// This macro is a pain because in case the dereferenced element type is an +// array, you cannot simply write `t x` as it would yield `int[4] x` instead, +// which is NOT correct C syntax, so we add a dedicated phase in Eurydice that +// adds an extra argument to this macro at the last minute so that we have the +// correct type of *pointers* to elements. +#define Eurydice_slice_index(s, i, t, t_ptr_t) (((t_ptr_t)s.ptr)[i]) + +// The following functions get sub slices from a slice. + +#define Eurydice_slice_subslice(s, r, t, _0, _1) \ + EURYDICE_SLICE((t *)s.ptr, r.start, r.end) + +// Variant for when the start and end indices are statically known (i.e., the +// range argument `r` is a literal). +#define Eurydice_slice_subslice2(s, start, end, t) \ + EURYDICE_SLICE((t *)s.ptr, (start), (end)) + +// Previous version above does not work when t is an array type (as usual). Will +// be deprecated soon. +#define Eurydice_slice_subslice3(s, start, end, t_ptr) \ + EURYDICE_SLICE((t_ptr)s.ptr, (start), (end)) + +#define Eurydice_slice_subslice_to(s, subslice_end_pos, t, _0, _1) \ + EURYDICE_SLICE((t *)s.ptr, 0, subslice_end_pos) + +#define Eurydice_slice_subslice_from(s, subslice_start_pos, t, _0, _1) \ + EURYDICE_SLICE((t *)s.ptr, subslice_start_pos, s.len) + +#define Eurydice_array_to_slice(end, x, t) \ + EURYDICE_SLICE(x, 0, \ + end) /* x is already at an array type, no need for cast */ +#define Eurydice_array_to_subslice(_arraylen, x, r, t, _0, _1) \ + EURYDICE_SLICE((t *)x, r.start, r.end) + +// Same as above, variant for when start and end are statically known +#define Eurydice_array_to_subslice2(x, start, end, t) \ + EURYDICE_SLICE((t *)x, (start), (end)) + +// Same as above, variant for when start and end are statically known +#define Eurydice_array_to_subslice3(x, start, end, t_ptr) \ + EURYDICE_SLICE((t_ptr)x, (start), (end)) + +#define Eurydice_array_repeat(dst, len, init, t) \ + ERROR "should've been desugared" + +// The following functions convert an array into a slice. + +#define Eurydice_array_to_subslice_to(_size, x, r, t, _range_t, _0) \ + EURYDICE_SLICE((t *)x, 0, r) +#define Eurydice_array_to_subslice_from(size, x, r, t, _range_t, _0) \ + EURYDICE_SLICE((t *)x, r, size) + +// Copy a slice with memcopy +#define Eurydice_slice_copy(dst, src, t) \ + memcpy(dst.ptr, src.ptr, dst.len * sizeof(t)) + +#define core_array___Array_T__N___as_slice(len_, ptr_, t, _ret_t) \ + KRML_CLITERAL(Eurydice_slice) { ptr_, len_ } + +#define core_array__core__clone__Clone_for__Array_T__N___clone( \ + len, src, dst, elem_type, _ret_t) \ + (memcpy(dst, src, len * sizeof(elem_type))) +#define TryFromSliceError uint8_t +#define core_array_TryFromSliceError uint8_t + +#define Eurydice_array_eq(sz, a1, a2, t) (memcmp(a1, a2, sz * sizeof(t)) == 0) + +// core::cmp::PartialEq<&0 (@Slice)> for @Array +#define Eurydice_array_eq_slice(sz, a1, s2, t, _) \ + (memcmp(a1, (s2)->ptr, sz * sizeof(t)) == 0) + +#define core_array_equality___core__cmp__PartialEq__Array_U__N___for__Array_T__N____eq( \ + sz, a1, a2, t, _, _ret_t) \ + Eurydice_array_eq(sz, a1, a2, t, _) +#define core_array_equality___core__cmp__PartialEq__0___Slice_U____for__Array_T__N___3__eq( \ + sz, a1, a2, t, _, _ret_t) \ + Eurydice_array_eq(sz, a1, ((a2)->ptr), t, _) + +#define Eurydice_slice_split_at(slice, mid, element_type, ret_t) \ + KRML_CLITERAL(ret_t) { \ + EURYDICE_CFIELD(.fst =) \ + EURYDICE_SLICE((element_type *)(slice).ptr, 0, mid), \ + EURYDICE_CFIELD(.snd =) \ + EURYDICE_SLICE((element_type *)(slice).ptr, mid, (slice).len) \ + } + +#define Eurydice_slice_split_at_mut(slice, mid, element_type, ret_t) \ + KRML_CLITERAL(ret_t) { \ + EURYDICE_CFIELD(.fst =) \ + KRML_CLITERAL(Eurydice_slice){EURYDICE_CFIELD(.ptr =)(slice.ptr), \ + EURYDICE_CFIELD(.len =) mid}, \ + EURYDICE_CFIELD(.snd =) KRML_CLITERAL(Eurydice_slice) { \ + EURYDICE_CFIELD(.ptr =) \ + ((char *)slice.ptr + mid * sizeof(element_type)), \ + EURYDICE_CFIELD(.len =)(slice.len - mid) \ + } \ + } + +// Conversion of slice to an array, rewritten (by Eurydice) to name the +// destination array, since arrays are not values in C. +// N.B.: see note in karamel/lib/Inlining.ml if you change this. +#define Eurydice_slice_to_array2(dst, src, _0, t_arr, _1) \ + Eurydice_slice_to_array3(&(dst)->tag, (char *)&(dst)->val.case_Ok, src, \ + sizeof(t_arr)) + +static inline void Eurydice_slice_to_array3(uint8_t *dst_tag, char *dst_ok, + Eurydice_slice src, size_t sz) { + *dst_tag = 0; + memcpy(dst_ok, src.ptr, sz); +} + +// SUPPORT FOR DSTs (Dynamically-Sized Types) + +// A DST is a fat pointer that keeps tracks of the size of it flexible array +// member. Slices are a specific case of DSTs, where [T; N] implements +// Unsize<[T]>, meaning an array of statically known size can be converted to a +// fat pointer, i.e. a slice. +// +// Unlike slices, DSTs have a built-in definition that gets monomorphized, of +// the form: +// +// typedef struct { +// T *ptr; +// size_t len; // number of elements +// } Eurydice_dst; +// +// Furthermore, T = T0<[U0]> where `struct T0`, where the `U` is the +// last field. This means that there are two monomorphizations of T0 in the +// program. One is `T0<[V; N]>` +// -- this is directly converted to a Eurydice_dst via suitable codegen (no +// macro). The other is `T = T0<[U]>`, where `[U]` gets emitted to +// `Eurydice_derefed_slice`, a type that only appears in that precise situation +// and is thus defined to give rise to a flexible array member. + +typedef char Eurydice_derefed_slice[]; + +#define Eurydice_slice_of_dst(fam_ptr, len_, t, _) \ + ((Eurydice_slice){.ptr = (void *)(fam_ptr), .len = len_}) + +#define Eurydice_slice_of_boxed_array(ptr_, len_, t, _) \ + ((Eurydice_slice){.ptr = (void *)(ptr_), .len = len_}) + +// CORE STUFF (conversions, endianness, ...) + +// We slap extern "C" on declarations that intend to implement a prototype +// generated by Eurydice, because Eurydice prototypes are always emitted within +// an extern "C" block, UNLESS you use -fcxx17-compat, in which case, you must +// pass -DKRML_CXX17_COMPAT="" to your C++ compiler. +#if defined(__cplusplus) && !defined(KRML_CXX17_COMPAT) +extern "C" { +#endif + +static inline void core_num__u64__to_le_bytes(uint64_t v, uint8_t buf[8]) { + store64_le(buf, v); +} + +static inline uint64_t core_num__u64__from_le_bytes(uint8_t buf[8]) { + return load64_le(buf); +} + +// unsigned overflow wraparound semantics in C +static inline uint16_t core_num__u16__wrapping_add(uint16_t x, uint16_t y) { + return x + y; +} +static inline uint8_t core_num__u8__wrapping_sub(uint8_t x, uint8_t y) { + return x - y; +} +static inline uint64_t core_num__u64__rotate_left(uint64_t x0, uint32_t x1) { + return (x0 << x1 | x0 >> (64 - x1)); +} + +#if defined(__cplusplus) && !defined(KRML_CXX17_COMPAT) +} +#endif + +// ITERATORS + +#define Eurydice_range_iter_next(iter_ptr, t, ret_t) \ + (((iter_ptr)->start >= (iter_ptr)->end) \ + ? (KRML_CLITERAL(ret_t){EURYDICE_CFIELD(.tag =) 0, \ + EURYDICE_CFIELD(.f0 =) 0}) \ + : (KRML_CLITERAL(ret_t){EURYDICE_CFIELD(.tag =) 1, \ + EURYDICE_CFIELD(.f0 =)(iter_ptr)->start++})) + +#define core_iter_range___core__iter__traits__iterator__Iterator_A__for_core__ops__range__Range_A__TraitClause_0___6__next \ + Eurydice_range_iter_next + +// See note in karamel/lib/Inlining.ml if you change this +#define Eurydice_into_iter(x, t, _ret_t, _) (x) +#define core_iter_traits_collect___core__iter__traits__collect__IntoIterator_Clause1_Item__I__for_I__1__into_iter \ + Eurydice_into_iter + +typedef struct { + Eurydice_slice s; + size_t index; +} Eurydice_slice_iterator; + +#define core_slice___Slice_T___iter(x, t, _ret_t) \ + ((Eurydice_slice_iterator){.s = x, .index = 0}) +#define core_slice_iter_Iter Eurydice_slice_iterator +#define core_slice_iter__core__slice__iter__Iter__a__T__181__next(iter, t, \ + ret_t) \ + (((iter)->index == (iter)->s.len) \ + ? (KRML_CLITERAL(ret_t){.tag = core_option_None}) \ + : (KRML_CLITERAL(ret_t){ \ + .tag = core_option_Some, \ + .f0 = ((iter)->index++, \ + &((t *)((iter)->s.ptr))[(iter)->index - 1])})) +#define core_option__core__option__Option_T__TraitClause_0___is_some(X, _0, \ + _1) \ + ((X)->tag == 1) +// STRINGS + +typedef const char *Prims_string; + +// MISC (UNTESTED) + +typedef void *core_fmt_Formatter; +typedef void *core_fmt_Arguments; +typedef void *core_fmt_rt_Argument; +#define core_fmt_rt__core__fmt__rt__Argument__a__1__new_display(x1, x2, x3, \ + x4) \ + NULL + +// BOXES + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_mlkem_core.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_mlkem_core_H +#define libcrux_mlkem_core_H + + +#if defined(__cplusplus) +extern "C" { +#endif + +/** +A monomorphic instance of core.ops.range.Range +with types size_t + +*/ +typedef struct core_ops_range_Range_08_s { + size_t start; + size_t end; +} core_ops_range_Range_08; + +static inline uint16_t core_num__u16__wrapping_add(uint16_t x0, uint16_t x1); + +static inline uint64_t core_num__u64__from_le_bytes(uint8_t x0[8U]); + +static inline uint64_t core_num__u64__rotate_left(uint64_t x0, uint32_t x1); + +static inline void core_num__u64__to_le_bytes(uint64_t x0, uint8_t x1[8U]); + +static inline uint8_t core_num__u8__wrapping_sub(uint8_t x0, uint8_t x1); + +#define LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE ((size_t)32U) + +#define LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_COEFFICIENT ((size_t)12U) + +#define LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT ((size_t)256U) + +#define LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT \ + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * (size_t)12U) + +#define LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT \ + (LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE ((size_t)32U) + +#define LIBCRUX_ML_KEM_CONSTANTS_G_DIGEST_SIZE ((size_t)64U) + +#define LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE ((size_t)32U) + +/** + K * BITS_PER_RING_ELEMENT / 8 + + [eurydice] Note that we can't use const generics here because that breaks + C extraction with eurydice. +*/ +static inline size_t libcrux_ml_kem_constants_ranked_bytes_per_ring_element( + size_t rank) { + return rank * LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT / (size_t)8U; +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint8_t + +*/ +static KRML_MUSTINLINE uint8_t +libcrux_secrets_int_public_integers_classify_27_90(uint8_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types int16_t + +*/ +static KRML_MUSTINLINE int16_t +libcrux_secrets_int_public_integers_declassify_d8_39(int16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE uint8_t libcrux_secrets_int_as_u8_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_90( + (uint8_t)libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types int16_t + +*/ +static KRML_MUSTINLINE int16_t +libcrux_secrets_int_public_integers_classify_27_39(int16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t + +*/ +static KRML_MUSTINLINE uint8_t +libcrux_secrets_int_public_integers_declassify_d8_90(uint8_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u8} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_59(uint8_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_90(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types int32_t + +*/ +static KRML_MUSTINLINE int32_t +libcrux_secrets_int_public_integers_classify_27_a8(int32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE int32_t libcrux_secrets_int_as_i32_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_a8( + (int32_t)libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types int32_t + +*/ +static KRML_MUSTINLINE int32_t +libcrux_secrets_int_public_integers_declassify_d8_a8(int32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i32} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_36(int32_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_a8(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint32_t + +*/ +static KRML_MUSTINLINE uint32_t +libcrux_secrets_int_public_integers_declassify_d8_df(uint32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u32} +*/ +static KRML_MUSTINLINE int32_t libcrux_secrets_int_as_i32_b8(uint32_t self) { + return libcrux_secrets_int_public_integers_classify_27_a8( + (int32_t)libcrux_secrets_int_public_integers_declassify_d8_df(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint16_t + +*/ +static KRML_MUSTINLINE uint16_t +libcrux_secrets_int_public_integers_classify_27_de(uint16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE uint16_t libcrux_secrets_int_as_u16_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_de( + (uint16_t)libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint16_t + +*/ +static KRML_MUSTINLINE uint16_t +libcrux_secrets_int_public_integers_declassify_d8_de(uint16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u16} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_ca(uint16_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_de(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint64_t + +*/ +static KRML_MUSTINLINE uint64_t +libcrux_secrets_int_public_integers_classify_27_49(uint64_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u16} +*/ +static KRML_MUSTINLINE uint64_t libcrux_secrets_int_as_u64_ca(uint16_t self) { + return libcrux_secrets_int_public_integers_classify_27_49( + (uint64_t)libcrux_secrets_int_public_integers_declassify_d8_de(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint32_t + +*/ +static KRML_MUSTINLINE uint32_t +libcrux_secrets_int_public_integers_classify_27_df(uint32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint64_t + +*/ +static KRML_MUSTINLINE uint64_t +libcrux_secrets_int_public_integers_declassify_d8_49(uint64_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u64} +*/ +static KRML_MUSTINLINE uint32_t libcrux_secrets_int_as_u32_a3(uint64_t self) { + return libcrux_secrets_int_public_integers_classify_27_df( + (uint32_t)libcrux_secrets_int_public_integers_declassify_d8_49(self)); +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u32} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_b8(uint32_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_df(self)); +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +typedef struct libcrux_ml_kem_utils_extraction_helper_Keypair768_s { + uint8_t fst[1152U]; + uint8_t snd[1184U]; +} libcrux_ml_kem_utils_extraction_helper_Keypair768; + +#define Ok 0 +#define Err 1 + +typedef uint8_t Result_b2_tags; + +/** +This function found in impl {core::convert::From<@Array> for +libcrux_ml_kem::types::MlKemPublicKey} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_fd +with const generics +- SIZE= 1184 +*/ +static inline libcrux_ml_kem_types_MlKemPublicKey_30 +libcrux_ml_kem_types_from_fd_d0(uint8_t value[1184U]) { + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_value[1184U]; + memcpy(copy_of_value, value, (size_t)1184U * sizeof(uint8_t)); + libcrux_ml_kem_types_MlKemPublicKey_30 lit; + memcpy(lit.value, copy_of_value, (size_t)1184U * sizeof(uint8_t)); + return lit; +} + +/** +This function found in impl +{libcrux_ml_kem::types::MlKemKeyPair} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_17 +with const generics +- PRIVATE_KEY_SIZE= 2400 +- PUBLIC_KEY_SIZE= 1184 +*/ +static inline libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_types_from_17_74(libcrux_ml_kem_types_MlKemPrivateKey_d9 sk, + libcrux_ml_kem_types_MlKemPublicKey_30 pk) { + return (KRML_CLITERAL(libcrux_ml_kem_mlkem768_MlKem768KeyPair){.sk = sk, + .pk = pk}); +} + +/** +This function found in impl {core::convert::From<@Array> for +libcrux_ml_kem::types::MlKemPrivateKey} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_77 +with const generics +- SIZE= 2400 +*/ +static inline libcrux_ml_kem_types_MlKemPrivateKey_d9 +libcrux_ml_kem_types_from_77_28(uint8_t value[2400U]) { + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_value[2400U]; + memcpy(copy_of_value, value, (size_t)2400U * sizeof(uint8_t)); + libcrux_ml_kem_types_MlKemPrivateKey_d9 lit; + memcpy(lit.value, copy_of_value, (size_t)2400U * sizeof(uint8_t)); + return lit; +} + +/** +A monomorphic instance of core.result.Result +with types uint8_t[32size_t], core_array_TryFromSliceError + +*/ +typedef struct Result_fb_s { + Result_b2_tags tag; + union { + uint8_t case_Ok[32U]; + TryFromSliceError case_Err; + } val; +} Result_fb; + +/** +This function found in impl {core::result::Result[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of core.result.unwrap_26 +with types uint8_t[32size_t], core_array_TryFromSliceError + +*/ +static inline void unwrap_26_b3(Result_fb self, uint8_t ret[32U]) { + if (self.tag == Ok) { + uint8_t f0[32U]; + memcpy(f0, self.val.case_Ok, (size_t)32U * sizeof(uint8_t)); + memcpy(ret, f0, (size_t)32U * sizeof(uint8_t)); + } else { + KRML_HOST_EPRINTF("KaRaMeL abort at %s:%d\n%s\n", __FILE__, __LINE__, + "unwrap not Ok"); + KRML_HOST_EXIT(255U); + } +} + +/** +This function found in impl {core::convert::From<@Array> for +libcrux_ml_kem::types::MlKemCiphertext} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_e0 +with const generics +- SIZE= 1088 +*/ +static inline libcrux_ml_kem_mlkem768_MlKem768Ciphertext +libcrux_ml_kem_types_from_e0_80(uint8_t value[1088U]) { + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_value[1088U]; + memcpy(copy_of_value, value, (size_t)1088U * sizeof(uint8_t)); + libcrux_ml_kem_mlkem768_MlKem768Ciphertext lit; + memcpy(lit.value, copy_of_value, (size_t)1088U * sizeof(uint8_t)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::types::MlKemPublicKey} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.as_slice_e6 +with const generics +- SIZE= 1184 +*/ +static inline uint8_t *libcrux_ml_kem_types_as_slice_e6_d0( + libcrux_ml_kem_types_MlKemPublicKey_30 *self) { + return self->value; +} + +/** +This function found in impl {libcrux_ml_kem::types::MlKemCiphertext} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.as_slice_a9 +with const generics +- SIZE= 1088 +*/ +static inline uint8_t *libcrux_ml_kem_types_as_slice_a9_80( + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *self) { + return self->value; +} + +/** +A monomorphic instance of libcrux_ml_kem.utils.prf_input_inc +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE uint8_t libcrux_ml_kem_utils_prf_input_inc_e0( + uint8_t (*prf_inputs)[33U], uint8_t domain_separator) { + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + prf_inputs[i0][32U] = domain_separator; + domain_separator = (uint32_t)domain_separator + 1U; + } + return domain_separator; +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 33 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_c8( + Eurydice_slice slice, uint8_t ret[33U]) { + uint8_t out[33U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)33U * sizeof(uint8_t)); +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 34 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_b6( + Eurydice_slice slice, uint8_t ret[34U]) { + uint8_t out[34U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)34U * sizeof(uint8_t)); +} + +/** +This function found in impl {core::convert::AsRef<@Slice> for +libcrux_ml_kem::types::MlKemCiphertext} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.as_ref_d3 +with const generics +- SIZE= 1088 +*/ +static inline Eurydice_slice libcrux_ml_kem_types_as_ref_d3_80( + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *self) { + return Eurydice_array_to_slice((size_t)1088U, self->value, uint8_t); +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 1120 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_15( + Eurydice_slice slice, uint8_t ret[1120U]) { + uint8_t out[1120U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)1120U * sizeof(uint8_t)); +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 64 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_24( + Eurydice_slice slice, uint8_t ret[64U]) { + uint8_t out[64U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)64U * sizeof(uint8_t)); +} + +typedef struct Eurydice_slice_uint8_t_x4_s { + Eurydice_slice fst; + Eurydice_slice snd; + Eurydice_slice thd; + Eurydice_slice f3; +} Eurydice_slice_uint8_t_x4; + +typedef struct Eurydice_slice_uint8_t_x2_s { + Eurydice_slice fst; + Eurydice_slice snd; +} Eurydice_slice_uint8_t_x2; + +/** + Unpack an incoming private key into it's different parts. + + We have this here in types to extract into a common core for C. +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.unpack_private_key +with const generics +- CPA_SECRET_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +*/ +static inline Eurydice_slice_uint8_t_x4 +libcrux_ml_kem_types_unpack_private_key_b4(Eurydice_slice private_key) { + Eurydice_slice_uint8_t_x2 uu____0 = Eurydice_slice_split_at( + private_key, (size_t)1152U, uint8_t, Eurydice_slice_uint8_t_x2); + Eurydice_slice ind_cpa_secret_key = uu____0.fst; + Eurydice_slice secret_key0 = uu____0.snd; + Eurydice_slice_uint8_t_x2 uu____1 = Eurydice_slice_split_at( + secret_key0, (size_t)1184U, uint8_t, Eurydice_slice_uint8_t_x2); + Eurydice_slice ind_cpa_public_key = uu____1.fst; + Eurydice_slice secret_key = uu____1.snd; + Eurydice_slice_uint8_t_x2 uu____2 = Eurydice_slice_split_at( + secret_key, LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE, uint8_t, + Eurydice_slice_uint8_t_x2); + Eurydice_slice ind_cpa_public_key_hash = uu____2.fst; + Eurydice_slice implicit_rejection_value = uu____2.snd; + return ( + KRML_CLITERAL(Eurydice_slice_uint8_t_x4){.fst = ind_cpa_secret_key, + .snd = ind_cpa_public_key, + .thd = ind_cpa_public_key_hash, + .f3 = implicit_rejection_value}); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[24size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_d2(uint8_t self[24U], + uint8_t ret[24U]) { + memcpy(ret, self, (size_t)24U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[20size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_57(uint8_t self[20U], + uint8_t ret[20U]) { + memcpy(ret, self, (size_t)20U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[8size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_76(uint8_t self[8U], + uint8_t ret[8U]) { + memcpy(ret, self, (size_t)8U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[2size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_d4(uint8_t self[2U], + uint8_t ret[2U]) { + memcpy(ret, self, (size_t)2U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types int16_t[16size_t] + +*/ +static KRML_MUSTINLINE void libcrux_secrets_int_public_integers_classify_27_46( + int16_t self[16U], int16_t ret[16U]) { + memcpy(ret, self, (size_t)16U * sizeof(int16_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::ClassifyRef<&'a +(@Slice)> for &'a (@Slice)} +*/ +/** +A monomorphic instance of libcrux_secrets.int.classify_public.classify_ref_9b +with types uint8_t + +*/ +static KRML_MUSTINLINE Eurydice_slice +libcrux_secrets_int_classify_public_classify_ref_9b_90(Eurydice_slice self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::ClassifyRef<&'a +(@Slice)> for &'a (@Slice)} +*/ +/** +A monomorphic instance of libcrux_secrets.int.classify_public.classify_ref_9b +with types int16_t + +*/ +static KRML_MUSTINLINE Eurydice_slice +libcrux_secrets_int_classify_public_classify_ref_9b_39(Eurydice_slice self) { + return self; +} + +/** +A monomorphic instance of core.result.Result +with types int16_t[16size_t], core_array_TryFromSliceError + +*/ +typedef struct Result_0a_s { + Result_b2_tags tag; + union { + int16_t case_Ok[16U]; + TryFromSliceError case_Err; + } val; +} Result_0a; + +/** +This function found in impl {core::result::Result[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of core.result.unwrap_26 +with types int16_t[16size_t], core_array_TryFromSliceError + +*/ +static inline void unwrap_26_00(Result_0a self, int16_t ret[16U]) { + if (self.tag == Ok) { + int16_t f0[16U]; + memcpy(f0, self.val.case_Ok, (size_t)16U * sizeof(int16_t)); + memcpy(ret, f0, (size_t)16U * sizeof(int16_t)); + } else { + KRML_HOST_EPRINTF("KaRaMeL abort at %s:%d\n%s\n", __FILE__, __LINE__, + "unwrap not Ok"); + KRML_HOST_EXIT(255U); + } +} + +/** +A monomorphic instance of core.result.Result +with types uint8_t[8size_t], core_array_TryFromSliceError + +*/ +typedef struct Result_15_s { + Result_b2_tags tag; + union { + uint8_t case_Ok[8U]; + TryFromSliceError case_Err; + } val; +} Result_15; + +/** +This function found in impl {core::result::Result[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of core.result.unwrap_26 +with types uint8_t[8size_t], core_array_TryFromSliceError + +*/ +static inline void unwrap_26_68(Result_15 self, uint8_t ret[8U]) { + if (self.tag == Ok) { + uint8_t f0[8U]; + memcpy(f0, self.val.case_Ok, (size_t)8U * sizeof(uint8_t)); + memcpy(ret, f0, (size_t)8U * sizeof(uint8_t)); + } else { + KRML_HOST_EPRINTF("KaRaMeL abort at %s:%d\n%s\n", __FILE__, __LINE__, + "unwrap not Ok"); + KRML_HOST_EXIT(255U); + } +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_mlkem_core_H_DEFINED +#endif /* libcrux_mlkem_core_H */ + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_ct_ops.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_ct_ops_H +#define libcrux_ct_ops_H + + +#if defined(__cplusplus) +extern "C" { +#endif + + +/** + Return 1 if `value` is not zero and 0 otherwise. +*/ +static KRML_NOINLINE uint8_t +libcrux_ml_kem_constant_time_ops_inz(uint8_t value) { + uint16_t value0 = (uint16_t)value; + uint8_t result = + (uint8_t)((uint32_t)core_num__u16__wrapping_add(~value0, 1U) >> 8U); + return (uint32_t)result & 1U; +} + +static KRML_NOINLINE uint8_t +libcrux_ml_kem_constant_time_ops_is_non_zero(uint8_t value) { + return libcrux_ml_kem_constant_time_ops_inz(value); +} + +/** + Return 1 if the bytes of `lhs` and `rhs` do not exactly + match and 0 otherwise. +*/ +static KRML_NOINLINE uint8_t libcrux_ml_kem_constant_time_ops_compare( + Eurydice_slice lhs, Eurydice_slice rhs) { + uint8_t r = 0U; + for (size_t i = (size_t)0U; i < Eurydice_slice_len(lhs, uint8_t); i++) { + size_t i0 = i; + uint8_t nr = (uint32_t)r | + ((uint32_t)Eurydice_slice_index(lhs, i0, uint8_t, uint8_t *) ^ + (uint32_t)Eurydice_slice_index(rhs, i0, uint8_t, uint8_t *)); + r = nr; + } + return libcrux_ml_kem_constant_time_ops_is_non_zero(r); +} + +static KRML_NOINLINE uint8_t +libcrux_ml_kem_constant_time_ops_compare_ciphertexts_in_constant_time( + Eurydice_slice lhs, Eurydice_slice rhs) { + return libcrux_ml_kem_constant_time_ops_compare(lhs, rhs); +} + +/** + If `selector` is not zero, return the bytes in `rhs`; return the bytes in + `lhs` otherwise. +*/ +static KRML_NOINLINE void libcrux_ml_kem_constant_time_ops_select_ct( + Eurydice_slice lhs, Eurydice_slice rhs, uint8_t selector, + uint8_t ret[32U]) { + uint8_t mask = core_num__u8__wrapping_sub( + libcrux_ml_kem_constant_time_ops_is_non_zero(selector), 1U); + uint8_t out[32U] = {0U}; + for (size_t i = (size_t)0U; i < LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE; + i++) { + size_t i0 = i; + uint8_t outi = + ((uint32_t)Eurydice_slice_index(lhs, i0, uint8_t, uint8_t *) & + (uint32_t)mask) | + ((uint32_t)Eurydice_slice_index(rhs, i0, uint8_t, uint8_t *) & + (uint32_t)~mask); + out[i0] = outi; + } + memcpy(ret, out, (size_t)32U * sizeof(uint8_t)); +} + +static KRML_NOINLINE void +libcrux_ml_kem_constant_time_ops_select_shared_secret_in_constant_time( + Eurydice_slice lhs, Eurydice_slice rhs, uint8_t selector, + uint8_t ret[32U]) { + libcrux_ml_kem_constant_time_ops_select_ct(lhs, rhs, selector, ret); +} + +static KRML_NOINLINE void +libcrux_ml_kem_constant_time_ops_compare_ciphertexts_select_shared_secret_in_constant_time( + Eurydice_slice lhs_c, Eurydice_slice rhs_c, Eurydice_slice lhs_s, + Eurydice_slice rhs_s, uint8_t ret[32U]) { + uint8_t selector = + libcrux_ml_kem_constant_time_ops_compare_ciphertexts_in_constant_time( + lhs_c, rhs_c); + uint8_t ret0[32U]; + libcrux_ml_kem_constant_time_ops_select_shared_secret_in_constant_time( + lhs_s, rhs_s, selector, ret0); + memcpy(ret, ret0, (size_t)32U * sizeof(uint8_t)); +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_ct_ops_H_DEFINED +#endif /* libcrux_ct_ops_H */ + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_sha3_portable.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_sha3_portable_H +#define libcrux_sha3_portable_H + + +#if defined(__cplusplus) +extern "C" { +#endif + + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable_zero_d2(void) { + return 0ULL; +} + +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable__veor5q_u64( + uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) { + return (((a ^ b) ^ c) ^ d) ^ e; +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable_xor5_d2( + uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) { + return libcrux_sha3_simd_portable__veor5q_u64(a, b, c, d, e); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 1 +- RIGHT= 63 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_76(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)1); +} + +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vrax1q_u64(uint64_t a, uint64_t b) { + uint64_t uu____0 = a; + return uu____0 ^ libcrux_sha3_simd_portable_rotate_left_76(b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left1_and_xor_d2(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vrax1q_u64(a, b); +} + +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vbcaxq_u64(uint64_t a, uint64_t b, uint64_t c) { + return a ^ (b & ~c); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_and_not_xor_d2(uint64_t a, uint64_t b, uint64_t c) { + return libcrux_sha3_simd_portable__vbcaxq_u64(a, b, c); +} + +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__veorq_n_u64(uint64_t a, uint64_t c) { + return a ^ c; +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_constant_d2(uint64_t a, uint64_t c) { + return libcrux_sha3_simd_portable__veorq_n_u64(a, c); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable_xor_d2(uint64_t a, + uint64_t b) { + return a ^ b; +} + +static const uint64_t + libcrux_sha3_generic_keccak_constants_ROUNDCONSTANTS[24U] = { + 1ULL, + 32898ULL, + 9223372036854808714ULL, + 9223372039002292224ULL, + 32907ULL, + 2147483649ULL, + 9223372039002292353ULL, + 9223372036854808585ULL, + 138ULL, + 136ULL, + 2147516425ULL, + 2147483658ULL, + 2147516555ULL, + 9223372036854775947ULL, + 9223372036854808713ULL, + 9223372036854808579ULL, + 9223372036854808578ULL, + 9223372036854775936ULL, + 32778ULL, + 9223372039002259466ULL, + 9223372039002292353ULL, + 9223372036854808704ULL, + 2147483649ULL, + 9223372039002292232ULL}; + +typedef struct size_t_x2_s { + size_t fst; + size_t snd; +} size_t_x2; + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.KeccakState +with types uint64_t +with const generics +- $1size_t +*/ +typedef struct libcrux_sha3_generic_keccak_KeccakState_17_s { + uint64_t st[25U]; +} libcrux_sha3_generic_keccak_KeccakState_17; + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.new_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE libcrux_sha3_generic_keccak_KeccakState_17 +libcrux_sha3_generic_keccak_new_80_04(void) { + libcrux_sha3_generic_keccak_KeccakState_17 lit; + uint64_t repeat_expression[25U]; + for (size_t i = (size_t)0U; i < (size_t)25U; i++) { + repeat_expression[i] = libcrux_sha3_simd_portable_zero_d2(); + } + memcpy(lit.st, repeat_expression, (size_t)25U * sizeof(uint64_t)); + return lit; +} + +/** +A monomorphic instance of libcrux_sha3.traits.get_ij +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE uint64_t *libcrux_sha3_traits_get_ij_04(uint64_t *arr, + size_t i, + size_t j) { + return &arr[(size_t)5U * j + i]; +} + +/** +A monomorphic instance of libcrux_sha3.traits.set_ij +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_traits_set_ij_04(uint64_t *arr, + size_t i, size_t j, + uint64_t value) { + arr[(size_t)5U * j + i] = value; +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block +with const generics +- RATE= 72 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_block_f8( + uint64_t *state, Eurydice_slice blocks, size_t start) { + uint64_t state_flat[25U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)72U / (size_t)8U; i++) { + size_t i0 = i; + size_t offset = start + (size_t)8U * i0; + uint8_t uu____0[8U]; + Result_15 dst; + Eurydice_slice_to_array2( + &dst, + Eurydice_slice_subslice3(blocks, offset, offset + (size_t)8U, + uint8_t *), + Eurydice_slice, uint8_t[8U], TryFromSliceError); + unwrap_26_68(dst, uu____0); + state_flat[i0] = core_num__u64__from_le_bytes(uu____0); + } + for (size_t i = (size_t)0U; i < (size_t)72U / (size_t)8U; i++) { + size_t i0 = i; + libcrux_sha3_traits_set_ij_04( + state, i0 / (size_t)5U, i0 % (size_t)5U, + libcrux_sha3_traits_get_ij_04(state, i0 / (size_t)5U, + i0 % (size_t)5U)[0U] ^ + state_flat[i0]); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block_a1 +with const generics +- RATE= 72 +*/ +static inline void libcrux_sha3_simd_portable_load_block_a1_f8( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start) { + libcrux_sha3_simd_portable_load_block_f8(self->st, input[0U], start); +} + +/** +This function found in impl {core::ops::index::Index<(usize, usize), T> for +libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.index_c2 +with types uint64_t +with const generics +- N= 1 +*/ +static inline uint64_t *libcrux_sha3_generic_keccak_index_c2_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, size_t_x2 index) { + return libcrux_sha3_traits_get_ij_04(self->st, index.fst, index.snd); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.theta_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_theta_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, uint64_t ret[5U]) { + uint64_t c[5U] = { + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)0U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)1U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)2U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)3U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)4U}))[0U])}; + uint64_t uu____0 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)0U + (size_t)4U) % (size_t)5U], + c[((size_t)0U + (size_t)1U) % (size_t)5U]); + uint64_t uu____1 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)1U + (size_t)4U) % (size_t)5U], + c[((size_t)1U + (size_t)1U) % (size_t)5U]); + uint64_t uu____2 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)2U + (size_t)4U) % (size_t)5U], + c[((size_t)2U + (size_t)1U) % (size_t)5U]); + uint64_t uu____3 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)3U + (size_t)4U) % (size_t)5U], + c[((size_t)3U + (size_t)1U) % (size_t)5U]); + ret[0U] = uu____0; + ret[1U] = uu____1; + ret[2U] = uu____2; + ret[3U] = uu____3; + ret[4U] = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)4U + (size_t)4U) % (size_t)5U], + c[((size_t)4U + (size_t)1U) % (size_t)5U]); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.set_80 +with types uint64_t +with const generics +- N= 1 +*/ +static inline void libcrux_sha3_generic_keccak_set_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, size_t i, size_t j, + uint64_t v) { + libcrux_sha3_traits_set_ij_04(self->st, i, j, v); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 36 +- RIGHT= 28 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_02(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)36); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 36 +- RIGHT= 28 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_02(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_02(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 36 +- RIGHT= 28 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_02(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_02(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 3 +- RIGHT= 61 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_ac(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)3); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 3 +- RIGHT= 61 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_ac(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_ac(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 3 +- RIGHT= 61 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_ac(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_ac(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 41 +- RIGHT= 23 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_020(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)41); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 41 +- RIGHT= 23 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_020(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_020(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 41 +- RIGHT= 23 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_020(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_020(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 18 +- RIGHT= 46 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_a9(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)18); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 18 +- RIGHT= 46 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_a9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_a9(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 18 +- RIGHT= 46 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_a9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_a9(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 1 +- RIGHT= 63 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_76(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_76(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 1 +- RIGHT= 63 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_76(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_76(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 44 +- RIGHT= 20 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_58(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)44); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 44 +- RIGHT= 20 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_58(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_58(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 44 +- RIGHT= 20 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_58(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_58(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 10 +- RIGHT= 54 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_e0(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)10); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 10 +- RIGHT= 54 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_e0(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_e0(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 10 +- RIGHT= 54 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_e0(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_e0(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 45 +- RIGHT= 19 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_63(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)45); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 45 +- RIGHT= 19 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_63(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_63(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 45 +- RIGHT= 19 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_63(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_63(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 2 +- RIGHT= 62 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_6a(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)2); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 2 +- RIGHT= 62 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_6a(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_6a(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 2 +- RIGHT= 62 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_6a(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_6a(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 62 +- RIGHT= 2 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_ab(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)62); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 62 +- RIGHT= 2 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_ab(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_ab(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 62 +- RIGHT= 2 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_ab(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_ab(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 6 +- RIGHT= 58 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_5b(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)6); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 6 +- RIGHT= 58 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_5b(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_5b(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 6 +- RIGHT= 58 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_5b(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_5b(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 43 +- RIGHT= 21 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_6f(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)43); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 43 +- RIGHT= 21 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_6f(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_6f(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 43 +- RIGHT= 21 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_6f(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_6f(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 15 +- RIGHT= 49 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_62(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)15); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 15 +- RIGHT= 49 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_62(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_62(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 15 +- RIGHT= 49 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_62(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_62(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 61 +- RIGHT= 3 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_23(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)61); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 61 +- RIGHT= 3 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_23(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_23(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 61 +- RIGHT= 3 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_23(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_23(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 28 +- RIGHT= 36 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_37(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)28); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 28 +- RIGHT= 36 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_37(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_37(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 28 +- RIGHT= 36 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_37(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_37(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 55 +- RIGHT= 9 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_bb(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)55); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 55 +- RIGHT= 9 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_bb(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_bb(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 55 +- RIGHT= 9 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_bb(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_bb(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 25 +- RIGHT= 39 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_b9(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)25); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 25 +- RIGHT= 39 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_b9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_b9(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 25 +- RIGHT= 39 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_b9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_b9(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 21 +- RIGHT= 43 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_54(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)21); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 21 +- RIGHT= 43 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_54(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_54(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 21 +- RIGHT= 43 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_54(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_54(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 56 +- RIGHT= 8 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_4c(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)56); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 56 +- RIGHT= 8 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_4c(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_4c(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 56 +- RIGHT= 8 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_4c(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_4c(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 27 +- RIGHT= 37 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_ce(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)27); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 27 +- RIGHT= 37 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_ce(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_ce(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 27 +- RIGHT= 37 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_ce(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_ce(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 20 +- RIGHT= 44 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_77(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)20); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 20 +- RIGHT= 44 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_77(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_77(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 20 +- RIGHT= 44 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_77(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_77(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 39 +- RIGHT= 25 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_25(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)39); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 39 +- RIGHT= 25 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_25(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_25(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 39 +- RIGHT= 25 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_25(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_25(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 8 +- RIGHT= 56 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_af(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)8); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 8 +- RIGHT= 56 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_af(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_af(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 8 +- RIGHT= 56 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_af(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_af(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 14 +- RIGHT= 50 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_fd(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)14); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 14 +- RIGHT= 50 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_fd(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_fd(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 14 +- RIGHT= 50 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_fd(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_fd(a, b); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.rho_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_rho_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, uint64_t t[5U]) { + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)0U, + libcrux_sha3_simd_portable_xor_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____0 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____0, (size_t)1U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_02( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____1 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____1, (size_t)2U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_ac( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____2 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____2, (size_t)3U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_020( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____3 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____3, (size_t)4U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_a9( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____4 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____4, (size_t)0U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_76( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____5 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____5, (size_t)1U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_58( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____6 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____6, (size_t)2U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_e0( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____7 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____7, (size_t)3U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_63( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____8 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____8, (size_t)4U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_6a( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____9 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____9, (size_t)0U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_ab( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____10 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____10, (size_t)1U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_5b( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____11 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____11, (size_t)2U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_6f( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____12 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____12, (size_t)3U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_62( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____13 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____13, (size_t)4U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_23( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____14 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____14, (size_t)0U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_37( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____15 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____15, (size_t)1U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_bb( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____16 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____16, (size_t)2U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_b9( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____17 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____17, (size_t)3U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_54( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____18 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____18, (size_t)4U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_4c( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____19 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____19, (size_t)0U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_ce( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____20 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____20, (size_t)1U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_77( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____21 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____21, (size_t)2U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_25( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____22 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____22, (size_t)3U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_af( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____23 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____23, (size_t)4U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_fd( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)4U}))[0U], + t[4U])); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.pi_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_pi_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self) { + libcrux_sha3_generic_keccak_KeccakState_17 old = self[0U]; + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)1U}))[0U]); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.chi_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_chi_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self) { + libcrux_sha3_generic_keccak_KeccakState_17 old = self[0U]; + for (size_t i0 = (size_t)0U; i0 < (size_t)5U; i0++) { + size_t i1 = i0; + for (size_t i = (size_t)0U; i < (size_t)5U; i++) { + size_t j = i; + libcrux_sha3_generic_keccak_set_80_04( + self, i1, j, + libcrux_sha3_simd_portable_and_not_xor_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = i1, .snd = j}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + &old, + (KRML_CLITERAL(size_t_x2){ + .fst = i1, .snd = (j + (size_t)2U) % (size_t)5U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + &old, + (KRML_CLITERAL(size_t_x2){ + .fst = i1, .snd = (j + (size_t)1U) % (size_t)5U}))[0U])); + } + } +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.iota_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_iota_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, size_t i) { + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)0U, + libcrux_sha3_simd_portable_xor_constant_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_constants_ROUNDCONSTANTS[i])); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.keccakf1600_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_keccakf1600_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self) { + for (size_t i = (size_t)0U; i < (size_t)24U; i++) { + size_t i0 = i; + uint64_t t[5U]; + libcrux_sha3_generic_keccak_theta_80_04(self, t); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____0 = self; + uint64_t uu____1[5U]; + memcpy(uu____1, t, (size_t)5U * sizeof(uint64_t)); + libcrux_sha3_generic_keccak_rho_80_04(uu____0, uu____1); + libcrux_sha3_generic_keccak_pi_80_04(self); + libcrux_sha3_generic_keccak_chi_80_04(self); + libcrux_sha3_generic_keccak_iota_80_04(self, i0); + } +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_block_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 72 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_block_80_c6( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *blocks, + size_t start) { + libcrux_sha3_simd_portable_load_block_a1_f8(self, blocks, start); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 72 +- DELIMITER= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_96( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[72U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 6U; + size_t uu____0 = (size_t)72U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_f8( + state, Eurydice_array_to_slice((size_t)72U, buffer, uint8_t), (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 72 +- DELIMITER= 6 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_96( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_96(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 72 +- DELIM= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_96(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.store_block +with const generics +- RATE= 72 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_store_block_f8( + uint64_t *s, Eurydice_slice out, size_t start, size_t len) { + size_t octets = len / (size_t)8U; + for (size_t i = (size_t)0U; i < octets; i++) { + size_t i0 = i; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, start + (size_t)8U * i0, start + (size_t)8U * i0 + (size_t)8U, + uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, i0 / (size_t)5U, i0 % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)8U, ret, uint8_t), uint8_t); + } + size_t remaining = len % (size_t)8U; + if (remaining > (size_t)0U) { + Eurydice_slice uu____1 = Eurydice_slice_subslice3( + out, start + len - remaining, start + len, uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, octets / (size_t)5U, + octets % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____1, + Eurydice_array_to_subslice3(ret, (size_t)0U, remaining, uint8_t *), + uint8_t); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Squeeze1 for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.squeeze_13 +with const generics +- RATE= 72 +*/ +static inline void libcrux_sha3_simd_portable_squeeze_13_f8( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start, size_t len) { + libcrux_sha3_simd_portable_store_block_f8(self->st, out, start, len); +} + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.portable.keccak1 +with const generics +- RATE= 72 +- DELIM= 6 +*/ +static inline void libcrux_sha3_generic_keccak_portable_keccak1_96( + Eurydice_slice data, Eurydice_slice out) { + libcrux_sha3_generic_keccak_KeccakState_17 s = + libcrux_sha3_generic_keccak_new_80_04(); + size_t data_len = Eurydice_slice_len(data, uint8_t); + for (size_t i = (size_t)0U; i < data_len / (size_t)72U; i++) { + size_t i0 = i; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_block_80_c6(&s, buf, i0 * (size_t)72U); + } + size_t rem = data_len % (size_t)72U; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_final_80_9e(&s, buf, data_len - rem, rem); + size_t outlen = Eurydice_slice_len(out, uint8_t); + size_t blocks = outlen / (size_t)72U; + size_t last = outlen - outlen % (size_t)72U; + if (blocks == (size_t)0U) { + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, (size_t)0U, outlen); + } else { + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, (size_t)0U, (size_t)72U); + for (size_t i = (size_t)1U; i < blocks; i++) { + size_t i0 = i; + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, i0 * (size_t)72U, + (size_t)72U); + } + if (last < outlen) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, last, outlen - last); + } + } +} + +/** + A portable SHA3 512 implementation. +*/ +static KRML_MUSTINLINE void libcrux_sha3_portable_sha512(Eurydice_slice digest, + Eurydice_slice data) { + libcrux_sha3_generic_keccak_portable_keccak1_96(data, digest); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block +with const generics +- RATE= 136 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_block_5b( + uint64_t *state, Eurydice_slice blocks, size_t start) { + uint64_t state_flat[25U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)136U / (size_t)8U; i++) { + size_t i0 = i; + size_t offset = start + (size_t)8U * i0; + uint8_t uu____0[8U]; + Result_15 dst; + Eurydice_slice_to_array2( + &dst, + Eurydice_slice_subslice3(blocks, offset, offset + (size_t)8U, + uint8_t *), + Eurydice_slice, uint8_t[8U], TryFromSliceError); + unwrap_26_68(dst, uu____0); + state_flat[i0] = core_num__u64__from_le_bytes(uu____0); + } + for (size_t i = (size_t)0U; i < (size_t)136U / (size_t)8U; i++) { + size_t i0 = i; + libcrux_sha3_traits_set_ij_04( + state, i0 / (size_t)5U, i0 % (size_t)5U, + libcrux_sha3_traits_get_ij_04(state, i0 / (size_t)5U, + i0 % (size_t)5U)[0U] ^ + state_flat[i0]); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block_a1 +with const generics +- RATE= 136 +*/ +static inline void libcrux_sha3_simd_portable_load_block_a1_5b( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start) { + libcrux_sha3_simd_portable_load_block_5b(self->st, input[0U], start); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_block_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 136 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_block_80_c60( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *blocks, + size_t start) { + libcrux_sha3_simd_portable_load_block_a1_5b(self, blocks, start); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 136 +- DELIMITER= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_ad( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[136U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 6U; + size_t uu____0 = (size_t)136U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_5b( + state, Eurydice_array_to_slice((size_t)136U, buffer, uint8_t), + (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 136 +- DELIMITER= 6 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_ad( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_ad(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 136 +- DELIM= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e0( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_ad(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.store_block +with const generics +- RATE= 136 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_store_block_5b( + uint64_t *s, Eurydice_slice out, size_t start, size_t len) { + size_t octets = len / (size_t)8U; + for (size_t i = (size_t)0U; i < octets; i++) { + size_t i0 = i; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, start + (size_t)8U * i0, start + (size_t)8U * i0 + (size_t)8U, + uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, i0 / (size_t)5U, i0 % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)8U, ret, uint8_t), uint8_t); + } + size_t remaining = len % (size_t)8U; + if (remaining > (size_t)0U) { + Eurydice_slice uu____1 = Eurydice_slice_subslice3( + out, start + len - remaining, start + len, uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, octets / (size_t)5U, + octets % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____1, + Eurydice_array_to_subslice3(ret, (size_t)0U, remaining, uint8_t *), + uint8_t); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Squeeze1 for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.squeeze_13 +with const generics +- RATE= 136 +*/ +static inline void libcrux_sha3_simd_portable_squeeze_13_5b( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start, size_t len) { + libcrux_sha3_simd_portable_store_block_5b(self->st, out, start, len); +} + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.portable.keccak1 +with const generics +- RATE= 136 +- DELIM= 6 +*/ +static inline void libcrux_sha3_generic_keccak_portable_keccak1_ad( + Eurydice_slice data, Eurydice_slice out) { + libcrux_sha3_generic_keccak_KeccakState_17 s = + libcrux_sha3_generic_keccak_new_80_04(); + size_t data_len = Eurydice_slice_len(data, uint8_t); + for (size_t i = (size_t)0U; i < data_len / (size_t)136U; i++) { + size_t i0 = i; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_block_80_c60(&s, buf, i0 * (size_t)136U); + } + size_t rem = data_len % (size_t)136U; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_final_80_9e0(&s, buf, data_len - rem, rem); + size_t outlen = Eurydice_slice_len(out, uint8_t); + size_t blocks = outlen / (size_t)136U; + size_t last = outlen - outlen % (size_t)136U; + if (blocks == (size_t)0U) { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, outlen); + } else { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, (size_t)136U); + for (size_t i = (size_t)1U; i < blocks; i++) { + size_t i0 = i; + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, i0 * (size_t)136U, + (size_t)136U); + } + if (last < outlen) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, last, outlen - last); + } + } +} + +/** + A portable SHA3 256 implementation. +*/ +static KRML_MUSTINLINE void libcrux_sha3_portable_sha256(Eurydice_slice digest, + Eurydice_slice data) { + libcrux_sha3_generic_keccak_portable_keccak1_ad(data, digest); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 136 +- DELIMITER= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_ad0( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[136U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 31U; + size_t uu____0 = (size_t)136U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_5b( + state, Eurydice_array_to_slice((size_t)136U, buffer, uint8_t), + (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 136 +- DELIMITER= 31 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_ad0( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_ad0(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 136 +- DELIM= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e1( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_ad0(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.portable.keccak1 +with const generics +- RATE= 136 +- DELIM= 31 +*/ +static inline void libcrux_sha3_generic_keccak_portable_keccak1_ad0( + Eurydice_slice data, Eurydice_slice out) { + libcrux_sha3_generic_keccak_KeccakState_17 s = + libcrux_sha3_generic_keccak_new_80_04(); + size_t data_len = Eurydice_slice_len(data, uint8_t); + for (size_t i = (size_t)0U; i < data_len / (size_t)136U; i++) { + size_t i0 = i; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_block_80_c60(&s, buf, i0 * (size_t)136U); + } + size_t rem = data_len % (size_t)136U; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_final_80_9e1(&s, buf, data_len - rem, rem); + size_t outlen = Eurydice_slice_len(out, uint8_t); + size_t blocks = outlen / (size_t)136U; + size_t last = outlen - outlen % (size_t)136U; + if (blocks == (size_t)0U) { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, outlen); + } else { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, (size_t)136U); + for (size_t i = (size_t)1U; i < blocks; i++) { + size_t i0 = i; + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, i0 * (size_t)136U, + (size_t)136U); + } + if (last < outlen) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, last, outlen - last); + } + } +} + +/** + A portable SHAKE256 implementation. +*/ +static KRML_MUSTINLINE void libcrux_sha3_portable_shake256( + Eurydice_slice digest, Eurydice_slice data) { + libcrux_sha3_generic_keccak_portable_keccak1_ad0(data, digest); +} + +typedef libcrux_sha3_generic_keccak_KeccakState_17 + libcrux_sha3_portable_KeccakState; + +/** + Create a new SHAKE-128 state object. +*/ +static KRML_MUSTINLINE libcrux_sha3_generic_keccak_KeccakState_17 +libcrux_sha3_portable_incremental_shake128_init(void) { + return libcrux_sha3_generic_keccak_new_80_04(); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block +with const generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_block_3a( + uint64_t *state, Eurydice_slice blocks, size_t start) { + uint64_t state_flat[25U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)168U / (size_t)8U; i++) { + size_t i0 = i; + size_t offset = start + (size_t)8U * i0; + uint8_t uu____0[8U]; + Result_15 dst; + Eurydice_slice_to_array2( + &dst, + Eurydice_slice_subslice3(blocks, offset, offset + (size_t)8U, + uint8_t *), + Eurydice_slice, uint8_t[8U], TryFromSliceError); + unwrap_26_68(dst, uu____0); + state_flat[i0] = core_num__u64__from_le_bytes(uu____0); + } + for (size_t i = (size_t)0U; i < (size_t)168U / (size_t)8U; i++) { + size_t i0 = i; + libcrux_sha3_traits_set_ij_04( + state, i0 / (size_t)5U, i0 % (size_t)5U, + libcrux_sha3_traits_get_ij_04(state, i0 / (size_t)5U, + i0 % (size_t)5U)[0U] ^ + state_flat[i0]); + } +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 168 +- DELIMITER= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_c6( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[168U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 31U; + size_t uu____0 = (size_t)168U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_3a( + state, Eurydice_array_to_slice((size_t)168U, buffer, uint8_t), + (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 168 +- DELIMITER= 31 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_c6( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_c6(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 168 +- DELIM= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e2( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_c6(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** + Absorb +*/ +static KRML_MUSTINLINE void +libcrux_sha3_portable_incremental_shake128_absorb_final( + libcrux_sha3_generic_keccak_KeccakState_17 *s, Eurydice_slice data0) { + libcrux_sha3_generic_keccak_KeccakState_17 *uu____0 = s; + Eurydice_slice uu____1[1U] = {data0}; + libcrux_sha3_generic_keccak_absorb_final_80_9e2( + uu____0, uu____1, (size_t)0U, Eurydice_slice_len(data0, uint8_t)); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.store_block +with const generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_store_block_3a( + uint64_t *s, Eurydice_slice out, size_t start, size_t len) { + size_t octets = len / (size_t)8U; + for (size_t i = (size_t)0U; i < octets; i++) { + size_t i0 = i; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, start + (size_t)8U * i0, start + (size_t)8U * i0 + (size_t)8U, + uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, i0 / (size_t)5U, i0 % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)8U, ret, uint8_t), uint8_t); + } + size_t remaining = len % (size_t)8U; + if (remaining > (size_t)0U) { + Eurydice_slice uu____1 = Eurydice_slice_subslice3( + out, start + len - remaining, start + len, uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, octets / (size_t)5U, + octets % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____1, + Eurydice_array_to_subslice3(ret, (size_t)0U, remaining, uint8_t *), + uint8_t); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Squeeze1 for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.squeeze_13 +with const generics +- RATE= 168 +*/ +static inline void libcrux_sha3_simd_portable_squeeze_13_3a( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start, size_t len) { + libcrux_sha3_simd_portable_store_block_3a(self->st, out, start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of +libcrux_sha3.generic_keccak.portable.squeeze_first_three_blocks_b4 with const +generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void +libcrux_sha3_generic_keccak_portable_squeeze_first_three_blocks_b4_3a( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out) { + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, (size_t)0U, (size_t)168U); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, (size_t)168U, + (size_t)168U); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, (size_t)2U * (size_t)168U, + (size_t)168U); +} + +/** + Squeeze three blocks +*/ +static KRML_MUSTINLINE void +libcrux_sha3_portable_incremental_shake128_squeeze_first_three_blocks( + libcrux_sha3_generic_keccak_KeccakState_17 *s, Eurydice_slice out0) { + libcrux_sha3_generic_keccak_portable_squeeze_first_three_blocks_b4_3a(s, + out0); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of +libcrux_sha3.generic_keccak.portable.squeeze_next_block_b4 with const generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void +libcrux_sha3_generic_keccak_portable_squeeze_next_block_b4_3a( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, start, (size_t)168U); +} + +/** + Squeeze another block +*/ +static KRML_MUSTINLINE void +libcrux_sha3_portable_incremental_shake128_squeeze_next_block( + libcrux_sha3_generic_keccak_KeccakState_17 *s, Eurydice_slice out0) { + libcrux_sha3_generic_keccak_portable_squeeze_next_block_b4_3a(s, out0, + (size_t)0U); +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_sha3_portable_H_DEFINED +#endif /* libcrux_sha3_portable_H */ + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_mlkem768_portable.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_mlkem768_portable_H +#define libcrux_mlkem768_portable_H + + +#if defined(__cplusplus) +extern "C" { +#endif + + +static inline void libcrux_ml_kem_hash_functions_portable_G( + Eurydice_slice input, uint8_t ret[64U]) { + uint8_t digest[64U] = {0U}; + libcrux_sha3_portable_sha512( + Eurydice_array_to_slice((size_t)64U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)64U * sizeof(uint8_t)); +} + +static inline void libcrux_ml_kem_hash_functions_portable_H( + Eurydice_slice input, uint8_t ret[32U]) { + uint8_t digest[32U] = {0U}; + libcrux_sha3_portable_sha256( + Eurydice_array_to_slice((size_t)32U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)32U * sizeof(uint8_t)); +} + +static const int16_t libcrux_ml_kem_polynomial_ZETAS_TIMES_MONTGOMERY_R[128U] = + {(int16_t)-1044, (int16_t)-758, (int16_t)-359, (int16_t)-1517, + (int16_t)1493, (int16_t)1422, (int16_t)287, (int16_t)202, + (int16_t)-171, (int16_t)622, (int16_t)1577, (int16_t)182, + (int16_t)962, (int16_t)-1202, (int16_t)-1474, (int16_t)1468, + (int16_t)573, (int16_t)-1325, (int16_t)264, (int16_t)383, + (int16_t)-829, (int16_t)1458, (int16_t)-1602, (int16_t)-130, + (int16_t)-681, (int16_t)1017, (int16_t)732, (int16_t)608, + (int16_t)-1542, (int16_t)411, (int16_t)-205, (int16_t)-1571, + (int16_t)1223, (int16_t)652, (int16_t)-552, (int16_t)1015, + (int16_t)-1293, (int16_t)1491, (int16_t)-282, (int16_t)-1544, + (int16_t)516, (int16_t)-8, (int16_t)-320, (int16_t)-666, + (int16_t)-1618, (int16_t)-1162, (int16_t)126, (int16_t)1469, + (int16_t)-853, (int16_t)-90, (int16_t)-271, (int16_t)830, + (int16_t)107, (int16_t)-1421, (int16_t)-247, (int16_t)-951, + (int16_t)-398, (int16_t)961, (int16_t)-1508, (int16_t)-725, + (int16_t)448, (int16_t)-1065, (int16_t)677, (int16_t)-1275, + (int16_t)-1103, (int16_t)430, (int16_t)555, (int16_t)843, + (int16_t)-1251, (int16_t)871, (int16_t)1550, (int16_t)105, + (int16_t)422, (int16_t)587, (int16_t)177, (int16_t)-235, + (int16_t)-291, (int16_t)-460, (int16_t)1574, (int16_t)1653, + (int16_t)-246, (int16_t)778, (int16_t)1159, (int16_t)-147, + (int16_t)-777, (int16_t)1483, (int16_t)-602, (int16_t)1119, + (int16_t)-1590, (int16_t)644, (int16_t)-872, (int16_t)349, + (int16_t)418, (int16_t)329, (int16_t)-156, (int16_t)-75, + (int16_t)817, (int16_t)1097, (int16_t)603, (int16_t)610, + (int16_t)1322, (int16_t)-1285, (int16_t)-1465, (int16_t)384, + (int16_t)-1215, (int16_t)-136, (int16_t)1218, (int16_t)-1335, + (int16_t)-874, (int16_t)220, (int16_t)-1187, (int16_t)-1659, + (int16_t)-1185, (int16_t)-1530, (int16_t)-1278, (int16_t)794, + (int16_t)-1510, (int16_t)-854, (int16_t)-870, (int16_t)478, + (int16_t)-108, (int16_t)-308, (int16_t)996, (int16_t)991, + (int16_t)958, (int16_t)-1460, (int16_t)1522, (int16_t)1628}; + +static KRML_MUSTINLINE int16_t libcrux_ml_kem_polynomial_zeta(size_t i) { + return libcrux_ml_kem_polynomial_ZETAS_TIMES_MONTGOMERY_R[i]; +} + +#define LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT ((size_t)16U) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR ((size_t)16U) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_MONTGOMERY_R_SQUARED_MOD_FIELD_MODULUS \ + ((int16_t)1353) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS ((int16_t)3329) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_INVERSE_OF_MODULUS_MOD_MONTGOMERY_R \ + (62209U) + +typedef struct libcrux_ml_kem_vector_portable_vector_type_PortableVector_s { + int16_t elements[16U]; +} libcrux_ml_kem_vector_portable_vector_type_PortableVector; + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_vector_type_from_i16_array( + Eurydice_slice array) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector lit; + int16_t ret[16U]; + Result_0a dst; + Eurydice_slice_to_array2( + &dst, Eurydice_slice_subslice3(array, (size_t)0U, (size_t)16U, int16_t *), + Eurydice_slice, int16_t[16U], TryFromSliceError); + unwrap_26_00(dst, ret); + memcpy(lit.elements, ret, (size_t)16U * sizeof(int16_t)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_from_i16_array_b8(Eurydice_slice array) { + return libcrux_ml_kem_vector_portable_vector_type_from_i16_array( + libcrux_secrets_int_classify_public_classify_ref_9b_39(array)); +} + +typedef struct int16_t_x8_s { + int16_t fst; + int16_t snd; + int16_t thd; + int16_t f3; + int16_t f4; + int16_t f5; + int16_t f6; + int16_t f7; +} int16_t_x8; + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_vector_type_zero(void) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector lit; + int16_t ret[16U]; + int16_t buf[16U] = {0U}; + libcrux_secrets_int_public_integers_classify_27_46(buf, ret); + memcpy(lit.elements, ret, (size_t)16U * sizeof(int16_t)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ZERO_b8(void) { + return libcrux_ml_kem_vector_portable_vector_type_zero(); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_add( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + lhs.elements[uu____0] = lhs.elements[uu____0] + rhs->elements[i0]; + } + return lhs; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_add_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + return libcrux_ml_kem_vector_portable_arithmetic_add(lhs, rhs); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_sub( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + lhs.elements[uu____0] = lhs.elements[uu____0] - rhs->elements[i0]; + } + return lhs; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_sub_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + return libcrux_ml_kem_vector_portable_arithmetic_sub(lhs, rhs); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_multiply_by_constant( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + vec.elements[uu____0] = vec.elements[uu____0] * c; + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_multiply_by_constant_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + return libcrux_ml_kem_vector_portable_arithmetic_multiply_by_constant(vec, c); +} + +/** + Note: This function is not secret independent + Only use with public values. +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_cond_subtract_3329( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + if (libcrux_secrets_int_public_integers_declassify_d8_39( + vec.elements[i0]) >= (int16_t)3329) { + size_t uu____0 = i0; + vec.elements[uu____0] = vec.elements[uu____0] - (int16_t)3329; + } + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_cond_subtract_3329_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v) { + return libcrux_ml_kem_vector_portable_arithmetic_cond_subtract_3329(v); +} + +#define LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_BARRETT_MULTIPLIER \ + ((int32_t)20159) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_SHIFT ((int32_t)26) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_R \ + ((int32_t)1 << (uint32_t)LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_SHIFT) + +/** + Signed Barrett Reduction + + Given an input `value`, `barrett_reduce` outputs a representative `result` + such that: + + - result ≡ value (mod FIELD_MODULUS) + - the absolute value of `result` is bound as follows: + + `|result| ≤ FIELD_MODULUS / 2 · (|value|/BARRETT_R + 1) + + Note: The input bound is 28296 to prevent overflow in the multiplication of + quotient by FIELD_MODULUS + +*/ +static inline int16_t +libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce_element( + int16_t value) { + int32_t t = libcrux_secrets_int_as_i32_f5(value) * + LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_BARRETT_MULTIPLIER + + (LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_R >> 1U); + int16_t quotient = libcrux_secrets_int_as_i16_36( + t >> (uint32_t)LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_SHIFT); + return value - quotient * LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int16_t vi = + libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce_element( + vec.elements[i0]); + vec.elements[i0] = vi; + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_barrett_reduce_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vector) { + return libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce(vector); +} + +#define LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_MONTGOMERY_SHIFT (16U) + +/** + Signed Montgomery Reduction + + Given an input `value`, `montgomery_reduce` outputs a representative `o` + such that: + + - o ≡ value · MONTGOMERY_R^(-1) (mod FIELD_MODULUS) + - the absolute value of `o` is bound as follows: + + `|result| ≤ ceil(|value| / MONTGOMERY_R) + 1665 + + In particular, if `|value| ≤ FIELD_MODULUS-1 * FIELD_MODULUS-1`, then `|o| <= + FIELD_MODULUS-1`. And, if `|value| ≤ pow2 16 * FIELD_MODULUS-1`, then `|o| <= + FIELD_MODULUS + 1664 + +*/ +static inline int16_t +libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + int32_t value) { + int32_t k = + libcrux_secrets_int_as_i32_f5(libcrux_secrets_int_as_i16_36(value)) * + libcrux_secrets_int_as_i32_b8( + libcrux_secrets_int_public_integers_classify_27_df( + LIBCRUX_ML_KEM_VECTOR_TRAITS_INVERSE_OF_MODULUS_MOD_MONTGOMERY_R)); + int32_t k_times_modulus = + libcrux_secrets_int_as_i32_f5(libcrux_secrets_int_as_i16_36(k)) * + libcrux_secrets_int_as_i32_f5( + libcrux_secrets_int_public_integers_classify_27_39( + LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS)); + int16_t c = libcrux_secrets_int_as_i16_36( + k_times_modulus >> + (uint32_t)LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_MONTGOMERY_SHIFT); + int16_t value_high = libcrux_secrets_int_as_i16_36( + value >> + (uint32_t)LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_MONTGOMERY_SHIFT); + return value_high - c; +} + +/** + If `fe` is some field element 'x' of the Kyber field and `fer` is congruent to + `y · MONTGOMERY_R`, this procedure outputs a value that is congruent to + `x · y`, as follows: + + `fe · fer ≡ x · y · MONTGOMERY_R (mod FIELD_MODULUS)` + + `montgomery_reduce` takes the value `x · y · MONTGOMERY_R` and outputs a + representative `x · y · MONTGOMERY_R * MONTGOMERY_R^{-1} ≡ x · y (mod + FIELD_MODULUS)`. +*/ +static KRML_MUSTINLINE int16_t +libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + int16_t fe, int16_t fer) { + int32_t product = + libcrux_secrets_int_as_i32_f5(fe) * libcrux_secrets_int_as_i32_f5(fer); + return libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + product); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_by_constant( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + vec.elements[i0] = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + vec.elements[i0], c); + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vector, + int16_t constant) { + return libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_by_constant( + vector, libcrux_secrets_int_public_integers_classify_27_39(constant)); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_bitwise_and_with_constant( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + vec.elements[uu____0] = vec.elements[uu____0] & c; + } + return vec; +} + +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.arithmetic.shift_right +with const generics +- SHIFT_BY= 15 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_shift_right_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + vec.elements[i0] = vec.elements[i0] >> (uint32_t)(int32_t)15; + } + return vec; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_to_unsigned_representative( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector t = + libcrux_ml_kem_vector_portable_arithmetic_shift_right_ef(a); + libcrux_ml_kem_vector_portable_vector_type_PortableVector fm = + libcrux_ml_kem_vector_portable_arithmetic_bitwise_and_with_constant( + t, LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS); + return libcrux_ml_kem_vector_portable_arithmetic_add(a, &fm); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_to_unsigned_representative_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_arithmetic_to_unsigned_representative( + a); +} + +/** + The `compress_*` functions implement the `Compress` function specified in the + NIST FIPS 203 standard (Page 18, Expression 4.5), which is defined as: + + ```plaintext + Compress_d: ℤq -> ℤ_{2ᵈ} + Compress_d(x) = ⌈(2ᵈ/q)·x⌋ + ``` + + Since `⌈x⌋ = ⌊x + 1/2⌋` we have: + + ```plaintext + Compress_d(x) = ⌊(2ᵈ/q)·x + 1/2⌋ + = ⌊(2^{d+1}·x + q) / 2q⌋ + ``` + + For further information about the function implementations, consult the + `implementation_notes.pdf` document in this directory. + + The NIST FIPS 203 standard can be found at + . +*/ +static inline uint8_t +libcrux_ml_kem_vector_portable_compress_compress_message_coefficient( + uint16_t fe) { + int16_t shifted = + libcrux_secrets_int_public_integers_classify_27_39((int16_t)1664) - + libcrux_secrets_int_as_i16_ca(fe); + int16_t mask = shifted >> 15U; + int16_t shifted_to_positive = mask ^ shifted; + int16_t shifted_positive_in_range = shifted_to_positive - (int16_t)832; + int16_t r0 = shifted_positive_in_range >> 15U; + int16_t r1 = r0 & (int16_t)1; + return libcrux_secrets_int_as_u8_f5(r1); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_compress_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + a.elements[i0] = libcrux_secrets_int_as_i16_59( + libcrux_ml_kem_vector_portable_compress_compress_message_coefficient( + libcrux_secrets_int_as_u16_f5(a.elements[i0]))); + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_1_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_compress_1(a); +} + +static KRML_MUSTINLINE uint32_t +libcrux_ml_kem_vector_portable_arithmetic_get_n_least_significant_bits( + uint8_t n, uint32_t value) { + return value & ((1U << (uint32_t)n) - 1U); +} + +static inline int16_t +libcrux_ml_kem_vector_portable_compress_compress_ciphertext_coefficient( + uint8_t coefficient_bits, uint16_t fe) { + uint64_t compressed = libcrux_secrets_int_as_u64_ca(fe) + << (uint32_t)coefficient_bits; + compressed = compressed + 1664ULL; + compressed = compressed * 10321340ULL; + compressed = compressed >> 35U; + return libcrux_secrets_int_as_i16_b8( + libcrux_ml_kem_vector_portable_arithmetic_get_n_least_significant_bits( + coefficient_bits, libcrux_secrets_int_as_u32_a3(compressed))); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_decompress_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector z = + libcrux_ml_kem_vector_portable_vector_type_zero(); + libcrux_ml_kem_vector_portable_vector_type_PortableVector s = + libcrux_ml_kem_vector_portable_arithmetic_sub(z, &a); + libcrux_ml_kem_vector_portable_vector_type_PortableVector res = + libcrux_ml_kem_vector_portable_arithmetic_bitwise_and_with_constant( + s, (int16_t)1665); + return res; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_decompress_1_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_decompress_1(a); +} + +static KRML_MUSTINLINE void libcrux_ml_kem_vector_portable_ntt_ntt_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *vec, + int16_t zeta, size_t i, size_t j) { + int16_t t = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + vec->elements[j], + libcrux_secrets_int_public_integers_classify_27_39(zeta)); + int16_t a_minus_t = vec->elements[i] - t; + int16_t a_plus_t = vec->elements[i] + t; + vec->elements[j] = a_minus_t; + vec->elements[i] = a_plus_t; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_layer_1_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)2U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)3U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)4U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)5U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta2, (size_t)8U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta2, (size_t)9U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta3, (size_t)12U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta3, (size_t)13U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_layer_1_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1, int16_t zeta2, int16_t zeta3) { + return libcrux_ml_kem_vector_portable_ntt_ntt_layer_1_step(a, zeta0, zeta1, + zeta2, zeta3); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_layer_2_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1) { + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)4U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)5U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)2U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)3U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)8U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)9U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)10U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)11U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_layer_2_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1) { + return libcrux_ml_kem_vector_portable_ntt_ntt_layer_2_step(a, zeta0, zeta1); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_layer_3_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta) { + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)0U, + (size_t)8U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)1U, + (size_t)9U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)2U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)3U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)4U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)5U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)6U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)7U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_layer_3_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta) { + return libcrux_ml_kem_vector_portable_ntt_ntt_layer_3_step(a, zeta); +} + +static KRML_MUSTINLINE void libcrux_ml_kem_vector_portable_ntt_inv_ntt_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *vec, + int16_t zeta, size_t i, size_t j) { + int16_t a_minus_b = vec->elements[j] - vec->elements[i]; + int16_t a_plus_b = vec->elements[j] + vec->elements[i]; + int16_t o0 = libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce_element( + a_plus_b); + int16_t o1 = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + a_minus_b, libcrux_secrets_int_public_integers_classify_27_39(zeta)); + vec->elements[i] = o0; + vec->elements[j] = o1; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_1_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)2U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)3U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)4U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)5U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta2, (size_t)8U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta2, (size_t)9U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta3, (size_t)12U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta3, (size_t)13U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_inv_ntt_layer_1_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1, int16_t zeta2, int16_t zeta3) { + return libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_1_step( + a, zeta0, zeta1, zeta2, zeta3); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_2_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1) { + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)4U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)5U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)2U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)3U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)8U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)9U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)10U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)11U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_inv_ntt_layer_2_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1) { + return libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_2_step(a, zeta0, + zeta1); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_3_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta) { + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)0U, + (size_t)8U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)1U, + (size_t)9U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)2U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)3U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)4U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)5U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)6U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)7U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_inv_ntt_layer_3_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta) { + return libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_3_step(a, zeta); +} + +/** + Compute the product of two Kyber binomials with respect to the + modulus `X² - zeta`. + + This function almost implements Algorithm 11 of the + NIST FIPS 203 standard, which is reproduced below: + + ```plaintext + Input: a₀, a₁, b₀, b₁ ∈ ℤq. + Input: γ ∈ ℤq. + Output: c₀, c₁ ∈ ℤq. + + c₀ ← a₀·b₀ + a₁·b₁·γ + c₁ ← a₀·b₁ + a₁·b₀ + return c₀, c₁ + ``` + We say "almost" because the coefficients output by this function are in + the Montgomery domain (unlike in the specification). + + The NIST FIPS 203 standard can be found at + . +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *a, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *b, int16_t zeta, + size_t i, libcrux_ml_kem_vector_portable_vector_type_PortableVector *out) { + int16_t ai = a->elements[(size_t)2U * i]; + int16_t bi = b->elements[(size_t)2U * i]; + int16_t aj = a->elements[(size_t)2U * i + (size_t)1U]; + int16_t bj = b->elements[(size_t)2U * i + (size_t)1U]; + int32_t ai_bi = + libcrux_secrets_int_as_i32_f5(ai) * libcrux_secrets_int_as_i32_f5(bi); + int32_t aj_bj_ = + libcrux_secrets_int_as_i32_f5(aj) * libcrux_secrets_int_as_i32_f5(bj); + int16_t aj_bj = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + aj_bj_); + int32_t aj_bj_zeta = libcrux_secrets_int_as_i32_f5(aj_bj) * + libcrux_secrets_int_as_i32_f5(zeta); + int32_t ai_bi_aj_bj = ai_bi + aj_bj_zeta; + int16_t o0 = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + ai_bi_aj_bj); + int32_t ai_bj = + libcrux_secrets_int_as_i32_f5(ai) * libcrux_secrets_int_as_i32_f5(bj); + int32_t aj_bi = + libcrux_secrets_int_as_i32_f5(aj) * libcrux_secrets_int_as_i32_f5(bi); + int32_t ai_bj_aj_bi = ai_bj + aj_bi; + int16_t o1 = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + ai_bj_aj_bi); + out->elements[(size_t)2U * i] = o0; + out->elements[(size_t)2U * i + (size_t)1U] = o1; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_multiply( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + int16_t nzeta0 = -zeta0; + int16_t nzeta1 = -zeta1; + int16_t nzeta2 = -zeta2; + int16_t nzeta3 = -zeta3; + libcrux_ml_kem_vector_portable_vector_type_PortableVector out = + libcrux_ml_kem_vector_portable_vector_type_zero(); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta0), + (size_t)0U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta0), + (size_t)1U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta1), + (size_t)2U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta1), + (size_t)3U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta2), + (size_t)4U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta2), + (size_t)5U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta3), + (size_t)6U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta3), + (size_t)7U, &out); + return out; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_multiply_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + return libcrux_ml_kem_vector_portable_ntt_ntt_multiply(lhs, rhs, zeta0, zeta1, + zeta2, zeta3); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[2U]) { + uint8_t result0 = + (((((((uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[0U]) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[1U]) << 1U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[2U]) << 2U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[3U]) << 3U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[4U]) << 4U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[5U]) << 5U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[6U]) << 6U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[7U]) << 7U; + uint8_t result1 = + (((((((uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[8U]) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[9U]) << 1U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[10U]) << 2U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[11U]) << 3U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[12U]) << 4U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[13U]) << 5U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[14U]) << 6U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[15U]) << 7U; + ret[0U] = result0; + ret[1U] = result1; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[2U]) { + uint8_t ret0[2U]; + libcrux_ml_kem_vector_portable_serialize_serialize_1(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_d4(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_1_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[2U]) { + libcrux_ml_kem_vector_portable_serialize_1(a, ret); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_1(Eurydice_slice v) { + int16_t result0 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) & 1U); + int16_t result1 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 1U & + 1U); + int16_t result2 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 2U & + 1U); + int16_t result3 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 3U & + 1U); + int16_t result4 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 4U & + 1U); + int16_t result5 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 5U & + 1U); + int16_t result6 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 6U & + 1U); + int16_t result7 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 7U & + 1U); + int16_t result8 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) & 1U); + int16_t result9 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 1U & + 1U); + int16_t result10 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 2U & + 1U); + int16_t result11 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 3U & + 1U); + int16_t result12 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 4U & + 1U); + int16_t result13 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 5U & + 1U); + int16_t result14 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 6U & + 1U); + int16_t result15 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 7U & + 1U); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {result0, result1, result2, result3, result4, result5, + result6, result7, result8, result9, result10, result11, + result12, result13, result14, result15}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_1(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_1( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_1_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_1(a); +} + +typedef struct uint8_t_x4_s { + uint8_t fst; + uint8_t snd; + uint8_t thd; + uint8_t f3; +} uint8_t_x4; + +static KRML_MUSTINLINE uint8_t_x4 +libcrux_ml_kem_vector_portable_serialize_serialize_4_int(Eurydice_slice v) { + uint8_t result0 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)0U, int16_t, int16_t *)); + uint8_t result1 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)3U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)2U, int16_t, int16_t *)); + uint8_t result2 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)5U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)4U, int16_t, int16_t *)); + uint8_t result3 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)7U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)6U, int16_t, int16_t *)); + return (KRML_CLITERAL(uint8_t_x4){ + .fst = result0, .snd = result1, .thd = result2, .f3 = result3}); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_4( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[8U]) { + uint8_t_x4 result0_3 = + libcrux_ml_kem_vector_portable_serialize_serialize_4_int( + Eurydice_array_to_subslice3(v.elements, (size_t)0U, (size_t)8U, + int16_t *)); + uint8_t_x4 result4_7 = + libcrux_ml_kem_vector_portable_serialize_serialize_4_int( + Eurydice_array_to_subslice3(v.elements, (size_t)8U, (size_t)16U, + int16_t *)); + ret[0U] = result0_3.fst; + ret[1U] = result0_3.snd; + ret[2U] = result0_3.thd; + ret[3U] = result0_3.f3; + ret[4U] = result4_7.fst; + ret[5U] = result4_7.snd; + ret[6U] = result4_7.thd; + ret[7U] = result4_7.f3; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_4( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[8U]) { + uint8_t ret0[8U]; + libcrux_ml_kem_vector_portable_serialize_serialize_4(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_76(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_4_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[8U]) { + libcrux_ml_kem_vector_portable_serialize_4(a, ret); +} + +static KRML_MUSTINLINE int16_t_x8 +libcrux_ml_kem_vector_portable_serialize_deserialize_4_int( + Eurydice_slice bytes) { + int16_t v0 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *) & + 15U); + int16_t v1 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *) >> + 4U & + 15U); + int16_t v2 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *) & + 15U); + int16_t v3 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *) >> + 4U & + 15U); + int16_t v4 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *) & + 15U); + int16_t v5 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *) >> + 4U & + 15U); + int16_t v6 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *) & + 15U); + int16_t v7 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *) >> + 4U & + 15U); + return (KRML_CLITERAL(int16_t_x8){.fst = v0, + .snd = v1, + .thd = v2, + .f3 = v3, + .f4 = v4, + .f5 = v5, + .f6 = v6, + .f7 = v7}); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_4(Eurydice_slice bytes) { + int16_t_x8 v0_7 = libcrux_ml_kem_vector_portable_serialize_deserialize_4_int( + Eurydice_slice_subslice3(bytes, (size_t)0U, (size_t)4U, uint8_t *)); + int16_t_x8 v8_15 = libcrux_ml_kem_vector_portable_serialize_deserialize_4_int( + Eurydice_slice_subslice3(bytes, (size_t)4U, (size_t)8U, uint8_t *)); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {v0_7.fst, v0_7.snd, v0_7.thd, v0_7.f3, v0_7.f4, v0_7.f5, + v0_7.f6, v0_7.f7, v8_15.fst, v8_15.snd, v8_15.thd, + v8_15.f3, v8_15.f4, v8_15.f5, v8_15.f6, v8_15.f7}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_4(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_4( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_4_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_4(a); +} + +typedef struct uint8_t_x5_s { + uint8_t fst; + uint8_t snd; + uint8_t thd; + uint8_t f3; + uint8_t f4; +} uint8_t_x5; + +static KRML_MUSTINLINE uint8_t_x5 +libcrux_ml_kem_vector_portable_serialize_serialize_10_int(Eurydice_slice v) { + uint8_t r0 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) & (int16_t)255); + uint8_t r1 = + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) & (int16_t)63) + << 2U | + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) >> 8U & + (int16_t)3); + uint8_t r2 = + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)2U, int16_t, int16_t *) & (int16_t)15) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) >> 6U & + (int16_t)15); + uint8_t r3 = + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)3U, int16_t, int16_t *) & (int16_t)3) + << 6U | + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)2U, int16_t, int16_t *) >> 4U & + (int16_t)63); + uint8_t r4 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)3U, int16_t, int16_t *) >> 2U & + (int16_t)255); + return (KRML_CLITERAL(uint8_t_x5){ + .fst = r0, .snd = r1, .thd = r2, .f3 = r3, .f4 = r4}); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_10( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[20U]) { + uint8_t_x5 r0_4 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)0U, (size_t)4U, + int16_t *)); + uint8_t_x5 r5_9 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)4U, (size_t)8U, + int16_t *)); + uint8_t_x5 r10_14 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)8U, (size_t)12U, + int16_t *)); + uint8_t_x5 r15_19 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)12U, (size_t)16U, + int16_t *)); + ret[0U] = r0_4.fst; + ret[1U] = r0_4.snd; + ret[2U] = r0_4.thd; + ret[3U] = r0_4.f3; + ret[4U] = r0_4.f4; + ret[5U] = r5_9.fst; + ret[6U] = r5_9.snd; + ret[7U] = r5_9.thd; + ret[8U] = r5_9.f3; + ret[9U] = r5_9.f4; + ret[10U] = r10_14.fst; + ret[11U] = r10_14.snd; + ret[12U] = r10_14.thd; + ret[13U] = r10_14.f3; + ret[14U] = r10_14.f4; + ret[15U] = r15_19.fst; + ret[16U] = r15_19.snd; + ret[17U] = r15_19.thd; + ret[18U] = r15_19.f3; + ret[19U] = r15_19.f4; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_10( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[20U]) { + uint8_t ret0[20U]; + libcrux_ml_kem_vector_portable_serialize_serialize_10(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_57(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_10_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[20U]) { + libcrux_ml_kem_vector_portable_serialize_10(a, ret); +} + +static KRML_MUSTINLINE int16_t_x8 +libcrux_ml_kem_vector_portable_serialize_deserialize_10_int( + Eurydice_slice bytes) { + int16_t r0 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *)) & + (int16_t)3) + << 8U | + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *)) & + (int16_t)255)); + int16_t r1 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *)) & + (int16_t)15) + << 6U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *)) >> + 2U); + int16_t r2 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *)) & + (int16_t)63) + << 4U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *)) >> + 4U); + int16_t r3 = libcrux_secrets_int_as_i16_f5( + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)4U, uint8_t, uint8_t *)) + << 2U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *)) >> + 6U); + int16_t r4 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)6U, uint8_t, uint8_t *)) & + (int16_t)3) + << 8U | + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)5U, uint8_t, uint8_t *)) & + (int16_t)255)); + int16_t r5 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)7U, uint8_t, uint8_t *)) & + (int16_t)15) + << 6U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)6U, uint8_t, uint8_t *)) >> + 2U); + int16_t r6 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)8U, uint8_t, uint8_t *)) & + (int16_t)63) + << 4U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)7U, uint8_t, uint8_t *)) >> + 4U); + int16_t r7 = libcrux_secrets_int_as_i16_f5( + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)9U, uint8_t, uint8_t *)) + << 2U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)8U, uint8_t, uint8_t *)) >> + 6U); + return (KRML_CLITERAL(int16_t_x8){.fst = r0, + .snd = r1, + .thd = r2, + .f3 = r3, + .f4 = r4, + .f5 = r5, + .f6 = r6, + .f7 = r7}); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_10(Eurydice_slice bytes) { + int16_t_x8 v0_7 = libcrux_ml_kem_vector_portable_serialize_deserialize_10_int( + Eurydice_slice_subslice3(bytes, (size_t)0U, (size_t)10U, uint8_t *)); + int16_t_x8 v8_15 = + libcrux_ml_kem_vector_portable_serialize_deserialize_10_int( + Eurydice_slice_subslice3(bytes, (size_t)10U, (size_t)20U, uint8_t *)); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {v0_7.fst, v0_7.snd, v0_7.thd, v0_7.f3, v0_7.f4, v0_7.f5, + v0_7.f6, v0_7.f7, v8_15.fst, v8_15.snd, v8_15.thd, + v8_15.f3, v8_15.f4, v8_15.f5, v8_15.f6, v8_15.f7}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_10(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_10( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_10_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_10(a); +} + +typedef struct uint8_t_x3_s { + uint8_t fst; + uint8_t snd; + uint8_t thd; +} uint8_t_x3; + +static KRML_MUSTINLINE uint8_t_x3 +libcrux_ml_kem_vector_portable_serialize_serialize_12_int(Eurydice_slice v) { + uint8_t r0 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) & (int16_t)255); + uint8_t r1 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) >> 8U | + (Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) & (int16_t)15) + << 4U); + uint8_t r2 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) >> 4U & + (int16_t)255); + return (KRML_CLITERAL(uint8_t_x3){.fst = r0, .snd = r1, .thd = r2}); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_12( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[24U]) { + uint8_t_x3 r0_2 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)0U, (size_t)2U, + int16_t *)); + uint8_t_x3 r3_5 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)2U, (size_t)4U, + int16_t *)); + uint8_t_x3 r6_8 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)4U, (size_t)6U, + int16_t *)); + uint8_t_x3 r9_11 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)6U, (size_t)8U, + int16_t *)); + uint8_t_x3 r12_14 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)8U, (size_t)10U, + int16_t *)); + uint8_t_x3 r15_17 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)10U, (size_t)12U, + int16_t *)); + uint8_t_x3 r18_20 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)12U, (size_t)14U, + int16_t *)); + uint8_t_x3 r21_23 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)14U, (size_t)16U, + int16_t *)); + ret[0U] = r0_2.fst; + ret[1U] = r0_2.snd; + ret[2U] = r0_2.thd; + ret[3U] = r3_5.fst; + ret[4U] = r3_5.snd; + ret[5U] = r3_5.thd; + ret[6U] = r6_8.fst; + ret[7U] = r6_8.snd; + ret[8U] = r6_8.thd; + ret[9U] = r9_11.fst; + ret[10U] = r9_11.snd; + ret[11U] = r9_11.thd; + ret[12U] = r12_14.fst; + ret[13U] = r12_14.snd; + ret[14U] = r12_14.thd; + ret[15U] = r15_17.fst; + ret[16U] = r15_17.snd; + ret[17U] = r15_17.thd; + ret[18U] = r18_20.fst; + ret[19U] = r18_20.snd; + ret[20U] = r18_20.thd; + ret[21U] = r21_23.fst; + ret[22U] = r21_23.snd; + ret[23U] = r21_23.thd; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_12( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[24U]) { + uint8_t ret0[24U]; + libcrux_ml_kem_vector_portable_serialize_serialize_12(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_d2(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_12_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[24U]) { + libcrux_ml_kem_vector_portable_serialize_12(a, ret); +} + +typedef struct int16_t_x2_s { + int16_t fst; + int16_t snd; +} int16_t_x2; + +static KRML_MUSTINLINE int16_t_x2 +libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice bytes) { + int16_t byte0 = libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *)); + int16_t byte1 = libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *)); + int16_t byte2 = libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *)); + int16_t r0 = (byte1 & (int16_t)15) << 8U | (byte0 & (int16_t)255); + int16_t r1 = byte2 << 4U | (byte1 >> 4U & (int16_t)15); + return (KRML_CLITERAL(int16_t_x2){.fst = r0, .snd = r1}); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_12(Eurydice_slice bytes) { + int16_t_x2 v0_1 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)0U, (size_t)3U, uint8_t *)); + int16_t_x2 v2_3 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)3U, (size_t)6U, uint8_t *)); + int16_t_x2 v4_5 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)6U, (size_t)9U, uint8_t *)); + int16_t_x2 v6_7 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)9U, (size_t)12U, uint8_t *)); + int16_t_x2 v8_9 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)12U, (size_t)15U, uint8_t *)); + int16_t_x2 v10_11 = + libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)15U, (size_t)18U, uint8_t *)); + int16_t_x2 v12_13 = + libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)18U, (size_t)21U, uint8_t *)); + int16_t_x2 v14_15 = + libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)21U, (size_t)24U, uint8_t *)); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {v0_1.fst, v0_1.snd, v2_3.fst, v2_3.snd, v4_5.fst, + v4_5.snd, v6_7.fst, v6_7.snd, v8_9.fst, v8_9.snd, + v10_11.fst, v10_11.snd, v12_13.fst, v12_13.snd, + v14_15.fst, v14_15.snd}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_12(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_12( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_12_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_12(a); +} + +static KRML_MUSTINLINE size_t +libcrux_ml_kem_vector_portable_sampling_rej_sample(Eurydice_slice a, + Eurydice_slice result) { + size_t sampled = (size_t)0U; + for (size_t i = (size_t)0U; i < Eurydice_slice_len(a, uint8_t) / (size_t)3U; + i++) { + size_t i0 = i; + int16_t b1 = (int16_t)Eurydice_slice_index(a, i0 * (size_t)3U + (size_t)0U, + uint8_t, uint8_t *); + int16_t b2 = (int16_t)Eurydice_slice_index(a, i0 * (size_t)3U + (size_t)1U, + uint8_t, uint8_t *); + int16_t b3 = (int16_t)Eurydice_slice_index(a, i0 * (size_t)3U + (size_t)2U, + uint8_t, uint8_t *); + int16_t d1 = (b2 & (int16_t)15) << 8U | b1; + int16_t d2 = b3 << 4U | b2 >> 4U; + if (d1 < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS) { + if (sampled < (size_t)16U) { + Eurydice_slice_index(result, sampled, int16_t, int16_t *) = d1; + sampled++; + } + } + if (d2 < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS) { + if (sampled < (size_t)16U) { + Eurydice_slice_index(result, sampled, int16_t, int16_t *) = d2; + sampled++; + } + } + } + return sampled; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline size_t libcrux_ml_kem_vector_portable_rej_sample_b8( + Eurydice_slice a, Eurydice_slice out) { + return libcrux_ml_kem_vector_portable_sampling_rej_sample(a, out); +} + +#define LIBCRUX_ML_KEM_MLKEM768_VECTOR_U_COMPRESSION_FACTOR ((size_t)10U) + +#define LIBCRUX_ML_KEM_MLKEM768_C1_BLOCK_SIZE \ + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_MLKEM768_VECTOR_U_COMPRESSION_FACTOR / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_RANK ((size_t)3U) + +#define LIBCRUX_ML_KEM_MLKEM768_C1_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_C1_BLOCK_SIZE * LIBCRUX_ML_KEM_MLKEM768_RANK) + +#define LIBCRUX_ML_KEM_MLKEM768_VECTOR_V_COMPRESSION_FACTOR ((size_t)4U) + +#define LIBCRUX_ML_KEM_MLKEM768_C2_SIZE \ + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_MLKEM768_VECTOR_V_COMPRESSION_FACTOR / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_CIPHERTEXT_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_C1_SIZE + LIBCRUX_ML_KEM_MLKEM768_C2_SIZE) + +#define LIBCRUX_ML_KEM_MLKEM768_T_AS_NTT_ENCODED_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_RANK * \ + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_COEFFICIENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_PUBLIC_KEY_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_T_AS_NTT_ENCODED_SIZE + (size_t)32U) + +#define LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_SECRET_KEY_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_RANK * \ + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_COEFFICIENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA1 ((size_t)2U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA1_RANDOMNESS_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_ETA1 * (size_t)64U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA2 ((size_t)2U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA2_RANDOMNESS_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_ETA2 * (size_t)64U) + +#define LIBCRUX_ML_KEM_MLKEM768_IMPLICIT_REJECTION_HASH_INPUT_SIZE \ + (LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE + \ + LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_CIPHERTEXT_SIZE) + +typedef libcrux_ml_kem_types_MlKemPrivateKey_d9 + libcrux_ml_kem_mlkem768_MlKem768PrivateKey; + +typedef libcrux_ml_kem_types_MlKemPublicKey_30 + libcrux_ml_kem_mlkem768_MlKem768PublicKey; + +#define LIBCRUX_ML_KEM_MLKEM768_RANKED_BYTES_PER_RING_ELEMENT \ + (LIBCRUX_ML_KEM_MLKEM768_RANK * \ + LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_SECRET_KEY_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_SECRET_KEY_SIZE + \ + LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_PUBLIC_KEY_SIZE + \ + LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE + \ + LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE) + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.PolynomialRingElement +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector + +*/ +typedef struct libcrux_ml_kem_polynomial_PolynomialRingElement_1d_s { + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficients[16U]; +} libcrux_ml_kem_polynomial_PolynomialRingElement_1d; + +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.unpacked.IndCpaPrivateKeyUnpacked with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- $3size_t +*/ +typedef struct libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0_s { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d secret_as_ntt[3U]; +} libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0; + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ZERO_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ZERO_d6_ea(void) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d lit; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + repeat_expression[16U]; + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + repeat_expression[i] = libcrux_ml_kem_vector_portable_ZERO_b8(); + } + memcpy(lit.coefficients, repeat_expression, + (size_t)16U * + sizeof(libcrux_ml_kem_vector_portable_vector_type_PortableVector)); + return lit; +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for libcrux_ml_kem::ind_cpa::decrypt::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.decrypt.call_mut_0b +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- VECTOR_U_ENCODED_SIZE= 960 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_decrypt_call_mut_0b_42(void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_to_uncompressed_ring_element with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_to_uncompressed_ring_element_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)24U; i++) { + size_t i0 = i; + Eurydice_slice bytes = + Eurydice_slice_subslice3(serialized, i0 * (size_t)24U, + i0 * (size_t)24U + (size_t)24U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_deserialize_12_b8(bytes); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** + Call [`deserialize_to_uncompressed_ring_element`] for each ring element. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.deserialize_vector +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_deserialize_vector_1b( + Eurydice_slice secret_key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *secret_as_ntt) { + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_serialize_deserialize_to_uncompressed_ring_element_ea( + Eurydice_slice_subslice3( + secret_key, + i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + (i0 + (size_t)1U) * + LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + uint8_t *)); + secret_as_ntt[i0] = uu____0; + } +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for +libcrux_ml_kem::ind_cpa::deserialize_then_decompress_u::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.deserialize_then_decompress_u.call_mut_35 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- U_COMPRESSION_FACTOR= 10 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_call_mut_35_6c( + void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.compress.decompress_ciphertext_coefficient with +const generics +- COEFFICIENT_BITS= 10 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int32_t decompressed = + libcrux_secrets_int_as_i32_f5(a.elements[i0]) * + libcrux_secrets_int_as_i32_f5( + libcrux_secrets_int_public_integers_classify_27_39( + LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS)); + decompressed = (decompressed << 1U) + ((int32_t)1 << (uint32_t)(int32_t)10); + decompressed = decompressed >> (uint32_t)((int32_t)10 + (int32_t)1); + a.elements[i0] = libcrux_secrets_int_as_i16_36(decompressed); + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.decompress_ciphertext_coefficient_b8 with const +generics +- COEFFICIENT_BITS= 10 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_ef( + a); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_10 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_10_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)20U; i++) { + size_t i0 = i; + Eurydice_slice bytes = + Eurydice_slice_subslice3(serialized, i0 * (size_t)20U, + i0 * (size_t)20U + (size_t)20U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_deserialize_10_b8(bytes); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_ef( + coefficient); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_ring_element_u with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- COMPRESSION_FACTOR= 10 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_u_0a( + Eurydice_slice serialized) { + return libcrux_ml_kem_serialize_deserialize_then_decompress_10_ea(serialized); +} + +typedef struct libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2_s { + libcrux_ml_kem_vector_portable_vector_type_PortableVector fst; + libcrux_ml_kem_vector_portable_vector_type_PortableVector snd; +} libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2; + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_layer_int_vec_step +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 + libcrux_ml_kem_ntt_ntt_layer_int_vec_step_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + libcrux_ml_kem_vector_portable_vector_type_PortableVector b, + int16_t zeta_r) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector t = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8(b, + zeta_r); + b = libcrux_ml_kem_vector_portable_sub_b8(a, &t); + a = libcrux_ml_kem_vector_portable_add_b8(a, &t); + return (KRML_CLITERAL( + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2){.fst = a, + .snd = b}); +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_4_plus +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t layer, size_t _initial_coefficient_bound) { + size_t step = (size_t)1U << (uint32_t)layer; + for (size_t i0 = (size_t)0U; i0 < (size_t)128U >> (uint32_t)layer; i0++) { + size_t round = i0; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + size_t offset = round * step * (size_t)2U; + size_t offset_vec = offset / (size_t)16U; + size_t step_vec = step / (size_t)16U; + for (size_t i = offset_vec; i < offset_vec + step_vec; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 uu____0 = + libcrux_ml_kem_ntt_ntt_layer_int_vec_step_ea( + re->coefficients[j], re->coefficients[j + step_vec], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + libcrux_ml_kem_vector_portable_vector_type_PortableVector x = uu____0.fst; + libcrux_ml_kem_vector_portable_vector_type_PortableVector y = uu____0.snd; + re->coefficients[j] = x; + re->coefficients[j + step_vec] = y; + } + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_3 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_3_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t _initial_coefficient_bound) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_ntt_layer_3_step_b8( + re->coefficients[round], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + re->coefficients[round] = uu____0; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_2 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_2_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t _initial_coefficient_bound) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_ntt_layer_2_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)1U)); + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_1_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t _initial_coefficient_bound) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_ntt_layer_1_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)1U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)2U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)3U)); + zeta_i[0U] = zeta_i[0U] + (size_t)3U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.poly_barrett_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_poly_barrett_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_barrett_reduce_b8( + myself->coefficients[i0]); + myself->coefficients[i0] = uu____0; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.poly_barrett_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self) { + libcrux_ml_kem_polynomial_poly_barrett_reduce_ea(self); +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_vector_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- VECTOR_U_COMPRESSION_FACTOR= 10 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_vector_u_0a( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + size_t zeta_i = (size_t)0U; + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)7U, + (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)6U, + (size_t)2U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)5U, + (size_t)3U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)4U, + (size_t)4U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_3_ea(&zeta_i, re, (size_t)5U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_2_ea(&zeta_i, re, (size_t)6U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_1_ea(&zeta_i, re, (size_t)7U * (size_t)3328U); + libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea(re); +} + +/** + Call [`deserialize_then_decompress_ring_element_u`] on each ring element + in the `ciphertext`. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.deserialize_then_decompress_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- U_COMPRESSION_FACTOR= 10 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_6c( + uint8_t *ciphertext, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d u_as_ntt[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + u_as_ntt[i] = + libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_call_mut_35_6c( + &lvalue, i); + } + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice((size_t)1088U, ciphertext, uint8_t), + uint8_t) / + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U); + i++) { + size_t i0 = i; + Eurydice_slice u_bytes = Eurydice_array_to_subslice3( + ciphertext, + i0 * (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U), + i0 * (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U) + + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U, + uint8_t *); + u_as_ntt[i0] = + libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_u_0a( + u_bytes); + libcrux_ml_kem_ntt_ntt_vector_u_0a(&u_as_ntt[i0]); + } + memcpy( + ret, u_as_ntt, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.compress.decompress_ciphertext_coefficient with +const generics +- COEFFICIENT_BITS= 4 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int32_t decompressed = + libcrux_secrets_int_as_i32_f5(a.elements[i0]) * + libcrux_secrets_int_as_i32_f5( + libcrux_secrets_int_public_integers_classify_27_39( + LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS)); + decompressed = (decompressed << 1U) + ((int32_t)1 << (uint32_t)(int32_t)4); + decompressed = decompressed >> (uint32_t)((int32_t)4 + (int32_t)1); + a.elements[i0] = libcrux_secrets_int_as_i16_36(decompressed); + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.decompress_ciphertext_coefficient_b8 with const +generics +- COEFFICIENT_BITS= 4 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_d1( + a); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.deserialize_then_decompress_4 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_4_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)8U; i++) { + size_t i0 = i; + Eurydice_slice bytes = Eurydice_slice_subslice3( + serialized, i0 * (size_t)8U, i0 * (size_t)8U + (size_t)8U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_deserialize_4_b8(bytes); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_d1( + coefficient); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_ring_element_v with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- COMPRESSION_FACTOR= 4 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_v_89( + Eurydice_slice serialized) { + return libcrux_ml_kem_serialize_deserialize_then_decompress_4_ea(serialized); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ZERO +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ZERO_ea(void) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d lit; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + repeat_expression[16U]; + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + repeat_expression[i] = libcrux_ml_kem_vector_portable_ZERO_b8(); + } + memcpy(lit.coefficients, repeat_expression, + (size_t)16U * + sizeof(libcrux_ml_kem_vector_portable_vector_type_PortableVector)); + return lit; +} + +/** + Given two `KyberPolynomialRingElement`s in their NTT representations, + compute their product. Given two polynomials in the NTT domain `f^` and `ĵ`, + the `iᵗʰ` coefficient of the product `k̂` is determined by the calculation: + + ```plaintext + ĥ[2·i] + ĥ[2·i + 1]X = (f^[2·i] + f^[2·i + 1]X)·(ĝ[2·i] + ĝ[2·i + 1]X) mod (X² + - ζ^(2·BitRev₇(i) + 1)) + ``` + + This function almost implements Algorithm 10 of the + NIST FIPS 203 standard, which is reproduced below: + + ```plaintext + Input: Two arrays fˆ ∈ ℤ₂₅₆ and ĝ ∈ ℤ₂₅₆. + Output: An array ĥ ∈ ℤq. + + for(i ← 0; i < 128; i++) + (ĥ[2i], ĥ[2i+1]) ← BaseCaseMultiply(fˆ[2i], fˆ[2i+1], ĝ[2i], ĝ[2i+1], + ζ^(2·BitRev₇(i) + 1)) end for return ĥ + ``` + We say "almost" because the coefficients of the ring element output by + this function are in the Montgomery domain. + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ntt_multiply +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ntt_multiply_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d out = + libcrux_ml_kem_polynomial_ZERO_ea(); + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_ntt_multiply_b8( + &myself->coefficients[i0], &rhs->coefficients[i0], + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0), + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0 + + (size_t)1U), + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0 + + (size_t)2U), + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0 + + (size_t)3U)); + out.coefficients[i0] = uu____0; + } + return out; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ntt_multiply_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ntt_multiply_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + return libcrux_ml_kem_polynomial_ntt_multiply_ea(self, rhs); +} + +/** + Given two polynomial ring elements `lhs` and `rhs`, compute the pointwise + sum of their constituent coefficients. +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_to_ring_element +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_to_ring_element_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)16U, myself->coefficients, + libcrux_ml_kem_vector_portable_vector_type_PortableVector), + libcrux_ml_kem_vector_portable_vector_type_PortableVector); + i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_add_b8(myself->coefficients[i0], + &rhs->coefficients[i0]); + myself->coefficients[i0] = uu____0; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_to_ring_element_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + libcrux_ml_kem_polynomial_add_to_ring_element_1b(self, rhs); +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_1_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_inv_ntt_layer_1_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)1U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)2U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)3U)); + zeta_i[0U] = zeta_i[0U] - (size_t)3U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_2 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_2_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_inv_ntt_layer_2_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)1U)); + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_3 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_3_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_inv_ntt_layer_3_step_b8( + re->coefficients[round], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + re->coefficients[round] = uu____0; + } +} + +/** +A monomorphic instance of +libcrux_ml_kem.invert_ntt.inv_ntt_layer_int_vec_step_reduce with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 + libcrux_ml_kem_invert_ntt_inv_ntt_layer_int_vec_step_reduce_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + libcrux_ml_kem_vector_portable_vector_type_PortableVector b, + int16_t zeta_r) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector a_minus_b = + libcrux_ml_kem_vector_portable_sub_b8(b, &a); + a = libcrux_ml_kem_vector_portable_barrett_reduce_b8( + libcrux_ml_kem_vector_portable_add_b8(a, &b)); + b = libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + a_minus_b, zeta_r); + return (KRML_CLITERAL( + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2){.fst = a, + .snd = b}); +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_4_plus +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t layer) { + size_t step = (size_t)1U << (uint32_t)layer; + for (size_t i0 = (size_t)0U; i0 < (size_t)128U >> (uint32_t)layer; i0++) { + size_t round = i0; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + size_t offset = round * step * (size_t)2U; + size_t offset_vec = + offset / LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; + size_t step_vec = + step / LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; + for (size_t i = offset_vec; i < offset_vec + step_vec; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 uu____0 = + libcrux_ml_kem_invert_ntt_inv_ntt_layer_int_vec_step_reduce_ea( + re->coefficients[j], re->coefficients[j + step_vec], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + libcrux_ml_kem_vector_portable_vector_type_PortableVector x = uu____0.fst; + libcrux_ml_kem_vector_portable_vector_type_PortableVector y = uu____0.snd; + re->coefficients[j] = x; + re->coefficients[j + step_vec] = y; + } + } +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_montgomery +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + size_t zeta_i = + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT / (size_t)2U; + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_1_ea(&zeta_i, re); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_2_ea(&zeta_i, re); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_3_ea(&zeta_i, re); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)4U); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)5U); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)6U); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)7U); + libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea(re); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.subtract_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_subtract_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d b) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + b.coefficients[i0], (int16_t)1441); + libcrux_ml_kem_vector_portable_vector_type_PortableVector diff = + libcrux_ml_kem_vector_portable_sub_b8(myself->coefficients[i0], + &coefficient_normal_form); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(diff); + b.coefficients[i0] = red; + } + return b; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.subtract_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_subtract_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d b) { + return libcrux_ml_kem_polynomial_subtract_reduce_ea(self, b); +} + +/** + The following functions compute various expressions involving + vectors and matrices. The computation of these expressions has been + abstracted away into these functions in order to save on loop iterations. + Compute v − InverseNTT(sᵀ ◦ NTT(u)) +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_message +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_matrix_compute_message_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *v, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *secret_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *u_as_ntt) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(&secret_as_ntt[i0], + &u_as_ntt[i0]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&result, &product); + } + libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b(&result); + return libcrux_ml_kem_polynomial_subtract_reduce_d6_ea(v, result); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.to_unsigned_field_modulus +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_to_unsigned_representative_b8(a); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.compress_then_serialize_message with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_message_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re, uint8_t ret[32U]) { + uint8_t serialized[32U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re.coefficients[i0]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_compressed = + libcrux_ml_kem_vector_portable_compress_1_b8(coefficient); + uint8_t bytes[2U]; + libcrux_ml_kem_vector_portable_serialize_1_b8(coefficient_compressed, + bytes); + Eurydice_slice_copy( + Eurydice_array_to_subslice3(serialized, (size_t)2U * i0, + (size_t)2U * i0 + (size_t)2U, uint8_t *), + Eurydice_array_to_slice((size_t)2U, bytes, uint8_t), uint8_t); + } + memcpy(ret, serialized, (size_t)32U * sizeof(uint8_t)); +} + +/** + This function implements Algorithm 14 of the + NIST FIPS 203 specification; this is the Kyber CPA-PKE decryption algorithm. + + Algorithm 14 is reproduced below: + + ```plaintext + Input: decryption key dkₚₖₑ ∈ 𝔹^{384k}. + Input: ciphertext c ∈ 𝔹^{32(dᵤk + dᵥ)}. + Output: message m ∈ 𝔹^{32}. + + c₁ ← c[0 : 32dᵤk] + c₂ ← c[32dᵤk : 32(dᵤk + dᵥ)] + u ← Decompress_{dᵤ}(ByteDecode_{dᵤ}(c₁)) + v ← Decompress_{dᵥ}(ByteDecode_{dᵥ}(c₂)) + ŝ ← ByteDecode₁₂(dkₚₖₑ) + w ← v - NTT-¹(ŝᵀ ◦ NTT(u)) + m ← ByteEncode₁(Compress₁(w)) + return m + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.decrypt_unpacked +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- VECTOR_U_ENCODED_SIZE= 960 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_decrypt_unpacked_42( + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 *secret_key, + uint8_t *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d u_as_ntt[3U]; + libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_6c(ciphertext, u_as_ntt); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d v = + libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_v_89( + Eurydice_array_to_subslice_from((size_t)1088U, ciphertext, + (size_t)960U, uint8_t, size_t, + uint8_t[])); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d message = + libcrux_ml_kem_matrix_compute_message_1b(&v, secret_key->secret_as_ntt, + u_as_ntt); + uint8_t ret0[32U]; + libcrux_ml_kem_serialize_compress_then_serialize_message_ea(message, ret0); + memcpy(ret, ret0, (size_t)32U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.decrypt +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- VECTOR_U_ENCODED_SIZE= 960 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_decrypt_42( + Eurydice_slice secret_key, uint8_t *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 + secret_key_unpacked; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret0[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + ret0[i] = libcrux_ml_kem_ind_cpa_decrypt_call_mut_0b_42(&lvalue, i); + } + memcpy( + secret_key_unpacked.secret_as_ntt, ret0, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + libcrux_ml_kem_ind_cpa_deserialize_vector_1b( + secret_key, secret_key_unpacked.secret_as_ntt); + uint8_t ret1[32U]; + libcrux_ml_kem_ind_cpa_decrypt_unpacked_42(&secret_key_unpacked, ciphertext, + ret1); + memcpy(ret, ret1, (size_t)32U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.G_4a +with const generics +- K= 3 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_slice input, uint8_t ret[64U]) { + libcrux_ml_kem_hash_functions_portable_G(input, ret); +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF +with const generics +- LEN= 32 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_9e( + Eurydice_slice input, uint8_t ret[32U]) { + uint8_t digest[32U] = {0U}; + libcrux_sha3_portable_shake256( + Eurydice_array_to_slice((size_t)32U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)32U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF_4a +with const generics +- K= 3 +- LEN= 32 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_4a_41( + Eurydice_slice input, uint8_t ret[32U]) { + libcrux_ml_kem_hash_functions_portable_PRF_9e(input, ret); +} + +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.unpacked.IndCpaPublicKeyUnpacked with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- $3size_t +*/ +typedef struct libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0_s { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d t_as_ntt[3U]; + uint8_t seed_for_A[32U]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d A[3U][3U]; +} libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0; + +/** +This function found in impl {core::default::Default for +libcrux_ml_kem::ind_cpa::unpacked::IndCpaPublicKeyUnpacked[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.unpacked.default_8b +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static inline libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 +libcrux_ml_kem_ind_cpa_unpacked_default_8b_1b(void) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + uu____0[i] = libcrux_ml_kem_polynomial_ZERO_d6_ea(); + } + uint8_t uu____1[32U] = {0U}; + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 lit; + memcpy( + lit.t_as_ntt, uu____0, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + memcpy(lit.seed_for_A, uu____1, (size_t)32U * sizeof(uint8_t)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d repeat_expression0[3U][3U]; + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d repeat_expression[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + repeat_expression[i] = libcrux_ml_kem_polynomial_ZERO_d6_ea(); + } + memcpy(repeat_expression0[i0], repeat_expression, + (size_t)3U * + sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + } + memcpy(lit.A, repeat_expression0, + (size_t)3U * + sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U])); + return lit; +} + +/** + Only use with public values. + + This MUST NOT be used with secret inputs, like its caller + `deserialize_ring_elements_reduced`. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_to_reduced_ring_element with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_to_reduced_ring_element_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)24U; i++) { + size_t i0 = i; + Eurydice_slice bytes = + Eurydice_slice_subslice3(serialized, i0 * (size_t)24U, + i0 * (size_t)24U + (size_t)24U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_deserialize_12_b8(bytes); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_cond_subtract_3329_b8(coefficient); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** + See [deserialize_ring_elements_reduced_out]. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_ring_elements_reduced with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_1b( + Eurydice_slice public_key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *deserialized_pk) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(public_key, uint8_t) / + LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT; + i++) { + size_t i0 = i; + Eurydice_slice ring_element = Eurydice_slice_subslice3( + public_key, i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT + + LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + uint8_t *); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_serialize_deserialize_to_reduced_ring_element_ea( + ring_element); + deserialized_pk[i0] = uu____0; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PortableHash +with const generics +- $3size_t +*/ +typedef struct libcrux_ml_kem_hash_functions_portable_PortableHash_88_s { + libcrux_sha3_generic_keccak_KeccakState_17 shake128_state[3U]; +} libcrux_ml_kem_hash_functions_portable_PortableHash_88; + +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_init_absorb_final with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_hash_functions_portable_PortableHash_88 +libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_e0( + uint8_t (*input)[34U]) { + libcrux_ml_kem_hash_functions_portable_PortableHash_88 shake128_state; + libcrux_sha3_generic_keccak_KeccakState_17 repeat_expression[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + repeat_expression[i] = libcrux_sha3_portable_incremental_shake128_init(); + } + memcpy(shake128_state.shake128_state, repeat_expression, + (size_t)3U * sizeof(libcrux_sha3_generic_keccak_KeccakState_17)); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_incremental_shake128_absorb_final( + &shake128_state.shake128_state[i0], + Eurydice_array_to_slice((size_t)34U, input[i0], uint8_t)); + } + return shake128_state; +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_init_absorb_final_4a with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_hash_functions_portable_PortableHash_88 +libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_4a_e0( + uint8_t (*input)[34U]) { + return libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_e0( + input); +} + +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_first_three_blocks with +const generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *st, + uint8_t ret[3U][504U]) { + uint8_t out[3U][504U] = {{0U}}; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_incremental_shake128_squeeze_first_three_blocks( + &st->shake128_state[i0], + Eurydice_array_to_slice((size_t)504U, out[i0], uint8_t)); + } + memcpy(ret, out, (size_t)3U * sizeof(uint8_t[504U])); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_first_three_blocks_4a +with const generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_4a_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *self, + uint8_t ret[3U][504U]) { + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_e0( + self, ret); +} + +/** + If `bytes` contains a set of uniformly random bytes, this function + uniformly samples a ring element `â` that is treated as being the NTT + representation of the corresponding polynomial `a`. + + Since rejection sampling is used, it is possible the supplied bytes are + not enough to sample the element, in which case an `Err` is returned and the + caller must try again with a fresh set of bytes. + + This function partially implements Algorithm + 6 of the NIST FIPS 203 standard, We say "partially" because this + implementation only accepts a finite set of bytes as input and returns an error + if the set is not enough; Algorithm 6 of the FIPS 203 standard on the other + hand samples from an infinite stream of bytes until the ring element is filled. + Algorithm 6 is reproduced below: + + ```plaintext + Input: byte stream B ∈ 𝔹*. + Output: array â ∈ ℤ₂₅₆. + + i ← 0 + j ← 0 + while j < 256 do + d₁ ← B[i] + 256·(B[i+1] mod 16) + d₂ ← ⌊B[i+1]/16⌋ + 16·B[i+2] + if d₁ < q then + â[j] ← d₁ + j ← j + 1 + end if + if d₂ < q and j < 256 then + â[j] ← d₂ + j ← j + 1 + end if + i ← i + 3 + end while + return â + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_uniform_distribution_next with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- N= 504 +*/ +static KRML_MUSTINLINE bool +libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_89( + uint8_t (*randomness)[504U], size_t *sampled_coefficients, + int16_t (*out)[272U]) { + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + size_t i1 = i0; + for (size_t i = (size_t)0U; i < (size_t)504U / (size_t)24U; i++) { + size_t r = i; + if (sampled_coefficients[i1] < + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + size_t sampled = libcrux_ml_kem_vector_portable_rej_sample_b8( + Eurydice_array_to_subslice3(randomness[i1], r * (size_t)24U, + r * (size_t)24U + (size_t)24U, + uint8_t *), + Eurydice_array_to_subslice3(out[i1], sampled_coefficients[i1], + sampled_coefficients[i1] + (size_t)16U, + int16_t *)); + size_t uu____0 = i1; + sampled_coefficients[uu____0] = sampled_coefficients[uu____0] + sampled; + } + } + } + bool done = true; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + if (sampled_coefficients[i0] >= + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + sampled_coefficients[i0] = + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT; + } else { + done = false; + } + } + return done; +} + +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_next_block with const +generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *st, + uint8_t ret[3U][168U]) { + uint8_t out[3U][168U] = {{0U}}; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_incremental_shake128_squeeze_next_block( + &st->shake128_state[i0], + Eurydice_array_to_slice((size_t)168U, out[i0], uint8_t)); + } + memcpy(ret, out, (size_t)3U * sizeof(uint8_t[168U])); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_next_block_4a with const +generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_4a_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *self, + uint8_t ret[3U][168U]) { + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_e0(self, + ret); +} + +/** + If `bytes` contains a set of uniformly random bytes, this function + uniformly samples a ring element `â` that is treated as being the NTT + representation of the corresponding polynomial `a`. + + Since rejection sampling is used, it is possible the supplied bytes are + not enough to sample the element, in which case an `Err` is returned and the + caller must try again with a fresh set of bytes. + + This function partially implements Algorithm + 6 of the NIST FIPS 203 standard, We say "partially" because this + implementation only accepts a finite set of bytes as input and returns an error + if the set is not enough; Algorithm 6 of the FIPS 203 standard on the other + hand samples from an infinite stream of bytes until the ring element is filled. + Algorithm 6 is reproduced below: + + ```plaintext + Input: byte stream B ∈ 𝔹*. + Output: array â ∈ ℤ₂₅₆. + + i ← 0 + j ← 0 + while j < 256 do + d₁ ← B[i] + 256·(B[i+1] mod 16) + d₂ ← ⌊B[i+1]/16⌋ + 16·B[i+2] + if d₁ < q then + â[j] ← d₁ + j ← j + 1 + end if + if d₂ < q and j < 256 then + â[j] ← d₂ + j ← j + 1 + end if + i ← i + 3 + end while + return â + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_uniform_distribution_next with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- N= 168 +*/ +static KRML_MUSTINLINE bool +libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_890( + uint8_t (*randomness)[168U], size_t *sampled_coefficients, + int16_t (*out)[272U]) { + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + size_t i1 = i0; + for (size_t i = (size_t)0U; i < (size_t)168U / (size_t)24U; i++) { + size_t r = i; + if (sampled_coefficients[i1] < + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + size_t sampled = libcrux_ml_kem_vector_portable_rej_sample_b8( + Eurydice_array_to_subslice3(randomness[i1], r * (size_t)24U, + r * (size_t)24U + (size_t)24U, + uint8_t *), + Eurydice_array_to_subslice3(out[i1], sampled_coefficients[i1], + sampled_coefficients[i1] + (size_t)16U, + int16_t *)); + size_t uu____0 = i1; + sampled_coefficients[uu____0] = sampled_coefficients[uu____0] + sampled; + } + } + } + bool done = true; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + if (sampled_coefficients[i0] >= + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + sampled_coefficients[i0] = + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT; + } else { + done = false; + } + } + return done; +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.from_i16_array +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_from_i16_array_ea(Eurydice_slice a) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result = + libcrux_ml_kem_polynomial_ZERO_ea(); + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_from_i16_array_b8( + Eurydice_slice_subslice3(a, i0 * (size_t)16U, + (i0 + (size_t)1U) * (size_t)16U, + int16_t *)); + result.coefficients[i0] = uu____0; + } + return result; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.from_i16_array_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_from_i16_array_d6_ea(Eurydice_slice a) { + return libcrux_ml_kem_polynomial_from_i16_array_ea(a); +} + +/** +This function found in impl {core::ops::function::FnMut<(@Array), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@2]> for libcrux_ml_kem::sampling::sample_from_xof::closure[TraitClause@0, TraitClause@1, TraitClause@2, TraitClause@3]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.sampling.sample_from_xof.call_mut_e7 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_sampling_sample_from_xof_call_mut_e7_2b( + void **_, int16_t tupled_args[272U]) { + int16_t s[272U]; + memcpy(s, tupled_args, (size_t)272U * sizeof(int16_t)); + return libcrux_ml_kem_polynomial_from_i16_array_d6_ea( + Eurydice_array_to_subslice3(s, (size_t)0U, (size_t)256U, int16_t *)); +} + +/** +A monomorphic instance of libcrux_ml_kem.sampling.sample_from_xof +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_sampling_sample_from_xof_2b( + uint8_t (*seeds)[34U], + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + size_t sampled_coefficients[3U] = {0U}; + int16_t out[3U][272U] = {{0U}}; + libcrux_ml_kem_hash_functions_portable_PortableHash_88 xof_state = + libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_4a_e0( + seeds); + uint8_t randomness0[3U][504U]; + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_4a_e0( + &xof_state, randomness0); + bool done = libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_89( + randomness0, sampled_coefficients, out); + while (true) { + if (done) { + break; + } else { + uint8_t randomness[3U][168U]; + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_4a_e0( + &xof_state, randomness); + done = libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_890( + randomness, sampled_coefficients, out); + } + } + /* Passing arrays by value in Rust generates a copy in C */ + int16_t copy_of_out[3U][272U]; + memcpy(copy_of_out, out, (size_t)3U * sizeof(int16_t[272U])); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret0[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + ret0[i] = libcrux_ml_kem_sampling_sample_from_xof_call_mut_e7_2b( + &lvalue, copy_of_out[i]); + } + memcpy( + ret, ret0, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** +A monomorphic instance of libcrux_ml_kem.matrix.sample_matrix_A +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_matrix_sample_matrix_A_2b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*A_transpose)[3U], + uint8_t *seed, bool transpose) { + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + size_t i1 = i0; + uint8_t seeds[3U][34U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + core_array__core__clone__Clone_for__Array_T__N___clone( + (size_t)34U, seed, seeds[i], uint8_t, void *); + } + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t j = i; + seeds[j][32U] = (uint8_t)i1; + seeds[j][33U] = (uint8_t)j; + } + libcrux_ml_kem_polynomial_PolynomialRingElement_1d sampled[3U]; + libcrux_ml_kem_sampling_sample_from_xof_2b(seeds, sampled); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, sampled, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t j = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d sample = sampled[j]; + if (transpose) { + A_transpose[j][i1] = sample; + } else { + A_transpose[i1][j] = sample; + } + } + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.build_unpacked_public_key_mut +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- T_AS_NTT_ENCODED_SIZE= 1152 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ind_cpa_build_unpacked_public_key_mut_3f( + Eurydice_slice public_key, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + *unpacked_public_key) { + Eurydice_slice uu____0 = Eurydice_slice_subslice_to( + public_key, (size_t)1152U, uint8_t, size_t, uint8_t[]); + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_1b( + uu____0, unpacked_public_key->t_as_ntt); + Eurydice_slice seed = Eurydice_slice_subslice_from( + public_key, (size_t)1152U, uint8_t, size_t, uint8_t[]); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d(*uu____1)[3U] = + unpacked_public_key->A; + uint8_t ret[34U]; + libcrux_ml_kem_utils_into_padded_array_b6(seed, ret); + libcrux_ml_kem_matrix_sample_matrix_A_2b(uu____1, ret, false); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.build_unpacked_public_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- T_AS_NTT_ENCODED_SIZE= 1152 +*/ +static KRML_MUSTINLINE + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + libcrux_ml_kem_ind_cpa_build_unpacked_public_key_3f( + Eurydice_slice public_key) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + unpacked_public_key = libcrux_ml_kem_ind_cpa_unpacked_default_8b_1b(); + libcrux_ml_kem_ind_cpa_build_unpacked_public_key_mut_3f(public_key, + &unpacked_public_key); + return unpacked_public_key; +} + +/** +A monomorphic instance of K. +with types libcrux_ml_kem_polynomial_PolynomialRingElement +libcrux_ml_kem_vector_portable_vector_type_PortableVector[3size_t], +libcrux_ml_kem_polynomial_PolynomialRingElement +libcrux_ml_kem_vector_portable_vector_type_PortableVector + +*/ +typedef struct tuple_ed_s { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d fst[3U]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d snd; +} tuple_ed; + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@2]> for libcrux_ml_kem::ind_cpa::encrypt_c1::closure[TraitClause@0, TraitClause@1, TraitClause@2, +TraitClause@3]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c1.call_mut_f1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- C1_LEN= 960 +- U_COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_f1_85(void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRFxN +with const generics +- K= 3 +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRFxN_41( + uint8_t (*input)[33U], uint8_t ret[3U][128U]) { + uint8_t out[3U][128U] = {{0U}}; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_shake256( + Eurydice_array_to_slice((size_t)128U, out[i0], uint8_t), + Eurydice_array_to_slice((size_t)33U, input[i0], uint8_t)); + } + memcpy(ret, out, (size_t)3U * sizeof(uint8_t[128U])); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRFxN_4a +with const generics +- K= 3 +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRFxN_4a_41( + uint8_t (*input)[33U], uint8_t ret[3U][128U]) { + libcrux_ml_kem_hash_functions_portable_PRFxN_41(input, ret); +} + +/** + Given a series of uniformly random bytes in `randomness`, for some number + `eta`, the `sample_from_binomial_distribution_{eta}` functions sample a ring + element from a binomial distribution centered at 0 that uses two sets of `eta` + coin flips. If, for example, `eta = ETA`, each ring coefficient is a value `v` + such such that `v ∈ {-ETA, -ETA + 1, ..., 0, ..., ETA + 1, ETA}` and: + + ```plaintext + - If v < 0, Pr[v] = Pr[-v] + - If v >= 0, Pr[v] = BINOMIAL_COEFFICIENT(2 * ETA; ETA - v) / 2 ^ (2 * ETA) + ``` + + The values `v < 0` are mapped to the appropriate `KyberFieldElement`. + + The expected value is: + + ```plaintext + E[X] = (-ETA)Pr[-ETA] + (-(ETA - 1))Pr[-(ETA - 1)] + ... + (ETA - 1)Pr[ETA - 1] + + (ETA)Pr[ETA] = 0 since Pr[-v] = Pr[v] when v < 0. + ``` + + And the variance is: + + ```plaintext + Var(X) = E[(X - E[X])^2] + = E[X^2] + = sum_(v=-ETA to ETA)v^2 * (BINOMIAL_COEFFICIENT(2 * ETA; ETA - v) / + 2^(2 * ETA)) = ETA / 2 + ``` + + This function implements Algorithm 7 of the NIST FIPS 203 + standard, which is reproduced below: + + ```plaintext + Input: byte array B ∈ 𝔹^{64η}. + Output: array f ∈ ℤ₂₅₆. + + b ← BytesToBits(B) + for (i ← 0; i < 256; i++) + x ← ∑(j=0 to η - 1) b[2iη + j] + y ← ∑(j=0 to η - 1) b[2iη + η + j] + f[i] ← x−y mod q + end for + return f + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_binomial_distribution_2 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_sampling_sample_from_binomial_distribution_2_ea( + Eurydice_slice randomness) { + int16_t sampled_i16s[256U] = {0U}; + for (size_t i0 = (size_t)0U; + i0 < Eurydice_slice_len(randomness, uint8_t) / (size_t)4U; i0++) { + size_t chunk_number = i0; + Eurydice_slice byte_chunk = Eurydice_slice_subslice3( + randomness, chunk_number * (size_t)4U, + chunk_number * (size_t)4U + (size_t)4U, uint8_t *); + uint32_t random_bits_as_u32 = + (((uint32_t)Eurydice_slice_index(byte_chunk, (size_t)0U, uint8_t, + uint8_t *) | + (uint32_t)Eurydice_slice_index(byte_chunk, (size_t)1U, uint8_t, + uint8_t *) + << 8U) | + (uint32_t)Eurydice_slice_index(byte_chunk, (size_t)2U, uint8_t, + uint8_t *) + << 16U) | + (uint32_t)Eurydice_slice_index(byte_chunk, (size_t)3U, uint8_t, + uint8_t *) + << 24U; + uint32_t even_bits = random_bits_as_u32 & 1431655765U; + uint32_t odd_bits = random_bits_as_u32 >> 1U & 1431655765U; + uint32_t coin_toss_outcomes = even_bits + odd_bits; + for (uint32_t i = 0U; i < 32U / 4U; i++) { + uint32_t outcome_set = i; + uint32_t outcome_set0 = outcome_set * 4U; + int16_t outcome_1 = + (int16_t)(coin_toss_outcomes >> (uint32_t)outcome_set0 & 3U); + int16_t outcome_2 = + (int16_t)(coin_toss_outcomes >> (uint32_t)(outcome_set0 + 2U) & 3U); + size_t offset = (size_t)(outcome_set0 >> 2U); + sampled_i16s[(size_t)8U * chunk_number + offset] = outcome_1 - outcome_2; + } + } + return libcrux_ml_kem_polynomial_from_i16_array_d6_ea( + Eurydice_array_to_slice((size_t)256U, sampled_i16s, int16_t)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_binomial_distribution with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- ETA= 2 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_slice randomness) { + return libcrux_ml_kem_sampling_sample_from_binomial_distribution_2_ea( + randomness); +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_7 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_7_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + size_t step = LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT / (size_t)2U; + for (size_t i = (size_t)0U; i < step; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector t = + libcrux_ml_kem_vector_portable_multiply_by_constant_b8( + re->coefficients[j + step], (int16_t)-1600); + re->coefficients[j + step] = + libcrux_ml_kem_vector_portable_sub_b8(re->coefficients[j], &t); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____1 = + libcrux_ml_kem_vector_portable_add_b8(re->coefficients[j], &t); + re->coefficients[j] = uu____1; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_binomially_sampled_ring_element +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ntt_ntt_binomially_sampled_ring_element_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + libcrux_ml_kem_ntt_ntt_at_layer_7_ea(re); + size_t zeta_i = (size_t)1U; + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)6U, + (size_t)11207U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)5U, + (size_t)11207U + (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea( + &zeta_i, re, (size_t)4U, (size_t)11207U + (size_t)2U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_3_ea( + &zeta_i, re, (size_t)11207U + (size_t)3U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_2_ea( + &zeta_i, re, (size_t)11207U + (size_t)4U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_1_ea( + &zeta_i, re, (size_t)11207U + (size_t)5U * (size_t)3328U); + libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea(re); +} + +/** + Sample a vector of ring elements from a centered binomial distribution and + convert them into their NTT representations. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.sample_vector_cbd_then_ntt +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- ETA= 2 +- ETA_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE uint8_t +libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re_as_ntt, + uint8_t *prf_input, uint8_t domain_separator) { + uint8_t prf_inputs[3U][33U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + core_array__core__clone__Clone_for__Array_T__N___clone( + (size_t)33U, prf_input, prf_inputs[i], uint8_t, void *); + } + domain_separator = + libcrux_ml_kem_utils_prf_input_inc_e0(prf_inputs, domain_separator); + uint8_t prf_outputs[3U][128U]; + libcrux_ml_kem_hash_functions_portable_PRFxN_4a_41(prf_inputs, prf_outputs); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + re_as_ntt[i0] = + libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_array_to_slice((size_t)128U, prf_outputs[i0], uint8_t)); + libcrux_ml_kem_ntt_ntt_binomially_sampled_ring_element_ea(&re_as_ntt[i0]); + } + return domain_separator; +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@2]> for libcrux_ml_kem::ind_cpa::encrypt_c1::closure#1[TraitClause@0, TraitClause@1, TraitClause@2, +TraitClause@3]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c1.call_mut_dd +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- C1_LEN= 960 +- U_COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_dd_85(void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** + Sample a vector of ring elements from a centered binomial distribution. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.sample_ring_element_cbd +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- ETA2_RANDOMNESS_SIZE= 128 +- ETA2= 2 +*/ +static KRML_MUSTINLINE uint8_t +libcrux_ml_kem_ind_cpa_sample_ring_element_cbd_3b( + uint8_t *prf_input, uint8_t domain_separator, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_1) { + uint8_t prf_inputs[3U][33U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + core_array__core__clone__Clone_for__Array_T__N___clone( + (size_t)33U, prf_input, prf_inputs[i], uint8_t, void *); + } + domain_separator = + libcrux_ml_kem_utils_prf_input_inc_e0(prf_inputs, domain_separator); + uint8_t prf_outputs[3U][128U]; + libcrux_ml_kem_hash_functions_portable_PRFxN_4a_41(prf_inputs, prf_outputs); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_array_to_slice((size_t)128U, prf_outputs[i0], uint8_t)); + error_1[i0] = uu____0; + } + return domain_separator; +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF +with const generics +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_a6( + Eurydice_slice input, uint8_t ret[128U]) { + uint8_t digest[128U] = {0U}; + libcrux_sha3_portable_shake256( + Eurydice_array_to_slice((size_t)128U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)128U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF_4a +with const generics +- K= 3 +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_4a_410( + Eurydice_slice input, uint8_t ret[128U]) { + libcrux_ml_kem_hash_functions_portable_PRF_a6(input, ret); +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for libcrux_ml_kem::matrix::compute_vector_u::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_vector_u.call_mut_a8 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_matrix_compute_vector_u_call_mut_a8_1b(void **_, + size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_error_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_error_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + myself->coefficients[j], (int16_t)1441); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum = + libcrux_ml_kem_vector_portable_add_b8(coefficient_normal_form, + &error->coefficients[j]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(sum); + myself->coefficients[j] = red; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_error_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_error_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + libcrux_ml_kem_polynomial_add_error_reduce_ea(self, error); +} + +/** + Compute u := InvertNTT(Aᵀ ◦ r̂) + e₁ +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_vector_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_matrix_compute_vector_u_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*a_as_ntt)[3U], + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *r_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_1, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + result[i] = + libcrux_ml_kem_matrix_compute_vector_u_call_mut_a8_1b(&lvalue, i); + } + for (size_t i0 = (size_t)0U; + i0 < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, a_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]); + i0++) { + size_t i1 = i0; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *row = a_as_ntt[i1]; + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, row, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t j = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *a_element = &row[j]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(a_element, &r_as_ntt[j]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&result[i1], + &product); + } + libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b(&result[i1]); + libcrux_ml_kem_polynomial_add_error_reduce_d6_ea(&result[i1], &error_1[i1]); + } + memcpy( + ret, result, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress.compress +with const generics +- COEFFICIENT_BITS= 10 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_compress_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int16_t uu____0 = libcrux_secrets_int_as_i16_f5( + libcrux_ml_kem_vector_portable_compress_compress_ciphertext_coefficient( + (uint8_t)(int32_t)10, + libcrux_secrets_int_as_u16_f5(a.elements[i0]))); + a.elements[i0] = uu____0; + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress_b8 +with const generics +- COEFFICIENT_BITS= 10 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_b8_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_compress_ef(a); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.compress_then_serialize_10 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- OUT_LEN= 320 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_10_ff( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, uint8_t ret[320U]) { + uint8_t serialized[320U] = {0U}; + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_compress_b8_ef( + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re->coefficients[i0])); + uint8_t bytes[20U]; + libcrux_ml_kem_vector_portable_serialize_10_b8(coefficient, bytes); + Eurydice_slice_copy( + Eurydice_array_to_subslice3(serialized, (size_t)20U * i0, + (size_t)20U * i0 + (size_t)20U, uint8_t *), + Eurydice_array_to_slice((size_t)20U, bytes, uint8_t), uint8_t); + } + memcpy(ret, serialized, (size_t)320U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.compress_then_serialize_ring_element_u with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- COMPRESSION_FACTOR= 10 +- OUT_LEN= 320 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_ring_element_u_fe( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, uint8_t ret[320U]) { + uint8_t uu____0[320U]; + libcrux_ml_kem_serialize_compress_then_serialize_10_ff(re, uu____0); + memcpy(ret, uu____0, (size_t)320U * sizeof(uint8_t)); +} + +/** + Call [`compress_then_serialize_ring_element_u`] on each ring element. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.compress_then_serialize_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- OUT_LEN= 960 +- COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_compress_then_serialize_u_43( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d input[3U], + Eurydice_slice out) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, input, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = input[i0]; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, i0 * ((size_t)960U / (size_t)3U), + (i0 + (size_t)1U) * ((size_t)960U / (size_t)3U), uint8_t *); + uint8_t ret[320U]; + libcrux_ml_kem_serialize_compress_then_serialize_ring_element_u_fe(&re, + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)320U, ret, uint8_t), uint8_t); + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- C1_LEN= 960 +- U_COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE tuple_ed libcrux_ml_kem_ind_cpa_encrypt_c1_85( + Eurydice_slice randomness, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*matrix)[3U], + Eurydice_slice ciphertext) { + uint8_t prf_input[33U]; + libcrux_ml_kem_utils_into_padded_array_c8(randomness, prf_input); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d r_as_ntt[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + r_as_ntt[i] = libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_f1_85(&lvalue, i); + } + uint8_t domain_separator0 = + libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b(r_as_ntt, prf_input, + 0U); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_1[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + error_1[i] = libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_dd_85(&lvalue, i); + } + uint8_t domain_separator = libcrux_ml_kem_ind_cpa_sample_ring_element_cbd_3b( + prf_input, domain_separator0, error_1); + prf_input[32U] = domain_separator; + uint8_t prf_output[128U]; + libcrux_ml_kem_hash_functions_portable_PRF_4a_410( + Eurydice_array_to_slice((size_t)33U, prf_input, uint8_t), prf_output); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_2 = + libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_array_to_slice((size_t)128U, prf_output, uint8_t)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d u[3U]; + libcrux_ml_kem_matrix_compute_vector_u_1b(matrix, r_as_ntt, error_1, u); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0[3U]; + memcpy( + uu____0, u, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + libcrux_ml_kem_ind_cpa_compress_then_serialize_u_43(uu____0, ciphertext); + /* Passing arrays by value in Rust generates a copy in C */ + libcrux_ml_kem_polynomial_PolynomialRingElement_1d copy_of_r_as_ntt[3U]; + memcpy( + copy_of_r_as_ntt, r_as_ntt, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + tuple_ed lit; + memcpy( + lit.fst, copy_of_r_as_ntt, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + lit.snd = error_2; + return lit; +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_message with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_message_ea( + uint8_t *serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_compressed = + libcrux_ml_kem_vector_portable_deserialize_1_b8( + Eurydice_array_to_subslice3(serialized, (size_t)2U * i0, + (size_t)2U * i0 + (size_t)2U, + uint8_t *)); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_decompress_1_b8(coefficient_compressed); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_message_error_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_add_message_error_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *message, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + result.coefficients[i0], (int16_t)1441); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum1 = + libcrux_ml_kem_vector_portable_add_b8(myself->coefficients[i0], + &message->coefficients[i0]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum2 = + libcrux_ml_kem_vector_portable_add_b8(coefficient_normal_form, &sum1); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(sum2); + result.coefficients[i0] = red; + } + return result; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_message_error_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_add_message_error_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *message, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result) { + return libcrux_ml_kem_polynomial_add_message_error_reduce_ea(self, message, + result); +} + +/** + Compute InverseNTT(tᵀ ◦ r̂) + e₂ + message +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_ring_element_v +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_matrix_compute_ring_element_v_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *r_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_2, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *message) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(&t_as_ntt[i0], + &r_as_ntt[i0]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&result, &product); + } + libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b(&result); + return libcrux_ml_kem_polynomial_add_message_error_reduce_d6_ea( + error_2, message, result); +} + +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress.compress +with const generics +- COEFFICIENT_BITS= 4 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_compress_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int16_t uu____0 = libcrux_secrets_int_as_i16_f5( + libcrux_ml_kem_vector_portable_compress_compress_ciphertext_coefficient( + (uint8_t)(int32_t)4, + libcrux_secrets_int_as_u16_f5(a.elements[i0]))); + a.elements[i0] = uu____0; + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress_b8 +with const generics +- COEFFICIENT_BITS= 4 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_b8_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_compress_d1(a); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.compress_then_serialize_4 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_4_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re, + Eurydice_slice serialized) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_compress_b8_d1( + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re.coefficients[i0])); + uint8_t bytes[8U]; + libcrux_ml_kem_vector_portable_serialize_4_b8(coefficient, bytes); + Eurydice_slice_copy( + Eurydice_slice_subslice3(serialized, (size_t)8U * i0, + (size_t)8U * i0 + (size_t)8U, uint8_t *), + Eurydice_array_to_slice((size_t)8U, bytes, uint8_t), uint8_t); + } +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.compress_then_serialize_ring_element_v with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- COMPRESSION_FACTOR= 4 +- OUT_LEN= 128 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_ring_element_v_6c( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re, Eurydice_slice out) { + libcrux_ml_kem_serialize_compress_then_serialize_4_ea(re, out); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c2 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- V_COMPRESSION_FACTOR= 4 +- C2_LEN= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_encrypt_c2_6c( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *r_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_2, + uint8_t *message, Eurydice_slice ciphertext) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d message_as_ring_element = + libcrux_ml_kem_serialize_deserialize_then_decompress_message_ea(message); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d v = + libcrux_ml_kem_matrix_compute_ring_element_v_1b( + t_as_ntt, r_as_ntt, error_2, &message_as_ring_element); + libcrux_ml_kem_serialize_compress_then_serialize_ring_element_v_6c( + v, ciphertext); +} + +/** + This function implements Algorithm 13 of the + NIST FIPS 203 specification; this is the Kyber CPA-PKE encryption algorithm. + + Algorithm 13 is reproduced below: + + ```plaintext + Input: encryption key ekₚₖₑ ∈ 𝔹^{384k+32}. + Input: message m ∈ 𝔹^{32}. + Input: encryption randomness r ∈ 𝔹^{32}. + Output: ciphertext c ∈ 𝔹^{32(dᵤk + dᵥ)}. + + N ← 0 + t̂ ← ByteDecode₁₂(ekₚₖₑ[0:384k]) + ρ ← ekₚₖₑ[384k: 384k + 32] + for (i ← 0; i < k; i++) + for(j ← 0; j < k; j++) + Â[i,j] ← SampleNTT(XOF(ρ, i, j)) + end for + end for + for(i ← 0; i < k; i++) + r[i] ← SamplePolyCBD_{η₁}(PRF_{η₁}(r,N)) + N ← N + 1 + end for + for(i ← 0; i < k; i++) + e₁[i] ← SamplePolyCBD_{η₂}(PRF_{η₂}(r,N)) + N ← N + 1 + end for + e₂ ← SamplePolyCBD_{η₂}(PRF_{η₂}(r,N)) + r̂ ← NTT(r) + u ← NTT-¹(Âᵀ ◦ r̂) + e₁ + μ ← Decompress₁(ByteDecode₁(m))) + v ← NTT-¹(t̂ᵀ ◦ rˆ) + e₂ + μ + c₁ ← ByteEncode_{dᵤ}(Compress_{dᵤ}(u)) + c₂ ← ByteEncode_{dᵥ}(Compress_{dᵥ}(v)) + return c ← (c₁ ‖ c₂) + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_unpacked +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_LEN= 960 +- C2_LEN= 128 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_encrypt_unpacked_2a( + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 *public_key, + uint8_t *message, Eurydice_slice randomness, uint8_t ret[1088U]) { + uint8_t ciphertext[1088U] = {0U}; + tuple_ed uu____0 = libcrux_ml_kem_ind_cpa_encrypt_c1_85( + randomness, public_key->A, + Eurydice_array_to_subslice3(ciphertext, (size_t)0U, (size_t)960U, + uint8_t *)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d r_as_ntt[3U]; + memcpy( + r_as_ntt, uu____0.fst, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_2 = uu____0.snd; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____1 = + public_key->t_as_ntt; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____2 = r_as_ntt; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____3 = &error_2; + uint8_t *uu____4 = message; + libcrux_ml_kem_ind_cpa_encrypt_c2_6c( + uu____1, uu____2, uu____3, uu____4, + Eurydice_array_to_subslice_from((size_t)1088U, ciphertext, (size_t)960U, + uint8_t, size_t, uint8_t[])); + memcpy(ret, ciphertext, (size_t)1088U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_LEN= 960 +- C2_LEN= 128 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_encrypt_2a( + Eurydice_slice public_key, uint8_t *message, Eurydice_slice randomness, + uint8_t ret[1088U]) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + unpacked_public_key = + libcrux_ml_kem_ind_cpa_build_unpacked_public_key_3f(public_key); + uint8_t ret0[1088U]; + libcrux_ml_kem_ind_cpa_encrypt_unpacked_2a(&unpacked_public_key, message, + randomness, ret0); + memcpy(ret, ret0, (size_t)1088U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::variant::Variant for +libcrux_ml_kem::variant::MlKem} +*/ +/** +A monomorphic instance of libcrux_ml_kem.variant.kdf_39 +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_variant_kdf_39_d6( + Eurydice_slice shared_secret, uint8_t *_, uint8_t ret[32U]) { + uint8_t out[32U] = {0U}; + Eurydice_slice_copy(Eurydice_array_to_slice((size_t)32U, out, uint8_t), + shared_secret, uint8_t); + memcpy(ret, out, (size_t)32U * sizeof(uint8_t)); +} + +/** + This code verifies on some machines, runs out of memory on others +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.decapsulate +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- SECRET_KEY_SIZE= 2400 +- CPA_SECRET_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +- IMPLICIT_REJECTION_HASH_INPUT_SIZE= 1120 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cca_decapsulate_62( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, uint8_t ret[32U]) { + Eurydice_slice_uint8_t_x4 uu____0 = + libcrux_ml_kem_types_unpack_private_key_b4( + Eurydice_array_to_slice((size_t)2400U, private_key->value, uint8_t)); + Eurydice_slice ind_cpa_secret_key = uu____0.fst; + Eurydice_slice ind_cpa_public_key = uu____0.snd; + Eurydice_slice ind_cpa_public_key_hash = uu____0.thd; + Eurydice_slice implicit_rejection_value = uu____0.f3; + uint8_t decrypted[32U]; + libcrux_ml_kem_ind_cpa_decrypt_42(ind_cpa_secret_key, ciphertext->value, + decrypted); + uint8_t to_hash0[64U]; + libcrux_ml_kem_utils_into_padded_array_24( + Eurydice_array_to_slice((size_t)32U, decrypted, uint8_t), to_hash0); + Eurydice_slice_copy( + Eurydice_array_to_subslice_from( + (size_t)64U, to_hash0, LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, + uint8_t, size_t, uint8_t[]), + ind_cpa_public_key_hash, uint8_t); + uint8_t hashed[64U]; + libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_array_to_slice((size_t)64U, to_hash0, uint8_t), hashed); + Eurydice_slice_uint8_t_x2 uu____1 = Eurydice_slice_split_at( + Eurydice_array_to_slice((size_t)64U, hashed, uint8_t), + LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, uint8_t, + Eurydice_slice_uint8_t_x2); + Eurydice_slice shared_secret0 = uu____1.fst; + Eurydice_slice pseudorandomness = uu____1.snd; + uint8_t to_hash[1120U]; + libcrux_ml_kem_utils_into_padded_array_15(implicit_rejection_value, to_hash); + Eurydice_slice uu____2 = Eurydice_array_to_subslice_from( + (size_t)1120U, to_hash, LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, + uint8_t, size_t, uint8_t[]); + Eurydice_slice_copy(uu____2, libcrux_ml_kem_types_as_ref_d3_80(ciphertext), + uint8_t); + uint8_t implicit_rejection_shared_secret0[32U]; + libcrux_ml_kem_hash_functions_portable_PRF_4a_41( + Eurydice_array_to_slice((size_t)1120U, to_hash, uint8_t), + implicit_rejection_shared_secret0); + uint8_t expected_ciphertext[1088U]; + libcrux_ml_kem_ind_cpa_encrypt_2a(ind_cpa_public_key, decrypted, + pseudorandomness, expected_ciphertext); + uint8_t implicit_rejection_shared_secret[32U]; + libcrux_ml_kem_variant_kdf_39_d6( + Eurydice_array_to_slice((size_t)32U, implicit_rejection_shared_secret0, + uint8_t), + libcrux_ml_kem_types_as_slice_a9_80(ciphertext), + implicit_rejection_shared_secret); + uint8_t shared_secret[32U]; + libcrux_ml_kem_variant_kdf_39_d6( + shared_secret0, libcrux_ml_kem_types_as_slice_a9_80(ciphertext), + shared_secret); + uint8_t ret0[32U]; + libcrux_ml_kem_constant_time_ops_compare_ciphertexts_select_shared_secret_in_constant_time( + libcrux_ml_kem_types_as_ref_d3_80(ciphertext), + Eurydice_array_to_slice((size_t)1088U, expected_ciphertext, uint8_t), + Eurydice_array_to_slice((size_t)32U, shared_secret, uint8_t), + Eurydice_array_to_slice((size_t)32U, implicit_rejection_shared_secret, + uint8_t), + ret0); + memcpy(ret, ret0, (size_t)32U * sizeof(uint8_t)); +} + +/** + Portable decapsulate +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.decapsulate with const generics +- K= 3 +- SECRET_KEY_SIZE= 2400 +- CPA_SECRET_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +- IMPLICIT_REJECTION_HASH_INPUT_SIZE= 1120 +*/ +static inline void +libcrux_ml_kem_ind_cca_instantiations_portable_decapsulate_35( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_ind_cca_decapsulate_62(private_key, ciphertext, ret); +} + +/** + Decapsulate ML-KEM 768 + + Generates an [`MlKemSharedSecret`]. + The input is a reference to an [`MlKem768PrivateKey`] and an + [`MlKem768Ciphertext`]. +*/ +void libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_ind_cca_instantiations_portable_decapsulate_35( + private_key, ciphertext, ret); +} + +/** +This function found in impl {libcrux_ml_kem::variant::Variant for +libcrux_ml_kem::variant::MlKem} +*/ +/** +A monomorphic instance of libcrux_ml_kem.variant.entropy_preprocess_39 +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_variant_entropy_preprocess_39_9c( + Eurydice_slice randomness, uint8_t ret[32U]) { + uint8_t out[32U] = {0U}; + Eurydice_slice_copy(Eurydice_array_to_slice((size_t)32U, out, uint8_t), + randomness, uint8_t); + memcpy(ret, out, (size_t)32U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.H_4a +with const generics +- K= 3 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_H_4a_e0( + Eurydice_slice input, uint8_t ret[32U]) { + libcrux_ml_kem_hash_functions_portable_H(input, ret); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.encapsulate +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- PUBLIC_KEY_SIZE= 1184 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE tuple_c2 libcrux_ml_kem_ind_cca_encapsulate_ca( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, uint8_t *randomness) { + uint8_t randomness0[32U]; + libcrux_ml_kem_variant_entropy_preprocess_39_9c( + Eurydice_array_to_slice((size_t)32U, randomness, uint8_t), randomness0); + uint8_t to_hash[64U]; + libcrux_ml_kem_utils_into_padded_array_24( + Eurydice_array_to_slice((size_t)32U, randomness0, uint8_t), to_hash); + Eurydice_slice uu____0 = Eurydice_array_to_subslice_from( + (size_t)64U, to_hash, LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE, uint8_t, + size_t, uint8_t[]); + uint8_t ret0[32U]; + libcrux_ml_kem_hash_functions_portable_H_4a_e0( + Eurydice_array_to_slice((size_t)1184U, + libcrux_ml_kem_types_as_slice_e6_d0(public_key), + uint8_t), + ret0); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)32U, ret0, uint8_t), uint8_t); + uint8_t hashed[64U]; + libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_array_to_slice((size_t)64U, to_hash, uint8_t), hashed); + Eurydice_slice_uint8_t_x2 uu____1 = Eurydice_slice_split_at( + Eurydice_array_to_slice((size_t)64U, hashed, uint8_t), + LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, uint8_t, + Eurydice_slice_uint8_t_x2); + Eurydice_slice shared_secret = uu____1.fst; + Eurydice_slice pseudorandomness = uu____1.snd; + uint8_t ciphertext[1088U]; + libcrux_ml_kem_ind_cpa_encrypt_2a( + Eurydice_array_to_slice((size_t)1184U, + libcrux_ml_kem_types_as_slice_e6_d0(public_key), + uint8_t), + randomness0, pseudorandomness, ciphertext); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_ciphertext[1088U]; + memcpy(copy_of_ciphertext, ciphertext, (size_t)1088U * sizeof(uint8_t)); + tuple_c2 lit; + lit.fst = libcrux_ml_kem_types_from_e0_80(copy_of_ciphertext); + uint8_t ret[32U]; + libcrux_ml_kem_variant_kdf_39_d6(shared_secret, ciphertext, ret); + memcpy(lit.snd, ret, (size_t)32U * sizeof(uint8_t)); + return lit; +} + +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.encapsulate with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- PUBLIC_KEY_SIZE= 1184 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static inline tuple_c2 +libcrux_ml_kem_ind_cca_instantiations_portable_encapsulate_cd( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, uint8_t *randomness) { + return libcrux_ml_kem_ind_cca_encapsulate_ca(public_key, randomness); +} + +/** + Encapsulate ML-KEM 768 + + Generates an ([`MlKem768Ciphertext`], [`MlKemSharedSecret`]) tuple. + The input is a reference to an [`MlKem768PublicKey`] and [`SHARED_SECRET_SIZE`] + bytes of `randomness`. +*/ +tuple_c2 libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]) { + return libcrux_ml_kem_ind_cca_instantiations_portable_encapsulate_cd( + public_key, randomness); +} + +/** +This function found in impl {core::default::Default for +libcrux_ml_kem::ind_cpa::unpacked::IndCpaPrivateKeyUnpacked[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.unpacked.default_70 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static inline libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 +libcrux_ml_kem_ind_cpa_unpacked_default_70_1b(void) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 lit; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d repeat_expression[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + repeat_expression[i] = libcrux_ml_kem_polynomial_ZERO_d6_ea(); + } + memcpy( + lit.secret_as_ntt, repeat_expression, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::variant::Variant for +libcrux_ml_kem::variant::MlKem} +*/ +/** +A monomorphic instance of libcrux_ml_kem.variant.cpa_keygen_seed_39 +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_variant_cpa_keygen_seed_39_9c( + Eurydice_slice key_generation_seed, uint8_t ret[64U]) { + uint8_t seed[33U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + seed, (size_t)0U, + LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE, uint8_t *), + key_generation_seed, uint8_t); + seed[LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE] = + (uint8_t)(size_t)3U; + uint8_t ret0[64U]; + libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_array_to_slice((size_t)33U, seed, uint8_t), ret0); + memcpy(ret, ret0, (size_t)64U * sizeof(uint8_t)); +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@3]> for +libcrux_ml_kem::ind_cpa::generate_keypair_unpacked::closure[TraitClause@0, TraitClause@1, +TraitClause@2, TraitClause@3, TraitClause@4, TraitClause@5]} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.generate_keypair_unpacked.call_mut_73 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_call_mut_73_1c( + void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.to_standard_domain +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_polynomial_to_standard_domain_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vector) { + return libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + vector, + LIBCRUX_ML_KEM_VECTOR_TRAITS_MONTGOMERY_R_SQUARED_MOD_FIELD_MODULUS); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_standard_error_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_polynomial_add_standard_error_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_polynomial_to_standard_domain_ea( + myself->coefficients[j]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum = + libcrux_ml_kem_vector_portable_add_b8(coefficient_normal_form, + &error->coefficients[j]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(sum); + myself->coefficients[j] = red; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_standard_error_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_polynomial_add_standard_error_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + libcrux_ml_kem_polynomial_add_standard_error_reduce_ea(self, error); +} + +/** + Compute  ◦ ŝ + ê +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_As_plus_e +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_matrix_compute_As_plus_e_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*matrix_A)[3U], + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *s_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_as_ntt) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, matrix_A, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]); + i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *row = matrix_A[i0]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + t_as_ntt[i0] = uu____0; + for (size_t i1 = (size_t)0U; + i1 < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, row, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i1++) { + size_t j = i1; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *matrix_element = + &row[j]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(matrix_element, + &s_as_ntt[j]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&t_as_ntt[i0], + &product); + } + libcrux_ml_kem_polynomial_add_standard_error_reduce_d6_ea( + &t_as_ntt[i0], &error_as_ntt[i0]); + } +} + +/** + This function implements most of Algorithm 12 of the + NIST FIPS 203 specification; this is the Kyber CPA-PKE key generation + algorithm. + + We say "most of" since Algorithm 12 samples the required randomness within + the function itself, whereas this implementation expects it to be provided + through the `key_generation_seed` parameter. + + Algorithm 12 is reproduced below: + + ```plaintext + Output: encryption key ekₚₖₑ ∈ 𝔹^{384k+32}. + Output: decryption key dkₚₖₑ ∈ 𝔹^{384k}. + + d ←$ B + (ρ,σ) ← G(d) + N ← 0 + for (i ← 0; i < k; i++) + for(j ← 0; j < k; j++) + Â[i,j] ← SampleNTT(XOF(ρ, i, j)) + end for + end for + for(i ← 0; i < k; i++) + s[i] ← SamplePolyCBD_{η₁}(PRF_{η₁}(σ,N)) + N ← N + 1 + end for + for(i ← 0; i < k; i++) + e[i] ← SamplePolyCBD_{η₂}(PRF_{η₂}(σ,N)) + N ← N + 1 + end for + ŝ ← NTT(s) + ê ← NTT(e) + t̂ ← Â◦ŝ + ê + ekₚₖₑ ← ByteEncode₁₂(t̂) ‖ ρ + dkₚₖₑ ← ByteEncode₁₂(ŝ) + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.generate_keypair_unpacked +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_1c( + Eurydice_slice key_generation_seed, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 *private_key, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 *public_key) { + uint8_t hashed[64U]; + libcrux_ml_kem_variant_cpa_keygen_seed_39_9c(key_generation_seed, hashed); + Eurydice_slice_uint8_t_x2 uu____0 = Eurydice_slice_split_at( + Eurydice_array_to_slice((size_t)64U, hashed, uint8_t), (size_t)32U, + uint8_t, Eurydice_slice_uint8_t_x2); + Eurydice_slice seed_for_A = uu____0.fst; + Eurydice_slice seed_for_secret_and_error = uu____0.snd; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d(*uu____1)[3U] = + public_key->A; + uint8_t ret[34U]; + libcrux_ml_kem_utils_into_padded_array_b6(seed_for_A, ret); + libcrux_ml_kem_matrix_sample_matrix_A_2b(uu____1, ret, true); + uint8_t prf_input[33U]; + libcrux_ml_kem_utils_into_padded_array_c8(seed_for_secret_and_error, + prf_input); + uint8_t domain_separator = + libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b( + private_key->secret_as_ntt, prf_input, 0U); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_as_ntt[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + error_as_ntt[i] = + libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_call_mut_73_1c(&lvalue, + i); + } + libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b(error_as_ntt, prf_input, + domain_separator); + libcrux_ml_kem_matrix_compute_As_plus_e_1b( + public_key->t_as_ntt, public_key->A, private_key->secret_as_ntt, + error_as_ntt); + uint8_t uu____2[32U]; + Result_fb dst; + Eurydice_slice_to_array2(&dst, seed_for_A, Eurydice_slice, uint8_t[32U], + TryFromSliceError); + unwrap_26_b3(dst, uu____2); + memcpy(public_key->seed_for_A, uu____2, (size_t)32U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.serialize_uncompressed_ring_element with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_serialize_uncompressed_ring_element_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, uint8_t ret[384U]) { + uint8_t serialized[384U] = {0U}; + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re->coefficients[i0]); + uint8_t bytes[24U]; + libcrux_ml_kem_vector_portable_serialize_12_b8(coefficient, bytes); + Eurydice_slice_copy( + Eurydice_array_to_subslice3(serialized, (size_t)24U * i0, + (size_t)24U * i0 + (size_t)24U, uint8_t *), + Eurydice_array_to_slice((size_t)24U, bytes, uint8_t), uint8_t); + } + memcpy(ret, serialized, (size_t)384U * sizeof(uint8_t)); +} + +/** + Call [`serialize_uncompressed_ring_element`] for each ring element. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_vector +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_serialize_vector_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *key, + Eurydice_slice out) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = key[i0]; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + (i0 + (size_t)1U) * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + uint8_t *); + uint8_t ret[384U]; + libcrux_ml_kem_serialize_serialize_uncompressed_ring_element_ea(&re, ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)384U, ret, uint8_t), uint8_t); + } +} + +/** + Concatenate `t` and `ρ` into the public key. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_public_key_mut +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_serialize_public_key_mut_89( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + Eurydice_slice seed_for_a, uint8_t *serialized) { + libcrux_ml_kem_ind_cpa_serialize_vector_1b( + t_as_ntt, + Eurydice_array_to_subslice3( + serialized, (size_t)0U, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t *)); + Eurydice_slice_copy( + Eurydice_array_to_subslice_from( + (size_t)1184U, serialized, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t, size_t, uint8_t[]), + seed_for_a, uint8_t); +} + +/** + Concatenate `t` and `ρ` into the public key. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_public_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_serialize_public_key_89( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + Eurydice_slice seed_for_a, uint8_t ret[1184U]) { + uint8_t public_key_serialized[1184U] = {0U}; + libcrux_ml_kem_ind_cpa_serialize_public_key_mut_89(t_as_ntt, seed_for_a, + public_key_serialized); + memcpy(ret, public_key_serialized, (size_t)1184U * sizeof(uint8_t)); +} + +/** + Serialize the secret key from the unpacked key pair generation. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_unpacked_secret_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PRIVATE_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +*/ +static inline libcrux_ml_kem_utils_extraction_helper_Keypair768 +libcrux_ml_kem_ind_cpa_serialize_unpacked_secret_key_6c( + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 *public_key, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 *private_key) { + uint8_t public_key_serialized[1184U]; + libcrux_ml_kem_ind_cpa_serialize_public_key_89( + public_key->t_as_ntt, + Eurydice_array_to_slice((size_t)32U, public_key->seed_for_A, uint8_t), + public_key_serialized); + uint8_t secret_key_serialized[1152U] = {0U}; + libcrux_ml_kem_ind_cpa_serialize_vector_1b( + private_key->secret_as_ntt, + Eurydice_array_to_slice((size_t)1152U, secret_key_serialized, uint8_t)); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_secret_key_serialized[1152U]; + memcpy(copy_of_secret_key_serialized, secret_key_serialized, + (size_t)1152U * sizeof(uint8_t)); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_public_key_serialized[1184U]; + memcpy(copy_of_public_key_serialized, public_key_serialized, + (size_t)1184U * sizeof(uint8_t)); + libcrux_ml_kem_utils_extraction_helper_Keypair768 lit; + memcpy(lit.fst, copy_of_secret_key_serialized, + (size_t)1152U * sizeof(uint8_t)); + memcpy(lit.snd, copy_of_public_key_serialized, + (size_t)1184U * sizeof(uint8_t)); + return lit; +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.generate_keypair +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- PRIVATE_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_utils_extraction_helper_Keypair768 +libcrux_ml_kem_ind_cpa_generate_keypair_ea(Eurydice_slice key_generation_seed) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 private_key = + libcrux_ml_kem_ind_cpa_unpacked_default_70_1b(); + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 public_key = + libcrux_ml_kem_ind_cpa_unpacked_default_8b_1b(); + libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_1c( + key_generation_seed, &private_key, &public_key); + return libcrux_ml_kem_ind_cpa_serialize_unpacked_secret_key_6c(&public_key, + &private_key); +} + +/** + Serialize the secret key. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.serialize_kem_secret_key_mut +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +- SERIALIZED_KEY_LEN= 2400 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ind_cca_serialize_kem_secret_key_mut_d6( + Eurydice_slice private_key, Eurydice_slice public_key, + Eurydice_slice implicit_rejection_value, uint8_t *serialized) { + size_t pointer = (size_t)0U; + uint8_t *uu____0 = serialized; + size_t uu____1 = pointer; + size_t uu____2 = pointer; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, uu____1, uu____2 + Eurydice_slice_len(private_key, uint8_t), + uint8_t *), + private_key, uint8_t); + pointer = pointer + Eurydice_slice_len(private_key, uint8_t); + uint8_t *uu____3 = serialized; + size_t uu____4 = pointer; + size_t uu____5 = pointer; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____3, uu____4, uu____5 + Eurydice_slice_len(public_key, uint8_t), + uint8_t *), + public_key, uint8_t); + pointer = pointer + Eurydice_slice_len(public_key, uint8_t); + Eurydice_slice uu____6 = Eurydice_array_to_subslice3( + serialized, pointer, pointer + LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE, + uint8_t *); + uint8_t ret[32U]; + libcrux_ml_kem_hash_functions_portable_H_4a_e0(public_key, ret); + Eurydice_slice_copy( + uu____6, Eurydice_array_to_slice((size_t)32U, ret, uint8_t), uint8_t); + pointer = pointer + LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE; + uint8_t *uu____7 = serialized; + size_t uu____8 = pointer; + size_t uu____9 = pointer; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____7, uu____8, + uu____9 + Eurydice_slice_len(implicit_rejection_value, uint8_t), + uint8_t *), + implicit_rejection_value, uint8_t); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.serialize_kem_secret_key +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +- SERIALIZED_KEY_LEN= 2400 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cca_serialize_kem_secret_key_d6( + Eurydice_slice private_key, Eurydice_slice public_key, + Eurydice_slice implicit_rejection_value, uint8_t ret[2400U]) { + uint8_t out[2400U] = {0U}; + libcrux_ml_kem_ind_cca_serialize_kem_secret_key_mut_d6( + private_key, public_key, implicit_rejection_value, out); + memcpy(ret, out, (size_t)2400U * sizeof(uint8_t)); +} + +/** + Packed API + + Generate a key pair. + + Depending on the `Vector` and `Hasher` used, this requires different hardware + features +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.generate_keypair +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- CPA_PRIVATE_KEY_SIZE= 1152 +- PRIVATE_KEY_SIZE= 2400 +- PUBLIC_KEY_SIZE= 1184 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_ind_cca_generate_keypair_15(uint8_t *randomness) { + Eurydice_slice ind_cpa_keypair_randomness = Eurydice_array_to_subslice3( + randomness, (size_t)0U, + LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE, uint8_t *); + Eurydice_slice implicit_rejection_value = Eurydice_array_to_subslice_from( + (size_t)64U, randomness, + LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE, uint8_t, + size_t, uint8_t[]); + libcrux_ml_kem_utils_extraction_helper_Keypair768 uu____0 = + libcrux_ml_kem_ind_cpa_generate_keypair_ea(ind_cpa_keypair_randomness); + uint8_t ind_cpa_private_key[1152U]; + memcpy(ind_cpa_private_key, uu____0.fst, (size_t)1152U * sizeof(uint8_t)); + uint8_t public_key[1184U]; + memcpy(public_key, uu____0.snd, (size_t)1184U * sizeof(uint8_t)); + uint8_t secret_key_serialized[2400U]; + libcrux_ml_kem_ind_cca_serialize_kem_secret_key_d6( + Eurydice_array_to_slice((size_t)1152U, ind_cpa_private_key, uint8_t), + Eurydice_array_to_slice((size_t)1184U, public_key, uint8_t), + implicit_rejection_value, secret_key_serialized); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_secret_key_serialized[2400U]; + memcpy(copy_of_secret_key_serialized, secret_key_serialized, + (size_t)2400U * sizeof(uint8_t)); + libcrux_ml_kem_types_MlKemPrivateKey_d9 private_key = + libcrux_ml_kem_types_from_77_28(copy_of_secret_key_serialized); + libcrux_ml_kem_types_MlKemPrivateKey_d9 uu____2 = private_key; + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_public_key[1184U]; + memcpy(copy_of_public_key, public_key, (size_t)1184U * sizeof(uint8_t)); + return libcrux_ml_kem_types_from_17_74( + uu____2, libcrux_ml_kem_types_from_fd_d0(copy_of_public_key)); +} + +/** + Portable generate key pair. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.generate_keypair with const +generics +- K= 3 +- CPA_PRIVATE_KEY_SIZE= 1152 +- PRIVATE_KEY_SIZE= 2400 +- PUBLIC_KEY_SIZE= 1184 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_ind_cca_instantiations_portable_generate_keypair_ce( + uint8_t *randomness) { + return libcrux_ml_kem_ind_cca_generate_keypair_15(randomness); +} + +/** + Generate ML-KEM 768 Key Pair +*/ +libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_mlkem768_portable_generate_key_pair(uint8_t randomness[64U]) { + return libcrux_ml_kem_ind_cca_instantiations_portable_generate_keypair_ce( + randomness); +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for +libcrux_ml_kem::serialize::deserialize_ring_elements_reduced_out::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_ring_elements_reduced_out.call_mut_0b with +types libcrux_ml_kem_vector_portable_vector_type_PortableVector with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_call_mut_0b_1b( + void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** + This function deserializes ring elements and reduces the result by the field + modulus. + + This function MUST NOT be used on secret inputs. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_ring_elements_reduced_out with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_1b( + Eurydice_slice public_key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d deserialized_pk[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + deserialized_pk[i] = + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_call_mut_0b_1b( + &lvalue, i); + } + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_1b( + public_key, deserialized_pk); + memcpy( + ret, deserialized_pk, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** + Validate an ML-KEM public key. + + This implements the Modulus check in 7.2 2. + Note that the size check in 7.2 1 is covered by the `PUBLIC_KEY_SIZE` in the + `public_key` type. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.validate_public_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE bool libcrux_ml_kem_ind_cca_validate_public_key_89( + uint8_t *public_key) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d deserialized_pk[3U]; + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_1b( + Eurydice_array_to_subslice_to( + (size_t)1184U, public_key, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t, size_t, uint8_t[]), + deserialized_pk); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____0 = deserialized_pk; + uint8_t public_key_serialized[1184U]; + libcrux_ml_kem_ind_cpa_serialize_public_key_89( + uu____0, + Eurydice_array_to_subslice_from( + (size_t)1184U, public_key, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t, size_t, uint8_t[]), + public_key_serialized); + return Eurydice_array_eq((size_t)1184U, public_key, public_key_serialized, + uint8_t); +} + +/** + Public key validation +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.validate_public_key with const +generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE bool +libcrux_ml_kem_ind_cca_instantiations_portable_validate_public_key_41( + uint8_t *public_key) { + return libcrux_ml_kem_ind_cca_validate_public_key_89(public_key); +} + +/** + Validate a public key. + + Returns `true` if valid, and `false` otherwise. +*/ +bool libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key) { + return libcrux_ml_kem_ind_cca_instantiations_portable_validate_public_key_41( + public_key->value); +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_mlkem768_portable_H_DEFINED +#endif /* libcrux_mlkem768_portable_H */ + + +/* rename some types to be a bit more ergonomic */ +#define libcrux_mlkem768_keypair libcrux_ml_kem_mlkem768_MlKem768KeyPair_s +#define libcrux_mlkem768_pk libcrux_ml_kem_types_MlKemPublicKey_30_s +#define libcrux_mlkem768_sk libcrux_ml_kem_types_MlKemPrivateKey_d9_s +#define libcrux_mlkem768_ciphertext libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s +#define libcrux_mlkem768_enc_result tuple_c2_s +/* defines for PRNG inputs */ +#define LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN 64U +#define LIBCRUX_ML_KEM_ENC_PRNG_LEN 32 diff --git a/src/libs/libssh-0.12.2/src/external/poly1305.c b/src/libs/libssh-0.12.2/src/external/poly1305.c new file mode 100644 index 000000000000..916dd625fffa --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/poly1305.c @@ -0,0 +1,156 @@ +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#include "config.h" + +#include +#include + +#include "libssh/poly1305.h" + +#define mul32x32_64(a,b) ((uint64_t)(a) * (b)) + +#define U8TO32_LE(p) \ + (((uint32_t)((p)[0])) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LE(p, v) \ + do { \ + (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); \ + } while (0) + +void +poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) { + uint32_t t0,t1,t2,t3; + uint32_t h0,h1,h2,h3,h4; + uint32_t r0,r1,r2,r3,r4; + uint32_t s1,s2,s3,s4; + uint32_t b, nb; + size_t j; + uint64_t t[5]; + uint64_t f0,f1,f2,f3; + uint32_t g0,g1,g2,g3,g4; + uint64_t c; + unsigned char mp[16]; + + /* clamp key */ + t0 = U8TO32_LE(key+0); + t1 = U8TO32_LE(key+4); + t2 = U8TO32_LE(key+8); + t3 = U8TO32_LE(key+12); + + /* precompute multipliers */ + r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6; + r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12; + r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18; + r3 = t2 & 0x3f03fff; t3 >>= 8; + r4 = t3 & 0x00fffff; + + s1 = r1 * 5; + s2 = r2 * 5; + s3 = r3 * 5; + s4 = r4 * 5; + + /* init state */ + h0 = 0; + h1 = 0; + h2 = 0; + h3 = 0; + h4 = 0; + + /* full blocks */ + if (inlen < 16) goto poly1305_donna_atmost15bytes; +poly1305_donna_16bytes: + m += 16; + inlen -= 16; + + t0 = U8TO32_LE(m-16); + t1 = U8TO32_LE(m-12); + t2 = U8TO32_LE(m-8); + t3 = U8TO32_LE(m-4); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8) | (1 << 24); + + +poly1305_donna_mul: + t[0] = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1); + t[1] = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2); + t[2] = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3); + t[3] = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4); + t[4] = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0); + + h0 = (uint32_t)t[0] & 0x3ffffff; c = (t[0] >> 26); + t[1] += c; h1 = (uint32_t)t[1] & 0x3ffffff; b = (uint32_t)(t[1] >> 26); + t[2] += b; h2 = (uint32_t)t[2] & 0x3ffffff; b = (uint32_t)(t[2] >> 26); + t[3] += b; h3 = (uint32_t)t[3] & 0x3ffffff; b = (uint32_t)(t[3] >> 26); + t[4] += b; h4 = (uint32_t)t[4] & 0x3ffffff; b = (uint32_t)(t[4] >> 26); + h0 += b * 5; + + if (inlen >= 16) goto poly1305_donna_16bytes; + + /* final bytes */ +poly1305_donna_atmost15bytes: + if (!inlen) goto poly1305_donna_finish; + + for (j = 0; j < inlen; j++) mp[j] = m[j]; + mp[j++] = 1; + for (; j < 16; j++) mp[j] = 0; + inlen = 0; + + t0 = U8TO32_LE(mp+0); + t1 = U8TO32_LE(mp+4); + t2 = U8TO32_LE(mp+8); + t3 = U8TO32_LE(mp+12); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8); + + goto poly1305_donna_mul; + +poly1305_donna_finish: + b = h0 >> 26; h0 = h0 & 0x3ffffff; + h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff; + h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff; + h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff; + h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff; + h0 += b * 5; b = h0 >> 26; h0 = h0 & 0x3ffffff; + h1 += b; + + g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff; + g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff; + g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff; + g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff; + g4 = h4 + b - (1 << 26); + + b = (g4 >> 31) - 1; + nb = ~b; + h0 = (h0 & nb) | (g0 & b); + h1 = (h1 & nb) | (g1 & b); + h2 = (h2 & nb) | (g2 & b); + h3 = (h3 & nb) | (g3 & b); + h4 = (h4 & nb) | (g4 & b); + + f0 = ((h0 ) | (h1 << 26)) + (uint64_t)U8TO32_LE(&key[16]); + f1 = ((h1 >> 6) | (h2 << 20)) + (uint64_t)U8TO32_LE(&key[20]); + f2 = ((h2 >> 12) | (h3 << 14)) + (uint64_t)U8TO32_LE(&key[24]); + f3 = ((h3 >> 18) | (h4 << 8)) + (uint64_t)U8TO32_LE(&key[28]); + + U32TO8_LE(&out[ 0], f0); f1 += (f0 >> 32); + U32TO8_LE(&out[ 4], f1); f2 += (f1 >> 32); + U32TO8_LE(&out[ 8], f2); f3 += (f2 >> 32); + U32TO8_LE(&out[12], f3); +} diff --git a/src/libs/libssh-0.12.2/src/external/sc25519.c b/src/libs/libssh-0.12.2/src/external/sc25519.c new file mode 100644 index 000000000000..5da91f70d9c7 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/sc25519.c @@ -0,0 +1,375 @@ +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/sc25519.c + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/sc25519.h" + +/*Arithmetic modulo the group order m = 2^252 + 27742317777372353535851937790883648493 = 7237005577332262213973186563042994240857116359379907606001950938285454250989 */ + +static const uint32_t m[32] = { + 0xED, 0xD3, 0xF5, 0x5C, 0x1A, 0x63, 0x12, 0x58, + 0xD6, 0x9C, 0xF7, 0xA2, 0xDE, 0xF9, 0xDE, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 +}; + +static const uint32_t mu[33] = { + 0x1B, 0x13, 0x2C, 0x0A, 0xA3, 0xE5, 0x9C, 0xED, + 0xA7, 0x29, 0x63, 0x08, 0x5D, 0x21, 0x06, 0x21, + 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F +}; + +static uint32_t lt(uint32_t a,uint32_t b) /* 16-bit inputs */ +{ + unsigned int x = a; + + x -= (unsigned int) b; /* 0..65535: no; 4294901761..4294967295: yes */ + x >>= 31; /* 0: no; 1: yes */ + + return x; +} + +/* Reduce coefficients of r before calling reduce_add_sub */ +static void reduce_add_sub(sc25519 *r) +{ + uint32_t pb = 0; + uint32_t b; + uint32_t mask; + int i; + unsigned char t[32]; + + for (i = 0; i < 32; i++) { + pb += m[i]; + b = lt(r->v[i],pb); + t[i] = r->v[i]-pb+(b<<8); + pb = b; + } + mask = b - 1; + for (i = 0; i < 32; i++) { + r->v[i] ^= mask & (r->v[i] ^ t[i]); + } +} + +/* Reduce coefficients of x before calling barrett_reduce */ +static void barrett_reduce(sc25519 *r, const uint32_t x[64]) +{ + /* See HAC, Alg. 14.42 */ + int i,j; + uint32_t q2[66]; + uint32_t *q3 = q2 + 33; + uint32_t r1[33]; + uint32_t r2[33]; + uint32_t carry; + uint32_t pb = 0; + uint32_t b; + + for (i = 0; i < 66; i++) { + q2[i] = 0; + } + for (i = 0; i < 33; i++) { + r2[i] = 0; + } + + for (i = 0; i < 33; i++) { + for (j = 0; j < 33; j++) { + if (i + j >= 31) { + q2[i+j] += mu[i]*x[j+31]; + } + } + } + + carry = q2[31] >> 8; + q2[32] += carry; + carry = q2[32] >> 8; + q2[33] += carry; + + for (i = 0; i < 33; i++) { + r1[i] = x[i]; + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 33; j++) { + if (i + j < 33) { + r2[i+j] += m[i]*q3[j]; + } + } + } + + for (i = 0; i < 32; i++) { + carry = r2[i] >> 8; + r2[i+1] += carry; + r2[i] &= 0xff; + } + + for (i = 0; i < 32; i++) { + pb += r2[i]; + b = lt(r1[i],pb); + r->v[i] = r1[i]-pb+(b<<8); + pb = b; + } + + /* XXX: Can it really happen that r<0?, See HAC, Alg 14.42, Step 3 + * If so: Handle it here! + */ + + reduce_add_sub(r); + reduce_add_sub(r); +} + +void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]) +{ + int i; + uint32_t t[64]; + + for (i = 0; i < 32; i++) { + t[i] = x[i]; + } + for (i = 32; i < 64; i++) { + t[i] = 0; + } + + barrett_reduce(r, t); +} + +void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]) +{ + int i; + + for (i = 0; i < 16; i++) { + r->v[i] = x[i]; + } +} + +void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]) +{ + int i; + uint32_t t[64]; + + for (i = 0; i < 64; i++) { + t[i] = x[i]; + } + + barrett_reduce(r, t); +} + +void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x) +{ + int i; + + for (i = 0; i < 16; i++) { + r->v[i] = x->v[i]; + } + for (i = 0; i < 16; i++) { + r->v[16+i] = 0; + } +} + +void sc25519_to32bytes(unsigned char r[32], const sc25519 *x) +{ + int i; + + for (i = 0; i < 32; i++) { + r[i] = x->v[i]; + } +} + +int sc25519_iszero_vartime(const sc25519 *x) +{ + int i; + + for (i = 0; i < 32; i++) { + if(x->v[i] != 0) { + return 0; + } + } + + return 1; +} + +int sc25519_isshort_vartime(const sc25519 *x) +{ + int i; + + for (i = 31; i > 15; i--) { + if (x->v[i] != 0) { + return 0; + } + } + + return 1; +} + +int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y) +{ + int i; + + for (i = 31; i >= 0; i--) { + if (x->v[i] < y->v[i]) { + return 1; + } + if (x->v[i] > y->v[i]) { + return 0; + } + } + + return 0; +} + +void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + uint32_t i, carry; + + for (i = 0; i < 32; i++) { + r->v[i] = x->v[i] + y->v[i]; + } + + for (i = 0;i < 31; i++) { + carry = r->v[i] >> 8; + r->v[i+1] += carry; + r->v[i] &= 0xff; + } + + reduce_add_sub(r); +} + +void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + uint32_t b = 0; + uint32_t t; + int i; + + for (i = 0; i < 32; i++) { + t = x->v[i] - y->v[i] - b; + r->v[i] = t & 255; + b = (t >> 8) & 1; + } +} + +void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + uint32_t i,j,carry; + uint32_t t[64]; + + for (i = 0; i < 64; i++) { + t[i] = 0; + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 32; j++) { + t[i+j] += x->v[i] * y->v[j]; + } + } + + /* Reduce coefficients */ + for (i = 0; i < 63; i++) { + carry = t[i] >> 8; + t[i+1] += carry; + t[i] &= 0xff; + } + + barrett_reduce(r, t); +} + +void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y) +{ + sc25519 t; + sc25519_from_shortsc(&t, y); + sc25519_mul(r, x, &t); +} + +void sc25519_window3(signed char r[85], const sc25519 *s) +{ + char carry; + int i; + + for (i = 0; i < 10; i++) { + r[8*i+0] = s->v[3*i+0] & 7; + r[8*i+1] = (s->v[3*i+0] >> 3) & 7; + r[8*i+2] = (s->v[3*i+0] >> 6) & 7; + r[8*i+2] ^= (s->v[3*i+1] << 2) & 7; + r[8*i+3] = (s->v[3*i+1] >> 1) & 7; + r[8*i+4] = (s->v[3*i+1] >> 4) & 7; + r[8*i+5] = (s->v[3*i+1] >> 7) & 7; + r[8*i+5] ^= (s->v[3*i+2] << 1) & 7; + r[8*i+6] = (s->v[3*i+2] >> 2) & 7; + r[8*i+7] = (s->v[3*i+2] >> 5) & 7; + } + r[8*i+0] = s->v[3*i+0] & 7; + r[8*i+1] = (s->v[3*i+0] >> 3) & 7; + r[8*i+2] = (s->v[3*i+0] >> 6) & 7; + r[8*i+2] ^= (s->v[3*i+1] << 2) & 7; + r[8*i+3] = (s->v[3*i+1] >> 1) & 7; + r[8*i+4] = (s->v[3*i+1] >> 4) & 7; + + /* Making it signed */ + carry = 0; + for (i = 0; i < 84; i++) { + r[i] += carry; + r[i+1] += r[i] >> 3; + r[i] &= 7; + carry = r[i] >> 2; + r[i] -= carry<<3; + } + + r[84] += carry; +} + +void sc25519_window5(signed char r[51], const sc25519 *s) +{ + char carry; + int i; + + for (i = 0; i < 6; i++) { + r[8*i+0] = s->v[5*i+0] & 31; + r[8*i+1] = (s->v[5*i+0] >> 5) & 31; + r[8*i+1] ^= (s->v[5*i+1] << 3) & 31; + r[8*i+2] = (s->v[5*i+1] >> 2) & 31; + r[8*i+3] = (s->v[5*i+1] >> 7) & 31; + r[8*i+3] ^= (s->v[5*i+2] << 1) & 31; + r[8*i+4] = (s->v[5*i+2] >> 4) & 31; + r[8*i+4] ^= (s->v[5*i+3] << 4) & 31; + r[8*i+5] = (s->v[5*i+3] >> 1) & 31; + r[8*i+6] = (s->v[5*i+3] >> 6) & 31; + r[8*i+6] ^= (s->v[5*i+4] << 2) & 31; + r[8*i+7] = (s->v[5*i+4] >> 3) & 31; + } + r[8*i+0] = s->v[5*i+0] & 31; + r[8*i+1] = (s->v[5*i+0] >> 5) & 31; + r[8*i+1] ^= (s->v[5*i+1] << 3) & 31; + r[8*i+2] = (s->v[5*i+1] >> 2) & 31; + + /* Making it signed */ + carry = 0; + for (i = 0; i < 50; i++) { + r[i] += carry; + r[i+1] += r[i] >> 5; + r[i] &= 31; + carry = r[i] >> 4; + r[i] -= carry<<5; + } + + r[50] += carry; +} + +void sc25519_2interleave2(unsigned char r[127], + const sc25519 *s1, + const sc25519 *s2) +{ + int i; + + for (i = 0; i < 31; i++) { + r[4*i] = ( s1->v[i] & 3) ^ (( s2->v[i] & 3) << 2); + r[4*i+1] = ((s1->v[i] >> 2) & 3) ^ (((s2->v[i] >> 2) & 3) << 2); + r[4*i+2] = ((s1->v[i] >> 4) & 3) ^ (((s2->v[i] >> 4) & 3) << 2); + r[4*i+3] = ((s1->v[i] >> 6) & 3) ^ (((s2->v[i] >> 6) & 3) << 2); + } + r[124] = ( s1->v[31] & 3) ^ (( s2->v[31] & 3) << 2); + r[125] = ((s1->v[31] >> 2) & 3) ^ (((s2->v[31] >> 2) & 3) << 2); + r[126] = ((s1->v[31] >> 4) & 3) ^ (((s2->v[31] >> 4) & 3) << 2); +} diff --git a/src/libs/libssh-0.12.2/src/external/sntrup761.c b/src/libs/libssh-0.12.2/src/external/sntrup761.c new file mode 100644 index 000000000000..2f45622dddd9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/sntrup761.c @@ -0,0 +1,1058 @@ +/* + * Derived from public domain source, written by (in alphabetical order): + * - Daniel J. Bernstein + * - Chitchanok Chuengsatiansup + * - Tanja Lange + * - Christine van Vredendaal + */ + +#include +#include + +#define SNTRUP761_SECRETKEY_SIZE 1763 +#define SNTRUP761_PUBLICKEY_SIZE 1158 +#define SNTRUP761_CIPHERTEXT_SIZE 1039 +#define SNTRUP761_SIZE 32 + +typedef void sntrup761_random_func (void *ctx, size_t length, uint8_t *dst); + +void +sntrup761_keypair (uint8_t *pk, uint8_t *sk, + void *random_ctx, sntrup761_random_func *random); + +void +sntrup761_enc (uint8_t *c, uint8_t *k, const uint8_t *pk, + void *random_ctx, sntrup761_random_func *random); + +void +sntrup761_dec (uint8_t *k, const uint8_t *c, const uint8_t *sk); + +extern int sha512(const unsigned char *digest, size_t len, unsigned char *hash); + +#define MAX_LEN 761 + +/* from supercop-20201130/crypto_sort/int32/portable4/int32_minmax.inc */ +#define int32_MINMAX(a,b) \ +do { \ + int64_t ab = (int64_t)b ^ (int64_t)a; \ + int64_t c = (int64_t)b - (int64_t)a; \ + c ^= ab & (c ^ b); \ + c >>= 31; \ + c &= ab; \ + a ^= c; \ + b ^= c; \ +} while(0) + +/* from supercop-20201130/crypto_sort/int32/portable4/sort.c */ +static void +crypto_sort_int32 (void *array, long long n) +{ + long long top, p, q, r, i, j; + int32_t *x = array; + + if (n < 2) + return; + top = 1; + while (top < n - top) + top += top; + + for (p = top; p >= 1; p >>= 1) + { + i = 0; + while (i + 2 * p <= n) + { + for (j = i; j < i + p; ++j) + int32_MINMAX (x[j], x[j + p]); + i += 2 * p; + } + for (j = i; j < n - p; ++j) + int32_MINMAX (x[j], x[j + p]); + + i = 0; + j = 0; + for (q = top; q > p; q >>= 1) + { + if (j != i) + for (;;) + { + int32_t a; + if (j == n - q) + goto done; + a = x[j + p]; + for (r = q; r > p; r >>= 1) + int32_MINMAX (a, x[j + r]); + x[j + p] = a; + ++j; + if (j == i + p) + { + i += 2 * p; + break; + } + } + while (i + p <= n - q) + { + for (j = i; j < i + p; ++j) + { + int32_t a = x[j + p]; + for (r = q; r > p; r >>= 1) + int32_MINMAX (a, x[j + r]); + x[j + p] = a; + } + i += 2 * p; + } + /* now i + p > n - q */ + j = i; + while (j < n - q) + { + int32_t a = x[j + p]; + for (r = q; r > p; r >>= 1) + int32_MINMAX (a, x[j + r]); + x[j + p] = a; + ++j; + } + + done:; + } + } +} + +/* from supercop-20201130/crypto_sort/uint32/useint32/sort.c */ + +/* can save time by vectorizing xor loops */ +/* can save time by integrating xor loops with int32_sort */ + +static void +crypto_sort_uint32 (void *array, long long n) +{ + uint32_t *x = array; + long long j; + for (j = 0; j < n; ++j) + x[j] ^= 0x80000000; + crypto_sort_int32 (array, n); + for (j = 0; j < n; ++j) + x[j] ^= 0x80000000; +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/uint32.c */ + +/* +CPU division instruction typically takes time depending on x. +This software is designed to take time independent of x. +Time still varies depending on m; user must ensure that m is constant. +Time also varies on CPUs where multiplication is variable-time. +There could be more CPU issues. +There could also be compiler issues. +*/ + +static void +uint32_divmod_uint14 (uint32_t * q, uint16_t * r, uint32_t x, uint16_t m) +{ + uint32_t v = 0x80000000; + uint32_t qpart; + uint32_t mask; + + v /= m; + + /* caller guarantees m > 0 */ + /* caller guarantees m < 16384 */ + /* vm <= 2^31 <= vm+m-1 */ + /* xvm <= 2^31 x <= xvm+x(m-1) */ + + *q = 0; + + qpart = (x * (uint64_t) v) >> 31; + /* 2^31 qpart <= xv <= 2^31 qpart + 2^31-1 */ + /* 2^31 qpart m <= xvm <= 2^31 qpart m + (2^31-1)m */ + /* 2^31 qpart m <= 2^31 x <= 2^31 qpart m + (2^31-1)m + x(m-1) */ + /* 0 <= 2^31 newx <= (2^31-1)m + x(m-1) */ + /* 0 <= newx <= (1-1/2^31)m + x(m-1)/2^31 */ + /* 0 <= newx <= (1-1/2^31)(2^14-1) + (2^32-1)((2^14-1)-1)/2^31 */ + + x -= qpart * m; + *q += qpart; + /* x <= 49146 */ + + qpart = (x * (uint64_t) v) >> 31; + /* 0 <= newx <= (1-1/2^31)m + x(m-1)/2^31 */ + /* 0 <= newx <= m + 49146(2^14-1)/2^31 */ + /* 0 <= newx <= m + 0.4 */ + /* 0 <= newx <= m */ + + x -= qpart * m; + *q += qpart; + /* x <= m */ + + x -= m; + *q += 1; + mask = -(x >> 31); + x += mask & (uint32_t) m; + *q += mask; + /* x < m */ + + *r = x; +} + + +static uint16_t +uint32_mod_uint14 (uint32_t x, uint16_t m) +{ + uint32_t q; + uint16_t r; + uint32_divmod_uint14 (&q, &r, x, m); + return r; +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/int32.c */ + +static void +int32_divmod_uint14 (int32_t * q, uint16_t * r, int32_t x, uint16_t m) +{ + uint32_t uq, uq2; + uint16_t ur, ur2; + uint32_t mask; + + uint32_divmod_uint14 (&uq, &ur, 0x80000000 + (uint32_t) x, m); + uint32_divmod_uint14 (&uq2, &ur2, 0x80000000, m); + ur -= ur2; + uq -= uq2; + mask = -(uint32_t) (ur >> 15); + ur += mask & m; + uq += mask; + *r = ur; + *q = uq; +} + + +static uint16_t +int32_mod_uint14 (int32_t x, uint16_t m) +{ + int32_t q; + uint16_t r; + int32_divmod_uint14 (&q, &r, x, m); + return r; +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/paramsmenu.h */ +#define p 761 +#define q 4591 +#define Rounded_bytes 1007 +#define Rq_bytes 1158 +#define w 286 + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Decode.h */ + +/* Decode(R,s,M,len) */ +/* assumes 0 < M[i] < 16384 */ +/* produces 0 <= R[i] < M[i] */ + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Decode.c */ + +static void +Decode (uint16_t * out, const unsigned char *S, const uint16_t * M, + long long len) +{ + if (len == 1) + { + if (M[0] == 1) + *out = 0; + else if (M[0] <= 256) + *out = uint32_mod_uint14 (S[0], M[0]); + else + *out = uint32_mod_uint14 (S[0] + (((uint16_t) S[1]) << 8), M[0]); + } + if (len > 1) + { + uint16_t R2[(MAX_LEN + 1) / 2]; + uint16_t M2[(MAX_LEN + 1) / 2]; + uint16_t bottomr[MAX_LEN / 2]; + uint32_t bottomt[MAX_LEN / 2]; + long long i; + for (i = 0; i < len - 1; i += 2) + { + uint32_t m = M[i] * (uint32_t) M[i + 1]; + if (m > 256 * 16383) + { + bottomt[i / 2] = 256 * 256; + bottomr[i / 2] = S[0] + 256 * S[1]; + S += 2; + M2[i / 2] = (((m + 255) >> 8) + 255) >> 8; + } + else if (m >= 16384) + { + bottomt[i / 2] = 256; + bottomr[i / 2] = S[0]; + S += 1; + M2[i / 2] = (m + 255) >> 8; + } + else + { + bottomt[i / 2] = 1; + bottomr[i / 2] = 0; + M2[i / 2] = m; + } + } + if (i < len) + M2[i / 2] = M[i]; + Decode (R2, S, M2, (len + 1) / 2); + for (i = 0; i < len - 1; i += 2) + { + uint32_t r = bottomr[i / 2]; + uint32_t r1; + uint16_t r0; + r += bottomt[i / 2] * R2[i / 2]; + uint32_divmod_uint14 (&r1, &r0, r, M[i]); + r1 = uint32_mod_uint14 (r1, M[i + 1]); /* only needed for invalid inputs */ + *out++ = r0; + *out++ = r1; + } + if (i < len) + *out++ = R2[i / 2]; + } +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Encode.h */ + +/* Encode(s,R,M,len) */ +/* assumes 0 <= R[i] < M[i] < 16384 */ + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Encode.c */ + +/* 0 <= R[i] < M[i] < 16384 */ +static void +Encode (unsigned char *out, const uint16_t * R, const uint16_t * M, + long long len) +{ + if (len == 1) + { + uint16_t r = R[0]; + uint16_t m = M[0]; + while (m > 1) + { + *out++ = r; + r >>= 8; + m = (m + 255) >> 8; + } + } + if (len > 1) + { + uint16_t R2[(MAX_LEN + 1) / 2]; + uint16_t M2[(MAX_LEN + 1) / 2]; + long long i; + for (i = 0; i < len - 1; i += 2) + { + uint32_t m0 = M[i]; + uint32_t r = R[i] + R[i + 1] * m0; + uint32_t m = M[i + 1] * m0; + while (m >= 16384) + { + *out++ = r; + r >>= 8; + m = (m + 255) >> 8; + } + R2[i / 2] = r; + M2[i / 2] = m; + } + if (i < len) + { + R2[i / 2] = R[i]; + M2[i / 2] = M[i]; + } + Encode (out, R2, M2, (len + 1) / 2); + } +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/kem.c */ + +/* ----- masks */ + +/* return -1 if x!=0; else return 0 */ +static int +int16_t_nonzero_mask (int16_t x) +{ + uint16_t u = x; /* 0, else 1...65535 */ + uint32_t v = u; /* 0, else 1...65535 */ + v = -v; /* 0, else 2^32-65535...2^32-1 */ + v >>= 31; /* 0, else 1 */ + return -v; /* 0, else -1 */ +} + +/* return -1 if x<0; otherwise return 0 */ +static int +int16_t_negative_mask (int16_t x) +{ + uint16_t u = x; + u >>= 15; + return -(int) u; + /* alternative with gcc -fwrapv: */ + /* x>>15 compiles to CPU's arithmetic right shift */ +} + +/* ----- arithmetic mod 3 */ + +typedef int8_t small; + +/* F3 is always represented as -1,0,1 */ +/* so ZZ_fromF3 is a no-op */ + +/* x must not be close to top int16_t */ +static small +F3_freeze (int16_t x) +{ + return int32_mod_uint14 (x + 1, 3) - 1; +} + +/* ----- arithmetic mod q */ + +#define q12 ((q-1)/2) +typedef int16_t Fq; +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +static Fq +Fq_freeze (int32_t x) +{ + return int32_mod_uint14 (x + q12, q) - q12; +} + +static Fq +Fq_recip (Fq a1) +{ + int i = 1; + Fq ai = a1; + + while (i < q - 2) + { + ai = Fq_freeze (a1 * (int32_t) ai); + i += 1; + } + return ai; +} + +/* ----- small polynomials */ + +/* 0 if Weightw_is(r), else -1 */ +static int +Weightw_mask (small * r) +{ + int weight = 0; + int i; + + for (i = 0; i < p; ++i) + weight += r[i] & 1; + return int16_t_nonzero_mask (weight - w); +} + +/* R3_fromR(R_fromRq(r)) */ +static void +R3_fromRq (small * out, const Fq * r) +{ + int i; + for (i = 0; i < p; ++i) + out[i] = F3_freeze (r[i]); +} + +/* h = f*g in the ring R3 */ +static void +R3_mult (small * h, const small * f, const small * g) +{ + small fg[p + p - 1]; + small result; + int i, j; + + for (i = 0; i < p; ++i) + { + result = 0; + for (j = 0; j <= i; ++j) + result = F3_freeze (result + f[j] * g[i - j]); + fg[i] = result; + } + for (i = p; i < p + p - 1; ++i) + { + result = 0; + for (j = i - p + 1; j < p; ++j) + result = F3_freeze (result + f[j] * g[i - j]); + fg[i] = result; + } + + for (i = p + p - 2; i >= p; --i) + { + fg[i - p] = F3_freeze (fg[i - p] + fg[i]); + fg[i - p + 1] = F3_freeze (fg[i - p + 1] + fg[i]); + } + + for (i = 0; i < p; ++i) + h[i] = fg[i]; +} + +/* returns 0 if recip succeeded; else -1 */ +static int +R3_recip (small * out, const small * in) +{ + small f[p + 1], g[p + 1], v[p + 1], r[p + 1]; + int i, loop, delta; + int sign, swap, t; + + for (i = 0; i < p + 1; ++i) + v[i] = 0; + for (i = 0; i < p + 1; ++i) + r[i] = 0; + r[0] = 1; + for (i = 0; i < p; ++i) + f[i] = 0; + f[0] = 1; + f[p - 1] = f[p] = -1; + for (i = 0; i < p; ++i) + g[p - 1 - i] = in[i]; + g[p] = 0; + + delta = 1; + + for (loop = 0; loop < 2 * p - 1; ++loop) + { + for (i = p; i > 0; --i) + v[i] = v[i - 1]; + v[0] = 0; + + sign = -g[0] * f[0]; + swap = int16_t_negative_mask (-delta) & int16_t_nonzero_mask (g[0]); + delta ^= swap & (delta ^ -delta); + delta += 1; + + for (i = 0; i < p + 1; ++i) + { + t = swap & (f[i] ^ g[i]); + f[i] ^= t; + g[i] ^= t; + t = swap & (v[i] ^ r[i]); + v[i] ^= t; + r[i] ^= t; + } + + for (i = 0; i < p + 1; ++i) + g[i] = F3_freeze (g[i] + sign * f[i]); + for (i = 0; i < p + 1; ++i) + r[i] = F3_freeze (r[i] + sign * v[i]); + + for (i = 0; i < p; ++i) + g[i] = g[i + 1]; + g[p] = 0; + } + + sign = f[0]; + for (i = 0; i < p; ++i) + out[i] = sign * v[p - 1 - i]; + + return int16_t_nonzero_mask (delta); +} + +/* ----- polynomials mod q */ + +/* h = f*g in the ring Rq */ +static void +Rq_mult_small (Fq * h, const Fq * f, const small * g) +{ + Fq fg[p + p - 1]; + Fq result; + int i, j; + + for (i = 0; i < p; ++i) + { + result = 0; + for (j = 0; j <= i; ++j) + result = Fq_freeze (result + f[j] * (int32_t) g[i - j]); + fg[i] = result; + } + for (i = p; i < p + p - 1; ++i) + { + result = 0; + for (j = i - p + 1; j < p; ++j) + result = Fq_freeze (result + f[j] * (int32_t) g[i - j]); + fg[i] = result; + } + + for (i = p + p - 2; i >= p; --i) + { + fg[i - p] = Fq_freeze (fg[i - p] + fg[i]); + fg[i - p + 1] = Fq_freeze (fg[i - p + 1] + fg[i]); + } + + for (i = 0; i < p; ++i) + h[i] = fg[i]; +} + +/* h = 3f in Rq */ +static void +Rq_mult3 (Fq * h, const Fq * f) +{ + int i; + + for (i = 0; i < p; ++i) + h[i] = Fq_freeze (3 * f[i]); +} + +/* out = 1/(3*in) in Rq */ +/* returns 0 if recip succeeded; else -1 */ +static int +Rq_recip3 (Fq * out, const small * in) +{ + Fq f[p + 1], g[p + 1], v[p + 1], r[p + 1]; + int i, loop, delta; + int swap, t; + int32_t f0, g0; + Fq scale; + + for (i = 0; i < p + 1; ++i) + v[i] = 0; + for (i = 0; i < p + 1; ++i) + r[i] = 0; + r[0] = Fq_recip (3); + for (i = 0; i < p; ++i) + f[i] = 0; + f[0] = 1; + f[p - 1] = f[p] = -1; + for (i = 0; i < p; ++i) + g[p - 1 - i] = in[i]; + g[p] = 0; + + delta = 1; + + for (loop = 0; loop < 2 * p - 1; ++loop) + { + for (i = p; i > 0; --i) + v[i] = v[i - 1]; + v[0] = 0; + + swap = int16_t_negative_mask (-delta) & int16_t_nonzero_mask (g[0]); + delta ^= swap & (delta ^ -delta); + delta += 1; + + for (i = 0; i < p + 1; ++i) + { + t = swap & (f[i] ^ g[i]); + f[i] ^= t; + g[i] ^= t; + t = swap & (v[i] ^ r[i]); + v[i] ^= t; + r[i] ^= t; + } + + f0 = f[0]; + g0 = g[0]; + for (i = 0; i < p + 1; ++i) + g[i] = Fq_freeze (f0 * g[i] - g0 * f[i]); + for (i = 0; i < p + 1; ++i) + r[i] = Fq_freeze (f0 * r[i] - g0 * v[i]); + + for (i = 0; i < p; ++i) + g[i] = g[i + 1]; + g[p] = 0; + } + + scale = Fq_recip (f[0]); + for (i = 0; i < p; ++i) + out[i] = Fq_freeze (scale * (int32_t) v[p - 1 - i]); + + return int16_t_nonzero_mask (delta); +} + +/* ----- rounded polynomials mod q */ + +static void +Round (Fq * out, const Fq * a) +{ + int i; + for (i = 0; i < p; ++i) + out[i] = a[i] - F3_freeze (a[i]); +} + +/* ----- sorting to generate short polynomial */ + +static void +Short_fromlist (small * out, const uint32_t * in) +{ + uint32_t L[p]; + int i; + + for (i = 0; i < w; ++i) + L[i] = in[i] & (uint32_t) - 2; + for (i = w; i < p; ++i) + L[i] = (in[i] & (uint32_t) - 3) | 1; + crypto_sort_uint32 (L, p); + for (i = 0; i < p; ++i) + out[i] = (L[i] & 3) - 1; +} + +/* ----- underlying hash function */ + +#define Hash_bytes 32 + +/* e.g., b = 0 means out = Hash0(in) */ +static void +Hash_prefix (unsigned char *out, int b, const unsigned char *in, int inlen) +{ +#define MAX_X_LEN 1158 + unsigned char x[MAX_X_LEN + 1]; + unsigned char h[64]; + int i; + + x[0] = b; + for (i = 0; i < inlen; ++i) + x[i + 1] = in[i]; + sha512 (x, inlen + 1, h); + for (i = 0; i < 32; ++i) + out[i] = h[i]; +} + +/* ----- higher-level randomness */ + +static uint32_t +urandom32 (void *random_ctx, sntrup761_random_func * random) +{ + unsigned char c[4]; + uint32_t out[4]; + + random (random_ctx, 4, c); + out[0] = (uint32_t) c[0]; + out[1] = ((uint32_t) c[1]) << 8; + out[2] = ((uint32_t) c[2]) << 16; + out[3] = ((uint32_t) c[3]) << 24; + return out[0] + out[1] + out[2] + out[3]; +} + +static void +Short_random (small * out, void *random_ctx, sntrup761_random_func * random) +{ + uint32_t L[p]; + int i; + + for (i = 0; i < p; ++i) + L[i] = urandom32 (random_ctx, random); + Short_fromlist (out, L); +} + +static void +Small_random (small * out, void *random_ctx, sntrup761_random_func * random) +{ + int i; + + for (i = 0; i < p; ++i) + out[i] = (((urandom32 (random_ctx, random) & 0x3fffffff) * 3) >> 30) - 1; +} + +/* ----- Streamlined NTRU Prime Core */ + +/* h,(f,ginv) = KeyGen() */ +static void +KeyGen (Fq * h, small * f, small * ginv, void *random_ctx, + sntrup761_random_func * random) +{ + small g[p]; + Fq finv[p]; + + for (;;) + { + Small_random (g, random_ctx, random); + if (R3_recip (ginv, g) == 0) + break; + } + Short_random (f, random_ctx, random); + Rq_recip3 (finv, f); /* always works */ + Rq_mult_small (h, finv, g); +} + +/* c = Encrypt(r,h) */ +static void +Encrypt (Fq * c, const small * r, const Fq * h) +{ + Fq hr[p]; + + Rq_mult_small (hr, h, r); + Round (c, hr); +} + +/* r = Decrypt(c,(f,ginv)) */ +static void +Decrypt (small * r, const Fq * c, const small * f, const small * ginv) +{ + Fq cf[p]; + Fq cf3[p]; + small e[p]; + small ev[p]; + int mask; + int i; + + Rq_mult_small (cf, c, f); + Rq_mult3 (cf3, cf); + R3_fromRq (e, cf3); + R3_mult (ev, e, ginv); + + mask = Weightw_mask (ev); /* 0 if weight w, else -1 */ + for (i = 0; i < w; ++i) + r[i] = ((ev[i] ^ 1) & ~mask) ^ 1; + for (i = w; i < p; ++i) + r[i] = ev[i] & ~mask; +} + +/* ----- encoding small polynomials (including short polynomials) */ + +#define Small_bytes ((p+3)/4) + +/* these are the only functions that rely on p mod 4 = 1 */ + +static void +Small_encode (unsigned char *s, const small * f) +{ + small x; + int i; + + for (i = 0; i < p / 4; ++i) + { + x = *f++ + 1; + x += (*f++ + 1) << 2; + x += (*f++ + 1) << 4; + x += (*f++ + 1) << 6; + *s++ = x; + } + x = *f++ + 1; + *s++ = x; +} + +static void +Small_decode (small * f, const unsigned char *s) +{ + unsigned char x; + int i; + + for (i = 0; i < p / 4; ++i) + { + x = *s++; + *f++ = ((small) (x & 3)) - 1; + x >>= 2; + *f++ = ((small) (x & 3)) - 1; + x >>= 2; + *f++ = ((small) (x & 3)) - 1; + x >>= 2; + *f++ = ((small) (x & 3)) - 1; + } + x = *s++; + *f++ = ((small) (x & 3)) - 1; +} + +/* ----- encoding general polynomials */ + +static void +Rq_encode (unsigned char *s, const Fq * r) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + R[i] = r[i] + q12; + for (i = 0; i < p; ++i) + M[i] = q; + Encode (s, R, M, p); +} + +static void +Rq_decode (Fq * r, const unsigned char *s) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + M[i] = q; + Decode (R, s, M, p); + for (i = 0; i < p; ++i) + r[i] = ((Fq) R[i]) - q12; +} + +/* ----- encoding rounded polynomials */ + +static void +Rounded_encode (unsigned char *s, const Fq * r) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + R[i] = ((r[i] + q12) * 10923) >> 15; + for (i = 0; i < p; ++i) + M[i] = (q + 2) / 3; + Encode (s, R, M, p); +} + +static void +Rounded_decode (Fq * r, const unsigned char *s) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + M[i] = (q + 2) / 3; + Decode (R, s, M, p); + for (i = 0; i < p; ++i) + r[i] = R[i] * 3 - q12; +} + +/* ----- Streamlined NTRU Prime Core plus encoding */ + +typedef small Inputs[p]; /* passed by reference */ +#define Inputs_random Short_random +#define Inputs_encode Small_encode +#define Inputs_bytes Small_bytes + +#define Ciphertexts_bytes Rounded_bytes +#define SecretKeys_bytes (2*Small_bytes) +#define PublicKeys_bytes Rq_bytes + +/* pk,sk = ZKeyGen() */ +static void +ZKeyGen (unsigned char *pk, unsigned char *sk, void *random_ctx, + sntrup761_random_func * random) +{ + Fq h[p]; + small f[p], v[p]; + + KeyGen (h, f, v, random_ctx, random); + Rq_encode (pk, h); + Small_encode (sk, f); + sk += Small_bytes; + Small_encode (sk, v); +} + +/* C = ZEncrypt(r,pk) */ +static void +ZEncrypt (unsigned char *C, const Inputs r, const unsigned char *pk) +{ + Fq h[p]; + Fq c[p]; + Rq_decode (h, pk); + Encrypt (c, r, h); + Rounded_encode (C, c); +} + +/* r = ZDecrypt(C,sk) */ +static void +ZDecrypt (Inputs r, const unsigned char *C, const unsigned char *sk) +{ + small f[p], v[p]; + Fq c[p]; + + Small_decode (f, sk); + sk += Small_bytes; + Small_decode (v, sk); + Rounded_decode (c, C); + Decrypt (r, c, f, v); +} + +/* ----- confirmation hash */ + +#define Confirm_bytes 32 + +/* h = HashConfirm(r,pk,cache); cache is Hash4(pk) */ +static void +HashConfirm (unsigned char *h, const unsigned char *r, + /* const unsigned char *pk, */ const unsigned char *cache) +{ + unsigned char x[Hash_bytes * 2]; + int i; + + Hash_prefix (x, 3, r, Inputs_bytes); + for (i = 0; i < Hash_bytes; ++i) + x[Hash_bytes + i] = cache[i]; + Hash_prefix (h, 2, x, sizeof x); +} + +/* ----- session-key hash */ + +/* k = HashSession(b,y,z) */ +static void +HashSession (unsigned char *k, int b, const unsigned char *y, + const unsigned char *z) +{ + unsigned char x[Hash_bytes + Ciphertexts_bytes + Confirm_bytes]; + int i; + + Hash_prefix (x, 3, y, Inputs_bytes); + for (i = 0; i < Ciphertexts_bytes + Confirm_bytes; ++i) + x[Hash_bytes + i] = z[i]; + Hash_prefix (k, b, x, sizeof x); +} + +/* ----- Streamlined NTRU Prime */ + +/* pk,sk = KEM_KeyGen() */ +void +sntrup761_keypair (unsigned char *pk, unsigned char *sk, void *random_ctx, + sntrup761_random_func * random) +{ + int i; + + ZKeyGen (pk, sk, random_ctx, random); + sk += SecretKeys_bytes; + for (i = 0; i < PublicKeys_bytes; ++i) + *sk++ = pk[i]; + random (random_ctx, Inputs_bytes, sk); + sk += Inputs_bytes; + Hash_prefix (sk, 4, pk, PublicKeys_bytes); +} + +/* c,r_enc = Hide(r,pk,cache); cache is Hash4(pk) */ +static void +Hide (unsigned char *c, unsigned char *r_enc, const Inputs r, + const unsigned char *pk, const unsigned char *cache) +{ + Inputs_encode (r_enc, r); + ZEncrypt (c, r, pk); + c += Ciphertexts_bytes; + HashConfirm (c, r_enc, cache); +} + +/* c,k = Encap(pk) */ +void +sntrup761_enc (unsigned char *c, unsigned char *k, const unsigned char *pk, + void *random_ctx, sntrup761_random_func * random) +{ + Inputs r; + unsigned char r_enc[Inputs_bytes]; + unsigned char cache[Hash_bytes]; + + Hash_prefix (cache, 4, pk, PublicKeys_bytes); + Inputs_random (r, random_ctx, random); + Hide (c, r_enc, r, pk, cache); + HashSession (k, 1, r_enc, c); +} + +/* 0 if matching ciphertext+confirm, else -1 */ +static int +Ciphertexts_diff_mask (const unsigned char *c, const unsigned char *c2) +{ + uint16_t differentbits = 0; + int len = Ciphertexts_bytes + Confirm_bytes; + + while (len-- > 0) + differentbits |= (*c++) ^ (*c2++); + return (1 & ((differentbits - 1) >> 8)) - 1; +} + +/* k = Decap(c,sk) */ +void +sntrup761_dec (unsigned char *k, const unsigned char *c, const unsigned char *sk) +{ + const unsigned char *pk = sk + SecretKeys_bytes; + const unsigned char *rho = pk + PublicKeys_bytes; + const unsigned char *cache = rho + Inputs_bytes; + Inputs r; + unsigned char r_enc[Inputs_bytes]; + unsigned char cnew[Ciphertexts_bytes + Confirm_bytes]; + int mask; + int i; + + ZDecrypt (r, c, sk); + Hide (cnew, r_enc, r, pk, cache); + mask = Ciphertexts_diff_mask (c, cnew); + for (i = 0; i < Inputs_bytes; ++i) + r_enc[i] ^= mask & (r_enc[i] ^ rho[i]); + HashSession (k, 1 + mask, r_enc, c); +} diff --git a/src/libs/libssh-0.12.2/src/gcrypt_missing.c b/src/libs/libssh-0.12.2/src/gcrypt_missing.c new file mode 100644 index 000000000000..5f84e6b6326b --- /dev/null +++ b/src/libs/libssh-0.12.2/src/gcrypt_missing.c @@ -0,0 +1,124 @@ +/* + * gcrypt_missing.c - routines that are in OpenSSL but not in libgcrypt. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2006 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "libssh/priv.h" +#include "libssh/libgcrypt.h" + +#ifdef HAVE_LIBGCRYPT +int ssh_gcry_dec2bn(bignum *bn, const char *data) { + int count; + + *bn = bignum_new(); + if (*bn == NULL) { + return 0; + } + gcry_mpi_set_ui(*bn, 0); + for (count = 0; data[count]; count++) { + gcry_mpi_mul_ui(*bn, *bn, 10); + gcry_mpi_add_ui(*bn, *bn, data[count] - '0'); + } + + return count; +} + +char *ssh_gcry_bn2dec(bignum bn) { + bignum bndup, num, ten; + char *ret = NULL; + int count, count2; + int size, rsize; + char decnum; + + size = gcry_mpi_get_nbits(bn) * 3; + rsize = size / 10 + size / 1000 + 2; + + ret = gcry_malloc(rsize + 1); + if (ret == NULL) { + return NULL; + } + + if (!gcry_mpi_cmp_ui(bn, 0)) { + strcpy(ret, "0"); + } else { + ten = bignum_new(); + if (ten == NULL) { + SAFE_FREE(ret); + return NULL; + } + + num = bignum_new(); + if (num == NULL) { + SAFE_FREE(ret); + bignum_safe_free(ten); + return NULL; + } + + for (bndup = gcry_mpi_copy(bn), bignum_set_word(ten, 10), count = rsize; + count; count--) { + gcry_mpi_div(bndup, num, bndup, ten, 0); + for (decnum = 0, count2 = gcry_mpi_get_nbits(num); count2; + decnum *= 2, decnum += (gcry_mpi_test_bit(num, count2 - 1) ? 1 : 0), + count2--) + ; + ret[count - 1] = decnum + '0'; + } + for (count = 0; count < rsize && ret[count] == '0'; count++) + ; + for (count2 = 0; count2 < rsize - count; ++count2) { + ret[count2] = ret[count2 + count]; + } + ret[count2] = 0; + bignum_safe_free(num); + bignum_safe_free(bndup); + bignum_safe_free(ten); + } + + return ret; +} + +/** @brief generates a random integer between 0 and max + * @returns 1 in case of success, 0 otherwise + */ +int ssh_gcry_rand_range(bignum dest, bignum max) +{ + size_t bits; + bignum rnd; + int rc; + + bits = bignum_num_bits(max) + 64; + rnd = bignum_new(); + if (rnd == NULL) { + return 0; + } + rc = bignum_rand(rnd, bits); + if (rc != 1) { + return rc; + } + gcry_mpi_mod(dest, rnd, max); + bignum_safe_free(rnd); + return 1; +} +#endif diff --git a/src/libs/libssh-0.12.2/src/getpass.c b/src/libs/libssh-0.12.2/src/getpass.c new file mode 100644 index 000000000000..2edab7d571c6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/getpass.c @@ -0,0 +1,295 @@ +/* + * getpass.c - platform independent getpass function. + * + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include + +/** + * @internal + * + * @brief Get the password from the console. + * + * @param[in] prompt The prompt to display. + * + * @param[in] buf The buffer to fill. + * + * @param[in] len The length of the buffer. + * + * @param[in] verify Should the password be verified? + * + * @return 1 on success, 0 on error. + */ +static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) +{ + char *tmp = NULL; + char *ptr = NULL; + int ok = 0; + + tmp = calloc(1, len); + if (tmp == NULL) { + return 0; + } + + /* read the password */ + while (!ok) { + if (buf[0] != '\0') { + fprintf(stdout, "%s[%s] ", prompt, buf); + } else { + fprintf(stdout, "%s", prompt); + } + fflush(stdout); + if (fgets(tmp, (int)len, stdin) == NULL) { + free(tmp); + return 0; + } + + if ((ptr = strchr(tmp, '\n'))) { + *ptr = '\0'; + } + fprintf(stdout, "\n"); + + if (*tmp) { + strncpy(buf, tmp, len); + } + + if (verify) { + char *key_string = NULL; + + key_string = calloc(1, len); + if (key_string == NULL) { + break; + } + + fprintf(stdout, "\nVerifying, please re-enter. %s", prompt); + fflush(stdout); + if (!fgets(key_string, (int)len, stdin)) { + ssh_burn(key_string, len); + SAFE_FREE(key_string); + clearerr(stdin); + continue; + } + if ((ptr = strchr(key_string, '\n'))) { + *ptr = '\0'; + } + fprintf(stdout, "\n"); + if (strcmp(buf, key_string)) { + printf("\n\07\07Mismatch - try again\n"); + ssh_burn(key_string, len); + SAFE_FREE(key_string); + fflush(stdout); + continue; + } + ssh_burn(key_string, len); + SAFE_FREE(key_string); + } + ok = 1; + } + ssh_burn(tmp, len); + free(tmp); + + return ok; +} + +#ifdef _WIN32 +#include + +int ssh_getpass(const char *prompt, + char *buf, + size_t len, + int echo, + int verify) +{ + HANDLE h; + DWORD mode = 0; + int ok; + + /* fgets needs at least len - 1 */ + if (prompt == NULL || buf == NULL || len < 2) { + return -1; + } + + /* get stdin and mode */ + h = GetStdHandle(STD_INPUT_HANDLE); + if (!GetConsoleMode(h, &mode)) { + return -1; + } + + /* disable echo */ + if (!echo) { + if (!SetConsoleMode(h, mode & ~ENABLE_ECHO_INPUT)) { + return -1; + } + } + + ok = ssh_gets(prompt, buf, len, verify); + + /* reset echo */ + SetConsoleMode(h, mode); + + if (!ok) { + ssh_burn(buf, len); + return -1; + } + + /* force termination */ + buf[len - 1] = '\0'; + + return 0; +} + +#else + +#include +#ifdef HAVE_TERMIOS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +/** + * @ingroup libssh_misc + * + * @brief Get a password from the console. + * + * You should make sure that the buffer is an empty string! + * + * You can also use this function to ask for a username. Then you can fill the + * buffer with the username and it is shows to the users. If the users just + * presses enter the buffer will be untouched. + * + * @code + * char username[128]; + * + * snprintf(username, sizeof(username), "john"); + * + * ssh_getpass("Username:", username, sizeof(username), 1, 0); + * @endcode + * + * The prompt will look like this: + * + * Username: [john] + * + * If you press enter then john is used as the username, or you can type it in + * to change it. + * + * @param[in] prompt The prompt to show to ask for the password. + * + * @param[out] buf The buffer the password should be stored. It NEEDS to be + * empty or filled out. + * + * @param[in] len The length of the buffer. + * + * @param[in] echo Should we echo what you type. + * + * @param[in] verify Should we ask for the password twice. + * + * @return 0 on success, -1 on error. + */ +int ssh_getpass(const char *prompt, + char *buf, + size_t len, + int echo, + int verify) +{ + struct termios attr; + struct termios old_attr; + int ok = 0; + int fd = -1; + + /* fgets needs at least len - 1 */ + if (prompt == NULL || buf == NULL || len < 2) { + return -1; + } + + if (isatty(STDIN_FILENO)) { + ZERO_STRUCT(attr); + ZERO_STRUCT(old_attr); + + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + + /* save terminal attributes */ + memcpy(&old_attr, &attr, sizeof(attr)); + if((fd = fcntl(0, F_GETFL, 0)) < 0) { + perror("fcntl"); + return -1; + } + + /* disable echo */ + if (!echo) { + attr.c_lflag &= ~(ECHO); + } + + /* write attributes to terminal */ + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr) < 0) { + perror("tcsetattr"); + return -1; + } + } + + /* disable nonblocking I/O */ + if (fd & O_NONBLOCK) { + ok = fcntl(0, F_SETFL, fd & ~O_NONBLOCK); + if (ok < 0) { + perror("fcntl"); + return -1; + } + } + + ok = ssh_gets(prompt, buf, len, verify); + + if (isatty(STDIN_FILENO)) { + /* reset terminal */ + tcsetattr(STDIN_FILENO, TCSANOW, &old_attr); + } + + /* close fd */ + if (fd & O_NONBLOCK) { + ok = fcntl(0, F_SETFL, fd); + if (ok < 0) { + perror("fcntl"); + return -1; + } + } + + if (!ok) { + ssh_burn(buf, len); + return -1; + } + + /* force termination */ + buf[len - 1] = '\0'; + + return 0; +} + +#endif diff --git a/src/libs/libssh-0.12.2/src/getrandom_crypto.c b/src/libs/libssh-0.12.2/src/getrandom_crypto.c new file mode 100644 index 000000000000..df8bd19f9418 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/getrandom_crypto.c @@ -0,0 +1,64 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include + +/** + * @addtogroup libssh_misc + * + * @{ + */ + +/** + * @brief Get random bytes + * + * Make sure to always check the return code of this function! + * + * @param[in] where The buffer to fill with random bytes + * + * @param[in] len The size of the buffer to fill. + * + * @param[in] strong Use a strong or private RNG source. + * + * @return 1 on success, 0 on error. + */ +int +ssh_get_random(void *where, int len, int strong) +{ +#ifdef HAVE_OPENSSL_RAND_PRIV_BYTES + if (strong) { + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_priv_bytes(where, len); + } +#else + (void)strong; +#endif /* HAVE_RAND_PRIV_BYTES */ + + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_bytes(where, len); +} + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/getrandom_gcrypt.c b/src/libs/libssh-0.12.2/src/getrandom_gcrypt.c new file mode 100644 index 000000000000..da7264051f25 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/getrandom_gcrypt.c @@ -0,0 +1,38 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include + +int +ssh_get_random(void *where, int len, int strong) +{ + /* variable not used in gcrypt */ + (void)strong; + + /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */ + gcry_randomize(where, len, GCRY_STRONG_RANDOM); + + return 1; +} diff --git a/src/libs/libssh-0.12.2/src/getrandom_mbedcrypto.c b/src/libs/libssh-0.12.2/src/getrandom_mbedcrypto.c new file mode 100644 index 000000000000..7e87b6a6c334 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/getrandom_mbedcrypto.c @@ -0,0 +1,52 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "mbedcrypto-compat.h" + +mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; + +int +ssh_mbedtls_random(void *where, int len, int strong) +{ + int rc = 0; + if (strong) { + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_ON); + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_OFF); + } else { + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + } + + return !rc; +} + +int +ssh_get_random(void *where, int len, int strong) +{ + return ssh_mbedtls_random(where, len, strong); +} diff --git a/src/libs/libssh-0.12.2/src/gssapi.c b/src/libs/libssh-0.12.2/src/gssapi.c new file mode 100644 index 000000000000..da4c704c248f --- /dev/null +++ b/src/libs/libssh-0.12.2/src/gssapi.c @@ -0,0 +1,1458 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; + +/** @internal + * @initializes a gssapi context for authentication + */ +int +ssh_gssapi_init(ssh_session session) +{ + if (session->gssapi != NULL) + return SSH_OK; + session->gssapi = calloc(1, sizeof(struct ssh_gssapi_struct)); + if (session->gssapi == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + session->gssapi->server_creds = GSS_C_NO_CREDENTIAL; + session->gssapi->client_creds = GSS_C_NO_CREDENTIAL; + session->gssapi->ctx = GSS_C_NO_CONTEXT; + session->gssapi->state = SSH_GSSAPI_STATE_NONE; + return SSH_OK; +} + +void +ssh_gssapi_log_error(int verb, const char *msg_a, int maj_stat, int min_stat) +{ + gss_buffer_desc msg = GSS_C_EMPTY_BUFFER; + OM_uint32 dummy_min; + OM_uint32 message_context = 0; + + do { + gss_display_status(&dummy_min, + maj_stat, + GSS_C_GSS_CODE, + GSS_C_NO_OID, + &message_context, + &msg); + SSH_LOG(verb, "GSSAPI(%s): %s", msg_a, (const char *)msg.value); + gss_release_buffer(&dummy_min, &msg); + + } while (message_context != 0); + + do { + gss_display_status(&dummy_min, + min_stat, + GSS_C_MECH_CODE, + GSS_C_NO_OID, + &message_context, + &msg); + SSH_LOG(verb, "GSSAPI(%s): %s", msg_a, (const char *)msg.value); + gss_release_buffer(&dummy_min, &msg); + + } while (message_context != 0); +} + +/** @internal + * @frees a gssapi context + */ +void +ssh_gssapi_free(ssh_session session) +{ + OM_uint32 min; + if (session->gssapi == NULL) + return; + SAFE_FREE(session->gssapi->user); + + gss_release_name(&min, &session->gssapi->client.server_name); + gss_release_cred(&min,&session->gssapi->server_creds); + if (session->gssapi->client.creds != + session->gssapi->client.client_deleg_creds) { + gss_release_cred(&min, &session->gssapi->client.creds); + } + gss_release_oid(&min, &session->gssapi->client.oid); + gss_delete_sec_context(&min, &session->gssapi->ctx, GSS_C_NO_BUFFER); + + SAFE_FREE(session->gssapi->canonic_user); + SAFE_FREE(session->gssapi); +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token){ +#ifdef WITH_SERVER + if(session->server) + return ssh_packet_userauth_gssapi_token_server(session, type, packet, user); +#endif + return ssh_packet_userauth_gssapi_token_client(session, type, packet, user); +} +#ifdef WITH_SERVER + +/** @internal + * @brief sends a SSH_MSG_USERAUTH_GSSAPI_RESPONSE packet + * @param[in] oid the OID that was selected for authentication + */ +static int ssh_gssapi_send_response(ssh_session session, ssh_string oid) +{ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) < 0 || + ssh_buffer_add_ssh_string(session->out_buffer,oid) < 0) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + + ssh_packet_send(session); + SSH_LOG(SSH_LOG_PACKET, + "Sent SSH_MSG_USERAUTH_GSSAPI_RESPONSE"); + return SSH_OK; +} + +#endif /* WITH_SERVER */ + +#ifdef WITH_SERVER + +/** @internal + * @brief get all the oids server supports + * @param[out] selected OID set of supported oids + * @returns SSH_OK if successful, SSH_ERROR otherwise + */ +int ssh_gssapi_server_oids(gss_OID_set *selected) +{ + OM_uint32 maj_stat, min_stat; + size_t i; + char *ptr = NULL; + gss_OID_set supported; /* oids supported by server */ + + maj_stat = gss_indicate_mechs(&min_stat, &supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "indicate mechs", + maj_stat, + min_stat); + return SSH_ERROR; + } + + for (i = 0; i < supported->count; ++i) { + ptr = ssh_get_hexa(supported->elements[i].elements, + supported->elements[i].length); + /* According to RFC 4462 we MUST NOT use SPNEGO */ + if (supported->elements[i].length == spnego_oid.length && + memcmp(supported->elements[i].elements, + spnego_oid.elements, + supported->elements[i].length) == 0) { + SAFE_FREE(ptr); + continue; + } + SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); + SAFE_FREE(ptr); + } + + *selected = supported; + + return SSH_OK; +} + +/** @internal + * @brief handles an user authentication using GSSAPI + */ +int +ssh_gssapi_handle_userauth(ssh_session session, const char *user, + uint32_t n_oid, ssh_string *oids) +{ + char *hostname = NULL; + OM_uint32 maj_stat, min_stat; + size_t i; + gss_OID_set supported; /* oids supported by server */ + gss_OID_set both_supported; /* oids supported by both client and server */ + gss_OID_set selected; /* oid selected for authentication */ + int present=0; + size_t oid_count=0; + struct gss_OID_desc_struct oid; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + /* Destroy earlier GSSAPI context if any */ + ssh_gssapi_free(session); + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + return rc; + } + + /* Callback should select oid and acquire credential */ + if (ssh_callbacks_exists(session->server_callbacks, + gssapi_select_oid_function)) { + ssh_string oid_s = NULL; + session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; + SAFE_FREE(session->gssapi->user); + session->gssapi->user = strdup(user); + oid_s = session->server_callbacks->gssapi_select_oid_function( + session, + user, + n_oid, + oids, + session->server_callbacks->userdata); + if (oid_s != NULL) { + rc = ssh_gssapi_send_response(session, oid_s); + return rc; + } else { + return ssh_auth_reply_default(session, 0); + } + } + /* Default implementation for selecting oid and acquiring credential */ + gss_create_empty_oid_set(&min_stat, &both_supported); + + /* Get the server supported oids */ + rc = ssh_gssapi_server_oids(&supported); + if (rc != SSH_OK) { + gss_release_oid_set(&min_stat, &both_supported); + return SSH_ERROR; + } + + /* Loop through client supported oids */ + for (i=0 ; i< n_oid ; ++i){ + unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); + size_t len = ssh_string_len(oids[i]); + + if (oid_s == NULL) { + continue; + } + if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ + SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); + continue; + } + /* Convert oid from string to gssapi format */ + oid.elements = &oid_s[2]; + oid.length = len - 2; + /* Check if this client oid is supported by server */ + gss_test_oid_set_member(&min_stat,&oid,supported,&present); + if(present){ + gss_add_oid_set_member(&min_stat,&oid,&both_supported); + oid_count++; + } + } + gss_release_oid_set(&min_stat, &supported); + if (oid_count == 0){ + SSH_LOG(SSH_LOG_DEBUG,"GSSAPI: no OID match"); + ssh_auth_reply_default(session, 0); + gss_release_oid_set(&min_stat, &both_supported); + return SSH_OK; + } + + hostname = ssh_get_local_hostname(); + if (hostname == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error getting hostname: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + rc = ssh_gssapi_import_name(session->gssapi, hostname); + SAFE_FREE(hostname); + if (rc != SSH_OK) { + ssh_auth_reply_default(session, 0); + gss_release_oid_set(&min_stat, &both_supported); + return SSH_ERROR; + } + + maj_stat = gss_acquire_cred(&min_stat, + session->gssapi->client.server_name, + 0, + both_supported, + GSS_C_ACCEPT, + &session->gssapi->server_creds, + &selected, + NULL); + gss_release_oid_set(&min_stat, &both_supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_TRACE, + "acquiring creds", + maj_stat, + min_stat); + ssh_auth_reply_default(session,0); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, "acquired credentials"); + + /* finding which OID from client we selected */ + for (i=0 ; i< n_oid ; ++i){ + unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); + size_t len = ssh_string_len(oids[i]); + + if (oid_s == NULL) { + continue; + } + if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ + SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); + continue; + } + oid.elements = &oid_s[2]; + oid.length = len - 2; + gss_test_oid_set_member(&min_stat,&oid,selected,&present); + if(present){ + SSH_LOG(SSH_LOG_PACKET, "Selected oid %zu", i); + break; + } + } + gss_release_oid_set(&min_stat, &selected); + if (i == n_oid) { + SSH_LOG(SSH_LOG_TRACE, "GSSAPI: no selected OID matched client OIDs"); + ssh_auth_reply_default(session, 0); + return SSH_ERROR; + } + session->gssapi->user = strdup(user); + session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; + return ssh_gssapi_send_response(session, oids[i]); +} + +char * +ssh_gssapi_name_to_char(gss_name_t name) +{ + gss_buffer_desc buffer; + OM_uint32 maj_stat, min_stat; + char *ptr = NULL; + maj_stat = gss_display_name(&min_stat, name, &buffer, NULL); + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "converting name", + maj_stat, + min_stat); + ptr = malloc(buffer.length + 1); + if (ptr == NULL) { + gss_release_buffer(&min_stat, &buffer); + return NULL; + } + memcpy(ptr, buffer.value, buffer.length); + ptr[buffer.length] = '\0'; + gss_release_buffer(&min_stat, &buffer); + return ptr; + +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server) +{ + ssh_string token = NULL; + char *hexa = NULL; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags=0; + gss_channel_bindings_t input_bindings=GSS_C_NO_CHANNEL_BINDINGS; + int rc; + + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_USERAUTH_GSSAPI_TOKEN"); + if (!session->gssapi || session->gssapi->state != SSH_GSSAPI_STATE_RCV_TOKEN){ + ssh_set_error(session, SSH_FATAL, "Received SSH_MSG_USERAUTH_GSSAPI_TOKEN in invalid state"); + return SSH_PACKET_USED; + } + token = ssh_buffer_get_ssh_string(packet); + + if (token == NULL){ + ssh_set_error(session, SSH_REQUEST_DENIED, "ssh_packet_userauth_gssapi_token: invalid packet"); + return SSH_PACKET_USED; + } + + if (ssh_callbacks_exists(session->server_callbacks, gssapi_accept_sec_ctx_function)){ + ssh_string out_token = NULL; + rc = session->server_callbacks->gssapi_accept_sec_ctx_function(session, + token, &out_token, session->server_callbacks->userdata); + if (rc == SSH_ERROR){ + ssh_auth_reply_default(session, 0); + return SSH_PACKET_USED; + } + if (ssh_string_len(out_token) != 0){ + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + out_token); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_PACKET_USED; + } + ssh_packet_send(session); + SSH_STRING_FREE(out_token); + } + session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; + return SSH_PACKET_USED; + } + hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s", hexa); + SAFE_FREE(hexa); + input_token.length = ssh_string_len(token); + input_token.value = ssh_string_data(token); + + maj_stat = gss_accept_sec_context(&min_stat, &session->gssapi->ctx, session->gssapi->server_creds, + &input_token, input_bindings, &client_name, NULL /*mech_oid*/, &output_token, &ret_flags, + NULL /*time*/, &session->gssapi->client_creds); + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "accepting token", + maj_stat, + min_stat); + SSH_STRING_FREE(token); + if (client_name != GSS_C_NO_NAME){ + session->gssapi->client_name = client_name; + session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); + } + if (GSS_ERROR(maj_stat)){ + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "accepting token failed", + maj_stat, + min_stat); + gss_release_buffer(&min_stat, &output_token); + ssh_auth_reply_default(session,0); + return SSH_PACKET_USED; + } + + if (output_token.length != 0){ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); + SAFE_FREE(hexa); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_auth_reply_default(session, 0); + return SSH_PACKET_USED; + } + ssh_packet_send(session); + } + + gss_release_buffer(&min_stat, &output_token); + gss_release_name(&min_stat, &client_name); + + if (maj_stat == GSS_S_COMPLETE) { + session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; + } + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +ssh_buffer ssh_gssapi_build_mic(ssh_session session, const char *context) +{ + struct ssh_crypto_struct *crypto = NULL; + ssh_buffer mic_buffer = NULL; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return NULL; + } + + mic_buffer = ssh_buffer_new(); + if (mic_buffer == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + rc = ssh_buffer_pack(mic_buffer, + "dPbsss", + crypto->session_id_len, + crypto->session_id_len, + crypto->session_id, + SSH2_MSG_USERAUTH_REQUEST, + session->gssapi->user, + "ssh-connection", + context); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(mic_buffer); + return NULL; + } + + return mic_buffer; +} + +#ifdef WITH_SERVER + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) +{ + ssh_string mic_token = NULL; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; + ssh_buffer mic_buffer = NULL; + + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_USERAUTH_GSSAPI_MIC"); + mic_token = ssh_buffer_get_ssh_string(packet); + if (mic_token == NULL) { + ssh_set_error(session, SSH_FATAL, "Missing MIC in packet"); + goto error; + } + if (session->gssapi == NULL || + session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) { + ssh_set_error(session, + SSH_FATAL, + "Received SSH_MSG_USERAUTH_GSSAPI_MIC in invalid state"); + goto error; + } + + mic_buffer = ssh_gssapi_build_mic(session, "gssapi-with-mic"); + if (mic_buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + if (ssh_callbacks_exists(session->server_callbacks, + gssapi_verify_mic_function)) { + int rc = session->server_callbacks->gssapi_verify_mic_function(session, mic_token, + ssh_buffer_get(mic_buffer), ssh_buffer_get_len(mic_buffer), + session->server_callbacks->userdata); + if (rc != SSH_OK) { + goto error; + } + } else { + mic_buf.length = ssh_buffer_get_len(mic_buffer); + mic_buf.value = ssh_buffer_get(mic_buffer); + mic_token_buf.length = ssh_string_len(mic_token); + mic_token_buf.value = ssh_string_data(mic_token); + + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &mic_buf, + &mic_token_buf, + NULL); + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "verifying MIC", + maj_stat, + min_stat); + if (maj_stat == GSS_S_DEFECTIVE_TOKEN || GSS_ERROR(maj_stat)) { + goto error; + } + } + + if (ssh_callbacks_exists(session->server_callbacks, auth_gssapi_mic_function)){ + switch(session->server_callbacks->auth_gssapi_mic_function(session, + session->gssapi->user, session->gssapi->canonic_user, + session->server_callbacks->userdata)){ + case SSH_AUTH_SUCCESS: + ssh_auth_reply_success(session, 0); + break; + case SSH_AUTH_PARTIAL: + ssh_auth_reply_success(session, 1); + break; + default: + ssh_auth_reply_default(session, 0); + break; + } + } + + goto end; + +error: + ssh_auth_reply_default(session,0); + +end: + if (mic_buffer != NULL) { + SSH_BUFFER_FREE(mic_buffer); + } + if (mic_token != NULL) { + SSH_STRING_FREE(mic_token); + } + + return SSH_PACKET_USED; +} + +/** @brief returns the client credentials of the connected client. + * If the client has given a forwardable token, the SSH server will + * retrieve it. + * @returns gssapi credentials handle. + * @returns NULL if no forwardable token is available. + */ +ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session) +{ + if (!session || !session->gssapi || session->gssapi->client_creds == GSS_C_NO_CREDENTIAL) + return NULL; + return (ssh_gssapi_creds)session->gssapi->client_creds; +} + +#endif /* SERVER */ + +/** + * @brief Set the forwardable ticket to be given to the server for authentication. + * Unlike ssh_gssapi_get_creds() this is called on the client side of an ssh + * connection. + * + * @param[in] session The session + * @param[in] creds gssapi credentials handle. + */ +void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds) +{ + int rc; + + if (session == NULL) { + return; + } + if (session->gssapi == NULL) { + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + return; + } + } + + session->gssapi->client.client_deleg_creds = (gss_cred_id_t)creds; +} + +static int +ssh_gssapi_send_auth_mic(ssh_session session, ssh_string *oid_set, int n_oid) +{ + int rc; + int i; + + rc = ssh_buffer_pack(session->out_buffer, + "bsssd", + SSH2_MSG_USERAUTH_REQUEST, + session->opts.username, + "ssh-connection", + "gssapi-with-mic", + n_oid); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto fail; + } + + for (i=0; iout_buffer, oid_set[i]); + if (rc < 0) { + goto fail; + } + } + + session->auth.state = SSH_AUTH_STATE_GSSAPI_REQUEST_SENT; + return ssh_packet_send(session); +fail: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + +/** @internal + * @brief Get the base64 encoding of md5 of the oid to add as suffix to GSSAPI + * key exchange algorithms. + * + * @param[in] oid The OID as a ssh_string + * + * @returns the hash or NULL on error + */ +char *ssh_gssapi_oid_hash(ssh_string oid) +{ + unsigned char *h = NULL; + int rc; + char *base64 = NULL; + + h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); + if (h == NULL) { + return NULL; + } + + rc = md5(ssh_string_data(oid), ssh_string_len(oid), h); + if (rc != SSH_OK) { + SAFE_FREE(h); + return NULL; + } + + base64 = (char *)bin_to_base64(h, 16); + SAFE_FREE(h); + return base64; +} + +/** @internal + * @brief Check if client has GSSAPI mechanisms configured + * + * @param[in] session The SSH session + * + * @returns SSH_OK if any one of the mechanisms is configured or NULL + */ +int ssh_gssapi_check_client_config(ssh_session session) +{ + OM_uint32 maj_stat, min_stat; + size_t i; + char *ptr = NULL; + gss_OID_set supported = GSS_C_NO_OID_SET; + gss_name_t client_id = GSS_C_NO_NAME; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + struct ssh_gssapi_struct *gssapi = NULL; + int ret = SSH_ERROR; + gss_OID_set one_oidset = GSS_C_NO_OID_SET; + + maj_stat = gss_indicate_mechs(&min_stat, &supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "indicate mechs", + maj_stat, + min_stat); + return SSH_ERROR; + } + + for (i = 0; i < supported->count; ++i) { + gssapi = calloc(1, sizeof(struct ssh_gssapi_struct)); + if (gssapi == NULL) { + ssh_set_error_oom(session); + ret = SSH_ERROR; + break; + } + gssapi->server_creds = GSS_C_NO_CREDENTIAL; + gssapi->client_creds = GSS_C_NO_CREDENTIAL; + gssapi->ctx = GSS_C_NO_CONTEXT; + gssapi->state = SSH_GSSAPI_STATE_NONE; + + /* According to RFC 4462 we MUST NOT use SPNEGO */ + if (supported->elements[i].length == spnego_oid.length && + memcmp(supported->elements[i].elements, + spnego_oid.elements, + supported->elements[i].length) == 0) { + ret = SSH_ERROR; + goto end; + } + + gss_create_empty_oid_set(&min_stat, &one_oidset); + gss_add_oid_set_member(&min_stat, &supported->elements[i], &one_oidset); + + if (session->opts.gss_client_identity != NULL) { + namebuf.value = (void *)session->opts.gss_client_identity; + namebuf.length = strlen(session->opts.gss_client_identity); + + maj_stat = gss_import_name(&min_stat, + &namebuf, + GSS_C_NT_USER_NAME, + &client_id); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + + maj_stat = gss_acquire_cred(&min_stat, + client_id, + GSS_C_INDEFINITE, + one_oidset, + GSS_C_INITIATE, + &gssapi->client.creds, + NULL, + NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "acquiring credential", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + + ret = ssh_gssapi_import_name(gssapi, session->opts.host); + if (ret != SSH_OK) { + goto end; + } + + maj_stat = + ssh_gssapi_init_ctx(gssapi, &input_token, &output_token, &oflags); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "initializing context", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + + ptr = ssh_get_hexa(supported->elements[i].elements, + supported->elements[i].length); + SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); + free(ptr); + + /* If at least one mechanism is configured then return successfully */ + ret = SSH_OK; + + end: + if (ret == SSH_ERROR) { + SSH_LOG(SSH_LOG_WARN, "GSSAPI not configured correctly"); + } + SAFE_FREE(gssapi->user); + + gss_release_oid_set(&min_stat, &one_oidset); + + gss_release_name(&min_stat, &gssapi->client.server_name); + gss_release_cred(&min_stat, &gssapi->server_creds); + gss_release_cred(&min_stat, &gssapi->client.creds); + gss_release_oid(&min_stat, &gssapi->client.oid); + gss_release_buffer(&min_stat, &output_token); + gss_delete_sec_context(&min_stat, &gssapi->ctx, GSS_C_NO_BUFFER); + + if (client_id != GSS_C_NO_NAME) { + gss_release_name(&min_stat, &client_id); + client_id = GSS_C_NO_NAME; + } + + SAFE_FREE(gssapi->canonic_user); + SAFE_FREE(gssapi); + + if (ret == SSH_OK) { + break; + } + } + gss_release_oid_set(&min_stat, &supported); + + return ret; +} + +/** @internal + * @brief acquires a credential and returns a set of mechanisms for which it is + * valid + * + * @param[in] session The SSH session + * @param[out] valid_oids The set of OIDs for which the credential is valid + * + * @returns SSH_OK if successful, SSH_ERROR otherwise + */ +int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids) +{ + OM_uint32 maj_stat, min_stat, lifetime; + gss_OID_set actual_mechs = GSS_C_NO_OID_SET; + gss_buffer_desc namebuf; + gss_name_t client_id = GSS_C_NO_NAME; + gss_OID oid; + unsigned int i; + char *ptr = NULL; + int ret; + + if (session == NULL || session->gssapi == NULL) { + return SSH_ERROR; + } + + if (session->gssapi->client.client_deleg_creds == NULL) { + if (session->opts.gss_client_identity != NULL) { + namebuf.value = (void *)session->opts.gss_client_identity; + namebuf.length = strlen(session->opts.gss_client_identity); + + maj_stat = gss_import_name(&min_stat, &namebuf, + GSS_C_NT_USER_NAME, &client_id); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + + maj_stat = gss_acquire_cred(&min_stat, client_id, GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, GSS_C_INITIATE, + &session->gssapi->client.creds, + &actual_mechs, NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "acquiring credential", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + } else { + session->gssapi->client.creds = + session->gssapi->client.client_deleg_creds; + + maj_stat = gss_inquire_cred(&min_stat, session->gssapi->client.creds, + &client_id, NULL, NULL, &actual_mechs); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + SSH_LOG(SSH_LOG_DEBUG, "acquired credentials"); + + gss_create_empty_oid_set(&min_stat, valid_oids); + + /* double check each single cred */ + for (i = 0; i < actual_mechs->count; i++) { + /* check lifetime is not 0 or skip */ + lifetime = 0; + oid = &actual_mechs->elements[i]; + maj_stat = gss_inquire_cred_by_mech(&min_stat, + session->gssapi->client.creds, + oid, NULL, &lifetime, NULL, NULL); + if (maj_stat == GSS_S_COMPLETE && lifetime > 0) { + gss_add_oid_set_member(&min_stat, oid, valid_oids); + ptr = ssh_get_hexa(oid->elements, oid->length); + SSH_LOG(SSH_LOG_DEBUG, "GSSAPI valid oid %d : %s", i, ptr); + SAFE_FREE(ptr); + } + } + + ret = SSH_OK; + +end: + gss_release_oid_set(&min_stat, &actual_mechs); + gss_release_name(&min_stat, &client_id); + return ret; +} + +/** @internal + * @brief Add suffixes of oid hash to each GSSAPI key exchange algorithm + * @param[in] session current session handler + * @returns string suffixed kex algorithms or NULL on error + */ +char *ssh_gssapi_kex_mechs(ssh_session session) +{ + size_t i, j; + /* oid selected for authentication */ + gss_OID_set selected = GSS_C_NO_OID_SET; + ssh_string *oids = NULL; + int rc; + size_t n_oids = 0; + struct ssh_tokens_st *algs = NULL; + char *oid_hash = NULL; + const char *gss_algs = session->opts.gssapi_key_exchange_algs; + char *new_gss_algs = NULL; + char gss_kex_algs[8000] = {0}; + OM_uint32 min_stat; + size_t offset = 0; + + /* Get supported oids */ + if (session->server) { +#ifdef WITH_SERVER + rc = ssh_gssapi_server_oids(&selected); + if (rc == SSH_ERROR) { + return NULL; + } +#endif + } else { + rc = ssh_gssapi_client_identity(session, &selected); + if (rc == SSH_ERROR) { + return NULL; + } + } + ssh_gssapi_free(session); + + n_oids = selected->count; + SSH_LOG(SSH_LOG_DEBUG, "Sending %zu oids", n_oids); + + oids = calloc(n_oids, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + /* Check if algorithms are valid */ + new_gss_algs = + ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, gss_algs); + if (gss_algs == NULL) { + ssh_set_error( + session, + SSH_FATAL, + "GSSAPI key exchange algorithms not supported or invalid"); + rc = SSH_ERROR; + goto out; + } + + algs = ssh_tokenize(new_gss_algs, ','); + if (algs == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Couldn't tokenize GSSAPI key exchange algs"); + rc = SSH_ERROR; + goto out; + } + for (i = 0; i < n_oids; ++i) { + oids[i] = ssh_string_new(selected->elements[i].length + 2); + if (oids[i] == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + ((unsigned char *)oids[i]->data)[0] = SSH_OID_TAG; + ((unsigned char *)oids[i]->data)[1] = selected->elements[i].length; + memcpy((unsigned char *)oids[i]->data + 2, + selected->elements[i].elements, + selected->elements[i].length); + + /* Get the algorithm suffix */ + oid_hash = ssh_gssapi_oid_hash(oids[i]); + if (oid_hash == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + + /* For each oid loop through the algorithms, append the oid and append + * the algorithms to a string */ + for (j = 0; algs->tokens[j]; j++) { + if (sizeof(gss_kex_algs) < offset) { + ssh_set_error(session, SSH_FATAL, "snprintf failed"); + rc = SSH_ERROR; + goto out; + } + rc = snprintf(&gss_kex_algs[offset], + sizeof(gss_kex_algs) - offset, + "%s%s,", + algs->tokens[j], + oid_hash); + if (rc < 0 || rc >= (ssize_t)sizeof(gss_kex_algs)) { + ssh_set_error(session, SSH_FATAL, "snprintf failed"); + rc = SSH_ERROR; + goto out; + } + /* + 1 for ',' */ + offset += strlen(algs->tokens[j]) + strlen(oid_hash) + 1; + } + SAFE_FREE(oid_hash); + SSH_STRING_FREE(oids[i]); + } + + rc = SSH_OK; + +out: + SAFE_FREE(oid_hash); + SAFE_FREE(oids); + SAFE_FREE(new_gss_algs); + gss_release_oid_set(&min_stat, &selected); + ssh_tokens_free(algs); + + if (rc != SSH_OK) { + return NULL; + } + + return strdup(gss_kex_algs); +} + +int ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host) +{ + gss_buffer_desc hostname; + char name_buf[256] = {0}; + OM_uint32 maj_stat, min_stat; + + /* import target host name */ + snprintf(name_buf, sizeof(name_buf), "host@%s", host); + + hostname.value = name_buf; + hostname.length = strlen(name_buf) + 1; + maj_stat = gss_import_name(&min_stat, + &hostname, + (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, + &gssapi->client.server_name); + SSH_LOG(SSH_LOG_DEBUG, "importing name: %s", name_buf); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "error importing name", + maj_stat, + min_stat); + } + + return maj_stat; +} + +OM_uint32 ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi, + gss_buffer_desc *input_token, + gss_buffer_desc *output_token, + OM_uint32 *ret_flags) +{ + OM_uint32 maj_stat, min_stat; + + maj_stat = gss_init_sec_context(&min_stat, + gssapi->client.creds, + &gssapi->ctx, + gssapi->client.server_name, + gssapi->client.oid, + gssapi->client.flags, + 0, + NULL, + input_token, + NULL, + output_token, + ret_flags, + NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "initializing gssapi context", + maj_stat, + min_stat); + } + return maj_stat; +} + +/** + * @brief launches a gssapi-with-mic auth request + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_gssapi_auth_mic(ssh_session session) +{ + size_t i; + gss_OID_set selected = GSS_C_NO_OID_SET; /* oid selected for authentication */ + ssh_string *oids = NULL; + int rc; + size_t n_oids = 0; + OM_uint32 min_stat; + const char *gss_host = session->opts.host; + + /* Destroy earlier GSSAPI context if any */ + ssh_gssapi_free(session); + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + if (session->opts.gss_server_identity != NULL) { + gss_host = session->opts.gss_server_identity; + } + + rc = ssh_gssapi_import_name(session->gssapi, gss_host); + if (rc != SSH_OK) { + return SSH_AUTH_DENIED; + } + + /* copy username */ + session->gssapi->user = strdup(session->opts.username); + if (session->gssapi->user == NULL) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi to host %s with user %s", + session->opts.host, session->gssapi->user); + rc = ssh_gssapi_client_identity(session, &selected); + if (rc == SSH_ERROR) { + return SSH_AUTH_DENIED; + } + + n_oids = selected->count; + SSH_LOG(SSH_LOG_DEBUG, "Sending %zu oids", n_oids); + + oids = calloc(n_oids, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + for (i=0; ielements[i].length + 2); + if (oids[i] == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + ((unsigned char *)oids[i]->data)[0] = SSH_OID_TAG; + ((unsigned char *)oids[i]->data)[1] = selected->elements[i].length; + memcpy((unsigned char *)oids[i]->data + 2, selected->elements[i].elements, + selected->elements[i].length); + } + + rc = ssh_gssapi_send_auth_mic(session, oids, n_oids); + +out: + for (i = 0; i < n_oids; i++) { + SSH_STRING_FREE(oids[i]); + } + free(oids); + gss_release_oid_set(&min_stat, &selected); + + if (rc != SSH_ERROR) { + return SSH_AUTH_AGAIN; + } + + return SSH_AUTH_ERROR; +} + +/** + * @brief Get the MIC for "gssapi-keyex" authentication. + * @returns SSH_ERROR: A serious error happened\n + * SSH_OK: MIC token is stored in mic_token_buf + */ +int ssh_gssapi_auth_keyex_mic(ssh_session session, + gss_buffer_desc *mic_token_buf) +{ + ssh_buffer buf = NULL; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + OM_uint32 maj_stat, min_stat; + + if (session->gssapi == NULL || session->gssapi->ctx == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + return SSH_ERROR; + } + + buf = ssh_gssapi_build_mic(session, "gssapi-keyex"); + if (buf == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + mic_buf.length = ssh_buffer_get_len(buf); + mic_buf.value = ssh_buffer_get(buf); + + maj_stat = gss_get_mic(&min_stat, + session->gssapi->ctx, + GSS_C_QOP_DEFAULT, + &mic_buf, + mic_token_buf); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "generating MIC", + maj_stat, + min_stat); + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + SSH_BUFFER_FREE(buf); + + return SSH_OK; +} + +static gss_OID ssh_gssapi_oid_from_string(ssh_string oid_s) +{ + gss_OID ret = NULL; + unsigned char *data = ssh_string_data(oid_s); + size_t len = ssh_string_len(oid_s); + + if (data == NULL) { + return NULL; + } + + if (len > 256 || len <= 2) { + SAFE_FREE(ret); + return NULL; + } + + if (data[0] != SSH_OID_TAG || data[1] != len - 2) { + SAFE_FREE(ret); + return NULL; + } + + ret = malloc(sizeof(gss_OID_desc)); + if (ret == NULL) { + return NULL; + } + + ret->elements = malloc(len - 2); + if (ret->elements == NULL) { + SAFE_FREE(ret); + return NULL; + } + memcpy(ret->elements, &data[2], len-2); + ret->length = len-2; + + return ret; +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ + int rc; + ssh_string oid_s = NULL; + gss_uint32 maj_stat, min_stat; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + char *hexa = NULL; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_USERAUTH_GSSAPI_RESPONSE"); + if (session->auth.state != SSH_AUTH_STATE_GSSAPI_REQUEST_SENT){ + ssh_set_error(session, SSH_FATAL, "Invalid state in ssh_packet_userauth_gssapi_response"); + goto error; + } + + oid_s = ssh_buffer_get_ssh_string(packet); + if (!oid_s){ + ssh_set_error(session, SSH_FATAL, "Missing OID"); + goto error; + } + session->gssapi->client.oid = ssh_gssapi_oid_from_string(oid_s); + SSH_STRING_FREE(oid_s); + if (!session->gssapi->client.oid) { + ssh_set_error(session, SSH_FATAL, "Invalid OID"); + goto error; + } + + session->gssapi->client.flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; + if (session->opts.gss_delegate_creds) { + session->gssapi->client.flags |= GSS_C_DELEG_FLAG; + } + + maj_stat = + ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL); + if (GSS_ERROR(maj_stat)) { + goto error; + } + + if (output_token.length != 0){ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s", hexa); + SAFE_FREE(hexa); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + } + + gss_release_buffer(&min_stat, &output_token); + return SSH_PACKET_USED; + +error: + session->auth.state = SSH_AUTH_STATE_ERROR; + return SSH_PACKET_USED; +} + +static int ssh_gssapi_send_mic(ssh_session session) +{ + OM_uint32 maj_stat, min_stat; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; + ssh_buffer mic_buffer; + int rc; + + SSH_LOG(SSH_LOG_PACKET,"Sending SSH_MSG_USERAUTH_GSSAPI_MIC"); + + mic_buffer = ssh_gssapi_build_mic(session, "gssapi-with-mic"); + if (mic_buffer == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + mic_buf.length = ssh_buffer_get_len(mic_buffer); + mic_buf.value = ssh_buffer_get(mic_buffer); + + maj_stat = gss_get_mic(&min_stat,session->gssapi->ctx, GSS_C_QOP_DEFAULT, + &mic_buf, &mic_token_buf); + + SSH_BUFFER_FREE(mic_buffer); + + if (GSS_ERROR(maj_stat)){ + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "generating MIC", + maj_stat, + min_stat); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_MIC, + mic_token_buf.length, + (size_t)mic_token_buf.length, mic_token_buf.value); + + gss_release_buffer(&min_stat, &mic_token_buf); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + return ssh_packet_send(session); +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client) +{ + int rc; + ssh_string token = NULL; + char *hexa = NULL; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER; + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_USERAUTH_GSSAPI_TOKEN"); + if (!session->gssapi || session->auth.state != SSH_AUTH_STATE_GSSAPI_TOKEN) { + ssh_set_error(session, SSH_FATAL, + "Received SSH_MSG_USERAUTH_GSSAPI_TOKEN in invalid state"); + goto error; + } + token = ssh_buffer_get_ssh_string(packet); + + if (token == NULL){ + ssh_set_error(session, SSH_REQUEST_DENIED, + "ssh_packet_userauth_gssapi_token: invalid packet"); + goto error; + } + + hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa); + SAFE_FREE(hexa); + + input_token.length = ssh_string_len(token); + input_token.value = ssh_string_data(token); + maj_stat = + ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL); + SSH_STRING_FREE(token); + if (GSS_ERROR(maj_stat)) { + goto error; + } + + if (output_token.length != 0) { + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); + SAFE_FREE(hexa); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + } + + gss_release_buffer(&min_stat, &output_token); + + if (maj_stat == GSS_S_COMPLETE) { + ssh_gssapi_send_mic(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_MIC_SENT; + } + + return SSH_PACKET_USED; + +error: + session->auth.state = SSH_AUTH_STATE_ERROR; + return SSH_PACKET_USED; +} diff --git a/src/libs/libssh-0.12.2/src/gzip.c b/src/libs/libssh-0.12.2/src/gzip.c new file mode 100644 index 000000000000..e814080ae23e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/gzip.c @@ -0,0 +1,302 @@ +/* + * gzip.c - hooks for compression of packets + * + * This file is part of the SSH Library + * + * Copyright (c) 2003 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#ifdef WITH_ZLIB +#include + +#ifndef BLOCKSIZE +#define BLOCKSIZE 4092 +#endif + +static z_stream * +initcompress(ssh_session session, int level) +{ + z_stream *stream = NULL; + int status; + + stream = calloc(1, sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } + + status = deflateInit(stream, level); + if (status != Z_OK) { + deflateEnd(stream); + SAFE_FREE(stream); + ssh_set_error(session, + SSH_FATAL, + "status %d initialising zlib deflate", + status); + return NULL; + } + + return stream; +} + +static ssh_buffer +gzip_compress(ssh_session session, ssh_buffer source, int level) +{ + struct ssh_crypto_struct *crypto = NULL; + z_stream *zout = NULL; + void *in_ptr = ssh_buffer_get(source); + uint32_t in_size = ssh_buffer_get_len(source); + ssh_buffer dest = NULL; + unsigned char out_buf[BLOCKSIZE] = {0}; + uint32_t len; + int status; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto == NULL) { + return NULL; + } + zout = crypto->compress_out_ctx; + if (zout == NULL) { + zout = crypto->compress_out_ctx = initcompress(session, level); + if (zout == NULL) { + return NULL; + } + } + + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; + } + + zout->next_out = out_buf; + zout->next_in = in_ptr; + zout->avail_in = in_size; + do { + zout->avail_out = BLOCKSIZE; + status = deflate(zout, Z_PARTIAL_FLUSH); + if (status != Z_OK) { + SSH_BUFFER_FREE(dest); + ssh_set_error(session, + SSH_FATAL, + "status %d deflating zlib packet", + status); + return NULL; + } + len = BLOCKSIZE - zout->avail_out; + if (ssh_buffer_add_data(dest, out_buf, len) < 0) { + SSH_BUFFER_FREE(dest); + return NULL; + } + zout->next_out = out_buf; + } while (zout->avail_out == 0); + + return dest; +} + +int +compress_buffer(ssh_session session, ssh_buffer buf) +{ + ssh_buffer dest = NULL; + int rv; + + dest = gzip_compress(session, buf, session->opts.compressionlevel); + if (dest == NULL) { + return -1; + } + + if (ssh_buffer_reinit(buf) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + rv = ssh_buffer_add_data(buf, + ssh_buffer_get(dest), + ssh_buffer_get_len(dest)); + if (rv < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + SSH_BUFFER_FREE(dest); + return 0; +} + +/* decompression */ + +static z_stream * +initdecompress(ssh_session session) +{ + z_stream *stream = NULL; + int status; + + stream = calloc(1, sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } + + status = inflateInit(stream); + if (status != Z_OK) { + inflateEnd(stream); + SAFE_FREE(stream); + ssh_set_error(session, + SSH_FATAL, + "Status = %d initiating inflate context!", + status); + return NULL; + } + + return stream; +} + +static ssh_buffer +gzip_decompress(ssh_session session, ssh_buffer source, size_t maxlen) +{ + struct ssh_crypto_struct *crypto = NULL; + z_stream *zin = NULL; + void *in_ptr = ssh_buffer_get(source); + uint32_t in_size = ssh_buffer_get_len(source); + unsigned char out_buf[BLOCKSIZE] = {0}; + ssh_buffer dest = NULL; + uint32_t len; + int status; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return NULL; + } + + zin = crypto->compress_in_ctx; + if (zin == NULL) { + zin = crypto->compress_in_ctx = initdecompress(session); + if (zin == NULL) { + return NULL; + } + } + + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; + } + + zin->next_out = out_buf; + zin->next_in = in_ptr; + zin->avail_in = in_size; + + do { + zin->avail_out = BLOCKSIZE; + status = inflate(zin, Z_PARTIAL_FLUSH); + if (status != Z_OK && status != Z_BUF_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "status %d inflating zlib packet", + status); + SSH_BUFFER_FREE(dest); + return NULL; + } + + len = BLOCKSIZE - zin->avail_out; + if (ssh_buffer_add_data(dest, out_buf, len) < 0) { + SSH_BUFFER_FREE(dest); + return NULL; + } + if (ssh_buffer_get_len(dest) > maxlen) { + /* Size of packet exceeded, avoid a denial of service attack */ + SSH_BUFFER_FREE(dest); + return NULL; + } + zin->next_out = out_buf; + } while (zin->avail_out == 0); + + return dest; +} + +int +decompress_buffer(ssh_session session, ssh_buffer buf, size_t maxlen) +{ + ssh_buffer dest = NULL; + int rv; + + dest = gzip_decompress(session, buf, maxlen); + if (dest == NULL) { + return -1; + } + + if (ssh_buffer_reinit(buf) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + rv = ssh_buffer_add_data(buf, + ssh_buffer_get(dest), + ssh_buffer_get_len(dest)); + if (rv < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + SSH_BUFFER_FREE(dest); + return 0; +} + +void +compress_cleanup(struct ssh_crypto_struct *crypto) +{ + if (crypto->compress_out_ctx) { + deflateEnd(crypto->compress_out_ctx); + } + SAFE_FREE(crypto->compress_out_ctx); + + if (crypto->compress_in_ctx) { + inflateEnd(crypto->compress_in_ctx); + } + SAFE_FREE(crypto->compress_in_ctx); +} +#else /* WITH_ZLIB */ + +int +compress_buffer(UNUSED_PARAM(ssh_session session), UNUSED_PARAM(ssh_buffer buf)) +{ + /* without zlib compiled in, this should never happen */ + return -1; +} +int +decompress_buffer(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_buffer buf), + UNUSED_PARAM(size_t maxlen)) +{ + /* without zlib compiled in, this should never happen */ + return -1; +} + +void +compress_cleanup(UNUSED_PARAM(struct ssh_crypto_struct *crypto)) +{ + /* no-op */ +} + +#endif /* WITH_ZLIB */ diff --git a/src/libs/libssh-0.12.2/src/hybrid_mlkem.c b/src/libs/libssh-0.12.2/src/hybrid_mlkem.c new file mode 100644 index 000000000000..d13729abe2d3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/hybrid_mlkem.c @@ -0,0 +1,910 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Sahana Prasad + * Author: Pavol Žáčik + * Author: Claude (Anthropic) + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/buffer.h" +#include "libssh/hybrid_mlkem.h" +#include "libssh/pki.h" +#include "libssh/ssh2.h" + +/* sorry, this needs to come last to avoid header dependency issues */ +#include "libssh/bignum.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_hybrid_mlkem_reply); + +static ssh_packet_callback dh_client_callbacks[] = { + ssh_packet_client_hybrid_mlkem_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_hybrid_mlkem_client_callbacks = { + .start = SSH2_MSG_KEX_HYBRID_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL, +}; + +static ssh_string derive_curve25519_secret(ssh_session session) +{ + ssh_string secret = NULL; + int rc; + + secret = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (secret == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + rc = ssh_curve25519_create_k(session, ssh_string_data(secret)); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Curve25519 secret derivation failed"); + ssh_string_free(secret); + return NULL; + } + + return secret; +} + +static ssh_string derive_nist_curve_secret(ssh_session session, + size_t secret_size) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string secret = NULL; + int rc; + + rc = ecdh_build_k(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "ECDH secret derivation failed"); + return NULL; + } + + secret = ssh_make_padded_bignum_string(crypto->shared_secret, secret_size); + if (secret == NULL) { + ssh_set_error(session, SSH_FATAL, "Failed to encode the shared secret"); + } + + bignum_safe_free(crypto->shared_secret); + + return secret; +} + +static ssh_string derive_ecdh_secret(ssh_session session) +{ + ssh_string secret = NULL; + + switch (session->next_crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + secret = derive_curve25519_secret(session); + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: + secret = derive_nist_curve_secret(session, NISTP256_SHARED_SECRET_SIZE); + break; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + secret = derive_nist_curve_secret(session, NISTP384_SHARED_SECRET_SIZE); + break; +#endif + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + return NULL; + } + + return secret; +} + +static int derive_hybrid_secret(ssh_session session, + ssh_mlkem_shared_secret mlkem_shared_secret, + ssh_string ecdh_shared_secret) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_buffer combined_secret = NULL; + int (*digest)(const unsigned char *, size_t, unsigned char *) = NULL; + size_t digest_len; + int rc, ret = SSH_ERROR; + + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + digest = sha256; + digest_len = SHA256_DIGEST_LEN; + break; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + digest = sha384; + digest_len = SHA384_DIGEST_LEN; + break; +#endif + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Concatenate the two shared secrets */ + combined_secret = ssh_buffer_new(); + if (combined_secret == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + ssh_buffer_set_secure(combined_secret); + + rc = ssh_buffer_pack(combined_secret, + "PP", + (size_t)MLKEM_SHARED_SECRET_SIZE, + mlkem_shared_secret, + ssh_string_len(ecdh_shared_secret), + ssh_string_data(ecdh_shared_secret)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to concatenate shared secrets"); + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Concatenated shared secrets", + ssh_buffer_get(combined_secret), + ssh_buffer_get_len(combined_secret)); +#endif + + /* Store the hashed combined shared secrets */ + ssh_string_burn(crypto->hybrid_shared_secret); + ssh_string_free(crypto->hybrid_shared_secret); + crypto->hybrid_shared_secret = ssh_string_new(digest_len); + if (crypto->hybrid_shared_secret == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = digest(ssh_buffer_get(combined_secret), + ssh_buffer_get_len(combined_secret), + ssh_string_data(crypto->hybrid_shared_secret)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Shared secret hashing failed"); + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Hybrid shared secret", + ssh_string_data(crypto->hybrid_shared_secret), + digest_len); +#endif + + ret = SSH_OK; + +cleanup: + ssh_buffer_free(combined_secret); + return ret; +} + +int ssh_client_hybrid_mlkem_init(ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_buffer client_init_buffer = NULL; + int rc, ret = SSH_ERROR; + + SSH_LOG(SSH_LOG_TRACE, "Initializing hybrid ML-KEM key exchange"); + + /* Prepare a buffer to concatenate ML-KEM + ECDH public keys */ + client_init_buffer = ssh_buffer_new(); + if (client_init_buffer == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + /* Generate an ML-KEM keypair */ + rc = ssh_mlkem_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate an ML-KEM keypair"); + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM client pubkey", + ssh_string_data(crypto->mlkem_client_pubkey), + ssh_string_len(crypto->mlkem_client_pubkey)); +#endif + + /* Generate an ECDH keypair and concatenate the public keys */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a Curve25519 ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 client pubkey", + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + rc = ssh_buffer_pack(client_init_buffer, + "PP", + ssh_string_len(crypto->mlkem_client_pubkey), + ssh_string_data(crypto->mlkem_client_pubkey), + (size_t)CURVE25519_PUBKEY_SIZE, + crypto->curve25519_client_pubkey); + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a NIST-curve ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH client pubkey", + ssh_string_data(crypto->ecdh_client_pubkey), + ssh_string_len(crypto->ecdh_client_pubkey)); +#endif + rc = ssh_buffer_pack(client_init_buffer, + "PP", + ssh_string_len(crypto->mlkem_client_pubkey), + ssh_string_data(crypto->mlkem_client_pubkey), + ssh_string_len(crypto->ecdh_client_pubkey), + ssh_string_data(crypto->ecdh_client_pubkey)); + + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to construct client init buffer"); + goto cleanup; + } + + /* Convert the client init buffer to an SSH string */ + ssh_string_free(crypto->hybrid_client_init); + crypto->hybrid_client_init = ssh_string_new(ssh_buffer_get_len(client_init_buffer)); + if (crypto->hybrid_client_init == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = ssh_string_fill(crypto->hybrid_client_init, + ssh_buffer_get(client_init_buffer), + ssh_buffer_get_len(client_init_buffer)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to convert client init to string"); + goto cleanup; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_KEX_HYBRID_INIT, + crypto->hybrid_client_init); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to construct SSH_MSG_KEX_HYBRID_INIT"); + goto cleanup; + } + + ssh_packet_set_callbacks(session, &ssh_hybrid_mlkem_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_HYBRID_INIT"); + goto cleanup; + } + + ret = SSH_OK; + +cleanup: + ssh_buffer_free(client_init_buffer); + return ret; +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_hybrid_mlkem_reply) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + ssh_mlkem_shared_secret mlkem_shared_secret; + ssh_string ecdh_shared_secret = NULL; + ssh_buffer server_reply_buffer = NULL; + size_t read_len; + size_t ecdh_server_pubkey_size; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM hybrid server reply"); + + ssh_client_hybrid_mlkem_remove_callbacks(session); + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + ssh_set_error(session, SSH_FATAL, "Unknown ML-KEM type"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "No public key in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to import public key"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Get server reply containing ML-KEM ciphertext + ECDH public key */ + ssh_string_free(crypto->hybrid_server_reply); + crypto->hybrid_server_reply = ssh_buffer_get_ssh_string(packet); + if (crypto->hybrid_server_reply == NULL) { + ssh_set_error(session, SSH_FATAL, "No server reply in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + server_reply_buffer = ssh_buffer_new(); + if (server_reply_buffer == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_buffer_add_data(server_reply_buffer, + ssh_string_data(crypto->hybrid_server_reply), + ssh_string_len(crypto->hybrid_server_reply)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack server reply to a buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Store ML-KEM ciphertext for decapsulation and sessionid calculation */ + ssh_string_free(crypto->mlkem_ciphertext); + crypto->mlkem_ciphertext = ssh_string_new(mlkem_info->ciphertext_size); + if (crypto->mlkem_ciphertext == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + read_len = ssh_buffer_get_data(server_reply_buffer, + ssh_string_data(crypto->mlkem_ciphertext), + mlkem_info->ciphertext_size); + if (read_len != mlkem_info->ciphertext_size) { + ssh_set_error(session, + SSH_FATAL, + "Could not read ML-KEM ciphertext from " + "the server reply buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM ciphertext", + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->mlkem_ciphertext)); +#endif + + /* Extract server ECDH public key */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + read_len = ssh_buffer_get_data(server_reply_buffer, + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (read_len != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Could not read Curve25519 pubkey from " + "the server reply buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + if (ssh_buffer_get_len(server_reply_buffer) > 0) { + ssh_set_error(session, + SSH_FATAL, + "Unrecognized data in the server reply buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 server pubkey", + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ecdh_server_pubkey_size = ssh_buffer_get_len(server_reply_buffer); + ssh_string_free(crypto->ecdh_server_pubkey); + crypto->ecdh_server_pubkey = ssh_string_new(ecdh_server_pubkey_size); + if (crypto->ecdh_server_pubkey == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + ssh_buffer_get_data(server_reply_buffer, + ssh_string_data(crypto->ecdh_server_pubkey), + ecdh_server_pubkey_size); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH server pubkey", + ssh_string_data(crypto->ecdh_server_pubkey), + ssh_string_len(crypto->ecdh_server_pubkey)); +#endif + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Decapsulate ML-KEM shared secret */ + rc = ssh_mlkem_decapsulate(session, mlkem_shared_secret); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "ML-KEM decapsulation failed"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM shared secret", + mlkem_shared_secret, + MLKEM_SHARED_SECRET_SIZE); +#endif + + /* Derive the classical ECDH shared secret */ + ecdh_shared_secret = derive_ecdh_secret(session); + if (ecdh_shared_secret == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH shared secret", + ssh_string_data(ecdh_shared_secret), + ssh_string_len(ecdh_shared_secret)); +#endif + + /* Derive the final shared secret */ + rc = derive_hybrid_secret(session, mlkem_shared_secret, ecdh_shared_secret); + if (rc != SSH_OK) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Get signature for verification */ + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + crypto->dh_server_signature = signature; + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + +cleanup: + ssh_burn(mlkem_shared_secret, sizeof(mlkem_shared_secret)); + ssh_string_burn(ecdh_shared_secret); + ssh_string_free(ecdh_shared_secret); + ssh_string_free(pubkey_blob); + ssh_buffer_free(server_reply_buffer); + return SSH_PACKET_USED; +} + +void ssh_client_hybrid_mlkem_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_hybrid_mlkem_client_callbacks); +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_hybrid_mlkem_init, +}; + +static struct ssh_packet_callbacks_struct ssh_hybrid_mlkem_server_callbacks = { + .start = SSH2_MSG_KEX_HYBRID_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL, +}; + +static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string ecdh_shared_secret = NULL; + ssh_mlkem_shared_secret mlkem_shared_secret; + ssh_buffer server_reply_buffer = NULL; + ssh_buffer client_init_buffer = NULL; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string signature = NULL; + ssh_string pubkey_blob = NULL; + size_t ecdh_client_pubkey_size; + size_t read_len; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM hybrid client init"); + + ssh_packet_remove_callbacks(session, &ssh_hybrid_mlkem_server_callbacks); + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + ssh_set_error(session, SSH_FATAL, "Unknown ML-KEM type"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Generate an ECDH keypair */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a Curve25519 ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 server pubkey", + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a NIST-curve ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH server pubkey", + ssh_string_data(crypto->ecdh_server_pubkey), + ssh_string_len(crypto->ecdh_server_pubkey)); +#endif + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Get client init: ML-KEM public key + ECDH public key */ + ssh_string_free(crypto->hybrid_client_init); + crypto->hybrid_client_init = ssh_buffer_get_ssh_string(packet); + if (crypto->hybrid_client_init == NULL) { + ssh_set_error(session, SSH_FATAL, "No client public keys in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + client_init_buffer = ssh_buffer_new(); + if (client_init_buffer == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_buffer_add_data(client_init_buffer, + ssh_string_data(crypto->hybrid_client_init), + ssh_string_len(crypto->hybrid_client_init)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack client init to a buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Extract client ML-KEM public key */ + ssh_string_free(crypto->mlkem_client_pubkey); + crypto->mlkem_client_pubkey = ssh_string_new(mlkem_info->pubkey_size); + if (crypto->mlkem_client_pubkey == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + read_len = ssh_buffer_get_data(client_init_buffer, + ssh_string_data(crypto->mlkem_client_pubkey), + mlkem_info->pubkey_size); + if (read_len != mlkem_info->pubkey_size) { + ssh_set_error(session, + SSH_FATAL, + "Could not read ML-KEM pubkey from " + "the client init buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM client pubkey", + ssh_string_data(crypto->mlkem_client_pubkey), + ssh_string_len(crypto->mlkem_client_pubkey)); +#endif + + /* Extract client ECDH public key */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + read_len = ssh_buffer_get_data(client_init_buffer, + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); + if (read_len != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Could not read Curve25519 pubkey from " + "the client init buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + if (ssh_buffer_get_len(client_init_buffer) > 0) { + ssh_set_error(session, + SSH_FATAL, + "Unrecognized data in the client init buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 client pubkey", + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ecdh_client_pubkey_size = ssh_buffer_get_len(client_init_buffer); + ssh_string_free(crypto->ecdh_client_pubkey); + crypto->ecdh_client_pubkey = ssh_string_new(ecdh_client_pubkey_size); + if (crypto->ecdh_client_pubkey == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + ssh_buffer_get_data(client_init_buffer, + ssh_string_data(crypto->ecdh_client_pubkey), + ecdh_client_pubkey_size); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH client pubkey", + ssh_string_data(crypto->ecdh_client_pubkey), + ssh_string_len(crypto->ecdh_client_pubkey)); +#endif + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Encapsulate an ML-KEM shared secret using client's ML-KEM public key */ + rc = ssh_mlkem_encapsulate(session, mlkem_shared_secret); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "ML-KEM encapsulation failed"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM shared secret", + mlkem_shared_secret, + MLKEM_SHARED_SECRET_SIZE); + ssh_log_hexdump("ML-KEM ciphertext", + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->mlkem_ciphertext)); +#endif + + /* Derive the classical ECDH shared secret */ + ecdh_shared_secret = derive_ecdh_secret(session); + if (ecdh_shared_secret == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH shared secret", + ssh_string_data(ecdh_shared_secret), + ssh_string_len(ecdh_shared_secret)); +#endif + + /* Derive the final shared secret */ + rc = derive_hybrid_secret(session, mlkem_shared_secret, ecdh_shared_secret); + if (rc != SSH_OK) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Create server reply: ML-KEM ciphertext + ECDH public key */ + server_reply_buffer = ssh_buffer_new(); + if (server_reply_buffer == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + rc = ssh_buffer_pack(server_reply_buffer, + "PP", + ssh_string_len(crypto->mlkem_ciphertext), + ssh_string_data(crypto->mlkem_ciphertext), + (size_t)CURVE25519_PUBKEY_SIZE, + crypto->curve25519_server_pubkey); + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_buffer_pack(server_reply_buffer, + "PP", + ssh_string_len(crypto->mlkem_ciphertext), + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->ecdh_server_pubkey), + ssh_string_data(crypto->ecdh_server_pubkey)); + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to construct server reply buffer"); + goto cleanup; + } + + /* Convert the reply buffer to an SSH string for sending */ + ssh_string_free(crypto->hybrid_server_reply); + crypto->hybrid_server_reply = ssh_string_new(ssh_buffer_get_len(server_reply_buffer)); + if (crypto->hybrid_server_reply == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_string_fill(crypto->hybrid_server_reply, + ssh_buffer_get(server_reply_buffer), + ssh_buffer_get_len(server_reply_buffer)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to convert reply buffer to string"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add MSG_KEX_ECDH_REPLY header */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_HYBRID_REPLY); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add MSG_KEX_HYBRID_REPLY to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Get server host key */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not get server key params"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Build session ID */ + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add server public key to output */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add server hostkey to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add server reply */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, crypto->hybrid_server_reply); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add server reply to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Sign the exchange hash */ + signature = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add signature */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, signature); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add signature to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_ECDH_REPLY"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + +cleanup: + ssh_burn(mlkem_shared_secret, sizeof(mlkem_shared_secret)); + ssh_string_burn(ecdh_shared_secret); + ssh_string_free(ecdh_shared_secret); + ssh_string_free(pubkey_blob); + ssh_string_free(signature); + ssh_buffer_free(client_init_buffer); + ssh_buffer_free(server_reply_buffer); + return SSH_PACKET_USED; +} + +void ssh_server_hybrid_mlkem_init(ssh_session session) +{ + SSH_LOG(SSH_LOG_TRACE, "Setting up ML-KEM hybrid server callbacks"); + ssh_packet_set_callbacks(session, &ssh_hybrid_mlkem_server_callbacks); +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/src/init.c b/src/libs/libssh-0.12.2/src/init.c new file mode 100644 index 000000000000..e516c3318036 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/init.c @@ -0,0 +1,295 @@ +/* + * init.c - initialization and finalization of the library + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/dh.h" +#include "libssh/poll.h" +#include "libssh/threads.h" + +#ifdef _WIN32 +#include +#endif + +#ifdef HAVE_CONSTRUCTOR_ATTRIBUTE +#define CONSTRUCTOR_ATTRIBUTE __attribute__((constructor)) +#else +#define CONSTRUCTOR_ATTRIBUTE +#endif /* HAVE_CONSTRUCTOR_ATTRIBUTE */ + +#ifdef HAVE_DESTRUCTOR_ATTRIBUTE +#define DESTRUCTOR_ATTRIBUTE __attribute__((destructor)) +#else +#define DESTRUCTOR_ATTRIBUTE +#endif /* HAVE_DESTRUCTOR_ATTRIBUTE */ + +/* Declare static mutex */ +static SSH_MUTEX ssh_init_mutex = SSH_MUTEX_STATIC_INIT; + +/* Counter for initializations */ +static int _ssh_initialized = 0; + +/* Cache the returned value */ +static int _ssh_init_ret = 0; + +void libssh_constructor(void) CONSTRUCTOR_ATTRIBUTE; +void libssh_destructor(void) DESTRUCTOR_ATTRIBUTE; + +static int _ssh_init(unsigned constructor) { + + int rc = 0; + + if (!constructor) { + ssh_mutex_lock(&ssh_init_mutex); + } + + _ssh_initialized++; + + if (_ssh_initialized > 1) { + rc = _ssh_init_ret; + goto _ret; + } + + rc = ssh_threads_init(); + if (rc) { + goto _ret; + } + + rc = ssh_crypto_init(); + if (rc) { + goto _ret; + } + + rc = ssh_dh_init(); + if (rc) { + goto _ret; + } + + rc = ssh_socket_init(); + if (rc) { + goto _ret; + } + +_ret: + _ssh_init_ret = rc; + + if (!constructor) { + ssh_mutex_unlock(&ssh_init_mutex); + } + + return rc; +} + +/** + * @brief Initialize global cryptographic data structures. + * + * This function is automatically called when the library is loaded. + * + */ +void libssh_constructor(void) +{ + + int rc; + + rc = _ssh_init(1); + + if (rc < 0) { + fprintf(stderr, "Error in auto_init()\n"); + } + + return; +} + +/** + * @defgroup libssh The libssh API + * + * The libssh library is implementing the SSH protocols and some of its + * extensions. This group of functions is mostly used to implement an SSH + * client. + * Some functions are needed to implement an SSH server too. + * + * @{ + */ + +/** + * @brief Initialize global cryptographic data structures. + * + * Since version 0.8.0, when libssh is dynamically linked, it is not necessary + * to call this function on systems that fully support threading (that is, + * systems with pthreads available). + * + * If libssh is statically linked, it is necessary to explicitly call ssh_init() + * before calling any other provided API, and it is necessary to explicitly call + * ssh_finalize() to free the allocated resources before exiting. + * + * If the library is already initialized, increments the _ssh_initialized + * counter and return the error code cached in _ssh_init_ret. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + * + * @see ssh_finalize() + */ +int ssh_init(void) { + return _ssh_init(0); +} + +static int _ssh_finalize(unsigned destructor) { + + if (!destructor) { + ssh_mutex_lock(&ssh_init_mutex); + + if (_ssh_initialized > 1) { + _ssh_initialized--; + ssh_mutex_unlock(&ssh_init_mutex); + return 0; + } + + if (_ssh_initialized == 1) { + if (_ssh_init_ret < 0) { + ssh_mutex_unlock(&ssh_init_mutex); + return 0; + } + } + } + + /* If the counter reaches zero or it is the destructor calling, finalize */ + ssh_dh_finalize(); + ssh_crypto_finalize(); + ssh_socket_cleanup(); + /* It is important to finalize threading after CRYPTO because + * it still depends on it */ + ssh_threads_finalize(); + + _ssh_initialized = 0; + + if (!destructor) { + ssh_mutex_unlock(&ssh_init_mutex); + } + +#if (defined(_WIN32) && !defined(HAVE_PTHREAD)) + if (ssh_init_mutex != NULL) { + DeleteCriticalSection(ssh_init_mutex); + SAFE_FREE(ssh_init_mutex); + } +#endif + + return 0; +} + +/** + * @brief Finalize and clean up all libssh and cryptographic data structures. + * + * This function is automatically called when the library is unloaded. + * + */ +void libssh_destructor(void) +{ + int rc; + + rc = _ssh_finalize(1); + + if (rc < 0) { + fprintf(stderr, "Error in libssh_destructor()\n"); + } +} + +/** + * @brief Finalize and clean up all libssh and cryptographic data structures. + * + * Since version 0.8.0, when libssh is dynamically linked, it is not necessary + * to call this function, since it is automatically called when the library is + * unloaded. + * + * If libssh is statically linked, it is necessary to explicitly call ssh_init() + * before calling any other provided API, and it is necessary to explicitly call + * ssh_finalize() to free the allocated resources before exiting. + * + * If ssh_init() is called explicitly, then ssh_finalize() must be called + * explicitly. + * + * When called, decrements the counter _ssh_initialized. If the counter reaches + * zero, then the libssh and cryptographic data structures are cleaned up. + * + * @returns 0 on success, -1 if an error occurred. + * + * @see ssh_init() + */ +int ssh_finalize(void) { + return _ssh_finalize(0); +} + +#ifdef _WIN32 + +#if defined(_MSC_VER) && !defined(LIBSSH_STATIC) +/* Library constructor and destructor */ +BOOL WINAPI DllMain(HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + int rc = 0; + + switch(fdwReason) { + case DLL_PROCESS_ATTACH: + rc = _ssh_init(1); + if (rc != 0) { + fprintf(stderr, "DllMain: ssh_init failed!"); + return FALSE; + } + break; + case DLL_PROCESS_DETACH: + _ssh_finalize(1); + break; + default: + break; + } + + return TRUE; +} +#endif /* _MSC_VER && !LIBSSH_STATIC */ + +#endif /* _WIN32 */ + +/** + * @internal + * @brief Return whether the library is initialized + * + * @returns true if the library is initialized; false otherwise. + * + * @see ssh_init() + */ +bool is_ssh_initialized(void) { + + bool is_initialized = false; + + ssh_mutex_lock(&ssh_init_mutex); + is_initialized = _ssh_initialized > 0; + ssh_mutex_unlock(&ssh_init_mutex); + + return is_initialized; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/kdf.c b/src/libs/libssh-0.12.2/src/kdf.c new file mode 100644 index 000000000000..6bc477ce76ff --- /dev/null +++ b/src/libs/libssh-0.12.2/src/kdf.c @@ -0,0 +1,238 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyrihgt (c) 2018 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#include "libssh/string.h" + + +/* The following implements the SSHKDF for crypto backend that + * do not have a native implementation */ +struct ssh_mac_ctx_struct { + enum ssh_kdf_digest digest_type; + union { + SHACTX sha1_ctx; + SHA256CTX sha256_ctx; + SHA384CTX sha384_ctx; + SHA512CTX sha512_ctx; + } ctx; +}; + +static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) +{ + ssh_mac_ctx ctx = malloc(sizeof(struct ssh_mac_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + + ctx->digest_type = type; + switch (type) { + case SSH_KDF_SHA1: + ctx->ctx.sha1_ctx = sha1_init(); + if (ctx->ctx.sha1_ctx == NULL) { + goto err; + } + return ctx; + case SSH_KDF_SHA256: + ctx->ctx.sha256_ctx = sha256_init(); + if (ctx->ctx.sha256_ctx == NULL) { + goto err; + } + return ctx; + case SSH_KDF_SHA384: + ctx->ctx.sha384_ctx = sha384_init(); + if (ctx->ctx.sha384_ctx == NULL) { + goto err; + } + return ctx; + case SSH_KDF_SHA512: + ctx->ctx.sha512_ctx = sha512_init(); + if (ctx->ctx.sha512_ctx == NULL) { + goto err; + } + return ctx; + } +err: + SAFE_FREE(ctx); + return NULL; +} + +static void ssh_mac_ctx_free(ssh_mac_ctx ctx) +{ + if (ctx == NULL) { + return; + } + + switch (ctx->digest_type) { + case SSH_KDF_SHA1: + sha1_ctx_free(ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: + sha256_ctx_free(ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: + sha384_ctx_free(ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: + sha512_ctx_free(ctx->ctx.sha512_ctx); + break; + } + SAFE_FREE(ctx); +} + +static int ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) +{ + switch (ctx->digest_type) { + case SSH_KDF_SHA1: + return sha1_update(ctx->ctx.sha1_ctx, data, len); + case SSH_KDF_SHA256: + return sha256_update(ctx->ctx.sha256_ctx, data, len); + case SSH_KDF_SHA384: + return sha384_update(ctx->ctx.sha384_ctx, data, len); + case SSH_KDF_SHA512: + return sha512_update(ctx->ctx.sha512_ctx, data, len); + } + return SSH_ERROR; +} + +static int ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) +{ + int rc = SSH_ERROR; + + switch (ctx->digest_type) { + case SSH_KDF_SHA1: + rc = sha1_final(md, ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: + rc = sha256_final(md, ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: + rc = sha384_final(md, ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: + rc = sha512_final(md, ctx->ctx.sha512_ctx); + break; + } + SAFE_FREE(ctx); + return rc; +} + +int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + unsigned char *key, + size_t key_len, + uint8_t key_type, + unsigned char *output, + size_t requested_len) +{ + /* Can't use VLAs with Visual Studio, so allocate the biggest + * digest buffer we can possibly need */ + unsigned char digest[DIGEST_MAX_LEN]; + size_t output_len = crypto->digest_len; + ssh_mac_ctx ctx; + int rc; + + if (DIGEST_MAX_LEN < crypto->digest_len) { + return -1; + } + + ctx = ssh_mac_ctx_init(crypto->digest_type); + if (ctx == NULL) { + return -1; + } + + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, &key_type, 1); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } + + if (requested_len < output_len) { + output_len = requested_len; + } + memcpy(output, digest, output_len); + + while (requested_len > output_len) { + ctx = ssh_mac_ctx_init(crypto->digest_type); + if (ctx == NULL) { + return -1; + } + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, output, output_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } + if (requested_len < output_len + crypto->digest_len) { + memcpy(output + output_len, digest, requested_len - output_len); + } else { + memcpy(output + output_len, digest, crypto->digest_len); + } + output_len += crypto->digest_len; + } + + return 0; +} diff --git a/src/libs/libssh-0.12.2/src/kex-gss.c b/src/libs/libssh-0.12.2/src/kex-gss.c new file mode 100644 index 000000000000..0cf8b5915186 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/kex-gss.c @@ -0,0 +1,687 @@ +/* + * kex-gss.c - GSSAPI key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Gauravsingh Sisodia + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/gssapi.h" +#include +#include +#include + +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/kex-gss.h" +#include "libssh/bignum.h" +#include "libssh/curve25519.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply); + +static ssh_packet_callback gss_kex_client_callbacks[] = { + ssh_packet_client_gss_kex_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callbacks = { + .start = SSH2_MSG_KEXGSS_COMPLETE, + .n_callbacks = 1, + .callbacks = gss_kex_client_callbacks, + .user = NULL, +}; + +static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey); + +static ssh_packet_callback gss_kex_client_callback_hostkey[] = { + ssh_packet_client_gss_kex_hostkey, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callback_hostkey = { + .start = SSH2_MSG_KEXGSS_HOSTKEY, + .n_callbacks = 1, + .callbacks = gss_kex_client_callback_hostkey, + .user = NULL, +}; + +static ssh_string dh_init(ssh_session session) +{ + int rc, keypair; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum const_pubkey; +#endif + bignum pubkey = NULL; + ssh_string pubkey_string = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + + if (session->server) { + keypair = DH_SERVER_KEYPAIR; + } else { + keypair = DH_CLIENT_KEYPAIR; + } + + rc = ssh_dh_init_common(crypto); + if (rc != SSH_OK) { + goto end; + } + + rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, keypair); + if (rc != SSH_OK) { + goto end; + } + +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &const_pubkey); + bignum_dup(const_pubkey, &pubkey); +#else + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &pubkey); +#endif + if (rc != SSH_OK) { + goto end; + } + + pubkey_string = ssh_make_bignum_string(pubkey); + +end: + bignum_safe_free(pubkey); + return pubkey_string; +} + +static int dh_import_peer_key(ssh_session session, ssh_string peer_key) +{ + int rc, keypair; + bignum peer_key_bn; + struct ssh_crypto_struct *crypto = session->next_crypto; + + if (session->server) { + keypair = DH_CLIENT_KEYPAIR; + } else { + keypair = DH_SERVER_KEYPAIR; + } + + peer_key_bn = ssh_make_string_bn(peer_key); + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, keypair, NULL, peer_key_bn); + if (rc != SSH_OK) { + bignum_safe_free(peer_key_bn); + } + + return rc; +} + +/** @internal + * @brief Starts gssapi key exchange + */ +int ssh_client_gss_kex_init(ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + int rc, ret = SSH_ERROR; + /* oid selected for authentication */ + gss_OID_set selected = GSS_C_NO_OID_SET; + OM_uint32 maj_stat, min_stat; + const char *gss_host = session->opts.host; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + ssh_string pubkey = NULL; + + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + pubkey = dh_init(session); + if (pubkey == NULL) { + ssh_set_error(session, SSH_FATAL, "Failed to generate DH keypair"); + goto out; + } + break; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate ECDH keypair"); + goto out; + } + pubkey = ssh_string_copy(crypto->ecdh_client_pubkey); + break; + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate Curve25519 keypair"); + goto out; + } + pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (pubkey == NULL) { + ssh_set_error_oom(session); + goto out; + } + rc = ssh_string_fill(pubkey, + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey"); + goto out; + } + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method"); + goto out; + } + + rc = ssh_gssapi_init(session); + if (rc != SSH_OK) { + goto out; + } + + if (session->opts.gss_server_identity != NULL) { + gss_host = session->opts.gss_server_identity; + } + + rc = ssh_gssapi_import_name(session->gssapi, gss_host); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_gssapi_client_identity(session, &selected); + if (rc != SSH_OK) { + goto out; + } + + session->gssapi->client.flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; + maj_stat = ssh_gssapi_init_ctx(session->gssapi, + &input_token, + &output_token, + &oflags); + gss_release_oid_set(&min_stat, &selected); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "Initializing gssapi context", + maj_stat, + min_stat); + goto out; + } + if (!(oflags & GSS_C_INTEG_FLAG) || !(oflags & GSS_C_MUTUAL_FLAG)) { + SSH_LOG(SSH_LOG_WARN, + "GSSAPI(init) integrity and mutual flags were not set"); + goto out; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdPS", + SSH2_MSG_KEXGSS_INIT, + output_token.length, + (size_t)output_token.length, + output_token.value, + pubkey); + if (rc != SSH_OK) { + goto out; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callbacks); + ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callback_hostkey); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + goto out; + } + + ret = SSH_OK; + +out: + gss_release_buffer(&min_stat, &output_token); + ssh_string_free(pubkey); + return ret; +} + +void ssh_client_gss_kex_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callbacks); +} + +void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callback_hostkey); +} + +SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string mic = NULL, otoken = NULL, server_pubkey = NULL; + uint8_t b; + int rc; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + OM_uint32 maj_stat; + + (void)type; + (void)user; + + ssh_client_gss_kex_remove_callbacks(session); + + rc = ssh_buffer_unpack(packet, "SSbS", &server_pubkey, &mic, &b, &otoken); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "No public key in server reply"); + goto error; + } + + SSH_STRING_FREE(session->gssapi_key_exchange_mic); + session->gssapi_key_exchange_mic = mic; + input_token.length = ssh_string_len(otoken); + input_token.value = ssh_string_data(otoken); + maj_stat = ssh_gssapi_init_ctx(session->gssapi, + &input_token, + &output_token, + &oflags); + if (maj_stat != GSS_S_COMPLETE) { + goto error; + } + SSH_STRING_FREE(otoken); + + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + rc = dh_import_peer_key(session, server_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not import server pubkey"); + goto error; + } + rc = ssh_dh_compute_shared_secret(crypto->dh_ctx, + DH_CLIENT_KEYPAIR, + DH_SERVER_KEYPAIR, + &crypto->shared_secret); + ssh_dh_debug_crypto(crypto); + break; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + crypto->ecdh_server_pubkey = ssh_string_copy(server_pubkey); + rc = ecdh_build_k(session); + break; + case SSH_GSS_KEX_CURVE25519_SHA256: + if (ssh_string_len(server_pubkey) != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect length of received server Curve25519 pubkey"); + goto error; + } + memcpy(crypto->curve25519_server_pubkey, + ssh_string_data(server_pubkey), + CURVE25519_PUBKEY_SIZE); + rc = ssh_curve25519_build_k(session); + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method"); + goto error; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not derive shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + ssh_string_free(server_pubkey); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + return SSH_PACKET_USED; + +error: + ssh_string_free(server_pubkey); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey) +{ + ssh_string pubkey_blob = NULL; + int rc; + + (void)type; + (void)user; + + ssh_client_gss_kex_remove_callback_hostkey(session); + + rc = ssh_buffer_unpack(packet, "S", &pubkey_blob); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Invalid SSH2_MSG_KEXGSS_HOSTKEY packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + return SSH_PACKET_USED; +error: + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init); + +static ssh_packet_callback gss_kex_server_callbacks[] = { + ssh_packet_server_gss_kex_init, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_kex_server_callbacks = { + .start = SSH2_MSG_KEXGSS_INIT, + .n_callbacks = 1, + .callbacks = gss_kex_server_callbacks, + .user = NULL, +}; + +/** @internal + * @brief sets up the gssapi kex callbacks + */ +void ssh_server_gss_kex_init(ssh_session session) +{ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_gss_kex_server_callbacks); +} + +/** @internal + * @brief processes a SSH_MSG_KEXGSS_INIT and sends + * the appropriate SSH_MSG_KEXGSS_COMPLETE + */ +int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string client_pubkey = NULL; + ssh_string server_pubkey = NULL; + int rc; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + ssh_string otoken = NULL; + ssh_string server_pubkey_blob = NULL; + OM_uint32 maj_stat, min_stat; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags = 0; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER; + char *hostname = NULL; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + rc = ssh_buffer_unpack(packet, "S", &otoken); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No token in client request"); + goto error; + } + input_token.length = ssh_string_len(otoken); + input_token.value = ssh_string_data(otoken); + + rc = ssh_buffer_unpack(packet, "S", &client_pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No public key in client request"); + goto error; + } + + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + server_pubkey = dh_init(session); + if (server_pubkey == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not generate a DH keypair"); + goto error; + } + rc = dh_import_peer_key(session, client_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not import client pubkey"); + goto error; + } + rc = ssh_dh_compute_shared_secret(crypto->dh_ctx, + DH_SERVER_KEYPAIR, + DH_CLIENT_KEYPAIR, + &crypto->shared_secret); + ssh_dh_debug_crypto(crypto); + break; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not generate an ECDH keypair"); + goto error; + } + crypto->ecdh_client_pubkey = ssh_string_copy(client_pubkey); + server_pubkey = ssh_string_copy(crypto->ecdh_server_pubkey); + rc = ecdh_build_k(session); + break; + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not generate a Curve25519 keypair"); + goto error; + } + server_pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (server_pubkey == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_string_fill(server_pubkey, + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey"); + goto error; + } + if (ssh_string_len(client_pubkey) != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect length of received client Curve25519 pubkey"); + goto error; + } + memcpy(crypto->curve25519_client_pubkey, + ssh_string_data(client_pubkey), + CURVE25519_PUBKEY_SIZE); + rc = ssh_curve25519_build_k(session); + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method"); + goto error; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not derive shared secret"); + goto error; + } + + /* Also imports next_crypto->server_pubkey + * Can give error when using null hostkey */ + ssh_get_key_params(session, &privkey, &digest); + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + if (strcmp(crypto->kex_methods[SSH_HOSTKEYS], "null") != 0) { + rc = + ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_KEXGSS_HOSTKEY, + server_pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Sent SSH2_MSG_KEXGSS_HOSTKEY"); + SSH_STRING_FREE(server_pubkey_blob); + } + + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + goto error; + } + + hostname = ssh_get_local_hostname(); + if (hostname == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error getting hostname: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + goto error; + } + + rc = ssh_gssapi_import_name(session->gssapi, hostname); + SAFE_FREE(hostname); + if (rc != SSH_OK) { + goto error; + } + + maj_stat = gss_acquire_cred(&min_stat, + session->gssapi->client.server_name, + 0, + GSS_C_NO_OID_SET, + GSS_C_ACCEPT, + &session->gssapi->server_creds, + NULL, + NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_TRACE, + "acquiring credentials", + maj_stat, + min_stat); + goto error; + } + + maj_stat = gss_accept_sec_context(&min_stat, + &session->gssapi->ctx, + session->gssapi->server_creds, + &input_token, + GSS_C_NO_CHANNEL_BINDINGS, + &client_name, + NULL /*mech_oid*/, + &output_token, + &ret_flags, + NULL /*time*/, + &session->gssapi->client_creds); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "accepting token failed", + maj_stat, + min_stat); + goto error; + } + SSH_STRING_FREE(otoken); + if (client_name != GSS_C_NO_NAME) { + session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); + } + gss_release_name(&min_stat, &client_name); + if (!(ret_flags & GSS_C_INTEG_FLAG) || !(ret_flags & GSS_C_MUTUAL_FLAG)) { + SSH_LOG(SSH_LOG_WARN, + "GSSAPI(accept) integrity and mutual flags were not set"); + gss_release_buffer(&min_stat, &output_token); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "token accepted"); + + msg.length = session->next_crypto->digest_len; + msg.value = session->next_crypto->secret_hash; + maj_stat = gss_get_mic(&min_stat, + session->gssapi->ctx, + GSS_C_QOP_DEFAULT, + &msg, + &mic); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "creating mic failed", + maj_stat, + min_stat); + gss_release_buffer(&min_stat, &output_token); + goto error; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bSdPbdP", + SSH2_MSG_KEXGSS_COMPLETE, + server_pubkey, + mic.length, + (size_t)mic.length, + mic.value, + 1, + output_token.length, + (size_t)output_token.length, + output_token.value); + gss_release_buffer(&min_stat, &output_token); + gss_release_buffer(&min_stat, &mic); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Sent SSH2_MSG_KEXGSS_COMPLETE"); + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + ssh_string_free(server_pubkey); + ssh_string_free(client_pubkey); + return SSH_OK; +error: + SSH_STRING_FREE(server_pubkey_blob); + ssh_string_free(server_pubkey); + ssh_string_free(client_pubkey); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_ERROR; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEXGSS_INIT packet and complete + * Diffie-Hellman key exchange + **/ +static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init) +{ + (void)type; + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEXGSS_INIT"); + ssh_packet_remove_callbacks(session, &ssh_gss_kex_server_callbacks); + ssh_server_gss_kex_process_init(session, packet); + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/src/kex.c b/src/libs/libssh-0.12.2/src/kex.c new file mode 100644 index 000000000000..4b0c8065ca43 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/kex.c @@ -0,0 +1,2073 @@ +/* + * kex.c - key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/dh.h" +#ifdef WITH_GEX +#include "libssh/dh-gex.h" +#endif /* WITH_GEX */ +#include "libssh/kex.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" +#include "libssh/string.h" +#include "libssh/curve25519.h" +#include "libssh/sntrup761.h" +#include "libssh/hybrid_mlkem.h" +#include "libssh/kex-gss.h" +#include "libssh/knownhosts.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/token.h" +#include "libssh/gssapi.h" + +#ifdef HAVE_BLOWFISH +# define BLOWFISH ",blowfish-cbc" +#else +# define BLOWFISH "" +#endif + +#ifdef HAVE_LIBGCRYPT +# define AES "aes256-gcm@openssh.com,aes128-gcm@openssh.com," \ + "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" + +#elif defined(HAVE_LIBMBEDCRYPTO) +# ifdef MBEDTLS_GCM_C +# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," +# else +# define GCM "" +# endif /* MBEDTLS_GCM_C */ +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" + +#elif defined(HAVE_LIBCRYPTO) +# ifdef HAVE_OPENSSL_AES_H +# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# else /* HAVE_OPENSSL_AES_H */ +# define AES "" +# define AES_CBC "" +# endif /* HAVE_OPENSSL_AES_H */ + +# define DES_SUPPORTED ",3des-cbc" +#endif /* HAVE_LIBCRYPTO */ + +#ifdef WITH_ZLIB +#define ZLIB "none,zlib@openssh.com,zlib" +#define ZLIB_DEFAULT "none,zlib@openssh.com" +#else +#define ZLIB "none" +#define ZLIB_DEFAULT "none" +#endif /* WITH_ZLIB */ + +#ifdef HAVE_CURVE25519 +#define CURVE25519 "curve25519-sha256,curve25519-sha256@libssh.org," +#else +#define CURVE25519 "" +#endif /* HAVE_CURVE25519 */ + +#ifdef HAVE_SNTRUP761 +#define SNTRUP761X25519 "sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com," +#else +#define SNTRUP761X25519 "" +#endif /* HAVE_SNTRUP761 */ + +#ifdef HAVE_MLKEM1024 +#define HYBRID_MLKEM "mlkem768x25519-sha256," \ + "mlkem768nistp256-sha256," \ + "mlkem1024nistp384-sha384," +#else +#define HYBRID_MLKEM "mlkem768x25519-sha256," \ + "mlkem768nistp256-sha256," +#endif /* HAVE_MLKEM1024 */ + +#ifdef HAVE_ECC +#define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521," +#define EC_HOSTKEYS "ecdsa-sha2-nistp521," \ + "ecdsa-sha2-nistp384," \ + "ecdsa-sha2-nistp256," +#define EC_SK_HOSTKEYS "sk-ecdsa-sha2-nistp256@openssh.com," +#define EC_FIPS_PUBLIC_KEY_ALGOS "ecdsa-sha2-nistp521-cert-v01@openssh.com," \ + "ecdsa-sha2-nistp384-cert-v01@openssh.com," \ + "ecdsa-sha2-nistp256-cert-v01@openssh.com," +#define EC_PUBLIC_KEY_ALGORITHMS EC_FIPS_PUBLIC_KEY_ALGOS \ + "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com," +#else +#define ECDH "" +#define EC_HOSTKEYS "" +#define EC_SK_HOSTKEYS "" +#define EC_FIPS_PUBLIC_KEY_ALGOS "" +#define EC_PUBLIC_KEY_ALGORITHMS "" +#endif /* HAVE_ECC */ + +#ifdef WITH_INSECURE_NONE +#define NONE ",none" +#else +#define NONE +#endif /* WITH_INSECURE_NONE */ + +#define HOSTKEYS "ssh-ed25519," \ + EC_HOSTKEYS \ + "sk-ssh-ed25519@openssh.com," \ + EC_SK_HOSTKEYS \ + "rsa-sha2-512," \ + "rsa-sha2-256," \ + "ssh-rsa" +#define DEFAULT_HOSTKEYS "ssh-ed25519," \ + EC_HOSTKEYS \ + "sk-ssh-ed25519@openssh.com," \ + EC_SK_HOSTKEYS \ + "rsa-sha2-512," \ + "rsa-sha2-256" + +#define PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ + "sk-ssh-ed25519-cert-v01@openssh.com," \ + EC_PUBLIC_KEY_ALGORITHMS \ + "rsa-sha2-512-cert-v01@openssh.com," \ + "rsa-sha2-256-cert-v01@openssh.com," \ + "ssh-rsa-cert-v01@openssh.com," \ + HOSTKEYS +#define DEFAULT_PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ + EC_PUBLIC_KEY_ALGORITHMS \ + "rsa-sha2-512-cert-v01@openssh.com," \ + "rsa-sha2-256-cert-v01@openssh.com," \ + DEFAULT_HOSTKEYS + +#ifdef WITH_GEX +#define GEX_SHA256 "diffie-hellman-group-exchange-sha256," +#define GEX_SHA1 "diffie-hellman-group-exchange-sha1," +#else +#define GEX_SHA256 +#define GEX_SHA1 +#endif /* WITH_GEX */ + +#define CHACHA20 "chacha20-poly1305@openssh.com," + +#define DEFAULT_KEY_EXCHANGE \ + HYBRID_MLKEM \ + SNTRUP761X25519 \ + CURVE25519 \ + ECDH \ + "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \ + GEX_SHA256 \ + "diffie-hellman-group14-sha256" \ + +#define KEY_EXCHANGE_SUPPORTED \ + GEX_SHA1 \ + DEFAULT_KEY_EXCHANGE \ + ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + +/* RFC 8308 */ +#define KEX_EXTENSION_CLIENT "ext-info-c" +/* Strict kex mitigation against CVE-2023-48795 */ +#define KEX_STRICT_CLIENT "kex-strict-c-v00@openssh.com" +#define KEX_STRICT_SERVER "kex-strict-s-v00@openssh.com" + +/* Allowed algorithms in FIPS mode */ +#define FIPS_ALLOWED_CIPHERS "aes256-gcm@openssh.com,"\ + "aes256-ctr,"\ + "aes256-cbc,"\ + "aes128-gcm@openssh.com,"\ + "aes128-ctr,"\ + "aes128-cbc" + +#define FIPS_ALLOWED_HOSTKEYS EC_HOSTKEYS \ + "rsa-sha2-512," \ + "rsa-sha2-256" + +#define FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS EC_FIPS_PUBLIC_KEY_ALGOS \ + "rsa-sha2-512-cert-v01@openssh.com," \ + "rsa-sha2-256-cert-v01@openssh.com," \ + FIPS_ALLOWED_HOSTKEYS + +#ifdef HAVE_MLKEM1024 +#define FIPS_MLKEM_KEX "mlkem768nistp256-sha256," \ + "mlkem1024nistp384-sha384," +#else +#define FIPS_MLKEM_KEX "mlkem768nistp256-sha256," +#endif + +#define FIPS_ALLOWED_KEX FIPS_MLKEM_KEX \ + "ecdh-sha2-nistp256,"\ + "ecdh-sha2-nistp384,"\ + "ecdh-sha2-nistp521,"\ + "diffie-hellman-group-exchange-sha256,"\ + "diffie-hellman-group14-sha256,"\ + "diffie-hellman-group16-sha512,"\ + "diffie-hellman-group18-sha512" + +#define FIPS_ALLOWED_MACS "hmac-sha2-256-etm@openssh.com,"\ + "hmac-sha1-etm@openssh.com,"\ + "hmac-sha2-512-etm@openssh.com,"\ + "hmac-sha2-256,"\ + "hmac-sha1,"\ + "hmac-sha2-512" + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *fips_methods[] = { + FIPS_ALLOWED_KEX, + FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS, + FIPS_ALLOWED_CIPHERS, + FIPS_ALLOWED_CIPHERS, + FIPS_ALLOWED_MACS, + FIPS_ALLOWED_MACS, + ZLIB_DEFAULT, + ZLIB_DEFAULT, + "", + "", + NULL +}; + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *default_methods[] = { + DEFAULT_KEY_EXCHANGE, + DEFAULT_PUBLIC_KEY_ALGORITHMS, + CHACHA20 AES, + CHACHA20 AES, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", + ZLIB_DEFAULT, + ZLIB_DEFAULT, + "", + "", + NULL +}; + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *supported_methods[] = { + KEY_EXCHANGE_SUPPORTED, + PUBLIC_KEY_ALGORITHMS, + CHACHA20 AES AES_CBC BLOWFISH DES_SUPPORTED NONE, + CHACHA20 AES AES_CBC BLOWFISH DES_SUPPORTED NONE, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1" NONE, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1" NONE, + ZLIB, + ZLIB, + "", + "", + NULL +}; + +/* descriptions of the key exchange packet */ +static const char *ssh_kex_descriptions[] = { + "kex algos", + "server host key algo", + "encryption client->server", + "encryption server->client", + "mac algo client->server", + "mac algo server->client", + "compression algo client->server", + "compression algo server->client", + "languages client->server", + "languages server->client", + NULL +}; + +const char *ssh_kex_get_default_methods(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } + + return default_methods[type]; +} +const char *ssh_kex_get_supported_method(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } + + return supported_methods[type]; +} + +const char *ssh_kex_get_description(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } + + return ssh_kex_descriptions[type]; +} + +const char *ssh_kex_get_fips_methods(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } + + return fips_methods[type]; +} + +/** + * @brief Get a list of supported algorithms of a given type. This respects the + * FIPS mode status. + * + * @param[in] type The type of the algorithm to query (SSH_KEX, SSH_MAC_C_S, + * ...). + * + * @return The list of supported methods as comma-separated string, or NULL for + * unknown type. + */ +const char *ssh_get_supported_methods(enum ssh_kex_types_e type) +{ + if (ssh_fips_mode()) { + return ssh_kex_get_fips_methods(type); + } else { + return ssh_kex_get_supported_method(type); + } +} + +/** + * @internal + * @brief returns whether the first client key exchange algorithm or + * hostkey type matches its server counterpart + * @returns whether the first client key exchange algorithm or hostkey type + * matches its server counterpart + */ +static int cmp_first_kex_algo(const char *client_str, + const char *server_str) { + size_t client_kex_len; + size_t server_kex_len; + + const char *colon = NULL; + + int is_wrong = 1; + + if (client_str == NULL || server_str == NULL) { + return is_wrong; + } + + colon = strchr(client_str, ','); + if (colon == NULL) { + client_kex_len = strlen(client_str); + } else { + client_kex_len = colon - client_str; + } + + colon = strchr(server_str, ','); + if (colon == NULL) { + server_kex_len = strlen(server_str); + } else { + server_kex_len = colon - server_str; + } + + if (client_kex_len != server_kex_len) { + return is_wrong; + } + + is_wrong = (strncmp(client_str, server_str, client_kex_len) != 0); + + return is_wrong; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexinit) +{ + int i, ok; + struct ssh_crypto_struct *crypto = session->next_crypto; + int server_kex = session->server; + ssh_string str = NULL; + char *strings[SSH_KEX_METHODS] = {0}; + int rc = SSH_ERROR; + size_t len; + + uint8_t first_kex_packet_follows = 0; + uint32_t kexinit_reserved = 0; + + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "KEXINIT received"); + + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) { + if (session->dh_handshake_state == DH_STATE_FINISHED) { + SSH_LOG(SSH_LOG_DEBUG, "Peer initiated key re-exchange"); + /* Reset the sent flag if the re-kex was initiated by the peer */ + session->flags &= ~SSH_SESSION_FLAG_KEXINIT_SENT; + } else if (session->flags & SSH_SESSION_FLAG_KEXINIT_SENT && + session->dh_handshake_state == DH_STATE_INIT_SENT) { + /* This happens only when we are sending our-guessed first kex + * packet right after our KEXINIT packet. */ + SSH_LOG(SSH_LOG_DEBUG, "Received peer kexinit answer."); + } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { + ssh_set_error(session, SSH_FATAL, + "SSH_KEXINIT received in wrong state"); + goto error; + } + } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { + ssh_set_error(session, SSH_FATAL, + "SSH_KEXINIT received in wrong state"); + goto error; + } + + if (server_kex) { +#ifdef WITH_SERVER + len = ssh_buffer_get_data(packet, crypto->client_kex.cookie, 16); + if (len != 16) { + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + + ok = ssh_hashbufin_add_cookie(session, crypto->client_kex.cookie); + if (ok < 0) { + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + + ok = server_set_kex(session); + if (ok == SSH_ERROR) { + goto error; + } +#endif /* WITH_SERVER */ + } else { + len = ssh_buffer_get_data(packet, crypto->server_kex.cookie, 16); + if (len != 16) { + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + + ok = ssh_hashbufin_add_cookie(session, crypto->server_kex.cookie); + if (ok < 0) { + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + + ok = ssh_set_client_kex(session); + if (ok == SSH_ERROR) { + goto error; + } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + str = ssh_buffer_get_ssh_string(packet); + if (str == NULL) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->in_hashbuf, str); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Error adding string in hash buffer"); + goto error; + } + + strings[i] = ssh_string_to_char(str); + if (strings[i] == NULL) { + ssh_set_error_oom(session); + goto error; + } + SSH_STRING_FREE(str); + str = NULL; + } + + /* copy the peer kex info into an array of strings */ + if (server_kex) { +#ifdef WITH_SERVER + for (i = 0; i < SSH_KEX_METHODS; i++) { + crypto->client_kex.methods[i] = strings[i]; + } +#endif /* WITH_SERVER */ + } else { /* client */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + crypto->server_kex.methods[i] = strings[i]; + } + } + + /* + * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): + * + * boolean first_kex_packet_follows + * uint32 0 (reserved for future extension) + * + * Notably if clients set 'first_kex_packet_follows', it is expected + * that its value is included when computing the session ID (see + * 'make_sessionid'). + */ + + rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); + if (rc != 1) { + goto error; + } + + rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); + if (rc < 0) { + goto error; + } + + /* + * Remember whether 'first_kex_packet_follows' was set and the client + * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message + * must be ignored on the server side. + * Client needs to start the Key exchange over with the correct method + */ + if (first_kex_packet_follows || session->send_first_kex_follows) { + char **client_methods = crypto->client_kex.methods; + char **server_methods = crypto->server_kex.methods; + session->first_kex_follows_guess_wrong = + cmp_first_kex_algo(client_methods[SSH_KEX], + server_methods[SSH_KEX]) || + cmp_first_kex_algo(client_methods[SSH_HOSTKEYS], + server_methods[SSH_HOSTKEYS]); + SSH_LOG(SSH_LOG_DEBUG, "The initial guess was %s.", + session->first_kex_follows_guess_wrong ? "wrong" : "right"); + } + + /* + * handle the "strict KEX" feature. If supported by peer, then set up the + * flag and verify packet sequence numbers. + */ + if (server_kex) { + ok = match_group(crypto->client_kex.methods[SSH_KEX], + KEX_STRICT_CLIENT); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Client supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } else { + /* client kex */ + ok = match_group(crypto->server_kex.methods[SSH_KEX], + KEX_STRICT_SERVER); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Server supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } +#ifdef WITH_SERVER + if (server_kex) { + /* + * If client sent a ext-info-c message in the kex list, it supports + * RFC 8308 extension negotiation. + */ + ok = match_group(crypto->client_kex.methods[SSH_KEX], + KEX_EXTENSION_CLIENT); + if (ok) { + const char *hostkeys = NULL, *wanted_hostkeys = NULL; + + /* The client supports extension negotiation */ + session->extensions |= SSH_EXT_NEGOTIATION; + /* + * RFC 8332 Section 3.1: Use for Server Authentication + * Check what algorithms were provided in the SSH_HOSTKEYS list + * by the client and enable the respective extensions to provide + * correct signature in the next packet if RSA is negotiated + */ + hostkeys = crypto->client_kex.methods[SSH_HOSTKEYS]; + wanted_hostkeys = session->opts.wanted_methods[SSH_HOSTKEYS]; + ok = match_group(hostkeys, "rsa-sha2-512"); + if (ok) { + /* Check if rsa-sha2-512 is allowed by config */ + if (wanted_hostkeys != NULL) { + char *is_allowed = ssh_find_matching(wanted_hostkeys, + "rsa-sha2-512"); + if (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } + SAFE_FREE(is_allowed); + } + } + ok = match_group(hostkeys, "rsa-sha2-256"); + if (ok) { + /* Check if rsa-sha2-256 is allowed by config */ + if (wanted_hostkeys != NULL) { + char *is_allowed = ssh_find_matching(wanted_hostkeys, + "rsa-sha2-256"); + if (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } + SAFE_FREE(is_allowed); + } + } + + /* + * Ensure that the client preference is honored for the case + * both signature types are enabled. + */ + if ((session->extensions & SSH_EXT_SIG_RSA_SHA256) && + (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { + char *rsa_sig_ext = NULL; + session->extensions &= ~(SSH_EXT_SIG_RSA_SHA256 | SSH_EXT_SIG_RSA_SHA512); + rsa_sig_ext = ssh_find_matching("rsa-sha2-512,rsa-sha2-256", + hostkeys); + if (rsa_sig_ext == NULL) { + goto error; /* should never happen */ + } else if (strcmp(rsa_sig_ext, "rsa-sha2-512") == 0) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } else if (strcmp(rsa_sig_ext, "rsa-sha2-256") == 0) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } else { + SAFE_FREE(rsa_sig_ext); + goto error; /* should never happen */ + } + SAFE_FREE(rsa_sig_ext); + } + + SSH_LOG(SSH_LOG_DEBUG, "The client supports extension " + "negotiation. Enabled signature algorithms: %s%s", + session->extensions & SSH_EXT_SIG_RSA_SHA256 ? "SHA256" : "", + session->extensions & SSH_EXT_SIG_RSA_SHA512 ? " SHA512" : ""); + } + } +#endif /* WITH_SERVER */ + + /* Note, that his overwrites authenticated state in case of rekeying */ + session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED; + /* if we already sent our initial key exchange packet, do not reset the + * DH state. We will know if we were right with our guess only in + * dh_handshake_state() */ + if (session->send_first_kex_follows == false) { + session->dh_handshake_state = DH_STATE_INIT; + } + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + +error: + SSH_STRING_FREE(str); + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (server_kex) { +#ifdef WITH_SERVER + session->next_crypto->client_kex.methods[i] = NULL; +#endif /* WITH_SERVER */ + } else { /* client */ + session->next_crypto->server_kex.methods[i] = NULL; + } + SAFE_FREE(strings[i]); + } + + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +void ssh_list_kex(struct ssh_kex_struct *kex) { + int i = 0; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("session cookie", kex->cookie, 16); +#endif + + for(i = 0; i < SSH_KEX_METHODS; i++) { + if (kex->methods[i] == NULL) { + continue; + } + SSH_LOG(SSH_LOG_FUNCTIONS, "%s: %s", + ssh_kex_descriptions[i], kex->methods[i]); + } +} + +/** + * @internal + * + * @brief selects the hostkey mechanisms to be chosen for the key exchange, + * as some hostkey mechanisms may be present in known_hosts files. + * + * @returns a cstring containing a comma-separated list of hostkey methods. + * NULL if no method matches + */ +char *ssh_client_select_hostkeys(ssh_session session) +{ + const char *wanted = NULL; + char *wanted_without_certs = NULL; + char *known_hosts_algorithms = NULL; + char *known_hosts_ordered = NULL; + char *new_hostkeys = NULL; + char *fips_hostkeys = NULL; + + wanted = session->opts.wanted_methods[SSH_HOSTKEYS]; + if (wanted == NULL) { + if (ssh_fips_mode()) { + wanted = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + wanted = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + + /* This removes the certificate types, unsupported for now */ + wanted_without_certs = ssh_find_all_matching(HOSTKEYS, wanted); + if (wanted_without_certs == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "List of allowed host key algorithms is empty or contains only " + "unsupported algorithms"); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Order of wanted host keys: \"%s\"", + wanted_without_certs); + + known_hosts_algorithms = ssh_known_hosts_get_algorithms_names(session); + if (known_hosts_algorithms == NULL) { + SSH_LOG(SSH_LOG_DEBUG, + "No key found in known_hosts; " + "changing host key method to \"%s\"", + wanted_without_certs); + + return wanted_without_certs; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Algorithms found in known_hosts files: \"%s\"", + known_hosts_algorithms); + + /* Filter and order the keys from known_hosts according to wanted list */ + known_hosts_ordered = ssh_find_all_matching(known_hosts_algorithms, + wanted_without_certs); + SAFE_FREE(known_hosts_algorithms); + if (known_hosts_ordered == NULL) { + SSH_LOG(SSH_LOG_DEBUG, + "No key found in known_hosts is allowed; " + "changing host key method to \"%s\"", + wanted_without_certs); + + return wanted_without_certs; + } + + /* Append the other supported keys after the preferred ones + * This function tolerates NULL pointers in parameters */ + new_hostkeys = ssh_append_without_duplicates(known_hosts_ordered, + wanted_without_certs); + SAFE_FREE(known_hosts_ordered); + SAFE_FREE(wanted_without_certs); + if (new_hostkeys == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (ssh_fips_mode()) { + /* Filter out algorithms not allowed in FIPS mode */ + fips_hostkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, new_hostkeys); + SAFE_FREE(new_hostkeys); + if (fips_hostkeys == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "None of the wanted host keys or keys in known_hosts files " + "is allowed in FIPS mode."); + return NULL; + } + new_hostkeys = fips_hostkeys; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Changing host key method to \"%s\"", + new_hostkeys); + + return new_hostkeys; +} + +/** + * @brief sets the key exchange parameters to be sent to the server, + * in function of the options and available methods. + */ +int ssh_set_client_kex(ssh_session session) +{ + struct ssh_kex_struct *client = &session->next_crypto->client_kex; + const char *wanted = NULL; + int ok; + int i; + bool gssapi_null_alg = false; + char *hostkeys = NULL; + + /* Skip if already set, for example for the rekey or when we do the guessing + * it could have been already used to make some protocol decisions. */ + if (client->methods[0] != NULL) { + return SSH_OK; + } + + ok = ssh_get_random(client->cookie, 16, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return SSH_ERROR; + } +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + char *gssapi_algs = NULL; + + ok = ssh_gssapi_init(session); + if (ok != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + ok = ssh_gssapi_import_name(session->gssapi, session->opts.host); + if (ok != SSH_OK) { + return SSH_ERROR; + } + + gssapi_algs = ssh_gssapi_kex_mechs(session); + if (gssapi_algs == NULL) { + return SSH_ERROR; + } + + /* Prefix the default algorithms with gsskex algs */ + if (ssh_fips_mode()) { + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(fips_methods[SSH_KEX], + gssapi_algs); + } else { + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(default_methods[SSH_KEX], + gssapi_algs); + } + + gssapi_null_alg = true; + + SAFE_FREE(gssapi_algs); + } +#endif + + /* Set the list of allowed algorithms in order of preference, if it hadn't + * been set yet. */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (i == SSH_HOSTKEYS) { + /* Set the hostkeys in the following order: + * - First: keys present in known_hosts files ordered by preference + * - Next: other wanted algorithms ordered by preference */ + client->methods[i] = ssh_client_select_hostkeys(session); + if (client->methods[i] == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + if (gssapi_null_alg) { + hostkeys = + ssh_append_without_duplicates(client->methods[i], "null"); + if (hostkeys == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + SAFE_FREE(client->methods[i]); + client->methods[i] = hostkeys; + } + continue; + } + + wanted = session->opts.wanted_methods[i]; + if (wanted == NULL) { + if (ssh_fips_mode()) { + wanted = fips_methods[i]; + } else { + wanted = default_methods[i]; + } + } + client->methods[i] = strdup(wanted); + if (client->methods[i] == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + + /* For rekeying, skip the extension negotiation */ + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + return SSH_OK; + } + + ok = ssh_kex_append_extensions(session, client); + if (ok != SSH_OK){ + return ok; + } + + return SSH_OK; +} + +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex) +{ + char *kex = NULL; + char *kex_tmp = NULL; + size_t kex_len, len; + + /* Here we append ext-info-c and kex-strict-c-v00@openssh.com for client + * and kex-strict-s-v00@openssh.com for server to the list of kex algorithms + */ + kex = pkex->methods[SSH_KEX]; + len = strlen(kex); + if (session->server) { + /* Comma, nul byte */ + kex_len = len + 1 + strlen(KEX_STRICT_SERVER) + 1; + } else { + /* Comma, comma, nul byte */ + kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + + strlen(KEX_STRICT_CLIENT) + 1; + } + if (kex_len >= MAX_PACKET_LEN) { + /* Overflow */ + return SSH_ERROR; + } + kex_tmp = realloc(kex, kex_len); + if (kex_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + if (session->server){ + snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_STRICT_SERVER); + } else { + snprintf(kex_tmp + len, + kex_len - len, + ",%s,%s", + KEX_EXTENSION_CLIENT, + KEX_STRICT_CLIENT); + } + pkex->methods[SSH_KEX] = kex_tmp; + return SSH_OK; +} + +static const char *ssh_find_aead_hmac(const char *cipher) +{ + if (cipher == NULL) { + return NULL; + } else if (strcmp(cipher, "chacha20-poly1305@openssh.com") == 0) { + return "aead-poly1305"; + } else if (strcmp(cipher, "aes256-gcm@openssh.com") == 0) { + return "aead-gcm"; + } else if (strcmp(cipher, "aes128-gcm@openssh.com") == 0) { + return "aead-gcm"; + } + return NULL; +} + +static enum ssh_key_exchange_e +kex_select_kex_type(const char *kex) +{ + if (strcmp(kex, "diffie-hellman-group1-sha1") == 0) { + return SSH_KEX_DH_GROUP1_SHA1; + } else if (strncmp(kex, "gss-group14-sha256-", 19) == 0) { + return SSH_GSS_KEX_DH_GROUP14_SHA256; + } else if (strncmp(kex, "gss-group16-sha512-", 19) == 0) { + return SSH_GSS_KEX_DH_GROUP16_SHA512; + } else if (strncmp(kex, "gss-nistp256-sha256-", 20) == 0) { + return SSH_GSS_KEX_ECDH_NISTP256_SHA256; + } else if (strncmp(kex, "gss-curve25519-sha256-", 22) == 0) { + return SSH_GSS_KEX_CURVE25519_SHA256; + } else if (strcmp(kex, "diffie-hellman-group14-sha1") == 0) { + return SSH_KEX_DH_GROUP14_SHA1; + } else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) { + return SSH_KEX_DH_GROUP14_SHA256; + } else if (strcmp(kex, "diffie-hellman-group16-sha512") == 0) { + return SSH_KEX_DH_GROUP16_SHA512; + } else if (strcmp(kex, "diffie-hellman-group18-sha512") == 0) { + return SSH_KEX_DH_GROUP18_SHA512; +#ifdef WITH_GEX + } else if (strcmp(kex, "diffie-hellman-group-exchange-sha1") == 0) { + return SSH_KEX_DH_GEX_SHA1; + } else if (strcmp(kex, "diffie-hellman-group-exchange-sha256") == 0) { + return SSH_KEX_DH_GEX_SHA256; +#endif /* WITH_GEX */ + } else if (strcmp(kex, "ecdh-sha2-nistp256") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP256; + } else if (strcmp(kex, "ecdh-sha2-nistp384") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP384; + } else if (strcmp(kex, "ecdh-sha2-nistp521") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP521; + } else if (strcmp(kex, "curve25519-sha256@libssh.org") == 0) { + return SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; + } else if (strcmp(kex, "curve25519-sha256") == 0) { + return SSH_KEX_CURVE25519_SHA256; + } else if (strcmp(kex, "sntrup761x25519-sha512@openssh.com") == 0) { + return SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM; + } else if (strcmp(kex, "sntrup761x25519-sha512") == 0) { + return SSH_KEX_SNTRUP761X25519_SHA512; + } else if (strcmp(kex, "mlkem768x25519-sha256") == 0) { + return SSH_KEX_MLKEM768X25519_SHA256; + } else if (strcmp(kex, "mlkem768nistp256-sha256") == 0) { + return SSH_KEX_MLKEM768NISTP256_SHA256; +#ifdef HAVE_MLKEM1024 + } else if (strcmp(kex, "mlkem1024nistp384-sha384") == 0) { + return SSH_KEX_MLKEM1024NISTP384_SHA384; +#endif + } + /* should not happen. We should be getting only valid names at this stage */ + return 0; +} + + +/** @internal + * @brief Reverts guessed callbacks set during the dh_handshake() + * @param session session handle + * @returns void + */ +static void revert_kex_callbacks(ssh_session session) +{ + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + ssh_client_dh_remove_callbacks(session); + break; + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: +#ifdef WITH_GSSAPI + ssh_client_gss_kex_remove_callbacks(session); +#endif /* WITH_GSSAPI */ + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + ssh_client_dhgex_remove_callbacks(session); + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + ssh_client_ecdh_remove_callbacks(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + ssh_client_curve25519_remove_callbacks(session); + break; +#endif +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + ssh_client_sntrup761x25519_remove_callbacks(session); + break; +#endif + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ssh_client_hybrid_mlkem_remove_callbacks(session); + break; + } +} + +/** @brief Select the different methods on basis of client's and + * server's kex messages, and watches out if a match is possible. + */ +int ssh_kex_select_methods (ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + struct ssh_kex_struct *server = &crypto->server_kex; + struct ssh_kex_struct *client = &crypto->client_kex; + char *ext_start = NULL; + const char *aead_hmac = NULL; + enum ssh_key_exchange_e kex_type; + int i; + + /* Here we should drop the extensions from the list so we avoid matching. + * it. We added it to the end, so we can just truncate the string here */ + if (session->client) { + ext_start = strstr(client->methods[SSH_KEX], "," KEX_EXTENSION_CLIENT); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } + } + if (session->server) { + ext_start = strstr(server->methods[SSH_KEX], "," KEX_STRICT_SERVER); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + crypto->kex_methods[i] = ssh_find_matching(server->methods[i], + client->methods[i]); + + if (i == SSH_MAC_C_S || i == SSH_MAC_S_C) { + aead_hmac = ssh_find_aead_hmac(crypto->kex_methods[i - 2]); + if (aead_hmac) { + free(crypto->kex_methods[i]); + crypto->kex_methods[i] = strdup(aead_hmac); + } + } + if (crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S) { + ssh_set_error(session, SSH_FATAL, + "kex error : no match for method %s: server [%s], " + "client [%s]", ssh_kex_descriptions[i], + server->methods[i], client->methods[i]); + return SSH_ERROR; + } else if ((i >= SSH_LANG_C_S) && (crypto->kex_methods[i] == NULL)) { + /* we can safely do that for languages */ + crypto->kex_methods[i] = strdup(""); + } + } + + /* We can not set this value directly as the old value is needed to revert + * callbacks if we are client */ + kex_type = kex_select_kex_type(crypto->kex_methods[SSH_KEX]); + if (session->client && session->first_kex_follows_guess_wrong) { + SSH_LOG(SSH_LOG_DEBUG, "Our guess was wrong. Restarting the KEX"); + /* We need to remove the wrong callbacks and start kex again */ + revert_kex_callbacks(session); + session->dh_handshake_state = DH_STATE_INIT; + session->first_kex_follows_guess_wrong = false; + } + crypto->kex_type = kex_type; + + SSH_LOG(SSH_LOG_DEBUG, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + session->next_crypto->kex_methods[SSH_KEX], + session->next_crypto->kex_methods[SSH_HOSTKEYS], + session->next_crypto->kex_methods[SSH_CRYPT_C_S], + session->next_crypto->kex_methods[SSH_CRYPT_S_C], + session->next_crypto->kex_methods[SSH_MAC_C_S], + session->next_crypto->kex_methods[SSH_MAC_S_C], + session->next_crypto->kex_methods[SSH_COMP_C_S], + session->next_crypto->kex_methods[SSH_COMP_S_C], + session->next_crypto->kex_methods[SSH_LANG_C_S], + session->next_crypto->kex_methods[SSH_LANG_S_C] + ); + return SSH_OK; +} + + +/* this function only sends the predefined set of kex methods */ +int ssh_send_kex(ssh_session session) +{ + struct ssh_kex_struct *kex = (session->server ? + &session->next_crypto->server_kex : + &session->next_crypto->client_kex); + ssh_string str = NULL; + int i; + int rc; + int first_kex_packet_follows = 0; + + /* Only client can initiate the handshake methods we implement. If we + * already received the peer mechanisms, there is no point in guessing */ + if (session->client && + session->session_state != SSH_SESSION_STATE_KEXINIT_RECEIVED && + session->send_first_kex_follows) { + first_kex_packet_follows = 1; + } + + SSH_LOG(SSH_LOG_TRACE, + "Sending KEXINIT packet, first_kex_packet_follows = %d", + first_kex_packet_follows); + + rc = ssh_buffer_pack(session->out_buffer, + "bP", + SSH2_MSG_KEXINIT, + (size_t)16, + kex->cookie); /* cookie */ + if (rc != SSH_OK) + goto error; + if (ssh_hashbufout_add_cookie(session) < 0) { + goto error; + } + + ssh_list_kex(kex); + + for (i = 0; i < SSH_KEX_METHODS; i++) { + str = ssh_string_from_char(kex->methods[i]); + if (str == NULL) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_hashbuf, str); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_ssh_string(session->out_buffer, str); + if (rc < 0) { + goto error; + } + SSH_STRING_FREE(str); + str = NULL; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + first_kex_packet_follows, + 0); + if (rc != SSH_OK) { + goto error; + } + + /* Prepare also the first_kex_packet_follows and reserved to 0 */ + rc = ssh_buffer_add_u8(session->out_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(session->out_hashbuf, 0); + if (rc < 0) { + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return -1; + } + + session->flags |= SSH_SESSION_FLAG_KEXINIT_SENT; + SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); + + /* If we indicated that we are sending the guessed key exchange packet, + * do it now. The packet is simple, but we need to do some preparations */ + if (first_kex_packet_follows == 1) { + char *list = kex->methods[SSH_KEX]; + const char *colon = strchr(list, ','); + size_t kex_name_len = colon ? (size_t)(colon - list) : strlen(list); + char *kex_name = calloc(kex_name_len + 1, 1); + if (kex_name == NULL) { + ssh_set_error_oom(session); + goto error; + } + snprintf(kex_name, kex_name_len + 1, "%.*s", (int)kex_name_len, list); + SSH_LOG(SSH_LOG_TRACE, "Sending the first kex packet for %s", kex_name); + + session->next_crypto->kex_type = kex_select_kex_type(kex_name); + free(kex_name); + + /* run the first step of the DH handshake */ + session->dh_handshake_state = DH_STATE_INIT; + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } + } + return 0; + +error: + ssh_buffer_reinit(session->out_buffer); + ssh_buffer_reinit(session->out_hashbuf); + SSH_STRING_FREE(str); + + return -1; +} + +/* + * Key re-exchange (rekey) is triggered by this function. + * It can not be called again after the rekey is initialized! + */ +int ssh_send_rekex(ssh_session session) +{ + int rc; + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + /* Rekey/Key exchange is already in progress */ + SSH_LOG(SSH_LOG_PACKET, "Attempting rekey in bad state"); + return SSH_ERROR; + } + + if (session->current_crypto == NULL) { + /* No current crypto used -- can not exchange it */ + SSH_LOG(SSH_LOG_PACKET, "No crypto to rekey"); + return SSH_ERROR; + } + + if (session->client) { + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Failed to set client kex"); + return rc; + } + } else { +#ifdef WITH_SERVER + rc = server_set_kex(session); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_PACKET, "Failed to set server kex"); + return rc; + } +#else + SSH_LOG(SSH_LOG_PACKET, "Invalid session state."); + return SSH_ERROR; +#endif /* WITH_SERVER */ + } + + session->dh_handshake_state = DH_STATE_INIT; + rc = ssh_send_kex(session); + if (rc < 0) { + SSH_LOG(SSH_LOG_PACKET, "Failed to send kex"); + return rc; + } + + /* Reset the handshake state */ + session->dh_handshake_state = DH_STATE_INIT_SENT; + return SSH_OK; +} + +/* returns a copy of the provided list if everything is supported, + * otherwise a new list of the supported algorithms */ +char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + return ssh_find_all_matching(supported_methods[algo], list); +} + +/** + * @internal + * + * @brief Return a newly allocated string containing only the FIPS allowed + * algorithms from the list. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be filtered + * + * @return A newly allocated list containing only the FIPS allowed algorithms from + * the list; NULL in case of error. + */ +char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + return ssh_find_all_matching(fips_methods[algo], list); +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms plus the algorithms specified in list. If the system + * runs in fips mode, this will add only fips approved algorithms. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be appended + * + * @return A newly allocated list containing the default algorithms and the + * algorithms in list at the end; NULL in case of error. + */ +char *ssh_add_to_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *tmp = NULL, *ret = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_append_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_append_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms excluding the algorithms specified in list. If the system + * runs in fips mode, this will remove from the fips_methods list. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be exclude + * + * @return A newly allocated list containing the default algorithms without the + * algorithms in list; NULL in case of error. + */ +char *ssh_remove_from_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + if (list == NULL || list[0] == '\0') { + if (ssh_fips_mode()) { + return strdup(fips_methods[algo]); + } else { + return strdup(default_methods[algo]); + } + } + + if (ssh_fips_mode()) { + return ssh_remove_all_matching(fips_methods[algo], list); + } else { + return ssh_remove_all_matching(default_methods[algo], list); + } +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms with prioritized algorithms specified in list. If the + * algorithms are present in the default list they get prioritized, if not + * they are added to the front of the default list. If the system + * runs in fips mode, this will work with the fips_methods list. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be pushed to priority + * + * @return A newly allocated list containing the default algorithms prioritized + * with the algorithms in list at the beginning of the list; NULL in case + * of error. + */ +char *ssh_prefix_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *ret = NULL, *tmp = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_prefix_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_prefix_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + +int ssh_make_sessionid(ssh_session session) +{ + ssh_string num = NULL; + ssh_buffer server_hash = NULL; + ssh_buffer client_hash = NULL; + ssh_buffer buf = NULL; + ssh_string server_pubkey_blob = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum client_pubkey, server_pubkey; +#else + bignum client_pubkey = NULL, server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ +#ifdef WITH_GEX +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum modulus, generator; +#else + bignum modulus = NULL, generator = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* WITH_GEX */ + int rc = SSH_ERROR; + + buf = ssh_buffer_new(); + if (buf == NULL) { + ssh_set_error_oom(session); + return rc; + } + + rc = ssh_buffer_pack(buf, + "ss", + session->clientbanner, + session->serverbanner); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack client and server banner"); + goto error; + } + + if (session->client) { + server_hash = session->in_hashbuf; + client_hash = session->out_hashbuf; + } else { + server_hash = session->out_hashbuf; + client_hash = session->in_hashbuf; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to get next server pubkey blob"); + goto error; + } + + if (server_pubkey_blob == NULL) { + if ((session->server && ssh_kex_is_gss(session->next_crypto)) || + session->opts.gssapi_key_exchange) { + server_pubkey_blob = ssh_string_new(0); + if (server_pubkey_blob == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + } + } + + rc = ssh_buffer_pack(buf, + "dPdPS", + ssh_buffer_get_len(client_hash), + (size_t)ssh_buffer_get_len(client_hash), + ssh_buffer_get(client_hash), + ssh_buffer_get_len(server_hash), + (size_t)ssh_buffer_get_len(server_hash), + ssh_buffer_get(server_hash), + server_pubkey_blob); + SSH_STRING_FREE(server_pubkey_blob); + if (rc != SSH_OK){ + ssh_set_error(session, + SSH_FATAL, + "Failed to pack hashes and pubkey blob"); + goto error; + } + + switch(session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, NULL, &client_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_SERVER_KEYPAIR, NULL, &server_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(buf, + "BB", + client_pubkey, + server_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack DH pubkeys"); + goto error; + } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, NULL, &client_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_SERVER_KEYPAIR, NULL, &server_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_get_parameters(session->next_crypto->dh_ctx, + &modulus, &generator); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(buf, + "dddBBBB", + session->next_crypto->dh_pmin, + session->next_crypto->dh_pn, + session->next_crypto->dh_pmax, + modulus, + generator, + client_pubkey, + server_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack DH GEX params"); + goto error; + } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(modulus); + bignum_safe_free(generator); +#endif /* OPENSSL_VERSION_NUMBER */ + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + if (session->next_crypto->ecdh_client_pubkey == NULL || + session->next_crypto->ecdh_server_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "ECDH parameter missing"); + goto error; + } + rc = ssh_buffer_pack(buf, + "SS", + session->next_crypto->ecdh_client_pubkey, + session->next_crypto->ecdh_server_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack ECDH pubkeys"); + goto error; + } + break; +#endif /* HAVE_ECDH */ +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_buffer_pack(buf, + "dPdP", + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_server_pubkey); + + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack Curve25519 pubkeys"); + goto error; + } + break; +#endif /* HAVE_CURVE25519 */ +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + rc = ssh_buffer_pack(buf, + "dPPdPP", + SNTRUP761_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE, + (size_t)SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_client_pubkey, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey, + SNTRUP761_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE, + (size_t)SNTRUP761_CIPHERTEXT_SIZE, + session->next_crypto->sntrup761_ciphertext, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_server_pubkey); + + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack SNTRU Prime params"); + goto error; + } + break; +#endif /* HAVE_SNTRUP761 */ + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_buffer_pack(buf, + "SS", + session->next_crypto->hybrid_client_init, + session->next_crypto->hybrid_server_reply); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack ML-KEM individual components"); + goto error; + } + break; + default: + /* Handle unsupported kex types - this should not happen in normal operation */ + rc = SSH_ERROR; + ssh_set_error(session, SSH_FATAL, "Unsupported KEX algorithm"); + goto error; + } + switch (session->next_crypto->kex_type) { + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + rc = ssh_buffer_pack(buf, + "F", + session->next_crypto->shared_secret, + SHA512_DIGEST_LEN); + break; + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_buffer_pack(buf, "S", session->next_crypto->hybrid_shared_secret); + break; + default: + rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); + break; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack shared secret"); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("hash buffer", ssh_buffer_get(buf), ssh_buffer_get_len(buf)); +#endif + + /* Set rc for the following switch statement in case we goto error. */ + rc = SSH_ERROR; + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: +#endif /* WITH_GEX */ + session->next_crypto->digest_len = SHA_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA1; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha1(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA256: +#endif /* WITH_GEX */ + session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA256; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha256(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_ECDH_SHA2_NISTP384: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + session->next_crypto->digest_len = SHA384_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA384; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha384(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + case SSH_KEX_ECDH_SHA2_NISTP521: + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + session->next_crypto->digest_len = SHA512_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA512; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha512(ssh_buffer_get(buf), + ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + default: + /* Handle unsupported kex types - this should not happen in normal operation */ + ssh_set_error(session, SSH_FATAL, "Unsupported KEX algorithm for hash computation"); + rc = SSH_ERROR; + goto error; + } + + /* During the first kex, secret hash and session ID are equal. However, after + * a key re-exchange, a new secret hash is calculated. This hash will not replace + * but complement existing session id. + */ + if (!session->next_crypto->session_id) { + session->next_crypto->session_id = malloc(session->next_crypto->digest_len); + if (session->next_crypto->session_id == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, + session->next_crypto->digest_len); + /* Initial length is the same as secret hash */ + session->next_crypto->session_id_len = session->next_crypto->digest_len; + } +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_DEBUG, "Session hash: \n"); + ssh_log_hexdump("secret hash", session->next_crypto->secret_hash, session->next_crypto->digest_len); + ssh_log_hexdump("session id", session->next_crypto->session_id, session->next_crypto->session_id_len); +#endif /* DEBUG_CRYPTO */ + + rc = SSH_OK; +error: + SSH_BUFFER_FREE(buf); + SSH_BUFFER_FREE(client_hash); + SSH_BUFFER_FREE(server_hash); + + session->in_hashbuf = NULL; + session->out_hashbuf = NULL; + + SSH_STRING_FREE(num); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ + + return rc; +} + +int ssh_hashbufout_add_cookie(ssh_session session) +{ + int rc; + + session->out_hashbuf = ssh_buffer_new(); + if (session->out_hashbuf == NULL) { + return -1; + } + + rc = ssh_buffer_allocate_size(session->out_hashbuf, + sizeof(uint8_t) + 16); + if (rc < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + + if (ssh_buffer_add_u8(session->out_hashbuf, 20) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + + if (session->server) { + if (ssh_buffer_add_data(session->out_hashbuf, + session->next_crypto->server_kex.cookie, 16) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + } else { + if (ssh_buffer_add_data(session->out_hashbuf, + session->next_crypto->client_kex.cookie, 16) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + } + + return 0; +} + +int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie) +{ + int rc; + + session->in_hashbuf = ssh_buffer_new(); + if (session->in_hashbuf == NULL) { + return -1; + } + + rc = ssh_buffer_allocate_size(session->in_hashbuf, + sizeof(uint8_t) + 20 + 16); + if (rc < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + + if (ssh_buffer_add_u8(session->in_hashbuf, 20) < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + if (ssh_buffer_add_data(session->in_hashbuf,cookie, 16) < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + + return 0; +} + +int ssh_generate_session_keys(ssh_session session) +{ + ssh_string k_string = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + unsigned char *key = NULL; + unsigned char *IV_cli_to_srv = NULL; + unsigned char *IV_srv_to_cli = NULL; + unsigned char *enckey_cli_to_srv = NULL; + unsigned char *enckey_srv_to_cli = NULL; + unsigned char *intkey_cli_to_srv = NULL; + unsigned char *intkey_srv_to_cli = NULL; + size_t key_len = 0; + size_t IV_len = 0; + size_t enckey_cli_to_srv_len = 0; + size_t enckey_srv_to_cli_len = 0; + size_t intkey_cli_to_srv_len = 0; + size_t intkey_srv_to_cli_len = 0; + int rc = -1; + + switch (session->next_crypto->kex_type) { + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + k_string = ssh_make_padded_bignum_string(crypto->shared_secret, + crypto->digest_len); + break; + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + k_string = ssh_string_copy(crypto->hybrid_shared_secret); + break; + default: + k_string = ssh_make_bignum_string(crypto->shared_secret); + break; + } + if (k_string == NULL) { + ssh_set_error_oom(session); + goto error; + } + /* See RFC4251 Section 5 for the definition of mpint which is the + * encoding we need to use for key in the SSH KDF */ + key = (unsigned char *)k_string; + key_len = ssh_string_len(k_string) + 4; + + IV_len = crypto->digest_len; + if (session->client) { + enckey_cli_to_srv_len = crypto->out_cipher->keysize / 8; + enckey_srv_to_cli_len = crypto->in_cipher->keysize / 8; + intkey_cli_to_srv_len = hmac_digest_len(crypto->out_hmac); + intkey_srv_to_cli_len = hmac_digest_len(crypto->in_hmac); + } else { + enckey_cli_to_srv_len = crypto->in_cipher->keysize / 8; + enckey_srv_to_cli_len = crypto->out_cipher->keysize / 8; + intkey_cli_to_srv_len = hmac_digest_len(crypto->in_hmac); + intkey_srv_to_cli_len = hmac_digest_len(crypto->out_hmac); + } + + IV_cli_to_srv = malloc(IV_len); + IV_srv_to_cli = malloc(IV_len); + enckey_cli_to_srv = malloc(enckey_cli_to_srv_len); + enckey_srv_to_cli = malloc(enckey_srv_to_cli_len); + intkey_cli_to_srv = malloc(intkey_cli_to_srv_len); + intkey_srv_to_cli = malloc(intkey_srv_to_cli_len); + if (IV_cli_to_srv == NULL || IV_srv_to_cli == NULL || + enckey_cli_to_srv == NULL || enckey_srv_to_cli == NULL || + intkey_cli_to_srv == NULL || intkey_srv_to_cli == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* IV */ + rc = ssh_kdf(crypto, key, key_len, 'A', IV_cli_to_srv, IV_len); + if (rc < 0) { + goto error; + } + rc = ssh_kdf(crypto, key, key_len, 'B', IV_srv_to_cli, IV_len); + if (rc < 0) { + goto error; + } + /* Encryption Key */ + rc = ssh_kdf(crypto, key, key_len, 'C', enckey_cli_to_srv, + enckey_cli_to_srv_len); + if (rc < 0) { + goto error; + } + rc = ssh_kdf(crypto, key, key_len, 'D', enckey_srv_to_cli, + enckey_srv_to_cli_len); + if (rc < 0) { + goto error; + } + /* Integrity Key */ + rc = ssh_kdf(crypto, key, key_len, 'E', intkey_cli_to_srv, + intkey_cli_to_srv_len); + if (rc < 0) { + goto error; + } + rc = ssh_kdf(crypto, key, key_len, 'F', intkey_srv_to_cli, + intkey_srv_to_cli_len); + if (rc < 0) { + goto error; + } + + if (session->client) { + crypto->encryptIV = IV_cli_to_srv; + crypto->decryptIV = IV_srv_to_cli; + crypto->encryptkey = enckey_cli_to_srv; + crypto->decryptkey = enckey_srv_to_cli; + crypto->encryptMAC = intkey_cli_to_srv; + crypto->decryptMAC = intkey_srv_to_cli; + } else { + crypto->encryptIV = IV_srv_to_cli; + crypto->decryptIV = IV_cli_to_srv; + crypto->encryptkey = enckey_srv_to_cli; + crypto->decryptkey = enckey_cli_to_srv; + crypto->encryptMAC = intkey_srv_to_cli; + crypto->decryptMAC = intkey_cli_to_srv; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Client to Server IV", IV_cli_to_srv, IV_len); + ssh_log_hexdump("Server to Client IV", IV_srv_to_cli, IV_len); + ssh_log_hexdump("Client to Server Encryption Key", enckey_cli_to_srv, + enckey_cli_to_srv_len); + ssh_log_hexdump("Server to Client Encryption Key", enckey_srv_to_cli, + enckey_srv_to_cli_len); + ssh_log_hexdump("Client to Server Integrity Key", intkey_cli_to_srv, + intkey_cli_to_srv_len); + ssh_log_hexdump("Server to Client Integrity Key", intkey_srv_to_cli, + intkey_srv_to_cli_len); +#endif /* DEBUG_CRYPTO */ + + rc = 0; +error: + ssh_string_burn(k_string); + SSH_STRING_FREE(k_string); + if (rc != 0) { + free(IV_cli_to_srv); + free(IV_srv_to_cli); + free(enckey_cli_to_srv); + free(enckey_srv_to_cli); + free(intkey_cli_to_srv); + free(intkey_srv_to_cli); + } + + return rc; +} + +/** @internal + * @brief Check if a given crypto context has a GSSAPI KEX set + * + * @param[in] crypto The SSH crypto context + * @return true if the KEX of the context is a GSSAPI KEX, false otherwise + */ +bool ssh_kex_is_gss(struct ssh_crypto_struct *crypto) +{ + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: + return true; + default: + return false; + } +} diff --git a/src/libs/libssh-0.12.2/src/known_hosts.c b/src/libs/libssh-0.12.2/src/known_hosts.c new file mode 100644 index 000000000000..701576ce8c13 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/known_hosts.c @@ -0,0 +1,589 @@ +/* + * keyfiles.c - private and public key handling for authentication. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/options.h" +#include "libssh/knownhosts.h" +/*todo: remove this include */ +#include "libssh/string.h" +#include "libssh/token.h" + +#ifndef _WIN32 +# include +# include +#endif + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 4096 +#endif + +/** + * @addtogroup libssh_session + * + * @{ + */ + +/** + * @internal + * + * @brief Return one line of known host file. + * + * This will return a token array containing (host|ip), keytype and key. + * + * @param[out] file A pointer to the known host file. Could be pointing to + * NULL at start. + * + * @param[in] filename The filename of the known host file. + * + * @param[out] found_type A pointer to a string to be set with the found key + * type. + * + * @returns The found_type type of key (ie "ssh-rsa"). Don't + * free that value. NULL if no match was found or the file + * was not found. + */ +static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file, + const char *filename, + const char **found_type) +{ + char buffer[MAX_LINE_SIZE] = {0}; + char *ptr = NULL; + struct ssh_tokens_st *tokens = NULL; + + if (*file == NULL) { + *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (*file == NULL) { + return NULL; + } + } + + while (fgets(buffer, sizeof(buffer), *file)) { + ptr = strchr(buffer, '\n'); + if (ptr) { + *ptr = '\0'; + } + + ptr = strchr(buffer,'\r'); + if (ptr) { + *ptr = '\0'; + } + + if (buffer[0] == '\0' || buffer[0] == '#') { + continue; /* skip empty lines */ + } + + tokens = ssh_tokenize(buffer, ' '); + if (tokens == NULL) { + fclose(*file); + *file = NULL; + + return NULL; + } + + if (tokens->tokens[0] == NULL || + tokens->tokens[1] == NULL || + tokens->tokens[2] == NULL) + { + /* it should have at least 3 tokens */ + ssh_tokens_free(tokens); + continue; + } + + *found_type = tokens->tokens[1]; + + return tokens; + } + + fclose(*file); + *file = NULL; + + /* we did not find anything, end of file*/ + return NULL; +} + +/** + * @internal + * + * @brief Check the public key in the known host line matches the public key of + * the currently connected server. + * + * @param[in] session The SSH session to use. + * + * @param[in] tokens A list of tokens in the known_hosts line. + * + * @returns 1 if the key matches, 0 if the key doesn't match and -1 + * on error. + */ +static int check_public_key(ssh_session session, char **tokens) { + ssh_string pubkey_blob = NULL; + ssh_buffer pubkey_buffer; + char *pubkey_64 = NULL; + int rc; + + /* ssh-rsa, ssh-ed25519, .. */ + pubkey_64 = tokens[2]; + pubkey_buffer = base64_to_bin(pubkey_64); + + if (pubkey_buffer == NULL) { + ssh_set_error(session, SSH_FATAL, + "Verifying that server is a known host: base64 error"); + return -1; + } + + rc = ssh_dh_get_current_server_publickey_blob(session, &pubkey_blob); + if (rc != 0) { + ssh_buffer_free(pubkey_buffer); + return -1; + } + + if (ssh_buffer_get_len(pubkey_buffer) != ssh_string_len(pubkey_blob)) { + ssh_string_free(pubkey_blob); + ssh_buffer_free(pubkey_buffer); + return 0; + } + + /* now test that they are identical */ + if (memcmp(ssh_buffer_get(pubkey_buffer), ssh_string_data(pubkey_blob), + ssh_buffer_get_len(pubkey_buffer)) != 0) { + ssh_string_free(pubkey_blob); + ssh_buffer_free(pubkey_buffer); + return 0; + } + + ssh_string_free(pubkey_blob); + ssh_buffer_free(pubkey_buffer); + return 1; +} + +/** + * @internal + * @brief Check if a hostname matches a openssh-style hashed known host. + * + * @param[in] host The host to check. + * + * @param[in] hashed The hashed value. + * + * @returns 1 if it matches, 0 otherwise. + */ +static int match_hashed_host(const char *host, const char *sourcehash) +{ + /* Openssh hash structure : + * |1|base64 encoded salt|base64 encoded hash + * hash is produced that way : + * hash := HMAC_SHA1(key=salt,data=host) + */ + unsigned char buffer[256] = {0}; + ssh_buffer salt = NULL; + ssh_buffer hash = NULL; + HMACCTX mac = NULL; + char *source = NULL; + char *b64hash = NULL; + int match, rc; + size_t size; + + if (strncmp(sourcehash, "|1|", 3) != 0) { + return 0; + } + + source = strdup(sourcehash + 3); + if (source == NULL) { + return 0; + } + + b64hash = strchr(source, '|'); + if (b64hash == NULL) { + /* Invalid hash */ + SAFE_FREE(source); + + return 0; + } + + *b64hash = '\0'; + b64hash++; + + salt = base64_to_bin(source); + if (salt == NULL) { + SAFE_FREE(source); + + return 0; + } + + hash = base64_to_bin(b64hash); + SAFE_FREE(source); + if (hash == NULL) { + ssh_buffer_free(salt); + + return 0; + } + + mac = hmac_init(ssh_buffer_get(salt), ssh_buffer_get_len(salt), SSH_HMAC_SHA1); + if (mac == NULL) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + size = sizeof(buffer); + rc = hmac_update(mac, host, strlen(host)); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + rc = hmac_final(mac, buffer, &size); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + + if (size == ssh_buffer_get_len(hash) && + memcmp(buffer, ssh_buffer_get(hash), size) == 0) { + match = 1; + } else { + match = 0; + } + + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + SSH_LOG(SSH_LOG_PACKET, + "Matching a hashed host: %s match=%d", host, match); + + return match; +} + +/* How it's working : + * 1- we open the known host file and bitch if it doesn't exist + * 2- we need to examine each line of the file, until going on state SSH_SERVER_KNOWN_OK: + * - there's a match. if the key is good, state is SSH_SERVER_KNOWN_OK, + * else it's SSH_SERVER_KNOWN_CHANGED (or SSH_SERVER_FOUND_OTHER) + * - there's no match : no change + */ + +/** + * @brief This function is deprecated + * + * @deprecated Please use ssh_session_is_known_server() + * @see ssh_session_is_known_server() + */ +int ssh_is_server_known(ssh_session session) +{ + FILE *file = NULL; + char *host = NULL; + char *hostport = NULL; + const char *type = NULL; + int match; + int i = 0; + char *files[3] = {0}; + + struct ssh_tokens_st *tokens = NULL; + + int ret = SSH_SERVER_NOT_KNOWN; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return SSH_SERVER_FILE_NOT_FOUND; + } + } + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't verify host in known hosts if the hostname isn't known"); + + return SSH_SERVER_ERROR; + } + + if (session->current_crypto == NULL){ + ssh_set_error(session, SSH_FATAL, + "ssh_is_host_known called without cryptographic context"); + + return SSH_SERVER_ERROR; + } + + host = ssh_lowercase(session->opts.host); + hostport = ssh_hostport(host, session->opts.port > 0 ? session->opts.port : 22); + if (host == NULL || hostport == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(host); + SAFE_FREE(hostport); + + return SSH_SERVER_ERROR; + } + + /* Set the list of known hosts files */ + i = 0; + if (session->opts.global_knownhosts != NULL){ + files[i++] = session->opts.global_knownhosts; + } + files[i++] = session->opts.knownhosts; + files[i] = NULL; + i = 0; + + do { + tokens = ssh_get_knownhost_line(&file, + files[i], + &type); + + /* End of file, return the current state or use next file */ + if (tokens == NULL) { + ++i; + if(files[i] == NULL) + break; + else + continue; + } + match = match_hashed_host(host, tokens->tokens[0]); + if (match == 0){ + match = match_hostname(hostport, tokens->tokens[0], + strlen(tokens->tokens[0])); + } + if (match == 0) { + match = match_hostname(host, tokens->tokens[0], + strlen(tokens->tokens[0])); + } + if (match == 0) { + match = match_hashed_host(hostport, tokens->tokens[0]); + } + if (match) { + ssh_key pubkey = ssh_dh_get_current_server_publickey(session); + const char *pubkey_type = ssh_key_type_to_char(ssh_key_type(pubkey)); + + /* We got a match. Now check the key type */ + if (strcmp(pubkey_type, type) != 0) { + SSH_LOG(SSH_LOG_PACKET, + "ssh_is_server_known: server type [%s] doesn't match the " + "type [%s] in known_hosts file", + pubkey_type, + type); + /* Different type. We don't override the known_changed error which is + * more important */ + if (ret != SSH_SERVER_KNOWN_CHANGED) + ret = SSH_SERVER_FOUND_OTHER; + ssh_tokens_free(tokens); + continue; + } + /* so we know the key type is good. We may get a good key or a bad key. */ + match = check_public_key(session, tokens->tokens); + ssh_tokens_free(tokens); + + if (match < 0) { + ret = SSH_SERVER_ERROR; + break; + } else if (match == 1) { + ret = SSH_SERVER_KNOWN_OK; + break; + } else if(match == 0) { + /* We override the status with the wrong key state */ + ret = SSH_SERVER_KNOWN_CHANGED; + } + } else { + ssh_tokens_free(tokens); + } + } while (1); + + if ((ret == SSH_SERVER_NOT_KNOWN) && + (session->opts.StrictHostKeyChecking == 0)) { + int rv = ssh_session_update_known_hosts(session); + if (rv != SSH_OK) { + ret = SSH_SERVER_ERROR; + } else { + ret = SSH_SERVER_KNOWN_OK; + } + } + + SAFE_FREE(host); + SAFE_FREE(hostport); + if (file != NULL) { + fclose(file); + } + + /* Return the current state at end of file */ + return ret; +} + +/** + * @deprecated Please use ssh_session_export_known_hosts_entry() + * @brief This function is deprecated. + */ +char *ssh_dump_knownhost(ssh_session session) +{ + ssh_key server_pubkey = NULL; + char *host = NULL; + char *hostport = NULL; + char *buffer = NULL; + char *b64_key = NULL; + int rc; + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't write host in known hosts if the hostname isn't known"); + return NULL; + } + + host = ssh_lowercase(session->opts.host); + /* If using a nonstandard port, save the host in the [host]:port format */ + if (session->opts.port > 0 && session->opts.port != 22) { + hostport = ssh_hostport(host, session->opts.port); + SAFE_FREE(host); + if (hostport == NULL) { + return NULL; + } + host = hostport; + hostport = NULL; + } + + if (session->current_crypto==NULL) { + ssh_set_error(session, SSH_FATAL, "No current crypto context"); + SAFE_FREE(host); + return NULL; + } + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL){ + ssh_set_error(session, SSH_FATAL, "No public key present"); + SAFE_FREE(host); + return NULL; + } + + buffer = calloc (1, MAX_LINE_SIZE); + if (!buffer) { + SAFE_FREE(host); + return NULL; + } + + rc = ssh_pki_export_pubkey_base64(server_pubkey, &b64_key); + if (rc < 0) { + SAFE_FREE(buffer); + SAFE_FREE(host); + return NULL; + } + + snprintf(buffer, MAX_LINE_SIZE, + "%s %s %s\n", + host, + server_pubkey->type_c, + b64_key); + + SAFE_FREE(host); + SAFE_FREE(b64_key); + + return buffer; +} + +/** + * @deprecated Please use ssh_session_update_known_hosts() + * @brief This function is deprecated + */ +int ssh_write_knownhost(ssh_session session) +{ + FILE *file = NULL; + char *buffer = NULL; + char *dir = NULL; + int rc; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file"); + return SSH_ERROR; + } + } + + errno = 0; + file = fopen(session->opts.knownhosts, "a"); + if (file == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + if (errno == ENOENT) { + dir = ssh_dirname(session->opts.knownhosts); + if (dir == NULL) { + ssh_set_error(session, SSH_FATAL, + "%s", ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + rc = ssh_mkdirs(dir, 0700); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Cannot create %s directory: %s", + dir, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + SAFE_FREE(dir); + return SSH_ERROR; + } + SAFE_FREE(dir); + + errno = 0; + file = fopen(session->opts.knownhosts, "a"); + if (file == NULL) { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s" + " for appending: %s", + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + } else { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s for appending: %s", + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + } + + rc = ssh_session_export_known_hosts_entry(session, &buffer); + if (rc != SSH_OK) { + fclose(file); + return SSH_ERROR; + } + + if (fwrite(buffer, strlen(buffer), 1, file) != 1 || ferror(file)) { + SAFE_FREE(buffer); + fclose(file); + return -1; + } + + SAFE_FREE(buffer); + fclose(file); + return 0; +} + +#define KNOWNHOSTS_MAXTYPES 10 + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/knownhosts.c b/src/libs/libssh-0.12.2/src/knownhosts.c new file mode 100644 index 000000000000..e495c4036791 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/knownhosts.c @@ -0,0 +1,1339 @@ +/* + * known_hosts: Host and public key verification. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009-2017 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/dh.h" +#include "libssh/session.h" +#include "libssh/options.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/libssh.h" +#include "libssh/dh.h" +#include "libssh/knownhosts.h" +#include "libssh/token.h" + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 8192 +#endif + +/** + * @addtogroup libssh_session + * + * @{ + */ + +static int hash_hostname(const char *name, + unsigned char *salt, + unsigned int salt_size, + unsigned char **hash, + size_t *hash_size) +{ + int rc; + HMACCTX mac_ctx = NULL; + + mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1); + if (mac_ctx == NULL) { + return SSH_ERROR; + } + + rc = hmac_update(mac_ctx, name, strlen(name)); + if (rc != 1) + return SSH_ERROR; + + rc = hmac_final(mac_ctx, *hash, hash_size); + if (rc != 1) + return SSH_ERROR; + + return SSH_OK; +} + +static int match_hashed_hostname(const char *host, const char *hashed_host) +{ + char *hashed = NULL; + char *b64_hash = NULL; + ssh_buffer salt = NULL; + ssh_buffer hash = NULL; + unsigned char hashed_buf[256] = {0}; + unsigned char *hashed_buf_ptr = hashed_buf; + size_t hashed_buf_size = sizeof(hashed_buf); + int cmp; + int rc; + int match = 0; + + cmp = strncmp(hashed_host, "|1|", 3); + if (cmp != 0) { + return 0; + } + + hashed = strdup(hashed_host + 3); + if (hashed == NULL) { + return 0; + } + + b64_hash = strchr(hashed, '|'); + if (b64_hash == NULL) { + goto error; + } + *b64_hash = '\0'; + b64_hash++; + + salt = base64_to_bin(hashed); + if (salt == NULL) { + goto error; + } + + hash = base64_to_bin(b64_hash); + if (hash == NULL) { + goto error; + } + + rc = hash_hostname(host, + ssh_buffer_get(salt), + ssh_buffer_get_len(salt), + &hashed_buf_ptr, + &hashed_buf_size); + if (rc != SSH_OK) { + goto error; + } + + if (hashed_buf_size != ssh_buffer_get_len(hash)) { + goto error; + } + + cmp = memcmp(hashed_buf, ssh_buffer_get(hash), hashed_buf_size); + if (cmp == 0) { + match = 1; + } + +error: + free(hashed); + SSH_BUFFER_FREE(salt); + SSH_BUFFER_FREE(hash); + + return match; +} + +/** + * @brief Free an allocated ssh_knownhosts_entry. + * + * Use SSH_KNOWNHOSTS_ENTRY_FREE() to set the pointer to NULL. + * + * @param[in] entry The entry to free. + */ +void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry) +{ + if (entry == NULL) { + return; + } + + SAFE_FREE(entry->hostname); + SAFE_FREE(entry->unparsed); + ssh_key_free(entry->publickey); + SAFE_FREE(entry->comment); + SAFE_FREE(entry); +} + +static int known_hosts_read_line(FILE *fp, + char *buf, + size_t buf_size, + size_t *buf_len, + size_t *lineno) +{ + while (fgets(buf, (int)buf_size, fp) != NULL) { + size_t len; + if (buf[0] == '\0') { + continue; + } + + *lineno += 1; + len = strlen(buf); + if (buf_len != NULL) { + *buf_len = len; + } + if (buf[len - 1] == '\n' || feof(fp)) { + return 0; + } else { + errno = E2BIG; + return -1; + } + } + + return -1; +} + +static int +ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry *k1, + struct ssh_knownhosts_entry *k2) +{ + int cmp; + + if (k1 == NULL || k2 == NULL) { + return 1; + } + + cmp = strcmp(k1->hostname, k2->hostname); + if (cmp != 0) { + return cmp; + } + + cmp = ssh_key_cmp(k1->publickey, k2->publickey, SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + return cmp; + } + + return 0; +} + +/** + * @internal + * + * @brief Read entries from filename to provided list + * + * This method reads the known_hosts file referenced by the path + * in filename argument, and entries matching the match argument + * will be added to the list in entries argument. + * If the entries list is NULL, it will allocate a new list. Caller + * is responsible to free it even if an error occurs. + * + * @param match[in] The host name (with port) to match against + * @param filename[in] The known hosts file to parse + * @param entries[in,out] The list of entries to append matching ones + * @return `SSH_OK` on missing file or success parsing, + * `SSH_ERROR` on error + */ +static int ssh_known_hosts_read_entries(const char *match, + const char *filename, + struct ssh_list **entries) +{ + char line[MAX_LINE_SIZE]; + size_t lineno = 0; + size_t len = 0; + FILE *fp = NULL; + int rc; + + fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + /* The missing file is not an error here */ + return SSH_OK; + } + + if (*entries == NULL) { + *entries = ssh_list_new(); + if (*entries == NULL) { + fclose(fp); + return SSH_ERROR; + } + } + + for (rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno); + rc == 0; + rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno)) { + struct ssh_knownhosts_entry *entry = NULL; + struct ssh_iterator *it = NULL; + char *p = NULL; + + if (line[len] != '\n') { + len = strcspn(line, "\n"); + } + line[len] = '\0'; + + /* Skip leading spaces */ + for (p = line; isspace((int)p[0]); p++); + + /* Skip comments and empty lines */ + if (p[0] == '\0' || p[0] == '#') { + continue; + } + + /* Skip lines starting with markers (@cert-authority, @revoked): + * we do not completely support them anyway */ + if (p[0] == '@') { + continue; + } + + rc = ssh_known_hosts_parse_line(match, + line, + &entry); + if (rc == SSH_AGAIN) { + continue; + } else if (rc != SSH_OK) { + goto error; + } + + /* Check for duplicates */ + for (it = ssh_list_get_iterator(*entries); + it != NULL; + it = it->next) { + struct ssh_knownhosts_entry *entry2 = NULL; + int cmp; + entry2 = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + cmp = ssh_known_hosts_entries_compare(entry, entry2); + if (cmp == 0) { + ssh_knownhosts_entry_free(entry); + entry = NULL; + break; + } + } + if (entry != NULL) { + rc = ssh_list_append(*entries, entry); + if (rc != SSH_OK) { + ssh_knownhosts_entry_free(entry); + goto error; + } + } + } + + fclose(fp); + return SSH_OK; +error: + fclose(fp); + return SSH_ERROR; +} + +static char *ssh_session_get_host_port(ssh_session session) +{ + char *host_port = NULL; + char *host = NULL; + + if (session->opts.host == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Can't verify server in known hosts if the host we " + "should connect to has not been set"); + + return NULL; + } + + host = ssh_lowercase(session->opts.host); + if (host == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (session->opts.port == 0 || session->opts.port == 22) { + host_port = host; + } else { + host_port = ssh_hostport(host, session->opts.port); + SAFE_FREE(host); + if (host_port == NULL) { + ssh_set_error_oom(session); + return NULL; + } + } + + return host_port; +} + +/** + * @internal + * + * @brief Free known hosts entries list + * + * @param[in] entry_list The list of ssh_knownhosts_entry items + */ +static void ssh_knownhosts_entries_free(struct ssh_list *entry_list) +{ + struct ssh_iterator *it = NULL; + + if (entry_list == NULL) { + return; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); +} +/** + * @internal + * + * @brief Check which host keys should be preferred for the session. + * + * This checks the known_hosts file to find out which algorithms should be + * preferred for the connection we are going to establish. + * + * @param[in] session The ssh session to use. + * + * @return A list of supported key types, NULL on error. + */ +struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) +{ + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + char *host_port = NULL; + size_t count; + struct ssh_list *list = NULL; + int list_error = 0; + int rc; + + if (session->opts.knownhosts == NULL || + session->opts.global_knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return NULL; + } + } + + list = ssh_list_new(); + if (list == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + goto error; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + goto error; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); + SAFE_FREE(host_port); + if (rc != 0) { + goto error; + } + + if (entry_list == NULL) { + goto error; + } + + count = ssh_list_count(entry_list); + if (count == 0) { + goto error; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_iterator *it2 = NULL; + struct ssh_knownhosts_entry *entry = NULL; + const char *algo = NULL; + bool present = false; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + algo = entry->publickey->type_c; + + /* Check for duplicates */ + for (it2 = ssh_list_get_iterator(list); + it2 != NULL; + it2 = it2->next) { + char *alg2 = ssh_iterator_value(char *, it2); + int cmp = strcmp(alg2, algo); + if (cmp == 0) { + present = true; + break; + } + } + + /* Add to the new list only if it is unique */ + if (!present) { + rc = ssh_list_append(list, algo); + if (rc != SSH_OK) { + list_error = 1; + } + } + + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); + if (list_error) { + goto error; + } + + return list; +error: + ssh_knownhosts_entries_free(entry_list); + ssh_list_free(list); + return NULL; +} + +/** + * @internal + * + * @brief Returns a static string containing a list of the signature types the + * given key type can generate. + * + * @returns A static cstring containing the signature types the key is able to + * generate separated by commas; NULL in case of error + */ +static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA: + return "rsa-sha2-512,rsa-sha2-256,ssh-rsa"; + case SSH_KEYTYPE_ED25519: + return "ssh-ed25519"; + case SSH_KEYTYPE_SK_ED25519: + return "sk-ssh-ed25519@openssh.com"; +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA_P256: + return "ecdsa-sha2-nistp256"; + case SSH_KEYTYPE_ECDSA_P384: + return "ecdsa-sha2-nistp384"; + case SSH_KEYTYPE_ECDSA_P521: + return "ecdsa-sha2-nistp521"; + case SSH_KEYTYPE_SK_ECDSA: + return "sk-ecdsa-sha2-nistp256@openssh.com"; +#else + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + SSH_LOG(SSH_LOG_WARN, "ECDSA keys are not supported by this build"); + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, + "The given type %d is not a base private key type " + "or is unsupported", + type); + } + + return NULL; +} + +/** + * @internal + * + * @brief Get the host keys algorithms identifiers from the known_hosts files + * + * This expands the signatures types that can be generated from the keys types + * present in the known_hosts files + * + * @param[in] session The ssh session to use. + * + * @return A newly allocated cstring containing a list of signature algorithms + * that can be generated by the host using the keys listed in the known_hosts + * files, NULL on error. + */ +char *ssh_known_hosts_get_algorithms_names(ssh_session session) +{ + char methods_buffer[256 + 1] = {0}; + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + char *host_port = NULL; + size_t count; + bool needcomma = false; + char *names = NULL; + + int rc; + + if (session->opts.knownhosts == NULL || + session->opts.global_knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return NULL; + } + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return NULL; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + ssh_knownhosts_entries_free(entry_list); + return NULL; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); + SAFE_FREE(host_port); + if (rc != 0) { + ssh_knownhosts_entries_free(entry_list); + return NULL; + } + + if (entry_list == NULL) { + return NULL; + } + + count = ssh_list_count(entry_list); + if (count == 0) { + ssh_list_free(entry_list); + return NULL; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) + { + struct ssh_knownhosts_entry *entry = NULL; + const char *algo = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type); + if (algo == NULL) { + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + continue; + } + + if (needcomma) { + strncat(methods_buffer, + ",", + sizeof(methods_buffer) - strlen(methods_buffer) - 1); + } + + strncat(methods_buffer, + algo, + sizeof(methods_buffer) - strlen(methods_buffer) - 1); + needcomma = true; + + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + + ssh_list_free(entry_list); + + names = ssh_remove_duplicates(methods_buffer); + + return names; +} + +/** + * @brief Parse a line from a known_hosts entry into a structure + * + * This parses a known_hosts entry into a structure with the key in a libssh + * consumeable form. You can use the PKI key function to further work with it. + * + * @param[in] hostname The hostname to match the line to + * + * @param[in] line The line to compare and parse if we have a hostname + * match. + * + * @param[in] entry A pointer to store the allocated known_hosts + * entry structure. The user needs to free the memory + * using SSH_KNOWNHOSTS_ENTRY_FREE(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_known_hosts_parse_line(const char *hostname, + const char *line, + struct ssh_knownhosts_entry **entry) +{ + struct ssh_knownhosts_entry *e = NULL; + char *known_host = NULL; + char *p = NULL; + const char *cp = NULL; + char *save_tok = NULL; + enum ssh_keytypes_e key_type; + int match = 0; + int rc = SSH_OK; + + known_host = strdup(line); + if (known_host == NULL) { + return SSH_ERROR; + } + + /* match pattern for hostname or hashed hostname */ + p = strtok_r(known_host, " ", &save_tok); + if (p == NULL ) { + free(known_host); + return SSH_ERROR; + } + + e = calloc(1, sizeof(struct ssh_knownhosts_entry)); + if (e == NULL) { + free(known_host); + return SSH_ERROR; + } + + if (hostname != NULL) { + char *host_port = NULL; + char *q = NULL; + + /* Hashed */ + if (p[0] == '|') { + match = match_hashed_hostname(hostname, p); + } + + save_tok = NULL; + + for (q = strtok_r(p, ",", &save_tok); + q != NULL; + q = strtok_r(NULL, ",", &save_tok)) { + int cmp; + + if (q[0] == '[' && hostname[0] != '[') { + /* Corner case: We have standard port so we do not have + * hostname in square braces. But the pattern is enclosed + * in braces with, possibly standard or wildcard, port. + * We need to test against [host]:port pair here. + */ + if (host_port == NULL) { + host_port = ssh_hostport(hostname, 22); + if (host_port == NULL) { + rc = SSH_ERROR; + goto out; + } + } + + cmp = match_hostname(host_port, q, strlen(q)); + } else { + cmp = match_hostname(hostname, q, strlen(q)); + } + if (cmp == 1) { + match = 1; + break; + } + } + free(host_port); + + if (match == 0) { + rc = SSH_AGAIN; + goto out; + } + + e->hostname = strdup(hostname); + if (e->hostname == NULL) { + rc = SSH_ERROR; + goto out; + } + } + + /* Restart parsing */ + SAFE_FREE(known_host); + known_host = strdup(line); + if (known_host == NULL) { + rc = SSH_ERROR; + goto out; + } + + save_tok = NULL; + + p = strtok_r(known_host, " ", &save_tok); + if (p == NULL ) { + rc = SSH_ERROR; + goto out; + } + + e->unparsed = strdup(p); + if (e->unparsed == NULL) { + rc = SSH_ERROR; + goto out; + } + + /* pubkey type */ + p = strtok_r(NULL, " ", &save_tok); + if (p == NULL) { + rc = SSH_ERROR; + goto out; + } + + key_type = ssh_key_type_from_name(p); + if (key_type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "key type '%s' unknown!", p); + rc = SSH_ERROR; + goto out; + } + + /* public key */ + p = strtok_r(NULL, " ", &save_tok); + if (p == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = ssh_pki_import_pubkey_base64(p, + key_type, + &e->publickey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to parse %s key for entry: %s!", + ssh_key_type_to_char(key_type), + e->unparsed); + goto out; + } + + /* comment */ + p = strtok_r(NULL, " ", &save_tok); + if (p != NULL) { + cp = strstr(line, p); + if (cp != NULL) { + e->comment = strdup(cp); + if (e->comment == NULL) { + rc = SSH_ERROR; + goto out; + } + } + } + + *entry = e; + SAFE_FREE(known_host); + + return SSH_OK; +out: + SAFE_FREE(known_host); + ssh_knownhosts_entry_free(e); + return rc; +} + +/** + * @brief Check if the set hostname and port match an entry in known_hosts. + * + * This check if the set hostname and port have an entry in the known_hosts file. + * You need to set at least the hostname using ssh_options_set(). + * + * @param[in] session The session with the values set to check. + * + * @return A ssh_known_hosts_e return value. + */ +enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) +{ + struct ssh_list *entry_list = NULL; + char *host_port = NULL; + bool global_known_hosts_found = false; + bool known_hosts_found = false; + int rc; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Cannot find a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + } + + if (session->opts.knownhosts == NULL && + session->opts.global_knownhosts == NULL) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "No path set for a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + + if (session->opts.knownhosts != NULL) { + known_hosts_found = ssh_file_readaccess_ok(session->opts.knownhosts); + if (!known_hosts_found) { + SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", + session->opts.knownhosts); + } + } + + if (session->opts.global_knownhosts != NULL) { + global_known_hosts_found = + ssh_file_readaccess_ok(session->opts.global_knownhosts); + if (!global_known_hosts_found) { + SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", + session->opts.global_knownhosts); + } + } + + if ((!known_hosts_found) && (!global_known_hosts_found)) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Cannot find a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return SSH_KNOWN_HOSTS_ERROR; + } + + if (known_hosts_found) { + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + ssh_knownhosts_entries_free(entry_list); + return SSH_KNOWN_HOSTS_ERROR; + } + } + + if (global_known_hosts_found) { + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + ssh_knownhosts_entries_free(entry_list); + return SSH_KNOWN_HOSTS_ERROR; + } + } + + SAFE_FREE(host_port); + + if (ssh_list_count(entry_list) == 0) { + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + ssh_knownhosts_entries_free(entry_list); + + return SSH_KNOWN_HOSTS_OK; +} + +/** + * @brief Export the current session information to a known_hosts string. + * + * This exports the current information of a session which is connected so a + * ssh server into an entry line which can be added to a known_hosts file. + * + * @param[in] session The session with information to export. + * + * @param[in] pentry_string A pointer to a string to store the allocated + * line of the entry. The user must free it using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_session_export_known_hosts_entry(ssh_session session, + char **pentry_string) +{ + ssh_key server_pubkey = NULL; + char *host = NULL; + char entry_buf[MAX_LINE_SIZE] = {0}; + char *b64_key = NULL; + int rc; + + if (pentry_string == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't create known_hosts entry - hostname unknown"); + return SSH_ERROR; + } + + host = ssh_session_get_host_port(session); + if (host == NULL) { + return SSH_ERROR; + } + + if (session->current_crypto == NULL) { + ssh_set_error(session, SSH_FATAL, + "No current crypto context, please connect first"); + SAFE_FREE(host); + return SSH_ERROR; + } + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL){ + ssh_set_error(session, SSH_FATAL, "No public key present"); + SAFE_FREE(host); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_base64(server_pubkey, &b64_key); + if (rc < 0) { + SAFE_FREE(host); + return SSH_ERROR; + } + + snprintf(entry_buf, sizeof(entry_buf), + "%s %s %s\n", + host, + server_pubkey->type_c, + b64_key); + + SAFE_FREE(host); + SAFE_FREE(b64_key); + + *pentry_string = strdup(entry_buf); + if (*pentry_string == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Adds the currently connected server to the user known_hosts file. + * + * This adds the currently connected server to the known_hosts file by + * appending a new line at the end. The global known_hosts file is considered + * read-only so it is not touched by this function. + * + * @param[in] session The session to use to write the entry. + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_session_update_known_hosts(ssh_session session) +{ + FILE *fp = NULL; + char *entry = NULL; + char *dir = NULL; + size_t nwritten; + size_t len; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + if (session->opts.knownhosts == NULL) { + rc = ssh_options_apply(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file"); + return SSH_ERROR; + } + } + + errno = 0; + fp = fopen(session->opts.knownhosts, "a"); + if (fp == NULL) { + if (errno == ENOENT) { + dir = ssh_dirname(session->opts.knownhosts); + if (dir == NULL) { + ssh_set_error(session, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + rc = ssh_mkdirs(dir, 0700); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Cannot create %s directory: %s", + dir, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + SAFE_FREE(dir); + return SSH_ERROR; + } + SAFE_FREE(dir); + + errno = 0; + fp = fopen(session->opts.knownhosts, "a"); + if (fp == NULL) { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s" + " for appending: %s", + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + } else { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s for appending: %s", + session->opts.knownhosts, strerror(errno)); + return SSH_ERROR; + } + } + + rc = ssh_session_export_known_hosts_entry(session, &entry); + if (rc != SSH_OK) { + fclose(fp); + return rc; + } + + len = strlen(entry); + nwritten = fwrite(entry, sizeof(char), len, fp); + SAFE_FREE(entry); + if (nwritten != len || ferror(fp)) { + ssh_set_error(session, SSH_FATAL, + "Couldn't append to known_hosts file %s: %s", + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + fclose(fp); + return SSH_ERROR; + } + + fclose(fp); + return SSH_OK; +} + +static enum ssh_known_hosts_e +ssh_known_hosts_check_server_key(const char *hosts_entry, + const char *filename, + ssh_key server_key, + struct ssh_knownhosts_entry **pentry) +{ + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; + int rc; + + rc = ssh_known_hosts_read_entries(hosts_entry, + filename, + &entry_list); + if (rc != 0) { + ssh_knownhosts_entries_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + it = ssh_list_get_iterator(entry_list); + if (it == NULL) { + ssh_knownhosts_entries_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + for (;it != NULL; it = it->next) { + struct ssh_knownhosts_entry *entry = NULL; + int cmp; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + + cmp = ssh_key_cmp(server_key, entry->publickey, SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found = SSH_KNOWN_HOSTS_OK; + if (pentry != NULL) { + *pentry = entry; + ssh_list_remove(entry_list, it); + } + break; + } + + if (ssh_key_type(server_key) == ssh_key_type(entry->publickey)) { + found = SSH_KNOWN_HOSTS_CHANGED; + continue; + } + + if (found != SSH_KNOWN_HOSTS_CHANGED) { + found = SSH_KNOWN_HOSTS_OTHER; + } + } + + ssh_knownhosts_entries_free(entry_list); + + return found; +} + +/** + * @brief Get the known_hosts entry for the currently connected session. + * + * @param[in] session The session to validate. + * + * @param[in] pentry A pointer to store the allocated known hosts entry. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * Note: When GSSAPI key exchange is used, + * host keys may change frequently and + * without advance warning per RFC 4462. + * Clients SHOULD NOT issue strong warnings + * or abort when this occurs with GSSAPI + * key exchange.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct. + * This is also returned when GSSAPI key + * exchange with null hostkey is negotiated, + * as there is no host key to verify, or when + * the server does not send the host key despite + * negotiating a non-null host key algorithm.\n + * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The + * host is thus unknown. File will be + * created if host key is accepted.\n + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. + * + * @see ssh_knownhosts_entry_free() + */ +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry) +{ + enum ssh_known_hosts_e old_rv, rv = SSH_KNOWN_HOSTS_UNKNOWN; + + if (pentry != NULL) { + *pentry = NULL; + } + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + } + + rv = ssh_session_get_known_hosts_entry_file(session, + session->opts.knownhosts, + pentry); + if (rv == SSH_KNOWN_HOSTS_OK) { + /* We already found a match in the first file: return */ + return rv; + } + + old_rv = rv; + rv = ssh_session_get_known_hosts_entry_file(session, + session->opts.global_knownhosts, + pentry); + + /* If we did not find any match at all: we report the previous result */ + if (rv == SSH_KNOWN_HOSTS_UNKNOWN) { + if (session->opts.StrictHostKeyChecking == 0) { + return SSH_KNOWN_HOSTS_OK; + } + return old_rv; + } + + /* We found some match: return it */ + return rv; + +} + +/** + * @internal + * + * @brief Get the known_hosts entry for the current connected session + * from the given known_hosts file. + * + * @param[in] session The session to validate. + * + * @param[in] filename The filename to parse. + * + * @param[in] pentry A pointer to store the allocated known hosts entry. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct.\n + * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The + * host is thus unknown. File will be + * created if host key is accepted.\n + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. + * + * @see ssh_knownhosts_entry_free() + */ +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry_file(ssh_session session, + const char *filename, + struct ssh_knownhosts_entry **pentry) +{ + ssh_key server_pubkey = NULL; + char *host_port = NULL; + enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL) { +#ifdef WITH_GSSAPI + /* After GSSAPI key exchange, sending the host key is optional + * because the server is already authenticated via Kerberos. + * Return SSH_KNOWN_HOSTS_UNKNOWN if there is no host key + * to let the application decide how to handle this case. */ + if (ssh_session_kex_is_gss(session)) { + return SSH_KNOWN_HOSTS_UNKNOWN; + } +#endif + ssh_set_error(session, + SSH_FATAL, + "ssh_session_is_known_host called without a " + "server_key!"); + + return SSH_KNOWN_HOSTS_ERROR; + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return SSH_KNOWN_HOSTS_ERROR; + } + + found = ssh_known_hosts_check_server_key(host_port, + filename, + server_pubkey, + pentry); + SAFE_FREE(host_port); + + return found; +} + +/** + * @brief Check if the servers public key for the connected session is known. + * + * This checks if we already know the public key of the server we want to + * connect to. This allows to detect if there is a MITM attach going on + * of if there have been changes on the server we don't know about. + * + * @param[in] session The SSH to validate. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct.\n + * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The + * host is thus unknown. File will be + * created if host key is accepted.\n + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. + */ +enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session) +{ + return ssh_session_get_known_hosts_entry(session, NULL); +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/legacy.c b/src/libs/libssh-0.12.2/src/legacy.c new file mode 100644 index 000000000000..5fad635f9fdb --- /dev/null +++ b/src/libs/libssh-0.12.2/src/legacy.c @@ -0,0 +1,790 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** functions in that file are wrappers to the newly named functions. All + * of them are depreciated, but these wrappers will avoid breaking backward + * compatibility + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "libssh/pki_priv.h" +#include +#include +#include "libssh/options.h" + +/* AUTH FUNCTIONS */ +int ssh_auth_list(ssh_session session) { + return ssh_userauth_list(session, NULL); +} + +int ssh_userauth_offer_pubkey(ssh_session session, const char *username, + int type, ssh_string publickey) +{ + ssh_key key = NULL; + int rc; + + (void) type; /* unused */ + + rc = ssh_pki_import_pubkey_blob(publickey, &key); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Failed to convert public key"); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_try_publickey(session, username, key); + ssh_key_free(key); + + return rc; +} + +int ssh_userauth_pubkey(ssh_session session, + const char *username, + ssh_string publickey, + ssh_private_key privatekey) +{ + ssh_key key = NULL; + int rc; + + (void) publickey; /* unused */ + + key = ssh_key_new(); + if (key == NULL) { + return SSH_AUTH_ERROR; + } + + key->type = privatekey->type; + key->type_c = ssh_key_type_to_char(key->type); + key->flags = SSH_KEY_FLAG_PRIVATE|SSH_KEY_FLAG_PUBLIC; +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = privatekey->rsa_priv; +#elif defined(HAVE_LIBCRYPTO) + key->key = privatekey->key_priv; +#else + key->rsa = privatekey->rsa_priv; +#endif /* HAVE_LIBCRYPTO */ + + rc = ssh_userauth_publickey(session, username, key); +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + key->key = NULL; +#else + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + ssh_key_free(key); + + return rc; +} + +int ssh_userauth_autopubkey(ssh_session session, const char *passphrase) { + return ssh_userauth_publickey_auto(session, NULL, passphrase); +} + +int ssh_userauth_privatekey_file(ssh_session session, + const char *username, + const char *filename, + const char *passphrase) { + char *pubkeyfile = NULL; + ssh_string pubkey = NULL; + ssh_private_key privkey = NULL; + int type = 0; + int rc = SSH_AUTH_ERROR; + size_t klen = strlen(filename) + 4 + 1; + + pubkeyfile = malloc(klen); + if (pubkeyfile == NULL) { + ssh_set_error_oom(session); + + return SSH_AUTH_ERROR; + } + snprintf(pubkeyfile, klen, "%s.pub", filename); + + pubkey = publickey_from_file(session, pubkeyfile, &type); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_RARE, "Public key file %s not found. Trying to generate it.", pubkeyfile); + /* auto-detect the key type with type=0 */ + privkey = privatekey_from_file(session, filename, 0, passphrase); + } else { + SSH_LOG(SSH_LOG_RARE, "Public key file %s loaded.", pubkeyfile); + privkey = privatekey_from_file(session, filename, type, passphrase); + } + if (privkey == NULL) { + goto error; + } + /* ssh_userauth_pubkey is responsible for taking care of null-pubkey */ + rc = ssh_userauth_pubkey(session, username, pubkey, privkey); + privatekey_free(privkey); + +error: + SAFE_FREE(pubkeyfile); + ssh_string_free(pubkey); + + return rc; +} + +/* BUFFER FUNCTIONS */ + +void buffer_free(ssh_buffer buffer){ + ssh_buffer_free(buffer); +} +void *buffer_get(ssh_buffer buffer){ + return ssh_buffer_get(buffer); +} +uint32_t buffer_get_len(ssh_buffer buffer){ + return ssh_buffer_get_len(buffer); +} +ssh_buffer buffer_new(void){ + return ssh_buffer_new(); +} + +ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms){ + return ssh_channel_accept_x11(channel, timeout_ms); +} + +int channel_change_pty_size(ssh_channel channel,int cols,int rows){ + return ssh_channel_change_pty_size(channel,cols,rows); +} + +ssh_channel channel_forward_accept(ssh_session session, int timeout_ms){ + return ssh_channel_open_forward_port(session, timeout_ms, NULL, NULL, NULL); +} + +int channel_close(ssh_channel channel){ + return ssh_channel_close(channel); +} + +int channel_forward_cancel(ssh_session session, const char *address, int port){ + return ssh_channel_cancel_forward(session, address, port); +} + +int channel_forward_listen(ssh_session session, const char *address, + int port, int *bound_port){ + return ssh_channel_listen_forward(session, address, port, bound_port); +} + +void channel_free(ssh_channel channel){ + ssh_channel_free(channel); +} + +int channel_get_exit_status(ssh_channel channel){ + return ssh_channel_get_exit_status(channel); +} + +ssh_session channel_get_session(ssh_channel channel){ + return ssh_channel_get_session(channel); +} + +int channel_is_closed(ssh_channel channel){ + return ssh_channel_is_closed(channel); +} + +int channel_is_eof(ssh_channel channel){ + return ssh_channel_is_eof(channel); +} + +int channel_is_open(ssh_channel channel){ + return ssh_channel_is_open(channel); +} + +ssh_channel channel_new(ssh_session session){ + return ssh_channel_new(session); +} + +int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport){ + return ssh_channel_open_forward(channel, remotehost, remoteport, + sourcehost,localport); +} + +int channel_open_session(ssh_channel channel){ + return ssh_channel_open_session(channel); +} + +int channel_poll(ssh_channel channel, int is_stderr){ + return ssh_channel_poll(channel, is_stderr); +} + +int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr){ + return ssh_channel_read(channel, dest, count, is_stderr); +} + +/* + * This function will completely be depreciated. The old implementation was not + * renamed. + * int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + * int is_stderr); + */ + +int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr){ + return ssh_channel_read_nonblocking(channel, dest, count, is_stderr); +} + +int channel_request_env(ssh_channel channel, const char *name, const char *value){ + return ssh_channel_request_env(channel, name, value); +} + +int channel_request_exec(ssh_channel channel, const char *cmd){ + return ssh_channel_request_exec(channel, cmd); +} + +int channel_request_pty(ssh_channel channel){ + return ssh_channel_request_pty(channel); +} + +int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows){ + return ssh_channel_request_pty_size(channel, term, cols, rows); +} + +int channel_request_shell(ssh_channel channel){ + return ssh_channel_request_shell(channel); +} + +int channel_request_send_signal(ssh_channel channel, const char *signum){ + return ssh_channel_request_send_signal(channel, signum); +} + +int channel_request_sftp(ssh_channel channel){ + return ssh_channel_request_sftp(channel); +} + +int channel_request_subsystem(ssh_channel channel, const char *subsystem){ + return ssh_channel_request_subsystem(channel, subsystem); +} + +int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number){ + return ssh_channel_request_x11(channel, single_connection, protocol, cookie, + screen_number); +} + +int channel_send_eof(ssh_channel channel){ + return ssh_channel_send_eof(channel); +} + +int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout){ + return ssh_channel_select(readchans, writechans, exceptchans, timeout); +} + +void channel_set_blocking(ssh_channel channel, int blocking){ + ssh_channel_set_blocking(channel, blocking); +} + +int channel_write(ssh_channel channel, const void *data, uint32_t len){ + return ssh_channel_write(channel, data, len); +} + +/* + * These functions have to be wrapped around the pki.c functions. + +void privatekey_free(ssh_private_key prv); +ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +ssh_string publickey_to_string(ssh_public_key key); + * + */ + +void string_burn(ssh_string str){ + ssh_string_burn(str); +} + +ssh_string string_copy(ssh_string str){ + return ssh_string_copy(str); +} + +void *string_data(ssh_string str){ + return ssh_string_data(str); +} + +int string_fill(ssh_string str, const void *data, size_t len){ + return ssh_string_fill(str,data,len); +} + +void string_free(ssh_string str){ + ssh_string_free(str); +} + +ssh_string string_from_char(const char *what){ + return ssh_string_from_char(what); +} + +size_t string_len(ssh_string str){ + return ssh_string_len(str); +} + +ssh_string string_new(size_t size){ + return ssh_string_new(size); +} + +char *string_to_char(ssh_string str){ + return ssh_string_to_char(str); +} + +/* OLD PKI FUNCTIONS */ + +void publickey_free(ssh_public_key key) { + if (key == NULL) { + return; + } + + switch(key->type) { + case SSH_KEYTYPE_RSA: +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(key->rsa_pub); +#elif defined HAVE_LIBCRYPTO + EVP_PKEY_free(key->key_pub); +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_pk_free(key->rsa_pub); + SAFE_FREE(key->rsa_pub); +#endif /* HAVE_LIBGCRYPT */ + break; + default: + break; + } + SAFE_FREE(key); +} + +ssh_public_key publickey_from_privatekey(ssh_private_key prv) +{ + struct ssh_public_key_struct *p = NULL; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + + privkey = ssh_key_new(); + if (privkey == NULL) { + return NULL; + } + + privkey->type = prv->type; + privkey->type_c = ssh_key_type_to_char(privkey->type); + privkey->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->pk = prv->rsa_priv; +#elif defined(HAVE_LIBCRYPTO) + privkey->key = prv->key_priv; +#else + privkey->rsa = prv->rsa_priv; +#endif /* HAVE_LIBCRYPTO */ + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + privkey->key = NULL; +#else + privkey->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + ssh_key_free(privkey); + if (rc < 0) { + return NULL; + } + + p = ssh_pki_convert_key_to_publickey(pubkey); + ssh_key_free(pubkey); + + return p; +} + +ssh_private_key privatekey_from_file(ssh_session session, + const char *filename, + int type, + const char *passphrase) { + ssh_auth_callback auth_fn = NULL; + void *auth_data = NULL; + ssh_private_key privkey = NULL; + ssh_key key = NULL; + int rc; + + (void) type; /* unused */ + + if (session->common.callbacks) { + auth_fn = session->common.callbacks->auth_function; + auth_data = session->common.callbacks->userdata; + } + + + rc = ssh_pki_import_privkey_file(filename, + passphrase, + auth_fn, + auth_data, + &key); + if (rc != SSH_OK) { + return NULL; + } + + privkey = malloc(sizeof(struct ssh_private_key_struct)); + if (privkey == NULL) { + ssh_key_free(key); + return NULL; + } + + privkey->type = key->type; +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->rsa_priv = key->pk; + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + privkey->key_priv = key->key; + key->key = NULL; +#else + privkey->rsa_priv = key->rsa; + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + + ssh_key_free(key); + + return privkey; +} + +enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey){ + if (privatekey==NULL) + return SSH_KEYTYPE_UNKNOWN; + return privatekey->type; +} + +void privatekey_free(ssh_private_key prv) { + if (prv == NULL) { + return; + } + +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(prv->rsa_priv); +#elif defined HAVE_LIBCRYPTO + EVP_PKEY_free(prv->key_priv); +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_pk_free(prv->rsa_priv); + SAFE_FREE(prv->rsa_priv); +#endif /* HAVE_LIBGCRYPT */ + memset(prv, 0, sizeof(struct ssh_private_key_struct)); + SAFE_FREE(prv); +} + +ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type) { + ssh_key key = NULL; + ssh_string key_str = NULL; + int rc; + + (void) session; /* unused */ + + rc = ssh_pki_import_pubkey_file(filename, &key); + if (rc < 0) { + return NULL; + } + + rc = ssh_pki_export_pubkey_blob(key, &key_str); + if (rc < 0) { + ssh_key_free(key); + return NULL; + } + + if (type) { + *type = key->type; + } + ssh_key_free(key); + + return key_str; +} + +const char *ssh_type_to_char(int type) { + return ssh_key_type_to_char(type); +} + +int ssh_type_from_name(const char *name) { + return ssh_key_type_from_name(name); +} + +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) +{ + struct ssh_public_key_struct *pubkey = NULL; + ssh_key key = NULL; + int rc; + + (void) session; /* unused */ + + rc = ssh_pki_import_pubkey_blob(pubkey_s, &key); + if (rc < 0) { + return NULL; + } + + pubkey = malloc(sizeof(struct ssh_public_key_struct)); + if (pubkey == NULL) { + ssh_key_free(key); + return NULL; + } + + pubkey->type = key->type; + pubkey->type_c = key->type_c; + +#if defined(HAVE_LIBMBEDCRYPTO) + pubkey->rsa_pub = key->pk; + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + pubkey->key_pub = key->key; + key->key = NULL; +#else + pubkey->rsa_pub = key->rsa; + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + + ssh_key_free(key); + + return pubkey; +} + +ssh_string publickey_to_string(ssh_public_key pubkey) +{ + ssh_key key = NULL; + ssh_string key_blob = NULL; + int rc; + + if (pubkey == NULL) { + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + return NULL; + } + + key->type = pubkey->type; + key->type_c = pubkey->type_c; + +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = pubkey->rsa_pub; +#elif defined(HAVE_LIBCRYPTO) + key->key = pubkey->key_pub; +#else + key->rsa = pubkey->rsa_pub; +#endif /* HAVE_LIBCRYPTO */ + + rc = ssh_pki_export_pubkey_blob(key, &key_blob); + if (rc < 0) { + key_blob = NULL; + } + +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + key->key = NULL; +#else + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + ssh_key_free(key); + + return key_blob; +} + +int ssh_publickey_to_file(ssh_session session, + const char *file, + ssh_string pubkey, + int type) +{ + FILE *fp = NULL; + char *user = NULL; + char buffer[1024]; + char *host = NULL; + unsigned char *pubkey_64 = NULL; + size_t len; + + if(session==NULL) + return SSH_ERROR; + if(file==NULL || pubkey==NULL){ + ssh_set_error(session, SSH_FATAL, "Invalid parameters"); + return SSH_ERROR; + } + pubkey_64 = bin_to_base64(ssh_string_data(pubkey), ssh_string_len(pubkey)); + if (pubkey_64 == NULL) { + return SSH_ERROR; + } + + user = ssh_get_local_username(); + if (user == NULL) { + SAFE_FREE(pubkey_64); + return SSH_ERROR; + } + + host = ssh_get_local_hostname(); + if (host == NULL) { + SAFE_FREE(user); + SAFE_FREE(pubkey_64); + return SSH_ERROR; + } + + snprintf(buffer, sizeof(buffer), "%s %s %s@%s\n", + ssh_type_to_char(type), + pubkey_64, + user, + host); + + SAFE_FREE(pubkey_64); + SAFE_FREE(user); + SAFE_FREE(host); + + SSH_LOG(SSH_LOG_RARE, "Trying to write public key file: %s", file); + SSH_LOG(SSH_LOG_PACKET, "public key file content: %s", buffer); + + fp = fopen(file, "w+"); + if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Error opening %s: %s", + file, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + len = strlen(buffer); + if (fwrite(buffer, len, 1, fp) != 1 || ferror(fp)) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Unable to write to %s", file); + fclose(fp); + unlink(file); + return SSH_ERROR; + } + + fclose(fp); + return SSH_OK; +} + +int ssh_try_publickey_from_file(ssh_session session, + const char *keyfile, + ssh_string *publickey, + int *type) { + char *pubkey_file = NULL; + size_t len; + ssh_string pubkey_string = NULL; + int pubkey_type; + + if (session == NULL || keyfile == NULL || publickey == NULL || type == NULL) { + return -1; + } + + if (session->opts.sshdir == NULL) { + if (ssh_options_apply(session) < 0) { + return -1; + } + } + + SSH_LOG(SSH_LOG_PACKET, "Trying to open privatekey %s", keyfile); + if (!ssh_file_readaccess_ok(keyfile)) { + SSH_LOG(SSH_LOG_PACKET, "Failed to open privatekey %s", keyfile); + return -1; + } + + len = strlen(keyfile) + 5; + pubkey_file = malloc(len); + if (pubkey_file == NULL) { + return -1; + } + snprintf(pubkey_file, len, "%s.pub", keyfile); + + SSH_LOG(SSH_LOG_PACKET, "Trying to open publickey %s", + pubkey_file); + if (!ssh_file_readaccess_ok(pubkey_file)) { + SSH_LOG(SSH_LOG_PACKET, "Failed to open publickey %s", + pubkey_file); + SAFE_FREE(pubkey_file); + return 1; + } + + SSH_LOG(SSH_LOG_PACKET, "Success opening public and private key"); + + /* + * We are sure both the private and public key file is readable. We return + * the public as a string, and the private filename as an argument + */ + pubkey_string = publickey_from_file(session, pubkey_file, &pubkey_type); + if (pubkey_string == NULL) { + SSH_LOG(SSH_LOG_PACKET, + "Wasn't able to open public key file %s: %s", + pubkey_file, + ssh_get_error(session)); + SAFE_FREE(pubkey_file); + return -1; + } + + SAFE_FREE(pubkey_file); + + *publickey = pubkey_string; + *type = pubkey_type; + + return 0; +} + +ssh_string ssh_get_pubkey(ssh_session session) +{ + ssh_string pubkey_blob = NULL; + int rc; + + if (session == NULL || + session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + return NULL; + } + + rc = ssh_dh_get_current_server_publickey_blob(session, + &pubkey_blob); + if (rc != 0) { + return NULL; + } + + return pubkey_blob; +} + +/**************************************************************************** + * SERVER SUPPORT + ****************************************************************************/ + +#ifdef WITH_SERVER +int ssh_accept(ssh_session session) { + return ssh_handle_key_exchange(session); +} + +int channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { + return ssh_channel_write_stderr(channel, data, len); +} + +/** @deprecated + * @brief Interface previously exported by error. + */ +ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype){ + (void) packettype; + ssh_set_error(session, SSH_FATAL, "ssh_message_retrieve: obsolete libssh call"); + return NULL; +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/src/libcrypto-compat.h b/src/libs/libssh-0.12.2/src/libcrypto-compat.h new file mode 100644 index 000000000000..0f2dc1847de7 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libcrypto-compat.h @@ -0,0 +1,14 @@ +#ifndef LIBCRYPTO_COMPAT_H +#define LIBCRYPTO_COMPAT_H + +#include + +#define NISTP256 "P-256" +#define NISTP384 "P-384" +#define NISTP521 "P-521" + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#define EVP_PKEY_eq EVP_PKEY_cmp +#endif /* OPENSSL_VERSION_NUMBER */ + +#endif /* LIBCRYPTO_COMPAT_H */ diff --git a/src/libs/libssh-0.12.2/src/libcrypto.c b/src/libs/libssh-0.12.2/src/libcrypto.c new file mode 100644 index 000000000000..849b539cbb16 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libcrypto.c @@ -0,0 +1,1668 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/libcrypto.h" +#include "libssh/pki.h" +#ifdef HAVE_OPENSSL_EVP_CHACHA20 +#include "libssh/bytearray.h" +#include "libssh/chacha20-poly1305-common.h" +#endif + +#ifdef HAVE_LIBCRYPTO +#ifdef LIBRESSL_VERSION_NUMBER +#include +#endif +#include +#include +#include +#include +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#include +#include +#else +#include +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ +#include +#if defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER) +#include +#endif + +#include "libcrypto-compat.h" + +#ifdef HAVE_OPENSSL_AES_H +#define HAS_AES +#include +#endif /* HAVE_OPENSSL_AES_H */ +#ifdef HAVE_OPENSSL_DES_H +#define HAS_DES +#include +#endif /* HAVE_OPENSSL_DES_H */ + +#if (defined(HAVE_VALGRIND_VALGRIND_H) && defined(HAVE_OPENSSL_IA32CAP_LOC)) +#include +#define CAN_DISABLE_AESNI +#endif + +#include "libssh/crypto.h" + +#ifdef HAVE_OPENSSL_EVP_KDF_CTX +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ + +#include "libssh/crypto.h" + +static int libcrypto_initialized = 0; + + +void ssh_reseed(void){ +#ifndef _WIN32 + struct timeval tv; + gettimeofday(&tv, NULL); + RAND_add(&tv, sizeof(tv), 0.0); +#endif +} + +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) +static OSSL_PROVIDER *provider = NULL; +static bool pkcs11_provider_failed = false; + +int pki_load_pkcs11_provider(void) +{ + if (OSSL_PROVIDER_available(NULL, "pkcs11") == 1) { + /* the provider is already available. + * Loaded through a configuration file? */ + return SSH_OK; + } + + if (pkcs11_provider_failed) { + /* the loading failed previously -- do not retry */ + return SSH_ERROR; + } + + provider = OSSL_PROVIDER_try_load(NULL, "pkcs11", 1); + if (provider != NULL) { + return SSH_OK; + } + + SSH_LOG(SSH_LOG_TRACE, + "Failed to load the pkcs11 provider: %s", + ERR_error_string(ERR_get_error(), NULL)); + /* Do not attempt to load it again */ + pkcs11_provider_failed = true; + return SSH_ERROR; +} +#else +static ENGINE *engine = NULL; + +ENGINE *pki_get_engine(void) +{ + int ok; + + if (engine == NULL) { + ENGINE_load_builtin_engines(); + + engine = ENGINE_by_id("pkcs11"); + if (engine == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Could not load the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + SSH_LOG(SSH_LOG_DEBUG, "Engine loaded successfully"); + + ok = ENGINE_init(engine); + if (!ok) { + SSH_LOG(SSH_LOG_TRACE, + "Could not initialize the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); + ENGINE_free(engine); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, "Engine init success"); + } + return engine; +} +#endif /* defined(WITH_PKCS11_PROVIDER) */ +#endif /* defined(WITH_PKCS11_URI) */ + +#ifdef HAVE_OPENSSL_EVP_KDF_CTX +#if OPENSSL_VERSION_NUMBER < 0x30000000L +static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) +{ + switch (digest_type) { + case SSH_KDF_SHA1: + return EVP_sha1(); + case SSH_KDF_SHA256: + return EVP_sha256(); + case SSH_KDF_SHA384: + return EVP_sha384(); + case SSH_KDF_SHA512: + return EVP_sha512(); + } + return NULL; +} +#else +static const char *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) +{ + switch (digest_type) { + case SSH_KDF_SHA1: + return SN_sha1; + case SSH_KDF_SHA256: + return SN_sha256; + case SSH_KDF_SHA384: + return SN_sha384; + case SSH_KDF_SHA512: + return SN_sha512; + } + return NULL; +} +#endif /* OPENSSL_VERSION_NUMBER */ + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len) +{ + int ret = SSH_ERROR, rv; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EVP_KDF_CTX *ctx = EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF); +#else + EVP_KDF_CTX *ctx = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + OSSL_PARAM *params = NULL; + const char *md = NULL; + EVP_KDF *kdf = NULL; + + md = sshkdf_digest_to_md(crypto->digest_type); + if (md == NULL) { + return -1; + } + + kdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); + if (kdf == NULL) { + return -1; + } + ctx = EVP_KDF_CTX_new(kdf); + EVP_KDF_free(kdf); + + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + EVP_KDF_CTX_free(ctx); + return -1; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + if (ctx == NULL) { + goto out; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + rv = EVP_KDF_ctrl(ctx, + EVP_KDF_CTRL_SET_MD, + sshkdf_digest_to_md(crypto->digest_type)); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_KEY, key, key_len); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_ctrl(ctx, + EVP_KDF_CTRL_SET_SSHKDF_XCGHASH, + crypto->secret_hash, + crypto->digest_len); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_TYPE, key_type); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_ctrl(ctx, + EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID, + crypto->session_id, + crypto->session_id_len); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_derive(ctx, output, requested_len); + if (rv != 1) { + goto out; + } +#else + rv = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_KDF_PARAM_DIGEST, + md, + strlen(md)); + if (rv != 1) { + goto out; + } + rv = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_KEY, + key, + key_len); + if (rv != 1) { + goto out; + } + rv = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_XCGHASH, + crypto->secret_hash, + crypto->digest_len); + if (rv != 1) { + goto out; + } + rv = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_SESSION_ID, + crypto->session_id, + crypto->session_id_len); + if (rv != 1) { + goto out; + } + rv = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_TYPE, + (const char *)&key_type, + 1); + if (rv != 1) { + goto out; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + goto out; + } + + rv = EVP_KDF_derive(ctx, output, requested_len, params); + if (rv != 1) { + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + ret = SSH_OK; + +out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD_free(param_bld); + OSSL_PARAM_free(params); +#endif + EVP_KDF_CTX_free(ctx); + if (ret < 0) { + return ret; + } + return 0; +} + +#else +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len) +{ + return sshkdf_derive_key(crypto, key, key_len, + key_type, output, requested_len); +} +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ + +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) +{ + HMACCTX ctx = NULL; + EVP_PKEY *pkey = NULL; + int rc = -1; + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + return NULL; + } + + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, (int)len); + if (pkey == NULL) { + goto error; + } + + switch (type) { + case SSH_HMAC_SHA1: + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha1(), NULL, pkey); + break; + case SSH_HMAC_SHA256: + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey); + break; + case SSH_HMAC_SHA512: + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha512(), NULL, pkey); + break; + case SSH_HMAC_MD5: + rc = EVP_DigestSignInit(ctx, NULL, EVP_md5(), NULL, pkey); + break; + default: + rc = -1; + break; + } + + EVP_PKEY_free(pkey); + if (rc != 1) { + goto error; + } + return ctx; + +error: + EVP_MD_CTX_free(ctx); + return NULL; +} + +int hmac_update(HMACCTX ctx, const void *data, size_t len) +{ + return EVP_DigestSignUpdate(ctx, data, len); +} + +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *len) +{ + size_t res = *len; + int rc; + rc = EVP_DigestSignFinal(ctx, hashmacbuf, &res); + EVP_MD_CTX_free(ctx); + if (rc == 1) { + *len = res; + } + + return rc; +} + +static void evp_cipher_init(struct ssh_cipher_struct *cipher) +{ + if (cipher->ctx == NULL) { + cipher->ctx = EVP_CIPHER_CTX_new(); + } else { + EVP_CIPHER_CTX_reset(cipher->ctx); + } + + switch(cipher->ciphertype){ + case SSH_AES128_CBC: + cipher->cipher = EVP_aes_128_cbc(); + break; + case SSH_AES192_CBC: + cipher->cipher = EVP_aes_192_cbc(); + break; + case SSH_AES256_CBC: + cipher->cipher = EVP_aes_256_cbc(); + break; + case SSH_AES128_CTR: + cipher->cipher = EVP_aes_128_ctr(); + break; + case SSH_AES192_CTR: + cipher->cipher = EVP_aes_192_ctr(); + break; + case SSH_AES256_CTR: + cipher->cipher = EVP_aes_256_ctr(); + break; + case SSH_AEAD_AES128_GCM: + cipher->cipher = EVP_aes_128_gcm(); + break; + case SSH_AEAD_AES256_GCM: + cipher->cipher = EVP_aes_256_gcm(); + break; + case SSH_3DES_CBC: + SSH_LOG(SSH_LOG_WARNING, "The DES cipher cannot be handled here"); + break; +#ifdef HAVE_BLOWFISH + case SSH_BLOWFISH_CBC: + cipher->cipher = EVP_bf_cbc(); + break; + /* ciphers not using EVP */ +#endif /* HAVE_BLOWFISH */ + case SSH_AEAD_CHACHA20_POLY1305: + SSH_LOG(SSH_LOG_TRACE, "The ChaCha cipher cannot be handled here"); + break; + case SSH_NO_CIPHER: + SSH_LOG(SSH_LOG_TRACE, "No valid ciphertype found"); + break; + } +} + +static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, void *IV) +{ + int rc; + + evp_cipher_init(cipher); + + rc = EVP_EncryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptInit_ex failed"); + return SSH_ERROR; + } + + /* For AES-GCM we need to set IV in specific way */ + if (cipher->ciphertype == SSH_AEAD_AES128_GCM || + cipher->ciphertype == SSH_AEAD_AES256_GCM) { + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_SET_IV_FIXED, + -1, + (uint8_t *)IV); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + return SSH_ERROR; + } + } + + EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); + + return SSH_OK; +} + +static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, + void *key, void *IV) { + int rc; + + evp_cipher_init(cipher); + + rc = EVP_DecryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptInit_ex failed"); + return SSH_ERROR; + } + + /* For AES-GCM we need to set IV in specific way */ + if (cipher->ciphertype == SSH_AEAD_AES128_GCM || + cipher->ciphertype == SSH_AEAD_AES256_GCM) { + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_SET_IV_FIXED, + -1, + (uint8_t *)IV); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + return SSH_ERROR; + } + } + + EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); + + return SSH_OK; +} + +/* EVP wrapper function for encrypt/decrypt */ +static void evp_cipher_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + int outlen = 0; + int rc = 0; + + rc = EVP_EncryptUpdate(cipher->ctx, + (unsigned char *)out, + &outlen, + (unsigned char *)in, + (int)len); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); + return; + } + if (outlen != (int)len){ + SSH_LOG(SSH_LOG_DEBUG, + "EVP_EncryptUpdate: output size %d for %zu in", + outlen, + len); + return; + } +} + +static void evp_cipher_decrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + int outlen = 0; + int rc = 0; + + rc = EVP_DecryptUpdate(cipher->ctx, + (unsigned char *)out, + &outlen, + (unsigned char *)in, + (int)len); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate failed"); + return; + } + if (outlen != (int)len){ + SSH_LOG(SSH_LOG_DEBUG, + "EVP_DecryptUpdate: output size %d for %zu in", + outlen, + len); + return; + } +} + +static void evp_cipher_cleanup(struct ssh_cipher_struct *cipher) { + if (cipher->ctx != NULL) { + EVP_CIPHER_CTX_free(cipher->ctx); + } +} + +static int evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + (void)cipher; + (void)seq; + + /* The length is not encrypted: Copy it to the result buffer */ + memcpy(out, in, len); + + return SSH_OK; +} + +static void evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + size_t authlen, aadlen; + uint8_t lastiv[1]; + int tmplen = 0; + size_t outlen; + int rc; + + (void) seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv); + if (rc == 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); + return; + } + + /* Pass over the authenticated data (not encrypted) */ + rc = EVP_EncryptUpdate(cipher->ctx, + NULL, + &tmplen, + (unsigned char *)in, + (int)aadlen); + outlen = tmplen; + if (rc == 0 || outlen != aadlen) { + SSH_LOG(SSH_LOG_TRACE, "Failed to pass authenticated data"); + return; + } + memcpy(out, in, aadlen); + + /* Encrypt the rest of the data */ + rc = EVP_EncryptUpdate(cipher->ctx, + (unsigned char *)out + aadlen, + &tmplen, + (unsigned char *)in + aadlen, + (int)(len - aadlen)); + outlen = tmplen; + if (rc != 1 || outlen != (int)len - aadlen) { + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); + return; + } + + /* compute tag */ + rc = EVP_EncryptFinal(cipher->ctx, NULL, &tmplen); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal failed: Failed to create a tag"); + return; + } + + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_GET_TAG, + (int)authlen, + (unsigned char *)tag); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_GET_TAG failed"); + return; + } +} + +static int evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + size_t authlen, aadlen; + uint8_t lastiv[1]; + int outlen = 0; + int rc = 0; + + (void)seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv); + if (rc == 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); + return SSH_ERROR; + } + + /* set tag for authentication */ + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_SET_TAG, + (int)authlen, + (unsigned char *)complete_packet + aadlen + + encrypted_size); + if (rc == 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_TAG failed"); + return SSH_ERROR; + } + + /* Pass over the authenticated data (not encrypted) */ + rc = EVP_DecryptUpdate(cipher->ctx, + NULL, + &outlen, + (unsigned char *)complete_packet, + (int)aadlen); + if (rc == 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to pass authenticated data"); + return SSH_ERROR; + } + /* Do not copy the length to the target buffer, because it is already processed */ + //memcpy(out, complete_packet, aadlen); + + /* Decrypt the rest of the data */ + rc = EVP_DecryptUpdate(cipher->ctx, + (unsigned char *)out, + &outlen, + (unsigned char *)complete_packet + aadlen, + (int)encrypted_size /* already subtracted aadlen */); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate failed"); + return SSH_ERROR; + } + + if (outlen != (int)encrypted_size) { + SSH_LOG(SSH_LOG_TRACE, + "EVP_DecryptUpdate: output size %d for %zd in", + outlen, + encrypted_size); + return SSH_ERROR; + } + + /* verify tag */ + rc = EVP_DecryptFinal(cipher->ctx, NULL, &outlen); + if (rc != 1 || outlen != 0) { + SSH_LOG(SSH_LOG_TRACE, + "EVP_DecryptFinal failed: Failed authentication"); + return SSH_ERROR; + } + + return SSH_OK; +} + +#ifdef HAVE_OPENSSL_EVP_CHACHA20 + +struct chacha20_poly1305_keysched { + /* cipher handle used for encrypting the packets */ + EVP_CIPHER_CTX *main_evp; + /* cipher handle used for encrypting the length field */ + EVP_CIPHER_CTX *header_evp; +#if defined(LIBRESSL_VERSION_NUMBER) + /* LibreSSL Poly1305 context */ + poly1305_context poly_ctx; +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + /* mac handle used for authenticating the packets */ + EVP_PKEY_CTX *pctx; + /* Poly1305 key */ + EVP_PKEY *key; + /* MD context for digesting data in poly1305 */ + EVP_MD_CTX *mctx; +#else + /* MAC context used to do poly1305 */ + EVP_MAC_CTX *mctx; +#endif /* OPENSSL_VERSION_NUMBER */ +}; + +static void chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + EVP_CIPHER_CTX_free(ctx->main_evp); + ctx->main_evp = NULL; + EVP_CIPHER_CTX_free(ctx->header_evp); + ctx->header_evp = NULL; +#if defined(LIBRESSL_VERSION_NUMBER) + /* nothing to free */ +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + /* ctx->pctx is freed as part of MD context */ + EVP_PKEY_free(ctx->key); + ctx->key = NULL; + EVP_MD_CTX_free(ctx->mctx); + ctx->mctx = NULL; +#else + EVP_MAC_CTX_free(ctx->mctx); + ctx->mctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + SAFE_FREE(cipher->chacha20_schedule); +} + +static int chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, + void *key, + UNUSED_PARAM(void *IV)) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + uint8_t *u8key = key; + int ret = SSH_ERROR, rv; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC *mac = NULL; +#endif + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + /* ChaCha20 initialization */ + /* K2 uses the first half of the key */ + ctx->main_evp = EVP_CIPHER_CTX_new(); + if (ctx->main_evp == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CIPHER_CTX_new failed"); + goto out; + } + rv = EVP_EncryptInit_ex(ctx->main_evp, EVP_chacha20(), NULL, u8key, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit failed"); + goto out; + } + /* K1 uses the second half of the key */ + ctx->header_evp = EVP_CIPHER_CTX_new(); + if (ctx->header_evp == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CIPHER_CTX_new failed"); + goto out; + } + rv = EVP_EncryptInit_ex(ctx->header_evp, EVP_chacha20(), NULL, + u8key + CHACHA20_KEYLEN, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit failed"); + goto out; + } + + /* The Poly1305 key initialization is delayed to the time we know + * the actual key for packet so we do not need to create a bogus keys + */ +#if defined(LIBRESSL_VERSION_NUMBER) + /* nothing, poly1305_context is stack based */ +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + ctx->mctx = EVP_MD_CTX_new(); + if (ctx->mctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MD_CTX_new failed"); + return SSH_ERROR; + } +#else + mac = EVP_MAC_fetch(NULL, SN_poly1305, NULL); + if (mac == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_fetch failed"); + goto out; + } + ctx->mctx = EVP_MAC_CTX_new(mac); + if (ctx->mctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_CTX_new failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + ret = SSH_OK; +out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC_free(mac); +#endif + if (ret != SSH_OK) { + chacha20_poly1305_cleanup(cipher); + } + return ret; +} + +static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; + +static int chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, + uint64_t seq, + int do_encrypt) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t seqbuf[16] = {0}; + int ret; + + /* Prepare the IV for OpenSSL -- it needs to be 128 b long. First 32 b is + * counter the rest is nonce. The memory is initialized to zeros + * (counter starts from 0) and we set the sequence number in the second half + */ + PUSH_BE_U64(seqbuf, 8, seq); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("seqbuf (chacha20 IV)", seqbuf, sizeof(seqbuf)); +#endif /* DEBUG_CRYPTO */ + + ret = EVP_CipherInit_ex(ctx->header_evp, NULL, NULL, NULL, seqbuf, do_encrypt); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit_ex(header_evp) failed"); + return SSH_ERROR; + } + + ret = EVP_CipherInit_ex(ctx->main_evp, NULL, NULL, NULL, seqbuf, do_encrypt); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit_ex(main_evp) failed"); + return SSH_ERROR; + } + + return SSH_OK; +} + +static int chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, + uint64_t seq, + int do_encrypt) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + int ret = SSH_ERROR, len, rv; + + /* The initialization for decrypt was already done with the length block */ + if (do_encrypt) { + rv = chacha20_poly1305_set_iv(cipher, seq, do_encrypt); + if (rv != SSH_OK) { + return SSH_ERROR; + } + } + + /* Output full ChaCha block so that counter increases by one for + * next step. */ + rv = EVP_CipherUpdate(ctx->main_evp, poly_key, &len, + (unsigned char *)zero_block, sizeof(zero_block)); + if (rv != 1 || len != CHACHA20_BLOCKSIZE) { + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); + goto out; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("poly_key", poly_key, POLY1305_KEYLEN); +#endif /* DEBUG_CRYPTO */ + +/* LibreSSL path: use direct Poly1305 implementation */ +#if defined(LIBRESSL_VERSION_NUMBER) + CRYPTO_poly1305_init(&ctx->poly_ctx, poly_key); +/* Set the Poly1305 key */ +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + if (ctx->key == NULL) { + /* Poly1305 Initialization needs to know the actual key */ + ctx->key = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305, + NULL, + poly_key, + POLY1305_KEYLEN); + if (ctx->key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_new_mac_key failed"); + goto out; + } + rv = EVP_DigestSignInit(ctx->mctx, &ctx->pctx, NULL, NULL, ctx->key); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignInit failed"); + goto out; + } + } else { + /* Updating the key is easier but less obvious */ + rv = EVP_PKEY_CTX_ctrl(ctx->pctx, + -1, + EVP_PKEY_OP_SIGNCTX, + EVP_PKEY_CTRL_SET_MAC_KEY, + POLY1305_KEYLEN, + (void *)poly_key); + if (rv <= 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_CTX_ctrl failed"); + goto out; + } + } +#else + rv = EVP_MAC_init(ctx->mctx, poly_key, POLY1305_KEYLEN, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_init failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + ret = SSH_OK; +out: + ssh_burn(poly_key, sizeof(poly_key)); + return ret; +} + +static int +chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int rv, outlen; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", (uint8_t *)in, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* Set IV for the header EVP */ + rv = chacha20_poly1305_set_iv(cipher, seq, 0); + if (rv != SSH_OK) { + return SSH_ERROR; + } + + rv = EVP_CipherUpdate(ctx->header_evp, out, &outlen, in, (int)len); + if (rv != 1 || outlen != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("deciphered length", out, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + rv = EVP_CipherFinal_ex(ctx->header_evp, out + outlen, &outlen); + if (rv != 1 || outlen != 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherFinal_ex failed"); + return SSH_ERROR; + } + + return SSH_OK; +} + +static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t *mac = + (uint8_t *)complete_packet + sizeof(uint32_t) + encrypted_size; + uint8_t tag[POLY1305_TAGLEN] = {0}; + int ret = SSH_ERROR; + int rv, cmp, len = 0; +#if !defined(LIBRESSL_VERSION_NUMBER) + size_t taglen = POLY1305_TAGLEN; +#endif + + /* Prepare the Poly1305 key */ + rv = chacha20_poly1305_packet_setup(cipher, seq, 0); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("received mac", mac, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Calculate MAC of received data */ +#if defined(LIBRESSL_VERSION_NUMBER) + CRYPTO_poly1305_update(&ctx->poly_ctx, + complete_packet, + encrypted_size + sizeof(uint32_t)); + CRYPTO_poly1305_finish(&ctx->poly_ctx, tag); + +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + rv = EVP_DigestSignUpdate(ctx->mctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed"); + goto out; + } + + rv = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "poly1305 verify error"); + goto out; + } +#else + rv = EVP_MAC_update(ctx->mctx, + complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed"); + goto out; + } + + rv = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_final failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("calculated mac", tag, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Verify the calculated MAC matches the attached MAC */ + cmp = CRYPTO_memcmp(tag, mac, POLY1305_TAGLEN); + if (cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + return SSH_ERROR; + } + + /* Decrypt the message */ + rv = EVP_CipherUpdate(ctx->main_evp, + out, + &len, + (uint8_t *)complete_packet + sizeof(uint32_t), + (int)encrypted_size); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); + goto out; + } + + rv = EVP_CipherFinal_ex(ctx->main_evp, out + len, &len); + if (rv != 1 || len != 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherFinal_ex failed"); + goto out; + } + + ret = SSH_OK; +out: + return ret; +} + +static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; +#if !defined(LIBRESSL_VERSION_NUMBER) + size_t taglen = POLY1305_TAGLEN; +#endif + int ret, outlen = 0; + + /* Prepare the Poly1305 key */ + ret = chacha20_poly1305_packet_setup(cipher, seq, 1); + if (ret != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); + return; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("plaintext length", + (unsigned char *)&in_packet->length, + sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + /* step 2, encrypt length field */ + ret = EVP_CipherUpdate(ctx->header_evp, + (unsigned char *)&out_packet->length, + &outlen, + (unsigned char *)&in_packet->length, + sizeof(uint32_t)); + if (ret != 1 || outlen != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); + return; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", + (unsigned char *)&out_packet->length, + outlen); +#endif /* DEBUG_CRYPTO */ + ret = EVP_CipherFinal_ex(ctx->header_evp, (uint8_t *)out + outlen, &outlen); + if (ret != 1 || outlen != 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal_ex failed"); + return; + } + + /* step 3, encrypt packet payload (main_evp counter == 1) */ + /* We already did encrypt one block so the counter should be in the correct position */ + ret = EVP_CipherUpdate(ctx->main_evp, + out_packet->payload, + &outlen, + in_packet->payload, + (int)(len - sizeof(uint32_t))); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); + return; + } + + /* step 4, compute the MAC */ +#if defined(LIBRESSL_VERSION_NUMBER) + + CRYPTO_poly1305_update(&ctx->poly_ctx, + (const unsigned char *)out_packet, + len); + CRYPTO_poly1305_finish(&ctx->poly_ctx, tag); +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + ret = EVP_DigestSignUpdate(ctx->mctx, out_packet, len); + if (ret <= 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed"); + return; + } + ret = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); + if (ret <= 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignFinal failed"); + return; + } +#else + ret = EVP_MAC_update(ctx->mctx, (void *)out_packet, len); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed"); + return; + } + + ret = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_final failed"); + return; + } +#endif /* OPENSSL_VERSION_NUMBER */ +} +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ + +#ifdef WITH_INSECURE_NONE +static void none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) +{ + memcpy(out, in, len); +} +#endif /* WITH_INSECURE_NONE */ + +/* + * The table of supported ciphers + */ +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef HAVE_BLOWFISH + { + .name = "blowfish-cbc", + .blocksize = 8, + .ciphertype = SSH_BLOWFISH_CBC, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, +#endif /* HAVE_BLOWFISH */ +#ifdef HAS_AES + { + .name = "aes128-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES128_CTR, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes192-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES192_CTR, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes256-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES256_CTR, + .keysize = 256, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes128-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES128_CBC, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes192-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES192_CBC, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes256-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES256_CBC, + .keysize = 256, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes128-gcm@openssh.com", + .blocksize = AES_BLOCK_SIZE, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .ciphertype = SSH_AEAD_AES128_GCM, + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .aead_encrypt = evp_cipher_aead_encrypt, + .aead_decrypt_length = evp_cipher_aead_get_length, + .aead_decrypt = evp_cipher_aead_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = AES_BLOCK_SIZE, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .ciphertype = SSH_AEAD_AES256_GCM, + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .aead_encrypt = evp_cipher_aead_encrypt, + .aead_decrypt_length = evp_cipher_aead_get_length, + .aead_decrypt = evp_cipher_aead_decrypt, + .cleanup = evp_cipher_cleanup, + }, +#endif /* HAS_AES */ +#ifdef HAS_DES + { + .name = "3des-cbc", + .blocksize = 8, + .ciphertype = SSH_3DES_CBC, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, +#endif /* HAS_DES */ + { +#ifdef HAVE_OPENSSL_EVP_CHACHA20 + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = CHACHA20_BLOCKSIZE / 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 2 * CHACHA20_KEYLEN * 8, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_poly1305_set_key, + .set_decrypt_key = chacha20_poly1305_set_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_poly1305_cleanup +#else + .name = "chacha20-poly1305@openssh.com" +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ + }, +#ifdef WITH_INSECURE_NONE + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt, + }, +#endif /* WITH_INSECURE_NONE */ + { + .name = NULL, + }, +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +/** + * @internal + * @brief Initialize libcrypto's subsystem + */ +int ssh_crypto_init(void) +{ +#ifndef HAVE_OPENSSL_EVP_CHACHA20 + size_t i; +#endif + + if (libcrypto_initialized) { + return SSH_OK; + } + if (OpenSSL_version_num() != OPENSSL_VERSION_NUMBER) { + SSH_LOG(SSH_LOG_DEBUG, + "libssh compiled with %s " + "headers, currently running with %s.", + OPENSSL_VERSION_TEXT, + OpenSSL_version(OpenSSL_version_num())); + } +#ifdef CAN_DISABLE_AESNI + /* + * disable AES-NI when running within Valgrind, because they generate + * too many "uninitialized memory access" false positives + */ + if (RUNNING_ON_VALGRIND) { + SSH_LOG(SSH_LOG_INFO, "Running within Valgrind, disabling AES-NI"); + /* Bit #57 denotes AES-NI instruction set extension */ + OPENSSL_ia32cap &= ~(1LL << 57); + } +#endif /* CAN_DISABLE_AESNI */ + +#ifndef HAVE_OPENSSL_EVP_CHACHA20 + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + ssh_get_chacha20poly1305_cipher(), + sizeof(struct ssh_cipher_struct)); + break; + } + } +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ + + libcrypto_initialized = 1; + + return SSH_OK; +} + +/** + * @internal + * @brief Finalize libcrypto's subsystem + */ +void ssh_crypto_finalize(void) +{ + if (!libcrypto_initialized) { + return; + } + +/* TODO this should finalize engine if it was started, but during atexit calls, + * we are crashing. AFAIK this is related to the dlopened pkcs11 modules calling + * the crypto cleanups earlier. */ +#if 0 + if (engine != NULL) { + ENGINE_finish(engine); + ENGINE_free(engine); + engine = NULL; + } +#endif +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) + if (provider != NULL) { + OSSL_PROVIDER_unload(provider); + provider = NULL; + } +#endif /* WITH_PKCS11_PROVIDER */ +#endif /* WITH_PKCS11_URI */ + + libcrypto_initialized = 0; +} + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +/** + * @internal + * @brief Create EVP_PKEY from parameters + * + * @param[in] name Algorithm to use. For more info see manpage of + * EVP_PKEY_CTX_new_from_name + * + * @param[in] param_bld Constructed param builder for the pkey + * + * @param[out] pkey Created EVP_PKEY variable + * + * @param[in] selection Reference selections at man EVP_PKEY_FROMDATA + * + * @return 0 on success, -1 on error + */ +int evp_build_pkey(const char *name, + OSSL_PARAM_BLD *param_bld, + EVP_PKEY **pkey, + int selection) +{ + int rc; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + OSSL_PARAM *params = NULL; + + if (ctx == NULL) { + return -1; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, pkey, selection, params); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +/** + * @brief creates a copy of EVP_PKEY + * + * @param[in] name Algorithm to use. For more info see manpage of + * EVP_PKEY_CTX_new_from_name + * + * @param[in] key Key being duplicated from + * + * @param[in] demote Same as at pki_key_dup, only the public + * part of the key gets duplicated if true + * + * @param[out] new_key The key where the duplicate is saved + * + * @return 0 on success, -1 on error + */ +static int +evp_dup_pkey(const char *name, const ssh_key key, int demote, ssh_key new_key) +{ + int rc; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM *params = NULL; + + /* The simple case -- just reference the existing key */ + if (!demote || (key->flags & SSH_KEY_FLAG_PRIVATE) == 0) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + return -1; + } + new_key->key = key->key; + return SSH_OK; + } + + /* demote == 1 */ + ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + if (ctx == NULL) { + return -1; + } + + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + if (strcmp(name, "EC") == 0) { + OSSL_PARAM *locate_param = NULL; + /* For ECC keys provided by engine or provider, we need to have the + * explicit public part available, otherwise the key will not be + * usable */ + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); + if (locate_param == NULL) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + } + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, &(new_key->key), EVP_PKEY_PUBLIC_KEY, params); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey(SN_rsa, key, demote, new_key); +} + +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey("EC", key, demote, new_key); +} + +int evp_dup_ed25519_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey(SN_ED25519, key, demote, new_key); +} + +#endif /* OPENSSL_VERSION_NUMBER */ + +ssh_string pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p) +{ + ssh_string s = NULL; + size_t len; + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + NULL, + 0, + NULL); + if (len == 0) { + return NULL; + } + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + ssh_string_data(s), + ssh_string_len(s), + NULL); + if (len != ssh_string_len(s)) { + SSH_STRING_FREE(s); + return NULL; + } + + return s; +} + +int pki_key_ecgroup_name_to_nid(const char *group) +{ + if (strcmp(group, NISTP256) == 0 || strcmp(group, "secp256r1") == 0 || + strcmp(group, SN_X9_62_prime256v1) == 0) { + return NID_X9_62_prime256v1; + } else if (strcmp(group, NISTP384) == 0 || + strcmp(group, SN_secp384r1) == 0) { + return NID_secp384r1; + } else if (strcmp(group, NISTP521) == 0 || + strcmp(group, SN_secp521r1) == 0) { + return NID_secp521r1; + } + return -1; +} +#endif /* LIBCRYPTO */ diff --git a/src/libs/libssh-0.12.2/src/libgcrypt.c b/src/libs/libssh-0.12.2/src/libgcrypt.c new file mode 100644 index 000000000000..8cce1a9f27a2 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libgcrypt.c @@ -0,0 +1,1011 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/string.h" +#include "libssh/misc.h" +#ifdef HAVE_GCRYPT_CHACHA_POLY +#include "libssh/chacha20-poly1305-common.h" +#endif + +#ifdef HAVE_LIBGCRYPT +#include + +#ifdef HAVE_GCRYPT_CHACHA_POLY + +struct chacha20_poly1305_keysched { + bool initialized; + /* cipher handle used for encrypting the packets */ + gcry_cipher_hd_t main_hd; + /* cipher handle used for encrypting the length field */ + gcry_cipher_hd_t header_hd; + /* mac handle used for authenticating the packets */ + gcry_mac_hd_t mac_hd; +}; + +static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; +#endif /* HAVE_GCRYPT_CHACHA_POLY */ + +static int libgcrypt_initialized = 0; + +static int alloc_key(struct ssh_cipher_struct *cipher) { + cipher->key = malloc(cipher->keylen); + if (cipher->key == NULL) { + return -1; + } + + return 0; +} + +void ssh_reseed(void){ +} + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len) +{ + return sshkdf_derive_key(crypto, key, key_len, + key_type, output, requested_len); +} + +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) { + HMACCTX c = NULL; + + switch(type) { + case SSH_HMAC_SHA1: + gcry_md_open(&c, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_SHA256: + gcry_md_open(&c, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_SHA512: + gcry_md_open(&c, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_MD5: + gcry_md_open(&c, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC); + break; + default: + c = NULL; + } + + gcry_md_setkey(c, key, len); + + return c; +} + +int hmac_update(HMACCTX c, const void *data, size_t len) { + gcry_md_write(c, data, len); + return 1; +} + +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { + unsigned int tmp = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); + *len = (size_t)tmp; + memcpy(hashmacbuf, gcry_md_read(c, 0), *len); + gcry_md_close(c); + return 1; +} + +#ifdef HAVE_BLOWFISH +/* the wrapper functions for blowfish */ +static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV){ + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_BLOWFISH, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 16)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + } + + return 0; +} + +static void blowfish_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, size_t len) { + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void blowfish_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, size_t len) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} +#endif /* HAVE_BLOWFISH */ + +static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { + int mode=GCRY_CIPHER_MODE_CBC; + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if(strstr(cipher->name,"-ctr")) + mode=GCRY_CIPHER_MODE_CTR; + if (strstr(cipher->name, "-gcm")) + mode = GCRY_CIPHER_MODE_GCM; + switch (cipher->keysize) { + case 128: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES128, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + case 192: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES192, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + case 256: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES256, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unsupported key length %u.", cipher->keysize); + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, cipher->keysize / 8)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + if(mode == GCRY_CIPHER_MODE_CBC){ + if (gcry_cipher_setiv(cipher->key[0], IV, 16)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + } else if (mode == GCRY_CIPHER_MODE_GCM) { + /* Store the IV so we can handle the packet counter increments later + * The IV is passed to the cipher context later. + */ + memcpy(cipher->last_iv, IV, AES_GCM_IVLEN); + } else { + if(gcry_cipher_setctr(cipher->key[0],IV,16)){ + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + } + } + + return 0; +} + +static void aes_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void aes_decrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +static int +aes_aead_get_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + (void)cipher; + (void)seq; + + /* The length is not encrypted: Copy it to the result buffer */ + memcpy(out, in, len); + + return SSH_OK; +} + +static void +aes_gcm_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + gpg_error_t err; + size_t aadlen, authlen; + + (void)seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + err = gcry_cipher_setiv(cipher->key[0], + cipher->last_iv, + AES_GCM_IVLEN); + /* This actually does not increment the packet counter for the + * current encryption operation, but for the next one. The first + * operation needs to be completed with the derived IV. + * + * The IV buffer has the following structure: + * [ 4B static IV ][ 8B packet counter ][ 4B block counter ] + */ + uint64_inc(cipher->last_iv + 4); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + return; + } + + /* Pass the authenticated data (packet_length) */ + err = gcry_cipher_authenticate(cipher->key[0], in, aadlen); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_authenticate failed: %s", + gpg_strerror(err)); + return; + } + memcpy(out, in, aadlen); + + /* Encrypt the rest of the data */ + err = gcry_cipher_encrypt(cipher->key[0], + (unsigned char *)out + aadlen, + len - aadlen, + (unsigned char *)in + aadlen, + len - aadlen); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + return; + } + + /* Calculate the tag */ + err = gcry_cipher_gettag(cipher->key[0], + (void *)tag, + authlen); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_gettag failed: %s", + gpg_strerror(err)); + return; + } +} + +static int +aes_gcm_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + gpg_error_t err; + size_t aadlen, authlen; + + (void)seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + err = gcry_cipher_setiv(cipher->key[0], + cipher->last_iv, + AES_GCM_IVLEN); + /* This actually does not increment the packet counter for the + * current encryption operation, but for the next one. The first + * operation needs to be completed with the derived IV. + * + * The IV buffer has the following structure: + * [ 4B static IV ][ 8B packet counter ][ 4B block counter ] + */ + uint64_inc(cipher->last_iv + 4); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + /* Pass the authenticated data (packet_length) */ + err = gcry_cipher_authenticate(cipher->key[0], + complete_packet, + aadlen); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_authenticate failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + /* Do not copy the length to the target buffer, because it is already processed */ + //memcpy(out, complete_packet, aadlen); + + /* Encrypt the rest of the data */ + err = gcry_cipher_decrypt(cipher->key[0], + out, + encrypted_size, + (unsigned char *)complete_packet + aadlen, + encrypted_size); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + /* Check the tag */ + err = gcry_cipher_checktag(cipher->key[0], + (unsigned char *)complete_packet + aadlen + encrypted_size, + authlen); + if (gpg_err_code(err) == GPG_ERR_CHECKSUM) { + SSH_LOG(SSH_LOG_DEBUG, "The authentication tag does not match"); + return SSH_ERROR; + } else if (err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "General error while decryption: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + return SSH_OK; +} + +static int des3_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_3DES, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 24)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + } + + return 0; +} + +static void des3_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, size_t len) { + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, size_t len) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +#ifdef HAVE_GCRYPT_CHACHA_POLY +static void chacha20_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + if (ctx->initialized) { + gcry_cipher_close(ctx->main_hd); + gcry_cipher_close(ctx->header_hd); + gcry_mac_close(ctx->mac_hd); + ctx->initialized = false; + } + + SAFE_FREE(cipher->chacha20_schedule); +} + +static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + UNUSED_PARAM(void *IV)) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + uint8_t *u8key = key; + gpg_error_t err; + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + if (!ctx->initialized) { + /* Open cipher/mac handles. */ + err = gcry_cipher_open(&ctx->main_hd, GCRY_CIPHER_CHACHA20, + GCRY_CIPHER_MODE_STREAM, 0); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_open failed: %s", + gpg_strerror(err)); + SAFE_FREE(cipher->chacha20_schedule); + return -1; + } + err = gcry_cipher_open(&ctx->header_hd, GCRY_CIPHER_CHACHA20, + GCRY_CIPHER_MODE_STREAM, 0); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_open failed: %s", + gpg_strerror(err)); + gcry_cipher_close(ctx->main_hd); + SAFE_FREE(cipher->chacha20_schedule); + return -1; + } + err = gcry_mac_open(&ctx->mac_hd, GCRY_MAC_POLY1305, 0, NULL); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_open failed: %s", + gpg_strerror(err)); + gcry_cipher_close(ctx->main_hd); + gcry_cipher_close(ctx->header_hd); + SAFE_FREE(cipher->chacha20_schedule); + return -1; + } + + ctx->initialized = true; + } + + err = gcry_cipher_setkey(ctx->main_hd, u8key, CHACHA20_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setkey failed: %s", + gpg_strerror(err)); + chacha20_cleanup(cipher); + return -1; + } + + err = gcry_cipher_setkey(ctx->header_hd, u8key + CHACHA20_KEYLEN, + CHACHA20_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setkey failed: %s", + gpg_strerror(err)); + chacha20_cleanup(cipher); + return -1; + } + + return 0; +} + +static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + size_t taglen = POLY1305_TAGLEN; + gpg_error_t err; + + seq = htonll(seq); + + /* step 1, prepare the poly1305 key */ + err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + goto out; + } + /* Output full ChaCha block so that counter increases by one for + * payload encryption step. */ + err = gcry_cipher_encrypt(ctx->main_hd, + poly_key, + sizeof(poly_key), + zero_block, + sizeof(zero_block)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_setkey failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 2, encrypt length field */ + err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_cipher_encrypt(ctx->header_hd, + (uint8_t *)&out_packet->length, + sizeof(uint32_t), + (uint8_t *)&in_packet->length, + sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 3, encrypt packet payload (main_hd counter == 1) */ + err = gcry_cipher_encrypt(ctx->main_hd, + out_packet->payload, + len - sizeof(uint32_t), + in_packet->payload, + len - sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 4, compute the MAC */ + err = gcry_mac_write(ctx->mac_hd, (uint8_t *)out_packet, len); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_write failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_read(ctx->mac_hd, tag, &taglen); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_read failed: %s", + gpg_strerror(err)); + goto out; + } + +out: + ssh_burn(poly_key, sizeof(poly_key)); +} + +static int chacha20_poly1305_aead_decrypt_length( + struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + gpg_error_t err; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + seq = htonll(seq); + + err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + err = gcry_cipher_decrypt(ctx->header_hd, + out, + sizeof(uint32_t), + in, + sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + return SSH_OK; +} + +static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + + encrypted_size; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + int ret = SSH_ERROR; + gpg_error_t err; + + seq = htonll(seq); + + /* step 1, prepare the poly1305 key */ + err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + goto out; + } + /* Output full ChaCha block so that counter increases by one for + * decryption step. */ + err = gcry_cipher_encrypt(ctx->main_hd, + poly_key, + sizeof(poly_key), + zero_block, + sizeof(zero_block)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_setkey failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 2, check MAC */ + err = gcry_mac_write(ctx->mac_hd, (uint8_t *)complete_packet, + encrypted_size + sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_write failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_verify(ctx->mac_hd, mac, POLY1305_TAGLEN); + if (gpg_err_code(err) == GPG_ERR_CHECKSUM) { + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + goto out; + } else if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_verify failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 3, decrypt packet payload (main_hd counter == 1) */ + err = gcry_cipher_decrypt(ctx->main_hd, + out, + encrypted_size, + (uint8_t *)complete_packet + sizeof(uint32_t), + encrypted_size); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", + gpg_strerror(err)); + goto out; + } + + ret = SSH_OK; + +out: + ssh_burn(poly_key, sizeof(poly_key)); + return ret; +} +#endif /* HAVE_GCRYPT_CHACHA_POLY */ + +#ifdef WITH_INSECURE_NONE +static void +none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) +{ + memcpy(out, in, len); +} +#endif /* WITH_INSECURE_NONE */ + +/* the table of supported ciphers */ +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef HAVE_BLOWFISH + { + .name = "blowfish-cbc", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = blowfish_set_key, + .set_decrypt_key = blowfish_set_key, + .encrypt = blowfish_encrypt, + .decrypt = blowfish_decrypt + }, +#endif /* HAVE_BLOWFISH */ + { + .name = "aes128-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_encrypt + }, + { + .name = "aes192-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_encrypt + }, + { + .name = "aes256-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_encrypt + }, + { + .name = "aes128-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_decrypt + }, + { + .name = "aes192-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_decrypt + }, + { + .name = "aes256-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_decrypt + }, + { + .name = "aes128-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .aead_encrypt = aes_gcm_encrypt, + .aead_decrypt_length = aes_aead_get_length, + .aead_decrypt = aes_gcm_decrypt, + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .aead_encrypt = aes_gcm_encrypt, + .aead_decrypt_length = aes_aead_get_length, + .aead_decrypt = aes_gcm_decrypt, + }, + { + .name = "3des-cbc", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = des3_set_key, + .set_decrypt_key = des3_set_key, + .encrypt = des3_encrypt, + .decrypt = des3_decrypt + }, + { +#ifdef HAVE_GCRYPT_CHACHA_POLY + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 2 * CHACHA20_KEYLEN * 8, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_set_encrypt_key, + .set_decrypt_key = chacha20_set_encrypt_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_cleanup +#else + .name = "chacha20-poly1305@openssh.com" +#endif + }, +#ifdef WITH_INSECURE_NONE + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt + }, +#endif /* WITH_INSECURE_NONE */ + { + .name = NULL, + .blocksize = 0, + .keylen = 0, + .key = NULL, + .keysize = 0, + .set_encrypt_key = NULL, + .set_decrypt_key = NULL, + .encrypt = NULL, + .decrypt = NULL + } +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +/* + * Extract an MPI from the given s-expression SEXP named NAME which is + * encoded using INFORMAT and store it in a newly allocated ssh_string + * encoded using OUTFORMAT. + */ +ssh_string ssh_sexp_extract_mpi(const gcry_sexp_t sexp, + const char *name, + enum gcry_mpi_format informat, + enum gcry_mpi_format outformat) +{ + gpg_error_t err; + ssh_string result = NULL; + gcry_sexp_t fragment = NULL; + gcry_mpi_t mpi = NULL; + size_t size; + + fragment = gcry_sexp_find_token(sexp, name, 0); + if (fragment == NULL) { + goto fail; + } + + mpi = gcry_sexp_nth_mpi(fragment, 1, informat); + if (mpi == NULL) { + goto fail; + } + + err = gcry_mpi_print(outformat, NULL, 0, &size, mpi); + if (err != 0) { + goto fail; + } + + result = ssh_string_new(size); + if (result == NULL) { + goto fail; + } + + err = gcry_mpi_print(outformat, ssh_string_data(result), size, NULL, mpi); + if (err != 0) { + ssh_string_burn(result); + SSH_STRING_FREE(result); + result = NULL; + goto fail; + } + +fail: + gcry_sexp_release(fragment); + gcry_mpi_release(mpi); + return result; +} + + +/** + * @internal + * + * @brief Initialize libgcrypt's subsystem + */ +int ssh_crypto_init(void) +{ + UNUSED_VAR(size_t i); + + if (libgcrypt_initialized) { + return SSH_OK; + } + + gcry_check_version(NULL); + + /* While the secure memory is not set up */ + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P, 0)) { + gcry_control(GCRYCTL_USE_SECURE_RNDPOOL); + gcry_control(GCRYCTL_INIT_SECMEM, 32768, 0); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + } + + /* Re-enable warning */ + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + +#ifndef HAVE_GCRYPT_CHACHA_POLY + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + ssh_get_chacha20poly1305_cipher(), + sizeof(struct ssh_cipher_struct)); + break; + } + } +#endif + + libgcrypt_initialized = 1; + + return SSH_OK; +} + +/** + * @internal + * + * @brief Finalize libgcrypt's subsystem + */ +void ssh_crypto_finalize(void) +{ + if (!libgcrypt_initialized) { + return; + } + + gcry_control(GCRYCTL_TERM_SECMEM); + + libgcrypt_initialized = 0; +} + +#endif diff --git a/src/libs/libssh-0.12.2/src/libmbedcrypto.c b/src/libs/libssh-0.12.2/src/libmbedcrypto.c new file mode 100644 index 000000000000..d52ec3dfe811 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libmbedcrypto.c @@ -0,0 +1,1120 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/wrapper.h" +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "mbedcrypto-compat.h" +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) +#include "libssh/bytearray.h" +#include "libssh/chacha20-poly1305-common.h" +#include +#include +#endif + +#ifdef HAVE_LIBMBEDCRYPTO +#include +#ifdef MBEDTLS_GCM_C +#include +#endif /* MBEDTLS_GCM_C */ + +static mbedtls_entropy_context ssh_mbedtls_entropy; +extern mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; + +static int libmbedcrypto_initialized = 0; + +void ssh_reseed(void) +{ + mbedtls_ctr_drbg_reseed(&ssh_mbedtls_ctr_drbg, NULL, 0); +} + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len) +{ + return sshkdf_derive_key(crypto, key, key_len, + key_type, output, requested_len); +} + +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) +{ + HMACCTX ctx = NULL; + const mbedtls_md_info_t *md_info = NULL; + int rc; + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + switch (type) { + case SSH_HMAC_SHA1: + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + break; + case SSH_HMAC_SHA256: + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + break; + case SSH_HMAC_SHA512: + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + break; + default: + goto error; + } + + mbedtls_md_init(ctx); + + if (md_info == NULL) { + goto error; + } + + rc = mbedtls_md_setup(ctx, md_info, 1); + if (rc != 0) { + goto error; + } + + rc = mbedtls_md_hmac_starts(ctx, key, len); + if (rc != 0) { + goto error; + } + + return ctx; + +error: + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return NULL; +} + +/* mbedtls returns 0 on success, but in this context + * success is 1 */ +int hmac_update(HMACCTX c, const void *data, size_t len) +{ + return !mbedtls_md_hmac_update(c, data, len); +} + +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) +{ + int rc; + *len = (unsigned int)mbedtls_md_get_size(c->MBEDTLS_PRIVATE(md_info)); + rc = !mbedtls_md_hmac_finish(c, hashmacbuf); + mbedtls_md_free(c); + SAFE_FREE(c); + return rc; +} + +static int +cipher_init(struct ssh_cipher_struct *cipher, + mbedtls_operation_t operation, + void *key, + void *IV) +{ + const mbedtls_cipher_info_t *cipher_info = NULL; + mbedtls_cipher_context_t *ctx = NULL; + size_t key_bitlen = 0; + size_t iv_size = 0; + int rc; + + if (operation == MBEDTLS_ENCRYPT) { + ctx = &cipher->encrypt_ctx; + } else if (operation == MBEDTLS_DECRYPT) { + ctx = &cipher->decrypt_ctx; + } else { + SSH_LOG(SSH_LOG_TRACE, "unknown operation"); + return 1; + } + + mbedtls_cipher_init(ctx); + cipher_info = mbedtls_cipher_info_from_type(cipher->type); + + rc = mbedtls_cipher_setup(ctx, cipher_info); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_setup failed"); + goto error; + } + + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_cipher_setkey(ctx, key, key_bitlen, operation); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_setkey failed"); + goto error; + } + + iv_size = mbedtls_cipher_info_get_iv_size(cipher_info); + rc = mbedtls_cipher_set_iv(ctx, IV, iv_size); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_iv failed"); + goto error; + } + + return 0; +error: + mbedtls_cipher_free(ctx); + return 1; +} + +static int +cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); + goto error; + } + + rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + return SSH_ERROR; +} + +static int +cipher_set_encrypt_key_cbc(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); + goto error; + } + + /* libssh only encrypts and decrypts packets that are multiples of a block + * size, and no padding is used */ + rc = mbedtls_cipher_set_padding_mode(&cipher->encrypt_ctx, + MBEDTLS_PADDING_NONE); + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_padding_mode failed"); + goto error; + } + + rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + mbedtls_cipher_free(&cipher->encrypt_ctx); + return SSH_ERROR; +} + +#ifdef MBEDTLS_GCM_C +static int +cipher_set_key_gcm(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + const mbedtls_cipher_info_t *cipher_info = NULL; + size_t key_bitlen = 0; + int rc; + + mbedtls_gcm_init(&cipher->gcm_ctx); + cipher_info = mbedtls_cipher_info_from_type(cipher->type); + + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_gcm_setkey(&cipher->gcm_ctx, MBEDTLS_CIPHER_ID_AES, + key, key_bitlen); + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_setkey failed"); + goto error; + } + + /* Store the IV so we can increment the packet counter later */ + memcpy(cipher->last_iv, IV, AES_GCM_IVLEN); + + return 0; +error: + mbedtls_gcm_free(&cipher->gcm_ctx); + return 1; +} +#endif /* MBEDTLS_GCM_C */ + +static int +cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); + goto error; + } + + mbedtls_cipher_reset(&cipher->decrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + mbedtls_cipher_free(&cipher->decrypt_ctx); + return SSH_ERROR; +} + +static int +cipher_set_decrypt_key_cbc(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); + goto error; + } + + rc = mbedtls_cipher_set_padding_mode(&cipher->decrypt_ctx, + MBEDTLS_PADDING_NONE); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_padding_mode failed"); + goto error; + } + + mbedtls_cipher_reset(&cipher->decrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + mbedtls_cipher_free(&cipher->decrypt_ctx); + return SSH_ERROR; +} + +static void cipher_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + size_t outlen = 0; + size_t total_len = 0; + int rc = 0; + rc = mbedtls_cipher_update(&cipher->encrypt_ctx, in, len, out, &outlen); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during encryption"); + return; + } + + total_len += outlen; + + if (total_len == len) { + return; + } + + rc = mbedtls_cipher_finish(&cipher->encrypt_ctx, (unsigned char *) out + outlen, + &outlen); + + total_len += outlen; + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_finish failed during encryption"); + return; + } + + if (total_len != len) { + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +static void cipher_encrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void *out, + size_t len) +{ + size_t outlen = 0; + int rc = 0; + rc = mbedtls_cipher_update(&cipher->encrypt_ctx, in, len, out, &outlen); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during encryption"); + return; + } + + if (outlen != len) { + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +static void cipher_decrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + size_t outlen = 0; + int rc = 0; + size_t total_len = 0; + + rc = mbedtls_cipher_update(&cipher->decrypt_ctx, in, len, out, &outlen); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during decryption"); + return; + } + + total_len += outlen; + + if (total_len == len) { + return; + } + + rc = mbedtls_cipher_finish(&cipher->decrypt_ctx, (unsigned char *) out + + outlen, &outlen); + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed during decryption"); + return; + } + + total_len += outlen; + + if (total_len != len) { + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +static void cipher_decrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void *out, + size_t len) +{ + size_t outlen = 0; + int rc = 0; + rc = mbedtls_cipher_update(&cipher->decrypt_ctx, in, len, out, &outlen); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during decryption"); + return; + } + + /* MbedTLS caches the last block when decrypting with cbc. + * By calling finish the block is flushed to out, however the unprocessed + * data counter is not reset. + * Calling mbedtls_cipher_reset resets the unprocessed data counter. + */ + if (outlen == 0) { + rc = mbedtls_cipher_finish(&cipher->decrypt_ctx, out, &outlen); + } else if (outlen == len) { + return; + } else { + rc = mbedtls_cipher_finish(&cipher->decrypt_ctx, (unsigned char *) out + + outlen , &outlen); + } + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_finish failed during decryption"); + return; + } + + rc = mbedtls_cipher_reset(&cipher->decrypt_ctx); + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed during decryption"); + return; + } + + if (outlen != len) { + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +#ifdef MBEDTLS_GCM_C +static int +cipher_gcm_get_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + (void)cipher; + (void)seq; + + /* The length is not encrypted: Copy it to the result buffer */ + memcpy(out, in, len); + + return SSH_OK; +} + +static void +cipher_encrypt_gcm(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + size_t authlen, aadlen; + int rc; + + (void) seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* The length is not encrypted */ + memcpy(out, in, aadlen); + rc = mbedtls_gcm_crypt_and_tag(&cipher->gcm_ctx, + MBEDTLS_GCM_ENCRYPT, + len - aadlen, /* encrypted data len */ + cipher->last_iv, /* IV */ + AES_GCM_IVLEN, + in, /* aad */ + aadlen, + (const unsigned char *)in + aadlen, /* input */ + (unsigned char *)out + aadlen, /* output */ + authlen, + tag); /* tag */ + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_crypt_and_tag failed"); + return; + } + + /* Increment the IV for the next invocation */ + uint64_inc(cipher->last_iv + 4); +} + +static int +cipher_decrypt_gcm(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + size_t authlen, aadlen; + int rc; + + (void) seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + rc = mbedtls_gcm_auth_decrypt(&cipher->gcm_ctx, + encrypted_size, /* encrypted data len */ + cipher->last_iv, /* IV */ + AES_GCM_IVLEN, + complete_packet, /* aad */ + aadlen, + (const uint8_t *)complete_packet + aadlen + encrypted_size, /* tag */ + authlen, + (const uint8_t *)complete_packet + aadlen, /* input */ + (unsigned char *)out); /* output */ + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_auth_decrypt failed"); + return SSH_ERROR; + } + + /* Increment the IV for the next invocation */ + uint64_inc(cipher->last_iv + 4); + + return SSH_OK; +} +#endif /* MBEDTLS_GCM_C */ + +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) + +struct chacha20_poly1305_keysched { + bool initialized; + /* cipher handle used for encrypting the packets */ + mbedtls_chacha20_context main_ctx; + /* cipher handle used for encrypting the length field */ + mbedtls_chacha20_context header_ctx; + /* Poly1305 key */ + mbedtls_poly1305_context poly_ctx; +}; + +static void +chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + if (ctx->initialized) { + mbedtls_chacha20_free(&ctx->main_ctx); + mbedtls_chacha20_free(&ctx->header_ctx); + mbedtls_poly1305_free(&ctx->poly_ctx); + ctx->initialized = false; + } + + SAFE_FREE(cipher->chacha20_schedule); +} + +static int +chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, + void *key, + UNUSED_PARAM(void *IV)) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + uint8_t *u8key = key; + int ret = SSH_ERROR, rv; + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + if (!ctx->initialized) { + mbedtls_chacha20_init(&ctx->main_ctx); + mbedtls_chacha20_init(&ctx->header_ctx); + mbedtls_poly1305_init(&ctx->poly_ctx); + ctx->initialized = true; + } + + /* ChaCha20 keys initialization */ + /* K2 uses the first half of the key */ + rv = mbedtls_chacha20_setkey(&ctx->main_ctx, u8key); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_setkey(main_ctx) failed"); + goto out; + } + + /* K1 uses the second half of the key */ + rv = mbedtls_chacha20_setkey(&ctx->header_ctx, u8key + CHACHA20_KEYLEN); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_setkey(header_ctx) failed"); + goto out; + } + + ret = SSH_OK; +out: + if (ret != SSH_OK) { + chacha20_poly1305_cleanup(cipher); + } + return ret; +} + +static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; + +static int +chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t seqbuf[12] = {0}; + int ret; + + /* The nonce in mbedTLS is 96 b long. The counter is passed through separate + * parameter of 32 b size. + * Encode the sequence number into the last 8 bytes. + */ + PUSH_BE_U64(seqbuf, 4, seq); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("seqbuf (chacha20 IV)", seqbuf, sizeof(seqbuf)); +#endif /* DEBUG_CRYPTO */ + + ret = mbedtls_chacha20_starts(&ctx->header_ctx, seqbuf, 0); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_starts(header_ctx) failed"); + return SSH_ERROR; + } + + ret = mbedtls_chacha20_starts(&ctx->main_ctx, seqbuf, 0); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_starts(main_ctx) failed"); + return SSH_ERROR; + } + + return SSH_OK; +} + +static int +chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, + uint64_t seq, + int do_encrypt) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + int ret = SSH_ERROR, rv; + + /* The initialization for decrypt was already done with the length block */ + if (do_encrypt) { + rv = chacha20_poly1305_set_iv(cipher, seq); + if (rv != SSH_OK) { + return SSH_ERROR; + } + } + + /* Output full ChaCha block so that counter increases by one for + * next step. */ + rv = mbedtls_chacha20_update(&ctx->main_ctx, sizeof(zero_block), + zero_block, poly_key); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + goto out; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("poly_key", poly_key, POLY1305_KEYLEN); +#endif /* DEBUG_CRYPTO */ + + /* Set the Poly1305 key */ + rv = mbedtls_poly1305_starts(&ctx->poly_ctx, poly_key); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_starts failed"); + goto out; + } + + ret = SSH_OK; +out: + ssh_burn(poly_key, sizeof(poly_key)); + return ret; +} + +static int +chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int rv; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", (uint8_t *)in, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* Set IV for the header context */ + rv = chacha20_poly1305_set_iv(cipher, seq); + if (rv != SSH_OK) { + return SSH_ERROR; + } + + rv = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), in, out); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("deciphered length", out, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + return SSH_OK; +} + +static int +chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + + encrypted_size; + uint8_t tag[POLY1305_TAGLEN] = {0}; + int ret = SSH_ERROR; + int rv, cmp = 0; + + /* Prepare the Poly1305 key */ + rv = chacha20_poly1305_packet_setup(cipher, seq, 0); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("received mac", mac, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Calculate MAC of received data */ + rv = mbedtls_poly1305_update(&ctx->poly_ctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_update failed"); + goto out; + } + + rv = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_finish failed"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("calculated mac", tag, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Verify the calculated MAC matches the attached MAC */ + cmp = secure_memcmp(tag, mac, POLY1305_TAGLEN); + if (cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + return SSH_ERROR; + } + + /* Decrypt the message */ + rv = mbedtls_chacha20_update(&ctx->main_ctx, encrypted_size, + (uint8_t *)complete_packet + sizeof(uint32_t), + out); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + goto out; + } + + ret = SSH_OK; +out: + return ret; +} + +static void +chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int ret; + + /* Prepare the Poly1305 key */ + ret = chacha20_poly1305_packet_setup(cipher, seq, 1); + if (ret != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); + return; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("plaintext length", + (unsigned char *)&in_packet->length, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + /* step 2, encrypt length field */ + ret = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), + (unsigned char *)&in_packet->length, + (unsigned char *)&out_packet->length); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + return; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", + (unsigned char *)&out_packet->length, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* step 3, encrypt packet payload (main_ctx counter == 1) */ + /* We already did encrypt one block so the counter should be in the correct position */ + ret = mbedtls_chacha20_update(&ctx->main_ctx, len - sizeof(uint32_t), + in_packet->payload, out_packet->payload); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + return; + } + + /* step 4, compute the MAC */ + ret = mbedtls_poly1305_update(&ctx->poly_ctx, (const unsigned char *)out_packet, len); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_update failed"); + return; + } + ret = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_finish failed"); + return; + } +} +#endif /* defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) */ + + +static void cipher_cleanup(struct ssh_cipher_struct *cipher) +{ + mbedtls_cipher_free(&cipher->encrypt_ctx); + mbedtls_cipher_free(&cipher->decrypt_ctx); +#ifdef MBEDTLS_GCM_C + mbedtls_gcm_free(&cipher->gcm_ctx); +#endif /* MBEDTLS_GCM_C */ +} + +#ifdef WITH_INSECURE_NONE +static void +none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) +{ + memcpy(out, in, len); +} +#endif /* WITH_INSECURE_NONE */ + +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef HAVE_BLOWFISH + { + .name = "blowfish-cbc", + .blocksize = 8, + .keysize = 128, + .type = MBEDTLS_CIPHER_BLOWFISH_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, +#endif /* HAVE_BLOWFISH */ + { + .name = "aes128-ctr", + .blocksize = 16, + .keysize = 128, + .type = MBEDTLS_CIPHER_AES_128_CTR, + .set_encrypt_key = cipher_set_encrypt_key, + .set_decrypt_key = cipher_set_decrypt_key, + .encrypt = cipher_encrypt, + .decrypt = cipher_decrypt, + .cleanup = cipher_cleanup + }, + { + .name = "aes192-ctr", + .blocksize = 16, + .keysize = 192, + .type = MBEDTLS_CIPHER_AES_192_CTR, + .set_encrypt_key = cipher_set_encrypt_key, + .set_decrypt_key = cipher_set_decrypt_key, + .encrypt = cipher_encrypt, + .decrypt = cipher_decrypt, + .cleanup = cipher_cleanup + }, + { + .name = "aes256-ctr", + .blocksize = 16, + .keysize = 256, + .type = MBEDTLS_CIPHER_AES_256_CTR, + .set_encrypt_key = cipher_set_encrypt_key, + .set_decrypt_key = cipher_set_decrypt_key, + .encrypt = cipher_encrypt, + .decrypt = cipher_decrypt, + .cleanup = cipher_cleanup + }, + { + .name = "aes128-cbc", + .blocksize = 16, + .keysize = 128, + .type = MBEDTLS_CIPHER_AES_128_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, + { + .name = "aes192-cbc", + .blocksize = 16, + .keysize = 192, + .type = MBEDTLS_CIPHER_AES_192_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, + { + .name = "aes256-cbc", + .blocksize = 16, + .keysize = 256, + .type = MBEDTLS_CIPHER_AES_256_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, +#ifdef MBEDTLS_GCM_C + { + .name = "aes128-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .type = MBEDTLS_CIPHER_AES_128_GCM, + .set_encrypt_key = cipher_set_key_gcm, + .set_decrypt_key = cipher_set_key_gcm, + .aead_encrypt = cipher_encrypt_gcm, + .aead_decrypt_length = cipher_gcm_get_length, + .aead_decrypt = cipher_decrypt_gcm, + .cleanup = cipher_cleanup + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .type = MBEDTLS_CIPHER_AES_256_GCM, + .set_encrypt_key = cipher_set_key_gcm, + .set_decrypt_key = cipher_set_key_gcm, + .aead_encrypt = cipher_encrypt_gcm, + .aead_decrypt_length = cipher_gcm_get_length, + .aead_decrypt = cipher_decrypt_gcm, + .cleanup = cipher_cleanup + }, +#endif /* MBEDTLS_GCM_C */ + { + .name = "3des-cbc", + .blocksize = 8, + .keysize = 192, + .type = MBEDTLS_CIPHER_DES_EDE3_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, + { +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 2 * CHACHA20_KEYLEN * 8, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_poly1305_set_key, + .set_decrypt_key = chacha20_poly1305_set_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_poly1305_cleanup +#else + .name = "chacha20-poly1305@openssh.com" +#endif + }, +#ifdef WITH_INSECURE_NONE + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt, + }, +#endif /* WITH_INSECURE_NONE */ + { + .name = NULL, + .blocksize = 0, + .keysize = 0, + .set_encrypt_key = NULL, + .set_decrypt_key = NULL, + .encrypt = NULL, + .decrypt = NULL, + .cleanup = NULL + } +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +int ssh_crypto_init(void) +{ + UNUSED_VAR(size_t i); + int rc; + + if (libmbedcrypto_initialized) { + return SSH_OK; + } + + mbedtls_entropy_init(&ssh_mbedtls_entropy); + mbedtls_ctr_drbg_init(&ssh_mbedtls_ctr_drbg); + + rc = mbedtls_ctr_drbg_seed(&ssh_mbedtls_ctr_drbg, mbedtls_entropy_func, + &ssh_mbedtls_entropy, NULL, 0); + if (rc != 0) { + mbedtls_ctr_drbg_free(&ssh_mbedtls_ctr_drbg); + } + +#if !(defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C)) + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + ssh_get_chacha20poly1305_cipher(), + sizeof(struct ssh_cipher_struct)); + break; + } + } +#endif + + libmbedcrypto_initialized = 1; + + return SSH_OK; +} + +mbedtls_ctr_drbg_context *ssh_get_mbedtls_ctr_drbg_context(void) +{ + return &ssh_mbedtls_ctr_drbg; +} + +void ssh_crypto_finalize(void) +{ + if (!libmbedcrypto_initialized) { + return; + } + + mbedtls_ctr_drbg_free(&ssh_mbedtls_ctr_drbg); + mbedtls_entropy_free(&ssh_mbedtls_entropy); + + libmbedcrypto_initialized = 0; +} + +#endif /* HAVE_LIBMBEDCRYPTO */ diff --git a/src/libs/libssh-0.12.2/src/libssh.map b/src/libs/libssh-0.12.2/src/libssh.map new file mode 100644 index 000000000000..81e80d759169 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libssh.map @@ -0,0 +1,516 @@ +# This map file was updated with abimap-0.4.0 + +LIBSSH_4_5_0 # Released +{ + global: + _ssh_log; + buffer_free; + buffer_get; + buffer_get_len; + buffer_new; + channel_accept_x11; + channel_change_pty_size; + channel_close; + channel_forward_accept; + channel_forward_cancel; + channel_forward_listen; + channel_free; + channel_get_exit_status; + channel_get_session; + channel_is_closed; + channel_is_eof; + channel_is_open; + channel_new; + channel_open_forward; + channel_open_session; + channel_poll; + channel_read; + channel_read_buffer; + channel_read_nonblocking; + channel_request_env; + channel_request_exec; + channel_request_pty; + channel_request_pty_size; + channel_request_send_signal; + channel_request_sftp; + channel_request_shell; + channel_request_subsystem; + channel_request_x11; + channel_select; + channel_send_eof; + channel_set_blocking; + channel_write; + channel_write_stderr; + privatekey_free; + privatekey_from_file; + publickey_free; + publickey_from_file; + publickey_from_privatekey; + publickey_to_string; + sftp_async_read; + sftp_async_read_begin; + sftp_attributes_free; + sftp_canonicalize_path; + sftp_chmod; + sftp_chown; + sftp_client_message_free; + sftp_client_message_get_data; + sftp_client_message_get_filename; + sftp_client_message_get_flags; + sftp_client_message_get_type; + sftp_client_message_set_filename; + sftp_close; + sftp_closedir; + sftp_dir_eof; + sftp_extension_supported; + sftp_extensions_get_count; + sftp_extensions_get_data; + sftp_extensions_get_name; + sftp_file_set_blocking; + sftp_file_set_nonblocking; + sftp_free; + sftp_fstat; + sftp_fstatvfs; + sftp_fsync; + sftp_get_client_message; + sftp_get_error; + sftp_handle; + sftp_handle_alloc; + sftp_handle_remove; + sftp_init; + sftp_lstat; + sftp_mkdir; + sftp_new; + sftp_new_channel; + sftp_open; + sftp_opendir; + sftp_read; + sftp_readdir; + sftp_readlink; + sftp_rename; + sftp_reply_attr; + sftp_reply_data; + sftp_reply_handle; + sftp_reply_name; + sftp_reply_names; + sftp_reply_names_add; + sftp_reply_status; + sftp_rewind; + sftp_rmdir; + sftp_seek; + sftp_seek64; + sftp_send_client_message; + sftp_server_init; + sftp_server_new; + sftp_server_version; + sftp_setstat; + sftp_stat; + sftp_statvfs; + sftp_statvfs_free; + sftp_symlink; + sftp_tell; + sftp_tell64; + sftp_unlink; + sftp_utimes; + sftp_write; + ssh_accept; + ssh_add_channel_callbacks; + ssh_auth_list; + ssh_basename; + ssh_bind_accept; + ssh_bind_accept_fd; + ssh_bind_fd_toaccept; + ssh_bind_free; + ssh_bind_get_fd; + ssh_bind_listen; + ssh_bind_new; + ssh_bind_options_set; + ssh_bind_set_blocking; + ssh_bind_set_callbacks; + ssh_bind_set_fd; + ssh_blocking_flush; + ssh_buffer_add_data; + ssh_buffer_free; + ssh_buffer_get; + ssh_buffer_get_data; + ssh_buffer_get_len; + ssh_buffer_new; + ssh_buffer_reinit; + ssh_channel_accept_forward; + ssh_channel_accept_x11; + ssh_channel_cancel_forward; + ssh_channel_change_pty_size; + ssh_channel_close; + ssh_channel_free; + ssh_channel_get_exit_status; + ssh_channel_get_session; + ssh_channel_is_closed; + ssh_channel_is_eof; + ssh_channel_is_open; + ssh_channel_listen_forward; + ssh_channel_new; + ssh_channel_open_auth_agent; + ssh_channel_open_forward; + ssh_channel_open_reverse_forward; + ssh_channel_open_session; + ssh_channel_open_x11; + ssh_channel_poll; + ssh_channel_poll_timeout; + ssh_channel_read; + ssh_channel_read_nonblocking; + ssh_channel_read_timeout; + ssh_channel_request_auth_agent; + ssh_channel_request_env; + ssh_channel_request_exec; + ssh_channel_request_pty; + ssh_channel_request_pty_size; + ssh_channel_request_send_break; + ssh_channel_request_send_exit_signal; + ssh_channel_request_send_exit_status; + ssh_channel_request_send_signal; + ssh_channel_request_sftp; + ssh_channel_request_shell; + ssh_channel_request_subsystem; + ssh_channel_request_x11; + ssh_channel_select; + ssh_channel_send_eof; + ssh_channel_set_blocking; + ssh_channel_set_counter; + ssh_channel_window_size; + ssh_channel_write; + ssh_channel_write_stderr; + ssh_clean_pubkey_hash; + ssh_connect; + ssh_connector_free; + ssh_connector_new; + ssh_connector_set_in_channel; + ssh_connector_set_in_fd; + ssh_connector_set_out_channel; + ssh_connector_set_out_fd; + ssh_copyright; + ssh_dirname; + ssh_disconnect; + ssh_dump_knownhost; + ssh_event_add_connector; + ssh_event_add_fd; + ssh_event_add_session; + ssh_event_dopoll; + ssh_event_free; + ssh_event_new; + ssh_event_remove_connector; + ssh_event_remove_fd; + ssh_event_remove_session; + ssh_execute_message_callbacks; + ssh_finalize; + ssh_forward_accept; + ssh_forward_cancel; + ssh_forward_listen; + ssh_free; + ssh_get_cipher_in; + ssh_get_cipher_out; + ssh_get_clientbanner; + ssh_get_disconnect_message; + ssh_get_error; + ssh_get_error_code; + ssh_get_fd; + ssh_get_hexa; + ssh_get_hmac_in; + ssh_get_hmac_out; + ssh_get_issue_banner; + ssh_get_kex_algo; + ssh_get_log_callback; + ssh_get_log_level; + ssh_get_log_userdata; + ssh_get_openssh_version; + ssh_get_poll_flags; + ssh_get_pubkey; + ssh_get_pubkey_hash; + ssh_get_publickey; + ssh_get_publickey_hash; + ssh_get_random; + ssh_get_server_publickey; + ssh_get_serverbanner; + ssh_get_status; + ssh_get_version; + ssh_getpass; + ssh_gssapi_get_creds; + ssh_gssapi_set_creds; + ssh_handle_key_exchange; + ssh_init; + ssh_is_blocking; + ssh_is_connected; + ssh_is_server_known; + ssh_key_cmp; + ssh_key_free; + ssh_key_is_private; + ssh_key_is_public; + ssh_key_new; + ssh_key_type; + ssh_key_type_from_name; + ssh_key_type_to_char; + ssh_known_hosts_parse_line; + ssh_knownhosts_entry_free; + ssh_log; + ssh_message_auth_interactive_request; + ssh_message_auth_kbdint_is_response; + ssh_message_auth_password; + ssh_message_auth_pubkey; + ssh_message_auth_publickey; + ssh_message_auth_publickey_state; + ssh_message_auth_reply_pk_ok; + ssh_message_auth_reply_pk_ok_simple; + ssh_message_auth_reply_success; + ssh_message_auth_set_methods; + ssh_message_auth_user; + ssh_message_channel_request_channel; + ssh_message_channel_request_command; + ssh_message_channel_request_env_name; + ssh_message_channel_request_env_value; + ssh_message_channel_request_open_destination; + ssh_message_channel_request_open_destination_port; + ssh_message_channel_request_open_originator; + ssh_message_channel_request_open_originator_port; + ssh_message_channel_request_open_reply_accept; + ssh_message_channel_request_pty_height; + ssh_message_channel_request_pty_pxheight; + ssh_message_channel_request_pty_pxwidth; + ssh_message_channel_request_pty_term; + ssh_message_channel_request_pty_width; + ssh_message_channel_request_reply_success; + ssh_message_channel_request_subsystem; + ssh_message_channel_request_x11_auth_cookie; + ssh_message_channel_request_x11_auth_protocol; + ssh_message_channel_request_x11_screen_number; + ssh_message_channel_request_x11_single_connection; + ssh_message_free; + ssh_message_get; + ssh_message_global_request_address; + ssh_message_global_request_port; + ssh_message_global_request_reply_success; + ssh_message_reply_default; + ssh_message_retrieve; + ssh_message_service_reply_success; + ssh_message_service_service; + ssh_message_subtype; + ssh_message_type; + ssh_mkdir; + ssh_new; + ssh_options_copy; + ssh_options_get; + ssh_options_get_port; + ssh_options_getopt; + ssh_options_parse_config; + ssh_options_set; + ssh_pcap_file_close; + ssh_pcap_file_free; + ssh_pcap_file_new; + ssh_pcap_file_open; + ssh_pki_copy_cert_to_privkey; + ssh_pki_export_privkey_file; + ssh_pki_export_privkey_to_pubkey; + ssh_pki_export_pubkey_base64; + ssh_pki_export_pubkey_file; + ssh_pki_generate; + ssh_pki_import_cert_base64; + ssh_pki_import_cert_file; + ssh_pki_import_privkey_base64; + ssh_pki_import_privkey_file; + ssh_pki_import_pubkey_base64; + ssh_pki_import_pubkey_file; + ssh_pki_key_ecdsa_name; + ssh_print_hexa; + ssh_privatekey_type; + ssh_publickey_to_file; + ssh_remove_channel_callbacks; + ssh_scp_accept_request; + ssh_scp_close; + ssh_scp_deny_request; + ssh_scp_free; + ssh_scp_init; + ssh_scp_leave_directory; + ssh_scp_new; + ssh_scp_pull_request; + ssh_scp_push_directory; + ssh_scp_push_file; + ssh_scp_push_file64; + ssh_scp_read; + ssh_scp_request_get_filename; + ssh_scp_request_get_permissions; + ssh_scp_request_get_size; + ssh_scp_request_get_size64; + ssh_scp_request_get_warning; + ssh_scp_write; + ssh_select; + ssh_send_debug; + ssh_send_ignore; + ssh_send_keepalive; + ssh_server_init_kex; + ssh_service_request; + ssh_session_export_known_hosts_entry; + ssh_session_has_known_hosts_entry; + ssh_session_is_known_server; + ssh_session_update_known_hosts; + ssh_set_agent_channel; + ssh_set_agent_socket; + ssh_set_auth_methods; + ssh_set_blocking; + ssh_set_callbacks; + ssh_set_channel_callbacks; + ssh_set_counters; + ssh_set_fd_except; + ssh_set_fd_toread; + ssh_set_fd_towrite; + ssh_set_log_callback; + ssh_set_log_level; + ssh_set_log_userdata; + ssh_set_message_callback; + ssh_set_pcap_file; + ssh_set_server_callbacks; + ssh_silent_disconnect; + ssh_string_burn; + ssh_string_copy; + ssh_string_data; + ssh_string_fill; + ssh_string_free; + ssh_string_free_char; + ssh_string_from_char; + ssh_string_get_char; + ssh_string_len; + ssh_string_new; + ssh_string_to_char; + ssh_threads_get_noop; + ssh_threads_get_pthread; + ssh_threads_set_callbacks; + ssh_try_publickey_from_file; + ssh_userauth_agent; + ssh_userauth_agent_pubkey; + ssh_userauth_autopubkey; + ssh_userauth_gssapi; + ssh_userauth_kbdint; + ssh_userauth_kbdint_getanswer; + ssh_userauth_kbdint_getinstruction; + ssh_userauth_kbdint_getname; + ssh_userauth_kbdint_getnanswers; + ssh_userauth_kbdint_getnprompts; + ssh_userauth_kbdint_getprompt; + ssh_userauth_kbdint_setanswer; + ssh_userauth_list; + ssh_userauth_none; + ssh_userauth_offer_pubkey; + ssh_userauth_password; + ssh_userauth_privatekey_file; + ssh_userauth_pubkey; + ssh_userauth_publickey; + ssh_userauth_publickey_auto; + ssh_userauth_try_publickey; + ssh_version; + ssh_write_knownhost; + string_burn; + string_copy; + string_data; + string_fill; + string_free; + string_from_char; + string_len; + string_new; + string_to_char; + local: + *; +} ; + +LIBSSH_4_6_0 # Released +{ + global: + ssh_print_hash; +} LIBSSH_4_5_0; + +LIBSSH_4_7_0 # Released +{ + global: + sftp_client_message_get_submessage; + ssh_get_fingerprint_hash; + ssh_pki_export_privkey_base64; +} LIBSSH_4_6_0; + +LIBSSH_4_8_0 # Released +{ + global: + sftp_server_free; + ssh_bind_options_parse_config; + ssh_channel_open_forward_unix; + ssh_message_channel_request_open_reply_accept_channel; +} LIBSSH_4_7_0; + +LIBSSH_4_8_1 # Released +{ + global: + ssh_session_get_known_hosts_entry; + ssh_threads_get_default; +} LIBSSH_4_8_0; + +LIBSSH_4_9_0 # Released +{ + global: + ssh_channel_open_forward_port; + ssh_key_dup; + ssh_send_issue_banner; + ssh_session_set_disconnect_message; + ssh_userauth_publickey_auto_get_current_identity; + ssh_vlog; +} LIBSSH_4_8_1; + +LIBSSH_4_10_0 # Released +{ + global: + sftp_aio_begin_read; + sftp_aio_begin_write; + sftp_aio_free; + sftp_aio_wait_read; + sftp_aio_wait_write; + sftp_channel_default_data_callback; + sftp_channel_default_subsystem_request; + sftp_expand_path; + sftp_hardlink; + sftp_home_directory; + sftp_limits; + sftp_limits_free; + sftp_lsetstat; + ssh_channel_get_exit_state; + ssh_channel_request_pty_size_modes; + ssh_pki_export_privkey_base64_format; + ssh_pki_export_privkey_file_format; + ssh_request_no_more_sessions; +} LIBSSH_4_9_0; + +LIBSSH_4_11_0 # Released +{ + global: + sftp_get_users_groups_by_id; + sftp_name_id_map_free; + sftp_name_id_map_new; + ssh_get_supported_methods; + ssh_key_get_sk_application; + ssh_key_get_sk_flags; + ssh_key_get_sk_user_id; + ssh_pki_ctx_free; + ssh_pki_ctx_get_sk_attestation_buffer; + ssh_pki_ctx_new; + ssh_pki_ctx_options_set; + ssh_pki_ctx_set_sk_pin_callback; + ssh_pki_ctx_sk_callbacks_option_set; + ssh_pki_ctx_sk_callbacks_options_clear; + ssh_pki_generate_key; + ssh_sk_resident_keys_load; + ssh_string_cmp; + ssh_string_from_data; + sshsig_sign; + sshsig_verify; +} LIBSSH_4_10_0; + +LIBSSH_4_12_0 # Released +{ + global: + ssh_session_kex_is_gss; + ssh_userauth_gssapi_keyex; +} LIBSSH_4_11_0; + diff --git a/src/libs/libssh-0.12.2/src/log.c b/src/libs/libssh-0.12.2/src/log.c new file mode 100644 index 000000000000..8bc8ccabdee9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/log.c @@ -0,0 +1,262 @@ +/* + * log.c - logging and debugging functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#ifdef HAVE_SYS_UTIME_H +#include +#endif /* HAVE_SYS_UTIME_H */ +#include + +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +#ifndef LOG_SIZE +#define LOG_SIZE 1024 +#endif + +static LIBSSH_THREAD int ssh_log_level; +static LIBSSH_THREAD ssh_logging_callback ssh_log_cb; +static LIBSSH_THREAD void *ssh_log_userdata = NULL; + +/** + * @defgroup libssh_log The SSH logging functions + * @ingroup libssh + * + * Logging functions for debugging and problem resolving. + * + * @{ + */ + +static int current_timestring(int hires, char *buf, size_t len) +{ + char tbuf[64]; + struct timeval tv; + struct tm tm, *tm_ptr = NULL; + time_t t; + + gettimeofday(&tv, NULL); + t = (time_t) tv.tv_sec; + + tm_ptr = localtime_r(&t, &tm); + if (tm_ptr == NULL) { + return -1; + } + + if (hires) { + strftime(tbuf, sizeof(tbuf), "%Y/%m/%d %H:%M:%S", &tm); + snprintf(buf, len, "%s.%06ld", tbuf, (long)tv.tv_usec); + } else { + strftime(tbuf, sizeof(tbuf), "%Y/%m/%d %H:%M:%S", &tm); + snprintf(buf, len, "%s", tbuf); + } + + return 0; +} + +static void ssh_log_stderr(int verbosity, + const char *function, + const char *buffer) +{ + char date[128] = {0}; + int rc; + + rc = current_timestring(1, date, sizeof(date)); + if (rc == 0) { + fprintf(stderr, "[%s, %d] %s:", date, verbosity, function); + } else { + fprintf(stderr, "[%d] %s", verbosity, function); + } + + fprintf(stderr, " %s\n", buffer); +} + +static void ssh_log_custom(ssh_logging_callback log_fn, + int verbosity, + const char *function, + const char *buffer) +{ + char buf[LOG_SIZE + 64]; + + snprintf(buf, sizeof(buf), "%s: %s", function, buffer); + log_fn(verbosity, function, buf, ssh_get_log_userdata()); +} + +void ssh_log_function(int verbosity, + const char *function, + const char *buffer) +{ + ssh_logging_callback log_fn = ssh_get_log_callback(); + + if (log_fn) { + ssh_log_custom(log_fn, verbosity, function, buffer); + return; + } + + ssh_log_stderr(verbosity, function, buffer); +} + +void ssh_vlog(int verbosity, + const char *function, + const char *format, + va_list *va) +{ + char buffer[LOG_SIZE]; + + vsnprintf(buffer, sizeof(buffer), format, *va); + ssh_log_function(verbosity, function, buffer); +} + +void _ssh_log(int verbosity, + const char *function, + const char *format, ...) +{ + va_list va; + + if (verbosity <= ssh_get_log_level()) { + va_start(va, format); + ssh_vlog(verbosity, function, format, &va); + va_end(va); + } +} + +/* LEGACY */ + +void ssh_log(ssh_session session, + int verbosity, + const char *format, ...) +{ + va_list va; + + if (verbosity <= session->common.log_verbosity) { + va_start(va, format); + ssh_vlog(verbosity, "", format, &va); + va_end(va); + } +} + +/** @internal + * @brief log a SSH event with a common pointer + * @param common The SSH/bind session. + * @param verbosity The verbosity of the event. + * @param format The format string of the log entry. + */ +void ssh_log_common(struct ssh_common_struct *common, + int verbosity, + const char *function, + const char *format, ...) +{ + va_list va; + + if (verbosity <= common->log_verbosity) { + va_start(va, format); + ssh_vlog(verbosity, function, format, &va); + va_end(va); + } +} + + +/* PUBLIC */ + +/** + * @brief Set the log level of the library. + * + * @param[in] level The level to set. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_set_log_level(int level) { + if (level < 0) { + return SSH_ERROR; + } + + ssh_log_level = level; + + return SSH_OK; +} + +/** + * @brief Get the log level of the library. + * + * @return The value of the log level. + */ +int ssh_get_log_level(void) { + return ssh_log_level; +} + +int ssh_set_log_callback(ssh_logging_callback cb) { + if (cb == NULL) { + return SSH_ERROR; + } + + ssh_log_cb = cb; + + return SSH_OK; +} + +void +_ssh_reset_log_cb(void) +{ + ssh_log_cb = NULL; +} + +ssh_logging_callback ssh_get_log_callback(void) { + return ssh_log_cb; +} + +/** + * @brief Get the userdata of the logging function. + * + * @return The userdata if set or NULL. + */ +void *ssh_get_log_userdata(void) +{ + if (ssh_log_userdata == NULL) { + return NULL; + } + + return ssh_log_userdata; +} + +/** + * @brief Set the userdata for the logging function. + * + * @param[in] data The userdata to set. + * + * @return SSH_OK on success. + */ +int ssh_set_log_userdata(void *data) +{ + ssh_log_userdata = data; + + return 0; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/match.c b/src/libs/libssh-0.12.2/src/match.c new file mode 100644 index 000000000000..40b9c0f8f491 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/match.c @@ -0,0 +1,615 @@ +/* + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * Simple pattern matching, with '*' and '?' as wildcards. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +/* + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#endif + +/* for systems without IPv6 support matching should still work */ +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + +#include "libssh/priv.h" + +/** + * @brief Compare a string with a pattern containing wildcards `*` and `?` + * + * This function is an iterative replacement for the previously recursive + * implementation to avoid exponential complexity (DoS) with specific patterns. + * + * @param[in] s The string to match. + * @param[in] pattern The pattern to match against. + * + * @return 1 if the pattern matches, 0 otherwise. + */ +static int match_pattern(const char *s, const char *pattern) +{ + const char *s_star = NULL; /* Position in s when last `*` was met */ + const char *p_star = NULL; /* Position in pattern after last `*` */ + + if (s == NULL || pattern == NULL) { + return 0; + } + + while (*s) { + /* Case 1: Exact match or '?' wildcard */ + if (*pattern == *s || *pattern == '?') { + s++; + pattern++; + continue; + } + + /* Case 2: '*' wildcard */ + if (*pattern == '*') { + /* Record the position of the star and the current string position. + * We optimistically assume * matches 0 characters first. + */ + p_star = ++pattern; + s_star = s; + continue; + } + + /* Case 3: Mismatch */ + if (p_star) { + /* If we have seen a star previously, backtrack. + * We restore the pattern to just after the star, + * but advance the string position (consume one more char for the + * star). + * No need to backtrack to previous stars as any match of the last + * star could be eaten the same way by the previous star. + */ + pattern = p_star; + s = ++s_star; + continue; + } + + /* Case 4: Mismatch and no star to backtrack to */ + return 0; + } + + /* Handle trailing stars in the pattern + * (e.g., pattern "abc*" matching "abc") */ + while (*pattern == '*') { + pattern++; + } + + /* If we reached the end of the pattern, it's a match */ + return (*pattern == '\0'); +} + +/* + * Tries to match the string against the comma-separated sequence of subpatterns + * (each possibly preceded by ! to indicate negation). + * Returns -1 if negation matches, 1 if there is a positive match, 0 if there is + * no match at all. + */ +int match_pattern_list(const char *string, const char *pattern, + size_t len, int dolower) { + char sub[1024]; + int negated; + int got_positive; + size_t i, subi; + + got_positive = 0; + for (i = 0; i < len;) { + /* Check if the subpattern is negated. */ + if (pattern[i] == '!') { + negated = 1; + i++; + } else { + negated = 0; + } + + /* + * Extract the subpattern up to a comma or end. Convert the + * subpattern to lowercase. + */ + for (subi = 0; + i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; + subi++, i++) { + sub[subi] = dolower && isupper(pattern[i]) ? + (char)tolower(pattern[i]) : pattern[i]; + } + + /* If subpattern too long, return failure (no match). */ + if (subi >= sizeof(sub) - 1) { + return 0; + } + + /* If the subpattern was terminated by a comma, skip the comma. */ + if (i < len && pattern[i] == ',') { + i++; + } + + /* Null-terminate the subpattern. */ + sub[subi] = '\0'; + + /* Try to match the subpattern against the string. */ + if (match_pattern(string, sub)) { + if (negated) { + return -1; /* Negative */ + } else { + got_positive = 1; /* Positive */ + } + } + } + + /* + * Return success if got a positive match. If there was a negative + * match, we have already returned -1 and never get here. + */ + return got_positive; +} + +/* + * Tries to match the host name (which must be in all lowercase) against the + * comma-separated sequence of subpatterns (each possibly preceded by ! to + * indicate negation). + * Returns -1 if negation matches, 1 if there is a positive match, 0 if there + * is no match at all. + */ +int +match_hostname(const char *host, const char *pattern, size_t len) +{ + return match_pattern_list(host, pattern, len, 1); +} + +#ifndef _WIN32 +/** + * @brief Tries to match the host IPv6 address against a given network address + * with specified prefix length in CIDR notation. + * + * @param[in] host_addr The host address to verify. + * + * @param[in] net_addr The network id address against which the match is + * being verified + * + * @param[in] bits The prefix length + * + * @return 0 on a negative match. + * @return 1 on a positive match. + */ +static int +cidr_match_6(struct in6_addr *host_addr, + struct in6_addr *net_addr, + unsigned int bits) +{ + const uint8_t *a = host_addr->s6_addr; + const uint8_t *b = net_addr->s6_addr; + + unsigned int byte_whole, bits_left; + + /* The number of a complete byte covered by the prefix */ + byte_whole = bits / 8; + + /* + * The number of bits remaining in the incomplete (last) byte + * covered by the prefix + */ + bits_left = bits % 8; + + if (byte_whole) { + if (memcmp(a, b, byte_whole) != 0) { + return 0; + } + } + + if (bits_left) { + if ((a[byte_whole] ^ b[byte_whole]) & (0xFFu << (8 - bits_left))) { + return 0; + } + } + + return 1; +} + +/** + * @brief Tries to match the host IPv4 address against a given network address + * with specified prefix length in CIDR notation. + * + * @param[in] host_addr The host address to verify. + * + * @param[in] net_addr The network id address against which the match is + * being verified + * + * @param[in] bits The prefix length + * + * @return 0 on a negative match. + * @return 1 on a positive match. + */ +static int +cidr_match_4(struct in_addr *host_addr, + struct in_addr *net_addr, + unsigned int bits) +{ + if (bits == 0) { + /* C99 6.5.7 (3): u32 << 32 is undefined behaviour */ + return 1; + } + + return !((host_addr->s_addr ^ net_addr->s_addr) & + htonl((0xFFFFFFFFu << (32 - bits)) & 0xFFFFFFFFu)); +} + +/** + * @brief Checks if the mask length is valid according to the address family + * (IPv4 or IPv6). + * + * @param[in] family The address family (e.g. AF_INET or AF_INET6) + * + * @param[in] mask The subnet mask (prefix) + * + * @return true if the mask length does not exceed the maximum valid length + * according to the address family (IPv4 or IPv6). + * @return false if the mask length exceeds the maximum valid length + * or there is no match with IPv4 or IPv6 address family. + */ +static bool +masklen_valid(int family, unsigned int mask) +{ + switch (family) { + case AF_INET: + return mask <= 32; + case AF_INET6: + return mask <= 128; + default: + return false; + } +} + +/** + * @brief Extracts address family given a network address. + * + * @param[in] address The network address. + * + * @return The value of the address family if no errors. + * @return -1 in case of errors. + */ +static int +get_address_family(const char *address) +{ + struct addrinfo hints, *ai = NULL; + int rc = -1, rv; + + ZERO_STRUCT(hints); + if (address == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad arguments"); + goto out; + } + + hints.ai_flags = AI_NUMERICHOST; + rv = getaddrinfo(address, NULL, &hints, &ai); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't get address information - getaddrinfo() failed: %s", + gai_strerror(rv)); + goto out; + } + + rc = ai->ai_family; + freeaddrinfo(ai); + +out: + return rc; +} + +/** + * @brief Tries to match the host address against a CIDR list provided + * by the user. If the host address family is unknown, it can be derived by + * passing -1 as sa_family argument. + * + * It can be also used to validate a CIDR list when the passed address is NULL + * and sa_family is -1. + * + * @param[in] address The host address to verify (NULL to validate CIDR list). + * + * @param[in] addrlist The CIDR list against which the match is being verified. + * The CIDR list can contain both IPv4 and IPv6 addresses + * and has to be comma separated + * (',' only, space after comma not allowed). + * + * @param[in] sa_family The socket address family (e.g. AF_INET or AF_INET6, + * -1 to validate CIDR list or unknown address family). + * + * @usage To validate CIDR list: match_cidr_address_list(NULL, addrlist, -1). + * @usage To verify a match with unknown address family: + * match_cidr_address_list(address, addrlist, -1). + * @return 1 only on positive match. + * @return 0 on negative match or valid CIDR list. + * @return -1 on errors or invalid CIDR list. + */ +int +match_cidr_address_list(const char *address, + const char *addrlist, + int sa_family) +{ + char *list = NULL, *cp = NULL, *a = NULL, *b = NULL, *sp = NULL; + char addr_buffer[64], addr[NI_MAXHOST]; + struct in_addr try_addr, match_addr; + struct in6_addr try_addr6, match_addr6; + unsigned long mask_len; + size_t addr_len, tmp_len; + int rc = 0, r, ai_family; + + ZERO_STRUCT(try_addr); + ZERO_STRUCT(try_addr6); + ZERO_STRUCT(match_addr); + ZERO_STRUCT(match_addr6); + + if (sa_family != AF_INET && sa_family != AF_INET6 && sa_family != -1) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid argument: sa_family %d is not valid", + sa_family); + return -1; + } + + if (address != NULL) { + strncpy(addr, address, NI_MAXHOST - 1); + + /* Remove interface in case of IPv6 address: addr%interface */ + a = strchr(addr, '%'); + if (a != NULL) { + *a = '\0'; + } + + /* + * If sa_family is set to -1 and address is not NULL then + * the socket address family should be derived + */ + if (sa_family == -1) { + r = get_address_family(addr); + if (r == -1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to derive address family for address " + "\"%.100s\"", + addr); + return -1; + } + sa_family = r; + } + + /* + * Translate host address from dot notation to binary network format + * according to family type, + * i.e. IPv4 (store in in_addr) or IPv6 (store in in6_addr) + */ + if (sa_family == AF_INET) { + if (inet_pton(AF_INET, addr, &try_addr) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv4 address \"%.100s\"", + addr); + return -1; + } + } else if (sa_family == AF_INET6) { + if (inet_pton(AF_INET6, addr, &try_addr6) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv6 address \"%.100s\"", + addr); + return -1; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Address family %d for address \"%.100s\" " + "is not recognized", + sa_family, + addr); + return -1; + } + } + + b = list = strdup(addrlist); + if (b == NULL) { + return -1; + } + + while ((cp = strsep(&list, ",")) != NULL) { + if (*cp == '\0') { + SSH_LOG(SSH_LOG_TRACE, "Empty entry in list \"%.100s\"", b); + rc = -1; + break; + } + + /* + * Stop junk from reaching address translation. +3 for the "/prefix". + * INET6_ADDRSTRLEN is 46 and includes space for '\0' terminator. The + * maximum IPv6 address printable is the one that carries IPv4 too. + * E.g. ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255 is 46 chars + * long ('\0' included) and the maximum prefix length possible is 96. + * This explains why +3. All the other IPv6 addresses with maximum /127 + * prefix length (39 + 4) are covered just by INET6_ADDRSTRLEN itself + */ + addr_len = strlen(cp); + if (addr_len > INET6_ADDRSTRLEN + 3) { + SSH_LOG(SSH_LOG_TRACE, + "List entry \"%.100s\" too long: %zu > %d (MAX ALLOWED)", + cp, + addr_len, + INET6_ADDRSTRLEN + 3); + rc = -1; + break; + } + +#define VALID_CIDR_CHARS "0123456789abcdefABCDEF.:/" + tmp_len = strspn(cp, VALID_CIDR_CHARS); + if (tmp_len != addr_len) { + SSH_LOG(SSH_LOG_TRACE, + "List entry \"%.100s\" contains invalid characters " + "-> \"%c\" is an invalid character", + cp, + cp[tmp_len]); + rc = -1; + break; + } +#undef VALID_CIDR_CHARS + + strncpy(addr_buffer, cp, sizeof(addr_buffer) - 1); + sp = strchr(addr_buffer, '/'); + if (sp != NULL) { + *sp = '\0'; + sp++; + mask_len = strtoul(sp, &cp, 10); + if (*sp < '0' || *sp > '9' || *cp != '\0') { + SSH_LOG(SSH_LOG_TRACE, "Error while parsing prefix: %s", sp); + rc = -1; + break; + } + if (mask_len > 128) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid prefix: %lu exceeds the maximum allowed " + "(>128)", + mask_len); + rc = -1; + break; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Missing prefix length for list entry \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + + ai_family = get_address_family(addr_buffer); + if (ai_family == -1) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't get address family for \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + + if (ai_family == AF_INET) { + if (inet_pton(AF_INET, addr_buffer, &match_addr) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv4 address \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + } else if (ai_family == AF_INET6) { + if (inet_pton(AF_INET6, addr_buffer, &match_addr6) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv6 address \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Address family %d for address \"%.100s\" " + "is not recognized", + ai_family, + addr_buffer); + rc = -1; + break; + } + + if (masklen_valid(ai_family, mask_len) != true) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid mask length %lu for list entry \"%.100s\"", + mask_len, + addr_buffer); + rc = -1; + break; + } + + /* Verify match between host address and network address*/ + if (((ai_family == AF_INET && sa_family == AF_INET) && + cidr_match_4(&try_addr, &match_addr, mask_len)) || + ((ai_family == AF_INET6 && sa_family == AF_INET6) && + cidr_match_6(&try_addr6, &match_addr6, mask_len))) { + rc = 1; + break; + } + } + SAFE_FREE(b); + + return rc; +} +#endif /* _WIN32 */ + +/** + * @brief Tries to match an object against a comma separated group of objects + * + * The characters '*' and '?' are NOT considered wildcards and an object in the + * group preceded by a ! does NOT indicate negation. The characters '*', '?' + * and '!' are treated normally like other characters, only ',' (comma) is + * treated specially and is considered as a delimiter that separates objects in + * the group. + * + * @param[in] group Group of objects (comma separated) to match against. + * + * @param[in] object Object to match. + * + * @returns 1 if there is a match, 0 if there is no match at all. + */ +int match_group(const char *group, const char *object) +{ + const char *a = NULL; + const char *z = NULL; + + if (group == NULL || object == NULL) { + return 0; + } + + z = group; + do { + a = strchr(z, ','); + if (a == NULL) { + if (strcmp(z, object) == 0) { + return 1; + } + return 0; + } else { + if (strncmp(z, object, a - z) == 0) { + return 1; + } + } + z = a + 1; + } while (1); + + /* not reached */ + return 0; +} diff --git a/src/libs/libssh-0.12.2/src/mbedcrypto-compat.h b/src/libs/libssh-0.12.2/src/mbedcrypto-compat.h new file mode 100644 index 000000000000..f028854fb8ee --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mbedcrypto-compat.h @@ -0,0 +1,56 @@ +#ifndef MBEDCRYPTO_COMPAT_H +#define MBEDCRYPTO_COMPAT_H + +/* mbedtls/version.h should be available for both v2 and v3 + * v3 defines the version inside build_info.h so if it isn't defined + * in version.h we should have v3 + */ +#include +#include + +#ifndef MBEDTLS_VERSION_MAJOR +#include +#endif /* MBEDTLS_VERSION_MAJOR */ + +#if MBEDTLS_VERSION_MAJOR < 3 + +static inline size_t +mbedtls_cipher_info_get_key_bitlen(const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return info->key_bitlen; +} + +static inline size_t +mbedtls_cipher_info_get_iv_size(const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return (size_t)info->iv_size; +} + +#define MBEDTLS_PRIVATE(X) X + +#ifdef HAVE_MBEDTLS_CURVE25519 +#include + +#define MBEDTLS_ECDH_PRIVATE(X) X +#define MBEDTLS_ECDH_PARAMS(X) X +typedef mbedtls_ecdh_context mbedtls_ecdh_params; +#endif /* HAVE_MBEDTLS_CURVE25519 */ + +#else /* MBEDTLS_VERSION_MAJOR < 3 */ + +#ifdef HAVE_MBEDTLS_CURVE25519 +#include + +#define MBEDTLS_ECDH_PRIVATE(X) MBEDTLS_PRIVATE(X) +#define MBEDTLS_ECDH_PARAMS(X) X.MBEDTLS_PRIVATE(ctx).MBEDTLS_PRIVATE(mbed_ecdh) +typedef mbedtls_ecdh_context_mbed mbedtls_ecdh_params; +#endif /* HAVE_MBEDTLS_CURVE25519 */ + +#endif /* MBEDTLS_VERSION_MAJOR < 3 */ +#endif /* MBEDCRYPTO_COMPAT_H */ diff --git a/src/libs/libssh-0.12.2/src/mbedcrypto_missing.c b/src/libs/libssh-0.12.2/src/mbedcrypto_missing.c new file mode 100644 index 000000000000..2c1a8d7ad148 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mbedcrypto_missing.c @@ -0,0 +1,180 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/libmbedcrypto.h" + +#ifdef HAVE_LIBMBEDCRYPTO +bignum ssh_mbedcry_bn_new(void) +{ + bignum bn; + + bn = malloc(sizeof(mbedtls_mpi)); + if (bn) { + mbedtls_mpi_init(bn); + } + + return bn; +} + +void ssh_mbedcry_bn_free(bignum bn) +{ + mbedtls_mpi_free(bn); + SAFE_FREE(bn); +} + +char *ssh_mbedcry_bn2num(const_bignum num, int radix) +{ + char *buf = NULL; + size_t olen; + int rc; + + rc = mbedtls_mpi_write_string(num, radix, buf, 0, &olen); + if (rc != 0 && rc != MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL) { + return NULL; + } + + buf = mbedtls_calloc(1, olen); + if (buf == NULL) { + return NULL; + } + + rc = mbedtls_mpi_write_string(num, radix, buf, olen, &olen); + if (rc != 0) { + SAFE_FREE(buf); + return NULL; + } + + return buf; +} + +int ssh_mbedcry_rand(bignum rnd, int bits, int top, int bottom) +{ + size_t len; + int rc; + int i; + + if (bits <= 0) { + return 0; + } + + len = bits / 8 + 1; + /* FIXME weird bug: over 1024, fill_random function returns an error code + * MBEDTLS_ERR_MPI_BAD_INPUT_DATA -0x0004 + */ + if (len > 1024){ + len = 1024; + } + rc = mbedtls_mpi_fill_random(rnd, + len, + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + if (rc != 0) { + return 0; + } + + for (i = len * 8 - 1; i >= bits; i--) { + rc = mbedtls_mpi_set_bit(rnd, i, 0); + if (rc != 0) { + return 0; + } + } + + if (top == 0) { + rc = mbedtls_mpi_set_bit(rnd, bits - 1, 0); + if (rc != 0) { + return 0; + } + } + + if (top == 1) { + if (bits < 2) { + return 0; + } + + rc = mbedtls_mpi_set_bit(rnd, bits - 2, 0); + if (rc != 0) { + return 0; + } + } + + if (bottom) { + rc = mbedtls_mpi_set_bit(rnd, 0, 1); + if (rc != 0) { + return 0; + } + } + + return 1; +} + +int ssh_mbedcry_is_bit_set(bignum num, size_t pos) +{ + int bit; + bit = mbedtls_mpi_get_bit(num, pos); + return bit; +} + +/** @brief generates a random integer between 0 and max + * @returns 1 in case of success, 0 otherwise + */ +int ssh_mbedcry_rand_range(bignum dest, bignum max) +{ + size_t bits; + bignum rnd; + int rc; + + bits = bignum_num_bits(max) + 64; + rnd = bignum_new(); + if (rnd == NULL){ + return 0; + } + rc = bignum_rand(rnd, bits); + if (rc != 1) { + bignum_safe_free(rnd); + return rc; + } + mbedtls_mpi_mod_mpi(dest, rnd, max); + bignum_safe_free(rnd); + return 1; +} + +int ssh_mbedcry_hex2bn(bignum *dest, char *data) +{ + int rc; + + *dest = bignum_new(); + if (*dest == NULL){ + return 0; + } + rc = mbedtls_mpi_read_string(*dest, 16, data); + if (rc == 0) { + return 1; + } + + return 0; +} + +#endif diff --git a/src/libs/libssh-0.12.2/src/md_crypto.c b/src/libs/libssh-0.12.2/src/md_crypto.c new file mode 100644 index 000000000000..100f1079d6cf --- /dev/null +++ b/src/libs/libssh-0.12.2/src/md_crypto.c @@ -0,0 +1,373 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libcrypto-compat.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include +#include +#include +#include + +SHACTX +sha1_init(void) +{ + int rc; + SHACTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha1(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha1_ctx_free(SHACTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHACTX c = sha1_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha1_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha1_final(hash, c); +} + +SHA256CTX +sha256_init(void) +{ + int rc; + SHA256CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha256(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHA256CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA256CTX c = sha256_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha256_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha256_final(hash, c); +} + +SHA384CTX +sha384_init(void) +{ + int rc; + SHA384CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha384(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHA384CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA384CTX c = sha384_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha384_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha384_final(hash, c); +} + +SHA512CTX +sha512_init(void) +{ + int rc = 0; + SHA512CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha512(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHA512CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA512CTX c = sha512_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha512_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha512_final(hash, c); +} + +MD5CTX +md5_init(void) +{ + int rc; + MD5CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +md5_ctx_free(MD5CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +/** +* @ brief One-shot MD5. Not intended for use in security-relevant contexts. +*/ +int +md5(const unsigned char *digest, size_t len, unsigned char *hash) +{ + int rc, ret = SSH_ERROR; + unsigned int mdlen = 0; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD *md5 = NULL; +#endif + MD5CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + goto out; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + md5 = EVP_MD_fetch(NULL, "MD5", FIPS_FALLBACK_PROPQ); + if (md5 == NULL) { + goto out; + } + rc = EVP_DigestInit(c, md5); +#else + rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); +#endif + if (rc == 0) { + goto out; + } + + rc = EVP_DigestUpdate(c, digest, len); + if (rc != 1) { + goto out; + } + + rc = EVP_DigestFinal(c, hash, &mdlen); + if (rc != 1) { + goto out; + } + + ret = SSH_OK; + +out: + EVP_MD_CTX_free(c); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD_free(md5); +#endif + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/md_gcrypt.c b/src/libs/libssh-0.12.2/src/md_gcrypt.c new file mode 100644 index 000000000000..c8830773e254 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/md_gcrypt.c @@ -0,0 +1,252 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA1, 0); + + return ctx; +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +void +sha1_ctx_free(SHACTX c) +{ + gcry_md_close(c); +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); + return SSH_OK; +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA256, 0); + + return ctx; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + gcry_md_close(c); +} + +int +sha256_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA256_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); + return SSH_OK; +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA384, 0); + + return ctx; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + gcry_md_close(c); +} + +int +sha384_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA384_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); + return SSH_OK; +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA512, 0); + + return ctx; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + gcry_md_close(c); +} + +int +sha512_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA512_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); + return SSH_OK; +} + +MD5CTX +md5_init(void) +{ + MD5CTX c = NULL; + gcry_md_open(&c, GCRY_MD_MD5, 0); + + return c; +} + +void +md5_ctx_free(MD5CTX c) +{ + gcry_md_close(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, MD5_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int md5(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_MD5, hash, digest, len); + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/md_mbedcrypto.c b/src/libs/libssh-0.12.2/src/md_mbedcrypto.c new file mode 100644 index 000000000000..445f644da118 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/md_mbedcrypto.c @@ -0,0 +1,455 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "mbedcrypto-compat.h" + +#include + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha1_ctx_free(SHACTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha1_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHA256CTX c) +{ + int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHA384CTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha384_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHA512CTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha512_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +MD5CTX +md5_init(void) +{ + MD5CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +md5_ctx_free(MD5CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int md5(const unsigned char *digest, size_t len, unsigned char *hash) +{ + MD5CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + if (md_info == NULL) { + return SSH_ERROR; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return SSH_ERROR; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return SSH_ERROR; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return SSH_ERROR; + } + + rc = mbedtls_md_update(ctx, digest, len); + if (rc != 0) { + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return SSH_ERROR; + } + + rc = mbedtls_md_finish(ctx, hash); + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/messages.c b/src/libs/libssh-0.12.2/src/messages.c new file mode 100644 index 000000000000..561879888d7b --- /dev/null +++ b/src/libs/libssh-0.12.2/src/messages.c @@ -0,0 +1,1980 @@ +/* + * messages.c - message parsing for client and server + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/messages.h" +#include "libssh/socket.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#include "libssh/gssapi.h" +#endif + +/** + * @defgroup libssh_messages The SSH message functions + * @ingroup libssh + * + * This file contains the message parsing utilities for client and server + * programs using libssh. + * + * On the server the main loop of the program will call + * ssh_message_get(session) to get messages as they come. They are not 1-1 with + * the protocol messages. Then, the user will know what kind of a message it is + * and use the appropriate functions to handle it (or use the default handlers + * if you don't know what to do). + * + * @{ + */ + +static ssh_message ssh_message_new(ssh_session session) +{ + ssh_message msg = calloc(1, sizeof(struct ssh_message_struct)); + if (msg == NULL) { + return NULL; + } + msg->session = session; + + /* Set states explicitly */ + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + + return msg; +} + +#ifndef WITH_SERVER + +/* Reduced version of the reply default that only replies with + * SSH_MSG_UNIMPLEMENTED + */ +static int ssh_message_reply_default(ssh_message msg) { + SSH_LOG(SSH_LOG_FUNCTIONS, "Reporting unknown packet"); + + if (ssh_buffer_add_u8(msg->session->out_buffer, SSH2_MSG_UNIMPLEMENTED) < 0) + goto error; + if (ssh_buffer_add_u32(msg->session->out_buffer, + htonl(msg->session->recv_seq-1)) < 0) + goto error; + return ssh_packet_send(msg->session); + error: + return SSH_ERROR; +} + +#endif + +static int ssh_send_disconnect(ssh_session session) +{ + int rc = SSH_ERROR; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->disconnect_message == NULL) { + session->disconnect_message = strdup("Bye Bye"); + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + + if (session->socket != NULL && ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bdss", + SSH2_MSG_DISCONNECT, + SSH2_DISCONNECT_BY_APPLICATION, + session->disconnect_message, + ""); /* language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = ssh_packet_send(session); + ssh_session_socket_close(session); + } + + return rc; +} + +#ifdef WITH_SERVER + +static int ssh_execute_server_request(ssh_session session, ssh_message msg) +{ + ssh_channel channel = NULL; + int rc; + + switch(msg->type) { + case SSH_REQUEST_AUTH: + if (msg->auth_request.method == SSH_AUTH_METHOD_PASSWORD && + ssh_callbacks_exists(session->server_callbacks, auth_password_function)) { + rc = session->server_callbacks->auth_password_function(session, + msg->auth_request.username, msg->auth_request.password, + session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } else if(msg->auth_request.method == SSH_AUTH_METHOD_PUBLICKEY && + ssh_callbacks_exists(session->server_callbacks, auth_pubkey_function)) { + rc = session->server_callbacks->auth_pubkey_function(session, + msg->auth_request.username, msg->auth_request.pubkey, + msg->auth_request.signature_state, + session->server_callbacks->userdata); + if (msg->auth_request.signature_state != SSH_PUBLICKEY_STATE_NONE) { + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else { + ssh_message_reply_default(msg); + } + } else { + if (rc == SSH_AUTH_SUCCESS) { + ssh_message_auth_reply_pk_ok_simple(msg); + } else { + ssh_message_reply_default(msg); + } + } + + return SSH_OK; + } else if (msg->auth_request.method == SSH_AUTH_METHOD_NONE && + ssh_callbacks_exists(session->server_callbacks, auth_none_function)) { + rc = session->server_callbacks->auth_none_function(session, + msg->auth_request.username, session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL){ + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } else if (msg->auth_request.method == SSH_AUTH_METHOD_INTERACTIVE && + ssh_callbacks_exists(session->server_callbacks, auth_kbdint_function)) { + rc = session->server_callbacks->auth_kbdint_function(msg, + session, + session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else if (rc == SSH_AUTH_INFO) { + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + break; + case SSH_REQUEST_CHANNEL_OPEN: + if (msg->channel_request_open.type == SSH_CHANNEL_SESSION && + ssh_callbacks_exists(session->server_callbacks, channel_open_request_session_function)) { + channel = session->server_callbacks->channel_open_request_session_function(session, + session->server_callbacks->userdata); + if (channel != NULL) { + rc = ssh_message_channel_request_open_reply_accept_channel(msg, channel); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to send reply for accepting a channel " + "open"); + } + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; +#define CB channel_open_request_direct_tcpip_function + } else if (msg->channel_request_open.type == SSH_CHANNEL_DIRECT_TCPIP && + ssh_callbacks_exists(session->server_callbacks, CB)) { + struct ssh_channel_request_open *rq = &msg->channel_request_open; + channel = session->server_callbacks->CB(session, + rq->destination, + rq->destination_port, + rq->originator, + rq->originator_port, + session->server_callbacks->userdata); +#undef CB + if (channel != NULL) { + rc = ssh_message_channel_request_open_reply_accept_channel( + msg, + channel); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to send reply for accepting a channel " + "open"); + } + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + + break; + case SSH_REQUEST_CHANNEL: + channel = msg->channel_request.channel; + + if (msg->channel_request.type == SSH_CHANNEL_REQUEST_PTY){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_pty_request_function) { + rc = ssh_callbacks_iterate_exec(channel_pty_request_function, + session, + channel, + msg->channel_request.TERM, + msg->channel_request.width, + msg->channel_request.height, + msg->channel_request.pxwidth, + msg->channel_request.pxheight); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_SHELL){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_shell_request_function) { + rc = ssh_callbacks_iterate_exec(channel_shell_request_function, + session, + channel); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_X11){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_x11_req_function) { + ssh_callbacks_iterate_exec(channel_x11_req_function, + session, + channel, + msg->channel_request.x11_single_connection, + msg->channel_request.x11_auth_protocol, + msg->channel_request.x11_auth_cookie, + msg->channel_request.x11_screen_number); + ssh_message_channel_request_reply_success(msg); + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_WINDOW_CHANGE){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_pty_window_change_function) { + rc = ssh_callbacks_iterate_exec(channel_pty_window_change_function, + session, + channel, + msg->channel_request.width, + msg->channel_request.height, + msg->channel_request.pxwidth, + msg->channel_request.pxheight); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to iterate callbacks for window change"); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_EXEC){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_exec_request_function) { + rc = ssh_callbacks_iterate_exec(channel_exec_request_function, + session, + channel, + msg->channel_request.command); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_ENV){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_env_request_function) { + rc = ssh_callbacks_iterate_exec(channel_env_request_function, + session, + channel, + msg->channel_request.var_name, + msg->channel_request.var_value); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_SUBSYSTEM){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_subsystem_request_function) { + rc = ssh_callbacks_iterate_exec(channel_subsystem_request_function, + session, + channel, + msg->channel_request.subsystem); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } + break; + case SSH_REQUEST_SERVICE: + if (ssh_callbacks_exists(session->server_callbacks, service_request_function)) { + rc = session->server_callbacks->service_request_function(session, + msg->service_request.service, session->server_callbacks->userdata); + if (rc == 0) { + ssh_message_reply_default(msg); + } else { + ssh_send_disconnect(session); + } + + return SSH_OK; + } + + return SSH_AGAIN; + case SSH_REQUEST_GLOBAL: + break; + } + + return SSH_AGAIN; +} + +static int ssh_reply_channel_open_request(ssh_message msg, ssh_channel channel) +{ + if (channel != NULL) { + return ssh_message_channel_request_open_reply_accept_channel(msg, channel); + } + + ssh_message_reply_default(msg); + + return SSH_OK; +} + +static int ssh_execute_client_request(ssh_session session, ssh_message msg) +{ + ssh_channel channel = NULL; + int rc = SSH_AGAIN; + + if (msg->type == SSH_REQUEST_CHANNEL_OPEN + && msg->channel_request_open.type == SSH_CHANNEL_X11 + && ssh_callbacks_exists(session->common.callbacks, channel_open_request_x11_function)) { + channel = session->common.callbacks->channel_open_request_x11_function (session, + msg->channel_request_open.originator, + msg->channel_request_open.originator_port, + session->common.callbacks->userdata); + + return ssh_reply_channel_open_request(msg, channel); + } else if (msg->type == SSH_REQUEST_CHANNEL_OPEN + && msg->channel_request_open.type == SSH_CHANNEL_AUTH_AGENT + && ssh_callbacks_exists(session->common.callbacks, channel_open_request_auth_agent_function)) { + channel = session->common.callbacks->channel_open_request_auth_agent_function (session, + session->common.callbacks->userdata); + + return ssh_reply_channel_open_request(msg, channel); + } else if (msg->type == SSH_REQUEST_CHANNEL_OPEN + && msg->channel_request_open.type == SSH_CHANNEL_FORWARDED_TCPIP + && ssh_callbacks_exists(session->common.callbacks, channel_open_request_forwarded_tcpip_function)) { + channel = session->common.callbacks->channel_open_request_forwarded_tcpip_function(session, + msg->channel_request_open.destination, + msg->channel_request_open.destination_port, + msg->channel_request_open.originator, + msg->channel_request_open.originator_port, + session->common.callbacks->userdata); + + return ssh_reply_channel_open_request(msg, channel); + } + + return rc; +} + +/** @internal + * Executes the callbacks defined in session->server_callbacks, out of an ssh_message + * I don't like ssh_message interface but it works. + * @returns SSH_OK if the message has been handled, or SSH_AGAIN otherwise. + */ +static int ssh_execute_server_callbacks(ssh_session session, ssh_message msg){ + int rc = SSH_AGAIN; + + if (session->server_callbacks != NULL){ + rc = ssh_execute_server_request(session, msg); + } else if (session->common.callbacks != NULL) { + /* This one is in fact a client callback... */ + rc = ssh_execute_client_request(session, msg); + } + + return rc; +} + +#endif /* WITH_SERVER */ + +static int ssh_execute_message_callback(ssh_session session, ssh_message msg) { + int ret; + if(session->ssh_message_callback != NULL) { + ret = session->ssh_message_callback(session, msg, + session->ssh_message_callback_data); + if(ret == 1) { + ret = ssh_message_reply_default(msg); + SSH_MESSAGE_FREE(msg); + if(ret != SSH_OK) { + return ret; + } + } else { + SSH_MESSAGE_FREE(msg); + } + } else { + ret = ssh_message_reply_default(msg); + SSH_MESSAGE_FREE(msg); + if(ret != SSH_OK) { + return ret; + } + } + return SSH_OK; +} + +/** + * @internal + * + * @brief Add a message to the current queue of messages to be parsed and/or call + * the various callback functions. + * + * @param[in] session The SSH session to add the message. + * + * @param[in] message The message to add to the queue. + */ +static void ssh_message_queue(ssh_session session, ssh_message message) +{ +#ifdef WITH_SERVER + int ret; +#endif + + if (message == NULL) { + return; + } + +#ifdef WITH_SERVER + /* probably not the best place to execute server callbacks, but still better + * than nothing. + */ + ret = ssh_execute_server_callbacks(session, message); + if (ret == SSH_OK) { + SSH_MESSAGE_FREE(message); + return; + } +#endif /* WITH_SERVER */ + + if (session->ssh_message_callback != NULL) { + /* This will transfer the message, do not free. */ + ssh_execute_message_callback(session, message); + return; + } + + if (session->server_callbacks != NULL) { + /* if we have server callbacks, but nothing was executed, it means we are + * in non-synchronous mode, and we just don't care about the message we + * received. Just send a default response. Do not queue it. + */ + ssh_message_reply_default(message); + SSH_MESSAGE_FREE(message); + return; + } + + if (session->ssh_message_list == NULL) { + session->ssh_message_list = ssh_list_new(); + if (session->ssh_message_list == NULL) { + /* + * If the message list couldn't be allocated, the message can't be + * enqueued + */ + ssh_message_reply_default(message); + ssh_set_error_oom(session); + SSH_MESSAGE_FREE(message); + return; + } + } + + /* This will transfer the message, do not free. */ + ssh_list_append(session->ssh_message_list, message); + return; +} + +/** + * @internal + * + * @brief Pop a message from the message list and dequeue it. + * + * @param[in] session The SSH session to pop the message. + * + * @returns The head message or NULL if it doesn't exist. + */ +ssh_message ssh_message_pop_head(ssh_session session) +{ + ssh_message msg = NULL; + struct ssh_iterator *i = NULL; + + if (session->ssh_message_list == NULL) + return NULL; + + i = ssh_list_get_iterator(session->ssh_message_list); + if (i != NULL) { + msg = ssh_iterator_value(ssh_message, i); + ssh_list_remove(session->ssh_message_list, i); + } + return msg; +} + +/* Returns 1 if there is a message available */ +static int ssh_message_termination(void *s) +{ + ssh_session session = s; + struct ssh_iterator *it = NULL; + + if (session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + + it = ssh_list_get_iterator(session->ssh_message_list); + if (!it) + return 0; + else + return 1; +} +/** + * @brief Retrieve a SSH message from a SSH session. + * + * @param[in] session The SSH session to get the message. + * + * @returns The SSH message received, NULL in case of error, or timeout + * elapsed. + * + * @warning This function blocks until a message has been received. Betterset up + * a callback if this behavior is unwanted. + */ +ssh_message ssh_message_get(ssh_session session) +{ + ssh_message msg = NULL; + int rc; + + msg = ssh_message_pop_head(session); + if (msg != NULL) { + return msg; + } + if (session->ssh_message_list == NULL) { + session->ssh_message_list = ssh_list_new(); + if (session->ssh_message_list == NULL) { + ssh_set_error_oom(session); + return NULL; + } + } + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_message_termination, session); + if (rc || session->session_state == SSH_SESSION_STATE_ERROR) { + return NULL; + } + msg = ssh_list_pop_head(ssh_message, session->ssh_message_list); + + return msg; +} + +/** + * @brief Get the type of the message. + * + * @param[in] msg The message to get the type from. + * + * @return The message type or -1 on error. + */ +int ssh_message_type(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + return msg->type; +} + +/** + * @brief Get the subtype of the message. + * + * @param[in] msg The message to get the subtype from. + * + * @return The message type or -1 on error. + */ +int ssh_message_subtype(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + return msg->auth_request.method; + case SSH_REQUEST_CHANNEL_OPEN: + return msg->channel_request_open.type; + case SSH_REQUEST_CHANNEL: + return msg->channel_request.type; + case SSH_REQUEST_GLOBAL: + return msg->global_request.type; + } + + return -1; +} + +/** + * @brief Free a SSH message. + * + * @param[in] msg The message to release the memory. + */ +void ssh_message_free(ssh_message msg){ + if (msg == NULL) { + return; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + SAFE_FREE(msg->auth_request.username); + SAFE_FREE(msg->auth_request.sigtype); + if (msg->auth_request.password) { + ssh_burn(msg->auth_request.password, + strlen(msg->auth_request.password)); + SAFE_FREE(msg->auth_request.password); + } + ssh_key_free(msg->auth_request.pubkey); + ssh_key_free(msg->auth_request.server_pubkey); + break; + case SSH_REQUEST_CHANNEL_OPEN: + SAFE_FREE(msg->channel_request_open.originator); + SAFE_FREE(msg->channel_request_open.destination); + break; + case SSH_REQUEST_CHANNEL: + SAFE_FREE(msg->channel_request.TERM); + SAFE_FREE(msg->channel_request.modes); + SAFE_FREE(msg->channel_request.var_name); + SAFE_FREE(msg->channel_request.var_value); + SAFE_FREE(msg->channel_request.command); + SAFE_FREE(msg->channel_request.subsystem); + switch (msg->channel_request.type) { + case SSH_CHANNEL_REQUEST_EXEC: + SAFE_FREE(msg->channel_request.command); + break; + case SSH_CHANNEL_REQUEST_ENV: + SAFE_FREE(msg->channel_request.var_name); + SAFE_FREE(msg->channel_request.var_value); + break; + case SSH_CHANNEL_REQUEST_PTY: + SAFE_FREE(msg->channel_request.TERM); + break; + case SSH_CHANNEL_REQUEST_SUBSYSTEM: + SAFE_FREE(msg->channel_request.subsystem); + break; + case SSH_CHANNEL_REQUEST_X11: + SAFE_FREE(msg->channel_request.x11_auth_protocol); + SAFE_FREE(msg->channel_request.x11_auth_cookie); + break; + } + break; + case SSH_REQUEST_SERVICE: + SAFE_FREE(msg->service_request.service); + break; + case SSH_REQUEST_GLOBAL: + SAFE_FREE(msg->global_request.bind_address); + break; + } + ZERO_STRUCTP(msg); + SAFE_FREE(msg); +} + +#ifdef WITH_SERVER + +SSH_PACKET_CALLBACK(ssh_packet_service_request) +{ + char *service_c = NULL; + ssh_message msg = NULL; + int rc; + + (void)type; + (void)user; + + rc = ssh_buffer_unpack(packet, + "s", + &service_c); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Invalid SSH_MSG_SERVICE_REQUEST packet"); + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received a SERVICE_REQUEST for service %s", + service_c); + + msg = ssh_message_new(session); + if (msg == NULL) { + SAFE_FREE(service_c); + goto error; + } + + msg->type = SSH_REQUEST_SERVICE; + msg->service_request.service = service_c; + + ssh_message_queue(session, msg); +error: + + return SSH_PACKET_USED; +} + + +/* + * This function concats in a buffer the values needed to do a signature + * verification. + */ +static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, + ssh_message msg, + const char *service, + ssh_string algo, + const char *method) +{ + struct ssh_crypto_struct *crypto = NULL; + ssh_buffer buffer = NULL; + ssh_string str = NULL; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + rc = ssh_pki_export_pubkey_blob(msg->auth_request.pubkey, &str); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dPbsssbsS", + (uint32_t)crypto->session_id_len, /* session ID string */ + crypto->session_id_len, + crypto->session_id, + SSH2_MSG_USERAUTH_REQUEST, /* type */ + msg->auth_request.username, + service, + method, + 1, /* has to be signed (true) */ + ssh_string_get_char(algo), /* pubkey algorithm */ + str); /* public key as a blob */ + + SSH_STRING_FREE(str); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(buffer); + return NULL; + } + + /* Add server public key for hostbound extension */ + if (strcmp(method, "publickey-hostbound-v00@openssh.com") == 0 && + msg->auth_request.server_pubkey != NULL) { + + rc = ssh_pki_export_pubkey_blob(msg->auth_request.server_pubkey, &str); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, str); + SSH_STRING_FREE(str); + if (rc < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(buffer); + return NULL; + } + } + + return buffer; +} + +/** + * @internal + * + * @brief Handle a SSH_MSG_MSG_USERAUTH_REQUEST packet and queue a + * SSH Message + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_request) +{ + ssh_message msg = NULL; + ssh_signature sig = NULL; + char *service = NULL; + char *method = NULL; + int cmp; + int rc; + + (void)user; + (void)type; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->type = SSH_REQUEST_AUTH; + rc = ssh_buffer_unpack(packet, + "sss", + &msg->auth_request.username, + &service, + &method); + + if (rc != SSH_OK) { + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Auth request for service %s, method %s for user '%s'", + service, + method, + msg->auth_request.username); + + cmp = strcmp(service, "ssh-connection"); + if (cmp != 0) { + SSH_LOG(SSH_LOG_TRACE, "Invalid service request: %s", service); + goto end; + } + + if (strcmp(method, "none") == 0) { + msg->auth_request.method = SSH_AUTH_METHOD_NONE; + goto end; + } + + if (strcmp(method, "password") == 0) { + uint8_t tmp; + + msg->auth_request.method = SSH_AUTH_METHOD_PASSWORD; + rc = ssh_buffer_unpack(packet, "bs", &tmp, &msg->auth_request.password); + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(method, "keyboard-interactive") == 0) { + ssh_string lang = NULL; + ssh_string submethods = NULL; + + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + lang = ssh_buffer_get_ssh_string(packet); + if (lang == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "The language tag is deprecated and SHOULD be the empty string." + */ + SSH_STRING_FREE(lang); + + submethods = ssh_buffer_get_ssh_string(packet); + if (submethods == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "One possible implementation strategy of the submethods field on the + * server is that, unless the user may use multiple different + * submethods, the server ignores this field." + */ + SSH_STRING_FREE(submethods); + + goto end; + } + + if (strcmp(method, "publickey") == 0 || + strcmp(method, "publickey-hostbound-v00@openssh.com") == 0) { + ssh_string algo = NULL; + ssh_string pubkey_blob = NULL; + ssh_string server_pubkey_blob = NULL; + uint8_t has_sign; + + msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY; + + rc = ssh_buffer_unpack(packet, "bSS", &has_sign, &algo, &pubkey_blob); + + if (rc != SSH_OK) { + goto error; + } + + cmp = strcmp(method, "publickey-hostbound-v00@openssh.com"); + if (cmp == 0) { + server_pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (server_pubkey_blob == NULL) { + SSH_STRING_FREE(pubkey_blob); + SSH_STRING_FREE(algo); + goto error; + } + + rc = ssh_pki_import_pubkey_blob(server_pubkey_blob, + &msg->auth_request.server_pubkey); + SSH_STRING_FREE(server_pubkey_blob); + + if (rc < 0) { + SSH_STRING_FREE(pubkey_blob); + SSH_STRING_FREE(algo); + goto error; + } + } + + rc = ssh_pki_import_pubkey_blob(pubkey_blob, &msg->auth_request.pubkey); + SSH_STRING_FREE(pubkey_blob); + pubkey_blob = NULL; + if (rc < 0) { + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + msg->auth_request.sigtype = strdup(ssh_string_get_char(algo)); + if (msg->auth_request.sigtype == NULL) { + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + + // has a valid signature ? + if (has_sign) { + ssh_string sig_blob = NULL; + ssh_buffer digest = NULL; + + sig_blob = ssh_buffer_get_ssh_string(packet); + if (sig_blob == NULL) { + SSH_LOG(SSH_LOG_PACKET, "Invalid signature packet from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + + digest = ssh_msg_userauth_build_digest(session, + msg, + service, + algo, + method); + SSH_STRING_FREE(algo); + algo = NULL; + if (digest == NULL) { + SSH_STRING_FREE(sig_blob); + SSH_LOG(SSH_LOG_PACKET, "Failed to get digest"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, + msg->auth_request.pubkey, + &sig); + if (rc == SSH_OK) { + /* Check if the signature from client matches server preferences + */ + if (session->opts.pubkey_accepted_types) { + cmp = match_group(session->opts.pubkey_accepted_types, + sig->type_c); + if (cmp != 1) { + ssh_set_error( + session, + SSH_FATAL, + "Public key from client (%s) doesn't match server " + "preference (%s)", + sig->type_c, + session->opts.pubkey_accepted_types); + rc = SSH_ERROR; + } + } + + if (rc == SSH_OK) { + rc = ssh_pki_signature_verify(session, + sig, + msg->auth_request.pubkey, + ssh_buffer_get(digest), + ssh_buffer_get_len(digest)); + } + } + SSH_STRING_FREE(sig_blob); + SSH_BUFFER_FREE(digest); + ssh_signature_free(sig); + if (rc < 0) { + SSH_LOG(SSH_LOG_PACKET, + "Received an invalid signature from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, "Valid signature received"); + + cmp = strcmp(method, "publickey-hostbound-v00@openssh.com"); + if (cmp == 0) { + ssh_key server_key = NULL; + + if (msg->auth_request.server_pubkey == NULL) { + SSH_LOG(SSH_LOG_PACKET, + "Server public key not provided by client"); + msg->auth_request.signature_state = + SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + rc = ssh_get_server_publickey(session, &server_key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, + "Failed to get server public key for hostbound " + "verification"); + msg->auth_request.signature_state = + SSH_PUBLICKEY_STATE_ERROR; + ssh_key_free(server_key); + goto error; + } + + if (ssh_key_cmp(server_key, + msg->auth_request.server_pubkey, + SSH_KEY_CMP_PUBLIC) != 0) { + SSH_LOG(SSH_LOG_PACKET, + "Server public key doesn't match the one provided " + "by client"); + msg->auth_request.signature_state = + SSH_PUBLICKEY_STATE_WRONG; + ssh_key_free(server_key); + goto error; + } + ssh_key_free(server_key); + } + + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID; + } + + SAFE_FREE(method); + SSH_STRING_FREE(algo); + goto end; + } +#ifdef WITH_GSSAPI + if (strcmp(method, "gssapi-with-mic") == 0) { + uint32_t n_oid; + ssh_string *oids = NULL; + ssh_string oid = NULL; + char *hexa = NULL; + int i; + ssh_buffer_get_u32(packet, &n_oid); + n_oid = ntohl(n_oid); + if (n_oid > 100) { + ssh_set_error( + session, + SSH_FATAL, + "USERAUTH_REQUEST: gssapi-with-mic OID count too big (%d)", + n_oid); + goto error; + } + SSH_LOG(SSH_LOG_PACKET, "gssapi: %d OIDs", n_oid); + oids = calloc(n_oid, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + goto error; + } + for (i = 0; i < (int)n_oid; ++i) { + oid = ssh_buffer_get_ssh_string(packet); + if (oid == NULL) { + for (i = i - 1; i >= 0; --i) { + SAFE_FREE(oids[i]); + } + SAFE_FREE(oids); + ssh_set_error(session, + SSH_LOG_PACKET, + "USERAUTH_REQUEST: gssapi-with-mic missing OID"); + goto error; + } + oids[i] = oid; + if (session->common.log_verbosity >= SSH_LOG_PACKET) { + hexa = ssh_get_hexa(ssh_string_data(oid), ssh_string_len(oid)); + SSH_LOG(SSH_LOG_PACKET, "gssapi: OID %d: %s", i, hexa); + SAFE_FREE(hexa); + } + } + ssh_gssapi_handle_userauth(session, + msg->auth_request.username, + n_oid, + oids); + + for (i = 0; i < (int)n_oid; ++i) { + SAFE_FREE(oids[i]); + } + SAFE_FREE(oids); + /* bypass the message queue thing */ + SAFE_FREE(service); + SAFE_FREE(method); + SSH_MESSAGE_FREE(msg); + + return SSH_PACKET_USED; + } + if (strcmp(method, "gssapi-keyex") == 0) { + gss_buffer_desc received_mic = GSS_C_EMPTY_BUFFER; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + ssh_string mic_token_string = NULL; + OM_uint32 maj_stat, min_stat; + ssh_buffer buf = NULL; + ssh_server_callbacks callbacks = session->server_callbacks; + + if (!ssh_session_kex_is_gss(session)) { + ssh_set_error(session, + SSH_FATAL, + "Attempt to authenticate with gssapi-keyex without " + "doing GSSAPI Key Exchange."); + ssh_auth_reply_default(session, 0); + goto error; + } + + if (session->gssapi == NULL || session->gssapi->ctx == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + ssh_auth_reply_default(session, 0); + goto error; + } + + rc = ssh_buffer_unpack(packet, "S", &mic_token_string); + if (rc != SSH_OK) { + ssh_auth_reply_default(session, 0); + goto error; + } + received_mic.length = ssh_string_len(mic_token_string); + received_mic.value = ssh_string_data(mic_token_string); + + SAFE_FREE(session->gssapi->user); + session->gssapi->user = strdup(msg->auth_request.username); + buf = ssh_gssapi_build_mic(session, "gssapi-keyex"); + if (buf == NULL) { + ssh_set_error_oom(session); + SSH_STRING_FREE(mic_token_string); + ssh_auth_reply_default(session, 0); + goto error; + } + + mic_buf.length = ssh_buffer_get_len(buf); + mic_buf.value = ssh_buffer_get(buf); + + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &mic_buf, + &received_mic, + NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify MIC for gssapi-keyex auth"); + SSH_BUFFER_FREE(buf); + SSH_STRING_FREE(mic_token_string); + ssh_auth_reply_default(session, 0); + goto error; + } + + if (ssh_callbacks_exists(callbacks, auth_gssapi_mic_function)) { + rc = callbacks->auth_gssapi_mic_function(session, + session->gssapi->user, + session->gssapi->canonic_user, + callbacks->userdata); + switch (rc) { + case SSH_AUTH_SUCCESS: + ssh_auth_reply_success(session, 0); + break; + case SSH_AUTH_PARTIAL: + ssh_auth_reply_success(session, 1); + break; + default: + ssh_auth_reply_default(session, 0); + break; + } + } + + /* bypass the message queue thing */ + SAFE_FREE(service); + SAFE_FREE(method); + SSH_BUFFER_FREE(buf); + SSH_MESSAGE_FREE(msg); + SSH_STRING_FREE(mic_token_string); + + return SSH_PACKET_USED; + } +#endif + + msg->auth_request.method = SSH_AUTH_METHOD_UNKNOWN; + SAFE_FREE(method); + goto end; +error: + SAFE_FREE(service); + SAFE_FREE(method); + + SSH_MESSAGE_FREE(msg); + + return SSH_PACKET_USED; +end: + SAFE_FREE(service); + SAFE_FREE(method); + + ssh_message_queue(session, msg); + + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ +/** + * @internal + * + * @brief Handle a SSH_MSG_MSG_USERAUTH_INFO_RESPONSE packet and queue a + * SSH Message + */ +#ifndef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + (void)session; + (void)type; + (void)packet; + (void)user; + return SSH_PACKET_USED; +} +#else /* WITH_SERVER */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + uint32_t nanswers; + uint32_t i; + ssh_string tmp = NULL; + int rc; + + ssh_message msg = NULL; + + /* GSSAPI_TOKEN has same packed number. XXX fix this */ +#ifdef WITH_GSSAPI + if (session->gssapi != NULL) { + return ssh_packet_userauth_gssapi_token(session, type, packet, user); + } +#endif + (void)user; + (void)type; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* HACK: we forge a message to be able to handle it in the + * same switch() as other auth methods */ + msg->type = SSH_REQUEST_AUTH; + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + msg->auth_request.kbdint_response = 1; +#if 0 // should we wipe the username ? + msg->auth_request.username = NULL; +#endif + + rc = ssh_buffer_unpack(packet, "d", &nanswers); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + goto error; + } + + if (session->kbdint == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Warning: Got a keyboard-interactive " + "response but it seems we didn't send the request."); + + session->kbdint = ssh_kbdint_new(); + if (session->kbdint == NULL) { + ssh_set_error_oom(session); + + goto error; + } + } else if (session->kbdint->answers != NULL) { + uint32_t n; + + for (n = 0; n < session->kbdint->nanswers; n++) { + ssh_burn(session->kbdint->answers[n], + strlen(session->kbdint->answers[n])); + SAFE_FREE(session->kbdint->answers[n]); + } + SAFE_FREE(session->kbdint->answers); + session->kbdint->nanswers = 0; + } + + SSH_LOG(SSH_LOG_PACKET,"kbdint: %" PRIu32 " answers", nanswers); + if (nanswers > KBDINT_MAX_PROMPT) { + ssh_set_error(session, SSH_FATAL, + "Too much answers received from client: %" PRIu32 " (0x%.4" PRIx32 ")", + nanswers, nanswers); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + + if(nanswers != session->kbdint->nprompts) { + /* warn but let the application handle this case */ + SSH_LOG(SSH_LOG_DEBUG, "Warning: Number of prompts and answers" + " mismatch: p=%" PRIu32 " a=%" PRIu32, session->kbdint->nprompts, nanswers); + } + session->kbdint->nanswers = nanswers; + + session->kbdint->answers = calloc(nanswers, sizeof(char *)); + if (session->kbdint->answers == NULL) { + session->kbdint->nanswers = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + + for (i = 0; i < nanswers; i++) { + tmp = ssh_buffer_get_ssh_string(packet); + if (tmp == NULL) { + ssh_set_error(session, SSH_FATAL, "Short INFO_RESPONSE packet"); + session->kbdint->nanswers = i; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + session->kbdint->answers[i] = ssh_string_to_char(tmp); + SSH_STRING_FREE(tmp); + if (session->kbdint->answers[i] == NULL) { + ssh_set_error_oom(session); + session->kbdint->nanswers = i; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + } + + ssh_message_queue(session,msg); + + return SSH_PACKET_USED; + +error: + SSH_MESSAGE_FREE(msg); + + return SSH_PACKET_USED; +} +#endif /* WITH_SERVER */ + +SSH_PACKET_CALLBACK(ssh_packet_channel_open){ + ssh_message msg = NULL; + char *type_c = NULL; + uint32_t originator_port, destination_port; + int rc; + + (void)type; + (void)user; + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + msg->type = SSH_REQUEST_CHANNEL_OPEN; + rc = ssh_buffer_unpack(packet, "s", &type_c); + if (rc != SSH_OK){ + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Clients wants to open a %s channel", type_c); + + rc = ssh_buffer_unpack(packet, + "ddd", + &msg->channel_request_open.sender, + &msg->channel_request_open.window, + &msg->channel_request_open.packet_size); + if (rc != SSH_OK){ + goto error; + } + + if (msg->channel_request_open.packet_size == 0) { + ssh_set_error(session, + SSH_FATAL, + "Invalid maximum packet size 0 in SSH2_MSG_CHANNEL_OPEN"); + goto error; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED){ + ssh_set_error(session,SSH_FATAL, "Invalid state when receiving channel open request (must be authenticated)"); + goto error; + } + + if (strcmp(type_c, "session") == 0) { + if (session->flags & SSH_SESSION_FLAG_NO_MORE_SESSIONS) { + ssh_session_set_disconnect_message(session, "No more sessions allowed!"); + ssh_set_error(session, SSH_FATAL, "No more sessions allowed!"); + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_send_disconnect(session); + goto error; + } + + msg->channel_request_open.type = SSH_CHANNEL_SESSION; + SAFE_FREE(type_c); + goto end; + } + + if (strcmp(type_c,"direct-tcpip") == 0) { + rc = ssh_buffer_unpack(packet, + "sdsd", + &msg->channel_request_open.destination, + &destination_port, + &msg->channel_request_open.originator, + &originator_port); + if (rc != SSH_OK) { + goto error; + } + + msg->channel_request_open.destination_port = (uint16_t) destination_port; + msg->channel_request_open.originator_port = (uint16_t) originator_port; + msg->channel_request_open.type = SSH_CHANNEL_DIRECT_TCPIP; + goto end; + } + + if (strcmp(type_c,"forwarded-tcpip") == 0) { + rc = ssh_buffer_unpack(packet, "sdsd", + &msg->channel_request_open.destination, + &destination_port, + &msg->channel_request_open.originator, + &originator_port + ); + if (rc != SSH_OK){ + goto error; + } + msg->channel_request_open.destination_port = (uint16_t) destination_port; + msg->channel_request_open.originator_port = (uint16_t) originator_port; + msg->channel_request_open.type = SSH_CHANNEL_FORWARDED_TCPIP; + goto end; + } + + if (strcmp(type_c,"x11") == 0) { + rc = ssh_buffer_unpack(packet, "sd", + &msg->channel_request_open.originator, + &originator_port); + if (rc != SSH_OK){ + goto error; + } + msg->channel_request_open.originator_port = (uint16_t) originator_port; + msg->channel_request_open.type = SSH_CHANNEL_X11; + goto end; + } + + if (strcmp(type_c,"auth-agent@openssh.com") == 0) { + msg->channel_request_open.type = SSH_CHANNEL_AUTH_AGENT; + goto end; + } + + msg->channel_request_open.type = SSH_CHANNEL_UNKNOWN; + goto end; + +error: + SSH_MESSAGE_FREE(msg); +end: + SAFE_FREE(type_c); + if(msg != NULL) + ssh_message_queue(session,msg); + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief This function accepts a channel open request for the specified channel. + * + * @param[in] msg The message. + * + * @param[in] chan The channel the request is made on. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, + ssh_channel chan) +{ + ssh_session session = NULL; + int rc; + + if (msg == NULL) { + return SSH_ERROR; + } + + session = msg->session; + + chan->local_channel = ssh_channel_new_id(session); + chan->local_maxpacket = 35000; + chan->local_window = 32000; + chan->remote_channel = msg->channel_request_open.sender; + chan->remote_maxpacket = msg->channel_request_open.packet_size; + chan->remote_window = msg->channel_request_open.window; + chan->state = SSH_CHANNEL_STATE_OPEN; + chan->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND; + + rc = ssh_buffer_pack(session->out_buffer, + "bdddd", + SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, + chan->remote_channel, + chan->local_channel, + chan->local_window, + chan->local_maxpacket); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Accepting a channel request_open for chan %" PRIu32, + chan->remote_channel); + + rc = ssh_packet_send(session); + + return rc; +} + +/** + * @internal + * + * @brief This function accepts a channel open request. + * + * @param[in] msg The message. + * + * @returns a valid ssh_channel handle if the request is to be allowed + * + * @returns NULL in case of error + */ +ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) +{ + ssh_channel chan = NULL; + int rc; + + if (msg == NULL) { + return NULL; + } + + chan = ssh_channel_new(msg->session); + if (chan == NULL) { + return NULL; + } + rc = ssh_message_channel_request_open_reply_accept_channel(msg, chan); + if (rc < 0) { + ssh_channel_free(chan); + chan = NULL; + } + return chan; +} + +/** + * @internal + * + * @brief This function parses the last end of a channel request packet. + * + * This is normally converted to a SSH message and placed in the queue. + * + * @param[in] session The SSH session. + * + * @param[in] channel The channel the request is made on. + * + * @param[in] packet The rest of the packet to be parsed. + * + * @param[in] request The type of request. + * + * @param[in] want_reply The want_reply field from the request. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +int +ssh_message_handle_channel_request(ssh_session session, + ssh_channel channel, + ssh_buffer packet, + const char *request, + uint8_t want_reply) +{ + ssh_message msg = NULL; + int rc; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received a %s channel_request for channel (%" PRIu32 ":%" PRIu32 + ") (want_reply=%hhu)", + request, + channel->local_channel, + channel->remote_channel, + want_reply); + + msg->type = SSH_REQUEST_CHANNEL; + msg->channel_request.channel = channel; + msg->channel_request.want_reply = want_reply; + + if (strcmp(request, "pty-req") == 0) { + rc = ssh_buffer_unpack(packet, + "sddddS", + &msg->channel_request.TERM, + &msg->channel_request.width, + &msg->channel_request.height, + &msg->channel_request.pxwidth, + &msg->channel_request.pxheight, + &msg->channel_request.modes); + + msg->channel_request.type = SSH_CHANNEL_REQUEST_PTY; + + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "window-change") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_WINDOW_CHANGE; + rc = ssh_buffer_unpack(packet, + "dddd", + &msg->channel_request.width, + &msg->channel_request.height, + &msg->channel_request.pxwidth, + &msg->channel_request.pxheight); + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "subsystem") == 0) { + rc = ssh_buffer_unpack(packet, "s", &msg->channel_request.subsystem); + msg->channel_request.type = SSH_CHANNEL_REQUEST_SUBSYSTEM; + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "shell") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_SHELL; + goto end; + } + + if (strcmp(request, "exec") == 0) { + rc = ssh_buffer_unpack(packet, "s", &msg->channel_request.command); + msg->channel_request.type = SSH_CHANNEL_REQUEST_EXEC; + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "env") == 0) { + rc = ssh_buffer_unpack(packet, + "ss", + &msg->channel_request.var_name, + &msg->channel_request.var_value); + msg->channel_request.type = SSH_CHANNEL_REQUEST_ENV; + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "x11-req") == 0) { + rc = ssh_buffer_unpack(packet, + "bssd", + &msg->channel_request.x11_single_connection, + &msg->channel_request.x11_auth_protocol, + &msg->channel_request.x11_auth_cookie, + &msg->channel_request.x11_screen_number); + + msg->channel_request.type = SSH_CHANNEL_REQUEST_X11; + if (rc != SSH_OK) { + goto error; + } + + goto end; + } + + msg->channel_request.type = SSH_CHANNEL_REQUEST_UNKNOWN; +end: + ssh_message_queue(session, msg); + + return SSH_OK; +error: + SSH_MESSAGE_FREE(msg); + + return SSH_ERROR; +} + +/** @internal + * + * @brief Sends a successful channel request reply + * + * @param msg A message to reply to + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +int ssh_message_channel_request_reply_success(ssh_message msg) +{ + uint32_t channel; + int rc; + + if (msg == NULL) { + return SSH_ERROR; + } + + if (msg->channel_request.want_reply) { + channel = msg->channel_request.channel->remote_channel; + + SSH_LOG(SSH_LOG_PACKET, + "Sending a channel_request success to channel %" PRIu32, + channel); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_SUCCESS, + channel); + if (rc != SSH_OK) { + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + return ssh_packet_send(msg->session); + } + + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request succeeded"); + + return SSH_OK; +} + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_global_request) +{ + ssh_message msg = NULL; + char *request = NULL; + uint8_t want_reply; + int rc = SSH_PACKET_USED; + int r; + + (void)user; + (void)type; + (void)packet; + + SSH_LOG(SSH_LOG_DEBUG,"Received SSH_MSG_GLOBAL_REQUEST packet"); + r = ssh_buffer_unpack(packet, "sb", &request, &want_reply); + if (r != SSH_OK){ + goto error; + } + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->type = SSH_REQUEST_GLOBAL; + + if (strcmp(request, "tcpip-forward") == 0) { + + /* According to RFC4254, the client SHOULD reject this message */ + if (session->client) { + goto reply_with_failure; + } + + r = ssh_buffer_unpack(packet, + "sd", + &msg->global_request.bind_address, + &msg->global_request.bind_port); + if (r != SSH_OK){ + goto reply_with_failure; + } + msg->global_request.type = SSH_GLOBAL_REQUEST_TCPIP_FORWARD; + msg->global_request.want_reply = want_reply; + + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_MSG_GLOBAL_REQUEST %s %hhu %s:%d", + request, + want_reply, + msg->global_request.bind_address, + msg->global_request.bind_port); + + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + SSH_LOG(SSH_LOG_DEBUG, + "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %hhu %s:%d", + request, + want_reply, + msg->global_request.bind_address, + msg->global_request.bind_port); + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else { + SAFE_FREE(request); + ssh_message_queue(session, msg); + return rc; + } + } else if (strcmp(request, "cancel-tcpip-forward") == 0) { + + /* According to RFC4254, the client SHOULD reject this message */ + if (session->client) { + goto reply_with_failure; + } + + r = ssh_buffer_unpack(packet, + "sd", + &msg->global_request.bind_address, + &msg->global_request.bind_port); + if (r != SSH_OK){ + goto reply_with_failure; + } + msg->global_request.type = SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD; + msg->global_request.want_reply = want_reply; + + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_MSG_GLOBAL_REQUEST %s %hhu %s:%d", + request, + want_reply, + msg->global_request.bind_address, + msg->global_request.bind_port); + + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else { + SAFE_FREE(request); + ssh_message_queue(session, msg); + return rc; + } + } else if(strcmp(request, "keepalive@openssh.com") == 0) { + msg->global_request.type = SSH_GLOBAL_REQUEST_KEEPALIVE; + msg->global_request.want_reply = want_reply; + SSH_LOG(SSH_LOG_DEBUG, + "Received keepalive@openssh.com %hhu", + want_reply); + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + SSH_LOG(SSH_LOG_DEBUG, + "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %hhu", + request, + want_reply); + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else if (want_reply) { + ssh_message_global_request_reply_success(msg, 0); + } + } else if (strcmp(request, "no-more-sessions@openssh.com") == 0) { + msg->global_request.type = SSH_GLOBAL_REQUEST_NO_MORE_SESSIONS; + msg->global_request.want_reply = want_reply; + + SSH_LOG(SSH_LOG_PROTOCOL, + "Received no-more-sessions@openssh.com %hhu", + want_reply); + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + SSH_LOG(SSH_LOG_DEBUG, + "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %hhu", + request, + want_reply); + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else if (want_reply) { + ssh_message_global_request_reply_success(msg, 0); + } + + session->flags |= SSH_SESSION_FLAG_NO_MORE_SESSIONS; + } else { + SSH_LOG(SSH_LOG_DEBUG, + "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s, want_reply = %hhu", + request, + want_reply); + goto reply_with_failure; + } + + SAFE_FREE(msg); + SAFE_FREE(request); + return rc; + +reply_with_failure: + /* Only report the failure if requested */ + if (want_reply) { + r = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_REQUEST_FAILURE); + if (r < 0) { + ssh_set_error_oom(session); + goto error; + } + + r = ssh_packet_send(session); + if (r != SSH_OK) { + goto error; + } + } else { + SSH_LOG(SSH_LOG_PACKET, + "The requester doesn't want to know the request failed!"); + } + + /* Consume the message to avoid sending UNIMPLEMENTED later */ + rc = SSH_PACKET_USED; +error: + SAFE_FREE(msg); + SAFE_FREE(request); + SSH_LOG(SSH_LOG_TRACE, "Invalid SSH_MSG_GLOBAL_REQUEST packet"); + return rc; +} + +#endif /* WITH_SERVER */ + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/misc.c b/src/libs/libssh-0.12.2/src/misc.c new file mode 100644 index 000000000000..f6f741081036 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/misc.c @@ -0,0 +1,2506 @@ +/* + * misc.c - useful client functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#ifndef _WIN32 +/* This is needed for a standard getpwuid_r on opensolaris */ +#define _POSIX_PTHREAD_SEMANTICS +#include +#include +#include +#include +#include +#include + +#endif /* _WIN32 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ + + +#ifdef _WIN32 + +#ifndef _WIN32_IE +# define _WIN32_IE 0x0501 // SHGetSpecialFolderPath +#endif + +#include // Must be the first to include +#include +#include +#include +#include + +#ifdef HAVE_IO_H +#include +#endif /* HAVE_IO_H */ + +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +#ifdef HAVE_LIBGCRYPT +#define GCRYPT_STRING "/gcrypt" +#else +#define GCRYPT_STRING "" +#endif + +#ifdef HAVE_LIBCRYPTO +#define CRYPTO_STRING "/openssl" +#else +#define CRYPTO_STRING "" +#endif + +#ifdef HAVE_LIBMBEDCRYPTO +#define MBED_STRING "/mbedtls" +#else +#define MBED_STRING "" +#endif + +#ifdef WITH_ZLIB +#define ZLIB_STRING "/zlib" +#else +#define ZLIB_STRING "" +#endif + +#define ARPA_DOMAIN_MAX_LEN 63 + +/** + * @defgroup libssh_misc The SSH helper functions + * @ingroup libssh + * + * Different helper functions used in the SSH Library. + * + * @{ + */ + +#ifdef _WIN32 +static char *ssh_get_user_home_dir_internal(void) +{ + char tmp[PATH_MAX] = {0}; + char *szPath = NULL; + + if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) { + szPath = malloc(strlen(tmp) + 1); + if (szPath == NULL) { + return NULL; + } + + strcpy(szPath, tmp); + return szPath; + } + + return NULL; +} + +/* we have read access on file */ +int ssh_file_readaccess_ok(const char *file) +{ + if (_access(file, 4) < 0) { + return 0; + } + + return 1; +} + +/** + * @brief Check if the given path is an existing directory and that is + * accessible for writing. + * + * @param[in] path Path to the directory to be checked + * + * @return Return 1 if the directory exists and is accessible; 0 otherwise + * */ +int ssh_dir_writeable(const char *path) +{ + struct _stat buffer; + int rc; + + rc = _stat(path, &buffer); + if (rc < 0) { + return 0; + } + + if ((buffer.st_mode & _S_IFDIR) && (buffer.st_mode & _S_IWRITE)) { + return 1; + } + + return 0; +} + +#define SSH_USEC_IN_SEC 1000000LL +#define SSH_SECONDS_SINCE_1601 11644473600LL + +int ssh_gettimeofday(struct timeval *__p, void *__t) +{ + union { + unsigned long long ns100; /* time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } now; + + GetSystemTimeAsFileTime (&now.ft); + __p->tv_usec = (long) ((now.ns100 / 10LL) % SSH_USEC_IN_SEC); + __p->tv_sec = (long)(((now.ns100 / 10LL ) / SSH_USEC_IN_SEC) - SSH_SECONDS_SINCE_1601); + + return (0); +} + +/** + * @internal + * + * @brief Convert time in seconds since the Epoch to broken-down local time + * + * This is a helper used to provide localtime_r() like function interface + * on Windows. + * + * @param timer Pointer to a location storing the time_t which + * represents the time in seconds since the Epoch. + * + * @param result Pointer to a location where the broken-down time + * (expressed as local time) should be stored. + * + * @returns A pointer to the structure pointed to by the parameter + * result on success, NULL on error with the errno + * set to indicate the error. + */ +struct tm *ssh_localtime(const time_t *timer, struct tm *result) +{ + errno_t rc; + rc = localtime_s(result, timer); + if (rc != 0) { + return NULL; + } + + return result; +} + +char *ssh_get_local_username(void) +{ + DWORD size = 0; + char *user = NULL; + int rc; + + /* get the size */ + GetUserName(NULL, &size); + + user = (char *)malloc(size); + if (user == NULL) { + return NULL; + } + + if (GetUserName(user, &size)) { + rc = ssh_check_username_syntax(user); + if (rc == SSH_OK) { + return user; + } + } + + free(user); + + return NULL; +} + +int ssh_is_ipaddr_v4(const char *str) +{ + struct sockaddr_storage ss; + int sslen = sizeof(ss); + int rc = SOCKET_ERROR; + + /* WSAStringToAddressA thinks that 0.0.0 is a valid IP */ + if (strlen(str) < 7) { + return 0; + } + + rc = WSAStringToAddressA((LPSTR) str, + AF_INET, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { + return 1; + } + + return 0; +} + +int ssh_is_ipaddr(const char *str) +{ + int rc = SOCKET_ERROR; + char *s = strdup(str); + + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { + struct sockaddr_storage ss; + int sslen = sizeof(ss); + char *network_interface = strchr(s, '%'); + + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = WSAStringToAddressA((LPSTR) s, + AF_INET6, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { + free(s); + return 1; + } + } + + free(s); + return ssh_is_ipaddr_v4(str); +} +#else /* _WIN32 */ + +#ifndef NSS_BUFLEN_PASSWD +#define NSS_BUFLEN_PASSWD 4096 +#endif /* NSS_BUFLEN_PASSWD */ + +static char *ssh_get_user_home_dir_internal(void) +{ + char *szPath = NULL; + struct passwd pwd; + struct passwd *pwdbuf = NULL; + char buf[NSS_BUFLEN_PASSWD] = {0}; + int rc; + + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0 || pwdbuf == NULL ) { + szPath = getenv("HOME"); + if (szPath == NULL) { + return NULL; + } + snprintf(buf, sizeof(buf), "%s", szPath); + return strdup(buf); + } + + szPath = strdup(pwd.pw_dir); + + return szPath; +} + +/* we have read access on file */ +int ssh_file_readaccess_ok(const char *file) +{ + if (access(file, R_OK) < 0) { + return 0; + } + + return 1; +} + +/** + * @brief Check if the given path is an existing directory and that is + * accessible for writing. + * + * @param[in] path Path to the directory to be checked + * + * @return Return 1 if the directory exists and is accessible; 0 otherwise + * */ +int ssh_dir_writeable(const char *path) +{ + struct stat buffer; + int rc; + + rc = stat(path, &buffer); + if (rc < 0) { + return 0; + } + + if (S_ISDIR(buffer.st_mode) && (buffer.st_mode & S_IWRITE)) { + return 1; + } + + return 0; +} + +char *ssh_get_local_username(void) +{ + struct passwd pwd; + struct passwd *pwdbuf = NULL; + char buf[NSS_BUFLEN_PASSWD]; + char *name = NULL; + int rc; + + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0 || pwdbuf == NULL) { + return NULL; + } + + name = strdup(pwd.pw_name); + rc = ssh_check_username_syntax(name); + + if (rc != SSH_OK) { + free(name); + return NULL; + } + + return name; +} + +int ssh_is_ipaddr_v4(const char *str) +{ + int rc = -1; + struct in_addr dest; + + rc = inet_pton(AF_INET, str, &dest); + if (rc > 0) { + return 1; + } + + return 0; +} + +int ssh_is_ipaddr(const char *str) +{ + int rc = -1; + char *s = strdup(str); + + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { + struct in6_addr dest6; + char *network_interface = strchr(s, '%'); + + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = inet_pton(AF_INET6, s, &dest6); + if (rc > 0) { + free(s); + return 1; + } + } + + free(s); + return ssh_is_ipaddr_v4(str); +} + +#endif /* _WIN32 */ + +char *ssh_get_user_home_dir(ssh_session session) +{ + char *szPath = NULL; + + /* If used previously, reuse cached value */ + if (session != NULL && session->opts.homedir != NULL) { + return strdup(session->opts.homedir); + } + + szPath = ssh_get_user_home_dir_internal(); + if (szPath == NULL) { + return NULL; + } + + if (session != NULL) { + /* cache it: + * failure is not fatal -- at worst we will just not cache it */ + session->opts.homedir = strdup(szPath); + } + + return szPath; +} + +char *ssh_lowercase(const char* str) +{ + char *new = NULL, *p = NULL; + + if (str == NULL) { + return NULL; + } + + new = strdup(str); + if (new == NULL) { + return NULL; + } + + for (p = new; *p; p++) { + *p = tolower(*p); + } + + return new; +} + +char *ssh_hostport(const char *host, int port) +{ + char *dest = NULL; + size_t len; + + if (host == NULL) { + return NULL; + } + + /* 3 for []:, 5 for 65536 and 1 for nul */ + len = strlen(host) + 3 + 5 + 1; + dest = malloc(len); + if (dest == NULL) { + return NULL; + } + snprintf(dest, len, "[%s]:%d", host, port); + + return dest; +} + +static char * +ssh_get_hexa_internal(const unsigned char *what, size_t len, bool colons) +{ + const char h[] = "0123456789abcdef"; + char *hexa = NULL; + size_t i; + size_t bytes_per_byte = 2 + (colons ? 1 : 0); + size_t hlen = len * bytes_per_byte; + + if (what == NULL || len < 1 || len > (UINT_MAX - 1) / bytes_per_byte) { + return NULL; + } + + hexa = calloc(hlen + 1, sizeof(char)); + if (hexa == NULL) { + return NULL; + } + + for (i = 0; i < len; i++) { + hexa[i * bytes_per_byte] = h[(what[i] >> 4) & 0xF]; + hexa[i * bytes_per_byte + 1] = h[what[i] & 0xF]; + if (colons) { + hexa[i * bytes_per_byte + 2] = ':'; + } + } + if (colons) { + hexa[hlen - 1] = '\0'; + } + + return hexa; +} + +/** + * @brief Convert a buffer into a colon separated hex string. + * The caller has to free the memory. + * + * @param[in] what What should be converted to a hex string. + * + * @param[in] len Length of the buffer to convert. + * + * @return The hex string or NULL on error. The memory needs + * to be freed using ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_get_hexa(const unsigned char *what, size_t len) +{ + return ssh_get_hexa_internal(what, len, true); +} + +/** + * @deprecated Please use ssh_print_hash() instead + */ +void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) +{ + char *hexa = ssh_get_hexa(what, len); + + if (hexa == NULL) { + return; + } + fprintf(stderr, "%s: %s\n", descr, hexa); + + free(hexa); +} + +/** + * @brief Log the content of a buffer in hexadecimal format, similar to the + * output of 'hexdump -C' command. + * + * The first logged line is the given description followed by the length. + * Then the content of the buffer is logged 16 bytes per line in the following + * format: + * + * (offset) (first 8 bytes) (last 8 bytes) (the 16 bytes as ASCII char values) + * + * The output for a 16 bytes array containing values from 0x00 to 0x0f would be: + * + * "Example (16 bytes):" + * " 00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................" + * + * The value for each byte as corresponding ASCII character is printed at the + * end if the value is printable. Otherwise, it is replaced with '.'. + * + * @param[in] descr A description for the content to be logged + * @param[in] what The buffer to be logged + * @param[in] len The length of the buffer given in what + * + * @note If a too long description is provided (which would result in a first + * line longer than 80 bytes), the function will fail. + */ +void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len) +{ + size_t i; + char ascii[17]; + const unsigned char *pc = NULL; + size_t count = 0; + ssize_t printed = 0; + + /* The required buffer size is calculated from: + * + * 2 bytes for spaces at the beginning + * 8 bytes for the offset + * 2 bytes for spaces + * 24 bytes to print the first 8 bytes + spaces + * 1 byte for an extra space + * 24 bytes to print next 8 bytes + spaces + * 2 bytes for extra spaces + * 16 bytes for the content as ASCII characters at the end + * 1 byte for the ending '\0' + * + * Resulting in 80 bytes. + * + * Except for the first line (description + size), all lines have fixed + * length. If a too long description is used, the function will fail. + * */ + char buffer[80]; + + /* Print description */ + if (descr != NULL) { + printed = snprintf(buffer, sizeof(buffer), "%s ", descr); + if (printed < 0) { + goto error; + } + count += printed; + } else { + printed = snprintf(buffer, sizeof(buffer), "(NULL description) "); + if (printed < 0) { + goto error; + } + count += printed; + } + + if (len == 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, + "(zero length):"); + if (printed < 0) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + return; + } else { + printed = snprintf(buffer + count, sizeof(buffer) - count, + "(%zu bytes):", len); + if (printed < 0) { + goto error; + } + count += printed; + } + + if (what == NULL) { + printed = snprintf(buffer + count, sizeof(buffer) - count, + "(NULL)"); + if (printed < 0) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + return; + } + + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + + /* Reset state */ + count = 0; + pc = what; + + for (i = 0; i < len; i++) { + /* Add one space after printing 8 bytes */ + if ((i % 8) == 0) { + if (i != 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, " "); + if (printed < 0) { + goto error; + } + count += printed; + } + } + + /* Log previous line and reset state for new line */ + if ((i % 16) == 0) { + if (i != 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, + " %s", ascii); + if (printed < 0) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + count = 0; + } + + /* Start a new line with the offset */ + printed = snprintf(buffer, sizeof(buffer), + " %08zx ", i); + if (printed < 0) { + goto error; + } + count += printed; + } + + /* Print the current byte hexadecimal representation */ + printed = snprintf(buffer + count, sizeof(buffer) - count, + " %02x", pc[i]); + if (printed < 0) { + goto error; + } + count += printed; + + /* If printable, store the ASCII character */ + if (isprint(pc[i])) { + ascii[i % 16] = pc[i]; + } else { + ascii[i % 16] = '.'; + } + ascii[(i % 16) + 1] = '\0'; + } + + /* Add padding if not exactly 16 characters */ + while ((i % 16) != 0) { + /* Add one space after printing 8 bytes */ + if ((i % 8) == 0) { + if (i != 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, " "); + if (printed < 0) { + goto error; + } + count += printed; + } + } + + printed = snprintf(buffer + count, sizeof(buffer) - count, " "); + if (printed < 0) { + goto error; + } + count += printed; + i++; + } + + /* Print the last printable part */ + printed = snprintf(buffer + count, sizeof(buffer) - count, + " %s", ascii); + if (printed < 0) { + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + + return; + +error: + SSH_LOG(SSH_LOG_DEBUG, "Could not print to buffer"); + return; +} + +/** + * @brief Check if libssh is the required version or get the version + * string. + * + * @param[in] req_version The version required. + * + * @return If the version of libssh is newer than the version + * required it will return a version string. + * NULL if the version is older. + * + * Example: + * + * @code + * if (ssh_version(SSH_VERSION_INT(0,2,1)) == NULL) { + * fprintf(stderr, "libssh version is too old!\n"); + * exit(1); + * } + * + * if (debug) { + * printf("libssh %s\n", ssh_version(0)); + * } + * @endcode + */ +const char *ssh_version(int req_version) +{ + if (req_version <= LIBSSH_VERSION_INT) { + return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING + MBED_STRING ZLIB_STRING; + } + + return NULL; +} + +struct ssh_list *ssh_list_new(void) +{ + struct ssh_list *ret = malloc(sizeof(struct ssh_list)); + if (ret == NULL) { + return NULL; + } + ret->root = ret->end = NULL; + return ret; +} + +void ssh_list_free(struct ssh_list *list) +{ + struct ssh_iterator *ptr = NULL, *next = NULL; + if (!list) + return; + ptr = list->root; + while (ptr) { + next = ptr->next; + SAFE_FREE(ptr); + ptr = next; + } + SAFE_FREE(list); +} + +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list) +{ + if (!list) + return NULL; + return list->root; +} + +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value) +{ + struct ssh_iterator *it = NULL; + + for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) + if (it->data == value) + return it; + return NULL; +} + +/** + * @brief Get the number of elements in the list + * + * @param[in] list The list to count. + * + * @return The number of elements in the list. + */ +size_t ssh_list_count(const struct ssh_list *list) +{ + struct ssh_iterator *it = NULL; + size_t count = 0; + + for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) { + count++; + } + + return count; +} + +static struct ssh_iterator *ssh_iterator_new(const void *data) +{ + struct ssh_iterator *iterator = malloc(sizeof(struct ssh_iterator)); + + if (iterator == NULL) { + return NULL; + } + iterator->next = NULL; + iterator->data = data; + return iterator; +} + +/** + * @internal + * + * @brief Appends an element to the end of the list. + * + * @param[in] list The list to append the element + * @param[in] data The element to append + * + * @return `SSH_OK` on success, `SSH_ERROR` on error + */ +int ssh_list_append(struct ssh_list *list, const void *data) +{ + struct ssh_iterator *iterator = NULL; + + if (list == NULL) { + return SSH_ERROR; + } + + iterator = ssh_iterator_new(data); + if (iterator == NULL) { + return SSH_ERROR; + } + + if(!list->end){ + /* list is empty */ + list->root=list->end=iterator; + } else { + /* put it on end of list */ + list->end->next=iterator; + list->end=iterator; + } + return SSH_OK; +} + +int ssh_list_prepend(struct ssh_list *list, const void *data) +{ + struct ssh_iterator *it = NULL; + + if (list == NULL) { + return SSH_ERROR; + } + + it = ssh_iterator_new(data); + if (it == NULL) { + return SSH_ERROR; + } + + if (list->end == NULL) { + /* list is empty */ + list->root = list->end = it; + } else { + /* set as new root */ + it->next = list->root; + list->root = it; + } + + return SSH_OK; +} + +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator) +{ + struct ssh_iterator *ptr = NULL, *prev = NULL; + + if (list == NULL) { + return; + } + + prev = NULL; + ptr = list->root; + while (ptr && ptr != iterator) { + prev = ptr; + ptr = ptr->next; + } + if (!ptr) { + /* we did not find the element */ + return; + } + /* unlink it */ + if (prev) + prev->next = ptr->next; + /* if iterator was the head */ + if (list->root == iterator) + list->root = iterator->next; + /* if iterator was the tail */ + if (list->end == iterator) + list->end = prev; + SAFE_FREE(iterator); +} + +/** + * @internal + * + * @brief Removes the top element of the list and returns the data value + * attached to it. + * + * @param[in] list The ssh_list to remove the element. + * + * @returns A pointer to the element being stored in head, or NULL + * if the list is empty. + */ +const void *_ssh_list_pop_head(struct ssh_list *list) +{ + struct ssh_iterator *iterator = NULL; + const void *data = NULL; + + if (list == NULL) { + return NULL; + } + + iterator = list->root; + if (iterator == NULL) { + return NULL; + } + data=iterator->data; + list->root=iterator->next; + if(list->end==iterator) + list->end=NULL; + SAFE_FREE(iterator); + return data; +} + +/** + * @brief Parse directory component. + * + * dirname breaks a null-terminated pathname string into a directory component. + * In the usual case, ssh_dirname() returns the string up to, but not including, + * the final '/'. Trailing '/' characters are not counted as part of the + * pathname. The caller must free the memory using ssh_string_free_char(). + * + * @param[in] path The path to parse. + * + * @return The dirname of path or NULL if we can't allocate memory. + * If path does not contain a slash, c_dirname() returns + * the string ".". If path is a string "/", it returns + * the string "/". If path is NULL or an empty string, + * "." is returned. The memory needs to be freed using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_dirname (const char *path) +{ + char *new = NULL; + size_t len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + /* goto next slash */ + while(len > 0 && path[len - 1] != '/') --len; + + if (len == 0) { + return strdup("."); + } else if (len == 1) { + return strdup("/"); + } + + /* Remove slashes again */ + while(len > 0 && path[len - 1] == '/') --len; + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, path, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief basename - parse filename component. + * + * basename breaks a null-terminated pathname string into a filename component. + * ssh_basename() returns the component following the final '/'. Trailing '/' + * characters are not counted as part of the pathname. + * + * @param[in] path The path to parse. + * + * @return The filename of path or NULL if we can't allocate + * memory. If path is the string "/", basename returns + * the string "/". If path is NULL or an empty string, + * "." is returned. The caller needs to free this memory + * ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_basename (const char *path) +{ + char *new = NULL; + const char *s = NULL; + size_t len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + while(len > 0 && path[len - 1] != '/') --len; + + if (len > 0) { + s = path + len; + len = strlen(s); + + while(len > 0 && s[len - 1] == '/') --len; + } else { + return strdup(path); + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, s, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief Attempts to create a directory with the given pathname. + * + * This is the portable version of mkdir, mode is ignored on Windows systems. + * + * @param[in] pathname The path name to create the directory. + * + * @param[in] mode The permissions to use. + * + * @return 0 on success, < 0 on error with errno set. + */ +int ssh_mkdir(const char *pathname, mode_t mode) +{ + int r; +#ifdef _WIN32 + r = _mkdir(pathname); +#else + r = mkdir(pathname, mode); +#endif + + return r; +} + +/** + * @brief Attempts to create a directory with the given pathname. The missing + * directories in the given pathname are created recursively. + * + * @param[in] pathname The path name to create the directory. + * + * @param[in] mode The permissions to use. + * + * @return 0 on success, < 0 on error with errno set. + * + * @note mode is ignored on Windows systems. + */ +int ssh_mkdirs(const char *pathname, mode_t mode) +{ + int rc = 0; + char *parent = NULL; + + if (pathname == NULL || + pathname[0] == '\0' || + !strcmp(pathname, "/") || + !strcmp(pathname, ".")) + { + errno = EINVAL; + return -1; + } + + errno = 0; + +#ifdef _WIN32 + rc = _mkdir(pathname); +#else + rc = mkdir(pathname, mode); +#endif + + if (rc < 0) { + /* If a directory was missing, try to create the parent */ + if (errno == ENOENT) { + parent = ssh_dirname(pathname); + if (parent == NULL) { + errno = ENOMEM; + return -1; + } + + rc = ssh_mkdirs(parent, mode); + if (rc < 0) { + /* We could not create the parent */ + SAFE_FREE(parent); + return -1; + } + + SAFE_FREE(parent); + + /* Try again */ + errno = 0; +#ifdef _WIN32 + rc = _mkdir(pathname); +#else + rc = mkdir(pathname, mode); +#endif + } + } + + return rc; +} + +/** + * @brief Expand a directory starting with a tilde '~' + * + * @param[in] d The directory to expand. + * + * @return The expanded directory, NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_path_expand_tilde(const char *d) +{ + char *h = NULL, *r = NULL; + const char *p = NULL; + size_t ld; + size_t lh = 0; + + if (d[0] != '~') { + return strdup(d); + } + d++; + + /* handle ~user/path */ + p = strchr(d, '/'); + if (p != NULL && p > d) { +#ifdef _WIN32 + return strdup(d); +#else + struct passwd *pw = NULL; + size_t s = p - d; + char u[128]; + + if (s >= sizeof(u)) { + return NULL; + } + memcpy(u, d, s); + u[s] = '\0'; + pw = getpwnam(u); + if (pw == NULL) { + return NULL; + } + ld = strlen(p); + h = strdup(pw->pw_dir); +#endif + } else { + ld = strlen(d); + p = (char *) d; + h = ssh_get_user_home_dir(NULL); + } + if (h == NULL) { + return NULL; + } + lh = strlen(h); + + r = malloc(ld + lh + 1); + if (r == NULL) { + SAFE_FREE(h); + return NULL; + } + + if (lh > 0) { + memcpy(r, h, lh); + } + SAFE_FREE(h); + memcpy(r + lh, p, ld + 1); + + return r; +} + +char *ssh_get_local_hostname(void) +{ + char host[NI_MAXHOST] = {0}; + int rc; + + rc = gethostname(host, sizeof(host)); + if (rc != 0) { + return NULL; + } + return strdup(host); +} + +static char *get_connection_hash(ssh_session session) +{ + unsigned char conn_hash[SHA_DIGEST_LENGTH]; + char *local_hostname = NULL; + SHACTX ctx = sha1_init(); + char strport[10] = {0}; + unsigned int port; + char *username = NULL; + int rc; + + if (session == NULL) { + return NULL; + } + + if (ctx == NULL) { + goto err; + } + + /* Local hostname %l */ + local_hostname = ssh_get_local_hostname(); + if (local_hostname == NULL) { + goto err; + } + rc = sha1_update(ctx, local_hostname, strlen(local_hostname)); + if (rc != SSH_OK) { + goto err; + } + SAFE_FREE(local_hostname); + + /* Remote hostname %h */ + if (session->opts.host == NULL) { + goto err; + } + rc = sha1_update(ctx, session->opts.host, strlen(session->opts.host)); + if (rc != SSH_OK) { + goto err; + } + + /* Remote port %p */ + ssh_options_get_port(session, &port); + snprintf(strport, sizeof(strport), "%d", port); + rc = sha1_update(ctx, strport, strlen(strport)); + if (rc != SSH_OK) { + goto err; + } + + /* The remote username %r */ + username = session->opts.username; + if (username == NULL) { + /* fallback to local username: it will be used if not explicitly set */ + username = ssh_get_local_username(); + if (username == NULL) { + goto err; + } + } + rc = sha1_update(ctx, username, strlen(username)); + if (username != session->opts.username) { + free(username); + } + if (rc != SSH_OK) { + goto err; + } + + /* ProxyJump */ + if (session->opts.proxy_jumps_str != NULL) { + rc = sha1_update(ctx, + session->opts.proxy_jumps_str, + strlen(session->opts.proxy_jumps_str)); + } + if (rc != SSH_OK) { + goto err; + } + + /* Frees context */ + rc = sha1_final(conn_hash, ctx); + if (rc != SSH_OK) { + goto err; + } + + return ssh_get_hexa_internal(conn_hash, SHA_DIGEST_LENGTH, false); + +err: + free(local_hostname); + sha1_ctx_free(ctx); + return NULL; +} + +/** @internal + * @brief expands a string in function of session options + * + * @param[in] s Format string to expand. Known parameters: + * - %d user home directory (~) + * - %h target host name + * - %u local username + * - %l local hostname + * - %r remote username + * - %p remote port + * - %j proxyjump string + * - %C Hash of %l%h%p%r%j + * + * @returns Expanded string. The caller needs to free the memory using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_path_expand_escape(ssh_session session, const char *s) +{ + char *buf = NULL; + char *r = NULL; + char *x = NULL; + const char *p = NULL; + size_t i, l; + + r = ssh_path_expand_tilde(s); + if (r == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (strlen(r) > MAX_BUF_SIZE) { + ssh_set_error(session, SSH_FATAL, "string to expand too long"); + free(r); + return NULL; + } + + buf = malloc(MAX_BUF_SIZE); + if (buf == NULL) { + ssh_set_error_oom(session); + free(r); + return NULL; + } + + p = r; + buf[0] = '\0'; + + for (i = 0; *p != '\0'; p++) { + if (*p != '%') { + escape: + buf[i] = *p; + i++; + if (i >= MAX_BUF_SIZE) { + free(buf); + free(r); + return NULL; + } + buf[i] = '\0'; + continue; + } + + p++; + if (*p == '\0') { + break; + } + + switch (*p) { + case '%': + goto escape; + case 'd': + x = ssh_get_user_home_dir(session); + if (x == NULL) { + ssh_set_error(session, SSH_FATAL, "Cannot expand homedir"); + free(buf); + free(r); + return NULL; + } + break; + case 'u': + x = ssh_get_local_username(); + break; + case 'l': + x = ssh_get_local_hostname(); + break; + case 'h': + if (session->opts.host) { + x = strdup(session->opts.host); + } else { + ssh_set_error(session, SSH_FATAL, "Cannot expand host"); + free(buf); + free(r); + return NULL; + } + break; + case 'r': + if (session->opts.username) { + x = strdup(session->opts.username); + } else { + ssh_set_error(session, SSH_FATAL, "Cannot expand username"); + free(buf); + free(r); + return NULL; + } + break; + case 'p': { + char tmp[6]; + unsigned int port; + + ssh_options_get_port(session, &port); + snprintf(tmp, sizeof(tmp), "%u", port); + x = strdup(tmp); + break; + } + case 'j': + if (session->opts.proxy_jumps_str != NULL) { + x = strdup(session->opts.proxy_jumps_str); + } else { + x = strdup(""); + } + break; + case 'C': + x = get_connection_hash(session); + break; + default: + ssh_set_error(session, SSH_FATAL, "Wrong escape sequence detected"); + free(buf); + free(r); + return NULL; + } + + if (x == NULL) { + ssh_set_error_oom(session); + free(buf); + free(r); + return NULL; + } + + i += strlen(x); + if (i >= MAX_BUF_SIZE) { + ssh_set_error(session, SSH_FATAL, "String too long"); + free(buf); + free(x); + free(r); + return NULL; + } + l = strlen(buf); + strncpy(buf + l, x, MAX_BUF_SIZE - l - 1); + buf[i] = '\0'; + SAFE_FREE(x); + } + + free(r); + + /* strip the unused space by realloc */ + x = realloc(buf, strlen(buf) + 1); + if (x == NULL) { + ssh_set_error_oom(session); + free(buf); + } + return x; +} + +/** + * @internal + * + * @brief Analyze the SSH banner to extract version information. + * + * @param session The session to analyze the banner from. + * @param server 0 means we are a client, 1 a server. + * + * @return 0 on success, < 0 on error. + * + * @see ssh_get_issue_banner() + */ +int ssh_analyze_banner(ssh_session session, int server) +{ + const char *banner = NULL; + const char *openssh = NULL; + const char *ios = NULL; + + if (server) { + banner = session->clientbanner; + } else { + banner = session->serverbanner; + } + + if (banner == NULL) { + ssh_set_error(session, SSH_FATAL, "Invalid banner"); + return -1; + } + + /* + * Typical banners e.g. are: + * + * SSH-1.5-openSSH_5.4 + * SSH-1.99-openSSH_3.0 + * + * SSH-2.0-something + * 012345678901234567890 + */ + if (strlen(banner) < 6 || + strncmp(banner, "SSH-", 4) != 0) { + ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner); + return -1; + } + + SSH_LOG(SSH_LOG_DEBUG, "Analyzing banner: %s", banner); + + switch (banner[4]) { + case '2': + break; + case '1': + if (strlen(banner) > 6) { + if (banner[6] == '9') { + break; + } + } + FALL_THROUGH; + default: + ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner); + return -1; + } + + /* Make a best-effort to extract OpenSSH version numbers. */ + openssh = strstr(banner, "OpenSSH"); + if (openssh != NULL) { + char *tmp = NULL; + unsigned long int major = 0UL; + unsigned long int minor = 0UL; + int off = 0; + + /* + * The banner is typical: + * OpenSSH_5.4 + * 012345678901234567890 + */ + if (strlen(openssh) > 9) { + errno = 0; + major = strtoul(openssh + 8, &tmp, 10); + if ((tmp == (openssh + 8)) || + ((errno == ERANGE) && (major == ULONG_MAX)) || + ((errno != 0) && (major == 0)) || + ((major < 1) || (major > 100))) { + /* invalid major */ + errno = 0; + goto done; + } + + errno = 0; + off = major >= 10 ? 11 : 10; + minor = strtoul(openssh + off, &tmp, 10); + if ((tmp == (openssh + off)) || + ((errno == ERANGE) && (major == ULONG_MAX)) || + ((errno != 0) && (major == 0)) || + (minor > 100)) { + /* invalid minor */ + errno = 0; + goto done; + } + + session->openssh = SSH_VERSION_INT(((int) major), ((int) minor), 0); + + SSH_LOG(SSH_LOG_DEBUG, + "We are talking to an OpenSSH %s version: %lu.%lu (%x)", + server ? "client" : "server", + major, minor, session->openssh); + } + } + /* Cisco devices have odd scp implementation which breaks */ + ios = strstr(banner, "Cisco"); + if (ios != NULL) { + session->flags |= SSH_SESSION_FLAG_SCP_QUOTING_BROKEN; + } + +done: + return 0; +} + +/* try the Monotonic clock if possible for perfs reasons */ +#ifdef _POSIX_MONOTONIC_CLOCK +#define CLOCK CLOCK_MONOTONIC +#else +#define CLOCK CLOCK_REALTIME +#endif + +/** + * @internal + * @brief initializes a timestamp to the current time + * @param[out] ts pointer to an allocated ssh_timestamp structure + */ +void ssh_timestamp_init(struct ssh_timestamp *ts) +{ +#ifdef HAVE_CLOCK_GETTIME + struct timespec tp; + clock_gettime(CLOCK, &tp); + ts->useconds = tp.tv_nsec / 1000; +#else + struct timeval tp; + gettimeofday(&tp, NULL); + ts->useconds = tp.tv_usec; +#endif + ts->seconds = tp.tv_sec; +} + +#undef CLOCK + +/** + * @internal + * @brief gets the time difference between two timestamps in ms + * @param[in] old older value + * @param[in] new newer value + * @returns difference in milliseconds + */ + +static int +ssh_timestamp_difference(struct ssh_timestamp *old, struct ssh_timestamp *new) +{ + long seconds, usecs, msecs; + seconds = new->seconds - old->seconds; + usecs = new->useconds - old->useconds; + if (usecs < 0){ + seconds--; + usecs += 1000000; + } + msecs = seconds * 1000 + usecs/1000; + return msecs; +} + +/** + * @internal + * @brief turn seconds and microseconds pair (as provided by user-set options) + * into millisecond value + * @param[in] sec number of seconds + * @param[in] usec number of microseconds + * @returns milliseconds, or 10000 if user supplied values are equal to zero + */ +int ssh_make_milliseconds(unsigned long sec, unsigned long usec) +{ + unsigned long res = usec ? (usec / 1000) : 0; + res += (sec * 1000); + if (res == 0) { + res = 10 * 1000; /* use a reasonable default value in case + * SSH_OPTIONS_TIMEOUT is not set in options. */ + } + + if (res > INT_MAX) { + return SSH_TIMEOUT_INFINITE; + } else { + return (int)res; + } +} + +/** + * @internal + * @brief Checks if a timeout is elapsed, in function of a previous + * timestamp and an assigned timeout + * @param[in] ts pointer to an existing timestamp + * @param[in] timeout timeout in milliseconds. Negative values mean infinite + * timeout + * @returns 1 if timeout is elapsed + * 0 otherwise + */ +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) +{ + struct ssh_timestamp now; + + switch(timeout) { + case -2: /* + * -2 means user-defined timeout as available in + * session->timeout, session->timeout_usec. + */ + SSH_LOG(SSH_LOG_DEBUG, "ssh_timeout_elapsed called with -2. this needs to " + "be fixed. please set a breakpoint on misc.c:%d and " + "fix the caller\n", __LINE__); + return 0; + case -1: /* -1 means infinite timeout */ + return 0; + case 0: /* 0 means no timeout */ + return 1; + default: + break; + } + + ssh_timestamp_init(&now); + + return (ssh_timestamp_difference(ts,&now) >= timeout); +} + +/** + * @brief updates a timeout value so it reflects the remaining time + * @param[in] ts pointer to an existing timestamp + * @param[in] timeout timeout in milliseconds. Negative values mean infinite + * timeout + * @returns remaining time in milliseconds, 0 if elapsed, -1 if never. + */ +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout) +{ + struct ssh_timestamp now; + int ms, ret; + if (timeout <= 0) { + return timeout; + } + ssh_timestamp_init(&now); + ms = ssh_timestamp_difference(ts,&now); + if(ms < 0) + ms = 0; + ret = timeout - ms; + return ret >= 0 ? ret: 0; +} + +/** + * @brief Securely free memory by overwriting it before deallocation + * + * Overwrites the memory region with zeros before calling free() to prevent + * sensitive data from remaining in memory after deallocation. + * + * @param[in] ptr Pointer to the memory region to securely free. + * Can be NULL (no operation performed). + * @param[in] len Length of the memory region in bytes. + * + */ +void burn_free(void *ptr, size_t len) +{ + if (ptr == NULL || len == 0) { + return; + } + + ssh_burn(ptr, len); + free(ptr); +} + +#if !defined(HAVE_STRNDUP) +char *strndup(const char *s, size_t n) +{ + char *x = NULL; + + if (n + 1 < n) { + return NULL; + } + + x = malloc(n + 1); + if (x == NULL) { + return NULL; + } + + memcpy(x, s, n); + x[n] = '\0'; + + return x; +} +#endif /* ! HAVE_STRNDUP */ + +/* Increment 64b integer in network byte order */ +void +uint64_inc(unsigned char *counter) +{ + int i; + + for (i = 7; i >= 0; i--) { + counter[i]++; + if (counter[i]) + return; + } +} + +/** + * @internal + * + * @brief Quote file name to be used on shell. + * + * Try to put the given file name between single quotes. There are special + * cases: + * + * - When the '\'' char is found in the file name, it is double quoted + * - example: + * input: a'b + * output: 'a'"'"'b' + * - When the '!' char is found in the file name, it is replaced by an unquoted + * verbatim char "\!" + * - example: + * input: a!b + * output 'a'\!'b' + * + * @param[in] file_name File name string to be quoted before used on shell + * @param[out] buf Buffer to receive the final quoted file name. Must + * have room for the final quoted string. The maximum + * output length would be (3 * strlen(file_name) + 1) + * since in the worst case each character would be + * replaced by 3 characters, plus the terminating '\0'. + * @param[in] buf_len The size of the provided output buffer + * + * @returns SSH_ERROR on error; length of the resulting string not counting the + * string terminator '\0' + * */ +int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len) +{ + const char *src = NULL; + char *dst = NULL; + size_t required_buf_len; + + enum ssh_quote_state_e state = NO_QUOTE; + + if (file_name == NULL || buf == NULL || buf_len == 0) { + SSH_LOG(SSH_LOG_TRACE, "Invalid parameter"); + return SSH_ERROR; + } + + /* Only allow file names smaller than 32kb. */ + if (strlen(file_name) > 32 * 1024) { + SSH_LOG(SSH_LOG_TRACE, "File name too long"); + return SSH_ERROR; + } + + /* Paranoia check */ + required_buf_len = (size_t)3 * strlen(file_name) + 1; + if (required_buf_len > buf_len) { + SSH_LOG(SSH_LOG_TRACE, "Buffer too small"); + return SSH_ERROR; + } + + src = file_name; + dst = buf; + + while ((*src != '\0')) { + switch (*src) { + + /* The '\'' char is double quoted */ + + case '\'': + switch (state) { + case NO_QUOTE: + /* Start a new double quoted string. The '\'' char will be + * copied to the beginning of it at the end of the loop. */ + *dst++ = '"'; + break; + case SINGLE_QUOTE: + /* Close the current single quoted string and start a new double + * quoted string. The '\'' char will be copied to the beginning + * of it at the end of the loop. */ + *dst++ = '\''; + *dst++ = '"'; + break; + case DOUBLE_QUOTE: + /* If already in the double quoted string, keep copying the + * sequence of chars. */ + break; + default: + /* Should never be reached */ + goto error; + } + + /* When the '\'' char is found, the resulting state will be + * DOUBLE_QUOTE in any case*/ + state = DOUBLE_QUOTE; + break; + + /* The '!' char is replaced by unquoted "\!" */ + + case '!': + switch (state) { + case NO_QUOTE: + /* The '!' char is interpreted in some shells (e.g. CSH) even + * when is quoted with single quotes. Replace it with unquoted + * "\!" which is correctly interpreted as the '!' character. */ + *dst++ = '\\'; + break; + case SINGLE_QUOTE: + /* Close the currently quoted string and replace '!' for unquoted + * "\!" */ + *dst++ = '\''; + *dst++ = '\\'; + break; + case DOUBLE_QUOTE: + /* Close currently quoted string and replace "!" for unquoted + * "\!" */ + *dst++ = '"'; + *dst++ = '\\'; + break; + default: + /* Should never be reached */ + goto error; + } + + /* When the '!' char is found, the resulting state will be NO_QUOTE + * in any case*/ + state = NO_QUOTE; + break; + + /* Ordinary chars are single quoted */ + + default: + switch (state) { + case NO_QUOTE: + /* Start a new single quoted string */ + *dst++ = '\''; + break; + case SINGLE_QUOTE: + /* If already in the single quoted string, keep copying the + * sequence of chars. */ + break; + case DOUBLE_QUOTE: + /* Close current double quoted string and start a new single + * quoted string. */ + *dst++ = '"'; + *dst++ = '\''; + break; + default: + /* Should never be reached */ + goto error; + } + + /* When an ordinary char is found, the resulting state will be + * SINGLE_QUOTE in any case*/ + state = SINGLE_QUOTE; + break; + } + + /* Copy the current char to output */ + *dst++ = *src++; + } + + /* Close the quoted string when necessary */ + + switch (state) { + case NO_QUOTE: + /* No open string */ + break; + case SINGLE_QUOTE: + /* Close current single quoted string */ + *dst++ = '\''; + break; + case DOUBLE_QUOTE: + /* Close current double quoted string */ + *dst++ = '"'; + break; + default: + /* Should never be reached */ + goto error; + } + + /* Put the string terminator */ + *dst = '\0'; + + return (int)(dst - buf); + +error: + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Given a string, encode existing newlines as the string "\\n" + * + * @param[in] string Input string + * @param[out] buf Output buffer. This buffer must be at least (2 * + * strlen(string)) + 1 long. In the worst case, + * each character can be encoded as 2 characters plus the + * terminating '\0'. + * @param[in] buf_len Size of the provided output buffer + * + * @returns SSH_ERROR on error; length of the resulting string not counting the + * terminating '\0' otherwise + */ +int ssh_newline_vis(const char *string, char *buf, size_t buf_len) +{ + const char *in = NULL; + char *out = NULL; + + if (string == NULL || buf == NULL || buf_len == 0) { + return SSH_ERROR; + } + + if ((2 * strlen(string) + 1) > buf_len) { + SSH_LOG(SSH_LOG_TRACE, "Buffer too small"); + return SSH_ERROR; + } + + out = buf; + for (in = string; *in != '\0'; in++) { + if (*in == '\n') { + *out++ = '\\'; + *out++ = 'n'; + } else { + *out++ = *in; + } + } + *out = '\0'; + + return (int)(out - buf); +} + +/** + * @internal + * + * @brief Replaces the last 6 characters of a string from 'X' to 6 random hexdigits. + * + * @param[in,out] name Any input string with last 6 characters as 'X'. + * @returns -1 as error when the last 6 characters of the input to be replaced are not 'X' + * 0 otherwise. + */ +int ssh_tmpname(char *name) +{ + char *tmp = NULL; + size_t i = 0; + int rc = 0; + uint8_t random[6]; + + if (name == NULL) { + goto err; + } + + tmp = name + strlen(name) - 6; + if (tmp < name) { + goto err; + } + + for (i = 0; i < 6; i++) { + if (tmp[i] != 'X') { + SSH_LOG(SSH_LOG_WARNING, + "Invalid input. Last six characters of the input must be \'X\'"); + goto err; + } + } + + rc = ssh_get_random(random, 6, 0); + if (!rc) { + SSH_LOG(SSH_LOG_WARNING, + "Could not generate random data\n"); + goto err; + } + + for (i = 0; i < 6; i++) { + /* Limit the random[i] < 32 */ + random[i] &= 0x1f; + /* For values from 0 to 9 use numbers, otherwise use letters */ + tmp[i] = random[i] > 9 ? random[i] + 'a' - 10 : random[i] + '0'; + } + + return 0; + +err: + errno = EINVAL; + return -1; +} + +/** + * @internal + * + * @brief Finds the first occurrence of a pattern in a string and replaces it. + * + * @param[in] src Source string containing the pattern to be replaced. + * @param[in] pattern Pattern to be replaced in the source string. + * Note: this function replaces the first occurrence of + * pattern only. + * @param[in] replace String to be replaced is stored in replace. + * + * @returns src_replaced a pointer that points to the replaced string. + * NULL if allocation fails or if src is NULL. The returned memory needs to be + * freed using ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_strreplace(const char *src, const char *pattern, const char *replace) +{ + const char *p = NULL; + char *src_replaced = NULL; + + if (src == NULL) { + return NULL; + } + + if (pattern == NULL || replace == NULL) { + return strdup(src); + } + + p = strstr(src, pattern); + + if (p != NULL) { + size_t offset = p - src; + size_t pattern_len = strlen(pattern); + size_t replace_len = strlen(replace); + size_t len = strlen(src); + size_t len_replaced = len + replace_len - pattern_len + 1; + + src_replaced = (char *)malloc(len_replaced); + + if (src_replaced == NULL) { + return NULL; + } + + memset(src_replaced, 0, len_replaced); + memcpy(src_replaced, src, offset); + memcpy(src_replaced + offset, replace, replace_len); + memcpy(src_replaced + offset + replace_len, src + offset + pattern_len, len - offset - pattern_len); + return src_replaced; /* free in the caller */ + } else { + return strdup(src); + } +} + +/** + * @internal + * + * @brief Processes errno into error string + * + * @param[in] err_num The errno value + * @param[out] buf Pointer to a place where the string could be saved + * @param[in] buflen The allocated size of buf + * + * @return error string + */ +char *ssh_strerror(int err_num, char *buf, size_t buflen) +{ +#if ((defined(__linux__) && defined(__GLIBC__)) || defined(__CYGWIN__)) && defined(_GNU_SOURCE) + /* GNU extension on Linux */ + return strerror_r(err_num, buf, buflen); +#else + int rv; + +#if defined(_WIN32) + rv = strerror_s(buf, buflen, err_num); +#else + /* POSIX version available for example on FreeBSD or in musl libc */ + rv = strerror_r(err_num, buf, buflen); +#endif /* _WIN32 */ + + /* make sure the buffer is initialized and terminated with NULL */ + if (-rv == ERANGE) { + buf[0] = '\0'; + } + return buf; +#endif /* ((defined(__linux__) && defined(__GLIBC__)) || defined(__CYGWIN__)) && defined(_GNU_SOURCE) */ +} + +/** + * @brief Read the requested number of bytes from a local file. + * + * A call to read() may perform a short read even when sufficient data is + * present in the file. This function can be used to avoid such short reads. + * + * This function tries to read the requested number of bytes from the file + * until one of the following occurs : + * - Requested number of bytes are read. + * - EOF is encountered before reading the requested number of bytes. + * - An error occurs. + * + * On encountering an error due to an interrupt, this function ignores that + * error and continues trying to read the data. + * + * @param[in] fd The file descriptor of the local file to read from. + * + * @param[out] buf Pointer to a buffer in which read data will be + * stored. + * + * @param[in] nbytes Number of bytes to read. + * + * @returns Number of bytes read on success, + * SSH_ERROR on error with errno set to indicate the + * error. + */ +ssize_t ssh_readn(int fd, void *buf, size_t nbytes) +{ + size_t total_bytes_read = 0; + ssize_t bytes_read; + + if (fd < 0 || buf == NULL || nbytes == 0) { + errno = EINVAL; + return SSH_ERROR; + } + + do { + bytes_read = read(fd, + ((char *)buf) + total_bytes_read, + nbytes - total_bytes_read); + if (bytes_read == -1) { + if (errno == EINTR) { + /* Ignoring errors due to signal interrupts */ + continue; + } + + return SSH_ERROR; + } + + if (bytes_read == 0) { + /* EOF encountered on the local file before reading nbytes */ + break; + } + + total_bytes_read += (size_t)bytes_read; + } while (total_bytes_read < nbytes); + + return total_bytes_read; +} + +/** + * @brief Write the requested number of bytes to a local file. + * + * A call to write() may perform a short write on a local file. This function + * can be used to avoid short writes. + * + * This function tries to write the requested number of bytes until those many + * bytes are written or some error occurs. + * + * On encountering an error due to an interrupt, this function ignores that + * error and continues trying to write the data. + * + * @param[in] fd The file descriptor of the local file to write to. + * + * @param[in] buf Pointer to a buffer in which data to write is stored. + * + * @param[in] nbytes Number of bytes to write. + * + * @returns Number of bytes written on success, + * SSH_ERROR on error with errno set to indicate the + * error. + */ +ssize_t ssh_writen(int fd, const void *buf, size_t nbytes) +{ + size_t total_bytes_written = 0; + ssize_t bytes_written; + + if (fd < 0 || buf == NULL || nbytes == 0) { + errno = EINVAL; + return SSH_ERROR; + } + + do { + bytes_written = write(fd, + ((const char *)buf) + total_bytes_written, + nbytes - total_bytes_written); + if (bytes_written == -1) { + if (errno == EINTR) { + /* Ignoring errors due to signal interrupts */ + continue; + } + + return SSH_ERROR; + } + + total_bytes_written += (size_t)bytes_written; + } while (total_bytes_written < nbytes); + + return total_bytes_written; +} + +/** + * @brief Checks syntax of a domain name + * + * The check is made based on the RFC1035 section 2.3.1 + * Allowed characters are: hyphen, period, digits (0-9) and letters (a-zA-Z) + * + * The label should be no longer than 63 characters + * The label should start with a letter and end with a letter or number + * The label in this implementation can start with a number to allow virtual + * URLs to pass. Note that this will make IPv4 addresses to pass + * this check too. + * + * @param hostname The domain name to be checked, has to be null terminated + * + * @return SSH_OK if the hostname passes syntax check + * SSH_ERROR otherwise or if hostname is NULL or empty string + */ +int ssh_check_hostname_syntax(const char *hostname) +{ + char *it = NULL, *s = NULL, *buf = NULL; + size_t it_len; + char c; + + if (hostname == NULL || strlen(hostname) == 0) { + return SSH_ERROR; + } + + /* strtok_r writes into the string, keep the input clean */ + s = strdup(hostname); + if (s == NULL) { + return SSH_ERROR; + } + + it = strtok_r(s, ".", &buf); + /* if the token has 0 length */ + if (it == NULL) { + free(s); + return SSH_ERROR; + } + do { + it_len = strlen(it); + if (it_len > ARPA_DOMAIN_MAX_LEN || + /* the first char must be a letter, but some virtual urls start + * with a number */ + isalnum(it[0]) == 0 || + isalnum(it[it_len - 1]) == 0) { + free(s); + return SSH_ERROR; + } + while (*it != '\0') { + c = *it; + /* the "." is allowed too, but tokenization removes it from the + * string */ + if (isalnum(c) == 0 && c != '-') { + free(s); + return SSH_ERROR; + } + it++; + } + } while ((it = strtok_r(NULL, ".", &buf)) != NULL); + + free(s); + + return SSH_OK; +} + +/** + * @brief Checks syntax of a username + * + * This check disallows metacharacters in the username + * + * @param username The username to be checked, has to be null terminated + * + * @return SSH_OK if the username passes syntax check + * SSH_ERROR otherwise or if username is NULL or empty string + */ +int ssh_check_username_syntax(const char *username) +{ + size_t username_len; + + if (username == NULL || *username == '-') { + return SSH_ERROR; + } + + username_len = strlen(username); + if (username_len == 0 || username[username_len - 1] == '\\' || + strpbrk(username, SSH_DANGEROUS_SHELL_CHARS) != NULL) { + return SSH_ERROR; + } + for (size_t i = 0; i < username_len; i++) { + if (isspace(username[i]) != 0 && username[i + 1] == '-') { + return SSH_ERROR; + } + } + + return SSH_OK; +} + +/** + * @brief Free proxy jump list + * + * Frees everything in a proxy jump list, but doesn't free the ssh_list + * + * @param proxy_jump_list + * + */ +void +ssh_proxyjumps_free(struct ssh_list *proxy_jump_list) +{ + struct ssh_jump_info_struct *jump = NULL; + + for (jump = + ssh_list_pop_head(struct ssh_jump_info_struct *, proxy_jump_list); + jump != NULL; + jump = ssh_list_pop_head(struct ssh_jump_info_struct *, + proxy_jump_list)) { + SAFE_FREE(jump->hostname); + SAFE_FREE(jump->username); + SAFE_FREE(jump); + } +} + +/** + * @brief Check if libssh proxy jumps is enabled + * + * If env variable OPENSSH_PROXYJUMP is set to 1 then proxyjump will be + * through the OpenSSH binary. + * + * @return false if OPENSSH_PROXYJUMP=1 + * true otherwise + */ +bool +ssh_libssh_proxy_jumps(void) +{ + const char *t = getenv("OPENSSH_PROXYJUMP"); + + return !(t != NULL && t[0] == '1'); +} + +/** + * @internal + * + * @brief Safely open a file containing some configuration. + * + * Runs checks if the file can be used as some configuration file (is regular + * file and is not too large). If so, returns the opened file (for reading). + * Otherwise logs error and returns `NULL`. + * + * @param filename The path to the file to open. + * @param max_file_size Maximum file size that is accepted. + * + * @returns the opened file or `NULL` on error. + */ +FILE *ssh_strict_fopen(const char *filename, size_t max_file_size) +{ + FILE *f = NULL; + struct stat sb; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + int r, fd; + + /* open first to avoid TOCTOU */ + fd = open(filename, O_RDONLY); + if (fd == -1) { + SSH_LOG(SSH_LOG_RARE, + "Failed to open a file %s for reading: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return NULL; + } + + /* Check the file is sensible for a configuration file */ + r = fstat(fd, &sb); + if (r != 0) { + SSH_LOG(SSH_LOG_RARE, + "Failed to stat %s: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + close(fd); + return NULL; + } + if ((sb.st_mode & S_IFMT) != S_IFREG) { + SSH_LOG(SSH_LOG_RARE, + "The file %s is not a regular file: skipping", + filename); + close(fd); + return NULL; + } + + if ((size_t)sb.st_size > max_file_size) { + SSH_LOG(SSH_LOG_RARE, + "The file %s is too large (%jd MB > %zu MB): skipping", + filename, + (intmax_t)sb.st_size / 1024 / 1024, + max_file_size / 1024 / 1024); + close(fd); + return NULL; + } + + f = fdopen(fd, "r"); + if (f == NULL) { + SSH_LOG(SSH_LOG_RARE, + "Failed to open a file %s for reading: %s", + filename, + ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); + close(fd); + return NULL; + } + + /* the flcose() will close also the underlying fd */ + return f; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/mlkem.c b/src/libs/libssh-0.12.2/src/mlkem.c new file mode 100644 index 000000000000..967c4f11ea55 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mlkem.c @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Pavol Žáčik + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/mlkem.h" + +const struct mlkem_type_info *kex_type_to_mlkem_info(enum ssh_key_exchange_e kex_type) +{ + switch (kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + return &MLKEM768_INFO; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + return &MLKEM1024_INFO; +#endif + default: + return NULL; + } +} diff --git a/src/libs/libssh-0.12.2/src/mlkem_crypto.c b/src/libs/libssh-0.12.2/src/mlkem_crypto.c new file mode 100644 index 000000000000..8f58b80bf7cb --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mlkem_crypto.c @@ -0,0 +1,279 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Pavol Žáčik + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/mlkem.h" +#include "libssh/session.h" + +#include +#include +#include + +const struct mlkem_type_info MLKEM768_INFO = { + .pubkey_size = OSSL_ML_KEM_768_PUBLIC_KEY_BYTES, + .ciphertext_size = OSSL_ML_KEM_768_CIPHERTEXT_BYTES, + .name = LN_ML_KEM_768, +}; + +const struct mlkem_type_info MLKEM1024_INFO = { + .pubkey_size = OSSL_ML_KEM_1024_PUBLIC_KEY_BYTES, + .ciphertext_size = OSSL_ML_KEM_1024_CIPHERTEXT_BYTES, + .name = LN_ML_KEM_1024, +}; + +static const char *ssh_mlkem_get_propq(const struct mlkem_type_info *mlkem_info) +{ + static const char *propq = NULL; + static bool is_cached = false; + EVP_KEM *kem = NULL; + + if (is_cached) { + return propq; + } + is_cached = true; + + if (!ssh_fips_mode()) { + return propq; + } + + kem = EVP_KEM_fetch(NULL, mlkem_info->name, NULL); + if (kem != NULL) { + EVP_KEM_free(kem); + return propq; + } + + propq = FIPS_FALLBACK_PROPQ; + return propq; +} + +int ssh_mlkem_init(ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + int rc, ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string pubkey = NULL; + size_t pubkey_size; + const char *propq = NULL; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + propq = ssh_mlkem_get_propq(mlkem_info); + + ctx = EVP_PKEY_CTX_new_from_name(NULL, mlkem_info->name, propq); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_keygen_init(ctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to initialize ML-KEM keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_keygen(ctx, &pkey); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to perform ML-KEM keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + EVP_PKEY_free(crypto->mlkem_privkey); + crypto->mlkem_privkey = pkey; + + pubkey_size = mlkem_info->pubkey_size; + pubkey = ssh_string_new(pubkey_size); + if (pubkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = EVP_PKEY_get_raw_public_key(pkey, + ssh_string_data(pubkey), + &pubkey_size); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to extract ML-KEM public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_client_pubkey); + crypto->mlkem_client_pubkey = pubkey; + pubkey = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(pubkey); + EVP_PKEY_CTX_free(ctx); + return ret; +} + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + int rc, ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + const unsigned char *pubkey = ssh_string_data(crypto->mlkem_client_pubkey); + const size_t pubkey_len = ssh_string_len(crypto->mlkem_client_pubkey); + size_t shared_secret_size = MLKEM_SHARED_SECRET_SIZE; + ssh_string ciphertext = NULL; + size_t ciphertext_size; + const char *propq = NULL; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + propq = ssh_mlkem_get_propq(mlkem_info); + + pkey = EVP_PKEY_new_raw_public_key_ex(NULL, + mlkem_info->name, + propq, + pubkey, + pubkey_len); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM public key from raw data: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, propq); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_encapsulate_init(ctx, NULL); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to initialize ML-KEM encapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ciphertext_size = mlkem_info->ciphertext_size; + ciphertext = ssh_string_new(ciphertext_size); + if (ciphertext == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = EVP_PKEY_encapsulate(ctx, + ssh_string_data(ciphertext), + &ciphertext_size, + shared_secret, + &shared_secret_size); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to perform ML-KEM encapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_ciphertext); + crypto->mlkem_ciphertext = ciphertext; + ciphertext = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(ciphertext); + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + return ret; +} + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + EVP_PKEY_CTX *ctx = NULL; + int rc, ret = SSH_ERROR; + size_t shared_secret_size = MLKEM_SHARED_SECRET_SIZE; + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + const char *propq = NULL; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + propq = ssh_mlkem_get_propq(mlkem_info); + + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, crypto->mlkem_privkey, propq); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_decapsulate_init(ctx, NULL); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to initialize ML-KEM decapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_decapsulate(ctx, + shared_secret, + &shared_secret_size, + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->mlkem_ciphertext)); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to perform ML-KEM decapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ret = SSH_OK; + +cleanup: + EVP_PKEY_CTX_free(ctx); + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/mlkem_gcrypt.c b/src/libs/libssh-0.12.2/src/mlkem_gcrypt.c new file mode 100644 index 000000000000..1898becbe3ed --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mlkem_gcrypt.c @@ -0,0 +1,207 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/mlkem.h" +#include "libssh/session.h" + +#include + +const struct mlkem_type_info MLKEM768_INFO = { + .pubkey_size = GCRY_KEM_MLKEM768_PUBKEY_LEN, + .privkey_size = GCRY_KEM_MLKEM768_SECKEY_LEN, + .ciphertext_size = GCRY_KEM_MLKEM768_CIPHER_LEN, + .alg = GCRY_KEM_MLKEM768, +}; + +const struct mlkem_type_info MLKEM1024_INFO = { + .pubkey_size = GCRY_KEM_MLKEM1024_PUBKEY_LEN, + .privkey_size = GCRY_KEM_MLKEM1024_SECKEY_LEN, + .ciphertext_size = GCRY_KEM_MLKEM1024_CIPHER_LEN, + .alg = GCRY_KEM_MLKEM1024, +}; + +int ssh_mlkem_init(ssh_session session) +{ + int ret = SSH_ERROR; + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string pubkey = NULL; + unsigned char *privkey = NULL, *pubkey_data = NULL; + gcry_error_t err; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + privkey = malloc(mlkem_info->privkey_size); + if (privkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + pubkey = ssh_string_new(mlkem_info->pubkey_size); + if (pubkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + pubkey_data = ssh_string_data(pubkey); + err = gcry_kem_keypair(mlkem_info->alg, + pubkey_data, + mlkem_info->pubkey_size, + privkey, + mlkem_info->privkey_size); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate ML-KEM key: %s", + gpg_strerror(err)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_client_pubkey); + crypto->mlkem_client_pubkey = pubkey; + pubkey = NULL; + + free(crypto->mlkem_privkey); + crypto->mlkem_privkey = privkey; + crypto->mlkem_privkey_len = mlkem_info->privkey_size; + privkey = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(pubkey); + if (privkey != NULL) { + ssh_burn(privkey, mlkem_info->privkey_size); + free(privkey); + } + return ret; +} + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + int ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + const unsigned char *pubkey_data = NULL; + unsigned char *ciphertext_data = NULL; + ssh_string ciphertext = NULL; + ssh_string pubkey = crypto->mlkem_client_pubkey; + gcry_error_t err; + + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing pubkey in session"); + return SSH_ERROR; + } + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + ciphertext = ssh_string_new(mlkem_info->ciphertext_size); + if (ciphertext == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + pubkey_data = ssh_string_data(pubkey); + ciphertext_data = ssh_string_data(ciphertext); + err = gcry_kem_encap(mlkem_info->alg, + pubkey_data, + mlkem_info->pubkey_size, + ciphertext_data, + mlkem_info->ciphertext_size, + shared_secret, + MLKEM_SHARED_SECRET_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to encapsulate ML-KEM shared secret: %s", + gpg_strerror(err)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_ciphertext); + crypto->mlkem_ciphertext = ciphertext; + ciphertext = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(ciphertext); + return ret; +} + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string ciphertext = NULL; + unsigned char *ciphertext_data = NULL; + gcry_error_t err; + + ciphertext = crypto->mlkem_ciphertext; + if (ciphertext == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing ciphertext in session"); + return SSH_ERROR; + } + + if (crypto->mlkem_privkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing ML-KEM private key in session"); + return SSH_ERROR; + } + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + ciphertext_data = ssh_string_data(ciphertext); + err = gcry_kem_decap(mlkem_info->alg, + crypto->mlkem_privkey, + mlkem_info->privkey_size, + ciphertext_data, + mlkem_info->ciphertext_size, + shared_secret, + MLKEM_SHARED_SECRET_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to decapsulate ML-KEM shared secret: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/mlkem_native.c b/src/libs/libssh-0.12.2/src/mlkem_native.c new file mode 100644 index 000000000000..97b22a36b5a6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mlkem_native.c @@ -0,0 +1,202 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/mlkem.h" +#include "libssh/mlkem_native.h" +#include "libssh/session.h" + +#define crypto_kem_mlkem768_PUBLICKEYBYTES 1184 +#define crypto_kem_mlkem768_SECRETKEYBYTES 2400 +#define crypto_kem_mlkem768_CIPHERTEXTBYTES 1088 + +const struct mlkem_type_info MLKEM768_INFO = { + .pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES, + .privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES, + .ciphertext_size = crypto_kem_mlkem768_CIPHERTEXTBYTES, +}; + +int ssh_mlkem_init(ssh_session session) +{ + int ret = SSH_ERROR; + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + unsigned char rnd[LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN]; + struct libcrux_mlkem768_keypair keypair; + int err; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + err = ssh_get_random(rnd, sizeof(rnd), 0); + if (err != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to generate random data for ML-KEM keygen"); + goto cleanup; + } + + keypair = libcrux_ml_kem_mlkem768_portable_generate_key_pair(rnd); + + if (ssh_string_len(crypto->mlkem_client_pubkey) < mlkem_info->pubkey_size) { + SSH_STRING_FREE(crypto->mlkem_client_pubkey); + } + if (crypto->mlkem_client_pubkey == NULL) { + crypto->mlkem_client_pubkey = ssh_string_new(mlkem_info->pubkey_size); + if (crypto->mlkem_client_pubkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + } + err = ssh_string_fill(crypto->mlkem_client_pubkey, + keypair.pk.value, + mlkem_info->pubkey_size); + if (err) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to fill the string with client pubkey"); + goto cleanup; + } + + if (crypto->mlkem_privkey == NULL) { + crypto->mlkem_privkey = malloc(mlkem_info->privkey_size); + if (crypto->mlkem_privkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + } + memcpy(crypto->mlkem_privkey, keypair.sk.value, mlkem_info->privkey_size); + crypto->mlkem_privkey_len = mlkem_info->privkey_size; + + ret = SSH_OK; + +cleanup: + ssh_burn(&keypair, sizeof(keypair)); + ssh_burn(rnd, sizeof(rnd)); + return ret; +} + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + int ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + const unsigned char *pubkey_data = NULL; + ssh_string pubkey = crypto->mlkem_client_pubkey; + struct libcrux_mlkem768_enc_result enc; + struct libcrux_mlkem768_pk mlkem_pub = {0}; + unsigned char rnd[LIBCRUX_ML_KEM_ENC_PRNG_LEN]; + int err; + + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing pubkey in session"); + return SSH_ERROR; + } + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + pubkey_data = ssh_string_data(pubkey); + memcpy(mlkem_pub.value, pubkey_data, mlkem_info->pubkey_size); + err = libcrux_ml_kem_mlkem768_portable_validate_public_key(&mlkem_pub); + if (err == 0) { + SSH_LOG(SSH_LOG_WARNING, "Invalid public key"); + return SSH_ERROR; + } + + err = ssh_get_random(rnd, sizeof(rnd), 0); + if (err != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to generate random data for ML-KEM keygen"); + goto cleanup; + } + + enc = libcrux_ml_kem_mlkem768_portable_encapsulate(&mlkem_pub, rnd); + + if (ssh_string_len(crypto->mlkem_ciphertext) < mlkem_info->ciphertext_size) { + SSH_STRING_FREE(crypto->mlkem_ciphertext); + } + if (crypto->mlkem_ciphertext == NULL) { + crypto->mlkem_ciphertext = ssh_string_new(mlkem_info->ciphertext_size); + if (crypto->mlkem_ciphertext == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + } + err = ssh_string_fill(crypto->mlkem_ciphertext, + enc.fst.value, + sizeof(enc.fst.value)); + if (err != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, "Failed to fill the string with ciphertext"); + goto cleanup; + } + memcpy(shared_secret, enc.snd, sizeof(enc.snd)); + + ret = SSH_OK; + +cleanup: + ssh_burn(rnd, sizeof(rnd)); + ssh_burn(&enc, sizeof(enc)); + return ret; +} + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string ciphertext = NULL; + unsigned char *ciphertext_data = NULL; + struct libcrux_mlkem768_sk mlkem_priv = {0}; + struct libcrux_mlkem768_ciphertext mlkem_ciphertext = {0}; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + ciphertext = crypto->mlkem_ciphertext; + if (ciphertext == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing ciphertext in session"); + return SSH_ERROR; + } + + ciphertext_data = ssh_string_data(ciphertext); + memcpy(mlkem_ciphertext.value, + ciphertext_data, + sizeof(mlkem_ciphertext.value)); + + memcpy(mlkem_priv.value, crypto->mlkem_privkey, crypto->mlkem_privkey_len); + + libcrux_ml_kem_mlkem768_portable_decapsulate(&mlkem_priv, + &mlkem_ciphertext, + shared_secret); + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/options.c b/src/libs/libssh-0.12.2/src/options.c new file mode 100644 index 000000000000..1563c907be08 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/options.c @@ -0,0 +1,3120 @@ +/* + * options.c - handle pre-connection options + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include +#ifndef _WIN32 +#include +#else +#include +#endif +#include "libssh/config.h" +#include "libssh/config_parser.h" +#include "libssh/misc.h" +#include "libssh/options.h" +#include "libssh/pki.h" +#include "libssh/pki_context.h" +#include "libssh/pki_priv.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include +#include "libssh/misc.h" +#include "libssh/options.h" +#include "libssh/config_parser.h" +#include "libssh/gssapi.h" +#include "libssh/token.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#include "libssh/bind.h" +#include "libssh/bind_config.h" +#endif + +/** + * @addtogroup libssh_session + * @{ + */ + +/** + * @brief Duplicate the options of a session structure. + * + * If you make several sessions with the same options this is useful. You + * cannot use twice the same option structure in ssh_connect. + * + * @param src The session to use to copy the options. + * + * @param dest A pointer to store the allocated session with duplicated + * options. You have to free the memory using ssh_free() + * + * @returns 0 on success, -1 on error with errno set. + * + * @see ssh_connect() + * @see ssh_free() + */ +int ssh_options_copy(ssh_session src, ssh_session *dest) +{ + ssh_session new = NULL; + struct ssh_iterator *it = NULL; + struct ssh_list *list = NULL; + char *id = NULL; + int i; + + if (src == NULL || dest == NULL) { + return -1; + } + + new = ssh_new(); + if (new == NULL) { + return -1; + } + + if (src->opts.username != NULL) { + new->opts.username = strdup(src->opts.username); + if (new->opts.username == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.host != NULL) { + new->opts.host = strdup(src->opts.host); + if (new->opts.host == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.bindaddr != NULL) { + new->opts.bindaddr = strdup(src->opts.bindaddr); + if (new->opts.bindaddr == NULL) { + ssh_free(new); + return -1; + } + } + + /* Remove the default identities */ + for (id = ssh_list_pop_head(char *, new->opts.identity_non_exp); + id != NULL; + id = ssh_list_pop_head(char *, new->opts.identity_non_exp)) { + SAFE_FREE(id); + } + /* Copy the new identities from the source list */ + list = new->opts.identity_non_exp; + it = ssh_list_get_iterator(src->opts.identity_non_exp); + for (i = 0; i < 2; i++) { + while (it) { + int rc; + + id = strdup((char *)it->data); + if (id == NULL) { + ssh_free(new); + return -1; + } + + rc = ssh_list_append(list, id); + if (rc < 0) { + free(id); + ssh_free(new); + return -1; + } + it = it->next; + } + + /* copy the identity list if there is any already */ + list = new->opts.identity; + it = ssh_list_get_iterator(src->opts.identity); + } + + list = new->opts.certificate_non_exp; + it = ssh_list_get_iterator(src->opts.certificate_non_exp); + for (i = 0; i < 2; i++) { + while (it) { + int rc; + + id = strdup((char *)it->data); + if (id == NULL) { + ssh_free(new); + return -1; + } + + rc = ssh_list_append(list, id); + if (rc < 0) { + free(id); + ssh_free(new); + return -1; + } + it = it->next; + } + + /* copy the certificate list if there is any already */ + list = new->opts.certificate; + it = ssh_list_get_iterator(src->opts.certificate); + } + + if (src->opts.sshdir != NULL) { + new->opts.sshdir = strdup(src->opts.sshdir); + if (new->opts.sshdir == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.knownhosts != NULL) { + new->opts.knownhosts = strdup(src->opts.knownhosts); + if (new->opts.knownhosts == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.global_knownhosts != NULL) { + new->opts.global_knownhosts = strdup(src->opts.global_knownhosts); + if (new->opts.global_knownhosts == NULL) { + ssh_free(new); + return -1; + } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (src->opts.wanted_methods[i] != NULL) { + new->opts.wanted_methods[i] = strdup(src->opts.wanted_methods[i]); + if (new->opts.wanted_methods[i] == NULL) { + ssh_free(new); + return -1; + } + } + } + + if (src->opts.ProxyCommand != NULL) { + new->opts.ProxyCommand = strdup(src->opts.ProxyCommand); + if (new->opts.ProxyCommand == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.pubkey_accepted_types != NULL) { + new->opts.pubkey_accepted_types = strdup(src->opts.pubkey_accepted_types); + if (new->opts.pubkey_accepted_types == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.gss_server_identity != NULL) { + new->opts.gss_server_identity = strdup(src->opts.gss_server_identity); + if (new->opts.gss_server_identity == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.gss_client_identity != NULL) { + new->opts.gss_client_identity = strdup(src->opts.gss_client_identity); + if (new->opts.gss_client_identity == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.control_path != NULL) { + new->opts.control_path = strdup(src->opts.control_path); + if (new->opts.control_path == NULL) { + ssh_free(new); + return -1; + } + } + + memcpy(new->opts.options_seen, src->opts.options_seen, + sizeof(new->opts.options_seen)); + + new->opts.fd = src->opts.fd; + new->opts.port = src->opts.port; + new->opts.timeout = src->opts.timeout; + new->opts.timeout_usec = src->opts.timeout_usec; + new->opts.compressionlevel = src->opts.compressionlevel; + new->opts.StrictHostKeyChecking = src->opts.StrictHostKeyChecking; + new->opts.gss_delegate_creds = src->opts.gss_delegate_creds; + new->opts.flags = src->opts.flags; + new->opts.nodelay = src->opts.nodelay; + new->opts.config_processed = src->opts.config_processed; + new->opts.control_master = src->opts.control_master; + new->opts.address_family = src->opts.address_family; + new->common.log_verbosity = src->common.log_verbosity; + new->common.callbacks = src->common.callbacks; + + SSH_PKI_CTX_FREE(new->pki_context); + if (src->pki_context != NULL) { + new->pki_context = ssh_pki_ctx_dup(src->pki_context); + if (new->pki_context == NULL) { + ssh_free(new); + return -1; + } + } + + *dest = new; + + return 0; +} + +int ssh_options_set_algo(ssh_session session, + enum ssh_kex_types_e algo, + const char *list, + char **place) +{ + /* When the list start with +,-,^ the filtration of unknown algorithms + * gets handled inside the helper functions, otherwise the list is taken + * as it is. */ + char *p = (char *)list; + + if (algo < SSH_COMP_C_S) { + if (list[0] == '+') { + p = ssh_add_to_default_algos(algo, list+1); + } else if (list[0] == '-') { + p = ssh_remove_from_default_algos(algo, list+1); + } else if (list[0] == '^') { + p = ssh_prefix_default_algos(algo, list+1); + } + } + + if (p == list) { + if (ssh_fips_mode()) { + p = ssh_keep_fips_algos(algo, list); + } else { + p = ssh_keep_known_algos(algo, list); + } + } + + if (p == NULL) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Setting method: no allowed algorithm for method \"%s\" (%s)", + ssh_kex_get_description(algo), list); + return -1; + } + + SAFE_FREE(*place); + *place = p; + + return 0; +} + +/* + * Map a public ssh_options_e onto the internal config opcode whose parser + * case applies it via ssh_options_set(). Used to mark a value as "seen" when + * an application sets it explicitly, so later config-file processing does not + * override it (OpenSSH "first obtained value wins" semantics). + * + * Returns SOC_UNKNOWN for options that must NOT be protected: + * - accumulative options (IdentityFile/CertificateFile and friends), + * - SSH_OPTIONS_HOST, the Host/Match lookup key that config HostName + * intentionally overrides during alias resolution, + * - operational settings such as log verbosity, + * - options that have no ssh_config equivalent, + * - getter-only options, which ssh_options_set() never accepts. + */ +static enum ssh_config_opcode_e ssh_opt_type_to_opcode(enum ssh_options_e type) +{ + switch (type) { + case SSH_OPTIONS_PORT: + case SSH_OPTIONS_PORT_STR: + return SOC_PORT; + case SSH_OPTIONS_USER: + return SOC_USERNAME; + case SSH_OPTIONS_KNOWNHOSTS: + return SOC_KNOWNHOSTS; + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + return SOC_GLOBALKNOWNHOSTSFILE; + case SSH_OPTIONS_TIMEOUT: + return SOC_TIMEOUT; + case SSH_OPTIONS_CIPHERS_C_S: + case SSH_OPTIONS_CIPHERS_S_C: + return SOC_CIPHERS; + case SSH_OPTIONS_COMPRESSION: + case SSH_OPTIONS_COMPRESSION_C_S: + case SSH_OPTIONS_COMPRESSION_S_C: + return SOC_COMPRESSION; + case SSH_OPTIONS_PROXYCOMMAND: + return SOC_PROXYCOMMAND; + case SSH_OPTIONS_PROXYJUMP: + return SOC_PROXYJUMP; + case SSH_OPTIONS_BINDADDR: + return SOC_BINDADDRESS; + case SSH_OPTIONS_STRICTHOSTKEYCHECK: + return SOC_STRICTHOSTKEYCHECK; + case SSH_OPTIONS_KEY_EXCHANGE: + return SOC_KEXALGORITHMS; + case SSH_OPTIONS_HOSTKEYS: + return SOC_HOSTKEYALGORITHMS; + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + return SOC_PUBKEYACCEPTEDKEYTYPES; + case SSH_OPTIONS_HMAC_C_S: + case SSH_OPTIONS_HMAC_S_C: + return SOC_MACS; + case SSH_OPTIONS_GSSAPI_SERVER_IDENTITY: + return SOC_GSSAPISERVERIDENTITY; + case SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY: + return SOC_GSSAPICLIENTIDENTITY; + case SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS: + return SOC_GSSAPIDELEGATECREDENTIALS; + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE: + return SOC_GSSAPIKEYEXCHANGE; + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + return SOC_GSSAPIKEXALGORITHMS; + case SSH_OPTIONS_PASSWORD_AUTH: + return SOC_PASSWORDAUTHENTICATION; + case SSH_OPTIONS_PUBKEY_AUTH: + return SOC_PUBKEYAUTHENTICATION; + case SSH_OPTIONS_KBDINT_AUTH: + return SOC_KBDINTERACTIVEAUTHENTICATION; + case SSH_OPTIONS_GSSAPI_AUTH: + return SOC_GSSAPIAUTHENTICATION; + case SSH_OPTIONS_REKEY_DATA: + case SSH_OPTIONS_REKEY_TIME: + return SOC_REKEYLIMIT; + case SSH_OPTIONS_RSA_MIN_SIZE: + return SOC_REQUIRED_RSA_SIZE; + case SSH_OPTIONS_IDENTITY_AGENT: + return SOC_IDENTITYAGENT; + case SSH_OPTIONS_IDENTITIES_ONLY: + return SOC_IDENTITIESONLY; + case SSH_OPTIONS_CONTROL_MASTER: + return SOC_CONTROLMASTER; + case SSH_OPTIONS_CONTROL_PATH: + return SOC_CONTROLPATH; + case SSH_OPTIONS_ADDRESS_FAMILY: + return SOC_ADDRESSFAMILY; + /* + * Accumulative options append to a list instead of replacing a value, so + * the "first value wins" precedence between config and the application does + * not apply to them. + */ + case SSH_OPTIONS_IDENTITY: + case SSH_OPTIONS_ADD_IDENTITY: + case SSH_OPTIONS_CERTIFICATE: + case SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND: + /* + * SSH_OPTIONS_HOST carries the destination as given by the application, + * which is OpenSSH's "host" (the Host/Match lookup key), not its + * "hostname". Config HostName resolves that key to the real hostname and + * must keep doing so. HostName has its own "first value wins" precedence + * between config entries, enforced independently via seen[SOC_HOSTNAME] + * while parsing the configuration. + */ + case SSH_OPTIONS_HOST: + /* + * Operational settings that applications and frameworks routinely set on + * their own, independent of the connection configuration. OpenSSH's config + * parser notably does not let a previously-set value suppress LogLevel, so + * we follow it and leave log verbosity unprotected. + */ + case SSH_OPTIONS_LOG_VERBOSITY: + case SSH_OPTIONS_LOG_VERBOSITY_STR: + /* + * Options with no OpenSSH ssh_config equivalent (or that are never applied + * from a config file), so there is no config value that could override the + * application's choice. + */ + case SSH_OPTIONS_FD: + case SSH_OPTIONS_SSH_DIR: + case SSH_OPTIONS_SSH1: + case SSH_OPTIONS_SSH2: + case SSH_OPTIONS_TIMEOUT_USEC: + case SSH_OPTIONS_COMPRESSION_LEVEL: + case SSH_OPTIONS_NODELAY: + case SSH_OPTIONS_PROCESS_CONFIG: + case SSH_OPTIONS_PKI_CONTEXT: + /* + * Getter-only options: ssh_options_set() rejects them, so they never reach + * the marking step. Listed to keep the switch exhaustive. + */ + case SSH_OPTIONS_NEXT_IDENTITY: + return SOC_UNKNOWN; + } + + return SOC_UNKNOWN; +} + +/** + * @brief This function can set all possible ssh options. + * + * @param session An allocated SSH session structure. + * + * @param type The option type to set. This could be one of the + * following: + * + * - SSH_OPTIONS_HOST: + * The hostname or ip address to connect to. It can be also in + * the format of URI, containing also username, such as + * [username@]hostname. The IPv6 addresses can be enclosed + * within square braces, for example [::1]. The IPv4 address + * supports any format supported by OS. The hostname needs to be + * encoded to match RFC1035, so for IDN it needs to be encoded + * in punycode. + * (const char *). + * + * - SSH_OPTIONS_PORT: + * The port to connect to (unsigned int). + * + * - SSH_OPTIONS_PORT_STR: + * The port to connect to (const char *). + * + * - SSH_OPTIONS_FD: + * The file descriptor to use (socket_t).\n + * \n + * If you wish to open the socket yourself for a reason + * or another, set the file descriptor and take care of closing + * it (this is new behavior in libssh 0.10). + * Don't forget to set the hostname as the hostname is used + * as a key in the known_host mechanism. + * + * - SSH_OPTIONS_BINDADDR: + * The address to bind the client to (const char *). + * + * - SSH_OPTIONS_USER: + * The username for authentication (const char *).\n + * \n + * If the value is NULL, the username is set to the + * default username. + * + * - SSH_OPTIONS_SSH_DIR: + * Set the ssh directory (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default ssh directory.\n + * \n + * The ssh directory is used for files like known_hosts + * and identity (private and public key). It may start + * with ~ which will be replaced by the user home + * directory. + * + * - SSH_OPTIONS_KNOWNHOSTS: + * Set the known hosts file name (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default known hosts file, normally + * ~/.ssh/known_hosts.\n + * \n + * The known hosts file is used to certify remote hosts + * are genuine. It may include "%d" which will be + * replaced by the user home directory. + * + * - SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + * Set the global known hosts file name (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default global known hosts file, normally + * /etc/ssh/ssh_known_hosts.\n + * \n + * The known hosts file is used to certify remote hosts + * are genuine. + * + * - SSH_OPTIONS_ADD_IDENTITY (or SSH_OPTIONS_IDENTITY): + * Add a new identity file (const char *, format string) to + * the identity list.\n + * \n + * By default id_rsa, id_ecdsa and id_ed25519 files are used.\n + * If libssh is built with FIDO2/U2F support, id_ecdsa_sk and\n + * id_ed25519_sk files are also used by default.\n + * \n + * The identity used to authenticate with public key will be + * prepended to the list. + * It may include "%s" which will be replaced by the + * user home directory. + * + * - SSH_OPTIONS_CERTIFICATE: + * Add a new certificate file (const char *, format string) to + * the certificate list.\n + * \n + * By default id_rsa-cert.pub, id_ecdsa-cert.pub and + * id_ed25519-cert.pub files are used, when the underlying + * private key is present.\n + * \n + * The certificate itself can not be used to authenticate to + * remote server so it needs to be paired with private key + * (aka identity file) provided with separate option, from agent + * or from PKCS#11 token. + * It may include "%s" which will be replaced by the + * user home directory. + * + * - SSH_OPTIONS_TIMEOUT: + * Set a timeout for the connection in seconds (long). + * + * - SSH_OPTIONS_TIMEOUT_USEC: + * Set a timeout for the connection in micro seconds + * (long). + * + * - SSH_OPTIONS_SSH1: + * Deprecated + * + * - SSH_OPTIONS_SSH2: + * Unused + * + * - SSH_OPTIONS_LOG_VERBOSITY: + * Set the session logging verbosity (int).\n + * \n + * The verbosity of the messages. Every log smaller or + * equal to verbosity will be shown. + * - SSH_LOG_NOLOG: No logging + * - SSH_LOG_WARNING: Only warnings + * - SSH_LOG_PROTOCOL: High level protocol information + * - SSH_LOG_PACKET: Lower level protocol information, packet level + * - SSH_LOG_FUNCTIONS: Every function path + * The default is SSH_LOG_NOLOG. + * + * - SSH_OPTIONS_LOG_VERBOSITY_STR: + * Set the session logging verbosity via a + * string that will be converted to a numerical + * value (e.g. "3") and interpreted according + * to the values of + * SSH_OPTIONS_LOG_VERBOSITY above (const + * char *). + * + * - SSH_OPTIONS_CIPHERS_C_S: + * Set the symmetric cipher client to server (const char *, + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. + * + * - SSH_OPTIONS_CIPHERS_S_C: + * Set the symmetric cipher server to client (const char *, + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. + * + * - SSH_OPTIONS_KEY_EXCHANGE: + * Set the key exchange method to be used (const char *, + * comma-separated list). ex: + * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. + * + * - SSH_OPTIONS_HMAC_C_S: + * Set the Message Authentication Code algorithm client to server + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. + * + * - SSH_OPTIONS_HMAC_S_C: + * Set the Message Authentication Code algorithm server to client + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. + * + * - SSH_OPTIONS_HOSTKEYS: + * Set the preferred server host key types (const char *, + * comma-separated list). ex: + * "ssh-rsa,ecdh-sha2-nistp256". The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. + * + * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + * Set the preferred public key algorithms to be used for + * authentication (const char *, comma-separated list). ex: + * "ssh-rsa,rsa-sha2-256,ecdh-sha2-nistp256" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. + * + * - SSH_OPTIONS_COMPRESSION_C_S: + * Set the compression to use for client to server + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION_S_C: + * Set the compression to use for server to client + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION: + * Set the compression to use for both directions + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION_LEVEL: + * Set the compression level to use for zlib functions. (int, + * value from 1 to 9, 9 being the most efficient but slower). + * + * - SSH_OPTIONS_STRICTHOSTKEYCHECK: + * Set the parameter StrictHostKeyChecking to avoid + * asking about a fingerprint (int, 0 = false). + * + * - SSH_OPTIONS_PROXYCOMMAND: + * Set the command to be executed in order to connect to + * server (const char *). + * + * - SSH_OPTIONS_PROXYJUMP: + * Set the comma separated jump hosts in order to connect to + * server (const char *). Set to "none" to disable. + * Example: + * "alice@127.0.0.1:5555,bob@127.0.0.2" + * + * If environment variable OPENSSH_PROXYJUMP is set to 1 then proxyjump will be + * handled by the OpenSSH binary. + * + * - SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND: + * Append the callbacks struct for a jump in order of + * SSH_OPTIONS_PROXYJUMP. Append as many times + * as the number of jumps (struct ssh_jump_callbacks_struct *). + * + * - SSH_OPTIONS_GSSAPI_SERVER_IDENTITY + * Set it to specify the GSSAPI server identity that libssh + * should expect when connecting to the server (const char *). + * + * - SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY + * Set it to specify the GSSAPI client identity that libssh + * should expect when connecting to the server (const char *). + * + * - SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS + * Set it to specify that GSSAPI should delegate credentials + * to the server (int, 0 = false). + * + * - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE + * Set to true to allow GSSAPI key exchange (bool). + * + * - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS + * Set the GSSAPI key exchange method to be used (const char *, + * comma-separated list). ex: + * "gss-curve25519-sha256-,gss-nistp256-sha256-" + * These will prefix the default algorithms if + * SSH_OPTIONS_GSSAPI_KEY_EXCHANGE is true. + * + * - SSH_OPTIONS_PASSWORD_AUTH + * Set it if password authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * Currently without effect (ssh_userauth_auto_pubkey doesn't use + * password authentication). + * + * - SSH_OPTIONS_PUBKEY_AUTH + * Set it if pubkey authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * + * - SSH_OPTIONS_KBDINT_AUTH + * Set it if keyboard-interactive authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * Currently without effect (ssh_userauth_auto_pubkey doesn't use + * keyboard-interactive authentication). + * + * - SSH_OPTIONS_GSSAPI_AUTH + * Set it if gssapi authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * Currently without effect (ssh_userauth_auto_pubkey doesn't use + * gssapi authentication). + * + * - SSH_OPTIONS_NODELAY + * Set it to disable Nagle's Algorithm (TCP_NODELAY) on the + * session socket. (int, 0=false) + * + * - SSH_OPTIONS_PROCESS_CONFIG + * Set it to false to disable automatic processing of per-user + * and system-wide OpenSSH configuration files. LibSSH + * automatically uses these configuration files unless + * you provide it with this option or with different file (bool). + * + * - SSH_OPTIONS_REKEY_DATA + * Set the data limit that can be transferred with a single + * key in bytes. RFC 4253 Section 9 recommends 1GB of data, while + * RFC 4344 provides more specific restrictions, that are applied + * automatically. When specified, the lower value will be used. + * (uint64_t, 0=default) + * + * - SSH_OPTIONS_REKEY_TIME + * Set the time limit for a session before initializing a rekey + * in seconds. RFC 4253 Section 9 recommends one hour. + * (uint32_t, 0=off) + * + * - SSH_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by the + * client for both authentication and hostkey verification. + * The values under 1024 bits are not accepted even with this + * configuration option as they are considered completely broken. + * Setting 0 will revert the value to defaults. + * Default is 3072 bits or 2048 bits in FIPS mode. + * (int) + * + * - SSH_OPTIONS_IDENTITY_AGENT + * Set the path to the SSH agent socket. If unset, the + * SSH_AUTH_SOCK environment is consulted. + * (const char *) + * + * - SSH_OPTIONS_IDENTITIES_ONLY + * Use only keys specified in the SSH config, even if agent + * offers more. + * (bool) + * + * - SSH_OPTIONS_CONTROL_MASTER + * Set the option to enable the sharing of multiple sessions over a + * single network connection using connection multiplexing (int). + * + * The possible options are among the following: + * - SSH_CONTROL_MASTER_AUTO: enable connection sharing if possible + * - SSH_CONTROL_MASTER_YES: enable connection sharing unconditionally + * - SSH_CONTROL_MASTER_ASK: ask for confirmation if connection sharing is to be enabled + * - SSH_CONTROL_MASTER_AUTOASK: enable connection sharing if possible, + * but ask for confirmation + * - SSH_CONTROL_MASTER_NO: disable connection sharing unconditionally + * + * The default is SSH_CONTROL_MASTER_NO. + * + * - SSH_OPTIONS_CONTROL_PATH + * Set the path to the control socket used for connection sharing. + * Set to "none" to disable connection sharing. + * (const char *) + * + * - SSH_OPTIONS_PKI_CONTEXT + * Attach a previously created generic PKI context to the + * session. This allows supplying per-session PKI + * configuration options for PKI operations. + * All fields from the user's context are copied to the session's + * own context. The user retains ownership of the original + * context and can free it after this call. + * (ssh_pki_ctx) + * + * - SSH_OPTIONS_ADDRESS_FAMILY + * Specify which address family to use when connecting. + * + * Possible options: + * - SSH_ADDRESS_FAMILY_ANY: use any address family + * - SSH_ADDRESS_FAMILY_INET: IPv4 only + * - SSH_ADDRESS_FAMILY_INET6: IPv6 only + * + * @param value The value to set. This is a generic pointer and the + * datatype which is used should be set according to the + * type set. + * + * @return 0 on success, < 0 on error. + * + * @warning When the option value to set is represented via a pointer + * (e.g const char * in case of strings, ssh_key in case of a + * libssh key), the value parameter should be that pointer. + * Do NOT pass a pointer to a pointer (const char **, ssh_key *) + * + * @warning When the option value to set is not a pointer (e.g int, + * unsigned int, bool, long), the value parameter should be + * a pointer to the location storing the value to set (int *, + * unsigned int *, bool *, long *) + * + * @warning If the value parameter has an invalid type (e.g if its not a + * pointer when it should have been a pointer, or if its a pointer + * to a pointer when it should have just been a pointer), then the + * behaviour is undefined. + */ +int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value) +{ + const char *v = NULL; + char *p = NULL, *q = NULL; + long int i; + unsigned int u; + int rc; + char **wanted_methods = session->opts.wanted_methods; + struct ssh_jump_callbacks_struct *j = NULL; + enum ssh_config_opcode_e opcode; + + if (session == NULL) { + return -1; + } + + switch (type) { + case SSH_OPTIONS_HOST: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + char *username = NULL, *hostname = NULL; + rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + return -1; + } + if (username != NULL) { + SAFE_FREE(session->opts.username); + session->opts.username = username; + } + if (hostname != NULL) { + SAFE_FREE(session->opts.host); + session->opts.host = hostname; + } + } + break; + case SSH_OPTIONS_PORT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x <= 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.port = *x & 0xffffU; + } + break; + case SSH_OPTIONS_PORT_STR: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(v); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No port number was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); + if (i <= 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.port = i & 0xffffU; + } + break; + case SSH_OPTIONS_FD: + if (value == NULL) { + session->opts.fd = SSH_INVALID_SOCKET; + ssh_set_error_invalid(session); + return -1; + } else { + socket_t *x = (socket_t *) value; + if (*x < 0) { + session->opts.fd = SSH_INVALID_SOCKET; + ssh_set_error_invalid(session); + return -1; + } + + session->opts.fd = *x & 0xffff; + } + break; + case SSH_OPTIONS_BINDADDR: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + + q = strdup(v); + if (q == NULL) { + return -1; + } + SAFE_FREE(session->opts.bindaddr); + session->opts.bindaddr = q; + break; + case SSH_OPTIONS_USER: + v = value; + SAFE_FREE(session->opts.username); + if (v == NULL) { + q = ssh_get_local_username(); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.username = q; + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { /* username provided */ + session->opts.username = strdup(value); + if (session->opts.username == NULL) { + ssh_set_error_oom(session); + return -1; + } + rc = ssh_check_username_syntax(session->opts.username); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + return -1; + } + } + break; + case SSH_OPTIONS_SSH_DIR: + v = value; + SAFE_FREE(session->opts.sshdir); + if (v == NULL) { + session->opts.sshdir = ssh_path_expand_tilde("~/.ssh"); + if (session->opts.sshdir == NULL) { + return -1; + } + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.sshdir = ssh_path_expand_tilde(v); + if (session->opts.sshdir == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_IDENTITY: + case SSH_OPTIONS_ADD_IDENTITY: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + q = strdup(v); + if (q == NULL) { + return -1; + } + if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) { + rc = ssh_list_append(session->opts.identity_non_exp, q); + } else { + rc = ssh_list_prepend(session->opts.identity_non_exp, q); + } + if (rc < 0) { + free(q); + return -1; + } + break; + case SSH_OPTIONS_CERTIFICATE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + q = strdup(v); + if (q == NULL) { + return -1; + } + rc = ssh_list_append(session->opts.certificate_non_exp, q); + if (rc < 0) { + free(q); + return -1; + } + break; + case SSH_OPTIONS_KNOWNHOSTS: + v = value; + SAFE_FREE(session->opts.knownhosts); + if (v == NULL) { + /* The default value will be set by the ssh_options_apply() */ + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.knownhosts = strdup(v); + if (session->opts.knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + } + break; + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + v = value; + SAFE_FREE(session->opts.global_knownhosts); + if (v == NULL) { + session->opts.global_knownhosts = + strdup(GLOBAL_CONF_DIR "/ssh_known_hosts"); + if (session->opts.global_knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.global_knownhosts = strdup(v); + if (session->opts.global_knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS; + } + break; + case SSH_OPTIONS_TIMEOUT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + long *x = (long *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.timeout = *x & 0xffffffffU; + } + break; + case SSH_OPTIONS_TIMEOUT_USEC: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + long *x = (long *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.timeout_usec = *x & 0xffffffffU; + } + break; + case SSH_OPTIONS_SSH1: + break; + case SSH_OPTIONS_SSH2: + break; + case SSH_OPTIONS_LOG_VERBOSITY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->common.log_verbosity = *x & 0xffffU; + ssh_set_log_level(*x & 0xffffU); + } + break; + case SSH_OPTIONS_LOG_VERBOSITY_STR: + v = value; + if (v == NULL || v[0] == '\0') { + session->common.log_verbosity = 0; + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(v); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); + if (i < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->common.log_verbosity = i & 0xffffU; + ssh_set_log_level(i & 0xffffU); + } + break; + case SSH_OPTIONS_CIPHERS_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_CIPHERS_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_KEY_EXCHANGE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_HOSTKEYS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &session->opts.pubkey_accepted_types); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_HMAC_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_HMAC_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_COMPRESSION_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com"; + } + rc = ssh_options_set_algo(session, + SSH_COMP_C_S, + tmp, + &wanted_methods[SSH_COMP_C_S]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_COMPRESSION_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com"; + } + + rc = ssh_options_set_algo(session, + SSH_COMP_S_C, + tmp, + &wanted_methods[SSH_COMP_S_C]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_COMPRESSION: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_C_S, v) < 0) + return -1; + if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_S_C, v) < 0) + return -1; + break; + case SSH_OPTIONS_COMPRESSION_LEVEL: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x < 1 || *x > 9) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.compressionlevel = *x & 0xff; + } + break; + case SSH_OPTIONS_STRICTHOSTKEYCHECK: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + + session->opts.StrictHostKeyChecking = (*x & 0xff) > 0 ? 1 : 0; + } + break; + case SSH_OPTIONS_PROXYCOMMAND: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.ProxyCommand); + /* Setting the command to 'none' disables this option. */ + rc = strcasecmp(v, "none"); + if (rc != 0) { + q = strdup(v); + if (q == NULL) { + return -1; + } + session->opts.ProxyCommand = q; + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_PROXYCOMMAND; + } + } + break; + case SSH_OPTIONS_PROXYJUMP: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_config_parse_proxy_jump(session, v, true); + if (rc != SSH_OK) { + return SSH_ERROR; + } + } + break; + case SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND: + j = (struct ssh_jump_callbacks_struct *)value; + if (j == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_list_prepend(session->opts.proxy_jumps_user_cb, j); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + break; + case SSH_OPTIONS_GSSAPI_SERVER_IDENTITY: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.gss_server_identity); + session->opts.gss_server_identity = strdup(v); + if (session->opts.gss_server_identity == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.gss_client_identity); + session->opts.gss_client_identity = strdup(v); + if (session->opts.gss_client_identity == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int x = *(int *)value; + + session->opts.gss_delegate_creds = (x & 0xff); + } + break; +#ifdef WITH_GSSAPI + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.gssapi_key_exchange = *x; + } + break; + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + /* Check if algorithms are supported */ + char *ret = + ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, v); + if (ret == NULL) { + ssh_set_error(session, + SSH_FATAL, + "GSSAPI key exchange algorithms not " + "supported or invalid"); + return -1; + } + SAFE_FREE(session->opts.gssapi_key_exchange_algs); + session->opts.gssapi_key_exchange_algs = ret; + } + break; +#endif + case SSH_OPTIONS_PASSWORD_AUTH: + case SSH_OPTIONS_PUBKEY_AUTH: + case SSH_OPTIONS_KBDINT_AUTH: + case SSH_OPTIONS_GSSAPI_AUTH: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int x = *(int *)value; + u = type == SSH_OPTIONS_PASSWORD_AUTH ? + SSH_OPT_FLAG_PASSWORD_AUTH: + type == SSH_OPTIONS_PUBKEY_AUTH ? + SSH_OPT_FLAG_PUBKEY_AUTH: + type == SSH_OPTIONS_KBDINT_AUTH ? + SSH_OPT_FLAG_KBDINT_AUTH: + SSH_OPT_FLAG_GSSAPI_AUTH; + if (x != 0){ + session->opts.flags |= u; + } else { + session->opts.flags &= ~u; + } + } + break; + case SSH_OPTIONS_NODELAY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + session->opts.nodelay = (*x & 0xff) > 0 ? 1 : 0; + } + break; + case SSH_OPTIONS_PROCESS_CONFIG: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.config_processed = !(*x); + } + break; + case SSH_OPTIONS_REKEY_DATA: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + uint64_t *x = (uint64_t *)value; + session->opts.rekey_data = *x; + } + break; + case SSH_OPTIONS_REKEY_TIME: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + uint32_t *x = (uint32_t *)value; + if ((*x * 1000) < *x) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The provided value (%" PRIu32 ") for rekey" + " time is too large", *x); + return -1; + } + session->opts.rekey_time = (*x) * 1000; + } + break; + case SSH_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + /* (*x == 0) is allowed as it is used to revert to default */ + + if (*x > 0 && *x < RSA_MIN_KEY_SIZE) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The provided value (%d) for minimal RSA key " + "size is too small. Use at least %d bits.", + *x, + RSA_MIN_KEY_SIZE); + return -1; + } + session->opts.rsa_min_size = *x; + } + break; + case SSH_OPTIONS_IDENTITY_AGENT: + v = value; + SAFE_FREE(session->opts.agent_socket); + if (v == NULL) { + /* The default value will be set by the ssh_options_apply() */ + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.agent_socket = ssh_path_expand_tilde(v); + if (session->opts.agent_socket == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_IDENTITIES_ONLY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.identities_only = *x; + } + break; + case SSH_OPTIONS_CONTROL_MASTER: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < SSH_CONTROL_MASTER_NO || *x > SSH_CONTROL_MASTER_AUTOASK) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.control_master = *x; + } + break; + case SSH_OPTIONS_CONTROL_PATH: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.control_path); + rc = strcasecmp(v, "none"); + if (rc != 0) { + session->opts.control_path = ssh_path_expand_tilde(v); + if (session->opts.control_path == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_CONTROL_PATH; + } + } + break; + case SSH_OPTIONS_PKI_CONTEXT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + SSH_PKI_CTX_FREE(session->pki_context); + + session->pki_context = ssh_pki_ctx_dup((const ssh_pki_ctx)value); + if (session->pki_context == NULL) { + ssh_set_error_oom(session); + return -1; + } + break; + case SSH_OPTIONS_ADDRESS_FAMILY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x < SSH_ADDRESS_FAMILY_ANY || + *x > SSH_ADDRESS_FAMILY_INET6) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.address_family = *x; + } + break; + default: + ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return -1; + break; + } + + /* + * The option was set successfully. Mark config-backed options as + * explicitly set so that later processing of OpenSSH configuration files + * keeps the application's value (issue #365). Options that map to + * SOC_UNKNOWN are intentionally left unmarked. + */ + opcode = ssh_opt_type_to_opcode(type); + if (opcode != SOC_UNKNOWN) { + session->opts.options_seen[opcode] = 1; + } + + return 0; +} + +/** + * @brief This function returns the current algorithms used for algorithm + * negotiation. It is either libssh default, option manually set or option + * read from configuration file. + * + * This function will return NULL on error + * + * @param session An allocated SSH session structure. + * @param algo One of the ssh_kex_types_e values. + */ +char *ssh_options_get_algo(ssh_session session, + enum ssh_kex_types_e algo) +{ + char *value = NULL; + + /* Check session and algo values are valid */ + + if (session == NULL) { + return NULL; + } + + if (algo >= SSH_LANG_C_S) { + ssh_set_error_invalid(session); + return NULL; + } + + /* Get the option the user has set, if there is one */ + value = session->opts.wanted_methods[algo]; + if (value == NULL) { + /* The user has not set a value, return the appropriate default */ + if (ssh_fips_mode()) + value = (char *)ssh_kex_get_fips_methods(algo); + else + value = (char *)ssh_kex_get_default_methods(algo); + } + + return value; +} + + +/** + * @brief This function can get ssh the ssh port. It must only be used on + * a valid ssh session. This function is useful when the session + * options have been automatically inferred from the environment + * or configuration files and one + * + * @param session An allocated SSH session structure. + * + * @param port_target An unsigned integer into which the + * port will be set from the ssh session. + * + * @return 0 on success, < 0 on error. + * + */ +int ssh_options_get_port(ssh_session session, unsigned int* port_target) { + if (session == NULL) { + return -1; + } + + if (session->opts.port == 0) { + *port_target = 22; + return 0; + } + + *port_target = session->opts.port; + + return 0; +} + +/** + * @brief This function can get ssh options, it does not support all options provided for + * ssh options set, but mostly those which a user-space program may care about having + * trusted the ssh driver to infer these values from underlying configuration files. + * It operates only on those SSH_OPTIONS_* which return char*. If you wish to receive + * the port then please use ssh_options_get_port() which returns an unsigned int. + * + * @param session An allocated SSH session structure. + * + * @param type The option type to get. This could be one of the + * following: + * + * - SSH_OPTIONS_HOST: + * The hostname or ip address to connect to (const char *). + * + * - SSH_OPTIONS_USER: + * The username for authentication (const char *).\n + * \n when not explicitly set this will be inferred from the + * ~/.ssh/config file. + * + * - SSH_OPTIONS_IDENTITY: + * Get the first identity file name (const char *).\n + * \n + * By default `id_rsa`, `id_ecdsa`, `id_ed25519`, `id_ecdsa_sk` + * and `id_ed25519_sk` (when SK support is built in) files are + * used. + * + * - SSH_OPTIONS_NEXT_IDENTITY: + * Get the next identity file name (const char *).\n + * \n + * Repeat calls to get all key paths. SSH_EOF is returned when + * the end of list is reached. Another call will start another + * iteration over the same list. + * + * - SSH_OPTIONS_PROXYCOMMAND: + * Get the proxycommand necessary to log into the + * remote host. When not explicitly set, it will be read + * from the ~/.ssh/config file. + * + * - SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + * Get the path to the global known_hosts file being used. + * + * - SSH_OPTIONS_KNOWNHOSTS: + * Get the path to the known_hosts file being used. + * + * - SSH_OPTIONS_CONTROL_PATH: + * Get the path to the control socket being used for connection + * multiplexing. + * + * - SSH_OPTIONS_KEY_EXCHANGE: + * Get the key exchange methods to be used. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HOSTKEYS: + * Get the preferred server host key types. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + * Get the preferred public key algorithms to be used for + * authentication. + * + * - SSH_OPTIONS_CIPHERS_C_S: + * Get the symmetric cipher client to server. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_CIPHERS_S_C: + * Get the symmetric cipher server to client. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_C_S: + * Get the Message Authentication Code algorithm client to server + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_S_C: + * Get the Message Authentication Code algorithm server to client + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_C_S: + * Get the compression to use for client to server communication + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_S_C: + * Get the compression to use for server to client communication + * If the option has not been set, returns the defaults. + * + * @param value The value to get into. As a char**, space will be + * allocated by the function for the value, it is + * your responsibility to free the memory using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) +{ + char *src = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (value == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + switch(type) + { + case SSH_OPTIONS_HOST: + src = session->opts.host; + break; + + case SSH_OPTIONS_USER: + src = session->opts.username; + break; + + case SSH_OPTIONS_IDENTITY: { + struct ssh_iterator *it = NULL; + it = ssh_list_get_iterator(session->opts.identity); + if (it == NULL) { + it = ssh_list_get_iterator(session->opts.identity_non_exp); + } + if (it == NULL) { + return SSH_ERROR; + } + src = ssh_iterator_value(char *, it); + break; + } + + case SSH_OPTIONS_NEXT_IDENTITY: { + if (session->opts.identity_it != NULL) { + /* Move to the next item */ + session->opts.identity_it = session->opts.identity_it->next; + if (session->opts.identity_it == NULL) { + *value = NULL; + return SSH_EOF; + } + } else { + /* Get iterator from opts */ + struct ssh_iterator *it = NULL; + it = ssh_list_get_iterator(session->opts.identity); + if (it == NULL) { + it = ssh_list_get_iterator(session->opts.identity_non_exp); + } + if (it == NULL) { + return SSH_ERROR; + } + session->opts.identity_it = it; + } + src = ssh_iterator_value(char *, session->opts.identity_it); + break; + } + + case SSH_OPTIONS_PROXYCOMMAND: + src = session->opts.ProxyCommand; + break; + + case SSH_OPTIONS_KNOWNHOSTS: + src = session->opts.knownhosts; + break; + + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + src = session->opts.global_knownhosts; + break; + case SSH_OPTIONS_CONTROL_PATH: + src = session->opts.control_path; + break; + + case SSH_OPTIONS_CIPHERS_C_S: + src = ssh_options_get_algo(session, SSH_CRYPT_C_S); + break; + + case SSH_OPTIONS_CIPHERS_S_C: + src = ssh_options_get_algo(session, SSH_CRYPT_S_C); + break; + + case SSH_OPTIONS_KEY_EXCHANGE: + src = ssh_options_get_algo(session, SSH_KEX); + break; + + case SSH_OPTIONS_HOSTKEYS: + src = ssh_options_get_algo(session, SSH_HOSTKEYS); + break; + + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + src = session->opts.pubkey_accepted_types; + break; + + case SSH_OPTIONS_HMAC_C_S: + src = ssh_options_get_algo(session, SSH_MAC_C_S); + break; + + case SSH_OPTIONS_HMAC_S_C: + src = ssh_options_get_algo(session, SSH_MAC_S_C); + break; + + case SSH_OPTIONS_COMPRESSION_C_S: + src = ssh_options_get_algo(session, SSH_COMP_C_S); + break; + + case SSH_OPTIONS_COMPRESSION_S_C: + src = ssh_options_get_algo(session, SSH_COMP_S_C); + break; + + default: + ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return SSH_ERROR; + break; + } + if (src == NULL) { + return SSH_ERROR; + } + *value = strdup(src); + if (*value == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Parse command line arguments. + * + * This is a helper for your application to generate the appropriate + * options from the command line arguments.\n + * The argv array and argc value are changed so that the parsed + * arguments won't appear anymore in them.\n + * The single arguments (without switches) are not parsed. thus, + * myssh -l user localhost\n + * The command won't set the hostname value of options to localhost. + * + * @param session The session to configure. + * + * @param argcptr The pointer to the argument count. + * + * @param argv The arguments list pointer. + * + * @returns 0 on success, < 0 on error. + * + * @see ssh_session_new() + */ +int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) +{ +#ifdef _MSC_VER + (void)session; + (void)argcptr; + (void)argv; + /* Not supported with a Microsoft compiler */ + return -1; +#else + char *user = NULL; + char *cipher = NULL; + char *identity = NULL; + char *port = NULL; + char **save = NULL; + char **tmp = NULL; + size_t i = 0; + int argc = *argcptr; + int debuglevel = 0; + int compress = 0; + int cont = 1; + size_t current = 0; + int opt_rc = 0; + int saveoptind = optind; /* need to save 'em */ + int saveopterr = opterr; + int opt; + + /* Nothing to do here */ + if (argc <= 1) { + return SSH_OK; + } + + opterr = 0; /* shut up getopt */ + while ((opt = getopt(argc, argv, "c:i:o:Cl:p:vb:r12")) != -1) { + switch(opt) { + case 'l': + user = optarg; + break; + case 'p': + port = optarg; + break; + case 'v': + debuglevel++; + ssh_set_log_level(debuglevel); + break; + case 'r': + break; + case 'c': + cipher = optarg; + break; + case 'i': + identity = optarg; + break; + case 'C': + compress++; + break; + case 'o': + opt_rc = ssh_config_parse_line_cli(session, optarg); + break; + case '2': + break; + case '1': + break; + default: + { + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current] = argv[optind-1]; + current++; + /* We can not use optarg here as getopt does not set it for + * unknown options. We need to manually extract following + * option and skip it manually from further processing */ + if (optind < argc && argv[optind][0] != '-') { + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current++] = argv[optind]; + optind++; + } + } + } /* switch */ + if (opt_rc == SSH_ERROR) { + break; + } + } /* while */ + opterr = saveopterr; + tmp = realloc(save, (current + (argc - optind)) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + while (optind < argc) { + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current] = argv[optind]; + current++; + optind++; + } + + optind = saveoptind; + + if (opt_rc == SSH_ERROR) { + SAFE_FREE(save); + return SSH_ERROR; + } + + if(!cont) { + SAFE_FREE(save); + return -1; + } + + /* first recopy the save vector into the original's */ + for (i = 0; i < current; i++) { + /* don't erase argv[0] */ + argv[ i + 1] = save[i]; + } + argv[current + 1] = NULL; + *argcptr = current + 1; + SAFE_FREE(save); + + /* set a new option struct */ + if (compress) { + if (ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes") < 0) { + cont = 0; + } + } + + if (cont && cipher) { + if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) < 0) { + cont = 0; + } + if (cont && ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher) < 0) { + cont = 0; + } + } + + if (cont && user) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + cont = 0; + } + } + + if (cont && identity) { + if (ssh_options_set(session, SSH_OPTIONS_IDENTITY, identity) < 0) { + cont = 0; + } + } + + if (port != NULL) { + ssh_options_set(session, SSH_OPTIONS_PORT_STR, port); + } + + if (!cont) { + return SSH_ERROR; + } + + return SSH_OK; +#endif +} + +/** + * @brief Parse the ssh config file. + * + * This should be the last call of all options. Options that were already set + * explicitly via ssh_options_set() take precedence and are not overwritten by + * the configuration file, matching OpenSSH's "first obtained value wins" + * behavior. Accumulative options such as IdentityFile and CertificateFile, as + * well as host-alias resolution via HostName, are still applied from the + * configuration. It requires that the host name is already set with + * ssh_options_set(SSH_OPTIONS_HOST). + * + * @param session SSH session handle + * + * @param filename The options file to use, if NULL the default + * ~/.ssh/config and /etc/ssh/ssh_config will be used. + * If complied with support for hermetic-usr, + * /usr/etc/ssh/ssh_config will be used last. + * + * @return 0 on success, < 0 on error. + * + * @see ssh_options_set() + */ +int ssh_options_parse_config(ssh_session session, const char *filename) +{ + char *expanded_filename = NULL; + int r; + FILE *fp = NULL; + + if (session == NULL) { + return -1; + } + if (session->opts.host == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + if (session->opts.sshdir == NULL) { + r = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + if (r < 0) { + ssh_set_error_oom(session); + return -1; + } + } + + /* set default filename */ + if (filename == NULL) { + expanded_filename = ssh_path_expand_escape(session, "%d/.ssh/config"); + } else { + expanded_filename = ssh_path_expand_escape(session, filename); + } + if (expanded_filename == NULL) { + return -1; + } + + r = ssh_config_parse_file(session, expanded_filename); + if (r < 0) { + goto out; + } + if (filename == NULL) { + fp = ssh_strict_fopen(GLOBAL_CLIENT_CONFIG, SSH_MAX_CONFIG_FILE_SIZE); + if (fp != NULL) { + filename = GLOBAL_CLIENT_CONFIG; +#ifdef USR_GLOBAL_CLIENT_CONFIG + } else { + fp = ssh_strict_fopen(USR_GLOBAL_CLIENT_CONFIG, + SSH_MAX_CONFIG_FILE_SIZE); + if (fp != NULL) { + filename = USR_GLOBAL_CLIENT_CONFIG; + } +#endif + } + + if (fp) { + SSH_LOG(SSH_LOG_PACKET, + "Reading configuration data from %s", + filename); + r = ssh_config_parse(session, fp, true); + fclose(fp); + } + } + + /* Do not process the default configuration as part of connection again */ + session->opts.config_processed = true; +out: + free(expanded_filename); + return r; +} + +int ssh_options_apply(ssh_session session) +{ + char *tmp = NULL; + int rc; + + if (session->opts.sshdir == NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + if (rc < 0) { + return -1; + } + } + + if (session->opts.username == NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL); + if (rc < 0) { + return -1; + } + } + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS) == 0) { + if (session->opts.knownhosts == NULL) { + tmp = ssh_path_expand_escape(session, "%d/.ssh/known_hosts"); + } else { + tmp = ssh_path_expand_escape(session, session->opts.knownhosts); + } + if (tmp == NULL) { + return -1; + } + free(session->opts.knownhosts); + session->opts.knownhosts = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_KNOWNHOSTS; + } + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS) == 0) { + if (session->opts.global_knownhosts == NULL) { + tmp = strdup(GLOBAL_CONF_DIR "/ssh_known_hosts"); + } else { + tmp = ssh_path_expand_escape(session, + session->opts.global_knownhosts); + } + if (tmp == NULL) { + return -1; + } + free(session->opts.global_knownhosts); + session->opts.global_knownhosts = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS; + } + + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND) == 0) { + if (session->opts.ProxyCommand != NULL) { + char *p = NULL; + size_t plen = strlen(session->opts.ProxyCommand) + + 5 /* strlen("exec ") */; + + if (strncmp(session->opts.ProxyCommand, "exec ", 5) != 0) { + p = malloc(plen + 1 /* \0 */); + if (p == NULL) { + return -1; + } + + rc = snprintf(p, plen + 1, "exec %s", session->opts.ProxyCommand); + if ((size_t)rc != plen) { + free(p); + return -1; + } + tmp = ssh_path_expand_escape(session, p); + free(p); + } else { + tmp = ssh_path_expand_escape(session, + session->opts.ProxyCommand); + } + + if (tmp == NULL) { + return -1; + } + free(session->opts.ProxyCommand); + session->opts.ProxyCommand = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_PROXYCOMMAND; + } + } + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_CONTROL_PATH) == 0) { + if (session->opts.control_path != NULL) { + tmp = ssh_path_expand_escape(session, session->opts.control_path); + if (tmp == NULL) { + return -1; + } + free(session->opts.control_path); + session->opts.control_path = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_CONTROL_PATH; + } + } + + for (tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp); + tmp != NULL; + tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp)) { + char *id = tmp; + if (strncmp(id, "pkcs11:", 6) != 0) { + /* PKCS#11 URIs are using percent-encoding so we can not mix + * it with ssh expansion of ssh escape characters. + */ + tmp = ssh_path_expand_escape(session, id); + free(id); + if (tmp == NULL) { + return -1; + } + } + + /* use append to keep the order at first call and use prepend + * to put anything that comes on the nth calls to the beginning */ + if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) { + rc = ssh_list_prepend(session->opts.identity, tmp); + } else { + rc = ssh_list_append(session->opts.identity, tmp); + } + if (rc != SSH_OK) { + free(tmp); + return -1; + } + } + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_IDENTITY; + + for (tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + tmp != NULL; + tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + char *id = tmp; + + tmp = ssh_path_expand_escape(session, id); + free(id); + if (tmp == NULL) { + return -1; + } + + rc = ssh_list_append(session->opts.certificate, tmp); + if (rc != SSH_OK) { + free(tmp); + return -1; + } + } + +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + rc = ssh_gssapi_check_client_config(session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Disabled GSSAPI key exchange"); + session->opts.gssapi_key_exchange = false; + } + } +#endif + + return 0; +} + +/** @} */ + +#ifdef WITH_SERVER +static bool ssh_bind_key_size_allowed(ssh_bind sshbind, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = sshbind->rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + +/** + * @addtogroup libssh_server + * @{ + */ +static int +ssh_bind_set_key(ssh_bind sshbind, char **key_loc, const void *value) +{ + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(*key_loc); + *key_loc = strdup(value); + if (*key_loc == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + return 0; +} + +static int ssh_bind_set_algo(ssh_bind sshbind, + enum ssh_kex_types_e algo, + const char *list, + char **place) +{ + /* sshbind is needed only for ssh_set_error which takes void* + * the typecast is only to satisfy function parameter type */ + return ssh_options_set_algo((ssh_session)sshbind, algo, list, place); +} + +/** + * @brief Set options for an SSH server bind. + * + * @param sshbind The ssh server bind to configure. + * + * @param type The option type to set. This should be one of the + * following: + * + * - SSH_BIND_OPTIONS_HOSTKEY: + * Set the path to an ssh host key, regardless + * of type. Only one key from per key type + * (RSA, ED25519 and ECDSA) is allowed in an ssh_bind + * at a time, and later calls to this function + * with this option for the same key type will + * override prior calls (const char *). + * + * - SSH_BIND_OPTIONS_BINDADDR: + * Set the IP address to bind (const char *). + * + * - SSH_BIND_OPTIONS_BINDPORT: + * Set the port to bind (unsigned int). + * + * - SSH_BIND_OPTIONS_BINDPORT_STR: + * Set the port to bind (const char *). + * + * - SSH_BIND_OPTIONS_LOG_VERBOSITY: + * Set the session logging verbosity (int). + * The logging verbosity should have one of the + * following values, which are listed in order + * of increasing verbosity. Every log message + * with verbosity less than or equal to the + * logging verbosity will be shown. + * - SSH_LOG_NOLOG: No logging + * - SSH_LOG_WARNING: Only warnings + * - SSH_LOG_PROTOCOL: High level protocol information + * - SSH_LOG_PACKET: Lower level protocol information, + * packet level + * - SSH_LOG_FUNCTIONS: Every function path + * The default is SSH_LOG_NOLOG. + * + * - SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: + * Set the session logging verbosity via a + * string that will be converted to a numerical + * value (e.g. "3") and interpreted according + * to the values of + * SSH_BIND_OPTIONS_LOG_VERBOSITY above + * (const char *). + * + * - SSH_BIND_OPTIONS_RSAKEY: + * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY + * (const char *). + * + * - SSH_BIND_OPTIONS_ECDSAKEY: + * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY + * (const char *). + * + * - SSH_BIND_OPTIONS_BANNER: + * Set the server banner sent to clients (const char *). + * + * - SSH_BIND_OPTIONS_DSAKEY: + * This is DEPRECATED, please do not use. + * + * - SSH_BIND_OPTIONS_IMPORT_KEY: + * Set the Private Key for the server directly + * (ssh_key). It will be free'd by ssh_bind_free(). + * + * - SSH_BIND_OPTIONS_IMPORT_KEY_STR: + * Set the Private key for the server from a + * base64 encoded buffer (const char *). + * + * - SSH_BIND_OPTIONS_CIPHERS_C_S: + * Set the symmetric cipher client to server + * (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_CIPHERS_S_C: + * Set the symmetric cipher server to client + * (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_KEY_EXCHANGE: + * Set the key exchange method to be used + * (const char *, comma-separated list). ex: + * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1" + * + * - SSH_BIND_OPTIONS_HMAC_C_S: + * Set the Message Authentication Code algorithm client + * to server (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_HMAC_S_C: + * Set the Message Authentication Code algorithm server + * to client (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_CONFIG_DIR: + * Set the directory (const char *, format string) + * to be used when the "%d" scape is used when providing + * paths of configuration files to + * ssh_bind_options_parse_config(). + * + * - SSH_BIND_OPTIONS_PROCESS_CONFIG + * Set it to false to disable automatic processing of + * system-wide configuration files. LibSSH automatically + * uses these configuration files otherwise. This + * option will only have effect if set before any call + * to ssh_bind_options_parse_config() (bool). + * + * - SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES: + * Set the public key algorithm accepted by the server + * (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: + * Set the list of allowed hostkey signatures algorithms + * to offer to the client, ordered by preference. This + * list is used as a filter when creating the list of + * algorithms to offer to the client: first the list of + * possible algorithms is created from the list of keys + * set and then filtered against this list. + * (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_MODULI + * Set the path to the moduli file. Defaults to + * /etc/ssh/moduli if not specified (const char *). + * + * - SSH_BIND_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by + * the server for both authentication and hostkey + * operations. The values under 1024 bits are not accepted + * even with this configuration option as they are + * considered completely broken. Setting 0 will revert + * the value to defaults. + * Default is 3072 bits or 2048 bits in FIPS mode. + * (int) + * + * - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE + * Set true to enable GSSAPI key exchange, + * false to disable GSSAPI key exchange. (bool) + * + * - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS + * Set the GSSAPI key exchange method to be used + * (const char *, comma-separated list). + * ex: "gss-group14-sha256-,gss-group16-sha512-" + * + * @param value The value to set. This is a generic pointer and the + * datatype which should be used is described at the + * corresponding value of type above. + * + * @return 0 on success, < 0 on error, invalid option, or + * parameter. + * + * @warning When the option value to set is represented via a + * pointer (e.g const char * in case of strings, ssh_key + * in case of a libssh key), the value parameter should be + * that pointer. Do NOT pass a pointer to a pointer (const + * char **, ssh_key *) + * + * @warning When the option value to set is not a pointer (e.g int, + * unsigned int, bool, long), the value parameter should be + * a pointer to the location storing the value to set (int + * *, unsigned int *, bool *, long *) + * + * @warning If the value parameter has an invalid type (e.g if its + * not a pointer when it should have been a pointer, or if + * its a pointer to a pointer when it should have just been + * a pointer), then the behaviour is undefined. + */ +int +ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, + const void *value) +{ + bool allowed; + char *p = NULL, *q = NULL; + const char *v = NULL; + int i, rc; + char **wanted_methods = sshbind->wanted_methods; + + if (sshbind == NULL) { + return -1; + } + + switch (type) { + case SSH_BIND_OPTIONS_RSAKEY: + case SSH_BIND_OPTIONS_ECDSAKEY: + /* deprecated */ + case SSH_BIND_OPTIONS_HOSTKEY: + case SSH_BIND_OPTIONS_IMPORT_KEY: + case SSH_BIND_OPTIONS_IMPORT_KEY_STR: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int key_type; + ssh_key *bind_key_loc = NULL; + ssh_key key = NULL; + char **bind_key_path_loc = NULL; + + if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) { + const char *key_str = (const char *)value; + rc = ssh_pki_import_privkey_base64(key_str, + NULL, + NULL, + NULL, + &key); + if (rc == SSH_ERROR) { + ssh_set_error(sshbind, + SSH_FATAL, + "Failed to import key from buffer"); + return -1; + } + } else if (type == SSH_BIND_OPTIONS_IMPORT_KEY) { + key = (ssh_key)value; + } else { + rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + return -1; + } + } + allowed = ssh_bind_key_size_allowed(sshbind, key); + if (!allowed) { + ssh_set_error(sshbind, + SSH_FATAL, + "The host key size %d is too small.", + ssh_key_size(key)); + if (type != SSH_BIND_OPTIONS_IMPORT_KEY) { + SSH_KEY_FREE(key); + } + return -1; + } + key_type = ssh_key_type(key); + switch (key_type) { + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_ECC + bind_key_loc = &sshbind->ecdsa; + bind_key_path_loc = &sshbind->ecdsakey; +#else + ssh_set_error(sshbind, + SSH_FATAL, + "ECDSA key used and libssh compiled " + "without ECDSA support"); +#endif + break; + case SSH_KEYTYPE_RSA: + bind_key_loc = &sshbind->rsa; + bind_key_path_loc = &sshbind->rsakey; + break; + case SSH_KEYTYPE_ED25519: + bind_key_loc = &sshbind->ed25519; + bind_key_path_loc = &sshbind->ed25519key; + break; + default: + ssh_set_error(sshbind, + SSH_FATAL, + "Unsupported key type %d", + key_type); + } + if (type == SSH_BIND_OPTIONS_RSAKEY || + type == SSH_BIND_OPTIONS_ECDSAKEY || + type == SSH_BIND_OPTIONS_HOSTKEY) { + if (bind_key_loc == NULL) { + ssh_key_free(key); + return -1; + } + /* Set the location of the key on disk even though we don't + need it in case some other function wants it */ + rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); + if (rc < 0) { + ssh_key_free(key); + return -1; + } + } else if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) { + if (bind_key_loc == NULL) { + ssh_key_free(key); + return -1; + } + } else { + if (bind_key_loc == NULL) { + return -1; + } + } + ssh_key_free(*bind_key_loc); + *bind_key_loc = key; + } + break; + case SSH_BIND_OPTIONS_BINDADDR: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->bindaddr); + sshbind->bindaddr = strdup(value); + if (sshbind->bindaddr == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_BINDPORT: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + sshbind->bindport = *x & 0xffffU; + } + break; + case SSH_BIND_OPTIONS_BINDPORT_STR: + if (value == NULL) { + sshbind->bindport = 22 & 0xffffU; + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); + + sshbind->bindport = i & 0xffffU; + } + break; + case SSH_BIND_OPTIONS_LOG_VERBOSITY: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + ssh_set_log_level(*x & 0xffffU); + } + break; + case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: + if (value == NULL) { + ssh_set_log_level(0); + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); + + ssh_set_log_level(i & 0xffffU); + } + break; + case SSH_BIND_OPTIONS_BANNER: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->banner); + sshbind->banner = strdup(value); + if (sshbind->banner == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_CIPHERS_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_CIPHERS_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_KEY_EXCHANGE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_HMAC_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_HMAC_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_CONFIG_DIR: + v = value; + SAFE_FREE(sshbind->config_dir); + if (v == NULL) { + break; + } else if (v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + sshbind->config_dir = ssh_path_expand_tilde(v); + if (sshbind->config_dir == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &sshbind->pubkey_accepted_key_types); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_PROCESS_CONFIG: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + bool *x = (bool *)value; + sshbind->config_processed = !(*x); + } + break; + case SSH_BIND_OPTIONS_MODULI: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->moduli_file); + sshbind->moduli_file = strdup(value); + if (sshbind->moduli_file == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + + if (*x < 0) { + ssh_set_error_invalid(sshbind); + return -1; + } + + /* (*x == 0) is allowed as it is used to revert to default */ + + if (*x > 0 && *x < RSA_MIN_KEY_SIZE) { + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "The provided value (%d) for minimal RSA key " + "size is too small. Use at least %d bits.", + *x, + RSA_MIN_KEY_SIZE); + return -1; + } + sshbind->rsa_min_size = *x; + } + break; +#ifdef WITH_GSSAPI + case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + bool *x = (bool *)value; + sshbind->gssapi_key_exchange = *x; + } + break; + case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + char *ret = NULL; + SAFE_FREE(sshbind->gssapi_key_exchange_algs); + ret = ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, value); + if (ret == NULL) { + ssh_set_error( + sshbind, + SSH_REQUEST_DENIED, + "GSSAPI key exchange algorithms not supported or invalid"); + return -1; + } + sshbind->gssapi_key_exchange_algs = ret; + } + break; +#endif /* WITH_GSSAPI */ + default: + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "Unknown ssh option %d", + type); + return -1; + break; + } + + return 0; +} + +static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) +{ + char *buf = NULL; + char *r = NULL; + char *x = NULL; + const char *p = NULL; + size_t i, l; + + r = ssh_path_expand_tilde(s); + if (r == NULL) { + ssh_set_error_oom(sshbind); + return NULL; + } + + if (strlen(r) > MAX_BUF_SIZE) { + ssh_set_error(sshbind, SSH_FATAL, "string to expand too long"); + free(r); + return NULL; + } + + buf = malloc(MAX_BUF_SIZE); + if (buf == NULL) { + ssh_set_error_oom(sshbind); + free(r); + return NULL; + } + + p = r; + buf[0] = '\0'; + + for (i = 0; *p != '\0'; p++) { + if (*p != '%') { + buf[i] = *p; + i++; + if (i >= MAX_BUF_SIZE) { + free(buf); + free(r); + return NULL; + } + buf[i] = '\0'; + continue; + } + + p++; + if (*p == '\0') { + break; + } + + switch (*p) { + case 'd': + x = strdup(sshbind->config_dir); + break; + default: + ssh_set_error(sshbind, SSH_FATAL, + "Wrong escape sequence detected"); + free(buf); + free(r); + return NULL; + } + + if (x == NULL) { + ssh_set_error_oom(sshbind); + free(buf); + free(r); + return NULL; + } + + i += strlen(x); + if (i >= MAX_BUF_SIZE) { + ssh_set_error(sshbind, SSH_FATAL, + "String too long"); + free(buf); + free(x); + free(r); + return NULL; + } + l = strlen(buf); + strncpy(buf + l, x, MAX_BUF_SIZE - l - 1); + buf[i] = '\0'; + SAFE_FREE(x); + } + + free(r); + + /* strip the unused space by realloc */ + x = realloc(buf, strlen(buf) + 1); + if (x == NULL) { + ssh_set_error_oom(sshbind); + free(buf); + } + return x; +} + +/** + * @brief Parse a ssh bind options configuration file. + * + * This parses the options file and set them to the ssh_bind handle provided. If + * an option was previously set, it is overridden. If the global configuration + * hasn't been processed yet, it is processed prior to the provided file. + * + * @param sshbind SSH bind handle + * + * @param filename The options file to use; if NULL only the global + * configuration is parsed and applied (if it hasn't been + * processed before). + * + * @return 0 on success, < 0 on error. + */ +int ssh_bind_options_parse_config(ssh_bind sshbind, const char *filename) +{ + int rc = 0; + char *expanded_filename = NULL; + + if (sshbind == NULL) { + return -1; + } + + /* If the global default configuration hasn't been processed yet, process it + * before the provided configuration. */ + if (!(sshbind->config_processed)) { + if (ssh_file_readaccess_ok(GLOBAL_BIND_CONFIG)) { + rc = ssh_bind_config_parse_file(sshbind, GLOBAL_BIND_CONFIG); +#ifdef USR_GLOBAL_BIND_CONFIG + } else { + rc = ssh_bind_config_parse_file(sshbind, USR_GLOBAL_BIND_CONFIG); +#endif + } + if (rc != 0) { + return rc; + } + sshbind->config_processed = true; + } + + if (filename != NULL) { + expanded_filename = ssh_bind_options_expand_escape(sshbind, filename); + if (expanded_filename == NULL) { + return -1; + } + + /* Apply the user provided configuration */ + rc = ssh_bind_config_parse_file(sshbind, expanded_filename); + free(expanded_filename); + } + + return rc; +} + +#endif + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/packet.c b/src/libs/libssh-0.12.2/src/packet.c new file mode 100644 index 000000000000..e6f8e579053c --- /dev/null +++ b/src/libs/libssh-0.12.2/src/packet.c @@ -0,0 +1,2285 @@ +/* + * packet.c - packet building functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/misc.h" +#include "libssh/session.h" +#include "libssh/messages.h" +#include "libssh/pcap.h" +#include "libssh/kex.h" +#include "libssh/auth.h" +#include "libssh/gssapi.h" +#include "libssh/bytearray.h" +#include "libssh/dh.h" + +static ssh_packet_callback default_packet_handlers[]= { + ssh_packet_disconnect_callback, // SSH2_MSG_DISCONNECT 1 + ssh_packet_ignore_callback, // SSH2_MSG_IGNORE 2 + ssh_packet_unimplemented, // SSH2_MSG_UNIMPLEMENTED 3 + ssh_packet_debug_callback, // SSH2_MSG_DEBUG 4 +#if WITH_SERVER + ssh_packet_service_request, // SSH2_MSG_SERVICE_REQUEST 5 +#else + NULL, +#endif + ssh_packet_service_accept, // SSH2_MSG_SERVICE_ACCEPT 6 + ssh_packet_ext_info, // SSH2_MSG_EXT_INFO 7 + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, // 8-19 + ssh_packet_kexinit, // SSH2_MSG_KEXINIT 20 + ssh_packet_newkeys, // SSH2_MSG_NEWKEYS 21 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, // 22-29 +#if WITH_SERVER + ssh_packet_kexdh_init, // SSH2_MSG_KEXDH_INIT 30 + // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#else + NULL, +#endif + NULL, // SSH2_MSG_KEXDH_REPLY 31 + // SSH2_MSG_KEX_DH_GEX_GROUP 31 + NULL, // SSH2_MSG_KEX_DH_GEX_INIT 32 + NULL, // SSH2_MSG_KEX_DH_GEX_REPLY 33 + NULL, // SSH2_MSG_KEX_DH_GEX_REQUEST 34 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, // 35-49 +#if WITH_SERVER + ssh_packet_userauth_request, // SSH2_MSG_USERAUTH_REQUEST 50 +#else + NULL, +#endif + ssh_packet_userauth_failure, // SSH2_MSG_USERAUTH_FAILURE 51 + ssh_packet_userauth_success, // SSH2_MSG_USERAUTH_SUCCESS 52 + ssh_packet_userauth_banner, // SSH2_MSG_USERAUTH_BANNER 53 + NULL,NULL,NULL,NULL,NULL,NULL, // 54-59 + ssh_packet_userauth_pk_ok, // SSH2_MSG_USERAUTH_PK_OK 60 + // SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 + // SSH2_MSG_USERAUTH_INFO_REQUEST 60 + // SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 + ssh_packet_userauth_info_response, // SSH2_MSG_USERAUTH_INFO_RESPONSE 61 + // SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 + NULL, // 62 + NULL, // SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 + NULL, // SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 + NULL, // SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#if defined(WITH_GSSAPI) && defined(WITH_SERVER) + ssh_packet_userauth_gssapi_mic, // SSH2_MSG_USERAUTH_GSSAPI_MIC 66 +#else /* WITH_GSSAPI && WITH_SERVER */ + NULL, +#endif /* WITH_GSSAPI && WITH_SERVER */ + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, // 67-79 +#ifdef WITH_SERVER + ssh_packet_global_request, // SSH2_MSG_GLOBAL_REQUEST 80 +#else /* WITH_SERVER */ + NULL, +#endif /* WITH_SERVER */ + ssh_request_success, // SSH2_MSG_REQUEST_SUCCESS 81 + ssh_request_denied, // SSH2_MSG_REQUEST_FAILURE 82 + NULL, NULL, NULL, NULL, NULL, NULL, NULL,// 83-89 + ssh_packet_channel_open, // SSH2_MSG_CHANNEL_OPEN 90 + ssh_packet_channel_open_conf, // SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 + ssh_packet_channel_open_fail, // SSH2_MSG_CHANNEL_OPEN_FAILURE 92 + channel_rcv_change_window, // SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 + channel_rcv_data, // SSH2_MSG_CHANNEL_DATA 94 + channel_rcv_data, // SSH2_MSG_CHANNEL_EXTENDED_DATA 95 + channel_rcv_eof, // SSH2_MSG_CHANNEL_EOF 96 + channel_rcv_close, // SSH2_MSG_CHANNEL_CLOSE 97 + channel_rcv_request, // SSH2_MSG_CHANNEL_REQUEST 98 + ssh_packet_channel_success, // SSH2_MSG_CHANNEL_SUCCESS 99 + ssh_packet_channel_failure, // SSH2_MSG_CHANNEL_FAILURE 100 +}; + +/** @internal + * @brief check if the received packet is allowed for the current session state + * @param session current ssh_session + * @returns SSH_PACKET_ALLOWED if the packet is allowed; SSH_PACKET_DENIED + * if the packet arrived in wrong state; SSH_PACKET_UNKNOWN if the packet type + * is unknown + */ +static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session session) +{ + enum ssh_packet_filter_result_e rc; + +#ifdef DEBUG_PACKET + SSH_LOG(SSH_LOG_PACKET, "Filtering packet type %d", + session->in_packet.type); +#endif + + switch(session->in_packet.type) { + case SSH2_MSG_DISCONNECT: // 1 + /* + * States required: + * - None + * + * Transitions: + * - session->socket->state = SSH_SOCKET_CLOSED + * - session->session_state = SSH_SESSION_STATE_ERROR + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_IGNORE: // 2 + /* + * States required: + * - None + * + * Transitions: + * - None + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_UNIMPLEMENTED: // 3 + /* + * States required: + * - None + * + * Transitions: + * - None + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_DEBUG: // 4 + /* + * States required: + * - None + * + * Transitions: + * - None + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_SERVICE_REQUEST: // 5 + /* Server only */ + + /* + * States required: + * - session->session_state == SSH_SESSION_STATE_AUTHENTICATING + * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED + * - session->dh_handshake_state == DH_STATE_FINISHED + * + * Transitions: + * - None + * */ + + /* If this is a client, reject the message */ + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && + (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_SERVICE_ACCEPT: // 6 + /* + * States required: + * - session->session_state == SSH_SESSION_STATE_AUTHENTICATING + * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED + * - session->dh_handshake_state == DH_STATE_FINISHED + * - session->auth.service_state == SSH_AUTH_SERVICE_SENT + * + * Transitions: + * - auth.service_state = SSH_AUTH_SERVICE_ACCEPTED + * */ + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && + (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + /* TODO check if only auth service can be requested */ + if (session->auth.service_state != SSH_AUTH_SERVICE_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_EXT_INFO: // 7 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED + * (re-exchange) + * - dh_handshake_state == DH_STATE_FINISHED + * + * Transitions: + * - None + * */ + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && + (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEXINIT: // 20 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * or session_state == SSH_SESSION_STATE_INITIAL_KEX + * - dh_handshake_state == DH_STATE_INIT + * or dh_handshake_state == DH_STATE_INIT_SENT (re-exchange) + * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) + * or dh_handshake_state == DH_STATE_FINISHED (re-exchange) + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT + * - session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED + * + * On server: + * - session->session_state = SSH_SESSION_STATE_DH + * */ + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATED) && + (session->session_state != SSH_SESSION_STATE_INITIAL_KEX)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->dh_handshake_state != DH_STATE_INIT) && + (session->dh_handshake_state != DH_STATE_INIT_SENT) && + (session->dh_handshake_state != DH_STATE_REQUEST_SENT) && + (session->dh_handshake_state != DH_STATE_FINISHED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_NEWKEYS: // 21 + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_NEWKEYS_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_FINISHED + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATING + * if session->flags & SSH_SESSION_FLAG_AUTHENTICATED + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED + * */ + + /* If DH has not been started, reject message */ + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in NEWKEYS_SENT state */ + if (session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEXDH_INIT: // 30 + // SSH2_MSG_KEX_ECDH_INIT: // 30 + // SSH2_MSG_KEX_HYBRID_INIT: // 30 + // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD: // 30 + + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT_SENT + * then calls dh_handshake_server which triggers: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in initial state */ + if (session->dh_handshake_state != DH_STATE_INIT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEXDH_REPLY: // 31 + // SSH2_MSG_KEX_ECDH_REPLY: // 31 + // SSH2_MSG_KEX_HYBRID_REPLY: // 31 + // SSH2_MSG_KEX_DH_GEX_GROUP: // 31 + + /* Client only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT_SENT + * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_INIT_SENT && + session->dh_handshake_state != DH_STATE_REQUEST_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEX_DH_GEX_INIT: // 32 + // SSH2_MSG_KEXGSS_COMPLETE: // 32 + if (ssh_kex_is_gss(session->next_crypto)) { + /* SSH2_MSG_KEXGSS_COMPLETE */ + /* Client only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT_SENT + * then calls ssh_packet_client_gss_kex_reply which triggers: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (!session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_INIT_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + } else { + /* SSH2_MSG_KEX_DH_GEX_INIT */ + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_GROUP_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_GROUP_SENT + * then calls ssh_packet_server_dhgex_init which triggers: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in initial state */ + if (session->dh_handshake_state != DH_STATE_GROUP_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + } + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEX_DH_GEX_REPLY: // 33 + + /* Client only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_INIT_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEX_DH_GEX_REQUEST: // 34 + + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT_SENT + * then calls ssh_packet_server_dhgex_request which triggers: + * - session->dh_handshake_state = DH_STATE_GROUP_SENT + * */ + + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in initial state */ + if (session->dh_handshake_state != DH_STATE_INIT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_REQUEST: // 50 + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - dh_handshake_state == DH_STATE_FINISHED + * + * Transitions: + * - if authentication was successful: + * - session_state = SSH_SESSION_STATE_AUTHENTICATED + * */ + + /* If this is a client, reject the message */ + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_FAILURE: // 51 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - dh_handshake_state == DH_STATE_FINISHED + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * or session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT + * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT + * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_MIC_SENT + * + * Transitions: + * - if unpacking failed: + * - session->auth.state = SSH_AUTH_ERROR + * - if failure was partial: + * - session->auth.state = SSH_AUTH_PARTIAL + * - else: + * - session->auth.state = SSH_AUTH_STATE_FAILED + * */ + + /* If this is a server, reject the message */ + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_SUCCESS: // 52 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - dh_handshake_state == DH_STATE_FINISHED + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT + * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_MIC_SENT + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT + * or session->auth.state == SSH_AUTH_STATE_AUTH_NONE_SENT + * + * Transitions: + * - session->auth.state = SSH_AUTH_STATE_SUCCESS + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED + * - session->flags |= SSH_SESSION_FLAG_AUTHENTICATED + * - sessions->auth.current_method = SSH_AUTH_METHOD_UNKNOWN + * */ + + /* If this is a server, reject the message */ + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->auth.state != SSH_AUTH_STATE_KBDINT_SENT) && + (session->auth.state != SSH_AUTH_STATE_PUBKEY_AUTH_SENT) && + (session->auth.state != SSH_AUTH_STATE_PASSWORD_AUTH_SENT) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_MIC_SENT) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT) && + (session->auth.state != SSH_AUTH_STATE_AUTH_NONE_SENT)) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_BANNER: // 53 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_PK_OK: // 60 + // SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // 60 + // SSH2_MSG_USERAUTH_INFO_REQUEST: // 60 + // SSH2_MSG_USERAUTH_GSSAPI_RESPONSE: // 60 + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * or + * session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT + * or + * session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT + * + * Transitions: + * Depending on the current state, the message is treated + * differently: + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * - session->auth.state = SSH_AUTH_STATE_INFO + * - session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT + * - session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN + * - session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT + * - session->auth.state = SSH_AUTH_STATE_PK_OK + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->auth.state != SSH_AUTH_STATE_KBDINT_SENT) && + (session->auth.state != SSH_AUTH_STATE_PUBKEY_OFFER_SENT) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_REQUEST_SENT)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_INFO_RESPONSE: // 61 + // SSH2_MSG_USERAUTH_GSSAPI_TOKEN: // 61 + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session_state->auth.state == SSH_SESSION_STATE_GSSAPI_TOKEN + * or + * session_state->auth.state == SSH_SESSION_STATE_INFO + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->auth.state != SSH_AUTH_STATE_INFO) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_TOKEN)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE: // 63 + /* Server only */ + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session->gssapi->state == SSH_GSSAPI_STATE_RCV_MIC + * + * Transitions: + * - None + */ +#ifdef WITH_GSSAPI + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->gssapi == NULL) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) { + rc = SSH_PACKET_DENIED; + break; + } + rc = SSH_PACKET_ALLOWED; + break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ + case SSH2_MSG_USERAUTH_GSSAPI_ERROR: // 64 + /* Client only */ + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * + * Transitions: + * - None + */ +#ifdef WITH_GSSAPI + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ + case SSH2_MSG_USERAUTH_GSSAPI_ERRTOK: // 65 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * + * Transitions: + * - None + */ +#ifdef WITH_GSSAPI + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ + case SSH2_MSG_USERAUTH_GSSAPI_MIC: // 66 + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session->gssapi->state == SSH_GSSAPI_STATE_RCV_MIC + * + * Transitions: + * Depending on the result of the verification, the states are + * changed: + * - SSH_AUTH_SUCCESS: + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED + * - session->flags != SSH_SESSION_FLAG_AUTHENTICATED + * - SSH_AUTH_PARTIAL: + * - None + * - any other case: + * - None + * */ +#ifdef WITH_GSSAPI + /* If this is a client, reject the message */ + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ + case SSH2_MSG_GLOBAL_REQUEST: // 80 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_REQUEST_SUCCESS: // 81 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_REQUEST_FAILURE: // 82 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_OPEN: // 90 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: // 91 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - channel->state = SSH_CHANNEL_STATE_OPEN + * - channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_OPEN_FAILURE: // 92 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - channel->state = SSH_CHANNEL_STATE_OPEN_DENIED + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: // 93 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_DATA: // 94 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_EXTENDED_DATA: // 95 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_EOF: // 96 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_CLOSE: // 97 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - channel->state = SSH_CHANNEL_STATE_CLOSED + * - channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_REQUEST: // 98 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - Depends on the request + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_SUCCESS: // 99 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_FAILURE: // 100 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + default: + /* Unknown message, do not filter */ + rc = SSH_PACKET_UNKNOWN; + goto end; + } + +end: +#ifdef DEBUG_PACKET + if (rc == SSH_PACKET_DENIED) { + SSH_LOG(SSH_LOG_PACKET, "REJECTED packet type %d: ", + session->in_packet.type); + } + + if (rc == SSH_PACKET_UNKNOWN) { + SSH_LOG(SSH_LOG_PACKET, "UNKNOWN packet type %d", + session->in_packet.type); + } +#endif + + return rc; +} + +/* Returns current_crypto structure from the session. + * During key exchange (or rekey), after one of the sides + * sending NEWKEYS packet, this might return next_crypto for one + * of the directions that is ahead to send already queued packets + */ +struct ssh_crypto_struct * +ssh_packet_get_current_crypto(ssh_session session, + enum ssh_crypto_direction_e direction) +{ + struct ssh_crypto_struct *crypto = NULL; + + if (session == NULL) { + return NULL; + } + + if (session->current_crypto != NULL && + session->current_crypto->used & direction) { + crypto = session->current_crypto; + } else if (session->next_crypto != NULL && + session->next_crypto->used & direction) { + crypto = session->next_crypto; + } else { + return NULL; + } + + switch (direction) { + case SSH_DIRECTION_IN: + if (crypto->in_cipher != NULL) { + return crypto; + } + break; + case SSH_DIRECTION_OUT: + if (crypto->out_cipher != NULL) { + return crypto; + } + break; + case SSH_DIRECTION_BOTH: + if (crypto->in_cipher != NULL && + crypto->out_cipher != NULL) { + return crypto; + } + } + + return NULL; +} + +#define MAX_PACKETS (1UL<<31) + +static bool ssh_packet_need_rekey(ssh_session session, + const uint32_t payloadsize) +{ + bool data_rekey_needed = false; + struct ssh_crypto_struct *crypto = NULL; + struct ssh_cipher_struct *out_cipher = NULL, *in_cipher = NULL; + uint32_t next_blocks; + + /* We can safely rekey only in authenticated state */ + if ((session->flags & SSH_SESSION_FLAG_AUTHENTICATED) == 0) { + return false; + } + + /* Do not rekey if the rekey/key-exchange is in progress */ + if (session->dh_handshake_state != DH_STATE_FINISHED) { + return false; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return false; + } + + out_cipher = crypto->out_cipher; + in_cipher = crypto->in_cipher; + + /* Make sure we can send at least something for very small limits */ + if ((out_cipher->packets == 0) && (in_cipher->packets == 0)) { + return false; + } + + /* Time based rekeying */ + if (session->opts.rekey_time != 0 && + ssh_timeout_elapsed(&session->last_rekey_time, + session->opts.rekey_time)) { + return true; + } + + /* RFC4344, Section 3.1 Recommends rekeying after 2^31 packets in either + * direction to avoid possible information leakage through the MAC tag + */ + if (out_cipher->packets > MAX_PACKETS || + in_cipher->packets > MAX_PACKETS) { + return true; + } + + /* Data-based rekeying: + * * For outgoing packets we can still delay them + * * Incoming packets need to be processed anyway, but we can + * signalize our intention to rekey + */ + next_blocks = payloadsize / out_cipher->blocksize; + data_rekey_needed = (out_cipher->max_blocks != 0 && + out_cipher->blocks + next_blocks > out_cipher->max_blocks) || + (in_cipher->max_blocks != 0 && + in_cipher->blocks + next_blocks > in_cipher->max_blocks); + + SSH_LOG(SSH_LOG_PACKET, + "rekey: [data_rekey_needed=%d, out_blocks=%" PRIu64 ", in_blocks=%" PRIu64 "]", + data_rekey_needed, + out_cipher->blocks + next_blocks, + in_cipher->blocks + next_blocks); + + return data_rekey_needed; +} + +/* in nonblocking mode, socket_read will read as much as it can, and return */ +/* SSH_OK if it has read at least len bytes, otherwise, SSH_AGAIN. */ +/* in blocking mode, it will read at least len bytes and will block until it's ok. */ + +/** @internal + * @brief handles a data received event + * + * Processes up to one packet from the given buffer and calls the handlers + * for the different packet types or an exception handler callback. If the + * buffer does not contain a complete packet, nothing is processed and zero + * is returned. So typically this function needs to be called in a loop until + * it returns zero to properly handle multiple packets in the buffer. + * + * @param user pointer to current ssh_session + * @param data pointer to the data received + * @len length of data received. It might not be enough for a complete packet + * @returns number of bytes read and processed. Zero means only partial packet + * received. + */ +size_t +ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) +{ + ssh_session session = (ssh_session)user; + uint32_t blocksize = 8; + uint32_t lenfield_blocksize = 8; + size_t current_macsize = 0; + uint8_t *ptr = NULL; + ssize_t to_be_read; + int rc; + uint8_t *cleartext_packet = NULL; + uint8_t *packet_second_block = NULL; + uint8_t *mac = NULL; + size_t packet_remaining, packet_offset; + uint32_t packet_len, compsize, payloadsize; + uint8_t padding; + size_t processed = 0; /* number of bytes processed from the callback */ + enum ssh_packet_filter_result_e filter_result; + struct ssh_crypto_struct *crypto = NULL; + bool etm = false; + uint32_t etm_packet_offset = 0; + bool ok; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL) { + current_macsize = hmac_digest_len(crypto->in_hmac); + blocksize = crypto->in_cipher->blocksize; + lenfield_blocksize = crypto->in_cipher->lenfield_blocksize; + etm = crypto->in_hmac_etm; + } + + if (etm) { + /* In EtM mode packet size is unencrypted. This means + * we need to use this offset and set the block size + * that is part of the encrypted part to 0. + */ + etm_packet_offset = sizeof(uint32_t); + lenfield_blocksize = 0; + } else if (lenfield_blocksize == 0) { + lenfield_blocksize = blocksize; + } + if (data == NULL) { + goto error; + } + + if (session->session_state == SSH_SESSION_STATE_ERROR) { + goto error; + } +#ifdef DEBUG_PACKET + SSH_LOG(SSH_LOG_PACKET, + "rcv packet cb (len=%zu, state=%s)", + receivedlen, + session->packet_state == PACKET_STATE_INIT ? + "INIT" : + session->packet_state == PACKET_STATE_SIZEREAD ? + "SIZE_READ" : + session->packet_state == PACKET_STATE_PROCESSING ? + "PROCESSING" : "unknown"); +#endif + switch (session->packet_state) { + case PACKET_STATE_INIT: + if (receivedlen < lenfield_blocksize + etm_packet_offset) { + /* + * We didn't receive enough data to read either at least one + * block size or the unencrypted length in EtM mode. + */ +#ifdef DEBUG_PACKET + SSH_LOG(SSH_LOG_PACKET, + "Waiting for more data (%zu < %u)", + receivedlen, + lenfield_blocksize); +#endif + return 0; + } + + session->in_packet = (struct packet_struct) { + .type = 0, + }; + + if (session->in_buffer) { + rc = ssh_buffer_reinit(session->in_buffer); + if (rc < 0) { + goto error; + } + } else { + session->in_buffer = ssh_buffer_new(); + if (session->in_buffer == NULL) { + goto error; + } + } + + if (!etm) { + ptr = ssh_buffer_allocate(session->in_buffer, + lenfield_blocksize); + if (ptr == NULL) { + goto error; + } + packet_len = ssh_packet_decrypt_len(session, ptr, + (uint8_t *)data); + to_be_read = packet_len - lenfield_blocksize + sizeof(uint32_t); + } else { + /* Length is unencrypted in case of Encrypt-then-MAC */ + packet_len = PULL_BE_U32(data, 0); + to_be_read = packet_len - etm_packet_offset; + } + + processed += lenfield_blocksize + etm_packet_offset; + if (packet_len > MAX_PACKET_LEN) { + ssh_set_error(session, + SSH_FATAL, + "read_packet(): Packet len too high(%" PRIu32 " %.4" PRIx32 ")", + packet_len, packet_len); + goto error; + } + if (to_be_read < 0) { + /* remote sshd sends invalid sizes? */ + ssh_set_error(session, + SSH_FATAL, + "Given numbers of bytes left to be read < 0 (%zd)!", + to_be_read); + goto error; + } + + session->in_packet.len = packet_len; + session->packet_state = PACKET_STATE_SIZEREAD; + FALL_THROUGH; + case PACKET_STATE_SIZEREAD: + packet_len = session->in_packet.len; + packet_offset = processed = lenfield_blocksize + etm_packet_offset; + to_be_read = packet_len + sizeof(uint32_t) + current_macsize; + /* if to_be_read is zero, the whole packet was blocksize bytes. */ + if (to_be_read != 0) { + if (receivedlen < (unsigned long)to_be_read) { + /* give up, not enough data in buffer */ + SSH_LOG(SSH_LOG_PACKET, + "packet: partial packet (read len) " + "[len=%" PRIu32 ", receivedlen=%zu, to_be_read=%zd]", + packet_len, + receivedlen, + to_be_read); + return 0; + } + + packet_second_block = (uint8_t*)data + packet_offset; + processed = to_be_read - current_macsize; + } + + if (packet_offset - sizeof(uint32_t) > (size_t)packet_len) { + ssh_set_error(session, + SSH_FATAL, + "Invalid packet length %" PRIu32 ", required %zu", + packet_len, + packet_offset + sizeof(uint32_t)); + goto error; + } + + /* remaining encrypted bytes from the packet, MAC not included */ + packet_remaining = packet_len - (packet_offset - sizeof(uint32_t)); + cleartext_packet = ssh_buffer_allocate(session->in_buffer, + (uint32_t)packet_remaining); + if (cleartext_packet == NULL) { + goto error; + } + + if (packet_second_block != NULL) { + if (crypto != NULL) { + mac = packet_second_block + packet_remaining; + + if (crypto->in_hmac != SSH_HMAC_NONE && etm) { + rc = ssh_packet_hmac_verify(session, + data, + processed, + mac, + crypto->in_hmac); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "HMAC error"); + goto error; + } + } + /* + * Decrypt the packet. In case of EtM mode, the length is + * already known as it's unencrypted. In the other case, + * lenfield_blocksize bytes already have been decrypted. + */ + if (packet_remaining > 0) { + rc = ssh_packet_decrypt(session, + cleartext_packet, + (uint8_t *)data, + packet_offset, + processed - packet_offset); + if (rc < 0) { + ssh_set_error(session, + SSH_FATAL, + "Decryption error"); + goto error; + } + } + + if (crypto->in_hmac != SSH_HMAC_NONE && !etm) { + ssh_buffer in = session->in_buffer; + rc = ssh_packet_hmac_verify(session, + ssh_buffer_get(in), + ssh_buffer_get_len(in), + mac, + crypto->in_hmac); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "HMAC error"); + goto error; + } + } + processed += current_macsize; + } else { + memcpy(cleartext_packet, + packet_second_block, + packet_remaining); + } + } + +#ifdef WITH_PCAP + if (session->pcap_ctx != NULL) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + ssh_buffer_get(session->in_buffer), + ssh_buffer_get_len(session->in_buffer), + ssh_buffer_get_len(session->in_buffer)); + } +#endif + + if (!etm) { + /* skip the size field which has been processed before */ + ssh_buffer_pass_bytes(session->in_buffer, sizeof(uint32_t)); + } + + rc = ssh_buffer_get_u8(session->in_buffer, &padding); + if (rc == 0) { + ssh_set_error(session, + SSH_FATAL, + "Packet too short to read padding"); + goto error; + } + + if (padding > ssh_buffer_get_len(session->in_buffer)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid padding: %d (%" PRIu32 " left)", + padding, + ssh_buffer_get_len(session->in_buffer)); + goto error; + } + ssh_buffer_pass_bytes_end(session->in_buffer, padding); + compsize = ssh_buffer_get_len(session->in_buffer); + + if (crypto && crypto->do_compress_in && + ssh_buffer_get_len(session->in_buffer) > 0) { + rc = decompress_buffer(session, session->in_buffer, + MAX_PACKET_LEN); + if (rc < 0) { + goto error; + } + } + payloadsize = ssh_buffer_get_len(session->in_buffer); + if (session->recv_seq == UINT32_MAX) { + /* Overflowing sequence numbers is always fishy */ + if (crypto == NULL) { + /* don't allow sequence number overflow when unencrypted */ + ssh_set_error(session, + SSH_FATAL, + "Incoming sequence number overflow"); + goto error; + } else { + SSH_LOG(SSH_LOG_WARNING, + "Incoming sequence number overflow"); + } + } + session->recv_seq++; + if (crypto != NULL) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = crypto->in_cipher; + cipher->packets++; + cipher->blocks += payloadsize / cipher->blocksize; + } + if (session->raw_counter != NULL) { + session->raw_counter->in_bytes += payloadsize; + session->raw_counter->in_packets++; + } + + /* + * We don't want to rewrite a new packet while still executing the + * packet callbacks + */ + session->packet_state = PACKET_STATE_PROCESSING; + ssh_packet_parse_type(session); + SSH_LOG(SSH_LOG_PACKET, + "packet: read type %hhd [len=%" PRIu32 ",padding=%hhd," + "comp=%" PRIu32 ",payload=%" PRIu32 "]", + session->in_packet.type, packet_len, padding, compsize, + payloadsize); + if (crypto == NULL) { + /* In strict kex, only a few packets are allowed. Taint the session + * if we received packets that are normally allowed but to be + * refused if we are in strict kex when KEX is over. + */ + uint8_t type = session->in_packet.type; + + if (type != SSH2_MSG_KEXINIT && type != SSH2_MSG_NEWKEYS && + (type < SSH2_MSG_KEXDH_INIT || + type > SSH2_MSG_KEX_DH_GEX_REQUEST)) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } + /* Check if the packet is expected */ + filter_result = ssh_packet_incoming_filter(session); + + switch (filter_result) { + case SSH_PACKET_ALLOWED: + /* Execute callbacks */ + ssh_packet_process(session, session->in_packet.type); + break; + case SSH_PACKET_DENIED: + ssh_set_error(session, + SSH_FATAL, + "Packet filter: rejected packet (type %d)", + session->in_packet.type); + goto error; + case SSH_PACKET_UNKNOWN: + if (crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + ssh_packet_send_unimplemented(session, session->recv_seq - 1); + break; + } + + session->packet_state = PACKET_STATE_INIT; + if (processed < receivedlen) { + SSH_LOG(SSH_LOG_PACKET, + "packet: %zu bytes still remaining in socket buffer " + "after processing", + receivedlen-processed); + } + + ok = ssh_packet_need_rekey(session, 0); + if (ok) { + SSH_LOG(SSH_LOG_PACKET, "Incoming packet triggered rekey"); + rc = ssh_send_rekex(session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Rekey failed: rc = %d", rc); + goto error; + } + } + + return processed; + case PACKET_STATE_PROCESSING: + SSH_LOG(SSH_LOG_PACKET, "Nested packet processing. Delaying."); + return 0; + } + + ssh_set_error(session, + SSH_FATAL, + "Invalid state into packet_read2(): %d", + session->packet_state); + +error: + session->session_state = SSH_SESSION_STATE_ERROR; + SSH_LOG(SSH_LOG_PACKET, "Packet: processed %zu bytes", processed); + return processed; +} + +static void ssh_packet_socket_controlflow_callback(int code, void *userdata) +{ + ssh_session session = userdata; + struct ssh_iterator *it = NULL; + ssh_channel channel = NULL; + + if (code == SSH_SOCKET_FLOW_WRITEWONTBLOCK) { + SSH_LOG(SSH_LOG_TRACE, "sending channel_write_wontblock callback"); + + /* the out pipe is empty so we can forward this to channels */ + it = ssh_list_get_iterator(session->channels); + while (it != NULL) { + channel = ssh_iterator_value(ssh_channel, it); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_write_wontblock_function, + session, + channel, + channel->remote_window); + it = it->next; + } + } +} + +void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s) +{ + struct ssh_socket_callbacks_struct *callbacks = &session->socket_callbacks; + + callbacks->data = ssh_packet_socket_callback; + callbacks->connected = NULL; + callbacks->controlflow = ssh_packet_socket_controlflow_callback; + callbacks->userdata = session; + ssh_socket_set_callbacks(s, callbacks); +} + +/** @internal + * @brief sets the callbacks for the packet layer + */ +void +ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks) +{ + if (session->packet_callbacks == NULL) { + session->packet_callbacks = ssh_list_new(); + if (session->packet_callbacks == NULL) { + ssh_set_error_oom(session); + return; + } + } + ssh_list_append(session->packet_callbacks, callbacks); +} + +/** @internal + * @brief remove the callbacks from the packet layer + */ +void +ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks) +{ + struct ssh_iterator *it = NULL; + + it = ssh_list_find(session->packet_callbacks, callbacks); + if (it != NULL) { + ssh_list_remove(session->packet_callbacks, it); + } +} + +/** @internal + * @brief sets the default packet handlers + */ +void ssh_packet_set_default_callbacks(ssh_session session) +{ + struct ssh_packet_callbacks_struct *c = &session->default_packet_callbacks; + + c->start = 1; + c->n_callbacks = sizeof(default_packet_handlers) / sizeof(ssh_packet_callback); + c->user = session; + c->callbacks = default_packet_handlers; + ssh_packet_set_callbacks(session, c); +} + +/** @internal + * @brief dispatch the call of packet handlers callbacks for a received packet + * @param type type of packet + */ +void ssh_packet_process(ssh_session session, uint8_t type) +{ + struct ssh_iterator *i = NULL; + int rc = SSH_PACKET_NOT_USED; + ssh_packet_callbacks cb; + + SSH_LOG(SSH_LOG_PACKET, "Dispatching handler for packet type %d", type); + if (session->packet_callbacks == NULL) { + SSH_LOG(SSH_LOG_RARE, "Packet callback is not initialized !"); + return; + } + + i = ssh_list_get_iterator(session->packet_callbacks); + while (i != NULL) { + cb = ssh_iterator_value(ssh_packet_callbacks, i); + i = i->next; + + if (!cb) { + continue; + } + + if (cb->start > type) { + continue; + } + + if (cb->start + cb->n_callbacks <= type) { + continue; + } + + if (cb->callbacks[type - cb->start] == NULL) { + continue; + } + + rc = cb->callbacks[type - cb->start](session, type, session->in_buffer, + cb->user); + if (rc == SSH_PACKET_USED) { + break; + } + } + + if (rc == SSH_PACKET_NOT_USED) { + SSH_LOG(SSH_LOG_RARE, "Couldn't do anything with packet type %d", type); + rc = ssh_packet_send_unimplemented(session, session->recv_seq - 1); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_RARE, "Failed to send unimplemented: %s", + ssh_get_error(session)); + } + if (session->current_crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } +} + +/** @internal + * @brief sends a SSH_MSG_NEWKEYS when enabling the new negotiated ciphers + * @param session the SSH session + * @return SSH_ERROR on error, else SSH_OK + */ +int ssh_packet_send_newkeys(ssh_session session) +{ + int rc; + + /* Send the MSG_NEWKEYS */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); + if (rc < 0) { + return rc; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return rc; + } + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + return rc; +} + +/** @internal + * @brief sends a SSH_MSG_UNIMPLEMENTED answer to an unhandled packet + * @param session the SSH session + * @param seqnum the sequence number of the unknown packet + * @return SSH_ERROR on error, else SSH_OK + */ +int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum){ + int rc; + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_UNIMPLEMENTED, + seqnum); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_packet_send(session); + + return rc; +} + +/** @internal + * @brief handles a SSH_MSG_UNIMPLEMENTED packet + */ +SSH_PACKET_CALLBACK(ssh_packet_unimplemented){ + uint32_t seq; + int rc; + + (void)session; /* unused */ + (void)type; + (void)user; + + rc = ssh_buffer_unpack(packet, "d", &seq); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Could not unpack SSH_MSG_UNIMPLEMENTED packet"); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_RARE, + "Received SSH_MSG_UNIMPLEMENTED (sequence number %" PRIu32 ")",seq); + + return SSH_PACKET_USED; +} + +/** @internal + * @parse the "Type" header field of a packet and updates the session + */ +int ssh_packet_parse_type(struct ssh_session_struct *session) +{ + session->in_packet = (struct packet_struct) { + .type = 0, + }; + + if (session->in_buffer == NULL) { + return SSH_ERROR; + } + + if (ssh_buffer_get_u8(session->in_buffer, &session->in_packet.type) == 0) { + ssh_set_error(session, SSH_FATAL, "Packet too short to read type"); + return SSH_ERROR; + } + + session->in_packet.valid = 1; + + return SSH_OK; +} + +/* + * This function places the outgoing packet buffer into an outgoing + * socket buffer + */ +static int ssh_packet_write(ssh_session session) { + int rc = SSH_ERROR; + + rc=ssh_socket_write(session->socket, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + + return rc; +} + +static int packet_send2(ssh_session session) +{ + unsigned int blocksize = 8; + unsigned int lenfield_blocksize = 0; + enum ssh_hmac_e hmac_type; + uint32_t currentlen = ssh_buffer_get_len(session->out_buffer); + struct ssh_crypto_struct *crypto = NULL; + unsigned char *hmac = NULL; + uint8_t padding_data[32] = { 0 }; + uint8_t padding_size; + uint32_t finallen, payloadsize, compsize; + uint8_t header[5] = {0}; + uint8_t type, *payload; + int rc = SSH_ERROR; + bool etm = false; + int etm_packet_offset = 0; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto) { + blocksize = crypto->out_cipher->blocksize; + lenfield_blocksize = crypto->out_cipher->lenfield_blocksize; + hmac_type = crypto->out_hmac; + etm = crypto->out_hmac_etm; + } else { + hmac_type = session->next_crypto->out_hmac; + } + + payload = (uint8_t *)ssh_buffer_get(session->out_buffer); + type = payload[0]; /* type is the first byte of the packet now */ + + payloadsize = currentlen; + if (etm) { + etm_packet_offset = sizeof(uint32_t); + lenfield_blocksize = 0; + } + + if (crypto != NULL && crypto->do_compress_out && + ssh_buffer_get_len(session->out_buffer) > 0) { + rc = compress_buffer(session,session->out_buffer); + if (rc < 0) { + goto error; + } + currentlen = ssh_buffer_get_len(session->out_buffer); + } + compsize = currentlen; + /* compressed payload + packet len (4) + padding_size len (1) */ + /* totallen - lenfield_blocksize - etm_packet_offset must be equal to 0 (mod blocksize) */ + padding_size = (blocksize - ((blocksize - lenfield_blocksize - etm_packet_offset + currentlen + 5) % blocksize)); + if (padding_size < 4) { + padding_size += blocksize; + } + + if (crypto != NULL) { + int ok; + + ok = ssh_get_random(padding_data, padding_size, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + goto error; + } + } + + finallen = currentlen - etm_packet_offset + padding_size + 1; + + PUSH_BE_U32(header, 0, finallen); + PUSH_BE_U8(header, 4, padding_size); + + rc = ssh_buffer_prepend_data(session->out_buffer, + header, + sizeof(header)); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_data(session->out_buffer, padding_data, padding_size); + if (rc < 0) { + goto error; + } + +#ifdef WITH_PCAP + if (session->pcap_ctx != NULL) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_OUT, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + } +#endif + + hmac = ssh_packet_encrypt(session, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + /* XXX This returns null before switching on crypto, with none MAC + * and on various errors. + * We should distinguish between these cases to avoid hiding errors. */ + if (hmac != NULL) { + rc = ssh_buffer_add_data(session->out_buffer, + hmac, + (uint32_t)hmac_digest_len(hmac_type)); + if (rc < 0) { + goto error; + } + } + + rc = ssh_packet_write(session); + if (rc == SSH_ERROR) { + goto error; + } + session->send_seq++; + if (crypto != NULL) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = crypto->out_cipher; + cipher->packets++; + cipher->blocks += payloadsize / cipher->blocksize; + } + if (session->raw_counter != NULL) { + session->raw_counter->out_bytes += payloadsize; + session->raw_counter->out_packets++; + } + + SSH_LOG(SSH_LOG_PACKET, + "packet: wrote [type=%u, len=%" PRIu32 ", padding_size=%hhd, comp=%" PRIu32 ", " + "payload=%" PRIu32 "]", + type, + finallen, + padding_size, + compsize, + payloadsize); + + rc = ssh_buffer_reinit(session->out_buffer); + if (rc < 0) { + rc = SSH_ERROR; + goto error; + } + + /* We sent the NEWKEYS so any further packet needs to be encrypted + * with the new keys. We can not switch both directions (need to decrypt + * peer NEWKEYS) and we do not want to wait for the peer NEWKEYS + * too, so we will switch only the OUT direction now. + */ + if (type == SSH2_MSG_NEWKEYS) { + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_OUT); + } +error: + return rc; /* SSH_OK, AGAIN or ERROR */ +} + +static bool +ssh_packet_is_kex(unsigned char type) +{ + return type >= SSH2_MSG_DISCONNECT && + type <= SSH2_MSG_KEX_DH_GEX_REQUEST && + type != SSH2_MSG_SERVICE_REQUEST && + type != SSH2_MSG_SERVICE_ACCEPT && + type != SSH2_MSG_IGNORE && + type != SSH2_MSG_EXT_INFO; +} + +static bool +ssh_packet_in_rekey(ssh_session session) +{ + /* We know we are rekeying if we are authenticated and the DH + * status is not finished, but we only queue packets until we've + * sent our NEWKEYS. + */ + return (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) && + (session->dh_handshake_state != DH_STATE_FINISHED) && + (session->dh_handshake_state != DH_STATE_NEWKEYS_SENT); +} + +int ssh_packet_send(ssh_session session) +{ + uint32_t payloadsize; + uint8_t type, *payload; + bool need_rekey, in_rekey; + int rc; + + payloadsize = ssh_buffer_get_len(session->out_buffer); + if (payloadsize < 1) { + return SSH_ERROR; + } + + payload = (uint8_t *)ssh_buffer_get(session->out_buffer); + type = payload[0]; /* type is the first byte of the packet now */ + need_rekey = ssh_packet_need_rekey(session, payloadsize); + in_rekey = ssh_packet_in_rekey(session); + + /* The rekey is triggered here. After that, only the key exchange + * packets can be sent, until we send our NEWKEYS. + */ + if (need_rekey || (in_rekey && !ssh_packet_is_kex(type))) { + if (need_rekey) { + SSH_LOG(SSH_LOG_PACKET, "Outgoing packet triggered rekey"); + } + /* Queue the current packet -- we will send it after the rekey */ + SSH_LOG(SSH_LOG_PACKET, "Queuing packet type %d", type); + rc = ssh_list_append(session->out_queue, session->out_buffer); + if (rc != SSH_OK) { + return SSH_ERROR; + } + session->out_buffer = ssh_buffer_new(); + if (session->out_buffer == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (need_rekey) { + /* Send the KEXINIT packet instead. + * This recursively calls the packet_send(), but it should + * not get into rekeying again. + * After that we need to handle the key exchange responses + * up to the point where we can send the rest of the queue. + */ + return ssh_send_rekex(session); + } + return SSH_OK; + } + + /* Send the packet normally */ + rc = packet_send2(session); + + /* We finished the key exchange so we can try to send our queue now */ + if (rc == SSH_OK && type == SSH2_MSG_NEWKEYS) { + struct ssh_iterator *it = NULL; + + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->send_seq = 0; + } + for (it = ssh_list_get_iterator(session->out_queue); + it != NULL; + it = ssh_list_get_iterator(session->out_queue)) { + struct ssh_buffer_struct *next_buffer = NULL; + + /* Peek only -- do not remove from queue yet */ + next_buffer = (struct ssh_buffer_struct *)it->data; + payloadsize = ssh_buffer_get_len(next_buffer); + if (ssh_packet_need_rekey(session, payloadsize)) { + /* Sigh ... we still can not send this packet. Repeat. */ + SSH_LOG(SSH_LOG_PACKET, "Queued packet triggered rekey"); + return ssh_send_rekex(session); + } + SSH_BUFFER_FREE(session->out_buffer); + session->out_buffer = ssh_list_pop_head(struct ssh_buffer_struct *, + session->out_queue); + payload = (uint8_t *)ssh_buffer_get(session->out_buffer); + type = payload[0]; + SSH_LOG(SSH_LOG_PACKET, "Dequeue packet type %d", type); + rc = packet_send2(session); + if (rc != SSH_OK) { + return rc; + } + } + } + + return rc; +} + +static void +ssh_init_rekey_state(struct ssh_session_struct *session, + struct ssh_cipher_struct *cipher) +{ + /* Reset the counters: should be NOOP */ + cipher->packets = 0; + cipher->blocks = 0; + + /* Default rekey limits for ciphers as specified in RFC4344, Section 3.2 */ + if (cipher->blocksize >= 16) { + /* For larger block size (L bits) use maximum of 2**(L/4) blocks */ + cipher->max_blocks = (uint64_t)1 << (cipher->blocksize*2); + } else { + /* For smaller blocks use limit of 1 GB as recommended in RFC4253 */ + cipher->max_blocks = ((uint64_t)1 << 30) / cipher->blocksize; + } + /* If we have limit provided by user, use the smaller one */ + if (session->opts.rekey_data != 0) { + cipher->max_blocks = MIN(cipher->max_blocks, + session->opts.rekey_data / cipher->blocksize); + } + + SSH_LOG(SSH_LOG_DEBUG, + "Set rekey after %" PRIu64 " blocks", + cipher->max_blocks); +} + +/* + * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and + * current_crypto for our desired direction + */ +int +ssh_packet_set_newkeys(ssh_session session, + enum ssh_crypto_direction_e direction) +{ + struct ssh_cipher_struct *in_cipher = NULL, *out_cipher = NULL; + int rc; + + SSH_LOG(SSH_LOG_TRACE, + "called, direction =%s%s", + direction & SSH_DIRECTION_IN ? " IN " : "", + direction & SSH_DIRECTION_OUT ? " OUT " : ""); + + if (session->next_crypto == NULL) { + return SSH_ERROR; + } + + session->next_crypto->used |= direction; + if (session->current_crypto != NULL) { + if (session->current_crypto->used & direction) { + SSH_LOG(SSH_LOG_TRACE, "This direction isn't used anymore."); + } + /* Mark the current requested direction unused */ + session->current_crypto->used &= ~direction; + } + + /* Both sides switched: do the actual switch now */ + if (session->next_crypto->used == SSH_DIRECTION_BOTH) { + size_t session_id_len; + + if (session->current_crypto != NULL) { + crypto_free(session->current_crypto); + session->current_crypto = NULL; + } + + session->current_crypto = session->next_crypto; + session->current_crypto->used = SSH_DIRECTION_BOTH; + + /* Initialize the next_crypto structure */ + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + session_id_len = session->current_crypto->session_id_len; + session->next_crypto->session_id = malloc(session_id_len); + if (session->next_crypto->session_id == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + memcpy(session->next_crypto->session_id, + session->current_crypto->session_id, + session_id_len); + session->next_crypto->session_id_len = session_id_len; + + return SSH_OK; + } + + /* Initialize common structures so the next context can be used in + * either direction */ + if (session->client) { + /* The server has this part already done */ + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + /* + * Set the cryptographic functions for the next crypto + * (it is needed for ssh_generate_session_keys for key lengths) + */ + rc = crypt_set_algorithms_client(session); + if (rc < 0) { + return SSH_ERROR; + } + } + + if (ssh_generate_session_keys(session) < 0) { + return SSH_ERROR; + } + + in_cipher = session->next_crypto->in_cipher; + out_cipher = session->next_crypto->out_cipher; + if (in_cipher == NULL || out_cipher == NULL) { + return SSH_ERROR; + } + + /* Initialize rekeying states */ + ssh_init_rekey_state(session, out_cipher); + ssh_init_rekey_state(session, in_cipher); + if (session->opts.rekey_time != 0) { + ssh_timestamp_init(&session->last_rekey_time); + SSH_LOG(SSH_LOG_DEBUG, "Set rekey after %" PRIu32 " seconds", + session->opts.rekey_time/1000); + } + + if (in_cipher->set_decrypt_key) { + /* Initialize the encryption and decryption keys in next_crypto */ + rc = in_cipher->set_decrypt_key(in_cipher, + session->next_crypto->decryptkey, + session->next_crypto->decryptIV); + if (rc < 0) { + /* On error, make sure it is not used */ + session->next_crypto->used = 0; + return SSH_ERROR; + } + } + + if (out_cipher->set_encrypt_key) { + rc = out_cipher->set_encrypt_key(out_cipher, + session->next_crypto->encryptkey, + session->next_crypto->encryptIV); + if (rc < 0) { + /* On error, make sure it is not used */ + session->next_crypto->used = 0; + return SSH_ERROR; + } + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/packet_cb.c b/src/libs/libssh-0.12.2/src/packet_cb.c new file mode 100644 index 000000000000..6228b44a48c6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/packet_cb.c @@ -0,0 +1,372 @@ +/* + * packet.c - packet building functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef WITH_GSSAPI +#include "libssh/gssapi.h" +#include +#endif + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/misc.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/session.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#include "libssh/curve25519.h" + +/** + * @internal + * + * @brief Handle a SSH_DISCONNECT packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback) +{ + int rc; + uint32_t code = 0; + char *error = NULL; + ssh_string error_s = NULL; + + (void)user; + (void)type; + + rc = ssh_buffer_get_u32(packet, &code); + if (rc != 0) { + code = ntohl(code); + } + + error_s = ssh_buffer_get_ssh_string(packet); + if (error_s != NULL) { + error = ssh_string_to_char(error_s); + SSH_STRING_FREE(error_s); + } + + if (error != NULL) { + session->peer_discon_msg = strdup(error); + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_MSG_DISCONNECT %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + ssh_set_error(session, + SSH_FATAL, + "Received SSH_MSG_DISCONNECT: %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + SAFE_FREE(error); + + ssh_session_socket_close(session); + /* correctly handle disconnect during authorization */ + session->auth.state = SSH_AUTH_STATE_FAILED; + + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_IGNORE packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback) +{ + (void)session; /* unused */ + (void)user; + (void)type; + (void)packet; + + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_IGNORE packet"); + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_DEBUG packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_debug_callback) +{ + uint8_t always_display = -1; + char *message = NULL; + int rc; + + (void)session; /* unused */ + (void)type; + (void)user; + + rc = ssh_buffer_unpack(packet, "bs", &always_display, &message); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Error reading debug message"); + return SSH_PACKET_USED; + } + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_MSG_DEBUG packet with message %s%s", + message, + always_display != 0 ? " (always display)" : ""); + SAFE_FREE(message); + + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_newkeys) +{ + ssh_string sig_blob = NULL; + ssh_signature sig = NULL; + int rc; + + (void)packet; + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_NEWKEYS"); + + if (session->session_state != SSH_SESSION_STATE_DH || + session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { + ssh_set_error(session, + SSH_FATAL, + "ssh_packet_newkeys called in wrong state : %d:%d", + session->session_state, + session->dh_handshake_state); + goto error; + } + + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->recv_seq = 0; + /* Check that we aren't tainted */ + if (session->flags & SSH_SESSION_FLAG_KEX_TAINTED) { + ssh_set_error(session, + SSH_FATAL, + "Received unexpected packets in strict KEX mode."); + goto error; + } + } + + if (session->server) { + /* server things are done in server.c */ + session->dh_handshake_state=DH_STATE_FINISHED; + } else { +#ifdef WITH_GSSAPI + if (ssh_kex_is_gss(session->next_crypto)) { + OM_uint32 maj_stat, min_stat; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER; + + if (session->gssapi == NULL || session->gssapi->ctx == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + goto error; + } + + if (session->gssapi_key_exchange_mic == NULL) { + ssh_set_error(session, + SSH_FATAL, + "GSSAPI mic not set"); + goto error; + } + + mic.length = ssh_string_len(session->gssapi_key_exchange_mic); + mic.value = ssh_string_data(session->gssapi_key_exchange_mic); + + msg.length = session->next_crypto->digest_len; + msg.value = session->next_crypto->secret_hash; + + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &msg, + &mic, + NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify mic after GSSAPI Key Exchange"); + goto error; + } + SSH_STRING_FREE(session->gssapi_key_exchange_mic); + } else +#endif + { + ssh_key server_key = NULL; + + /* client */ + + /* Verify the host's signature. FIXME do it sooner */ + sig_blob = session->next_crypto->dh_server_signature; + session->next_crypto->dh_server_signature = NULL; + + /* get the server public key */ + server_key = ssh_dh_get_next_server_publickey(session); + if (server_key == NULL) { + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc != SSH_OK) { + goto error; + } + + /* Check if signature from server matches user preferences */ + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + rc = match_group(session->opts.wanted_methods[SSH_HOSTKEYS], + sig->type_c); + if (rc == 0) { + ssh_set_error( + session, + SSH_FATAL, + "Public key from server (%s) doesn't match user " + "preference (%s)", + sig->type_c, + session->opts.wanted_methods[SSH_HOSTKEYS]); + goto error; + } + } + + rc = ssh_pki_signature_verify(session, + sig, + server_key, + session->next_crypto->secret_hash, + session->next_crypto->digest_len); + SSH_SIGNATURE_FREE(sig); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify server hostkey signature"); + goto error; + } + } + SSH_LOG(SSH_LOG_DEBUG, "Signature verified and valid"); + + /* When receiving this packet, we switch on the incoming crypto. */ + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); + if (rc != SSH_OK) { + goto error; + } + } + session->dh_handshake_state = DH_STATE_FINISHED; + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + +error: +#ifdef WITH_GSSAPI + SSH_STRING_FREE(session->gssapi_key_exchange_mic); +#endif + SSH_SIGNATURE_FREE(sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +/** + * @internal + * @brief handles a SSH_SERVICE_ACCEPT packet + * + */ +SSH_PACKET_CALLBACK(ssh_packet_service_accept) +{ + (void)packet; + (void)type; + (void)user; + + session->auth.service_state = SSH_AUTH_SERVICE_ACCEPTED; + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_SERVICE_ACCEPT"); + + return SSH_PACKET_USED; +} + +/** + * @internal + * @brief handles a SSH2_MSG_EXT_INFO packet defined in RFC 8308 + * + */ +SSH_PACKET_CALLBACK(ssh_packet_ext_info) +{ + int rc; + uint32_t nr_extensions = 0; + uint32_t i; + + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_EXT_INFO"); + + rc = ssh_buffer_get_u32(packet, &nr_extensions); + if (rc == 0) { + SSH_LOG(SSH_LOG_PACKET, "Failed to read number of extensions"); + return SSH_PACKET_USED; + } + + nr_extensions = ntohl(nr_extensions); + if (nr_extensions > 128) { + SSH_LOG(SSH_LOG_PACKET, "Invalid number of extensions"); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, "Follows %" PRIu32 " extensions", nr_extensions); + + for (i = 0; i < nr_extensions; i++) { + char *name = NULL; + char *value = NULL; + + rc = ssh_buffer_unpack(packet, "ss", &name, &value); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Error reading extension name-value pair"); + return SSH_PACKET_USED; + } + + if (strcmp(name, "server-sig-algs") == 0) { + /* TODO check for NULL bytes */ + SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value); + + rc = match_group(value, "rsa-sha2-512"); + if (rc == 1) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } + + rc = match_group(value, "rsa-sha2-256"); + if (rc == 1) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } + } else if (strcmp(name, "publickey-hostbound@openssh.com") == 0) { + SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value); + session->extensions |= SSH_EXT_PUBLICKEY_HOSTBOUND; + } else { + SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name); + } + free(name); + free(value); + } + + return SSH_PACKET_USED; +} diff --git a/src/libs/libssh-0.12.2/src/packet_crypt.c b/src/libs/libssh-0.12.2/src/packet_crypt.c new file mode 100644 index 000000000000..12846fe5836d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/packet_crypt.c @@ -0,0 +1,330 @@ +/* + * crypt.c - blowfish-cbc code + * + * This file is part of the SSH Library + * + * Copyright (c) 2003 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#ifdef OPENSSL_CRYPTO +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/wrapper.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/bytearray.h" + +/** @internal + * @brief decrypt the packet length from a raw encrypted packet, and store the first decrypted + * blocksize. + * @returns native byte-ordered decrypted length of the upcoming packet + */ +uint32_t ssh_packet_decrypt_len(ssh_session session, + uint8_t *destination, + uint8_t *source) +{ + struct ssh_crypto_struct *crypto = NULL; + uint32_t decrypted; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL) { + if (crypto->in_cipher->aead_decrypt_length != NULL) { + rc = crypto->in_cipher->aead_decrypt_length( + crypto->in_cipher, source, destination, + crypto->in_cipher->lenfield_blocksize, + session->recv_seq); + } else { + rc = ssh_packet_decrypt( + session, + destination, + source, + 0, + crypto->in_cipher->blocksize); + } + if (rc < 0) { + return 0; + } + } else { + memcpy(destination, source, 8); + } + memcpy(&decrypted,destination,sizeof(decrypted)); + + return ntohl(decrypted); +} + +/** @internal + * @brief decrypts the content of an SSH packet. + * @param[source] source packet, including the encrypted length field + * @param[start] index in the packet that was not decrypted yet. + * @param[encrypted_size] size of the encrypted data to be decrypted after start. + */ +int ssh_packet_decrypt(ssh_session session, + uint8_t *destination, + uint8_t *source, + size_t start, + size_t encrypted_size) +{ + struct ssh_crypto_struct *crypto = NULL; + struct ssh_cipher_struct *cipher = NULL; + + if (encrypted_size <= 0) { + return SSH_ERROR; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return SSH_ERROR; + } + cipher = crypto->in_cipher; + + if (encrypted_size % cipher->blocksize != 0) { + ssh_set_error(session, + SSH_FATAL, + "Cryptographic functions must be used on multiple of " + "blocksize (received %zu)", + encrypted_size); + return SSH_ERROR; + } + + if (cipher->aead_decrypt != NULL) { + return cipher->aead_decrypt(cipher, + source, + destination, + encrypted_size, + session->recv_seq); + } else { + cipher->decrypt(cipher, source + start, destination, encrypted_size); + } + + return 0; +} + +unsigned char *ssh_packet_encrypt(ssh_session session, void *data, size_t len) +{ + struct ssh_crypto_struct *crypto = NULL; + struct ssh_cipher_struct *cipher = NULL; + HMACCTX ctx = NULL; + char *out = NULL; + int etm_packet_offset = 0, rc; + unsigned int blocksize; + size_t finallen = DIGEST_MAX_LEN; + uint32_t seq, lenfield_blocksize; + enum ssh_hmac_e type; + bool etm; + + assert(len); + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto == NULL) { + return NULL; /* nothing to do here */ + } + + blocksize = crypto->out_cipher->blocksize; + lenfield_blocksize = crypto->out_cipher->lenfield_blocksize; + + type = crypto->out_hmac; + etm = crypto->out_hmac_etm; + + if (etm) { + etm_packet_offset = sizeof(uint32_t); + } + + if ((len - lenfield_blocksize - etm_packet_offset) % blocksize != 0) { + ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set" + " on at least one blocksize (received %zu)", len); + return NULL; + } + out = calloc(1, len); + if (out == NULL) { + return NULL; + } + + seq = ntohl(session->send_seq); + cipher = crypto->out_cipher; + + if (cipher->aead_encrypt != NULL) { + cipher->aead_encrypt(cipher, data, out, len, + crypto->hmacbuf, session->send_seq); + memcpy(data, out, len); + } else { + if (type != SSH_HMAC_NONE) { + ctx = hmac_init(crypto->encryptMAC, hmac_digest_len(type), type); + if (ctx == NULL) { + SAFE_FREE(out); + return NULL; + } + + if (!etm) { + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + } + } + + cipher->encrypt(cipher, (uint8_t*)data + etm_packet_offset, out, len - etm_packet_offset); + memcpy((uint8_t*)data + etm_packet_offset, out, len - etm_packet_offset); + + if (type != SSH_HMAC_NONE) { + if (etm) { + PUSH_BE_U32(data, 0, len - etm_packet_offset); + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("mac: ", data, len); + if (finallen != hmac_digest_len(type)) { + printf("Final len is %zu\n", finallen); + } + ssh_log_hexdump("Packet hmac", crypto->hmacbuf, hmac_digest_len(type)); +#endif + } + } + ssh_burn(out, len); + SAFE_FREE(out); + + return crypto->hmacbuf; +} + +/** + * @internal + * + * @brief Verify the hmac of a packet + * + * @param session The session to use. + * @param data The pointer to the data to verify the hmac from. + * @param len The length of the given data. + * @param mac The mac to compare with the hmac. + * + * @return 0 if hmac and mac are equal, < 0 if not or an error + * occurred. + */ +int ssh_packet_hmac_verify(ssh_session session, + const void *data, + size_t len, + uint8_t *mac, + enum ssh_hmac_e type) +{ + struct ssh_crypto_struct *crypto = NULL; + unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; + HMACCTX ctx = NULL; + size_t hmaclen = DIGEST_MAX_LEN; + uint32_t seq; + int cmp; + int rc; + + /* AEAD types have no mac checking */ + if (type == SSH_HMAC_AEAD_POLY1305 || + type == SSH_HMAC_AEAD_GCM) { + return SSH_OK; + } + + crypto = ssh_packet_get_current_crypto(session, + SSH_DIRECTION_IN); + if (crypto == NULL) { + return SSH_ERROR; + } + + ctx = hmac_init(crypto->decryptMAC, + hmac_digest_len(type), + type); + if (ctx == NULL) { + return SSH_ERROR; + } + + seq = htonl(session->recv_seq); + + rc = hmac_update(ctx, + (unsigned char *) &seq, + sizeof(uint32_t)); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_update(ctx, + data, + len); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_final(ctx, + hmacbuf, + &hmaclen); + if (rc != 1) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("received mac", + mac, + hmaclen); + ssh_log_hexdump("Computed mac", + hmacbuf, + hmaclen); + ssh_log_hexdump("seq", + (unsigned char *)&seq, + sizeof(uint32_t)); +#endif + cmp = secure_memcmp(mac, + hmacbuf, + hmaclen); + if (cmp == 0) { + return SSH_OK; + } + + return SSH_ERROR; +} diff --git a/src/libs/libssh-0.12.2/src/pcap.c b/src/libs/libssh-0.12.2/src/pcap.c new file mode 100644 index 000000000000..1a98e1cd1928 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pcap.c @@ -0,0 +1,585 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* pcap.c */ +#include "config.h" +#ifdef WITH_PCAP + +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include + +#include "libssh/libssh.h" +#include "libssh/pcap.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" + +/** + * @defgroup libssh_pcap The libssh pcap functions + * @ingroup libssh + * + * The pcap file generation + * + * @{ + */ + +/* The header of a pcap file is the following. We are not going to make it + * very complicated. + * Just for information. + */ +struct pcap_hdr_s { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +}; + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_VERSION_MAJOR 2 +#define PCAP_VERSION_MINOR 4 + +#define DLT_RAW 12 /* raw IP */ + +/* TCP flags */ +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 + +/* The header of a pcap packet. + * Just for information. + */ +struct pcaprec_hdr_s { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +}; + +/** @private + * @brief a pcap context expresses the state of a pcap dump + * in a SSH session only. Multiple pcap contexts may be used into + * a single pcap file. + */ +struct ssh_pcap_context_struct { + ssh_session session; + ssh_pcap_file file; + int connected; + /* All of this information is useful to generate + * the dummy IP and TCP packets + */ + uint32_t ipsource; + uint32_t ipdest; + uint16_t portsource; + uint16_t portdest; + uint32_t outsequence; + uint32_t insequence; +}; + +/** @private + * @brief a pcap file expresses the state of a pcap file which may + * contain several streams. + */ +struct ssh_pcap_file_struct { + FILE *output; + uint16_t ipsequence; +}; + +/** + * @brief create a new ssh_pcap_file object + */ +ssh_pcap_file ssh_pcap_file_new(void) +{ + struct ssh_pcap_file_struct *pcap = NULL; + + pcap = calloc(1, sizeof(struct ssh_pcap_file_struct)); + if (pcap == NULL) { + return NULL; + } + + return pcap; +} + +/** @internal + * @brief writes a packet on file + */ +static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet) +{ + int err; + uint32_t len; + if (pcap == NULL || pcap->output == NULL) { + return SSH_ERROR; + } + len = ssh_buffer_get_len(packet); + err = fwrite(ssh_buffer_get(packet), len, 1, pcap->output); + if (err < 0) { + return SSH_ERROR; + } else { + return SSH_OK; + } +} + +/** @internal + * @brief prepends a packet with the pcap header and writes packet + * on file + */ +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len) +{ + ssh_buffer header = ssh_buffer_new(); + struct timeval now; + int err; + + if (header == NULL) { + return SSH_ERROR; + } + + gettimeofday(&now, NULL); + err = ssh_buffer_allocate_size(header, + sizeof(uint32_t) * 4 + + ssh_buffer_get_len(packet)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(now.tv_sec)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(now.tv_usec)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(ssh_buffer_get_len(packet))); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(original_len)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_buffer(header, packet); + if (err < 0) { + goto error; + } + err = ssh_pcap_file_write(pcap, header); +error: + SSH_BUFFER_FREE(header); + return err; +} + +/** + * @brief opens a new pcap file and creates header + */ +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename) +{ + ssh_buffer header = NULL; + int err; + + if (pcap == NULL) { + return SSH_ERROR; + } + if (pcap->output) { + fclose(pcap->output); + pcap->output = NULL; + } + pcap->output = fopen(filename, "wb"); + if (pcap->output == NULL) { + return SSH_ERROR; + } + header = ssh_buffer_new(); + if (header == NULL) { + return SSH_ERROR; + } + err = ssh_buffer_allocate_size(header, + sizeof(uint32_t) * 5 + + sizeof(uint16_t) * 2); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(PCAP_MAGIC)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MAJOR)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MINOR)); + if (err < 0) { + goto error; + } + /* currently hardcode GMT to 0 */ + err = ssh_buffer_add_u32(header, htonl(0)); + if (err < 0) { + goto error; + } + /* accuracy */ + err = ssh_buffer_add_u32(header, htonl(0)); + if (err < 0) { + goto error; + } + /* size of the biggest packet */ + err = ssh_buffer_add_u32(header, htonl(MAX_PACKET_LEN)); + if (err < 0) { + goto error; + } + /* we will write sort-of IP */ + err = ssh_buffer_add_u32(header, htonl(DLT_RAW)); + if (err < 0) { + goto error; + } + err = ssh_pcap_file_write(pcap,header); +error: + SSH_BUFFER_FREE(header); + return err; +} + +int ssh_pcap_file_close(ssh_pcap_file pcap) +{ + int err; + + if (pcap == NULL || pcap->output == NULL) { + return SSH_ERROR; + } + err = fclose(pcap->output); + pcap->output = NULL; + if (err != 0) { + return SSH_ERROR; + } else { + return SSH_OK; + } +} + +void ssh_pcap_file_free(ssh_pcap_file pcap) +{ + ssh_pcap_file_close(pcap); + SAFE_FREE(pcap); +} + + +/** @internal + * @brief allocates a new ssh_pcap_context object + */ +ssh_pcap_context ssh_pcap_context_new(ssh_session session) +{ + ssh_pcap_context ctx = NULL; + + ctx = calloc(1, sizeof(struct ssh_pcap_context_struct)); + if (ctx == NULL) { + ssh_set_error_oom(session); + return NULL; + } + ctx->session = session; + return ctx; +} + +void ssh_pcap_context_free(ssh_pcap_context ctx) +{ + SAFE_FREE(ctx); +} + +void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap) +{ + ctx->file = pcap; +} + +/** @internal + * @brief sets the IP and port parameters in the connection + */ +static int ssh_pcap_context_connect(ssh_pcap_context ctx) +{ + ssh_session session=ctx->session; + struct sockaddr_in local = { + .sin_family = AF_UNSPEC, + }; + struct sockaddr_in remote = { + .sin_family = AF_UNSPEC, + }; + socket_t fd; + socklen_t len; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->socket == NULL) { + return SSH_ERROR; + } + + fd = ssh_socket_get_fd(session->socket); + + /* TODO: adapt for windows */ + if (fd < 0) { + return SSH_ERROR; + } + + len = sizeof(local); + rc = getsockname(fd, (struct sockaddr *)&local, &len); + if (rc < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Getting local IP address: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + len = sizeof(remote); + rc = getpeername(fd, (struct sockaddr *)&remote, &len); + if (rc < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Getting remote IP address: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + if (local.sin_family != AF_INET) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Only IPv4 supported for pcap logging"); + return SSH_ERROR; + } + + memcpy(&ctx->ipsource, &local.sin_addr, sizeof(ctx->ipsource)); + memcpy(&ctx->ipdest, &remote.sin_addr, sizeof(ctx->ipdest)); + memcpy(&ctx->portsource, &local.sin_port, sizeof(ctx->portsource)); + memcpy(&ctx->portdest, &remote.sin_port, sizeof(ctx->portdest)); + + ctx->connected = 1; + return SSH_OK; +} + +#define IPHDR_LEN 20 +#define TCPHDR_LEN 20 +#define TCPIPHDR_LEN (IPHDR_LEN + TCPHDR_LEN) +/** @internal + * @brief write a SSH packet as a TCP over IP in a pcap file + * @param ctx open pcap context + * @param direction SSH_PCAP_DIRECTION_IN if the packet has been received + * @param direction SSH_PCAP_DIRECTION_OUT if the packet has been emitted + * @param data pointer to the data to write + * @param len data to write in the pcap file. May be smaller than origlen. + * @param origlen number of bytes of complete data. + * @returns SSH_OK write is successful + * @returns SSH_ERROR an error happened. + */ +int ssh_pcap_context_write(ssh_pcap_context ctx, + enum ssh_pcap_direction direction, + void *data, + uint32_t len, + uint32_t origlen) +{ + ssh_buffer ip; + int rc; + + if (ctx == NULL || ctx->file == NULL) { + return SSH_ERROR; + } + if (ctx->connected == 0) { + if (ssh_pcap_context_connect(ctx) == SSH_ERROR) { + return SSH_ERROR; + } + } + ip = ssh_buffer_new(); + if (ip == NULL) { + ssh_set_error_oom(ctx->session); + return SSH_ERROR; + } + + /* build an IP packet */ + rc = ssh_buffer_pack(ip, + "bbwwwbbw", + 4 << 4 | 5, /* V4, 20 bytes */ + 0, /* tos */ + origlen + TCPIPHDR_LEN, /* total len */ + ctx->file->ipsequence, /* IP id number */ + 0, /* fragment offset */ + 64, /* TTL */ + 6, /* protocol TCP=6 */ + 0); /* checksum */ + + ctx->file->ipsequence++; + if (rc != SSH_OK) { + goto error; + } + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_add_u32(ip, ctx->ipsource); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(ip, ctx->ipdest); + if (rc < 0) { + goto error; + } + } else { + rc = ssh_buffer_add_u32(ip, ctx->ipdest); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(ip, ctx->ipsource); + if (rc < 0) { + goto error; + } + } + /* TCP */ + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_add_u16(ip, ctx->portsource); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u16(ip, ctx->portdest); + if (rc < 0) { + goto error; + } + } else { + rc = ssh_buffer_add_u16(ip, ctx->portdest); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u16(ip, ctx->portsource); + if (rc < 0) { + goto error; + } + } + /* sequence number */ + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_pack(ip, "d", ctx->outsequence); + if (rc != SSH_OK) { + goto error; + } + ctx->outsequence += origlen; + } else { + rc = ssh_buffer_pack(ip, "d", ctx->insequence); + if (rc != SSH_OK) { + goto error; + } + ctx->insequence += origlen; + } + /* ack number */ + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_pack(ip, "d", ctx->insequence); + if (rc != SSH_OK) { + goto error; + } + } else { + rc = ssh_buffer_pack(ip, "d", ctx->outsequence); + if (rc != SSH_OK) { + goto error; + } + } + + rc = ssh_buffer_pack(ip, + "bbwwwP", + 5 << 4, /* header len = 20 = 5 * 32 bits, at offset 4*/ + TH_PUSH | TH_ACK, /* flags */ + 65535, /* window */ + 0, /* checksum */ + 0, /* urgent data ptr */ + (size_t)len, data); /* actual data */ + if (rc != SSH_OK) { + goto error; + } + rc = ssh_pcap_file_write_packet(ctx->file, ip, origlen + TCPIPHDR_LEN); + +error: + SSH_BUFFER_FREE(ip); + return rc; +} + +/** @brief sets the pcap file used to trace the session + * @param current session + * @param pcap a handler to a pcap file. A pcap file may be used in several + * sessions. + * @returns SSH_ERROR in case of error, SSH_OK otherwise. + */ +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap) +{ + ssh_pcap_context ctx = ssh_pcap_context_new(session); + if (ctx == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + ctx->file = pcap; + if (session->pcap_ctx) { + ssh_pcap_context_free(session->pcap_ctx); + } + session->pcap_ctx = ctx; + return SSH_OK; +} +/** @} */ + +#else /* WITH_PCAP */ + +/* Simple stub returning errors when no pcap compiled in */ + +#include "libssh/libssh.h" +#include "libssh/priv.h" + +int ssh_pcap_file_close(ssh_pcap_file pcap) +{ + (void)pcap; + + return SSH_ERROR; +} + +void ssh_pcap_file_free(ssh_pcap_file pcap) +{ + (void)pcap; +} + +ssh_pcap_file ssh_pcap_file_new(void) +{ + return NULL; +} +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename) +{ + (void)pcap; + (void)filename; + + return SSH_ERROR; +} + +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile) +{ + (void)pcapfile; + + ssh_set_error(session, SSH_REQUEST_DENIED, "Pcap support not compiled in"); + return SSH_ERROR; +} + +#endif diff --git a/src/libs/libssh-0.12.2/src/pki.c b/src/libs/libssh-0.12.2/src/pki.c new file mode 100644 index 000000000000..b5fe7ccc81e1 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki.c @@ -0,0 +1,4011 @@ +/* + * pki.c + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * Copyright (c) 2011-2013 Andreas Schneider + * Copyright (c) 2019 Sahana Prasad + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** + * @defgroup libssh_pki The SSH Public Key Infrastructure + * @ingroup libssh + * + * Functions for the creation, importation and manipulation of public and + * private keys in the context of the SSH protocol + * + * @{ + */ + +#include "config.h" +#include "libssh/wrapper.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "libssh/agent.h" +#include "libssh/buffer.h" +#include "libssh/keys.h" +#include "libssh/libssh.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/pki_context.h" +#include "libssh/pki_priv.h" +#include "libssh/pki_sk.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/sk_common.h" /* For SK_NOT_SUPPORTED_MSG */ + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 4096 +#endif /* NOT MAX_LINE_SIZE */ + +#define PKCS11_URI "pkcs11:" + +enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey) +{ + const char *start = NULL; + + start = strstr(privkey, RSA_HEADER_BEGIN); + if (start != NULL) { + return SSH_KEYTYPE_RSA; + } + + start = strstr(privkey, ECDSA_HEADER_BEGIN); + if (start != 0) { + /* We don't know what the curve is at this point, so we don't actually + * know the type. We figure out the actual curve and fix things up in + * pki_private_key_from_base64 */ + return SSH_KEYTYPE_ECDSA_P256; + } + + return SSH_KEYTYPE_UNKNOWN; +} + +/** + * @brief returns the ECDSA key name ("ecdsa-sha2-nistp256" for example) + * + * @param[in] key the ssh_key whose ECDSA name to get + * + * @returns the ECDSA key name ("ecdsa-sha2-nistp256" for example) + * + * @returns "unknown" if the ECDSA key name is not known + */ +const char *ssh_pki_key_ecdsa_name(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + +#ifdef HAVE_ECC /* FIXME Better ECC check needed */ + return pki_key_ecdsa_nid_to_name(key->ecdsa_nid); +#else + return NULL; +#endif /* HAVE_ECC */ +} + +/** + * @brief creates a new empty SSH key + * + * @returns an empty ssh_key handle, or NULL on error. + */ +ssh_key ssh_key_new (void) +{ + ssh_key ptr = malloc (sizeof (struct ssh_key_struct)); + if (ptr == NULL) { + return NULL; + } + ZERO_STRUCTP(ptr); + return ptr; +} + +/** + * @internal + * + * @brief Initialize a new SSH key by duplicating common fields from an existing + * key. + * + * This function creates a new SSH key and copies the common fields from the + * source key, including the key type, type string, flags, and security key + * fields if applicable. This is a helper function used by key duplication + * routines. + * + * @param[in] key The source ssh_key to copy common fields from. + * @param[in] demote Whether to demote the new key to public only. If non-zero, + * only the public fields will be copied and the flags will + * be set accordingly. + * + * @return A new ssh_key with common fields initialized, or NULL on + * error. + * + * @note The caller is responsible for freeing the returned key with + * ssh_key_free(). + */ +ssh_key pki_key_dup_common_init(const ssh_key key, int demote) +{ + ssh_key new = NULL; + + if (key == NULL) { + return NULL; + } + + new = ssh_key_new(); + if (new == NULL) { + return NULL; + } + + new->type = key->type; + new->type_c = key->type_c; + if (demote) { + new->flags = SSH_KEY_FLAG_PUBLIC; + } else { + new->flags = key->flags; + } + + /* Copy security key fields if present */ + if (is_sk_key_type(key->type)) { + new->sk_application = ssh_string_copy(key->sk_application); + if (new->sk_application == NULL) { + goto fail; + } + + if (key->sk_user_id != NULL) { + new->sk_user_id = ssh_string_copy(key->sk_user_id); + if (new->sk_user_id == NULL) { + goto fail; + } + } + + if (!demote) { + new->sk_flags = key->sk_flags; + + new->sk_key_handle = ssh_string_copy(key->sk_key_handle); + if (new->sk_key_handle == NULL) { + goto fail; + } + + new->sk_reserved = ssh_string_copy(key->sk_reserved); + if (new->sk_reserved == NULL) { + goto fail; + } + } + } + + return new; + +fail: + SSH_KEY_FREE(new); + return NULL; +} + +/** + * @brief duplicates the key + * + * @param key An ssh_key to duplicate + * + * @return A duplicated ssh_key key + */ +ssh_key ssh_key_dup(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + + return pki_key_dup(key, 0); +} + +/** + * @brief clean up the key and deallocate all existing keys + * @param[in] key ssh_key to clean + */ +void ssh_key_clean (ssh_key key) +{ + if (key == NULL) + return; + + pki_key_clean(key); + +#ifndef HAVE_LIBCRYPTO + if (key->ed25519_privkey != NULL) { + ssh_burn(key->ed25519_privkey, sizeof(ed25519_privkey)); + SAFE_FREE(key->ed25519_privkey); + } + SAFE_FREE(key->ed25519_pubkey); +#endif /* HAVE_LIBCRYPTO */ + if (key->cert != NULL) { + SSH_BUFFER_FREE(key->cert); + } + if (is_sk_key_type(key->type)) { + ssh_string_burn(key->sk_application); + ssh_string_free(key->sk_application); + ssh_string_burn(key->sk_key_handle); + ssh_string_free(key->sk_key_handle); + ssh_string_burn(key->sk_reserved); + ssh_string_free(key->sk_reserved); + ssh_string_burn(key->sk_user_id); + ssh_string_free(key->sk_user_id); + key->sk_flags = 0; + } + key->cert_type = SSH_KEYTYPE_UNKNOWN; + key->flags = SSH_KEY_FLAG_EMPTY; + key->type = SSH_KEYTYPE_UNKNOWN; + key->ecdsa_nid = 0; + key->type_c = NULL; +} + +/** + * @brief deallocate a SSH key + * @param[in] key ssh_key handle to free + */ +void ssh_key_free (ssh_key key) +{ + if (key) { + ssh_key_clean(key); + SAFE_FREE(key); + } +} + +/** + * @brief returns the type of a ssh key + * @param[in] key the ssh_key handle + * @returns one of SSH_KEYTYPE_RSA, + * SSH_KEYTYPE_ECDSA_P256, SSH_KEYTYPE_ECDSA_P384, + * SSH_KEYTYPE_ECDSA_P521, SSH_KEYTYPE_ED25519, + * SSH_KEYTYPE_RSA_CERT01, SSH_KEYTYPE_ECDSA_P256_CERT01, + * SSH_KEYTYPE_ECDSA_P384_CERT01, SSH_KEYTYPE_ECDSA_P521_CERT01, or + * SSH_KEYTYPE_ED25519_CERT01. + * @returns SSH_KEYTYPE_UNKNOWN if the type is unknown + */ +enum ssh_keytypes_e ssh_key_type(const ssh_key key) +{ + if (key == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + return key->type; +} + +/** + * @brief Get security key (FIDO2) flags for a security key backed ssh_key. + * + * The returned value contains a bitmask of SSH_SK_* flags (e.g. + * SSH_SK_USER_PRESENCE_REQD, SSH_SK_USER_VERIFICATION_REQD, etc.). + * If NULL is passed, then 0 is returned. + * + * @param[in] key The ssh_key handle. + * + * @return Bitmask of security key flags, or 0 if not applicable. + */ +uint32_t ssh_key_get_sk_flags(const ssh_key key) +{ + if (key == NULL) { + return 0; + } + return key->sk_flags; +} + +/** + * @brief Get the application (RP ID) associated with a security key. + * + * This function returns a freshly allocated ssh_string containing a copy of the + * application (RP ID). The caller owns the returned ssh_string and must free it + * with SSH_STRING_FREE() when no longer needed. + * + * Returns NULL if the key is NULL, not a security key type or if the field is + * not set. + * + * @param[in] key The ssh_key handle. + * + * @return ssh_string copy of the application (RP ID) or NULL if not available. + */ +ssh_string ssh_key_get_sk_application(const ssh_key key) +{ + if (key == NULL || key->sk_application == NULL) { + return NULL; + } + + return ssh_string_copy(key->sk_application); +} + +/** + * @brief Get a copy of the user ID associated with a resident security key + * credential. + * + * For resident (discoverable) credentials, authenticators may provide a user + * id which can be arbitrary binary data to allow for storing multiple keys for + * the same Relying Party. This function returns a freshly allocated ssh_string + * containing a copy of that user id. The caller owns the returned ssh_string + * and must free it with SSH_STRING_FREE() when no longer needed. + * + * @note This function will only return useful information if the ssh_key + * passed represents a resident key loaded using the ssh_sk_resident_keys_load() + * function. + * + * @param[in] key The ssh_key handle. + * + * @return ssh_string copy of user id or NULL if not available. + */ +ssh_string ssh_key_get_sk_user_id(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + return ssh_string_copy(key->sk_user_id); +} + +/** + * @brief Convert a signature type to a string. + * + * @param[in] type The algorithm type to convert. + * + * @param[in] hash_type The hash type to convert + * + * @return A string for the keytype or NULL if unknown. + */ +const char * +ssh_key_signature_to_char(enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + switch (type) { + case SSH_KEYTYPE_RSA: + switch (hash_type) { + case SSH_DIGEST_SHA256: + return "rsa-sha2-256"; + case SSH_DIGEST_SHA512: + return "rsa-sha2-512"; + case SSH_DIGEST_SHA1: + case SSH_DIGEST_AUTO: + return "ssh-rsa"; + default: + return NULL; + } + break; + case SSH_KEYTYPE_RSA_CERT01: + switch (hash_type) { + case SSH_DIGEST_SHA256: + return "rsa-sha2-256-cert-v01@openssh.com"; + case SSH_DIGEST_SHA512: + return "rsa-sha2-512-cert-v01@openssh.com"; + case SSH_DIGEST_SHA1: + case SSH_DIGEST_AUTO: + return "ssh-rsa-cert-v01@openssh.com"; + default: + return NULL; + } + break; + default: + return ssh_key_type_to_char(type); + } + + /* We should never reach this */ + return NULL; +} + +/** + * @brief Convert a key type to a string. + * + * @param[in] type The type to convert. + * + * @return A string for the keytype or NULL if unknown. + */ +const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { + switch (type) { + case SSH_KEYTYPE_RSA: + return "ssh-rsa"; + case SSH_KEYTYPE_ECDSA: + return "ssh-ecdsa"; /* deprecated. invalid value */ + case SSH_KEYTYPE_ECDSA_P256: + return "ecdsa-sha2-nistp256"; + case SSH_KEYTYPE_ECDSA_P384: + return "ecdsa-sha2-nistp384"; + case SSH_KEYTYPE_ECDSA_P521: + return "ecdsa-sha2-nistp521"; + case SSH_KEYTYPE_ED25519: + return "ssh-ed25519"; + case SSH_KEYTYPE_RSA_CERT01: + return "ssh-rsa-cert-v01@openssh.com"; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + return "ecdsa-sha2-nistp256-cert-v01@openssh.com"; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return "ecdsa-sha2-nistp384-cert-v01@openssh.com"; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return "ecdsa-sha2-nistp521-cert-v01@openssh.com"; + case SSH_KEYTYPE_ED25519_CERT01: + return "ssh-ed25519-cert-v01@openssh.com"; + case SSH_KEYTYPE_SK_ECDSA: + return "sk-ecdsa-sha2-nistp256@openssh.com"; + case SSH_KEYTYPE_SK_ED25519: + return "sk-ssh-ed25519@openssh.com"; + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"; + case SSH_KEYTYPE_SK_ED25519_CERT01: + return "sk-ssh-ed25519-cert-v01@openssh.com"; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + /* We should never reach this */ + return NULL; +} + +enum ssh_digest_e ssh_key_hash_from_name(const char *name) +{ + if (name == NULL) { + /* TODO we should rather fail */ + return SSH_DIGEST_AUTO; + } + + if (strcmp(name, "ssh-rsa") == 0) { + return SSH_DIGEST_SHA1; + } else if (strcmp(name, "rsa-sha2-256") == 0) { + return SSH_DIGEST_SHA256; + } else if (strcmp(name, "rsa-sha2-512") == 0) { + return SSH_DIGEST_SHA512; + } else if (strcmp(name, "ecdsa-sha2-nistp256") == 0) { + return SSH_DIGEST_SHA256; + } else if (strcmp(name, "ecdsa-sha2-nistp384") == 0) { + return SSH_DIGEST_SHA384; + } else if (strcmp(name, "ecdsa-sha2-nistp521") == 0) { + return SSH_DIGEST_SHA512; + } else if (strcmp(name, "ssh-ed25519") == 0) { + return SSH_DIGEST_AUTO; + } else if (strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) { + return SSH_DIGEST_SHA256; + } else if (strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) { + return SSH_DIGEST_AUTO; + } + + SSH_LOG(SSH_LOG_TRACE, "Unknown signature name %s", name); + + /* TODO we should rather fail */ + return SSH_DIGEST_AUTO; +} + +/** + * @brief Checks the given key against the configured allowed + * public key algorithm types + * + * @param[in] session The SSH session + * @param[in] type The key algorithm to check + * @returns 1 if the key algorithm is allowed, 0 otherwise + */ +int ssh_key_algorithm_allowed(ssh_session session, const char *type) +{ + const char *allowed_list = NULL; + + if (session->client) { + allowed_list = session->opts.pubkey_accepted_types; + if (allowed_list == NULL) { + if (ssh_fips_mode()) { + allowed_list = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + allowed_list = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + } +#ifdef WITH_SERVER + else if (session->server) { + allowed_list = session->opts.wanted_methods[SSH_HOSTKEYS]; + if (allowed_list == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Session invalid: no host key available"); + return 0; + } + } +#endif /* WITH_SERVER */ + else { + SSH_LOG(SSH_LOG_TRACE, "Session invalid: not set as client nor server"); + return 0; + } + + SSH_LOG(SSH_LOG_DEBUG, "Checking %s with list <%s>", type, allowed_list); + return match_group(allowed_list, type); +} + +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key) +{ + int key_size = ssh_key_size(key); + + if (min_size < RSA_MIN_KEY_SIZE) { + if (ssh_fips_mode()) { + min_size = RSA_MIN_FIPS_KEY_SIZE; + } else { + min_size = RSA_MIN_KEY_SIZE; + } + } + return (key_size >= min_size); +} + +/** + * @brief Check the given key is acceptable in regards to the key size policy + * specified by the configuration + * + * @param[in] session The SSH session + * @param[in] key The SSH key + * @returns true if the key is allowed, false otherwise + */ +bool ssh_key_size_allowed(ssh_session session, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = session->opts.rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + +/** + * @brief Helper function to convert a key type to a hash type. + * + * @param[in] type The type to convert. + * + * @return A hash type to be used. + * + * @warning This helper function is available for use without session (for + * example for signing commits) and might cause interoperability issues + * when used within session! It is recommended to use + * ssh_key_type_to_hash() instead of this helper directly when a + * session is available. + * + * @note In order to follow current security best practises for RSA, defaults + * to SHA-2 with SHA-512 digest (RFC8332) instead of the default for + * the SSH protocol (SHA1 with RSA ; RFC 4253). + * + * @see ssh_key_type_to_hash() + */ +static enum ssh_digest_e key_type_to_hash(enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA: + return SSH_DIGEST_SHA512; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_SK_ECDSA: + return SSH_DIGEST_SHA256; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + return SSH_DIGEST_SHA384; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + return SSH_DIGEST_SHA512; + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + return SSH_DIGEST_AUTO; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, + "Digest algorithm to be used with key type %u " + "is not defined", + type); + } + + /* We should never reach this */ + return SSH_DIGEST_AUTO; +} + +/** + * @brief Convert a key type to a hash type. This is usually unambiguous + * for all the key types, unless the SHA2 extension (RFC 8332) is + * negotiated during key exchange. + * + * @param[in] session SSH Session. + * + * @param[in] type The type to convert. + * + * @return A hash type to be used. + */ +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA_CERT01: + /* If we are talking to an old OpenSSH version which does not support + * SHA2 in certificates */ + if ((session->openssh > 0) && + (session->openssh < SSH_VERSION_INT(7, 2, 0))) + { + SSH_LOG(SSH_LOG_DEBUG, + "We are talking to an old OpenSSH (%x); " + "returning SSH_DIGEST_SHA1", + session->openssh); + + return SSH_DIGEST_SHA1; + } + FALL_THROUGH; + case SSH_KEYTYPE_RSA: + if (ssh_key_algorithm_allowed(session, "rsa-sha2-512") && + (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { + return SSH_DIGEST_SHA512; + } + + if (ssh_key_algorithm_allowed(session, "rsa-sha2-256") && + (session->extensions & SSH_EXT_SIG_RSA_SHA256)) { + return SSH_DIGEST_SHA256; + } + + /* Default algorithm for RSA is SHA1 */ + return SSH_DIGEST_SHA1; + + default: + return key_type_to_hash(type); + } + + /* We should never reach this */ + return SSH_DIGEST_AUTO; +} + +/** + * @brief Gets signature algorithm name to be used with the given + * key type. + * + * @param[in] session SSH session. + * @param[in] type The algorithm type to convert. + * + * @return A string for the keytype or NULL if unknown. + */ +const char * +ssh_key_get_signature_algorithm(ssh_session session, + enum ssh_keytypes_e type) +{ + enum ssh_digest_e hash_type; + + if (type == SSH_KEYTYPE_RSA_CERT01) { + /* If we are talking to an old OpenSSH version which does not support + * rsa-sha2-{256,512}-cert-v01@openssh.com */ + if ((session->openssh > 0) && + (session->openssh < SSH_VERSION_INT(7, 8, 0))) + { + SSH_LOG(SSH_LOG_DEBUG, + "We are talking to an old OpenSSH (%x); " + "using old cert format", + session->openssh); + + return "ssh-rsa-cert-v01@openssh.com"; + } + } + + hash_type = ssh_key_type_to_hash(session, type); + + return ssh_key_signature_to_char(type, hash_type); +} + +/** + * @brief Convert a ssh key algorithm name to a ssh key algorithm type. + * + * @param[in] name The name to convert. + * + * @return The enum ssh key algorithm type. + */ +enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name) { + if (name == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + + if ((strcmp(name, "rsa-sha2-256") == 0) || + (strcmp(name, "rsa-sha2-512") == 0)) { + return SSH_KEYTYPE_RSA; + } + + /* Otherwise the key type matches the signature type */ + return ssh_key_type_from_name(name); +} + +/** + * @brief Convert a ssh key name to a ssh key type. + * + * @param[in] name The name to convert. + * + * @return The enum ssh key type. + */ +enum ssh_keytypes_e ssh_key_type_from_name(const char *name) +{ + if (name == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + + if (strcmp(name, "rsa") == 0) { + return SSH_KEYTYPE_RSA; + } else if (strcmp(name, "ssh-rsa") == 0) { + return SSH_KEYTYPE_RSA; + } else if (strcmp(name, "ssh-ecdsa") == 0 + || strcmp(name, "ecdsa") == 0 + || strcmp(name, "ecdsa-sha2-nistp256") == 0) { + return SSH_KEYTYPE_ECDSA_P256; + } else if (strcmp(name, "ecdsa-sha2-nistp384") == 0) { + return SSH_KEYTYPE_ECDSA_P384; + } else if (strcmp(name, "ecdsa-sha2-nistp521") == 0) { + return SSH_KEYTYPE_ECDSA_P521; + } else if (strcmp(name, "ssh-ed25519") == 0){ + return SSH_KEYTYPE_ED25519; + } else if (strcmp(name, "ssh-rsa-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_RSA_CERT01; + } else if (strcmp(name, "ecdsa-sha2-nistp256-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ECDSA_P256_CERT01; + } else if (strcmp(name, "ecdsa-sha2-nistp384-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ECDSA_P384_CERT01; + } else if (strcmp(name, "ecdsa-sha2-nistp521-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ECDSA_P521_CERT01; + } else if (strcmp(name, "ssh-ed25519-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ED25519_CERT01; + } else if(strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ECDSA; + } else if(strcmp(name, "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ECDSA_CERT01; + } else if(strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ED25519; + } else if(strcmp(name, "sk-ssh-ed25519-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ED25519_CERT01; + } + + return SSH_KEYTYPE_UNKNOWN; +} + +/** + * @brief Get the public key type corresponding to a certificate type. + * + * @param[in] type The certificate or public key type. + * + * @return The matching public key type. + */ +enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA_CERT01: + return SSH_KEYTYPE_RSA; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + return SSH_KEYTYPE_ECDSA_P256; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return SSH_KEYTYPE_ECDSA_P384; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return SSH_KEYTYPE_ECDSA_P521; + case SSH_KEYTYPE_ED25519_CERT01: + return SSH_KEYTYPE_ED25519; + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return SSH_KEYTYPE_SK_ECDSA; + case SSH_KEYTYPE_SK_ED25519_CERT01: + return SSH_KEYTYPE_SK_ED25519; + default: + return type; + } +} + +/** + * @brief Check if the key has/is a public key. + * + * @param[in] k The key to check. + * + * @return 1 if it is a public key, 0 if not. + */ +int ssh_key_is_public(const ssh_key k) +{ + if (k == NULL) { + return 0; + } + + return (k->flags & SSH_KEY_FLAG_PUBLIC) == SSH_KEY_FLAG_PUBLIC; +} + +/** + * @brief Check if the key is a private key. + * + * @param[in] k The key to check. + * + * @return 1 if it is a private key, 0 if not. + */ +int ssh_key_is_private(const ssh_key k) { + if (k == NULL) { + return 0; + } + + return (k->flags & SSH_KEY_FLAG_PRIVATE) == SSH_KEY_FLAG_PRIVATE; +} + +/** + * @brief Compare keys if they are equal. + * + * Note that comparing private keys is almost never needed. The private key + * is cryptographically bound to the public key and comparing public keys should + * always be preferred. + * + * @param[in] k1 The first key to compare. + * + * @param[in] k2 The second key to compare. + * + * @param[in] what What part or type of the key do you want to compare. + * + * @return 0 if equal, 1 if not. + */ +int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what) +{ + if (k1 == NULL || k2 == NULL) { + return 1; + } + + if (ssh_key_type_plain(k1->type) != ssh_key_type_plain(k2->type)) { + SSH_LOG(SSH_LOG_DEBUG, "key types don't match!"); + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (!ssh_key_is_private(k1) || + !ssh_key_is_private(k2)) { + return 1; + } + } + + if (is_sk_key_type(k1->type)) { + if (ssh_string_cmp(k1->sk_application, k2->sk_application) != 0) { + return 1; + } + + if (ssh_string_cmp(k1->sk_user_id, k2->sk_user_id) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (k1->sk_flags != k2->sk_flags) { + return 1; + } + + if (ssh_string_cmp(k1->sk_key_handle, k2->sk_key_handle) != 0) { + return 1; + } + + if (ssh_string_cmp(k1->sk_reserved, k2->sk_reserved) != 0) { + return 1; + } + } + } + + if (what == SSH_KEY_CMP_CERTIFICATE) { + if (!is_cert_type(k1->type) || + !is_cert_type(k2->type)) { + return 1; + } + if (k1->cert == NULL || k2->cert == NULL) { + return 1; + } + if (ssh_buffer_get_len(k1->cert) != ssh_buffer_get_len(k2->cert)) { + return 1; + } + return memcmp(ssh_buffer_get(k1->cert), + ssh_buffer_get(k2->cert), + ssh_buffer_get_len(k1->cert)); + } + +#ifndef HAVE_LIBCRYPTO + if (ssh_key_type_plain(k1->type) == SSH_KEYTYPE_ED25519) { + return pki_ed25519_key_cmp(k1, k2, what); + } else if (ssh_key_type_plain(k1->type) == SSH_KEYTYPE_SK_ED25519) { + return pki_ed25519_key_cmp(k1, k2, SSH_KEY_CMP_PUBLIC); + } +#endif + + return pki_key_compare(k1, k2, what); +} + +ssh_signature ssh_signature_new(void) +{ + struct ssh_signature_struct *sig = NULL; + + sig = calloc(1, sizeof(struct ssh_signature_struct)); + if (sig == NULL) { + return NULL; + } + + return sig; +} + +void ssh_signature_free(ssh_signature sig) +{ + if (sig == NULL) { + return; + } + + switch(sig->type) { + case SSH_KEYTYPE_RSA: +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(sig->rsa_sig); +#elif defined HAVE_LIBMBEDCRYPTO + SAFE_FREE(sig->rsa_sig); +#endif /* HAVE_LIBGCRYPT */ + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + gcry_sexp_release(sig->ecdsa_sig); +#elif defined HAVE_LIBMBEDCRYPTO + bignum_safe_free(sig->ecdsa_sig.r); + bignum_safe_free(sig->ecdsa_sig.s); +#endif /* HAVE_GCRYPT_ECC */ + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: +#ifndef HAVE_LIBCRYPTO + /* When using OpenSSL, the signature is stored in sig->raw_sig */ + SAFE_FREE(sig->ed25519_sig); +#endif /* HAVE_LIBCRYPTO */ + break; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + break; + } + + /* Explicitly zero the signature content before free */ + ssh_string_burn(sig->raw_sig); + SSH_STRING_FREE(sig->raw_sig); + SAFE_FREE(sig); +} + +/** + * @brief import a base64 formatted key from a memory c-string + * + * @param[in] b64_key The c-string holding the base64 encoded key + * + * @param[in] passphrase The passphrase to decrypt the key, or NULL + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free() + * + * @return SSH_ERROR in case of error, SSH_OK otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey) +{ + ssh_key key = NULL; + const char *openssh_header = NULL; + + if (b64_key == NULL || pkey == NULL) { + return SSH_ERROR; + } + + if (b64_key == NULL || !*b64_key) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Trying to decode privkey passphrase=%s", + passphrase ? "true" : "false"); + + /* Test for OpenSSH key format first */ + openssh_header = strstr(b64_key, OPENSSH_HEADER_BEGIN); + if (openssh_header != NULL) { + key = ssh_pki_openssh_privkey_import(openssh_header, + passphrase, + auth_fn, + auth_data); + } else { + /* fallback on PEM decoder */ + key = pki_private_key_from_base64(b64_key, + passphrase, + auth_fn, + auth_data); + } + if (key == NULL) { + return SSH_ERROR; + } + + *pkey = key; + + return SSH_OK; +} + + + /** + * @brief Convert a private key to a base64 encoded key in given format + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] b64_key A pointer to store the allocated base64 encoded key. You + * need to free the buffer using ssh_string_from_char(). + * + * @param[in] format The file format (OpenSSH, PEM, or default) + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int +ssh_pki_export_privkey_base64_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key, + enum ssh_file_format_e format) +{ + ssh_string blob = NULL; + char *b64 = NULL; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + * + * The FIDO2/U2F security keys are an extension to the SSH protocol + * proposed by OpenSSH, and do not have any representation in PEM format. + * So, they are always exported in the OpenSSH file format. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519 && + !is_sk_key_type(privkey->type)) { + format = SSH_FILE_FORMAT_PEM; + } +#endif /* HAVE_LIBCRYPTO */ + + switch (format) { + case SSH_FILE_FORMAT_PEM: + blob = pki_private_key_to_pem(privkey, + passphrase, + auth_fn, + auth_data); + break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; + } + if (blob == NULL) { + return SSH_ERROR; + } + + b64 = strndup(ssh_string_data(blob), ssh_string_len(blob)); + SSH_STRING_FREE(blob); + if (b64 == NULL) { + return SSH_ERROR; + } + + *b64_key = b64; + + return SSH_OK; +} + + /** + * @brief Convert a private key to a pem base64 encoded key, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] b64_key A pointer to store the allocated base64 encoded key. You + * need to free the buffer using ssh_string_from_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key) +{ + return ssh_pki_export_privkey_base64_format(privkey, + passphrase, + auth_fn, + auth_data, + b64_key, + SSH_FILE_FORMAT_DEFAULT); +} + + + +/** + * @brief Import a private key from a file or a PKCS #11 device. + * + * @param[in] filename The filename of the private key or the + * PKCS #11 URI corresponding to the private key. + * + * @param[in] passphrase The passphrase to decrypt the private key. Set to NULL + * if none is needed or it is unknown. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] pkey A pointer to store the allocated ssh_key. You need to + * free the key using ssh_key_free(). + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + **/ +int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey) { + struct stat sb; + char *key_buf = NULL; + FILE *file = NULL; + off_t size; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + if (pkey == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + +#ifdef WITH_PKCS11_URI + if (ssh_pki_is_uri(filename)) { + rc = pki_uri_import(filename, pkey, SSH_KEY_PRIVATE); + return rc; + } +#endif /* WITH_PKCS11_URI */ + + file = fopen(filename, "rb"); + if (file == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error opening %s: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_EOF; + } + + rc = fstat(fileno(file), &sb); + if (rc < 0) { + fclose(file); + SSH_LOG(SSH_LOG_TRACE, + "Error getting stat of %s: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + switch (errno) { + case ENOENT: + case EACCES: + return SSH_EOF; + } + + return SSH_ERROR; + } + + if (sb.st_size > MAX_PRIVKEY_SIZE) { + SSH_LOG(SSH_LOG_TRACE, + "Private key is bigger than 4M."); + fclose(file); + return SSH_ERROR; + } + + key_buf = malloc(sb.st_size + 1); + if (key_buf == NULL) { + fclose(file); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + return SSH_ERROR; + } + + size = fread(key_buf, 1, sb.st_size, file); + fclose(file); + + if (size != sb.st_size) { + SAFE_FREE(key_buf); + SSH_LOG(SSH_LOG_TRACE, + "Error reading %s: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + key_buf[size] = 0; + + rc = ssh_pki_import_privkey_base64(key_buf, + passphrase, + auth_fn, + auth_data, + pkey); + + SAFE_FREE(key_buf); + return rc; +} + +/** + * @brief Export a private key to a file in format specified in the argument + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[in] filename The path where to store the pem file. + * + * @param[in] format The file format (OpenSSH, PEM, or default) + * + * @return SSH_OK on success, SSH_ERROR on error. + */ + +int +ssh_pki_export_privkey_file_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename, + enum ssh_file_format_e format) +{ + ssh_string blob = NULL; + FILE *fp = NULL; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + fp = fopen(filename, "wb"); + if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_FUNCTIONS, "Error opening %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_EOF; + } + + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + * + * The FIDO2/U2F security keys are an extension to the SSH protocol + * proposed by OpenSSH, and do not have any representation in PEM format. + * So, they are always exported in the OpenSSH file format. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519 && + !is_sk_key_type(privkey->type)) { + + format = SSH_FILE_FORMAT_PEM; + } +#endif /* HAVE_LIBCRYPTO */ + + switch (format) { + case SSH_FILE_FORMAT_PEM: + blob = pki_private_key_to_pem(privkey, + passphrase, + auth_fn, + auth_data); + break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; + } + if (blob == NULL) { + fclose(fp); + return -1; + } + + rc = fwrite(ssh_string_data(blob), ssh_string_len(blob), 1, fp); + SSH_STRING_FREE(blob); + if (rc != 1 || ferror(fp)) { + fclose(fp); + unlink(filename); + return SSH_ERROR; + } + fclose(fp); + + return SSH_OK; +} + +/** + * @brief Export a private key to a pem file on disk, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[in] filename The path where to store the pem file. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int +ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename) +{ + return ssh_pki_export_privkey_file_format(privkey, + passphrase, + auth_fn, + auth_data, + filename, + SSH_FILE_FORMAT_DEFAULT); +} + +/* temporary function to migrate seamlessly to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) +{ + ssh_public_key pub = NULL; + ssh_key tmp = NULL; + + if (key == NULL) { + return NULL; + } + + tmp = ssh_key_dup(key); + if (tmp == NULL) { + return NULL; + } + + pub = calloc(1, sizeof(struct ssh_public_key_struct)); + if (pub == NULL) { + ssh_key_free(tmp); + return NULL; + } + + pub->type = tmp->type; + pub->type_c = tmp->type_c; + +#if defined(HAVE_LIBMBEDCRYPTO) + pub->rsa_pub = tmp->pk; + tmp->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + pub->key_pub = tmp->key; + tmp->key = NULL; +#else + pub->rsa_pub = tmp->rsa; + tmp->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + + ssh_key_free(tmp); + + return pub; +} + +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) +{ + ssh_private_key privkey = NULL; + + privkey = calloc(1, sizeof(struct ssh_private_key_struct)); + if (privkey == NULL) { + ssh_key_free(key); + return NULL; + } + + privkey->type = key->type; +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->rsa_priv = key->pk; +#elif defined(HAVE_LIBCRYPTO) + privkey->key_priv = key->key; +#else + privkey->rsa_priv = key->rsa; +#endif /* HAVE_LIBCRYPTO */ + + return privkey; +} + +int pki_import_privkey_buffer(enum ssh_keytypes_e type, + ssh_buffer buffer, + ssh_key *pkey) +{ + ssh_key key = NULL; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + switch (type) { + case SSH_KEYTYPE_RSA: { + ssh_string n = NULL; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + + rc = ssh_buffer_unpack(buffer, "SSSSSS", &n, &e, &d, &iqmp, &p, &q); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + rc = pki_privkey_build_rsa(key, n, e, d, iqmp, p, q); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); + ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); + ssh_log_hexdump("d", ssh_string_data(d), ssh_string_len(d)); + ssh_log_hexdump("iqmp", ssh_string_data(iqmp), ssh_string_len(iqmp)); + ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); + ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); +#endif /* DEBUG_CRYPTO */ + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA private key"); + goto fail; + } + break; + } +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: { + ssh_string e = NULL; + ssh_string exp = NULL; + ssh_string i = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SSS", &i, &e, &exp); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); + SSH_STRING_FREE(i); + if (nid == -1) { + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(exp); + SSH_STRING_FREE(exp); + goto fail; + } + + rc = pki_privkey_build_ecdsa(key, nid, e, exp); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(exp); + SSH_STRING_FREE(exp); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA private key"); + goto fail; + } + break; + } + case SSH_KEYTYPE_SK_ECDSA: { + ssh_string type_str = NULL; + ssh_string pubkey = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SS", &type_str, &pubkey); + if (rc != SSH_OK) { + goto fail; + } + + rc = pki_buffer_unpack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + SSH_STRING_FREE(type_str); + SSH_STRING_FREE(pubkey); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(type_str)); + SSH_STRING_FREE(type_str); + + if (nid == -1) { + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ecdsa(key, nid, pubkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + goto fail; + } + break; + } +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_ED25519: { + ssh_string pubkey = NULL, privkey = NULL; + + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "Ed25519 keys not supported in FIPS mode"); + goto fail; + } + + rc = ssh_buffer_unpack(buffer, "SS", &pubkey, &privkey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + rc = pki_privkey_build_ed25519(key, pubkey, privkey); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ed25519 key"); + goto fail; + } + break; + } + case SSH_KEYTYPE_SK_ED25519: { + ssh_string pubkey = NULL; + + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "Ed25519 keys not supported in FIPS mode"); + goto fail; + } + + rc = ssh_buffer_unpack(buffer, "S", &pubkey); + if (rc != SSH_OK) { + goto fail; + } + + rc = pki_buffer_unpack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ed25519(key, pubkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + goto fail; + } + break; + } + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown private key type (%d)", type); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +static int pki_import_pubkey_buffer(ssh_buffer buffer, + enum ssh_keytypes_e type, + ssh_key *pkey) +{ + ssh_key key = NULL; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PUBLIC; + + switch (type) { + case SSH_KEYTYPE_RSA: + { + ssh_string e = NULL; + ssh_string n = NULL; + + rc = ssh_buffer_unpack(buffer, "SS", &e, &n); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + rc = pki_pubkey_build_rsa(key, e, n); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); + ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); +#endif /* DEBUG_CRYPTO */ + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA public key"); + goto fail; + } + } + break; +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA: /* deprecated */ + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: + { + ssh_string e = NULL; + ssh_string i = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SS", &i, &e); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); + SSH_STRING_FREE(i); + if (nid == -1) { + ssh_string_burn(e); + SSH_STRING_FREE(e); + goto fail; + } + + rc = pki_pubkey_build_ecdsa(key, nid, e); + ssh_string_burn(e); + SSH_STRING_FREE(e); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA public key"); + goto fail; + } + + /* Unpack SK specific parameters */ + if (type == SSH_KEYTYPE_SK_ECDSA) { + ssh_string application = ssh_buffer_get_ssh_string(buffer); + if (application == NULL) { + SSH_LOG(SSH_LOG_TRACE, "SK Unpack error"); + goto fail; + } + key->sk_application = application; + key->type_c = ssh_key_type_to_char(key->type); + } + } + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + { + ssh_string pubkey = ssh_buffer_get_ssh_string(buffer); + + if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid public key length"); + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ed25519(key, pubkey); + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ED25519 public key"); + goto fail; + } + + if (type == SSH_KEYTYPE_SK_ED25519) { + ssh_string application = ssh_buffer_get_ssh_string(buffer); + if (application == NULL) { + SSH_LOG(SSH_LOG_TRACE, "SK Unpack error"); + goto fail; + } + key->sk_application = application; + } + } + break; + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type %d", type); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +static int pki_import_cert_buffer(ssh_buffer buffer, + enum ssh_keytypes_e type, + ssh_key *pkey) +{ + ssh_buffer cert = NULL; + ssh_string tmp_s = NULL; + const char *type_c = NULL; + ssh_key key = NULL; + int rc; + + /* + * The cert blob starts with the key type as an ssh_string, but this + * string has been read out of the buffer to identify the key type. + * Simply add it again as first element before copying the rest. + */ + cert = ssh_buffer_new(); + if (cert == NULL) { + goto fail; + } + type_c = ssh_key_type_to_char(type); + tmp_s = ssh_string_from_char(type_c); + if (tmp_s == NULL) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(cert, tmp_s); + SSH_STRING_FREE(tmp_s); + if (rc != 0) { + goto fail; + } + rc = ssh_buffer_add_buffer(cert, buffer); + if (rc != 0) { + goto fail; + } + + /* + * After the key type, comes an ssh_string nonce. Just after this comes the + * cert public key, which can be parsed out of the buffer. + */ + tmp_s = ssh_buffer_get_ssh_string(buffer); + if (tmp_s == NULL) { + goto fail; + } + SSH_STRING_FREE(tmp_s); + + switch (type) { + case SSH_KEYTYPE_RSA_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_RSA, &key); + break; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ECDSA_P256, &key); + break; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ECDSA_P384, &key); + break; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ECDSA_P521, &key); + break; + case SSH_KEYTYPE_ED25519_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ED25519, &key); + break; + case SSH_KEYTYPE_SK_ECDSA_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_SK_ECDSA, &key); + break; + case SSH_KEYTYPE_SK_ED25519_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_SK_ED25519, &key); + break; + default: + key = ssh_key_new(); + } + if (rc != 0 || key == NULL) { + goto fail; + } + + key->type = type; + key->type_c = type_c; + key->cert = cert; + + *pkey = key; + return SSH_OK; + +fail: + ssh_key_free(key); + SSH_BUFFER_FREE(cert); + return SSH_ERROR; +} + +/** + * @brief Import a base64 formatted public key from a memory c-string. + * + * Note that the public key is just the base64 part (without the key + * type prefix and comment suffix you can find in the OpenSSH public + * key file or known_hosts file). + * + * @param[in] b64_key The base64 key to import. + * @param[in] type The type of the key to import. + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free(). + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + int rc; + + if (b64_key == NULL || pkey == NULL) { + return SSH_ERROR; + } + + buffer = base64_to_bin(b64_key); + if (buffer == NULL) { + return SSH_ERROR; + } + + type_s = ssh_buffer_get_ssh_string(buffer); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + SSH_STRING_FREE(type_s); + + if (is_cert_type(type)) { + rc = pki_import_cert_buffer(buffer, type, pkey); + } else { + rc = pki_import_pubkey_buffer(buffer, type, pkey); + } + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @internal + * + * @brief Import a public key from a ssh string. + * + * @param[in] key_blob The key blob to import as specified in RFC 4253 section + * 6.6 "Public Key Algorithms". + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_blob(const ssh_string key_blob, + ssh_key *pkey) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + enum ssh_keytypes_e type; + int rc; + + if (key_blob == NULL || pkey == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + return SSH_ERROR; + } + + rc = ssh_buffer_add_data(buffer, + ssh_string_data(key_blob), + (uint32_t)ssh_string_len(key_blob)); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + goto fail; + } + + type_s = ssh_buffer_get_ssh_string(buffer); + if (type_s == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + goto fail; + } + + type = ssh_key_type_from_name(ssh_string_get_char(type_s)); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Unknown key type found!"); + goto fail; + } + SSH_STRING_FREE(type_s); + + if (is_cert_type(type)) { + rc = pki_import_cert_buffer(buffer, type, pkey); + } else { + rc = pki_import_pubkey_buffer(buffer, type, pkey); + } + + SSH_BUFFER_FREE(buffer); + + return rc; +fail: + SSH_BUFFER_FREE(buffer); + SSH_STRING_FREE(type_s); + + return SSH_ERROR; +} + +#ifdef WITH_PKCS11_URI +/** + *@brief Detect if the pathname in cmp is a PKCS #11 URI. + * + * @param[in] cmp The path to the public/private key + * or a private/public PKCS #11 URI. + * + * @returns true if filename is a URI starting with "pkcs11:" + * false otherwise. + */ +bool ssh_pki_is_uri(const char *cmp) +{ + int rc; + + rc = strncmp(cmp, PKCS11_URI, strlen(PKCS11_URI)); + if (rc == 0) { + return true; + } + + return false; +} + +/** + *@brief export a Public PKCS #11 URI from a Private PKCS #11 URI + * by replacing "type=private" to "type=public". + * TODO: Improve the parser + * + * @param[in] priv_uri Private PKCS #11 URI. + * + * @returns pointer to the public PKCS #11 URI. You need to free + * the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char(). + */ +char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri) +{ + char *pub_uri_temp = NULL; + + pub_uri_temp = ssh_strreplace(priv_uri, + "type=private", + "type=public"); + + return pub_uri_temp; +} +#endif /* WITH_PKCS11_URI */ + +/** + * @brief Import a public key from a file or a PKCS #11 device. + * + * @param[in] filename The filename of the public key or the + * PKCS #11 URI corresponding to the public key. + * + * @param[out] pkey A pointer to store the allocated public key. You need to + * free the memory using ssh_key_free(). + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) +{ + enum ssh_keytypes_e type; + struct stat sb; + char *key_buf = NULL, *p = NULL; + size_t buflen, i; + const char *q = NULL; + FILE *file = NULL; + off_t size; + int rc, cmp; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_key priv_key = NULL; + + if (pkey == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + +#ifdef WITH_PKCS11_URI + if (ssh_pki_is_uri(filename)) { + rc = pki_uri_import(filename, pkey, SSH_KEY_PUBLIC); + return rc; + } +#endif /* WITH_PKCS11_URI */ + + file = fopen(filename, "rb"); + if (file == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Error opening %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_EOF; + } + + rc = fstat(fileno(file), &sb); + if (rc < 0) { + fclose(file); + SSH_LOG(SSH_LOG_TRACE, "Error gettint stat of %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + switch (errno) { + case ENOENT: + case EACCES: + return SSH_EOF; + } + return SSH_ERROR; + } + + if (sb.st_size > MAX_PUBKEY_SIZE) { + fclose(file); + return SSH_ERROR; + } + + key_buf = malloc(sb.st_size + 1); + if (key_buf == NULL) { + fclose(file); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + return SSH_ERROR; + } + + size = fread(key_buf, 1, sb.st_size, file); + fclose(file); + + if (size != sb.st_size) { + SAFE_FREE(key_buf); + SSH_LOG(SSH_LOG_TRACE, "Error reading %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + key_buf[size] = '\0'; + buflen = strlen(key_buf); + + /* Test for new OpenSSH key format first */ + cmp = strncmp(key_buf, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); + if (cmp == 0) { + *pkey = ssh_pki_openssh_pubkey_import(key_buf); + SAFE_FREE(key_buf); + if (*pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key from OpenSSH" + " private key file"); + return SSH_ERROR; + } + return SSH_OK; + } + + /* + * Try to parse key as PEM. Set empty passphrase, so user won't be prompted + * for passphrase. Don't try to decrypt encrypted private key. + */ + priv_key = pki_private_key_from_base64(key_buf, "", NULL, NULL); + if (priv_key) { + rc = ssh_pki_export_privkey_to_pubkey(priv_key, pkey); + ssh_key_free(priv_key); + SAFE_FREE(key_buf); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to import public key from PEM" + " private key file"); + return SSH_ERROR; + } + return SSH_OK; + } + + /* This the old one-line public key format */ + q = p = key_buf; + for (i = 0; i < buflen; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + type = ssh_key_type_from_name(q); + if (type == SSH_KEYTYPE_UNKNOWN) { + SAFE_FREE(key_buf); + return SSH_ERROR; + } + + if (i >= buflen) { + SAFE_FREE(key_buf); + return SSH_ERROR; + } + q = &p[i + 1]; + for (; i < buflen; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + rc = ssh_pki_import_pubkey_base64(q, type, pkey); + SAFE_FREE(key_buf); + + return rc; +} + +/** + * @brief Import a base64 formatted certificate from a memory c-string. + * + * Note that the certificate is just the base64 part (without the key + * type prefix and comment suffix you can find in the OpenSSH certificate + * file). + * + * @param[in] b64_cert The base64 cert to import. + * @param[in] type The type of the cert to import. + * @param[out] pkey A pointer where the allocated certificate can be stored. + * You need to free the memory using ssh_key_free(). + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_cert_base64(const char *b64_cert, + enum ssh_keytypes_e type, + ssh_key *pkey) +{ + return ssh_pki_import_pubkey_base64(b64_cert, type, pkey); +} + +/** + * @internal + * + * @brief Import a certificate from a ssh string. + * + * @param[in] cert_blob The cert blob to import as specified in RFC 4253 section + * 6.6 "Public Key Algorithms". + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_cert_blob(const ssh_string cert_blob, + ssh_key *pkey) +{ + return ssh_pki_import_pubkey_blob(cert_blob, pkey); +} + +/** + * @brief Import a certificate from the given filename. + * + * @param[in] filename The path to the certificate. + * + * @param[out] pkey A pointer to store the allocated certificate. You need to + * free the memory using ssh_key_free(). + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_cert_file(const char *filename, ssh_key *pkey) +{ + int rc; + + rc = ssh_pki_import_pubkey_file(filename, pkey); + if (rc == SSH_OK) { + /* check the key is a cert type. */ + if (!is_cert_type((*pkey)->type)) { + SSH_KEY_FREE(*pkey); + return SSH_ERROR; + } + } + + return rc; +} + +/** + * @internal + * + * @brief Internal function to generate a key pair. + * + * @param[in] type Type of key to create + * + * @param[in] parameter Parameter to the creation of key: + * rsa : length of the key in bits (e.g. 1024, 2048, 4096) + * If parameter is 0, then the default size will be used. + * @param[out] pkey A pointer to store the allocated private key. You need + * to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +static int pki_generate_key_internal(enum ssh_keytypes_e type, + int parameter, + ssh_key *pkey) +{ + int rc; + ssh_key key = NULL; + + if (pkey == NULL) { + return SSH_ERROR; + } + + key = ssh_key_new(); + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + switch(type){ + case SSH_KEYTYPE_RSA: + if (parameter != 0 && parameter < RSA_MIN_KEY_SIZE) { + SSH_LOG( + SSH_LOG_WARN, + "RSA key size parameter (%d) is below minimum allowed (%d)", + parameter, + RSA_MIN_KEY_SIZE); + goto error; + } + + rc = pki_key_generate_rsa(key, parameter); + if(rc == SSH_ERROR) + goto error; + break; +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA: /* deprecated */ + rc = pki_key_generate_ecdsa(key, parameter); + if (rc == SSH_ERROR) { + goto error; + } + + /* Update key type */ + key->type_c = ssh_pki_key_ecdsa_name(key); + break; + case SSH_KEYTYPE_ECDSA_P256: + rc = pki_key_generate_ecdsa(key, 256); + if (rc == SSH_ERROR) { + goto error; + } + break; + case SSH_KEYTYPE_ECDSA_P384: + rc = pki_key_generate_ecdsa(key, 384); + if (rc == SSH_ERROR) { + goto error; + } + break; + case SSH_KEYTYPE_ECDSA_P521: + rc = pki_key_generate_ecdsa(key, 521); + if (rc == SSH_ERROR) { + goto error; + } + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_ED25519: + rc = pki_key_generate_ed25519(key); + if (rc == SSH_ERROR) { + goto error; + } + break; + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + goto error; + } + + *pkey = key; + return SSH_OK; +error: + ssh_key_free(key); + return SSH_ERROR; +} + +/** + * @brief Generates a key pair. + * + * @param[in] type Type of key to create + * + * @param[in] parameter Parameter to the creation of key: + * rsa : length of the key in bits (e.g. 1024, 2048, 4096) + * If parameter is 0, then the default size will be used. + * @param[out] pkey A pointer to store the allocated private key. You need + * to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @warning Generating a key pair may take some time. + * + * @see ssh_key_free() + */ +int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey) +{ + return pki_generate_key_internal(type, parameter, pkey); +} + +/** + * @brief Generates a key pair. + * + * @param[in] type Type of key to create + * + * @param[in] pki_context PKI context containing various configuration + * parameters and sub-contexts. Can be NULL for + * standard SSH key types (RSA, ECDSA, ED25519) where + * defaults will be used. Can also be NULL for security + * key types (SK_*), in which case default callbacks and + * settings will be used automatically. + * + * @param[out] pkey A pointer to store the allocated private key. You need + * to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_pki_ctx_new() + * @see ssh_key_free() + */ +int ssh_pki_generate_key(enum ssh_keytypes_e type, + ssh_pki_ctx pki_context, + ssh_key *pkey) +{ + + /* Handle Security Key types with the specialized function */ + if (is_sk_key_type(type)) { +#ifdef WITH_FIDO2 + ssh_pki_ctx temp_ctx = NULL; + ssh_pki_ctx ctx_to_use = pki_context; + int rc; + + /* If no context provided, create a temporary default one */ + if (pki_context == NULL) { + SSH_LOG(SSH_LOG_INFO, + "No PKI context provided, using the default one"); + + temp_ctx = ssh_pki_ctx_new(); + if (temp_ctx == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context"); + return SSH_ERROR; + } + ctx_to_use = temp_ctx; + } + + /* Verify that we have valid SK callbacks */ + if (ctx_to_use->sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Missing SK callbacks in PKI context"); + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + return SSH_ERROR; + } + + rc = pki_sk_enroll_key(ctx_to_use, type, pkey); + + /* Clean up temporary context if we created one */ + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + + return rc; +#else /* WITH_FIDO2 */ + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ + } else { + int parameter = 0; + + if (type == SSH_KEYTYPE_RSA && pki_context != NULL) { + parameter = pki_context->rsa_key_size; + } + + return pki_generate_key_internal(type, parameter, pkey); + } +} + +/** + * @brief Create a public key from a private key. + * + * @param[in] privkey The private key to get the public key from. + * + * @param[out] pkey A pointer to store the newly allocated public key. You + * NEED to free the key using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey) +{ + ssh_key pubkey = NULL; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + pubkey = pki_key_dup(privkey, 1); + if (pubkey == NULL) { + return SSH_ERROR; + } + + *pkey = pubkey; + return SSH_OK; +} + +/** + * @internal + * + * @brief Pack security key private data into a buffer. + * + * This function packs the common security key fields (application, flags, + * key handle, and reserved data) into a buffer. + * This is used for both ECDSA and Ed25519 security keys when exporting + * private key data. + * + * @param[in] buffer The buffer to pack the security key data into. + * + * @param[in] key The security key containing the data to pack. + * Must be a security key type (SK_ECDSA or SK_ED25519). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_buffer_pack() + */ +int pki_buffer_pack_sk_priv_data(ssh_buffer buffer, ssh_key key) +{ + return ssh_buffer_pack(buffer, + "SbSS", + key->sk_application, + key->sk_flags, + key->sk_key_handle, + key->sk_reserved); +} + +/** + * @internal + * + * @brief Unpack security key private data from a buffer. + * + * This function unpacks the common security key fields (application, flags, + * key handle, and reserved data) from a buffer. + * This is used for both ECDSA and Ed25519 security keys when importing + * private key data. + * + * @param[in] buffer The buffer to unpack the security key data from. + * + * @param[in] key The security key to store the unpacked data into. + * Must be a security key type (SK_ECDSA or SK_ED25519). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_buffer_unpack() + */ +int pki_buffer_unpack_sk_priv_data(ssh_buffer buffer, ssh_key key) +{ + return ssh_buffer_unpack(buffer, + "SbSS", + &key->sk_application, + &key->sk_flags, + &key->sk_key_handle, + &key->sk_reserved); +} + +/** + * @internal + * + * @brief Create a key_blob from a public key. + * + * The "key_blob" is encoded as per RFC 4253 section 6.6 "Public Key + * Algorithms" for any of the supported protocol 2 key types. + * Encoding of EC keys is described in RFC 5656 section 3.1 "Key + * Format". + * + * @param[in] key A public or private key to create the public ssh_string + * from. + * + * @param[out] pblob A pointer to store the newly allocated key blob. You + * need to free it using ssh_string_free(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + * + * @see ssh_string_free() + */ +int ssh_pki_export_pubkey_blob(const ssh_key key, + ssh_string *pblob) +{ + ssh_string blob = NULL; + + if (key == NULL) { + return SSH_OK; + } + + blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); + if (blob == NULL) { + return SSH_ERROR; + } + + *pblob = blob; + return SSH_OK; +} + +/** + * @internal + * + * @brief Create a key_blob from a private key. + * + * The "key_blob" is encoded as per draft-miller-ssh-agent-08 section 4.2 + * "Adding keys to the agent" for any of the supported key types. + * + * @param[in] key A private key to create the private ssh_string from. + * + * @param[out] pblob A pointer to store the newly allocated key blob. You + * need to free it using ssh_string_free(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + * + * @see ssh_string_free() + */ +int ssh_pki_export_privkey_blob(const ssh_key key, + ssh_string *pblob) +{ + ssh_string blob = NULL; + + if (key == NULL) { + return SSH_OK; + } + + blob = pki_key_to_blob(key, SSH_KEY_PRIVATE); + if (blob == NULL) { + return SSH_ERROR; + } + + *pblob = blob; + return SSH_OK; +} + +/** + * @brief Convert a public key to a base64 encoded key. + * + * @param[in] key The key to hash + * + * @param[out] b64_key A pointer to store the allocated base64 encoded key. You + * need to free the buffer using ssh_string_free_char() + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key) +{ + ssh_string key_blob = NULL; + unsigned char *b64 = NULL; + + if (key == NULL || b64_key == NULL) { + return SSH_ERROR; + } + + key_blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); + if (key_blob == NULL) { + return SSH_ERROR; + } + + b64 = bin_to_base64(ssh_string_data(key_blob), ssh_string_len(key_blob)); + SSH_STRING_FREE(key_blob); + if (b64 == NULL) { + return SSH_ERROR; + } + + *b64_key = (char *)b64; + + return SSH_OK; +} + +/** + * @brief Export public key to file + * + * Exports the public key in AuthorizedKeysFile acceptable format. + * For more information see `man sshd` + * + * @param key A key to export + * + * @param filename The name of the output file + * + * @returns SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename) +{ + char key_buf[MAX_LINE_SIZE]; + char *host = NULL; + char *b64_key = NULL; + char *user = NULL; + FILE *fp = NULL; + int rc; + + if (key == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + + user = ssh_get_local_username(); + if (user == NULL) { + return SSH_ERROR; + } + + host = ssh_get_local_hostname(); + if (host == NULL) { + free(user); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + if (rc < 0) { + free(user); + free(host); + return SSH_ERROR; + } + + rc = snprintf(key_buf, sizeof(key_buf), + "%s %s %s@%s\n", + key->type_c, + b64_key, + user, + host); + free(user); + free(host); + free(b64_key); + if (rc < 0) { + return SSH_ERROR; + } + + fp = fopen(filename, "wb+"); + if (fp == NULL) { + return SSH_ERROR; + } + rc = fwrite(key_buf, strlen(key_buf), 1, fp); + if (rc != 1 || ferror(fp)) { + fclose(fp); + unlink(filename); + return SSH_ERROR; + } + fclose(fp); + + return SSH_OK; +} + +/** + * @brief Copy the certificate part of a public key into a private key. + * + * @param[in] certkey The certificate key. + * + * @param[in] privkey The target private key to copy the certificate to. + * + * @returns SSH_OK on success, SSH_ERROR otherwise. + **/ +int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) +{ + ssh_buffer cert_buffer = NULL; + int rc, cmp; + + if (certkey == NULL || privkey == NULL) { + return SSH_ERROR; + } + + if (privkey->cert != NULL) { + return SSH_ERROR; + } + + if (certkey->cert == NULL) { + return SSH_ERROR; + } + + /* make sure the public keys match */ + cmp = ssh_key_cmp(certkey, privkey, SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + return SSH_ERROR; + } + + cert_buffer = ssh_buffer_new(); + if (cert_buffer == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_buffer(cert_buffer, certkey->cert); + if (rc != 0) { + SSH_BUFFER_FREE(cert_buffer); + return SSH_ERROR; + } + + privkey->cert = cert_buffer; + privkey->cert_type = certkey->type; + return SSH_OK; +} + +int ssh_pki_export_signature_blob(const ssh_signature sig, + ssh_string *sig_blob) +{ + ssh_buffer buf = NULL; + ssh_string str = NULL; + int rc; + + if (sig == NULL || sig_blob == NULL) { + return SSH_ERROR; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + str = ssh_string_from_char(sig->type_c); + if (str == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(buf, str); + SSH_STRING_FREE(str); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + str = pki_signature_to_blob(sig); + if (str == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(buf, str); + SSH_STRING_FREE(str); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + if (is_sk_key_type(sig->type)) { + /* Add flags and counter for SK keys */ + rc = ssh_buffer_pack(buf, "bd", sig->sk_flags, sig->sk_counter); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + } + + str = ssh_string_new(ssh_buffer_get_len(buf)); + if (str == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + rc = ssh_string_fill(str, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); + SSH_BUFFER_FREE(buf); + if (rc < 0) { + SSH_STRING_FREE(str); + return SSH_ERROR; + } + + *sig_blob = str; + + return SSH_OK; +} + +int ssh_pki_import_signature_blob(const ssh_string sig_blob, + const ssh_key pubkey, + ssh_signature *psig) +{ + ssh_signature sig = NULL; + enum ssh_keytypes_e type; + enum ssh_digest_e hash_type; + ssh_string algorithm = NULL, blob = NULL; + ssh_buffer buf = NULL; + const char *alg = NULL; + uint8_t flags = 0; + uint32_t counter = 0; + int rc; + + if (sig_blob == NULL || psig == NULL) { + return SSH_ERROR; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_data(buf, + ssh_string_data(sig_blob), + (uint32_t)ssh_string_len(sig_blob)); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + algorithm = ssh_buffer_get_ssh_string(buf); + if (algorithm == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + alg = ssh_string_get_char(algorithm); + type = ssh_key_type_from_signature_name(alg); + hash_type = ssh_key_hash_from_name(alg); + SSH_STRING_FREE(algorithm); + + blob = ssh_buffer_get_ssh_string(buf); + if (blob == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + if (type == SSH_KEYTYPE_SK_ECDSA || + type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_unpack(buf, "bd", &flags, &counter); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + SSH_STRING_FREE(blob); + return SSH_ERROR; + } + } + SSH_BUFFER_FREE(buf); + + sig = pki_signature_from_blob(pubkey, blob, type, hash_type); + SSH_STRING_FREE(blob); + if (sig == NULL) { + return SSH_ERROR; + } + + /* Set SK specific values */ + sig->sk_flags = flags; + sig->sk_counter = counter; + + *psig = sig; + return SSH_OK; +} + +/** + * @internal + * + * @brief Check if the provided key can be used with the provided hash type for + * data signing or signature verification. + * + * @param[in] key The key to be checked. + * @param[in] hash_type The digest algorithm to be checked. + * + * @return SSH_OK if compatible; SSH_ERROR otherwise + */ +int pki_key_check_hash_compatible(ssh_key key, + enum ssh_digest_e hash_type) +{ + if (key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Null pointer provided as key to " + "pki_key_check_hash_compatible()"); + return SSH_ERROR; + } + + switch(key->type) { + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA: + if (hash_type == SSH_DIGEST_SHA1) { + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "SHA1 is not allowed in FIPS mode"); + return SSH_ERROR; + } else { + return SSH_OK; + } + } + + if (hash_type == SSH_DIGEST_SHA256 || + hash_type == SSH_DIGEST_SHA512) + { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + if (hash_type == SSH_DIGEST_SHA256) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + if (hash_type == SSH_DIGEST_SHA384) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + if (hash_type == SSH_DIGEST_SHA512) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + if (hash_type == SSH_DIGEST_AUTO) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + SSH_LOG(SSH_LOG_TRACE, "Unknown key type %d", key->type); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_TRACE, "Key type %d incompatible with hash type %d", + key->type, hash_type); + + return SSH_ERROR; +} + +/** + * @brief Prepare buffer for FIDO2/U2F security key signature verification + * + * This function creates a buffer containing the application hash, flags, + * counter, and input hash for FIDO/U2F key signature verification. + * + * @param key The SSH key containing sk_application + * @param sig The signature containing sk_flags and sk_counter + * @param input The input data to hash + * @param input_len Length of the input data + * @param sk_buffer_out Pointer to store the created buffer + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int pki_sk_signature_buffer_prepare(const ssh_key key, + const ssh_signature sig, + const unsigned char *input, + size_t input_len, + ssh_buffer *sk_buffer_out) +{ + ssh_buffer sk_buffer = NULL; + SHA256CTX ctx = NULL; + unsigned char application_hash[SHA256_DIGEST_LEN] = {0}; + unsigned char input_hash[SHA256_DIGEST_LEN] = {0}; + int rc, ret = SSH_ERROR; + + if (key == NULL || sig == NULL || input == NULL || sk_buffer_out == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter(s) provided to %s()", __func__); + return SSH_ERROR; + } + + *sk_buffer_out = NULL; + + /* Calculate application hash */ + ctx = sha256_init(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for application hash"); + return SSH_ERROR; + } + sha256_update(ctx, + ssh_string_data(key->sk_application), + ssh_string_len(key->sk_application)); + sha256_final(application_hash, ctx); + + /* Calculate input hash */ + ctx = sha256_init(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for input hash"); + goto out; + } + sha256_update(ctx, input, input_len); + sha256_final(input_hash, ctx); + + /* Create and pack the sk_buffer */ + sk_buffer = ssh_buffer_new(); + if (sk_buffer == NULL) { + goto out; + } + + rc = ssh_buffer_pack(sk_buffer, + "PbdP", + (size_t)SHA256_DIGEST_LEN, + application_hash, + sig->sk_flags, + sig->sk_counter, + (size_t)SHA256_DIGEST_LEN, + input_hash); + if (rc != SSH_OK) { + goto out; + } + + *sk_buffer_out = sk_buffer; + sk_buffer = NULL; + ret = SSH_OK; + +out: + SSH_BUFFER_FREE(sk_buffer); + ssh_burn(application_hash, SHA256_DIGEST_LEN); + ssh_burn(input_hash, SHA256_DIGEST_LEN); + + return ret; +} + +int ssh_pki_signature_verify(ssh_session session, + ssh_signature sig, + const ssh_key key, + const unsigned char *input, + size_t input_len) +{ + int rc; + bool allowed; + enum ssh_keytypes_e key_type; + + if (session == NULL || sig == NULL || key == NULL || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter(s) provided to %s()", __func__); + return SSH_ERROR; + } + key_type = ssh_key_type_plain(key->type); + + SSH_LOG(SSH_LOG_FUNCTIONS, + "Going to verify a %s type signature", + sig->type_c); + + if (key_type != sig->type) { + SSH_LOG(SSH_LOG_TRACE, + "Can not verify %s signature with %s key", + sig->type_c, key->type_c); + return SSH_ERROR; + } + + allowed = ssh_key_size_allowed(session, key); + if (!allowed) { + ssh_set_error(session, + SSH_FATAL, + "The '%s' key of size %d is not allowed by RSA_MIN_SIZE", + key->type_c, + ssh_key_size(key)); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(key, sig->hash_type); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + if (is_sk_key_type(key->type)) { + ssh_buffer sk_buffer = NULL; + + rc = pki_sk_signature_buffer_prepare(key, + sig, + input, + input_len, + &sk_buffer); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + rc = pki_verify_data_signature(sig, + key, + ssh_buffer_get(sk_buffer), + ssh_buffer_get_len(sk_buffer)); + SSH_BUFFER_FREE(sk_buffer); + return rc; + } + + return pki_verify_data_signature(sig, key, input, input_len); +} + +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *input, + size_t input_len, + enum ssh_digest_e hash_type) +{ + int rc; + + if (privkey == NULL || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_do_sign()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + return pki_sign_data(privkey, hash_type, input, input_len); +} + +/** + * @brief Encodes a binary signature blob as an sshsig armored signature + * + * @param blob The binary signature blob to encode + * @param out_str Pointer to store the allocated base64 encoded string + * Must be freed with ssh_string_free_char() + * + * @return SSH_OK on success, SSH_ERROR on error + */ +static int sshsig_armor(ssh_buffer blob, char **out_str) +{ + char *b64_data = NULL; + char *armored = NULL; + const unsigned char *data = NULL; + size_t len, b64_len, armored_len, num_lines; + size_t i, j; + + if (blob == NULL || out_str == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid input parameters"); + return SSH_ERROR; + } + + *out_str = NULL; + + data = ssh_buffer_get(blob); + len = ssh_buffer_get_len(blob); + + b64_data = (char *)bin_to_base64(data, len); + if (b64_data == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to base64 encode signature blob"); + return SSH_ERROR; + } + + b64_len = strlen(b64_data); + + /* Calculate space needed: header + data with line breaks + footer */ + num_lines = (b64_len + SSHSIG_LINE_LENGTH - 1) / + SSHSIG_LINE_LENGTH; /* Round up division */ + armored_len = strlen(SSHSIG_BEGIN_SIGNATURE) + 1 + /* header + \n */ + b64_len + num_lines + /* data + line breaks */ + strlen(SSHSIG_END_SIGNATURE) + 1; /* footer + \0 */ + + armored = calloc(armored_len, 1); + if (armored == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate %zu bytes for armored signature", + armored_len); + SAFE_FREE(b64_data); + return SSH_ERROR; + } + + j = snprintf(armored, armored_len, SSHSIG_BEGIN_SIGNATURE "\n"); + for (i = 0; i < b64_len; i++) { + if (i > 0 && i % SSHSIG_LINE_LENGTH == 0) { + armored[j++] = '\n'; + } + armored[j++] = b64_data[i]; + } + armored[j++] = '\n'; + snprintf(armored + j, armored_len - j, SSHSIG_END_SIGNATURE); + + SAFE_FREE(b64_data); + + *out_str = armored; + return SSH_OK; +} + +/** + * @brief Dearmor an sshsig signature from ASCII armored format to binary + * + * @param[in] signature The armored sshsig signature string + * @param[out] out Pointer to store the allocated binary buffer + * + * @return SSH_OK on success, SSH_ERROR on error + */ +static int sshsig_dearmor(const char *signature, ssh_buffer *out) +{ + const char *begin = NULL; + const char *end = NULL; + char *clean_b64 = NULL; + ssh_buffer decoded_buffer = NULL; + int i, j; + int rc = SSH_ERROR; + + if (signature == NULL || out == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid input parameters"); + return SSH_ERROR; + } + + *out = NULL; + + rc = strncmp(signature, + SSHSIG_BEGIN_SIGNATURE, + strlen(SSHSIG_BEGIN_SIGNATURE)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Signature does not start with expected header"); + return SSH_ERROR; + } + + begin = signature + strlen(SSHSIG_BEGIN_SIGNATURE); + while (isspace(*begin)) { + begin++; + } + + end = strstr(begin, SSHSIG_END_SIGNATURE); + if (end == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Signature end marker not found"); + return SSH_ERROR; + } + + /* Backtrack to find the real end of data */ + while (end > begin && (isspace(*(end - 1)))) { + end--; + } + + clean_b64 = calloc(end - begin + 1, 1); + if (clean_b64 == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate %td bytes for clean base64 data", + end - begin + 1); + return SSH_ERROR; + } + + for (i = 0, j = 0; begin + i < end; i++) { + if (!isspace(begin[i])) { + clean_b64[j++] = begin[i]; + } + } + clean_b64[j] = '\0'; + + decoded_buffer = base64_to_bin(clean_b64); + SAFE_FREE(clean_b64); + + if (decoded_buffer == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to decode base64 signature data"); + return SSH_ERROR; + } + + *out = decoded_buffer; + return SSH_OK; +} + +/** + * @internal + * @brief Common helper function to prepare the data in sshsig format + * + * This function handles the common logic to prepare the sshsig format: + * 1. Hash the input data using the specified algorithm + * 2. Build the data buffer to sign + * + * @param data The raw data to process + * @param data_length The length of the data + * @param hash_alg The hash algorithm to use (sha256 or sha512) + * @param sig_namespace The signature namespace + * @param tosign_buf Pointer to store the allocated to-sign buffer + * + * @return SSH_OK on success, SSH_ERROR on error + */ +static int sshsig_prepare_data(const void *data, + size_t data_length, + const char *hash_alg, + const char *sig_namespace, + ssh_buffer *tosign_buf) +{ + ssh_buffer tosign = NULL; + ssh_string hash_string = NULL; + char hash[SHA512_DIGEST_LEN]; + size_t hash_len; + int rc = SSH_ERROR; + + if (data == NULL || hash_alg == NULL || sig_namespace == NULL || + tosign_buf == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid input parameters"); + return SSH_ERROR; + } + + *tosign_buf = NULL; + + if (strcmp(hash_alg, "sha256") == 0) { + hash_len = SHA256_DIGEST_LEN; + rc = sha256(data, data_length, (unsigned char *)hash); + } else if (strcmp(hash_alg, "sha512") == 0) { + hash_len = SHA512_DIGEST_LEN; + rc = sha512(data, data_length, (unsigned char *)hash); + } else { + SSH_LOG(SSH_LOG_TRACE, "Unsupported hash algorithm: %s", hash_alg); + goto cleanup; + } + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to compute %s hash of data", hash_alg); + goto cleanup; + } + + hash_string = ssh_string_new(hash_len); + if (hash_string == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to allocate ssh_string for hash"); + goto cleanup; + } + + rc = ssh_string_fill(hash_string, hash, hash_len); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to fill ssh_string with hash data"); + goto cleanup; + } + + tosign = ssh_buffer_new(); + if (tosign == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to allocate buffer for signing data"); + goto cleanup; + } + + rc = ssh_buffer_pack(tosign, + "tsssS", + SSHSIG_MAGIC_PREAMBLE, + sig_namespace, + "", + hash_alg, + hash_string); + + if (rc == SSH_OK) { + *tosign_buf = tosign; + tosign = NULL; + } else { + SSH_LOG(SSH_LOG_TRACE, "Failed to pack signing data into buffer"); + } + +cleanup: + SSH_BUFFER_FREE(tosign); + SSH_STRING_FREE(hash_string); + + return rc; +} + +/** + * @brief Signs data in sshsig compatible format + * + * @param data The data to sign + * @param data_length The length of the data + * @param privkey The private key to sign with + * @param pki_context The PKI context. For non-SK keys, this parameter is + * ignored and can be NULL. For SK keys, can be NULL in + * which case a default context with default callbacks + * will be used. If provided, the context must have + * sk_callbacks set with a valid sign callback + * implementation. See ssh_pki_ctx_set_sk_callbacks(). + * @param sig_namespace The signature namespace (e.g. "file", "email", etc.) + * @param hash_alg The hash algorithm to use (SSHSIG_DIGEST_SHA2_256 or + * SSHSIG_DIGEST_SHA2_512) + * @param signature Pointer to store the allocated signature string in the + * armored format. Must be freed with + * ssh_string_free_char() + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int sshsig_sign(const void *data, + size_t data_length, + ssh_key privkey, + ssh_pki_ctx pki_context, + const char *sig_namespace, + enum sshsig_digest_e hash_alg, + char **signature) +{ + ssh_buffer tosign = NULL; + ssh_buffer signature_blob = NULL; + ssh_signature sig = NULL; + ssh_string sig_string = NULL; + ssh_string pub_blob = NULL; + ssh_pki_ctx temp_ctx = NULL; + ssh_pki_ctx ctx_to_use = NULL; + enum ssh_digest_e digest_type; + const char *hash_alg_str = NULL; + int rc = SSH_ERROR; + + if (privkey == NULL || data == NULL || sig_namespace == NULL || + signature == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid parameters provided to sshsig_sign"); + return SSH_ERROR; + } + + if (strlen(sig_namespace) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid parameters provided to sshsig_sign: empty namespace " + "string"); + return SSH_ERROR; + } + + /* Check if this is an SK key that requires a PKI context */ + if (is_sk_key_type(privkey->type)) { + /* If no context provided, create a temporary default one */ + if (pki_context == NULL) { + SSH_LOG(SSH_LOG_INFO, + "No PKI context provided, using the default one"); + + temp_ctx = ssh_pki_ctx_new(); + if (temp_ctx == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context"); + return SSH_ERROR; + } + ctx_to_use = temp_ctx; + } else { + ctx_to_use = pki_context; + } + + /* Verify that we have valid SK callbacks */ + if (ctx_to_use->sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Security Key callbacks not configured in PKI context"); + goto cleanup; + } + } + + *signature = NULL; + + if (hash_alg == SSHSIG_DIGEST_SHA2_256) { + hash_alg_str = "sha256"; + } else if (hash_alg == SSHSIG_DIGEST_SHA2_512) { + hash_alg_str = "sha512"; + } else { + SSH_LOG(SSH_LOG_TRACE, "Invalid hash algorithm %d", hash_alg); + return SSH_ERROR; + } + + rc = sshsig_prepare_data(data, + data_length, + hash_alg_str, + sig_namespace, + &tosign); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to prepare data for sshsig signing"); + goto cleanup; + } + + /* Use appropriate signing method based on key type */ + if (is_sk_key_type(privkey->type)) { +#ifdef WITH_FIDO2 + sig = pki_sk_do_sign(ctx_to_use, + privkey, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign)); +#else + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + goto cleanup; +#endif + } else { + digest_type = key_type_to_hash(ssh_key_type_plain(privkey->type)); + sig = pki_sign_data(privkey, + digest_type, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign)); + } + if (sig == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to sign data with private key"); + goto cleanup; + } + + rc = ssh_pki_export_pubkey_blob(privkey, &pub_blob); + if (rc != SSH_OK || pub_blob == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to export public key blob from private key"); + goto cleanup; + } + + rc = ssh_pki_export_signature_blob(sig, &sig_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export signature blob"); + goto cleanup; + } + + signature_blob = ssh_buffer_new(); + if (signature_blob == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to allocate signature buffer"); + goto cleanup; + } + + rc = ssh_buffer_pack(signature_blob, + "tdSsssS", + SSHSIG_MAGIC_PREAMBLE, + SSHSIG_VERSION, + pub_blob, + sig_namespace, + "", + hash_alg_str, + sig_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to pack signature blob"); + goto cleanup; + } + + rc = sshsig_armor(signature_blob, signature); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to armor signature blob"); + goto cleanup; + } + +cleanup: + SSH_BUFFER_FREE(tosign); + SSH_BUFFER_FREE(signature_blob); + SSH_SIGNATURE_FREE(sig); + SSH_STRING_FREE(sig_string); + SSH_STRING_FREE(pub_blob); + + /* Clean up temporary context if we created one */ + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + + return rc; +} + +/** + * @brief Verifies an sshsig formatted signature against data + * + * @param data The data to verify + * @param data_length The length of the data + * @param signature The armored sshsig signature + * @param sig_namespace The expected signature namespace + * @param sign_key If not NULL, returns the allocated public key that was + * used for signing this data. Must be freed with + * ssh_key_free(). Note that this is an output parameter + * and is not checked against "allowed signers". The + * caller needs to compare it with expected signer key + * using ssh_key_cmp(). + * + * @return SSH_OK on success, SSH_ERROR on verification failure + */ +int sshsig_verify(const void *data, + size_t data_length, + const char *signature, + const char *sig_namespace, + ssh_key *sign_key) +{ + ssh_buffer sig_buf = NULL; + ssh_buffer tosign = NULL; + ssh_key key = NULL; + char *hash_alg_str = NULL; + ssh_string sig_data = NULL; + ssh_string sig_namespace_str = NULL; + ssh_string reserved_str = NULL; + ssh_string pubkey_blob = NULL; + int rc = SSH_ERROR; + ssh_signature signature_obj = NULL; + uint32_t sig_version; + + if (sign_key != NULL) { + *sign_key = NULL; + } + + if (signature == NULL || data == NULL || sig_namespace == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid parameters provided to sshsig_verify"); + return SSH_ERROR; + } + + if (strlen(sig_namespace) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid parameters provided to sshsig_verify: empty namespace " + "string"); + return SSH_ERROR; + } + + rc = sshsig_dearmor(signature, &sig_buf); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to dearmor signature"); + return SSH_ERROR; + } + + if (ssh_buffer_get_len(sig_buf) < SSHSIG_MAGIC_PREAMBLE_LEN || + memcmp(ssh_buffer_get(sig_buf), + SSHSIG_MAGIC_PREAMBLE, + SSHSIG_MAGIC_PREAMBLE_LEN) != 0) { + SSH_LOG(SSH_LOG_TRACE, "Invalid signature magic preamble"); + SSH_BUFFER_FREE(sig_buf); + return SSH_ERROR; + } + + ssh_buffer_pass_bytes(sig_buf, SSHSIG_MAGIC_PREAMBLE_LEN); + rc = ssh_buffer_unpack(sig_buf, + "dSSSsS", + &sig_version, + &pubkey_blob, + &sig_namespace_str, + &reserved_str, + &hash_alg_str, + &sig_data); + + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to unpack signature buffer"); + SSH_BUFFER_FREE(sig_buf); + return SSH_ERROR; + } + + if (sig_version != SSHSIG_VERSION) { + SSH_LOG(SSH_LOG_TRACE, + "Unsupported signature version %u, expected %u", + sig_version, + SSHSIG_VERSION); + rc = SSH_ERROR; + goto cleanup; + } + + rc = ssh_pki_import_pubkey_blob(pubkey_blob, &key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key from signature"); + goto cleanup; + } + + if (ssh_string_len(sig_namespace_str) != strlen(sig_namespace) || + memcmp(ssh_string_data(sig_namespace_str), + sig_namespace, + strlen(sig_namespace)) != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Signature namespace mismatch: expected '%s', got '%s'", + sig_namespace, + ssh_string_get_char(sig_namespace_str)); + rc = SSH_ERROR; + goto cleanup; + } + + if (strcmp(hash_alg_str, "sha256") != 0 && + strcmp(hash_alg_str, "sha512") != 0) { + SSH_LOG(SSH_LOG_TRACE, "Unsupported hash algorithm '%s'", hash_alg_str); + rc = SSH_ERROR; + goto cleanup; + } + + rc = sshsig_prepare_data(data, + data_length, + hash_alg_str, + sig_namespace, + &tosign); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to prepare data for sshsig verification"); + goto cleanup; + } + + rc = ssh_pki_import_signature_blob(sig_data, key, &signature_obj); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import signature blob"); + goto cleanup; + } + + if (is_sk_key_type(key->type)) { + ssh_buffer sk_buffer = NULL; + rc = pki_sk_signature_buffer_prepare(key, + signature_obj, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign), + &sk_buffer); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to prepare sk signature buffer"); + goto cleanup; + } + + rc = pki_verify_data_signature(signature_obj, + key, + ssh_buffer_get(sk_buffer), + ssh_buffer_get_len(sk_buffer)); + SSH_BUFFER_FREE(sk_buffer); + } else { + rc = pki_verify_data_signature(signature_obj, + key, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign)); + } + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Signature verification failed"); + goto cleanup; + } + + if (sign_key != NULL) { + *sign_key = key; + key = NULL; /* Transferred ownership */ + } + +cleanup: + SSH_STRING_FREE(pubkey_blob); + SSH_STRING_FREE(sig_namespace_str); + SSH_STRING_FREE(reserved_str); + SSH_STRING_FREE(sig_data); + SSH_BUFFER_FREE(tosign); + SSH_BUFFER_FREE(sig_buf); + SSH_KEY_FREE(key); + SAFE_FREE(hash_alg_str); + SSH_SIGNATURE_FREE(signature_obj); + + return rc; +} + +/* + * This function signs the session id as a string then + * the content of sigbuf */ +ssh_string ssh_pki_do_sign(ssh_session session, + ssh_buffer sigbuf, + const ssh_key privkey, + enum ssh_digest_e hash_type) +{ + struct ssh_crypto_struct *crypto = NULL; + + ssh_signature sig = NULL; + ssh_string sig_blob = NULL; + + ssh_string session_id = NULL; + ssh_buffer sign_input = NULL; + + int rc; + + if (session == NULL || sigbuf == NULL || privkey == NULL || + !ssh_key_is_private(privkey)) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "ssh_pki_do_sign()"); + return NULL; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return NULL; + } + + /* Get the session ID */ + session_id = ssh_string_new(crypto->session_id_len); + if (session_id == NULL) { + return NULL; + } + rc = ssh_string_fill(session_id, crypto->session_id, crypto->session_id_len); + if (rc < 0) { + goto end; + } + + /* Fill the input */ + sign_input = ssh_buffer_new(); + if (sign_input == NULL) { + goto end; + } + ssh_buffer_set_secure(sign_input); + + rc = ssh_buffer_pack(sign_input, + "SP", + session_id, + (size_t)ssh_buffer_get_len(sigbuf), + ssh_buffer_get(sigbuf)); + if (rc != SSH_OK) { + goto end; + } + + /* Generate the signature */ + if (is_sk_key_type(privkey->type)) { +#ifdef WITH_FIDO2 + if (session->pki_context == NULL || + session->pki_context->sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Missing PKI context or SK callbacks"); + goto end; + } + + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Incompatible hash type %d for sk key type %d", + hash_type, + privkey->type); + goto end; + } + + sig = pki_sk_do_sign(session->pki_context, + privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input)); +#else + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + goto end; +#endif /* WITH_FIDO2 */ + } else { + sig = pki_do_sign(privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + hash_type); + } + + if (sig == NULL) { + goto end; + } + + /* Convert the signature to blob */ + rc = ssh_pki_export_signature_blob(sig, &sig_blob); + if (rc < 0) { + sig_blob = NULL; + } + +end: + ssh_signature_free(sig); + SSH_BUFFER_FREE(sign_input); + SSH_STRING_FREE(session_id); + + return sig_blob; +} + +ssh_string ssh_pki_do_sign_agent(ssh_session session, + struct ssh_buffer_struct *buf, + const ssh_key pubkey) +{ + struct ssh_crypto_struct *crypto = NULL; + ssh_string session_id = NULL; + ssh_string sig_blob = NULL; + ssh_buffer sig_buf = NULL; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return NULL; + } + + /* prepend session identifier */ + session_id = ssh_string_new(crypto->session_id_len); + if (session_id == NULL) { + return NULL; + } + rc = ssh_string_fill(session_id, crypto->session_id, crypto->session_id_len); + if (rc < 0) { + SSH_STRING_FREE(session_id); + return NULL; + } + + sig_buf = ssh_buffer_new(); + if (sig_buf == NULL) { + SSH_STRING_FREE(session_id); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(sig_buf, session_id); + if (rc < 0) { + SSH_STRING_FREE(session_id); + SSH_BUFFER_FREE(sig_buf); + return NULL; + } + SSH_STRING_FREE(session_id); + + /* append out buffer */ + if (ssh_buffer_add_buffer(sig_buf, buf) < 0) { + SSH_BUFFER_FREE(sig_buf); + return NULL; + } + + /* create signature */ + sig_blob = ssh_agent_sign_data(session, pubkey, sig_buf); + + SSH_BUFFER_FREE(sig_buf); + + return sig_blob; +} + +#ifdef WITH_SERVER +ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, + const ssh_key privkey, + const enum ssh_digest_e digest) +{ + struct ssh_crypto_struct *crypto = NULL; + bool allowed; + ssh_signature sig = NULL; + ssh_string sig_blob = NULL; + + ssh_buffer sign_input = NULL; + + int rc; + + if (session == NULL || privkey == NULL || !ssh_key_is_private(privkey)) { + return NULL; + } + + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, SSH_FATAL, "The hostkey size too small"); + return NULL; + } + + crypto = session->next_crypto ? session->next_crypto : + session->current_crypto; + + if (crypto->secret_hash == NULL){ + ssh_set_error(session, SSH_FATAL, "Missing secret_hash"); + return NULL; + } + + /* Fill the input */ + sign_input = ssh_buffer_new(); + if (sign_input == NULL) { + goto end; + } + ssh_buffer_set_secure(sign_input); + + rc = ssh_buffer_pack(sign_input, + "P", + crypto->digest_len, + crypto->secret_hash); + if (rc != SSH_OK) { + goto end; + } + + /* Generate the signature */ + sig = pki_do_sign(privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + digest); + if (sig == NULL) { + goto end; + } + + /* Convert the signature to blob */ + rc = ssh_pki_export_signature_blob(sig, &sig_blob); + if (rc < 0) { + sig_blob = NULL; + } + +end: + ssh_signature_free(sig); + SSH_BUFFER_FREE(sign_input); + + return sig_blob; +} +#endif /* WITH_SERVER */ + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/pki_container_openssh.c b/src/libs/libssh-0.12.2/src/pki_container_openssh.c new file mode 100644 index 000000000000..3d782b93a0ed --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_container_openssh.c @@ -0,0 +1,696 @@ +/* + * pki_container_openssh.c + * This file is part of the SSH Library + * + * Copyright (c) 2013,2014 Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** + * @ingroup libssh_pki + * * + * @{ + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/buffer.h" + + +/** + * @internal + * + * @brief Import a private key from a ssh buffer. + * + * @param[in] key_blob_buffer The key blob to import as specified in + * key.c:key_private_serialize in OpenSSH source + * code. + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +static int pki_openssh_import_privkey_blob(ssh_buffer key_blob_buffer, + ssh_key *pkey) +{ + enum ssh_keytypes_e type; + char *type_s = NULL; + ssh_key key = NULL; + int rc; + + if (pkey == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_unpack(key_blob_buffer, "s", &type_s); + if (rc == SSH_ERROR){ + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + return SSH_ERROR; + } + + type = ssh_key_type_from_name(type_s); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Unknown key type '%s' found!", type_s); + return SSH_ERROR; + } + SAFE_FREE(type_s); + + rc = pki_import_privkey_buffer(type, key_blob_buffer, &key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to read key in OpenSSH format"); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +/** + * @brief decrypts an encrypted private key blob in OpenSSH format. + * + */ +static int pki_private_key_decrypt(ssh_string blob, + const char* passphrase, + const char *ciphername, + const char *kdfname, + ssh_string kdfoptions, + ssh_auth_callback auth_fn, + void *auth_data) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + struct ssh_cipher_struct cipher; + uint8_t key_material[128] = {0}; + char passphrase_buffer[128] = {0}; + size_t key_material_len; + ssh_buffer buffer = NULL; + ssh_string salt = NULL; + uint32_t rounds; + int cmp; + int rc; + int i; + + cmp = strcmp(ciphername, "none"); + if (cmp == 0){ + /* no decryption required */ + return SSH_OK; + } + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0){ + memcpy(&cipher, &ciphers[i], sizeof(cipher)); + break; + } + } + + if (ciphers[i].name == NULL){ + SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); + return SSH_ERROR; + } + + cmp = strcmp(kdfname, "bcrypt"); + if (cmp != 0) { + SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); + return SSH_ERROR; + } + if (ssh_string_len(blob) % cipher.blocksize != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Encrypted string not multiple of blocksize: %zu", + ssh_string_len(blob)); + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL){ + return SSH_ERROR; + } + rc = ssh_buffer_add_data(buffer, + ssh_string_data(kdfoptions), + (uint32_t)ssh_string_len(kdfoptions)); + if (rc != SSH_ERROR){ + rc = ssh_buffer_unpack(buffer, "Sd", &salt, &rounds); + } + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR){ + return SSH_ERROR; + } + + /* We need material for key (keysize bits / 8) and IV (blocksize) */ + key_material_len = cipher.keysize/8 + cipher.blocksize; + if (key_material_len > sizeof(key_material)) { + SSH_LOG(SSH_LOG_TRACE, "Key material too big"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Decryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", + cipher.keysize/8, + cipher.blocksize, + rounds, + ssh_string_len(salt)); + + if (passphrase == NULL) { + if (auth_fn == NULL) { + SAFE_FREE(salt); + SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); + return SSH_ERROR; + } + rc = auth_fn("Passphrase", + passphrase_buffer, + sizeof(passphrase_buffer), + 0, + 0, + auth_data); + if (rc != SSH_OK) { + SAFE_FREE(salt); + return SSH_ERROR; + } + passphrase = passphrase_buffer; + } + + rc = bcrypt_pbkdf(passphrase, + strlen(passphrase), + ssh_string_data(salt), + ssh_string_len(salt), + key_material, + key_material_len, + rounds); + SAFE_FREE(salt); + if (rc < 0){ + return SSH_ERROR; + } + ssh_burn(passphrase_buffer, sizeof(passphrase_buffer)); + + cipher.set_decrypt_key(&cipher, + key_material, + key_material + cipher.keysize/8); + cipher.decrypt(&cipher, + ssh_string_data(blob), + ssh_string_data(blob), + ssh_string_len(blob)); + ssh_cipher_clear(&cipher); + return SSH_OK; +} + + +/** @internal + * @brief Import a private key in OpenSSH (new) format. This format is + * typically used with ed25519 keys but can be used for others. + */ +static ssh_key +ssh_pki_openssh_import(const char *text_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + bool private) +{ + const char *ptr = text_key; + const char *end = NULL; + char *base64 = NULL; + int cmp; + int rc; + int i; + ssh_buffer buffer = NULL, privkey_buffer = NULL; + char *magic = NULL, *ciphername = NULL, *kdfname = NULL; + uint32_t nkeys = 0, checkint1 = 0, checkint2 = 0xFFFF; + ssh_string kdfoptions = NULL; + ssh_string pubkey0 = NULL; + ssh_string privkeys = NULL; + ssh_string comment = NULL; + ssh_key key = NULL; + uint8_t padding; + + cmp = strncmp(ptr, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); + if (cmp != 0) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no header)"); + goto out; + } + ptr += strlen(OPENSSH_HEADER_BEGIN); + while(ptr[0] != '\0' && !isspace((int)ptr[0])) { + ptr++; + } + end = strstr(ptr, OPENSSH_HEADER_END); + if (end == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no footer)"); + goto out; + } + base64 = malloc(end - ptr + 1); + if (base64 == NULL) { + goto out; + } + for (i = 0; ptr < end; ptr++) { + if (!isspace((int)ptr[0])) { + base64[i] = ptr[0]; + i++; + } + } + base64[i] = '\0'; + buffer = base64_to_bin(base64); + SAFE_FREE(base64); + if (buffer == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (base64 error)"); + goto out; + } + rc = ssh_buffer_unpack(buffer, "PssSdSS", + strlen(OPENSSH_AUTH_MAGIC) + 1, + &magic, + &ciphername, + &kdfname, + &kdfoptions, + &nkeys, + &pubkey0, + &privkeys); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (unpack error)"); + goto out; + } + cmp = strncmp(magic, OPENSSH_AUTH_MAGIC, strlen(OPENSSH_AUTH_MAGIC)); + if (cmp != 0) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (bad magic)"); + goto out; + } + SSH_LOG(SSH_LOG_DEBUG, + "Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %" PRIu32, + ciphername, + kdfname, + nkeys); + if (nkeys != 1) { + SSH_LOG(SSH_LOG_TRACE, "Opening OpenSSH private key: only 1 key supported (%" PRIu32 " available)", nkeys); + goto out; + } + + /* If we are interested only in public key do not progress + * to the key decryption later + */ + if (!private) { + rc = ssh_pki_import_pubkey_blob(pubkey0, &key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key blob"); + } + /* in either case we clean up here */ + goto out; + } + + rc = pki_private_key_decrypt(privkeys, + passphrase, + ciphername, + kdfname, + kdfoptions, + auth_fn, + auth_data); + if (rc == SSH_ERROR) { + goto out; + } + + privkey_buffer = ssh_buffer_new(); + if (privkey_buffer == NULL) { + goto out; + } + + ssh_buffer_set_secure(privkey_buffer); + ssh_buffer_add_data(privkey_buffer, + ssh_string_data(privkeys), + (uint32_t)ssh_string_len(privkeys)); + + rc = ssh_buffer_unpack(privkey_buffer, "dd", &checkint1, &checkint2); + if (rc == SSH_ERROR || checkint1 != checkint2) { + SSH_LOG(SSH_LOG_TRACE, "OpenSSH private key unpack error (correct password?)"); + goto out; + } + rc = pki_openssh_import_privkey_blob(privkey_buffer, &key); + if (rc == SSH_ERROR) { + goto out; + } + comment = ssh_buffer_get_ssh_string(privkey_buffer); + SAFE_FREE(comment); + /* verify that the remaining data is correct padding */ + for (i = 1; ssh_buffer_get_len(privkey_buffer) > 0; ++i) { + ssh_buffer_get_u8(privkey_buffer, &padding); + if (padding != i) { + ssh_key_free(key); + key = NULL; + SSH_LOG(SSH_LOG_TRACE, "Invalid padding"); + goto out; + } + } +out: + if (buffer != NULL) { + SSH_BUFFER_FREE(buffer); + buffer = NULL; + } + if (privkey_buffer != NULL) { + SSH_BUFFER_FREE(privkey_buffer); + privkey_buffer = NULL; + } + SAFE_FREE(magic); + SAFE_FREE(ciphername); + SAFE_FREE(kdfname); + SAFE_FREE(kdfoptions); + SAFE_FREE(pubkey0); + SAFE_FREE(privkeys); + return key; +} + +ssh_key ssh_pki_openssh_privkey_import(const char *text_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + return ssh_pki_openssh_import(text_key, passphrase, auth_fn, auth_data, true); +} + +ssh_key ssh_pki_openssh_pubkey_import(const char *text_key) +{ + return ssh_pki_openssh_import(text_key, NULL, NULL, NULL, false); +} + + +/** @internal + * @brief encrypts an ed25519 private key blob + * + */ +static int pki_private_key_encrypt(ssh_buffer privkey_buffer, + const char* passphrase, + const char *ciphername, + const char *kdfname, + ssh_auth_callback auth_fn, + void *auth_data, + uint32_t rounds, + ssh_string salt) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + struct ssh_cipher_struct cipher; + uint8_t key_material[128] = {0}; + size_t key_material_len; + char passphrase_buffer[128] = {0}; + int rc; + int i; + int cmp; + + cmp = strcmp(ciphername, "none"); + if (cmp == 0){ + /* no encryption required */ + return SSH_OK; + } + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0){ + memcpy(&cipher, &ciphers[i], sizeof(cipher)); + break; + } + } + + if (ciphers[i].name == NULL){ + SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); + return SSH_ERROR; + } + + cmp = strcmp(kdfname, "bcrypt"); + if (cmp != 0){ + SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); + return SSH_ERROR; + } + /* We need material for key (keysize bits / 8) and IV (blocksize) */ + key_material_len = cipher.keysize/8 + cipher.blocksize; + if (key_material_len > sizeof(key_material)){ + SSH_LOG(SSH_LOG_TRACE, "Key material too big"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, "Encryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", + cipher.keysize/8, + cipher.blocksize, rounds, ssh_string_len(salt)); + + if (passphrase == NULL){ + if (auth_fn == NULL){ + SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); + return SSH_ERROR; + } + rc = auth_fn("Passphrase", + passphrase_buffer, + sizeof(passphrase_buffer), + 0, + 0, + auth_data); + if (rc != SSH_OK){ + return SSH_ERROR; + } + passphrase = passphrase_buffer; + } + + rc = bcrypt_pbkdf(passphrase, + strlen(passphrase), + ssh_string_data(salt), + ssh_string_len(salt), + key_material, + key_material_len, + rounds); + if (rc < 0){ + return SSH_ERROR; + } + + cipher.set_encrypt_key(&cipher, + key_material, + key_material + cipher.keysize/8); + cipher.encrypt(&cipher, + ssh_buffer_get(privkey_buffer), + ssh_buffer_get(privkey_buffer), + ssh_buffer_get_len(privkey_buffer)); + ssh_cipher_clear(&cipher); + ssh_burn(passphrase_buffer, sizeof(passphrase_buffer)); + + return SSH_OK; +} + + +/** @internal + * generate an OpenSSH private key (defined in PROTOCOL.key) and output it in text format. + * @param privkey[in] private key to export + * @returns an SSH string containing the text representation of the exported key. + * @warning currently only supports ED25519 key types. + */ + +ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + ssh_buffer buffer = NULL; + ssh_string str = NULL, blob = NULL; + ssh_string pubkey_s = NULL; + ssh_buffer privkey_buffer = NULL; + uint32_t rnd; + uint32_t rounds = 16; + ssh_string salt = NULL; + ssh_string kdf_options = NULL; + int to_encrypt=0; + unsigned char *b64 = NULL; + uint32_t str_len, len; + uint8_t padding = 1; + int ok; + int rc; + + if (privkey == NULL) { + return NULL; + } + if (passphrase != NULL || auth_fn != NULL){ + SSH_LOG(SSH_LOG_DEBUG, "Enabling encryption for private key export"); + to_encrypt = 1; + } + buffer = ssh_buffer_new(); + rc = ssh_pki_export_pubkey_blob(privkey, &pubkey_s); + if (buffer == NULL || rc != SSH_OK) { + goto error; + } + + ok = ssh_get_random(&rnd, sizeof(rnd), 0); + if (!ok) { + goto error; + } + + privkey_buffer = ssh_buffer_new(); + if (privkey_buffer == NULL) { + goto error; + } + + rc = ssh_pki_export_privkey_blob(privkey, &blob); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_buffer_pack(privkey_buffer, + "ddPs", + rnd, /* checkint 1 & 2 */ + rnd, + ssh_string_len(blob), + ssh_string_data(blob), + "" /* comment */); + if (rc == SSH_ERROR){ + goto error; + } + + /* Add padding regardless encryption because it is expected + * by OpenSSH tools. + * XXX Using 16 B as we use only AES cipher below anyway. + */ + while (ssh_buffer_get_len(privkey_buffer) % 16 != 0) { + rc = ssh_buffer_add_u8(privkey_buffer, padding); + if (rc < 0) { + goto error; + } + padding++; + } + + if (to_encrypt){ + ssh_buffer kdf_buf; + + kdf_buf = ssh_buffer_new(); + if (kdf_buf == NULL) { + goto error; + } + + salt = ssh_string_new(16); + if (salt == NULL){ + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + + ok = ssh_get_random(ssh_string_data(salt), 16, 0); + if (!ok) { + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + + rc = ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); + if (rc != SSH_OK) { + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + kdf_options = ssh_string_new(ssh_buffer_get_len(kdf_buf)); + if (kdf_options == NULL){ + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + memcpy(ssh_string_data(kdf_options), + ssh_buffer_get(kdf_buf), + ssh_buffer_get_len(kdf_buf)); + SSH_BUFFER_FREE(kdf_buf); + rc = pki_private_key_encrypt(privkey_buffer, + passphrase, + "aes128-cbc", + "bcrypt", + auth_fn, + auth_data, + rounds, + salt); + if (rc != SSH_OK){ + goto error; + } + } else { + kdf_options = ssh_string_new(0); + } + + rc = ssh_buffer_pack(buffer, + "PssSdSdP", + strlen(OPENSSH_AUTH_MAGIC) + 1, + OPENSSH_AUTH_MAGIC, + to_encrypt ? "aes128-cbc" : "none", /* ciphername */ + to_encrypt ? "bcrypt" : "none", /* kdfname */ + kdf_options, /* kdfoptions */ + (uint32_t)1, /* nkeys */ + pubkey_s, + ssh_buffer_get_len(privkey_buffer), + /* rest of buffer is a string */ + (size_t)ssh_buffer_get_len(privkey_buffer), + ssh_buffer_get(privkey_buffer)); + if (rc != SSH_OK) { + goto error; + } + + b64 = bin_to_base64(ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (b64 == NULL){ + goto error; + } + + /* we can reuse the buffer */ + ssh_buffer_reinit(buffer); + rc = ssh_buffer_pack(buffer, + "tttttt", + OPENSSH_HEADER_BEGIN, + "\n", + b64, + "\n", + OPENSSH_HEADER_END, + "\n"); + ssh_burn(b64, strlen((char *)b64)); + SAFE_FREE(b64); + + if (rc != SSH_OK){ + goto error; + } + + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL){ + goto error; + } + + str_len = ssh_buffer_get_len(buffer); + len = ssh_buffer_get_data(buffer, ssh_string_data(str), str_len); + if (str_len != len) { + SSH_STRING_FREE(str); + str = NULL; + } + +error: + ssh_string_burn(blob); + ssh_string_free(blob); + if (privkey_buffer != NULL) { + void *bufptr = ssh_buffer_get(privkey_buffer); + ssh_burn(bufptr, ssh_buffer_get_len(privkey_buffer)); + SSH_BUFFER_FREE(privkey_buffer); + } + SAFE_FREE(pubkey_s); + SAFE_FREE(kdf_options); + SAFE_FREE(salt); + if (buffer != NULL) { + SSH_BUFFER_FREE(buffer); + } + + return str; +} + + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/pki_context.c b/src/libs/libssh-0.12.2/src/pki_context.c new file mode 100644 index 000000000000..6cd78fbfbeba --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_context.c @@ -0,0 +1,581 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/libssh.h" +#include "libssh/pki.h" +#include "libssh/pki_context.h" +#include "libssh/priv.h" +#include "libssh/sk_common.h" + +#ifdef WITH_FIDO2 +#include "libssh/buffer.h" +#include "libssh/callbacks.h" +#include "libssh/sk_api.h" +#endif /* WITH_FIDO2 */ + +/** + * @addtogroup libssh_pki + * @{ + */ + +/** + * @brief Allocate a new generic PKI context container. + * + * Allocates and default-initializes a new ssh_pki_ctx instance. + * + * @return Newly allocated context on success, or NULL on allocation failure. + * @see ssh_pki_ctx_free() + */ +ssh_pki_ctx ssh_pki_ctx_new(void) +{ + struct ssh_pki_ctx_struct *ctx = NULL; + + ctx = calloc(1, sizeof(struct ssh_pki_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + +#ifdef WITH_FIDO2 + /* Initialize SK fields with default, if available. */ + ctx->sk_callbacks = ssh_sk_get_default_callbacks(); + + /* + * Both OpenSSH security key enrollment and server authentication require + * user presence by default, so we replicate that for consistency. + */ + ctx->sk_flags = SSH_SK_USER_PRESENCE_REQD; + + ctx->sk_application = strdup("ssh:"); + if (ctx->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for default application"); + SAFE_FREE(ctx); + return NULL; + } +#endif /* WITH_FIDO2 */ + + return ctx; +} + +/** + * @brief Free a generic PKI context container. + * + * @param[in] context The PKI context to free (may be NULL). + * @see ssh_pki_ctx_new() + */ +void ssh_pki_ctx_free(ssh_pki_ctx context) +{ + if (context == NULL) { + return; + } + +#ifdef WITH_FIDO2 + SAFE_FREE(context->sk_application); + SSH_BUFFER_FREE(context->sk_challenge_buffer); + SSH_BUFFER_FREE(context->sk_attestation_buffer); + SK_OPTIONS_FREE(context->sk_callbacks_options); +#endif /* WITH_FIDO2 */ + + SAFE_FREE(context); +} + +/** + * @brief Set various options for a PKI context. + * + * This function can set all possible PKI context options. + * + * @param[in] context Target PKI context. + * @param option The option type to set. This could be one of the following: + * + * - SSH_PKI_OPTION_RSA_KEY_SIZE (int): + * Set the RSA key size in bits for key generation. + * Typically 2048, 3072, or 4096 bits. Must be greater + * than or equal to 1024, as anything below is considered + * insecure. + * + * - SSH_PKI_OPTION_SK_APPLICATION (const char *): + * The Relying Party identifier (application string) that + * determines which service/domain this security key + * credential will be associated with. This is a required + * field for all security key generation operations. + * The application string typically starts with "ssh:" for + * SSH keys. It is copied internally and can be freed + * after setting. + * + * - SSH_PKI_SK_OPTION_FLAGS (uint8_t): + * Set FIDO2/U2F operation flags that control how the FIDO2/U2F + * authenticator behaves during generation operations. Multiple + * flags can be combined using bitwise OR operations. The + * pointer must not be NULL. + * + * Available flags: + * + * SSH_SK_USER_PRESENCE_REQD: Requires user presence + * + * SSH_SK_USER_VERIFICATION_REQD: Requires user verification + * + * SSH_SK_FORCE_OPERATION: Forces generation even if a + * resident key already exists. + * + * SSH_SK_RESIDENT_KEY: Creates a resident + * key stored on the authenticator. + * + * - SSH_PKI_OPTION_SK_USER_ID (const char *): + * Sets the user identifier to associate with a resident + * credential during enrollment. When a resident key is + * requested (SSH_SK_RESIDENT_KEY), this ID is stored on the + * authenticator and later used to look up or prevent duplicate + * credentials. Maximum length is SK_MAX_USER_ID_LEN bytes; + * longer values will cause the operation to fail. + * + * - SSH_PKI_OPTION_SK_CHALLENGE (ssh_buffer): + * Set custom cryptographic challenge data to be included in + * the generation operation. The challenge is signed by the + * authenticator during key generation. If not provided, + * a random 32-byte challenge will be automatically generated. + * The challenge data is copied internally and the caller + * retains ownership of the provided buffer. + * + * - SSH_PKI_OPTION_SK_CALLBACKS (ssh_sk_callbacks): + * Set the security key callback structure to use custom + * callback functions for FIDO2/U2F operations like enrollment, + * signing, and loading resident keys. The structure is not + * copied so it needs to be valid for the whole context + * lifetime or until replaced. + * + * @param value The value to set. This is a generic pointer and the + * datatype which is used should be set according to the + * option type. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @warning When the option value to set is represented via a pointer + * (e.g const char *, ssh_buffer), the value parameter + * should be that pointer. Do NOT pass a pointer to a + * pointer. + * + * @warning When the option value to set is not a pointer (e.g int, + * uint8_t), the value parameter should be a pointer to the + * location storing the value to set (int *, uint8_t *). + */ +int ssh_pki_ctx_options_set(ssh_pki_ctx context, + enum ssh_pki_options_e option, + const void *value) +{ + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid PKI context passed"); + return SSH_ERROR; + } + + switch (option) { + case SSH_PKI_OPTION_RSA_KEY_SIZE: + if (value == NULL) { + SSH_LOG(SSH_LOG_WARN, "RSA key size pointer must not be NULL"); + return SSH_ERROR; + } else if (*(int *)value != 0 && *(int *)value <= RSA_MIN_KEY_SIZE) { + SSH_LOG( + SSH_LOG_WARN, + "RSA key size must be greater than %d bits or 0 for default", + RSA_MIN_KEY_SIZE); + return SSH_ERROR; + } + context->rsa_key_size = *(int *)value; + break; + +#ifdef WITH_FIDO2 + case SSH_PKI_OPTION_SK_APPLICATION: + SAFE_FREE(context->sk_application); + if (value != NULL) { + context->sk_application = strdup((char *)value); + if (context->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for application"); + return SSH_ERROR; + } + } + break; + + case SSH_PKI_OPTION_SK_FLAGS: + if (value == NULL) { + return SSH_ERROR; + } else { + context->sk_flags = *(uint8_t *)value; + } + break; + + case SSH_PKI_OPTION_SK_USER_ID: { + int rc; + + /* + * Set required to false, because only the enrollment callback supports + * the user ID option, and if this context is used for any other + * operation, it would fail unnecessarily. + */ + rc = ssh_pki_ctx_sk_callbacks_option_set(context, + SSH_SK_OPTION_NAME_USER_ID, + value, + false); + if (rc != SSH_OK) { + return SSH_ERROR; + } + break; + } + + case SSH_PKI_OPTION_SK_CHALLENGE: { + SSH_BUFFER_FREE(context->sk_challenge_buffer); + if (value == NULL) { + break; + } + + context->sk_challenge_buffer = ssh_buffer_dup((ssh_buffer)value); + if (context->sk_challenge_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to duplicate challenge buffer"); + return SSH_ERROR; + } + ssh_buffer_set_secure(context->sk_challenge_buffer); + break; + } + + case SSH_PKI_OPTION_SK_CALLBACKS: { + bool is_compatible = sk_callbacks_check_compatibility(value); + if (!is_compatible) { + return SSH_ERROR; + } + context->sk_callbacks = value; + break; + } +#else /* WITH_FIDO2 */ + case SSH_PKI_OPTION_SK_APPLICATION: + case SSH_PKI_OPTION_SK_FLAGS: + case SSH_PKI_OPTION_SK_USER_ID: + case SSH_PKI_OPTION_SK_CHALLENGE: + case SSH_PKI_OPTION_SK_CALLBACKS: + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ + + default: + SSH_LOG(SSH_LOG_WARN, "Unknown PKI context option: %d", option); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Set the PIN callback function to get the PIN for security + * key authenticator access. + * + * @param context The PKI context to modify. + * @param pin_callback The callback used when the authenticator requires PIN + * entry for verification. + * @param userdata A generic pointer that is passed as the userdata + * argument to the callback function. Can be NULL. + * + * @return SSH_OK on success, SSH_ERROR if context is NULL. + * + * @note The callback and userdata are stored internally in the context + * structure and must remain valid until the context is freed or + * replaced. + * + * @see ssh_auth_callback + */ +int ssh_pki_ctx_set_sk_pin_callback(ssh_pki_ctx context, + ssh_auth_callback pin_callback, + void *userdata) +{ +#ifdef WITH_FIDO2 + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context should not be NULL"); + return SSH_ERROR; + } + + context->sk_pin_callback = pin_callback; + context->sk_userdata = userdata; + + return SSH_OK; + +#else + (void)context; + (void)pin_callback; + (void)userdata; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Set a security key (FIDO2/U2F) callback option in the + * context. These options are passed to the sk_callbacks during + * enroll/sign/load_resident_keys operations. + * + * Both the name and value strings are duplicated internally so the caller + * retains ownership of the original pointers. + * + * @param[in] context The PKI context. Must not be NULL. + * @param[in] name option name string. Must not be NULL. + * @param[in] value option value string. Must not be NULL. + * @param[in] required Set to true if the option is mandatory. If set and the + * ssh_sk_callbacks do not recognize the option, + * the operation should fail. + * + * @return SSH_OK on success, SSH_ERROR on allocation failure or invalid args. + * + * @note The option objects are freed automatically when the context is freed + * via ssh_pki_sk_ctx_free(). + * + * @see ssh_sk_callbacks_struct + */ +int ssh_pki_ctx_sk_callbacks_option_set(ssh_pki_ctx context, + const char *name, + const char *value, + bool required) +{ +#ifdef WITH_FIDO2 + struct sk_option *new_option = NULL; + struct sk_option **temp = NULL; + size_t count = 0; + + if (context == NULL || name == NULL || value == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid parameters passed"); + return SSH_ERROR; + } + + /* Count existing options */ + if (context->sk_callbacks_options != NULL) { + while (context->sk_callbacks_options[count] != NULL) { + count++; + } + } + + /* Allocate new option */ + new_option = calloc(1, sizeof(struct sk_option)); + if (new_option == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for new option"); + return SSH_ERROR; + } + + new_option->name = strdup(name); + if (new_option->name == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for option name"); + SAFE_FREE(new_option); + return SSH_ERROR; + } + + new_option->value = strdup(value); + if (new_option->value == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for option value"); + SAFE_FREE(new_option->name); + SAFE_FREE(new_option); + return SSH_ERROR; + } + + new_option->required = required; + + /* Reallocate array to accommodate new option */ + temp = realloc(context->sk_callbacks_options, + (count + 2) * sizeof(struct sk_option *)); + if (temp == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to reallocate options array"); + SAFE_FREE(new_option->name); + SAFE_FREE(new_option->value); + SAFE_FREE(new_option); + return SSH_ERROR; + } + + context->sk_callbacks_options = temp; + context->sk_callbacks_options[count] = new_option; + context->sk_callbacks_options[count + 1] = NULL; + + return SSH_OK; +#else + (void)context; + (void)name; + (void)value; + (void)required; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Clear all sk_callbacks options. + * + * Removes and frees all previously set sk_callbacks options from the context. + * + * @param[in] context The PKI context to modify. + * + * @return SSH_OK on success, SSH_ERROR if context is NULL. + */ +int ssh_pki_ctx_sk_callbacks_options_clear(ssh_pki_ctx context) +{ +#ifdef WITH_FIDO2 + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context should not be NULL"); + return SSH_ERROR; + } + + SK_OPTIONS_FREE(context->sk_callbacks_options); + return SSH_OK; +#else + (void)context; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Get a copy of the attestation buffer from a PKI context. + * + * Retrieves a copy of the attestation buffer stored in the context after a key + * enrollment operation. The attestation buffer contains serialized attestation + * information in the "ssh-sk-attest-v01" format. + * + * @param[in] context The PKI context. Must not be NULL. + * @param[out] attestation_buffer Pointer to store a copy of the attestation + * buffer. Will be set to NULL if no attestation + * data is available (e.g., authenticator doesn't + * support attestation, or attestation data + * was invalid/incomplete). + * + * @return SSH_OK on success, SSH_ERROR if context or attestation_buffer is + * NULL, or if buffer duplication fails. + * + * @note The caller is responsible for freeing the returned buffer using + * SSH_BUFFER_FREE(). + */ +int ssh_pki_ctx_get_sk_attestation_buffer( + const struct ssh_pki_ctx_struct *context, + ssh_buffer *attestation_buffer) +{ +#ifdef WITH_FIDO2 + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context should not be NULL"); + return SSH_ERROR; + } + + if (attestation_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "attestation_buffer pointer should not be NULL"); + return SSH_ERROR; + } + + *attestation_buffer = ssh_buffer_dup(context->sk_attestation_buffer); + if (*attestation_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to duplicate attestation buffer"); + return SSH_ERROR; + } + + return SSH_OK; +#else + (void)context; + (void)attestation_buffer; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Duplicate an existing PKI context + * + * Creates a new PKI context and copies all fields from the source context. + * This function performs deep copying for all dynamically allocated fields + * to ensure independent ownership between source and destination contexts. + * + * @param[in] context The PKI context to copy from + * + * @return New PKI context with copied data on success, + * NULL on failure or if src_context is NULL + */ +ssh_pki_ctx ssh_pki_ctx_dup(const ssh_pki_ctx context) +{ + ssh_pki_ctx new_context = NULL; + + if (context == NULL) { + return NULL; + } + + new_context = ssh_pki_ctx_new(); + if (new_context == NULL) { + goto error; + } + + new_context->rsa_key_size = context->rsa_key_size; + +#ifdef WITH_FIDO2 + new_context->sk_callbacks = context->sk_callbacks; + + // Free the default application string before copying + SAFE_FREE(new_context->sk_application); + + if (context->sk_application != NULL) { + new_context->sk_application = strdup(context->sk_application); + if (new_context->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK application string"); + goto error; + } + } + + new_context->sk_flags = context->sk_flags; + + new_context->sk_pin_callback = context->sk_pin_callback; + new_context->sk_userdata = context->sk_userdata; + + if (context->sk_challenge_buffer != NULL) { + new_context->sk_challenge_buffer = + ssh_buffer_dup(context->sk_challenge_buffer); + if (new_context->sk_challenge_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK challenge buffer"); + goto error; + } + } + + if (context->sk_callbacks_options != NULL) { + new_context->sk_callbacks_options = sk_options_dup( + (const struct sk_option **)context->sk_callbacks_options); + if (new_context->sk_callbacks_options == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK callbacks options"); + goto error; + } + } + + if (context->sk_attestation_buffer != NULL) { + new_context->sk_attestation_buffer = + ssh_buffer_dup(context->sk_attestation_buffer); + if (new_context->sk_attestation_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK attestation buffer"); + goto error; + } + } +#endif /* WITH_FIDO2 */ + + return new_context; + +error: + SSH_PKI_CTX_FREE(new_context); + return NULL; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/pki_crypto.c b/src/libs/libssh-0.12.2/src/pki_crypto.c new file mode 100644 index 000000000000..7bb5b6447336 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_crypto.c @@ -0,0 +1,3038 @@ +/* + * pki_crypto.c - PKI infrastructure using OpenSSL + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2019 by Sahana Prasad + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _PKI_CRYPTO_H +#define _PKI_CRYPTO_H + +#include "config.h" + +#include "libssh/priv.h" +#include "libcrypto-compat.h" + +#include +#include +#if defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER) +#include +#endif +#include +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#include +#include +#else +#include +#include +#include +#if defined(WITH_PKCS11_URI) && defined(WITH_PKCS11_PROVIDER) +#include +#endif +#endif /* OPENSSL_VERSION_NUMBER */ + +#ifdef HAVE_OPENSSL_EC_H +#include +#endif +#ifdef HAVE_OPENSSL_ECDSA_H +#include +#endif + +#include "libssh/libssh.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/bignum.h" + +struct pem_get_password_struct { + ssh_auth_callback fn; + void *data; +}; + +static int pem_get_password(char *buf, int size, int rwflag, void *userdata) { + struct pem_get_password_struct *pgp = userdata; + + (void) rwflag; /* unused */ + + if (buf == NULL) { + return 0; + } + + memset(buf, '\0', size); + if (pgp) { + int rc; + + rc = pgp->fn("Passphrase for private key:", + buf, size, 0, 0, + pgp->data); + if (rc == 0) { + return (int)strlen(buf); + } + } + + return 0; +} + +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + EVP_PKEY_free(key->key); + key->key = NULL; +} + +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L +static int pki_key_ecdsa_to_nid(EC_KEY *k) +{ + const EC_GROUP *g = EC_KEY_get0_group(k); + int nid; + + if (g == NULL) { + return -1; + } + nid = EC_GROUP_get_curve_name(g); + if (nid) { + return nid; + } + + return -1; +} +#else +static int pki_key_ecdsa_to_nid(EVP_PKEY *k) +{ + char gname[25] = { 0 }; + int rc; + + rc = EVP_PKEY_get_utf8_string_param(k, + OSSL_PKEY_PARAM_GROUP_NAME, + gname, + 25, + NULL); + if (rc != 1) { + return -1; + } + + return pki_key_ecgroup_name_to_nid(gname); +} +#endif /* OPENSSL_VERSION_NUMBER */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EC_KEY *k) +#else +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EVP_PKEY *k) +#endif /* OPENSSL_VERSION_NUMBER */ +{ + int nid; + + nid = pki_key_ecdsa_to_nid(k); + + switch (nid) { + case NID_X9_62_prime256v1: + return SSH_KEYTYPE_ECDSA_P256; + case NID_secp384r1: + return SSH_KEYTYPE_ECDSA_P384; + case NID_secp521r1: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; + } +} + +const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return "ecdsa-sha2-nistp256"; + case NID_secp384r1: + return "ecdsa-sha2-nistp384"; + case NID_secp521r1: + return "ecdsa-sha2-nistp521"; + default: + break; + } + + return "unknown"; +} + +static const char *pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return "nistp256"; + case NID_secp384r1: + return "nistp384"; + case NID_secp521r1: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + if (strcmp(name, "nistp256") == 0) { + return NID_X9_62_prime256v1; + } else if (strcmp(name, "nistp384") == 0) { + return NID_secp384r1; + } else if (strcmp(name, "nistp521") == 0) { + return NID_secp521r1; + } + + return -1; +} + +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) +{ + int rc = 0; + BIGNUM *bexp = NULL; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_POINT *p = NULL; + const EC_GROUP *g = NULL; + EC_KEY *ecdsa = NULL; +#else + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld = NULL; + + if (group_name == NULL) { + return -1; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + bexp = ssh_make_string_bn(exp); + if (bexp == NULL) { + return -1; + } + + key->ecdsa_nid = nid; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + rc = -1; + goto cleanup; + } + + g = EC_KEY_get0_group(ecdsa); + + p = EC_POINT_new(g); + if (p == NULL) { + rc = -1; + goto cleanup; + } + + rc = EC_POINT_oct2point(g, + p, + ssh_string_data(e), + ssh_string_len(e), + NULL); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + /* EC_KEY_set_public_key duplicates p */ + rc = EC_KEY_set_public_key(ecdsa, p); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + /* EC_KEY_set_private_key duplicates exp */ + rc = EC_KEY_set_private_key(ecdsa, bexp); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + rc = -1; + goto cleanup; + } + + /* ecdsa will be freed when the EVP_PKEY key->key is freed */ + rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (rc != 1) { + rc = -1; + goto cleanup; + } + /* ssh_key is now the owner of this memory */ + ecdsa = NULL; + + /* set rc to 0 if everything went well */ + rc = 0; + +cleanup: + EC_KEY_free(ecdsa); + EC_POINT_free(p); + BN_free(bexp); + return rc; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL){ + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, bexp); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + +cleanup: + OSSL_PARAM_BLD_free(param_bld); + BN_free(bexp); + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_POINT *p = NULL; + const EC_GROUP *g = NULL; + EC_KEY *ecdsa = NULL; + int ok; +#else + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + key->ecdsa_nid = nid; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + return -1; + } + + g = EC_KEY_get0_group(ecdsa); + + p = EC_POINT_new(g); + if (p == NULL) { + EC_KEY_free(ecdsa); + return -1; + } + + ok = EC_POINT_oct2point(g, + p, + ssh_string_data(e), + ssh_string_len(e), + NULL); + if (!ok) { + EC_KEY_free(ecdsa); + EC_POINT_free(p); + return -1; + } + + /* EC_KEY_set_public_key duplicates p */ + ok = EC_KEY_set_public_key(ecdsa, p); + EC_POINT_free(p); + if (!ok) { + EC_KEY_free(ecdsa); + return -1; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + EC_KEY_free(ecdsa); + return -1; + } + + rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (rc != 1) { + EC_KEY_free(ecdsa); + return -1; + } + + return 0; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) + goto err; + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ +} +#endif /* HAVE_OPENSSL_ECC */ + +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey) +{ + EVP_PKEY *pkey = NULL; + + if (ssh_string_len(pubkey) != ED25519_KEY_LEN || + ssh_string_len(privkey) != (2 * ED25519_KEY_LEN)) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, + NULL, + (const uint8_t *)ssh_string_data(privkey), + ED25519_KEY_LEN); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + key->key = pkey; + + return SSH_OK; +} + +int pki_pubkey_build_ed25519(ssh_key key, ssh_string pubkey) +{ + EVP_PKEY *pkey = NULL; + + if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + if (ssh_fips_mode()) { + /* We do not want to fail here as we know the algorithm, but we can not + * use it. Just store the public key here. We won't be able to use it + * for anything though. */ + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate memory for the Ed25519 public key"); + return SSH_ERROR; + } + + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + return SSH_OK; + } + + pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, + NULL, + (const uint8_t *)ssh_string_data(pubkey), + ED25519_KEY_LEN); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + key->key = pkey; + + return SSH_OK; +} + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new = NULL; + int rc; + + new = pki_key_dup_common_init(key, demote); + if (new == NULL) { + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *n = NULL, *e = NULL, *d = NULL; + BIGNUM *nn, *ne, *nd; + RSA *new_rsa = NULL; + const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } +#endif /* WITH_PKCS11_URI */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + new_rsa = RSA_new(); + if (new_rsa == NULL) { + goto fail; + } + + /* + * n = public modulus + * e = public exponent + * d = private exponent + * p = secret prime factor + * q = secret prime factor + * dmp1 = d mod (p-1) + * dmq1 = d mod (q-1) + * iqmp = q^-1 mod p + */ + RSA_get0_key(key_rsa, &n, &e, &d); + nn = BN_dup(n); + ne = BN_dup(e); + if (nn == NULL || ne == NULL) { + RSA_free(new_rsa); + BN_free(nn); + BN_free(ne); + goto fail; + } + + /* Memory management of nn and ne is transferred to RSA object */ + rc = RSA_set0_key(new_rsa, nn, ne, NULL); + if (rc == 0) { + RSA_free(new_rsa); + BN_free(nn); + BN_free(ne); + goto fail; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + const BIGNUM *p = NULL, *q = NULL, *dmp1 = NULL, + *dmq1 = NULL, *iqmp = NULL; + BIGNUM *np, *nq, *ndmp1, *ndmq1, *niqmp; + + nd = BN_dup(d); + if (nd == NULL) { + RSA_free(new_rsa); + goto fail; + } + + /* Memory management of nd is transferred to RSA object */ + rc = RSA_set0_key(new_rsa, NULL, NULL, nd); + if (rc == 0) { + RSA_free(new_rsa); + goto fail; + } + + /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the + * RSA operations are much faster when these values are available. + */ + RSA_get0_factors(key_rsa, &p, &q); + if (p != NULL && q != NULL) { /* need to set both of them */ + np = BN_dup(p); + nq = BN_dup(q); + if (np == NULL || nq == NULL) { + RSA_free(new_rsa); + BN_free(np); + BN_free(nq); + goto fail; + } + + /* Memory management of np and nq is transferred to RSA object */ + rc = RSA_set0_factors(new_rsa, np, nq); + if (rc == 0) { + RSA_free(new_rsa); + BN_free(np); + BN_free(nq); + goto fail; + } + } + + RSA_get0_crt_params(key_rsa, &dmp1, &dmq1, &iqmp); + if (dmp1 != NULL || dmq1 != NULL || iqmp != NULL) { + ndmp1 = BN_dup(dmp1); + ndmq1 = BN_dup(dmq1); + niqmp = BN_dup(iqmp); + if (ndmp1 == NULL || ndmq1 == NULL || niqmp == NULL) { + RSA_free(new_rsa); + BN_free(ndmp1); + BN_free(ndmq1); + BN_free(niqmp); + goto fail; + } + + /* Memory management of ndmp1, ndmq1 and niqmp is transferred + * to RSA object */ + rc = RSA_set0_crt_params(new_rsa, ndmp1, ndmq1, niqmp); + if (rc == 0) { + RSA_free(new_rsa); + BN_free(ndmp1); + BN_free(ndmq1); + BN_free(niqmp); + goto fail; + } + } + } + + new->key = EVP_PKEY_new(); + if (new->key == NULL) { + RSA_free(new_rsa); + goto fail; + } + + rc = EVP_PKEY_assign_RSA(new->key, new_rsa); + if (rc != 1) { + EVP_PKEY_free(new->key); + RSA_free(new_rsa); + goto fail; + } + + new_rsa = NULL; +#else + rc = evp_dup_rsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_OPENSSL_ECC + new->ecdsa_nid = key->ecdsa_nid; +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } +#endif /* WITH_PKCS11_URI */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* privkey -> pubkey */ + if (demote && ssh_key_is_private(key)) { + const EC_POINT *p = NULL; + EC_KEY *new_ecdsa = NULL, *old_ecdsa = NULL; + int ok; + + new_ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (new_ecdsa == NULL) { + goto fail; + } + + old_ecdsa = EVP_PKEY_get0_EC_KEY(key->key); + if (old_ecdsa == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + p = EC_KEY_get0_public_key(old_ecdsa); + if (p == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + ok = EC_KEY_set_public_key(new_ecdsa, p); + if (ok != 1) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + new->key = EVP_PKEY_new(); + if (new->key == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + ok = EVP_PKEY_assign_EC_KEY(new->key, new_ecdsa); + if (ok != 1) { + EC_KEY_free(new_ecdsa); + goto fail; + } + } else { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + } +#else + rc = evp_dup_ecdsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + break; +#endif /* HAVE_OPENSSL_ECC */ + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE) && + key->type == SSH_KEYTYPE_ED25519) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + } else { + unsigned char *ed25519_pubkey = NULL; + size_t key_len = 0; + + rc = EVP_PKEY_get_raw_public_key(key->key, NULL, &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, + "Unexpected length of public key %zu. Expected %d.", + key_len, + ED25519_KEY_LEN); + goto fail; + } + + ed25519_pubkey = malloc(key_len); + if (ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + rc = EVP_PKEY_get_raw_public_key(key->key, + ed25519_pubkey, + &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + free(ed25519_pubkey); + goto fail; + } + + new->key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, + NULL, + ed25519_pubkey, + key_len); + free(ed25519_pubkey); + } + +#else + rc = evp_dup_ed25519_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ + break; + } + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_key_free(new); + return NULL; + } + + return new; +fail: + ssh_key_free(new); + return NULL; +} + +int pki_key_generate_rsa(ssh_key key, int parameter){ + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BIGNUM *e = NULL; + RSA *key_rsa = NULL; +#else + OSSL_PARAM params[3]; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + unsigned e = 65537; +#endif /* OPENSSL_VERSION_NUMBER */ + + if (parameter == 0) { + parameter = RSA_DEFAULT_KEY_SIZE; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + e = BN_new(); + key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } + + BN_set_word(e, 65537); + rc = RSA_generate_key_ex(key_rsa, parameter, e, NULL); + + BN_free(e); + + if (rc <= 0 || key_rsa == NULL) { + return SSH_ERROR; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + RSA_free(key_rsa); + return SSH_ERROR; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + RSA_free(key_rsa); + EVP_PKEY_free(key->key); + return SSH_ERROR; + } + + key_rsa = NULL; +#else + key->key = NULL; + + rc = EVP_PKEY_keygen_init(pctx); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + + params[0] = OSSL_PARAM_construct_int("bits", ¶meter); + params[1] = OSSL_PARAM_construct_uint("e", &e); + params[2] = OSSL_PARAM_construct_end(); + rc = EVP_PKEY_CTX_set_params(pctx, params); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_generate(pctx, &(key->key)); + + EVP_PKEY_CTX_free(pctx); + + if (rc != 1 || key->key == NULL) + return SSH_ERROR; +#endif /* OPENSSL_VERSION_NUMBER */ + return SSH_OK; +} + +#ifdef HAVE_OPENSSL_ECC +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdsa = NULL; + int ok; +#else + const char *group_name = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + switch (parameter) { + case 256: + key->ecdsa_nid = NID_X9_62_prime256v1; + key->type = SSH_KEYTYPE_ECDSA_P256; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP256; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + case 384: + key->ecdsa_nid = NID_secp384r1; + key->type = SSH_KEYTYPE_ECDSA_P384; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP384; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + case 521: + key->ecdsa_nid = NID_secp521r1; + key->type = SSH_KEYTYPE_ECDSA_P521; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP521; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Invalid parameter %d for ECDSA key " + "generation", parameter); + return SSH_ERROR; + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + return SSH_ERROR; + } + ok = EC_KEY_generate_key(ecdsa); + if (!ok) { + EC_KEY_free(ecdsa); + return SSH_ERROR; + } + + EC_KEY_set_asn1_flag(ecdsa, OPENSSL_EC_NAMED_CURVE); + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + EC_KEY_free(ecdsa); + return SSH_ERROR; + } + + ok = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (ok != 1) { + return SSH_ERROR; + } + +#else + key->key = EVP_EC_gen(group_name); + if (key->key == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + return SSH_OK; +} +#endif /* HAVE_OPENSSL_ECC */ + +/* With OpenSSL 3.0 and higher the parameter 'what' + * is ignored and the comparison is done by OpenSSL + */ +int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + int rc, cmp; + + (void)what; + + /* We got here only if the types match */ + switch (ssh_key_type_plain(k1->type)) { + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#ifdef HAVE_OPENSSL_ECC + { + const EC_KEY *ec1 = EVP_PKEY_get0_EC_KEY(k1->key); + const EC_KEY *ec2 = EVP_PKEY_get0_EC_KEY(k2->key); + const EC_POINT *p1 = NULL; + const EC_POINT *p2 = NULL; + const EC_GROUP *g1 = NULL; + const EC_GROUP *g2 = NULL; + + if (ec1 == NULL || ec2 == NULL) { + return 1; + } + + p1 = EC_KEY_get0_public_key(ec1); + p2 = EC_KEY_get0_public_key(ec2); + g1 = EC_KEY_get0_group(ec1); + g2 = EC_KEY_get0_group(ec2); + + if (p1 == NULL || p2 == NULL || g1 == NULL || g2 == NULL) { + return 1; + } + + if (EC_GROUP_cmp(g1, g2, NULL) != 0) { + return 1; + } + + if (EC_POINT_cmp(g1, p1, p2, NULL) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE && !is_sk_key_type(k1->type)) { + if (bignum_cmp(EC_KEY_get0_private_key(ec1), + EC_KEY_get0_private_key(ec2))) { + return 1; + } + } + break; + } +#endif /* HAVE_OPENSSL_ECC */ +#endif /* OPENSSL_VERSION_NUMBER */ + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + /* In FIPS mode, we can not use OpenSSL to compare Ed25519 keys. + * The OpenSSL < 3.0 also crashes in EVP_PKEY_eq() when either of + * keys keys is NULL so catch it here. */ + if (ssh_fips_mode() && k1->key == NULL && k2->key == NULL) { + if (what == SSH_KEY_CMP_PRIVATE) { + /* we should never have Ed25519 private key in FIPS mode */ + return 1; + } + cmp = memcmp(k1->ed25519_pubkey, + k2->ed25519_pubkey, + ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + /* they match */ + return 0; + } + FALL_THROUGH; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rc = EVP_PKEY_eq(k1->key, k2->key); + if (rc != 1) { + return 1; + } + break; + case SSH_KEYTYPE_UNKNOWN: + default: + return 1; + } + return 0; +} + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + ssh_string blob = NULL; + BUF_MEM *buf = NULL; + BIO *mem = NULL; + EVP_PKEY *pkey = NULL; + int rc; + + mem = BIO_new(BIO_s_mem()); + if (mem == NULL) { + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ED25519: + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto err; + } + pkey = key->key; + + /* Mark the operation as successful as for the other key types */ + rc = 1; + + break; + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, + "Unknown or invalid private key type %d", + key->type); + goto err; + } + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to initialize EVP_PKEY structure"); + goto err; + } + + if (passphrase == NULL) { + struct pem_get_password_struct pgp = { auth_fn, auth_data }; + + rc = PEM_write_bio_PrivateKey(mem, + pkey, + NULL, /* cipher */ + NULL, /* kstr */ + 0, /* klen */ + pem_get_password, + &pgp); + } else { + rc = PEM_write_bio_PrivateKey(mem, + pkey, + EVP_aes_128_cbc(), + NULL, /* kstr */ + 0, /* klen */ + NULL, /* auth_fn */ + (void*) passphrase); + } + EVP_PKEY_free(pkey); + pkey = NULL; + + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to write private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto err; + } + + BIO_get_mem_ptr(mem, &buf); + + blob = ssh_string_new(buf->length); + if (blob == NULL) { + goto err; + } + + rc = ssh_string_fill(blob, buf->data, buf->length); + if (rc < 0) { + ssh_string_free(blob); + goto err; + } + + BIO_free(mem); + + return blob; + +err: + EVP_PKEY_free(pkey); + BIO_free(mem); + return NULL; +} + +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + BIO *mem = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdsa = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + ssh_key key = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + EVP_PKEY *pkey = NULL; + + mem = BIO_new_mem_buf((void*)b64_key, -1); + + if (passphrase == NULL) { + if (auth_fn) { + struct pem_get_password_struct pgp = { auth_fn, auth_data }; + + pkey = PEM_read_bio_PrivateKey(mem, NULL, pem_get_password, &pgp); + } else { + /* openssl uses its own callback to get the passphrase here */ + pkey = PEM_read_bio_PrivateKey(mem, NULL, NULL, NULL); + } + } else { + pkey = PEM_read_bio_PrivateKey(mem, NULL, NULL, (void *) passphrase); + } + + BIO_free(mem); + + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error parsing private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + type = SSH_KEYTYPE_RSA; + break; + case EVP_PKEY_EC: +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EVP_PKEY_get0_EC_KEY(pkey); + if (ecdsa == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error parsing private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); + goto fail; + } + + break; +#endif /* HAVE_OPENSSL_ECC */ + case EVP_PKEY_ED25519: + type = SSH_KEYTYPE_ED25519; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", + EVP_PKEY_base_id(pkey)); + EVP_PKEY_free(pkey); + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + key->key = pkey; +#ifdef HAVE_OPENSSL_ECC + if (is_ecdsa_key_type(key->type)) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ + } +#endif /* HAVE_OPENSSL_ECC */ + + return key; +fail: + EVP_PKEY_free(pkey); + ssh_key_free(key); + return NULL; +} + +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + ssh_string iqmp, + ssh_string p, + ssh_string q) +{ + int rc; + BIGNUM *be = NULL, *bn = NULL, *bd = NULL; + BIGNUM *biqmp = NULL, *bp = NULL, *bq = NULL; + BIGNUM *aux = NULL, *d_consttime = NULL; + BIGNUM *bdmq1 = NULL, *bdmp1 = NULL; + BN_CTX *ctx = NULL; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + return SSH_ERROR; + } +#else + RSA *key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + bn = ssh_make_string_bn(n); + be = ssh_make_string_bn(e); + bd = ssh_make_string_bn(d); + biqmp = ssh_make_string_bn(iqmp); + bp = ssh_make_string_bn(p); + bq = ssh_make_string_bn(q); + if (be == NULL || bn == NULL || bd == NULL || + /*biqmp == NULL ||*/ bp == NULL || bq == NULL) { + rc = SSH_ERROR; + goto fail; + } + + /* Calculate remaining CRT parameters for OpenSSL to be happy + * taken from OpenSSH */ + if ((ctx = BN_CTX_new()) == NULL) { + rc = SSH_ERROR; + goto fail; + } + if ((aux = BN_new()) == NULL || + (bdmq1 = BN_new()) == NULL || + (bdmp1 = BN_new()) == NULL) { + rc = SSH_ERROR; + goto fail; + } + if ((d_consttime = BN_dup(bd)) == NULL) { + rc = SSH_ERROR; + goto fail; + } + BN_set_flags(aux, BN_FLG_CONSTTIME); + BN_set_flags(d_consttime, BN_FLG_CONSTTIME); + + if ((BN_sub(aux, bq, BN_value_one()) == 0) || + (BN_mod(bdmq1, d_consttime, aux, ctx) == 0) || + (BN_sub(aux, bp, BN_value_one()) == 0) || + (BN_mod(bdmp1, d_consttime, aux, ctx) == 0)) { + rc = SSH_ERROR; + goto fail; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* Memory management of be, bn and bd is transferred to RSA object */ + rc = RSA_set0_key(key_rsa, bn, be, bd); + if (rc == 0) { + goto fail; + } + + /* Memory management of bp and bq is transferred to RSA object */ + rc = RSA_set0_factors(key_rsa, bp, bq); + if (rc == 0) { + goto fail; + } + + /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the RSA + * operations are much faster when these values are available. + * https://www.openssl.org/docs/man1.0.2/crypto/rsa.html + * And OpenSSL fails to export these keys to PEM if these are missing: + * https://github.com/openssl/openssl/issues/21826 + */ + rc = RSA_set0_crt_params(key_rsa, bdmp1, bdmq1, biqmp); + if (rc == 0) { + goto fail; + } + bignum_safe_free(aux); + bignum_safe_free(d_consttime); + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + goto fail; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + goto fail; + } + + return SSH_OK; +fail: + RSA_free(key_rsa); + EVP_PKEY_free(key->key); + return SSH_ERROR; +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_D, bd); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR1, bp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR2, bq); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, bdmp1); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, bdmq1); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, biqmp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + rc = SSH_ERROR; + goto fail; + } + +fail: + OSSL_PARAM_BLD_free(param_bld); + bignum_safe_free(bn); + bignum_safe_free(be); + bignum_safe_free(bd); + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(biqmp); + + bignum_safe_free(aux); + bignum_safe_free(d_consttime); + bignum_safe_free(bdmp1); + bignum_safe_free(bdmq1); + BN_CTX_free(ctx); + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ +} + +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n) { + int rc; + BIGNUM *be = NULL, *bn = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + return SSH_ERROR; + } +#else + RSA *key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + be = ssh_make_string_bn(e); + if (be == NULL) { + rc = SSH_ERROR; + goto fail; + } + bn = ssh_make_string_bn(n); + if (bn == NULL) { + rc = SSH_ERROR; + goto fail; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + rc = RSA_set0_key(key_rsa, bn, be, NULL); + if (rc == 0) { + goto fail; + } + /* Memory management of bn and be is transferred to RSA object */ + bn = NULL; + be = NULL; + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + goto fail; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + goto fail; + } + + return SSH_OK; +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); +#endif /* OPENSSL_VERSION_NUMBER */ + +fail: + bignum_safe_free(bn); + bignum_safe_free(be); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EVP_PKEY_free(key->key); + RSA_free(key_rsa); + + return SSH_ERROR; +#else + OSSL_PARAM_BLD_free(param_bld); + + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ +} + +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + ssh_string str = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string p = NULL; + ssh_string g = NULL; + ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL, + *bn = NULL, *be = NULL, + *bd = NULL, *biqmp = NULL; + OSSL_PARAM *params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + uint8_t *ed25519_pubkey = NULL; + uint8_t *ed25519_privkey = NULL; + size_t key_len = 0; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); + + if (key->cert != NULL) { + rc = ssh_buffer_add_buffer(buffer, key->cert); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + goto makestring; + } + + type_s = ssh_string_from_char(key->type_c); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *be = NULL, *bn = NULL; + const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); + RSA_get0_key(key_rsa, &bn, &be, NULL); +#else + const OSSL_PARAM *out_param = NULL; + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { + goto fail; + } + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_E); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param E has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &be); + if (rc != 1) { + goto fail; + } + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_N); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param N has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bn); + if (rc != 1) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + e = ssh_make_bignum_string((BIGNUM *)be); + if (e == NULL) { + goto fail; + } + + n = ssh_make_bignum_string((BIGNUM *)bn); + if (n == NULL) { + goto fail; + } + + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *bd, *biqmp, *bp, *bq; + RSA_get0_key(key_rsa, NULL, NULL, &bd); + RSA_get0_factors(key_rsa, &bp, &bq); + RSA_get0_crt_params(key_rsa, NULL, NULL, &biqmp); +#else + OSSL_PARAM_free(params); + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_D); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param D has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bd); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param P has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bp); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR2); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param Q has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bq); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_COEFFICIENT1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param IQMP has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &biqmp); + if (rc != 1) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + + iqmp = ssh_make_bignum_string((BIGNUM *)biqmp); + if (iqmp == NULL) { + goto fail; + } + + p = ssh_make_bignum_string((BIGNUM *)bp); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string((BIGNUM *)bq); + if (q == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bd); + bignum_safe_free(biqmp); + bignum_safe_free(bp); + bignum_safe_free(bq); +#endif /* OPENSSL_VERSION_NUMBER */ + } + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bn); + bignum_safe_free(be); + OSSL_PARAM_free(params); + params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = EVP_PKEY_get_raw_public_key(key->key, NULL, &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, + "Unexpected length of private key %zu. Expected %d.", + key_len, + ED25519_KEY_LEN); + goto fail; + } + + ed25519_pubkey = malloc(key_len); + if (ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + rc = EVP_PKEY_get_raw_public_key(key->key, + (uint8_t *)ed25519_pubkey, + &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + rc = ssh_buffer_pack(buffer, + "dP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, + ed25519_pubkey); + if (rc == SSH_ERROR) { + goto fail; + } + + if (type == SSH_KEY_PRIVATE && key->type == SSH_KEYTYPE_ED25519) { + key_len = 0; + rc = EVP_PKEY_get_raw_private_key(key->key, NULL, &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, + "Unexpected length of private key %zu. Expected %d.", + key_len, + ED25519_KEY_LEN); + goto fail; + } + + ed25519_privkey = malloc(key_len); + if (ed25519_privkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + rc = EVP_PKEY_get_raw_private_key(key->key, + ed25519_privkey, + &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + rc = ssh_buffer_pack(buffer, + "dPP", + (uint32_t)(2 * ED25519_KEY_LEN), + (size_t)ED25519_KEY_LEN, + ed25519_privkey, + (size_t)ED25519_KEY_LEN, + ed25519_pubkey); + if (rc == SSH_ERROR) { + goto fail; + } + ssh_burn(ed25519_privkey, ED25519_KEY_LEN); + SAFE_FREE(ed25519_privkey); + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ED25519) { + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ED25519) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc != SSH_OK) { + goto fail; + } + } + + SAFE_FREE(ed25519_pubkey); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_OPENSSL_ECC + { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EC_GROUP *group = NULL; + EC_POINT *point = NULL; + const void *pubkey = NULL; + size_t pubkey_len; + OSSL_PARAM *locate_param = NULL; +#else + const EC_GROUP *group = NULL; + const EC_POINT *point = NULL; + const BIGNUM *exp = NULL; + EC_KEY *ec = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ec = EVP_PKEY_get0_EC_KEY(key->key); + if (ec == NULL) { + goto fail; + } +#ifdef WITH_PKCS11_URI + if (ssh_key_is_private(key) && !EC_KEY_get0_public_key(ec)) { + SSH_LOG(SSH_LOG_TRACE, + "It is mandatory to have separate" + " public ECDSA key objects in the PKCS #11 device." + " Unlike RSA, ECDSA public keys cannot be derived" + " from their private keys."); + goto fail; + } +#endif /* WITH_PKCS11_URI */ + group = EC_KEY_get0_group(ec); + point = EC_KEY_get0_public_key(ec); + if (group == NULL || point == NULL) { + goto fail; + } + e = pki_key_make_ecpoint_string(group, point); +#else + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc < 0) { + goto fail; + } + + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); +#ifdef WITH_PKCS11_URI + if (ssh_key_is_private(key) && !locate_param) { + SSH_LOG(SSH_LOG_TRACE, + "It is mandatory to have separate" + " public ECDSA key objects in the PKCS #11 device." + " Unlike RSA, ECDSA public keys cannot be derived" + " from their private keys."); + goto fail; + } +#endif /* WITH_PKCS11_URI */ + + rc = OSSL_PARAM_get_octet_string_ptr(locate_param, &pubkey, &pubkey_len); + if (rc != 1) { + goto fail; + } + /* Convert the data to low-level representation */ + group = EC_GROUP_new_by_curve_name_ex(NULL, NULL, key->ecdsa_nid); + point = EC_POINT_new(group); + rc = EC_POINT_oct2point(group, point, pubkey, pubkey_len, NULL); + if (group == NULL || point == NULL || rc != 1) { + EC_GROUP_free(group); + EC_POINT_free(point); + goto fail; + } + + e = pki_key_make_ecpoint_string(group, point); + EC_GROUP_free(group); + EC_POINT_free(point); +#endif /* OPENSSL_VERSION_NUMBER */ + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + + if (type == SSH_KEY_PRIVATE && key->type != SSH_KEYTYPE_SK_ECDSA) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_free(params); + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc < 0) { + goto fail; + } + + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY); + rc = OSSL_PARAM_get_BN(locate_param, &bd); + if (rc != 1) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + if (ssh_buffer_add_ssh_string(buffer, d) < 0) { + goto fail; + } +#else + exp = EC_KEY_get0_private_key(ec); + if (exp == NULL) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)exp); + if (d == NULL) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ECDSA) { + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc != SSH_OK) { + goto fail; + } + } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bd); + OSSL_PARAM_free(params); + params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } +#endif /* HAVE_OPENSSL_ECC */ + case SSH_KEYTYPE_UNKNOWN: + default: + goto fail; + } + +makestring: + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL) { + goto fail; + } + + rc = ssh_string_fill(str, ssh_buffer_get(buffer), ssh_buffer_get_len(buffer)); + if (rc < 0) { + goto fail; + } + SSH_BUFFER_FREE(buffer); + + return str; +fail: + SSH_BUFFER_FREE(buffer); + ssh_string_burn(str); + SSH_STRING_FREE(str); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(g); + SSH_STRING_FREE(g); + ssh_string_burn(q); + SSH_STRING_FREE(q); + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(bg); + bignum_safe_free(bpub_key); + bignum_safe_free(bn); + bignum_safe_free(be); + bignum_safe_free(bd); + bignum_safe_free(biqmp); + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ + free(ed25519_pubkey); + if (ed25519_privkey) { + ssh_burn(ed25519_privkey, ED25519_KEY_LEN); + free(ed25519_privkey); + } + + return NULL; +} + +static ssh_string pki_ecdsa_signature_to_blob(const ssh_signature sig) +{ + ssh_string r = NULL; + ssh_string s = NULL; + + ssh_buffer buf = NULL; + ssh_string sig_blob = NULL; + + const BIGNUM *pr = NULL, *ps = NULL; + + const unsigned char *raw_sig_data = NULL; + long raw_sig_len; + + ECDSA_SIG *ecdsa_sig = NULL; + + int rc; + + if (sig == NULL || sig->raw_sig == NULL) { + return NULL; + } + raw_sig_data = ssh_string_data(sig->raw_sig); + if (raw_sig_data == NULL) { + return NULL; + } + raw_sig_len = (long)ssh_string_len(sig->raw_sig); + + ecdsa_sig = d2i_ECDSA_SIG(NULL, &raw_sig_data, raw_sig_len); + if (ecdsa_sig == NULL) { + return NULL; + } + + ECDSA_SIG_get0(ecdsa_sig, &pr, &ps); + if (pr == NULL || ps == NULL) { + goto error; + } + + r = ssh_make_bignum_string((BIGNUM *)pr); + if (r == NULL) { + goto error; + } + + s = ssh_make_bignum_string((BIGNUM *)ps); + if (s == NULL) { + goto error; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(buf, r); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(buf, s); + if (rc < 0) { + goto error; + } + + sig_blob = ssh_string_new(ssh_buffer_get_len(buf)); + if (sig_blob == NULL) { + goto error; + } + + rc = ssh_string_fill(sig_blob, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); + if (rc < 0) { + goto error; + } + + SSH_STRING_FREE(r); + SSH_STRING_FREE(s); + ECDSA_SIG_free(ecdsa_sig); + SSH_BUFFER_FREE(buf); + + return sig_blob; + +error: + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(r); + SSH_STRING_FREE(s); + ECDSA_SIG_free(ecdsa_sig); + SSH_BUFFER_FREE(buf); + return NULL; +} + +ssh_string pki_signature_to_blob(const ssh_signature sig) +{ + ssh_string sig_blob = NULL; + + switch(sig->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + sig_blob = ssh_string_copy(sig->raw_sig); + break; + case SSH_KEYTYPE_ED25519: + sig_blob = pki_ed25519_signature_to_blob(sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_OPENSSL_ECC + sig_blob = pki_ecdsa_signature_to_blob(sig); + break; +#endif /* HAVE_OPENSSL_ECC */ + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ED25519: + /* For SK keys, signature data is already in raw_sig */ + sig_blob = ssh_string_copy(sig->raw_sig); + break; + default: + case SSH_KEYTYPE_UNKNOWN: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", sig->type_c); + return NULL; + } + + return sig_blob; +} + +static int pki_signature_from_rsa_blob(const ssh_key pubkey, + const ssh_string sig_blob, + ssh_signature sig) +{ + size_t pad_len = 0; + char *blob_orig = NULL; + char *blob_padded_data = NULL; + ssh_string sig_blob_padded = NULL; + + size_t rsalen = 0; + size_t len = ssh_string_len(sig_blob); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const RSA *rsa = EVP_PKEY_get0_RSA(pubkey->key); + + if (rsa == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA field NULL"); + goto errout; + } + + rsalen = RSA_size(rsa); +#else + if (EVP_PKEY_get_base_id(pubkey->key) != EVP_PKEY_RSA) { + SSH_LOG(SSH_LOG_TRACE, "Key has no RSA pubkey"); + goto errout; + } + + rsalen = EVP_PKEY_size(pubkey->key); +#endif /* OPENSSL_VERSION_NUMBER */ + if (len > rsalen) { + SSH_LOG(SSH_LOG_TRACE, + "Signature is too big: %lu > %lu", + (unsigned long)len, + (unsigned long)rsalen); + goto errout; + } + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); + ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); +#endif /* DEBUG_CRYPTO */ + + if (len == rsalen) { + sig->raw_sig = ssh_string_copy(sig_blob); + } else { + /* pad the blob to the expected rsalen size */ + SSH_LOG(SSH_LOG_DEBUG, + "RSA signature len %lu < %lu", + (unsigned long)len, + (unsigned long)rsalen); + + pad_len = rsalen - len; + + sig_blob_padded = ssh_string_new(rsalen); + if (sig_blob_padded == NULL) { + goto errout; + } + + blob_padded_data = (char *) ssh_string_data(sig_blob_padded); + blob_orig = (char *) ssh_string_data(sig_blob); + + if (blob_padded_data == NULL || blob_orig == NULL) { + goto errout; + } + + /* front-pad the buffer with zeroes */ + ssh_burn(blob_padded_data, pad_len); + /* fill the rest with the actual signature blob */ + memcpy(blob_padded_data + pad_len, blob_orig, len); + + sig->raw_sig = sig_blob_padded; + } + + return SSH_OK; + +errout: + SSH_STRING_FREE(sig_blob_padded); + return SSH_ERROR; +} + +static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey), + const ssh_string sig_blob, + ssh_signature sig) +{ + ECDSA_SIG *ecdsa_sig = NULL; + BIGNUM *pr = NULL, *ps = NULL; + + ssh_string r = NULL; + ssh_string s = NULL; + + ssh_buffer buf = NULL; + uint32_t rlen; + + unsigned char *raw_sig_data = NULL; + unsigned char *temp_raw_sig = NULL; + size_t raw_sig_len = 0; + + int rc; + + /* build ecdsa signature */ + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buf); + + rc = ssh_buffer_add_data(buf, + ssh_string_data(sig_blob), + (uint32_t)ssh_string_len(sig_blob)); + if (rc < 0) { + goto error; + } + + r = ssh_buffer_get_ssh_string(buf); + if (r == NULL) { + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); +#endif + + pr = ssh_make_string_bn(r); + ssh_string_burn(r); + SSH_STRING_FREE(r); + if (pr == NULL) { + goto error; + } + + s = ssh_buffer_get_ssh_string(buf); + rlen = ssh_buffer_get_len(buf); + SSH_BUFFER_FREE(buf); + if (s == NULL) { + goto error; + } + + if (rlen != 0) { + ssh_string_burn(s); + SSH_STRING_FREE(s); + SSH_LOG(SSH_LOG_TRACE, + "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); +#endif + + ps = ssh_make_string_bn(s); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (ps == NULL) { + goto error; + } + + ecdsa_sig = ECDSA_SIG_new(); + if (ecdsa_sig == NULL) { + goto error; + } + + /* Memory management of pr and ps is transferred to + * ECDSA signature object */ + rc = ECDSA_SIG_set0(ecdsa_sig, pr, ps); + if (rc == 0) { + goto error; + } + pr = NULL; + ps = NULL; + + /* Get the expected size of the buffer */ + rc = i2d_ECDSA_SIG(ecdsa_sig, NULL); + if (rc <= 0) { + goto error; + } + raw_sig_len = rc; + + raw_sig_data = (unsigned char *)calloc(1, raw_sig_len); + if (raw_sig_data == NULL) { + goto error; + } + temp_raw_sig = raw_sig_data; + + /* It is necessary to use a temporary pointer as i2d_* "advances" the + * pointer */ + rc = i2d_ECDSA_SIG(ecdsa_sig, &temp_raw_sig); + if (rc <= 0) { + goto error; + } + + sig->raw_sig = ssh_string_new(raw_sig_len); + if (sig->raw_sig == NULL) { + ssh_burn(raw_sig_data, raw_sig_len); + goto error; + } + + rc = ssh_string_fill(sig->raw_sig, raw_sig_data, raw_sig_len); + if (rc < 0) { + ssh_burn(raw_sig_data, raw_sig_len); + goto error; + } + + ssh_burn(raw_sig_data, raw_sig_len); + SAFE_FREE(raw_sig_data); + ECDSA_SIG_free(ecdsa_sig); + return SSH_OK; + +error: + SSH_BUFFER_FREE(buf); + bignum_safe_free(ps); + bignum_safe_free(pr); + SAFE_FREE(raw_sig_data); + if (ecdsa_sig != NULL) { + ECDSA_SIG_free(ecdsa_sig); + } + return SSH_ERROR; +} + +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + ssh_signature sig; + int rc; + + if (ssh_key_type_plain(pubkey->type) != type) { + SSH_LOG(SSH_LOG_TRACE, + "Incompatible public key provided (%d) expecting (%d)", + type, + pubkey->type); + return NULL; + } + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + sig->type_c = ssh_key_signature_to_char(type, hash_type); + sig->hash_type = hash_type; + + switch(type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rc = pki_signature_from_rsa_blob(pubkey, sig_blob, sig); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc != SSH_OK){ + goto error; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: +#ifdef HAVE_OPENSSL_ECC + rc = pki_signature_from_ecdsa_blob(pubkey, sig_blob, sig); + if (rc != SSH_OK) { + goto error; + } + break; +#endif + default: + case SSH_KEYTYPE_UNKNOWN: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); + goto error; + } + + return sig; + +error: + ssh_signature_free(sig); + return NULL; +} + +static const EVP_MD *pki_digest_to_md(enum ssh_digest_e hash_type) +{ + const EVP_MD *md = NULL; + + switch (hash_type) { + case SSH_DIGEST_SHA256: + md = EVP_sha256(); + break; + case SSH_DIGEST_SHA384: + md = EVP_sha384(); + break; + case SSH_DIGEST_SHA512: + md = EVP_sha512(); + break; + case SSH_DIGEST_SHA1: + md = EVP_sha1(); + break; + case SSH_DIGEST_AUTO: + md = NULL; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d", + hash_type); + return NULL; + } + + return md; +} + +static EVP_PKEY *pki_key_to_pkey(ssh_key key) +{ + EVP_PKEY *pkey = NULL; + int rc = 0; + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + if (key->key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "NULL key->key"); + goto error; + } + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to reference EVP_PKEY"); + return NULL; + } + pkey = key->key; + break; + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown private key algorithm for type: %d", + key->type); + goto error; + } + + return pkey; + +error: + EVP_PKEY_free(pkey); + return NULL; +} + +/** + * @internal + * + * @brief Sign the given input data. The digest to be signed is calculated + * internally as necessary. + * + * @param[in] privkey The private key to be used for signing. + * @param[in] hash_type The digest algorithm to be used. + * @param[in] input The data to be signed. + * @param[in] input_len The length of the data to be signed. + * + * @return a newly allocated ssh_signature or NULL on error. + */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + + unsigned char *raw_sig_data = NULL; + size_t raw_sig_len; + + ssh_signature sig = NULL; + + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_sign_data()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + /* Set hash algorithm to be used */ + md = pki_digest_to_md(hash_type); + if (md == NULL) { + if (hash_type != SSH_DIGEST_AUTO) { + return NULL; + } + } + + /* Setup private key EVP_PKEY */ + pkey = pki_key_to_pkey(privkey); + if (pkey == NULL) { + return NULL; + } + + /* Allocate buffer for signature */ + raw_sig_len = (size_t)EVP_PKEY_size(pkey); + raw_sig_data = (unsigned char *)malloc(raw_sig_len); + if (raw_sig_data == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto out; + } + + /* Create the context */ + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto out; + } + + /* Sign the data */ + rc = EVP_DigestSignInit(ctx, NULL, md, NULL, pkey); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestSignInit() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_DigestSign(ctx, raw_sig_data, &raw_sig_len, input, input_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestSign() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Generated signature", raw_sig_data, raw_sig_len); +#endif + + /* Allocate and fill output signature */ + sig = ssh_signature_new(); + if (sig == NULL) { + goto out; + } + + sig->raw_sig = ssh_string_new(raw_sig_len); + if (sig->raw_sig == NULL) { + ssh_signature_free(sig); + sig = NULL; + goto out; + } + + rc = ssh_string_fill(sig->raw_sig, raw_sig_data, raw_sig_len); + if (rc < 0) { + ssh_signature_free(sig); + sig = NULL; + goto out; + } + + sig->type = privkey->type; + sig->hash_type = hash_type; + sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); + +out: + if (ctx != NULL) { + EVP_MD_CTX_free(ctx); + } + if (raw_sig_data != NULL) { + ssh_burn(raw_sig_data, raw_sig_len); + } + SAFE_FREE(raw_sig_data); + EVP_PKEY_free(pkey); + return sig; +} + +/** + * @internal + * + * @brief Verify the signature of a given input. The digest of the input is + * calculated internally as necessary. + * + * @param[in] signature The signature to be verified. + * @param[in] pubkey The public key used to verify the signature. + * @param[in] input The signed data. + * @param[in] input_len The length of the signed data. + * + * @return SSH_OK if the signature is valid; SSH_ERROR otherwise. + */ +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len) +{ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + + unsigned char *raw_sig_data = NULL; + size_t raw_sig_len; + + /* Function return code + * Do not change this variable throughout the function until the signature + * is successfully verified! + */ + int rc = SSH_ERROR; + int ok; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL || signature->raw_sig == NULL) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_verify_data_signature()"); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + ok = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (ok != SSH_OK) { + return SSH_ERROR; + } + + /* Get the signature to be verified */ + raw_sig_data = ssh_string_data(signature->raw_sig); + raw_sig_len = ssh_string_len(signature->raw_sig); + if (raw_sig_data == NULL) { + return SSH_ERROR; + } + + /* Set hash algorithm to be used */ + md = pki_digest_to_md(signature->hash_type); + if (md == NULL) { + if (signature->hash_type != SSH_DIGEST_AUTO) { + return SSH_ERROR; + } + } + + /* Setup public key EVP_PKEY */ + pkey = pki_key_to_pkey(pubkey); + if (pkey == NULL) { + return SSH_ERROR; + } + + /* Create the context */ + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create EVP_MD_CTX: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + /* Verify the signature */ + ok = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); + if (ok != 1){ + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestVerifyInit() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + ok = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); + if (ok != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Signature invalid: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + SSH_LOG(SSH_LOG_TRACE, "Signature valid"); + rc = SSH_OK; + +out: + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return rc; +} + +int ssh_key_size(ssh_key key) +{ + int bits = 0; + EVP_PKEY *pkey = NULL; + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + pkey = pki_key_to_pkey(key); + if (pkey == NULL) { + return SSH_ERROR; + } + bits = EVP_PKEY_bits(pkey); + EVP_PKEY_free(pkey); + return bits; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +int pki_key_generate_ed25519(ssh_key key) +{ + int evp_rc; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + + if (key == NULL) { + return SSH_ERROR; + } + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY_CTX: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + evp_rc = EVP_PKEY_keygen_init(pctx); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize ed25519 key generation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + evp_rc = EVP_PKEY_keygen(pctx, &pkey); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate ed25519 key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + key->key = pkey; + + EVP_PKEY_CTX_free(pctx); + return SSH_OK; + +error: + if (pctx != NULL) { + EVP_PKEY_CTX_free(pctx); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return SSH_ERROR; +} + +#ifdef WITH_PKCS11_URI + +/** + * @internal + * + * @brief Populate the public/private ssh_key from the engine/provider with + * PKCS#11 URIs as the look up. + * + * @param[in] uri_name The PKCS#11 URI + * @param[in] nkey The ssh-key context for + * the key loaded from the engine/provider. + * @param[in] key_type The type of the key used. Public/Private. + * + * @return SSH_OK if ssh-key is valid; SSH_ERROR otherwise. + */ +int pki_uri_import(const char *uri_name, + ssh_key *nkey, + enum ssh_key_e key_type) +{ + EVP_PKEY *pkey = NULL; + ssh_key key = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; +#if OPENSSL_VERSION_NUMBER < 0x30000000L && HAVE_OPENSSL_ECC + EC_KEY *ecdsa = NULL; +#endif +#ifndef WITH_PKCS11_PROVIDER + ENGINE *engine = NULL; + + /* Do the init only once */ + engine = pki_get_engine(); + if (engine == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to initialize engine"); + goto fail; + } + + switch (key_type) { + case SSH_KEY_PRIVATE: + pkey = ENGINE_load_private_key(engine, uri_name, NULL, NULL); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Could not load key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + break; + case SSH_KEY_PUBLIC: + pkey = ENGINE_load_public_key(engine, uri_name, NULL, NULL); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Could not load key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + break; + default: + SSH_LOG(SSH_LOG_TRACE, + "Invalid key type: %d", key_type); + goto fail; + } +#else /* WITH_PKCS11_PROVIDER */ + OSSL_STORE_CTX *store = NULL; + OSSL_STORE_INFO *info = NULL; + int rv, expect_type = OSSL_STORE_INFO_PKEY; + + /* The provider can be either configured in openssl.cnf or dynamically + * loaded, assuming it does not need any special configuration */ + rv = pki_load_pkcs11_provider(); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to load or initialize pkcs11 provider"); + goto fail; + } + + store = OSSL_STORE_open(uri_name, NULL, NULL, NULL, NULL); + if (store == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to open OpenSSL store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + if (key_type == SSH_KEY_PUBLIC) { + expect_type = OSSL_STORE_INFO_PUBKEY; + } + rv = OSSL_STORE_expect(store, expect_type); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set the store preference. Ignoring the error: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + for (info = OSSL_STORE_load(store); + info != NULL; + info = OSSL_STORE_load(store)) { + int ossl_type = OSSL_STORE_INFO_get_type(info); + + if (ossl_type == OSSL_STORE_INFO_PUBKEY && key_type == SSH_KEY_PUBLIC) { + pkey = OSSL_STORE_INFO_get1_PUBKEY(info); + } else if (ossl_type == OSSL_STORE_INFO_PKEY && + key_type == SSH_KEY_PRIVATE) { + pkey = OSSL_STORE_INFO_get1_PKEY(info); + } else { + SSH_LOG(SSH_LOG_TRACE, + "Ignoring object not matching our type: %d", + ossl_type); + OSSL_STORE_INFO_free(info); + continue; + } + OSSL_STORE_INFO_free(info); + break; + } + OSSL_STORE_close(store); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "No key found in the pkcs11 store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + +#endif /* WITH_PKCS11_PROVIDER */ + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + type = SSH_KEYTYPE_RSA; + break; + case EVP_PKEY_EC: +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EVP_PKEY_get0_EC_KEY(pkey); + if (ecdsa == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Parsing pub key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ + type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid pub key."); + goto fail; + } + + break; +#endif + case EVP_PKEY_ED25519: + type = SSH_KEYTYPE_ED25519; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid public key type %d", + EVP_PKEY_base_id(pkey)); + goto fail; + } + + key->key = pkey; + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PKCS11_URI; + if (key_type == SSH_KEY_PRIVATE) { + key->flags |= SSH_KEY_FLAG_PRIVATE; + } +#ifdef HAVE_OPENSSL_ECC + if (is_ecdsa_key_type(key->type)) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ + } +#endif + + *nkey = key; + + return SSH_OK; + +fail: + EVP_PKEY_free(pkey); + ssh_key_free(key); + + return SSH_ERROR; +} +#endif /* WITH_PKCS11_URI */ + +#endif /* _PKI_CRYPTO_H */ diff --git a/src/libs/libssh-0.12.2/src/pki_ed25519.c b/src/libs/libssh-0.12.2/src/pki_ed25519.c new file mode 100644 index 000000000000..f39540645158 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_ed25519.c @@ -0,0 +1,349 @@ +/* + * pki_ed25519.c - PKI infrastructure using ed25519 + * + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/ed25519.h" +#include "libssh/buffer.h" + +int pki_pubkey_build_ed25519(ssh_key key, ssh_string pubkey) +{ + if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + + return SSH_OK; +} + +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey) +{ + if (ssh_string_len(pubkey) != ED25519_KEY_LEN || + ssh_string_len(privkey) != (2 * ED25519_KEY_LEN)) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + /* In the internal implementation, the private key is the concatenation of + * the private seed with the public key. */ + key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); + if (key->ed25519_privkey == NULL) { + goto error; + } + + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + goto error; + } + + memcpy(key->ed25519_privkey, ssh_string_data(privkey), 2 * ED25519_KEY_LEN); + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + + return SSH_OK; + +error: + SAFE_FREE(key->ed25519_privkey); + SAFE_FREE(key->ed25519_pubkey); + + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Compare ed25519 keys if they are equal. + * + * @param[in] k1 The first key to compare. + * + * @param[in] k2 The second key to compare. + * + * @param[in] what What part or type of the key do you want to compare. + * + * @return 0 if equal, 1 if not. + */ +int +pki_ed25519_key_cmp(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + int cmp; + + switch (what) { + case SSH_KEY_CMP_PRIVATE: + if (k1->ed25519_privkey == NULL || k2->ed25519_privkey == NULL) { + return 1; + } + /* In the internal implementation, the private key is the concatenation + * of the private seed with the public key. */ + cmp = secure_memcmp(k1->ed25519_privkey, + k2->ed25519_privkey, + 2 * ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + FALL_THROUGH; + case SSH_KEY_CMP_PUBLIC: + if (k1->ed25519_pubkey == NULL || k2->ed25519_pubkey == NULL) { + return 1; + } + cmp = memcmp(k1->ed25519_pubkey, k2->ed25519_pubkey, ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + break; + case SSH_KEY_CMP_CERTIFICATE: + /* handled globally */ + return 1; + } + + return 0; +} + +/** + * @internal + * + * @brief Duplicate an Ed25519 key + * + * @param[out] new Pre-initialized ssh_key structure + * + * @param[in] key Key to copy + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key) +{ + if (key->ed25519_privkey == NULL && key->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + + if (key->ed25519_privkey != NULL) { + /* In the internal implementation, the private key is the concatenation + * of the private seed with the public key. */ + new_key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); + if (new_key->ed25519_privkey == NULL) { + return SSH_ERROR; + } + memcpy(new_key->ed25519_privkey, + key->ed25519_privkey, + 2 * ED25519_KEY_LEN); + } + + if (key->ed25519_pubkey != NULL) { + new_key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (new_key->ed25519_pubkey == NULL) { + SAFE_FREE(new_key->ed25519_privkey); + return SSH_ERROR; + } + memcpy(new_key->ed25519_pubkey, key->ed25519_pubkey, ED25519_KEY_LEN); + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief Outputs an Ed25519 public key in a blob buffer. + * + * @param[out] buffer Output buffer + * + * @param[in] key Key to output + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) +{ + int rc; + + if (key->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_pack(buffer, + "dP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, + key->ed25519_pubkey); + + return rc; +} + +/** @internal + * @brief exports a ed25519 private key to a string blob. + * @param[in] privkey private key to convert + * @param[out] buffer buffer to write the blob in. + * @returns SSH_OK on success + */ +int pki_ed25519_private_key_to_blob(ssh_buffer buffer, const ssh_key privkey) +{ + int rc; + + if (privkey->type != SSH_KEYTYPE_ED25519) { + SSH_LOG(SSH_LOG_TRACE, "Type %s not supported", privkey->type_c); + return SSH_ERROR; + } + if (privkey->ed25519_privkey == NULL || privkey->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + rc = ssh_buffer_pack(buffer, + "dPdPP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, + privkey->ed25519_pubkey, + (uint32_t)(2 * ED25519_KEY_LEN), + (size_t)ED25519_KEY_LEN, + privkey->ed25519_privkey, + (size_t)ED25519_KEY_LEN, + privkey->ed25519_pubkey); + return rc; +} + +int pki_key_generate_ed25519(ssh_key key) +{ + int rc; + + key->ed25519_privkey = malloc(sizeof (ed25519_privkey)); + if (key->ed25519_privkey == NULL) { + goto error; + } + + key->ed25519_pubkey = malloc(sizeof (ed25519_pubkey)); + if (key->ed25519_pubkey == NULL) { + goto error; + } + + rc = crypto_sign_ed25519_keypair(*key->ed25519_pubkey, + *key->ed25519_privkey); + if (rc != 0) { + goto error; + } + + return SSH_OK; +error: + SAFE_FREE(key->ed25519_privkey); + SAFE_FREE(key->ed25519_pubkey); + + return SSH_ERROR; +} + +int pki_ed25519_sign(const ssh_key privkey, + ssh_signature sig, + const unsigned char *hash, + size_t hlen) +{ + int rc; + uint8_t *buffer = NULL; + uint64_t dlen = 0; + + buffer = malloc(hlen + ED25519_SIG_LEN); + if (buffer == NULL) { + return SSH_ERROR; + } + + rc = crypto_sign_ed25519(buffer, + &dlen, + hash, + hlen, + *privkey->ed25519_privkey); + if (rc != 0) { + goto error; + } + + /* This shouldn't happen */ + if (dlen - hlen != ED25519_SIG_LEN) { + goto error; + } + + sig->ed25519_sig = malloc(ED25519_SIG_LEN); + if (sig->ed25519_sig == NULL) { + goto error; + } + + memcpy(sig->ed25519_sig, buffer, ED25519_SIG_LEN); + SAFE_FREE(buffer); + + return SSH_OK; +error: + SAFE_FREE(buffer); + return SSH_ERROR; +} + +int pki_ed25519_verify(const ssh_key pubkey, + ssh_signature sig, + const unsigned char *hash, + size_t hlen) +{ + uint64_t mlen = 0; + uint8_t *buffer = NULL; + uint8_t *buffer2 = NULL; + int rc; + + if (pubkey == NULL || sig == NULL || + hash == NULL || sig->ed25519_sig == NULL) { + return SSH_ERROR; + } + + buffer = malloc(hlen + ED25519_SIG_LEN); + if (buffer == NULL) { + return SSH_ERROR; + } + + buffer2 = malloc(hlen + ED25519_SIG_LEN); + if (buffer2 == NULL) { + goto error; + } + + memcpy(buffer, sig->ed25519_sig, ED25519_SIG_LEN); + memcpy(buffer + ED25519_SIG_LEN, hash, hlen); + + rc = crypto_sign_ed25519_open(buffer2, + &mlen, + buffer, + hlen + ED25519_SIG_LEN, + *pubkey->ed25519_pubkey); + + ssh_burn(buffer, hlen + ED25519_SIG_LEN); + ssh_burn(buffer2, hlen); + SAFE_FREE(buffer); + SAFE_FREE(buffer2); + if (rc == 0) { + return SSH_OK; + } else { + return SSH_ERROR; + } +error: + SAFE_FREE(buffer); + SAFE_FREE(buffer2); + + return SSH_ERROR; +} + diff --git a/src/libs/libssh-0.12.2/src/pki_ed25519_common.c b/src/libs/libssh-0.12.2/src/pki_ed25519_common.c new file mode 100644 index 000000000000..26e40efb7afa --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_ed25519_common.c @@ -0,0 +1,109 @@ +/* + * pki_ed25519_common.c - Common ed25519 functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/buffer.h" + +/** + * @internal + * + * @brief output a signature blob from an ed25519 signature + * + * @param[in] sig signature to convert + * + * @return Signature blob in SSH string, or NULL on error + */ +ssh_string pki_ed25519_signature_to_blob(ssh_signature sig) +{ + ssh_string sig_blob = NULL; + int rc; + +#ifdef HAVE_LIBCRYPTO + /* When using the OpenSSL implementation, the signature is stored in raw_sig + * which is shared by all algorithms.*/ + if (sig->raw_sig == NULL) { + return NULL; + } +#else + /* When using the internal implementation, the signature is stored in an + * algorithm specific field. */ + if (sig->ed25519_sig == NULL) { + return NULL; + } +#endif + + sig_blob = ssh_string_new(ED25519_SIG_LEN); + if (sig_blob == NULL) { + return NULL; + } + +#ifdef HAVE_LIBCRYPTO + rc = ssh_string_fill(sig_blob, ssh_string_data(sig->raw_sig), + ssh_string_len(sig->raw_sig)); +#else + rc = ssh_string_fill(sig_blob, sig->ed25519_sig, ED25519_SIG_LEN); +#endif + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + + return sig_blob; +} + +/** + * @internal + * + * @brief Convert a signature blob in an ed25519 signature. + * + * @param[out] sig a preinitialized signature + * + * @param[in] sig_blob a signature blob + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob) +{ + size_t len; + + len = ssh_string_len(sig_blob); + if (len != ED25519_SIG_LEN){ + SSH_LOG(SSH_LOG_TRACE, "Invalid ssh-ed25519 signature len: %zu", len); + return SSH_ERROR; + } + +#ifdef HAVE_LIBCRYPTO + sig->raw_sig = ssh_string_copy(sig_blob); +#else + sig->ed25519_sig = malloc(ED25519_SIG_LEN); + if (sig->ed25519_sig == NULL){ + return SSH_ERROR; + } + memcpy(sig->ed25519_sig, ssh_string_data(sig_blob), ED25519_SIG_LEN); +#endif + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/pki_gcrypt.c b/src/libs/libssh-0.12.2/src/pki_gcrypt.c new file mode 100644 index 000000000000..802fa7e2bc15 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_gcrypt.c @@ -0,0 +1,2381 @@ +/* + * pki_gcrypt.c private and public key handling using gcrypt. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 Aris Adamantiadis + * Copyright (c) 2009-2011 Andreas Schneider + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#ifdef HAVE_LIBGCRYPT + +#include +#include +#include +#include +#include +#include + +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/wrapper.h" + +#define MAXLINESIZE 80 +#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" +#define ECDSA_HEADER_BEGIN "-----BEGIN EC PRIVATE KEY-----" +#define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" + +#define MAX_KEY_SIZE 32 +#define MAX_PASSPHRASE_SIZE 1024 +#define ASN1_INTEGER 2 +#define ASN1_BIT_STRING 3 +#define ASN1_OCTET_STRING 4 +#define ASN1_OBJECT_IDENTIFIER 6 +#define ASN1_SEQUENCE 48 +#define PKCS5_SALT_LEN 8 + +static int load_iv(const char *header, unsigned char *iv, int iv_len) +{ + int i; + int j; + int k; + + memset(iv, 0, iv_len); + for (i = 0; i < iv_len; i++) { + if ((header[2 * i] >= '0') && (header[2 * i] <= '9')) + j = header[2 * i] - '0'; + else if ((header[2 * i] >= 'A') && (header[2 * i] <= 'F')) + j = header[2 * i] - 'A' + 10; + else if ((header[2 * i] >= 'a') && (header[2 * i] <= 'f')) + j = header[2 * i] - 'a' + 10; + else + return -1; + if ((header[2 * i + 1] >= '0') && (header[2 * i + 1] <= '9')) + k = header[2 * i + 1] - '0'; + else if ((header[2 * i + 1] >= 'A') && (header[2 * i + 1] <= 'F')) + k = header[2 * i + 1] - 'A' + 10; + else if ((header[2 * i + 1] >= 'a') && (header[2 * i + 1] <= 'f')) + k = header[2 * i + 1] - 'a' + 10; + else + return -1; + iv[i] = (j << 4) + k; + } + return 0; +} + +static uint32_t char_to_u32(unsigned char *data, uint32_t size) +{ + uint32_t ret; + uint32_t i; + + for (i = 0, ret = 0; i < size; ret = ret << 8, ret += data[i++]) + ; + return ret; +} + +static uint32_t asn1_get_len(ssh_buffer buffer) +{ + uint32_t len; + unsigned char tmp[4]; + + if (ssh_buffer_get_data(buffer, tmp, 1) == 0) { + return 0; + } + + if (tmp[0] > 127) { + len = tmp[0] & 127; + if (len > 4) { + return 0; /* Length doesn't fit in u32. Can this really happen? */ + } + if (ssh_buffer_get_data(buffer, tmp, len) == 0) { + return 0; + } + len = char_to_u32(tmp, len); + } else { + len = char_to_u32(tmp, 1); + } + + return len; +} + +static ssh_string asn1_get(ssh_buffer buffer, unsigned char want) +{ + ssh_string str = NULL; + unsigned char type; + uint32_t size; + + if (ssh_buffer_get_data(buffer, &type, 1) == 0 || type != want) { + return NULL; + } + size = asn1_get_len(buffer); + if (size == 0) { + return NULL; + } + + str = ssh_string_new(size); + if (str == NULL) { + return NULL; + } + + if (ssh_buffer_get_data(buffer, ssh_string_data(str), size) == 0) { + SSH_STRING_FREE(str); + return NULL; + } + + return str; +} + +static ssh_string asn1_get_int(ssh_buffer buffer) +{ + return asn1_get(buffer, ASN1_INTEGER); +} + +static ssh_string asn1_get_bit_string(ssh_buffer buffer) +{ + ssh_string str = NULL; + unsigned char type; + uint32_t size; + unsigned char unused, last, *p = NULL; + uint32_t len; + + len = ssh_buffer_get_data(buffer, &type, 1); + if (len == 0 || type != ASN1_BIT_STRING) { + return NULL; + } + size = asn1_get_len(buffer); + if (size == 0) { + return NULL; + } + + /* The first octet encodes the number of unused bits. */ + size -= 1; + + str = ssh_string_new(size); + if (str == NULL) { + return NULL; + } + + len = ssh_buffer_get_data(buffer, &unused, 1); + if (len == 0) { + SSH_STRING_FREE(str); + return NULL; + } + + if (unused == 0) { + len = ssh_buffer_get_data(buffer, ssh_string_data(str), size); + if (len == 0) { + SSH_STRING_FREE(str); + return NULL; + } + return str; + } + + /* The bit string is padded at the end, we must shift the whole + string by UNUSED bits. */ + for (p = ssh_string_data(str), last = 0; size; size--, p++) { + unsigned char c; + + len = ssh_buffer_get_data(buffer, &c, 1); + if (len == 0) { + SSH_STRING_FREE(str); + return NULL; + } + *p = last | (c >> unused); + last = c << (8 - unused); + } + + return str; +} + +static int asn1_check_sequence(ssh_buffer buffer) +{ + unsigned char *j = NULL; + unsigned char tmp; + int i; + uint32_t size; + uint32_t padding; + + if (ssh_buffer_get_data(buffer, &tmp, 1) == 0 || tmp != ASN1_SEQUENCE) { + return 0; + } + + size = asn1_get_len(buffer); + if ((padding = ssh_buffer_get_len(buffer) - size) > 0) { + for (i = ssh_buffer_get_len(buffer) - size, + j = (unsigned char *)ssh_buffer_get(buffer) + size; + i; + i--, j++) { + if (*j != padding) { /* padding is allowed */ + return 0; /* but nothing else */ + } + } + } + + return 1; +} + +static int asn1_check_tag(ssh_buffer buffer, unsigned char tag) +{ + unsigned char tmp; + uint32_t len; + + len = ssh_buffer_get_data(buffer, &tmp, 1); + if (len == 0 || tmp != tag) { + return 0; + } + + (void)asn1_get_len(buffer); + return 1; +} + +static int passphrase_to_key(char *data, + unsigned int datalen, + unsigned char *salt, + unsigned char *key, + unsigned int keylen) +{ + MD5CTX md; + unsigned char digest[MD5_DIGEST_LEN] = {0}; + unsigned int i; + unsigned int j; + unsigned int md_not_empty; + + for (j = 0, md_not_empty = 0; j < keylen;) { + md = md5_init(); + if (md == NULL) { + return -1; + } + + if (md_not_empty) { + md5_update(md, digest, MD5_DIGEST_LEN); + } else { + md_not_empty = 1; + } + + md5_update(md, data, datalen); + if (salt) { + md5_update(md, salt, PKCS5_SALT_LEN); + } + md5_final(digest, md); + + for (i = 0; j < keylen && i < MD5_DIGEST_LEN; j++, i++) { + if (key) { + key[j] = digest[i]; + } + } + } + + return 0; +} + +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->rsa) + gcry_sexp_release(key->rsa); + if (key->ecdsa) + gcry_sexp_release(key->ecdsa); + + key->rsa = NULL; + key->ecdsa = NULL; +} + +static int privatekey_decrypt(int algo, + int mode, + unsigned int key_len, + unsigned char *iv, + unsigned int iv_len, + ssh_buffer data, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + char passphrase[MAX_PASSPHRASE_SIZE] = {0}; + unsigned char key[MAX_KEY_SIZE] = {0}; + unsigned char *tmp = NULL; + gcry_cipher_hd_t cipher; + int rc = -1; + + if (!algo) { + return -1; + } + + if (cb) { + rc = (*cb)(desc, passphrase, MAX_PASSPHRASE_SIZE, 0, 0, userdata); + if (rc < 0) { + return -1; + } + } else if (cb == NULL && userdata != NULL) { + snprintf(passphrase, MAX_PASSPHRASE_SIZE, "%s", (char *)userdata); + } + + if (passphrase_to_key(passphrase, strlen(passphrase), iv, key, key_len) < + 0) { + return -1; + } + + if (gcry_cipher_open(&cipher, algo, mode, 0) || + gcry_cipher_setkey(cipher, key, key_len) || + gcry_cipher_setiv(cipher, iv, iv_len) || + (tmp = calloc(ssh_buffer_get_len(data), sizeof(unsigned char))) == + NULL || + gcry_cipher_decrypt(cipher, + tmp, + ssh_buffer_get_len(data), + ssh_buffer_get(data), + ssh_buffer_get_len(data))) { + gcry_cipher_close(cipher); + return -1; + } + + memcpy(ssh_buffer_get(data), tmp, ssh_buffer_get_len(data)); + + SAFE_FREE(tmp); + gcry_cipher_close(cipher); + + return 0; +} + +static int privatekey_dek_header(const char *header, + unsigned int header_len, + int *algo, + int *mode, + unsigned int *key_len, + unsigned char **iv, + unsigned int *iv_len) +{ + unsigned int iv_pos; + + if (header_len > 13 && !strncmp("DES-EDE3-CBC", header, 12)) { + *algo = GCRY_CIPHER_3DES; + iv_pos = 13; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 8; + } else if (header_len > 8 && !strncmp("DES-CBC", header, 7)) { + *algo = GCRY_CIPHER_DES; + iv_pos = 8; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 8; + *iv_len = 8; + } else if (header_len > 12 && !strncmp("AES-128-CBC", header, 11)) { + *algo = GCRY_CIPHER_AES128; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 16; + *iv_len = 16; + } else if (header_len > 12 && !strncmp("AES-192-CBC", header, 11)) { + *algo = GCRY_CIPHER_AES192; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 16; + } else if (header_len > 12 && !strncmp("AES-256-CBC", header, 11)) { + *algo = GCRY_CIPHER_AES256; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 32; + *iv_len = 16; + } else { + return -1; + } + + *iv = malloc(*iv_len); + if (*iv == NULL) { + return -1; + } + + return load_iv(header + iv_pos, *iv, *iv_len); +} + +#define get_next_line(p, len) \ + { \ + while (p[len] == '\n' || p[len] == '\r') /* skip empty lines */ \ + len++; \ + if (p[len] == '\0') /* EOL */ \ + eol = true; \ + else /* calculate length */ \ + for (p += len, len = 0; \ + p[len] && p[len] != '\n' && p[len] != '\r'; \ + len++) \ + ; \ + } + +static ssh_buffer privatekey_string_to_buffer(const char *pkey, + int type, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + ssh_buffer buffer = NULL; + ssh_buffer out = NULL; + const char *p = NULL; + unsigned char *iv = NULL; + const char *header_begin = NULL; + const char *header_end = NULL; + unsigned int header_begin_size; + unsigned int header_end_size; + unsigned int key_len = 0; + unsigned int iv_len = 0; + int algo = 0; + int mode = 0; + bool eol = false; + size_t len; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + switch (type) { + case SSH_KEYTYPE_RSA: + header_begin = RSA_HEADER_BEGIN; + header_end = RSA_HEADER_END; + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + header_begin = ECDSA_HEADER_BEGIN; + header_end = ECDSA_HEADER_END; + break; + default: + SSH_BUFFER_FREE(buffer); + return NULL; + } + + header_begin_size = strlen(header_begin); + header_end_size = strlen(header_end); + + p = pkey; + len = 0; + get_next_line(p, len); + + while (!eol && strncmp(p, header_begin, header_begin_size)) { + /* skip line */ + get_next_line(p, len); + } + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + /* skip header line */ + get_next_line(p, len); + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + if (len > 11 && strncmp("Proc-Type: 4,ENCRYPTED", p, 11) == 0) { + /* skip line */ + get_next_line(p, len); + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + if (len > 10 && strncmp("DEK-Info: ", p, 10) == 0) { + p += 10; + len = 0; + get_next_line(p, len); + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + if (privatekey_dek_header(p, + len, + &algo, + &mode, + &key_len, + &iv, + &iv_len) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + } else { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + } else { + if (len > 0) { + if (ssh_buffer_add_data(buffer, p, len) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + } + } + + get_next_line(p, len); + while (!eol && strncmp(p, header_end, header_end_size) != 0) { + if (ssh_buffer_add_data(buffer, p, len) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + get_next_line(p, len); + } + + if (eol || strncmp(p, header_end, header_end_size) != 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + + if (ssh_buffer_add_data(buffer, "\0", 1) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + + out = base64_to_bin(ssh_buffer_get(buffer)); + SSH_BUFFER_FREE(buffer); + if (out == NULL) { + SAFE_FREE(iv); + return NULL; + } + + if (algo) { + if (privatekey_decrypt(algo, + mode, + key_len, + iv, + iv_len, + out, + cb, + userdata, + desc) < 0) { + SSH_BUFFER_FREE(out); + SAFE_FREE(iv); + return NULL; + } + } + SAFE_FREE(iv); + + return out; +} + +static int b64decode_rsa_privatekey(const char *pkey, + gcry_sexp_t *r, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + const unsigned char *data = NULL; + ssh_string n = NULL; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string unused1 = NULL; + ssh_string unused2 = NULL; + ssh_string u = NULL; + ssh_string v = NULL; + ssh_buffer buffer = NULL; + int rc = 1; + gcry_error_t rv = 0; + + buffer = + privatekey_string_to_buffer(pkey, SSH_KEYTYPE_RSA, cb, userdata, desc); + if (buffer == NULL) { + return 0; + } + + if (!asn1_check_sequence(buffer)) { + SSH_BUFFER_FREE(buffer); + return 0; + } + + v = asn1_get_int(buffer); + if (v == NULL) { + SSH_BUFFER_FREE(buffer); + return 0; + } + + data = ssh_string_data(v); + if (ssh_string_len(v) != 1 || data[0] != 0) { + SSH_STRING_FREE(v); + SSH_BUFFER_FREE(buffer); + return 0; + } + + n = asn1_get_int(buffer); + e = asn1_get_int(buffer); + d = asn1_get_int(buffer); + q = asn1_get_int(buffer); + p = asn1_get_int(buffer); + unused1 = asn1_get_int(buffer); + unused2 = asn1_get_int(buffer); + u = asn1_get_int(buffer); + + SSH_BUFFER_FREE(buffer); + + if (n == NULL || e == NULL || d == NULL || p == NULL || q == NULL || + unused1 == NULL || unused2 == NULL || u == NULL) { + rc = 0; + goto error; + } + + rv = gcry_sexp_build( + r, + NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(p), + ssh_string_data(p), + ssh_string_len(q), + ssh_string_data(q), + ssh_string_len(u), + ssh_string_data(u)); + if (rv) { + rc = 0; + } + +error: + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + SSH_STRING_FREE(unused1); + SSH_STRING_FREE(unused2); + ssh_string_burn(u); + SSH_STRING_FREE(u); + SSH_STRING_FREE(v); + + return rc; +} + +#ifdef HAVE_GCRYPT_ECC +static int pki_key_ecdsa_to_nid(gcry_sexp_t k) +{ + gcry_sexp_t sexp = NULL; + const char *tmp = NULL; + size_t size; + + sexp = gcry_sexp_find_token(k, "curve", 0); + if (sexp == NULL) { + return -1; + } + + tmp = gcry_sexp_nth_data(sexp, 1, &size); + + if (size == 10) { + int cmp; + + cmp = memcmp("NIST P-256", tmp, size); + if (cmp == 0) { + gcry_sexp_release(sexp); + return NID_gcrypt_nistp256; + } + + cmp = memcmp("NIST P-384", tmp, size); + if (cmp == 0) { + gcry_sexp_release(sexp); + return NID_gcrypt_nistp384; + } + + cmp = memcmp("NIST P-521", tmp, size); + if (cmp == 0) { + gcry_sexp_release(sexp); + return NID_gcrypt_nistp521; + } + } + + gcry_sexp_release(sexp); + return -1; +} + +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(gcry_sexp_t k) +{ + int nid; + + nid = pki_key_ecdsa_to_nid(k); + + switch (nid) { + case NID_gcrypt_nistp256: + return SSH_KEYTYPE_ECDSA_P256; + case NID_gcrypt_nistp384: + return SSH_KEYTYPE_ECDSA_P384; + case NID_gcrypt_nistp521: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; + } +} + +static const char *pki_key_ecdsa_nid_to_gcrypt_name(int nid) +{ + switch (nid) { + case NID_gcrypt_nistp256: + return "NIST P-256"; + case NID_gcrypt_nistp384: + return "NIST P-384"; + case NID_gcrypt_nistp521: + return "NIST P-521"; + } + + return "unknown"; +} + +const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_gcrypt_nistp256: + return "ecdsa-sha2-nistp256"; + case NID_gcrypt_nistp384: + return "ecdsa-sha2-nistp384"; + case NID_gcrypt_nistp521: + return "ecdsa-sha2-nistp521"; + } + + return "unknown"; +} + +static const char *pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_gcrypt_nistp256: + return "nistp256"; + case NID_gcrypt_nistp384: + return "nistp384"; + case NID_gcrypt_nistp521: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + int cmp; + + cmp = strcmp(name, "nistp256"); + if (cmp == 0) { + return NID_gcrypt_nistp256; + } + + cmp = strcmp(name, "nistp384"); + if (cmp == 0) { + return NID_gcrypt_nistp384; + } + + cmp = strcmp(name, "nistp521"); + if (cmp == 0) { + return NID_gcrypt_nistp521; + } + + return -1; +} + +static int asn1_oi_to_nid(const ssh_string oi) +{ + static const struct { + int nid; + size_t length; + const char *identifier; + } *e, mapping[] = { + {NID_gcrypt_nistp256, 8, "\x2a\x86\x48\xce\x3d\x03\x01\x07"}, + {NID_gcrypt_nistp384, 5, "\x2b\x81\x04\x00\x22"}, + {NID_gcrypt_nistp521, 5, "\x2b\x81\x04\x00\x23"}, + {0}, + }; + size_t len = ssh_string_len(oi); + for (e = mapping; e->length; e++) { + if (len == e->length && + memcmp(ssh_string_data(oi), e->identifier, len) == 0) { + return e->nid; + } + } + return -1; +} + +static int b64decode_ecdsa_privatekey(const char *pkey, + gcry_sexp_t *r, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + const unsigned char *data = NULL; + ssh_buffer buffer = NULL; + gcry_error_t err = 0; + ssh_string v = NULL; + ssh_string d = NULL; + ssh_string oi = NULL; + int nid; + ssh_string q = NULL; + int valid = 0; + int ok; + + buffer = privatekey_string_to_buffer(pkey, + SSH_KEYTYPE_ECDSA_P256, + cb, + userdata, + desc); + if (buffer == NULL) { + goto error; + } + + ok = asn1_check_sequence(buffer); + if (!ok) { + goto error; + } + + /* RFC5915 specifies version 1. */ + v = asn1_get_int(buffer); + if (v == NULL) { + goto error; + } + + data = ssh_string_data(v); + if (ssh_string_len(v) != 1 || data[0] != 1) { + goto error; + } + + d = asn1_get(buffer, ASN1_OCTET_STRING); + if (!asn1_check_tag(buffer, 0xa0)) { + goto error; + } + oi = asn1_get(buffer, ASN1_OBJECT_IDENTIFIER); + nid = asn1_oi_to_nid(oi); + ok = asn1_check_tag(buffer, 0xa1); + if (!ok) { + goto error; + } + q = asn1_get_bit_string(buffer); + + if (d == NULL || oi == NULL || nid == -1 || q == NULL) { + goto error; + } + + err = gcry_sexp_build(r, + NULL, + "(private-key(ecdsa(curve %s)(d %b)(q %b)))", + pki_key_ecdsa_nid_to_gcrypt_name(nid), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(q), + ssh_string_data(q)); + if (err == 0) { + valid = 1; + } + +error: + SSH_BUFFER_FREE(buffer); + SSH_STRING_FREE(v); + ssh_string_burn(d); + SSH_STRING_FREE(d); + SSH_STRING_FREE(oi); + ssh_string_burn(q); + SSH_STRING_FREE(q); + + return valid; +} +#endif + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + (void)key; + (void)passphrase; + (void)auth_fn; + (void)auth_data; + + SSH_LOG(SSH_LOG_TRACE, "PEM export not supported by gcrypt backend!"); + + return NULL; +} + +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + gcry_sexp_t rsa = NULL; + gcry_sexp_t ecdsa = NULL; + ssh_key key = NULL; + enum ssh_keytypes_e type; + int valid; + + type = pki_privatekey_type_from_string(b64_key); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key."); + return NULL; + } + + switch (type) { + case SSH_KEYTYPE_RSA: + if (passphrase == NULL) { + if (auth_fn) { + valid = b64decode_rsa_privatekey(b64_key, + &rsa, + auth_fn, + auth_data, + "Passphrase for private key:"); + } else { + valid = + b64decode_rsa_privatekey(b64_key, &rsa, NULL, NULL, NULL); + } + } else { + valid = b64decode_rsa_privatekey(b64_key, + &rsa, + NULL, + (void *)passphrase, + NULL); + } + + if (!valid) { + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); + goto fail; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#if HAVE_GCRYPT_ECC + if (passphrase == NULL) { + if (auth_fn != NULL) { + valid = + b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + auth_fn, + auth_data, + "Passphrase for private key:"); + } else { + valid = b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + NULL, + NULL, + NULL); + } + } else { + valid = b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + NULL, + (void *)passphrase, + NULL); + } + + if (!valid) { + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); + goto fail; + } + + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ + type = pki_key_ecdsa_to_key_type(ecdsa); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); + goto fail; + } + break; +#endif + case SSH_KEYTYPE_ED25519: + /* Cannot open ed25519 keys with libgcrypt */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", type); + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + key->rsa = rsa; + key->ecdsa = ecdsa; +#ifdef HAVE_GCRYPT_ECC + if (is_ecdsa_key_type(key->type)) { + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); + } +#endif + + return key; +fail: + ssh_key_free(key); + gcry_sexp_release(rsa); + gcry_sexp_release(ecdsa); + + return NULL; +} + +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + ssh_string iqmp, + ssh_string p, + ssh_string q) +{ + /* in gcrypt, there is no iqmp (inverse of q mod p) argument, + * but it is ipmq (inverse of p mod q) so we need to swap + * the p and q arguments */ + gcry_sexp_build(&key->rsa, + NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(q), + ssh_string_data(q), + ssh_string_len(p), + ssh_string_data(p), + ssh_string_len(iqmp), + ssh_string_data(iqmp)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) +{ + gcry_sexp_build(&key->rsa, + NULL, + "(public-key(rsa(n %b)(e %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +#ifdef HAVE_GCRYPT_ECC +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) +{ + gpg_error_t err; + + key->ecdsa_nid = nid; + + err = gcry_sexp_build(&key->ecdsa, + NULL, + "(private-key(ecdsa(curve %s)(d %b)(q %b)))", + pki_key_ecdsa_nid_to_gcrypt_name(nid), + ssh_string_len(exp), + ssh_string_data(exp), + ssh_string_len(e), + ssh_string_data(e)); + if (err) { + return SSH_ERROR; + } + + return SSH_OK; +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + gpg_error_t err; + + key->ecdsa_nid = nid; + + err = gcry_sexp_build(&key->ecdsa, + NULL, + "(public-key(ecdsa(curve %s)(q %b)))", + pki_key_ecdsa_nid_to_gcrypt_name(nid), + ssh_string_len(e), + ssh_string_data(e)); + if (err) { + return SSH_ERROR; + } + + return SSH_OK; +} +#endif + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new = NULL; + gcry_error_t err = 0; + int rc; + + gcry_mpi_t p = NULL; + gcry_mpi_t q = NULL; + gcry_mpi_t g = NULL; + gcry_mpi_t y = NULL; + gcry_mpi_t x = NULL; + + gcry_mpi_t e = NULL; + gcry_mpi_t n = NULL; + gcry_mpi_t d = NULL; + gcry_mpi_t u = NULL; + + gcry_sexp_t curve = NULL; + + new = pki_key_dup_common_init(key, demote); + if (new == NULL) { + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + err = gcry_sexp_extract_param(key->rsa, + NULL, + "ned?p?q?u?", + &n, + &e, + &d, + &p, + &q, + &u, + NULL); + if (err != 0) { + break; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + err = gcry_sexp_build( + &new->rsa, + NULL, + "(private-key(rsa(n %m)(e %m)(d %m)(p %m)(q %m)(u %m)))", + n, + e, + d, + p, + q, + u); + } else { + err = gcry_sexp_build(&new->rsa, + NULL, + "(public-key(rsa(n %m)(e %m)))", + n, + e); + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_ed25519_key_dup(new, key); + if (rc != SSH_OK) { + ssh_key_free(new); + return NULL; + } + break; + + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + new->ecdsa_nid = key->ecdsa_nid; + + err = gcry_sexp_extract_param(key->ecdsa, NULL, "qd?", &q, &d, NULL); + if (err) { + break; + } + + curve = gcry_sexp_find_token(key->ecdsa, "curve", 0); + if (curve == NULL) { + break; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE) && + !is_sk_key_type(key->type)) { + err = gcry_sexp_build(&new->ecdsa, + NULL, + "(private-key(ecdsa %S (d %m)(q %m)))", + curve, + d, + q); + } else { + err = gcry_sexp_build(&new->ecdsa, + NULL, + "(private-key(ecdsa %S (q %m)))", + curve, + q); + } + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_key_free(new); + return NULL; + } + + if (err) { + ssh_key_free(new); + new = NULL; + } + + gcry_mpi_release(p); + gcry_mpi_release(q); + gcry_mpi_release(g); + gcry_mpi_release(y); + gcry_mpi_release(x); + + gcry_mpi_release(e); + gcry_mpi_release(n); + gcry_mpi_release(d); + gcry_mpi_release(u); + + gcry_sexp_release(curve); + + return new; +} + +static int +pki_key_generate(ssh_key key, int parameter, const char *type_s, int type) +{ + gcry_sexp_t params = NULL; + int rc; + rc = gcry_sexp_build(¶ms, + NULL, + "(genkey(%s(nbits %d)(transient-key)))", + type_s, + parameter); + if (rc != 0) { + return SSH_ERROR; + } + + switch (type) { + case SSH_KEYTYPE_RSA: + rc = gcry_pk_genkey(&key->rsa, params); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + rc = gcry_pk_genkey(&key->ecdsa, params); + break; + default: + assert(!"reached"); + } + gcry_sexp_release(params); + if (rc != 0) + return SSH_ERROR; + return SSH_OK; +} + +int pki_key_generate_rsa(ssh_key key, int parameter) +{ + if (parameter == 0) { + parameter = RSA_DEFAULT_KEY_SIZE; + } + + return pki_key_generate(key, parameter, "rsa", SSH_KEYTYPE_RSA); +} + +#ifdef HAVE_GCRYPT_ECC +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ + switch (parameter) { + case 384: + key->ecdsa_nid = NID_gcrypt_nistp384; + key->type = SSH_KEYTYPE_ECDSA_P384; + return pki_key_generate(key, + parameter, + "ecdsa", + SSH_KEYTYPE_ECDSA_P384); + case 521: + key->ecdsa_nid = NID_gcrypt_nistp521; + key->type = SSH_KEYTYPE_ECDSA_P521; + return pki_key_generate(key, + parameter, + "ecdsa", + SSH_KEYTYPE_ECDSA_P521); + case 256: + default: + key->ecdsa_nid = NID_gcrypt_nistp256; + key->type = SSH_KEYTYPE_ECDSA_P256; + return pki_key_generate(key, + parameter, + "ecdsa", + SSH_KEYTYPE_ECDSA_P256); + } +} +#endif + +static int +_bignum_cmp(const gcry_sexp_t s1, const gcry_sexp_t s2, const char *what) +{ + gcry_sexp_t sexp = NULL; + bignum b1 = NULL; + bignum b2 = NULL; + int result; + + sexp = gcry_sexp_find_token(s1, what, 0); + if (sexp == NULL) { + return 1; + } + b1 = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b1 == NULL) { + return 1; + } + + sexp = gcry_sexp_find_token(s2, what, 0); + if (sexp == NULL) { + bignum_safe_free(b1); + return 1; + } + b2 = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b2 == NULL) { + bignum_safe_free(b1); + return 1; + } + + result = !!bignum_cmp(b1, b2); + bignum_safe_free(b1); + bignum_safe_free(b2); + return result; +} + +int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + switch (k1->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + if (_bignum_cmp(k1->rsa, k2->rsa, "e") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "n") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (_bignum_cmp(k1->rsa, k2->rsa, "d") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "p") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "q") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "u") != 0) { + return 1; + } + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys handled globally */ + return 1; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: +#ifdef HAVE_GCRYPT_ECC + if (k1->ecdsa_nid != k2->ecdsa_nid) { + return 1; + } + + if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "q") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE && !is_sk_key_type(k1->type)) { + if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "d") != 0) { + return 1; + } + } + break; +#endif + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_ECDSA: /* deprecated */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + return 1; + } + + return 0; +} + +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + ssh_string str = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string d = NULL; + ssh_string p = NULL; + ssh_string g = NULL; + ssh_string q = NULL; + ssh_string u = NULL; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); + + if (key->cert != NULL) { + rc = ssh_buffer_add_buffer(buffer, key->cert); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + goto makestring; + } + + type_s = ssh_string_from_char(key->type_c); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + e = ssh_sexp_extract_mpi(key->rsa, + "e", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (e == NULL) { + goto fail; + } + + n = ssh_sexp_extract_mpi(key->rsa, + "n", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (n == NULL) { + goto fail; + } + + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_sexp_extract_mpi(key->rsa, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + p = ssh_sexp_extract_mpi(key->rsa, + "p", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (p == NULL) { + goto fail; + } + + q = ssh_sexp_extract_mpi(key->rsa, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (q == NULL) { + goto fail; + } + + u = ssh_sexp_extract_mpi(key->rsa, + "u", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (u == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, u); + if (rc < 0) { + goto fail; + } + /* Swap the P and Q as the iqmp in gcrypt is ipmq ... */ + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + ssh_string_burn(u); + SSH_STRING_FREE(u); + } + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + } else { + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + type_s = + ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + goto fail; + } + + e = ssh_sexp_extract_mpi(key->ecdsa, + "q", + GCRYMPI_FMT_STD, + GCRYMPI_FMT_STD); + if (e == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + + if (type == SSH_KEY_PRIVATE && !is_sk_key_type(key->type)) { + d = ssh_sexp_extract_mpi(key->ecdsa, + "d", + GCRYMPI_FMT_STD, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (type == SSH_KEY_PRIVATE && is_sk_key_type(key->type)) { + /* Add security key private data for SK_ECDSA */ + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + goto fail; + } + +makestring: + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL) { + goto fail; + } + + rc = ssh_string_fill(str, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc < 0) { + goto fail; + } + SSH_BUFFER_FREE(buffer); + + return str; +fail: + SSH_BUFFER_FREE(buffer); + ssh_string_burn(str); + SSH_STRING_FREE(str); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(g); + SSH_STRING_FREE(g); + ssh_string_burn(q); + SSH_STRING_FREE(q); + ssh_string_burn(n); + SSH_STRING_FREE(n); + + return NULL; +} + +ssh_string pki_signature_to_blob(const ssh_signature sig) +{ + const char *s = NULL; /* used in RSA */ + + gcry_sexp_t sexp = NULL; + size_t size = 0; + ssh_string sig_blob = NULL; + int rc; + + switch (sig->type) { + case SSH_KEYTYPE_RSA: + sexp = gcry_sexp_find_token(sig->rsa_sig, "s", 0); + if (sexp == NULL) { + return NULL; + } + s = gcry_sexp_nth_data(sexp, 1, &size); + + /* + * Remove leading zeroes, but only the ones that do not make the MPI + * representation look like a negative value (first bit is one), + * which might confuse some implementations. + */ + while (size > 1 && s[0] == 0 && (s[1] & 0x80) == 0) { + size--; + s++; + } + + sig_blob = ssh_string_new(size); + if (sig_blob == NULL) { + return NULL; + } + rc = ssh_string_fill(sig_blob, discard_const_p(char, s), size); + gcry_sexp_release(sexp); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + sig_blob = pki_ed25519_signature_to_blob(sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + { + ssh_string R = NULL; + ssh_string S = NULL; + ssh_buffer b = NULL; + + b = ssh_buffer_new(); + if (b == NULL) { + return NULL; + } + + R = ssh_sexp_extract_mpi(sig->ecdsa_sig, + "r", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (R == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, R); + SSH_STRING_FREE(R); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + S = ssh_sexp_extract_mpi(sig->ecdsa_sig, + "s", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (S == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, S); + SSH_STRING_FREE(S); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + sig_blob = ssh_string_new(ssh_buffer_get_len(b)); + if (sig_blob == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = + ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b)); + SSH_BUFFER_FREE(b); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + break; + } +#endif + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ED25519: + /* For SK keys, signature data is already in raw_sig */ + sig_blob = ssh_string_copy(sig->raw_sig); + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %d", sig->type); + return NULL; + break; + } + + return sig_blob; +} + +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + ssh_signature sig = NULL; + gcry_error_t err; + size_t len; + size_t rsalen; + int rc; + + if (ssh_key_type_plain(pubkey->type) != type) { + SSH_LOG(SSH_LOG_TRACE, + "Incompatible public key provided (%d) expecting (%d)", + type, + pubkey->type); + return NULL; + } + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + sig->type_c = ssh_key_signature_to_char(type, hash_type); + sig->hash_type = hash_type; + + len = ssh_string_len(sig_blob); + + switch (type) { + case SSH_KEYTYPE_RSA: + rsalen = (gcry_pk_get_nbits(pubkey->rsa) + 7) / 8; + + if (len > rsalen) { + SSH_LOG(SSH_LOG_TRACE, + "Signature is too big: %lu > %lu", + (unsigned long)len, + (unsigned long)rsalen); + ssh_signature_free(sig); + return NULL; + } + + if (len < rsalen) { + SSH_LOG(SSH_LOG_DEBUG, + "RSA signature len %lu < %lu", + (unsigned long)len, + (unsigned long)rsalen); + } + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); + ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); +#endif + + err = gcry_sexp_build(&sig->rsa_sig, + NULL, + "(sig-val(rsa(s %b)))", + ssh_string_len(sig_blob), + ssh_string_data(sig_blob)); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc != SSH_OK) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + { /* build ecdsa siganature */ + ssh_buffer b = NULL; + ssh_string r = NULL, s = NULL; + uint32_t rlen; + + b = ssh_buffer_new(); + if (b == NULL) { + ssh_signature_free(sig); + return NULL; + } + /* The buffer will contain sensitive information. */ + ssh_buffer_set_secure(b); + + rc = ssh_buffer_add_data(b, + ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + if (rc < 0) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + r = ssh_buffer_get_ssh_string(b); + if (r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + s = ssh_buffer_get_ssh_string(b); + rlen = ssh_buffer_get_len(b); + SSH_BUFFER_FREE(b); + if (s == NULL) { + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_signature_free(sig); + return NULL; + } + + if (rlen != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_string_burn(s); + SSH_STRING_FREE(s); + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); +#endif + + err = gcry_sexp_build(&sig->ecdsa_sig, + NULL, + "(sig-val(ecdsa(r %b)(s %b)))", + ssh_string_len(r), + ssh_string_data(r), + ssh_string_len(s), + ssh_string_data(s)); + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (err) { + ssh_signature_free(sig); + return NULL; + } + } break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); + return NULL; + } + + return sig; +} + +ssh_signature pki_do_sign_hash(const ssh_key privkey, + const unsigned char *hash, + size_t hlen, + enum ssh_digest_e hash_type) +{ + const char *hash_c = NULL; + ssh_signature sig = NULL; + gcry_sexp_t sexp = NULL; + gcry_error_t err; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + sig->type = privkey->type; + sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); + sig->hash_type = hash_type; + switch (privkey->type) { + case SSH_KEYTYPE_RSA: + switch (hash_type) { + case SSH_DIGEST_SHA1: + hash_c = "sha1"; + break; + case SSH_DIGEST_SHA256: + hash_c = "sha256"; + break; + case SSH_DIGEST_SHA512: + hash_c = "sha512"; + break; + case SSH_DIGEST_AUTO: + default: + SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); + return NULL; + } + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash %s %b))", + hash_c, + hlen, + hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + + err = gcry_pk_sign(&sig->rsa_sig, sexp, privkey->rsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + err = pki_ed25519_sign(privkey, sig, hash, hlen); + if (err != SSH_OK) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags raw)(value %b))", + hlen, + hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + + err = gcry_pk_sign(&sig->ecdsa_sig, sexp, privkey->ecdsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_signature_free(sig); + return NULL; + } + + return sig; +} + +/** + * @internal + * + * @brief Sign the given input data. The digest of to be signed is calculated + * internally as necessary. + * + * @param[in] privkey The private key to be used for signing. + * @param[in] hash_type The digest algorithm to be used. + * @param[in] input The data to be signed. + * @param[in] input_len The length of the data to be signed. + * + * @return a newly allocated ssh_signature or NULL on error. + */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + unsigned char hash[SHA512_DIGEST_LEN] = {0}; + const unsigned char *sign_input = NULL; + uint32_t hlen = 0; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Bad parameter provided to " + "pki_sign_data()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + switch (hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_AUTO: + if (privkey->type == SSH_KEYTYPE_ED25519) { + /* SSH_DIGEST_AUTO should only be used with ed25519 */ + sign_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, + "Unknown hash algorithm for type: %d", + hash_type); + return NULL; + } + + return pki_do_sign_hash(privkey, sign_input, hlen, hash_type); +} + +/** + * @internal + * + * @brief Verify the signature of a given input. The digest of the input is + * calculated internally as necessary. + * + * @param[in] signature The signature to be verified. + * @param[in] pubkey The public key used to verify the signature. + * @param[in] input The signed data. + * @param[in] input_len The length of the signed data. + * + * @return SSH_OK if the signature is valid; SSH_ERROR otherwise. + */ +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len) +{ + const char *hash_type = NULL; + gcry_sexp_t sexp = NULL; + gcry_error_t err; + + unsigned char ghash[SHA512_DIGEST_LEN + 1] = {0}; + unsigned char *hash = ghash + 1; + uint32_t hlen = 0; + + const unsigned char *verify_input = NULL; + + int rc; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Bad parameter provided to " + "pki_verify_data_signature()"); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + switch (signature->hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + hash_type = "sha256"; + verify_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + hash_type = "sha384"; + verify_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + hash_type = "sha512"; + verify_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + hash_type = "sha1"; + verify_input = hash; + break; + case SSH_DIGEST_AUTO: + if (pubkey->type == SSH_KEYTYPE_ED25519 || + pubkey->type == SSH_KEYTYPE_ED25519_CERT01 || + pubkey->type == SSH_KEYTYPE_SK_ED25519 || + pubkey->type == SSH_KEYTYPE_SK_ED25519_CERT01) { + verify_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, + "Unknown sig->hash_type: %d", + signature->hash_type); + return SSH_ERROR; + } + + switch (pubkey->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash %s %b))", + hash_type, + hlen, + hash); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "RSA hash error: %s", gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(signature->rsa_sig, sexp, pubkey->rsa); + gcry_sexp_release(sexp); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "Invalid RSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { + SSH_LOG(SSH_LOG_TRACE, + "RSA verify error: %s", + gcry_strerror(err)); + } + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: +#ifdef HAVE_GCRYPT_ECC + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags raw)(value %b))", + hlen, + hash); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "ECDSA hash error: %s", gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(signature->ecdsa_sig, sexp, pubkey->ecdsa); + gcry_sexp_release(sexp); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ECDSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { + SSH_LOG(SSH_LOG_TRACE, + "ECDSA verify error: %s", + gcry_strerror(err)); + } + return SSH_ERROR; + } + break; +#endif + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + rc = pki_ed25519_verify(pubkey, signature, verify_input, hlen); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "ED25519 error: Signature invalid"); + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type"); + return SSH_ERROR; + } + + return SSH_OK; +} + +int ssh_key_size(ssh_key key) +{ + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return gcry_pk_get_nbits(key->rsa); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return gcry_pk_get_nbits(key->ecdsa); + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +#ifdef WITH_PKCS11_URI +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) +{ + (void)uri_name; + (void)key; + (void)key_type; + SSH_LOG(SSH_LOG_TRACE, "gcrypt does not support PKCS #11"); + return SSH_ERROR; +} +#endif /* WITH_PKCS11_URI */ +#endif /* HAVE_LIBGCRYPT */ diff --git a/src/libs/libssh-0.12.2/src/pki_mbedcrypto.c b/src/libs/libssh-0.12.2/src/pki_mbedcrypto.c new file mode 100644 index 000000000000..01a9ca80fc03 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_mbedcrypto.c @@ -0,0 +1,2032 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#ifdef HAVE_LIBMBEDCRYPTO +#include +#include +#include "mbedcrypto-compat.h" + +#include "libssh/priv.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/buffer.h" +#include "libssh/bignum.h" +#include "libssh/misc.h" + +#define MAX_PASSPHRASE_SIZE 1024 +#define MAX_KEY_SIZE 32 + +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->pk != NULL) { + mbedtls_pk_free(key->pk); + SAFE_FREE(key->pk); + } + + if (key->ecdsa != NULL) { + mbedtls_ecdsa_free(key->ecdsa); + SAFE_FREE(key->ecdsa); + } +} + +ssh_string pki_private_key_to_pem(const ssh_key key, const char *passphrase, + ssh_auth_callback auth_fn, void *auth_data) +{ + (void) key; + (void) passphrase; + (void) auth_fn; + (void) auth_data; return NULL; +} + +static int pki_key_ecdsa_to_nid(mbedtls_ecdsa_context *ecdsa) +{ + mbedtls_ecp_group_id id; + + id = ecdsa->MBEDTLS_PRIVATE(grp.id); + if (id == MBEDTLS_ECP_DP_SECP256R1) { + return NID_mbedtls_nistp256; + } else if (id == MBEDTLS_ECP_DP_SECP384R1) { + return NID_mbedtls_nistp384; + } else if (id == MBEDTLS_ECP_DP_SECP521R1) { + return NID_mbedtls_nistp521; + } + + return -1; +} + +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(mbedtls_ecdsa_context *ecdsa) +{ + int nid; + + nid = pki_key_ecdsa_to_nid(ecdsa); + + switch (nid) { + case NID_mbedtls_nistp256: + return SSH_KEYTYPE_ECDSA_P256; + case NID_mbedtls_nistp384: + return SSH_KEYTYPE_ECDSA_P384; + case NID_mbedtls_nistp521: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; + } +} + +ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, + ssh_auth_callback auth_fn, void *auth_data) +{ + ssh_key key = NULL; + mbedtls_pk_context *pk = NULL; + mbedtls_pk_type_t mbed_type; + int valid; + /* mbedtls pk_parse_key expects strlen to count the 0 byte */ + size_t b64len = strlen(b64_key) + 1; + unsigned char tmp[MAX_PASSPHRASE_SIZE] = {0}; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_ctr_drbg_context *ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); +#endif + + pk = malloc(sizeof(mbedtls_pk_context)); + if (pk == NULL) { + goto fail; + } + mbedtls_pk_init(pk); + + if (passphrase == NULL) { + if (auth_fn) { + valid = auth_fn("Passphrase for private key:", + (char *)tmp, + MAX_PASSPHRASE_SIZE, + 0, + 0, + auth_data); + if (valid < 0) { + goto fail; + } + valid = mbedtls_pk_parse_key( + pk, + (const unsigned char *)b64_key, + b64len, + tmp, + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE) +#if MBEDTLS_VERSION_MAJOR > 2 + , + mbedtls_ctr_drbg_random, + ctr_drbg +#endif + ); + } else { + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + NULL, + 0 +#if MBEDTLS_VERSION_MAJOR > 2 + , + mbedtls_ctr_drbg_random, + ctr_drbg +#endif + ); + } + } else { + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + (const unsigned char *)passphrase, + strnlen(passphrase, MAX_PASSPHRASE_SIZE) +#if MBEDTLS_VERSION_MAJOR > 2 + , + mbedtls_ctr_drbg_random, + ctr_drbg +#endif + ); + } + if (valid != 0) { + char error_buf[100]; + mbedtls_strerror(valid, error_buf, 100); + SSH_LOG(SSH_LOG_WARN, "Parsing private key %s", error_buf); + goto fail; + } + + mbed_type = mbedtls_pk_get_type(pk); + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + switch (mbed_type) { + case MBEDTLS_PK_RSA: + case MBEDTLS_PK_RSA_ALT: + key->pk = pk; + pk = NULL; + key->type = SSH_KEYTYPE_RSA; + break; + case MBEDTLS_PK_ECKEY: + case MBEDTLS_PK_ECDSA: { + /* type will be set later */ + mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*pk); + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + goto fail; + } + + mbedtls_ecdsa_init(key->ecdsa); + mbedtls_ecdsa_from_keypair(key->ecdsa, keypair); + key->pk = pk; + + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); + + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ + key->type = pki_key_ecdsa_to_key_type(key->ecdsa); + if (key->type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); + goto fail; + } + break; + } + default: + SSH_LOG(SSH_LOG_WARN, + "Unknown or invalid private key type %d", + mbed_type); + return NULL; + } + + key->type_c = ssh_key_type_to_char(key->type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + return key; +fail: + ssh_key_free(key); + if (pk != NULL) { + mbedtls_pk_free(pk); + SAFE_FREE(pk); + } + return NULL; +} + +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + UNUSED_PARAM(ssh_string iqmp), + ssh_string p, + ssh_string q) +{ + mbedtls_rsa_context *rsa = NULL; + const mbedtls_pk_info_t *pk_info = NULL; + int rc; + + key->pk = malloc(sizeof(mbedtls_pk_context)); + if (key->pk == NULL) { + return SSH_ERROR; + } + + mbedtls_pk_init(key->pk); + pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + mbedtls_pk_setup(key->pk, pk_info); + + rc = mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA); + if (rc == 0) { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->pk); + rc = mbedtls_rsa_import_raw(rsa, + ssh_string_data(n), ssh_string_len(n), + ssh_string_data(p), ssh_string_len(p), + ssh_string_data(q), ssh_string_len(q), + ssh_string_data(d), ssh_string_len(d), + ssh_string_data(e), ssh_string_len(e)); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import private RSA key"); + goto fail; + } + + rc = mbedtls_rsa_complete(rsa); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to complete private RSA key"); + goto fail; + } + + rc = mbedtls_rsa_check_privkey(rsa); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "Inconsistent private RSA key"); + goto fail; + } + + return SSH_OK; + +fail: + mbedtls_pk_free(key->pk); + SAFE_FREE(key->pk); + return SSH_ERROR; +} + +int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) +{ + mbedtls_rsa_context *rsa = NULL; + const mbedtls_pk_info_t *pk_info = NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; +#endif + int rc; + + key->pk = malloc(sizeof(mbedtls_pk_context)); + if (key->pk == NULL) { + return SSH_ERROR; + } + + mbedtls_pk_init(key->pk); + pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + mbedtls_pk_setup(key->pk, pk_info); + + rc = mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA); + if (rc == 0) { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->pk); +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); + + rc = mbedtls_mpi_read_binary(&N, ssh_string_data(n), + ssh_string_len(n)); +#else + rc = mbedtls_mpi_read_binary(&rsa->N, ssh_string_data(n), + ssh_string_len(n)); +#endif + if (rc != 0) { + goto fail; + } +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_mpi_read_binary(&E, ssh_string_data(e), + ssh_string_len(e)); +#else + rc = mbedtls_mpi_read_binary(&rsa->E, ssh_string_data(e), + ssh_string_len(e)); +#endif + if (rc != 0) { + goto fail; + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_import(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_rsa_complete(rsa); + if (rc != 0) { + goto fail; + } + +#else + rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3; +#endif + rc = SSH_OK; + goto exit; +fail: + rc = SSH_ERROR; + mbedtls_pk_free(key->pk); + SAFE_FREE(key->pk); +exit: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif + return rc; +} + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new = NULL; + int rc; + const mbedtls_pk_info_t *pk_info = NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; + mbedtls_mpi D; + mbedtls_mpi P; + mbedtls_mpi Q; +#endif + + new = pki_key_dup_common_init(key, demote); + if (new == NULL) { + return NULL; + } + +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); +#endif + + switch(key->type) { + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa, *new_rsa; + + new->pk = malloc(sizeof(mbedtls_pk_context)); + if (new->pk == NULL) { + goto fail; + } + + mbedtls_pk_init(new->pk); + pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + mbedtls_pk_setup(new->pk, pk_info); + + if (!mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(new->pk, MBEDTLS_PK_RSA)) { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->pk); + new_rsa = mbedtls_pk_rsa(*new->pk); + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } +#else + rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->E, &rsa->E); + if (rc != 0) { + goto fail; + } + + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; + + rc = mbedtls_mpi_copy(&new_rsa->D, &rsa->D); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->P, &rsa->P); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->Q, &rsa->Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->DP, &rsa->DP); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->DQ, &rsa->DQ); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->QP, &rsa->QP); + if (rc != 0) { + goto fail; + } +#endif + } else { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } +#else + rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->E, &rsa->E); + if (rc != 0) { + goto fail; + } + + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; +#endif + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_complete(new_rsa); + if (rc != 0) { + goto fail; + } +#endif + + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: + new->ecdsa_nid = key->ecdsa_nid; + + new->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + + if (new->ecdsa == NULL) { + goto fail; + } + + mbedtls_ecdsa_init(new->ecdsa); + + if ((demote && ssh_key_is_private(key)) || + is_sk_key_type(key->type)) { + rc = mbedtls_ecp_copy(&new->ecdsa->MBEDTLS_PRIVATE(Q), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_group_copy(&new->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(grp)); + if (rc != 0) { + goto fail; + } + } else { + mbedtls_ecdsa_from_keypair(new->ecdsa, key->ecdsa); + } + + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_ed25519_key_dup(new, key); + if (rc != SSH_OK) { + goto fail; + } + break; + default: + goto fail; + } + + goto cleanup; + +fail: + SSH_KEY_FREE(new); +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); +#endif + return new; +} + +int pki_key_generate_rsa(ssh_key key, int parameter) +{ + int rc; + const mbedtls_pk_info_t *info = NULL; + + if (parameter == 0) { + parameter = RSA_DEFAULT_KEY_SIZE; + } + + key->pk = malloc(sizeof(mbedtls_pk_context)); + if (key->pk == NULL) { + return SSH_ERROR; + } + + mbedtls_pk_init(key->pk); + + info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + rc = mbedtls_pk_setup(key->pk, info); + if (rc != 0) { + return SSH_ERROR; + } + + if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA)) { + rc = mbedtls_rsa_gen_key(mbedtls_pk_rsa(*key->pk), + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context(), + parameter, + 65537); + if (rc != 0) { + mbedtls_pk_free(key->pk); + return SSH_ERROR; + } + } + + return SSH_OK; +} + +int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + int rc = 0; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N1; + mbedtls_mpi N2; + mbedtls_mpi P1; + mbedtls_mpi P2; + mbedtls_mpi Q1; + mbedtls_mpi Q2; + mbedtls_mpi E1; + mbedtls_mpi E2; + + mbedtls_mpi_init(&N1); + mbedtls_mpi_init(&N2); + mbedtls_mpi_init(&P1); + mbedtls_mpi_init(&P2); + mbedtls_mpi_init(&Q1); + mbedtls_mpi_init(&Q2); + mbedtls_mpi_init(&E1); + mbedtls_mpi_init(&E2); +#endif + + switch (ssh_key_type_plain(k1->type)) { + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa1, *rsa2; + if (!mbedtls_pk_can_do(k1->pk, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(k2->pk, MBEDTLS_PK_RSA)) { + break; + } + + if (mbedtls_pk_get_type(k1->pk) != mbedtls_pk_get_type(k2->pk) || + mbedtls_pk_get_bitlen(k1->pk) != + mbedtls_pk_get_bitlen(k2->pk)) { + rc = 1; + goto cleanup; + } + + if (what == SSH_KEY_CMP_PUBLIC) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->pk); + rc = mbedtls_rsa_export(rsa1, &N1, NULL, NULL, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + rsa2 = mbedtls_pk_rsa(*k2->pk); + rc = mbedtls_rsa_export(rsa2, &N2, NULL, NULL, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } +#else + rsa1 = mbedtls_pk_rsa(*k1->pk); + rsa2 = mbedtls_pk_rsa(*k2->pk); + if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { + rc = 1; + goto cleanup; + } +#endif + } else if (what == SSH_KEY_CMP_PRIVATE) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->pk); + rc = mbedtls_rsa_export(rsa1, &N1, &P1, &Q1, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + rsa2 = mbedtls_pk_rsa(*k2->pk); + rc = mbedtls_rsa_export(rsa2, &N2, &P2, &Q2, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&P1, &P2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&Q1, &Q2) != 0) { + rc = 1; + goto cleanup; + } +#else + rsa1 = mbedtls_pk_rsa(*k1->pk); + rsa2 = mbedtls_pk_rsa(*k2->pk); + if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) { + rc = 1; + goto cleanup; + } +#endif + } + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: { + mbedtls_ecp_keypair *ecdsa1 = k1->ecdsa; + mbedtls_ecp_keypair *ecdsa2 = k2->ecdsa; + + if (ecdsa1->MBEDTLS_PRIVATE(grp).id != + ecdsa2->MBEDTLS_PRIVATE(grp).id) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X))) + { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y))) + { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z))) + { + rc = 1; + goto cleanup; + } + + if (what == SSH_KEY_CMP_PRIVATE && + k1->type != SSH_KEYTYPE_SK_ECDSA) { + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(d), + &ecdsa2->MBEDTLS_PRIVATE(d))) + { + rc = 1; + goto cleanup; + } + } + + break; + } + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + /* ed25519 keys handled globally */ + rc = 1; + break; + default: + rc = 1; + break; + } + +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N1); + mbedtls_mpi_free(&N2); + mbedtls_mpi_free(&P1); + mbedtls_mpi_free(&P2); + mbedtls_mpi_free(&Q1); + mbedtls_mpi_free(&Q2); + mbedtls_mpi_free(&E1); + mbedtls_mpi_free(&E2); +#endif + return rc; +} + +ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const + mbedtls_ecp_point *p) +{ + ssh_string s = NULL; + size_t len = 1; + int rc; + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + rc = mbedtls_ecp_point_write_binary(g, p, MBEDTLS_ECP_PF_UNCOMPRESSED, + &len, ssh_string_data(s), ssh_string_len(s)); + if (rc == MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL) { + SSH_STRING_FREE(s); + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + rc = mbedtls_ecp_point_write_binary(g, p, MBEDTLS_ECP_PF_UNCOMPRESSED, + &len, ssh_string_data(s), ssh_string_len(s)); + } + + if (rc != 0) { + SSH_STRING_FREE(s); + return NULL; + } + + if (len != ssh_string_len(s)) { + SSH_STRING_FREE(s); + return NULL; + } + + return s; +} + +static const char* pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_mbedtls_nistp256: + return "nistp256"; + case NID_mbedtls_nistp384: + return "nistp384"; + case NID_mbedtls_nistp521: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + ssh_string str = NULL; + int rc; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi E = {0}; + mbedtls_mpi N = {0}; + mbedtls_mpi D = {0}; + mbedtls_mpi IQMP = {0}; + mbedtls_mpi P = {0}; + mbedtls_mpi Q = {0}; + + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&IQMP); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); +#endif + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); + + if (key->cert != NULL) { + rc = ssh_buffer_add_buffer(buffer, key->cert); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + goto makestring; + } + + type_s = ssh_string_from_char(key->type_c); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa = NULL; + mbedtls_mpi *E_ptr = NULL, *N_ptr = NULL; + + if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) == 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rsa = mbedtls_pk_rsa(*key->pk); +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto out; + } + + E_ptr = &E; + N_ptr = &N; +#else + E_ptr = &rsa->E; + N_ptr = &rsa->N; +#endif + + e = ssh_make_bignum_string(E_ptr); + if (e == NULL) { + goto out; + } + + n = ssh_make_bignum_string(N_ptr); + if (n == NULL) { + goto out; + } + + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto out; + } + } else if (type == SSH_KEY_PRIVATE) { + mbedtls_mpi *P_ptr = NULL, *Q_ptr = NULL, *D_ptr = NULL; + mbedtls_mpi *IQMP_ptr = NULL; + + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, NULL, &P, &Q, &D, NULL); + if (rc != 0) { + goto out; + } + + rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP); + if (rc != 0) { + goto out; + } + + P_ptr = &P; + Q_ptr = &Q; + D_ptr = &D; + IQMP_ptr = &IQMP; +#else + P_ptr = &rsa->P; + Q_ptr = &rsa->Q; + D_ptr = &rsa->D; + IQMP_ptr = &rsa->QP; +#endif + + p = ssh_make_bignum_string(P_ptr); + if (p == NULL) { + goto out; + } + + q = ssh_make_bignum_string(Q_ptr); + if (q == NULL) { + goto out; + } + + d = ssh_make_bignum_string(D_ptr); + if (d == NULL) { + goto out; + } + + iqmp = ssh_make_bignum_string(IQMP_ptr); + if (iqmp == NULL) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto out; + } + } + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: + type_s = + ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); + + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } + + if (type == SSH_KEY_PRIVATE && key->type != SSH_KEYTYPE_SK_ECDSA) { + d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d)); + + if (d == NULL) { + SSH_BUFFER_FREE(buffer); + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto out; + } + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ECDSA) { + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + goto out; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto out; + } + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto out; + } + } + } else { + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + } + } + break; + default: + goto out; + } +makestring: + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL) { + goto out; + } + + rc = ssh_string_fill(str, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc < 0) { + ssh_string_burn(str); + SSH_STRING_FREE(str); + } + +out: + SSH_BUFFER_FREE(buffer); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&IQMP); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); +#endif + + return str; +} + +ssh_string pki_signature_to_blob(const ssh_signature sig) +{ + ssh_string sig_blob = NULL; + + switch(sig->type) { + case SSH_KEYTYPE_RSA: + sig_blob = ssh_string_copy(sig->rsa_sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: { + ssh_string r = NULL; + ssh_string s = NULL; + ssh_buffer b = NULL; + int rc; + + b = ssh_buffer_new(); + if (b == NULL) { + return NULL; + } + + r = ssh_make_bignum_string(sig->ecdsa_sig.r); + if (r == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, r); + SSH_STRING_FREE(r); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + s = ssh_make_bignum_string(sig->ecdsa_sig.s); + if (s == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, s); + SSH_STRING_FREE(s); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + sig_blob = ssh_string_new(ssh_buffer_get_len(b)); + if (sig_blob == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b)); + SSH_BUFFER_FREE(b); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + + break; + } + case SSH_KEYTYPE_ED25519: + sig_blob = pki_ed25519_signature_to_blob(sig); + break; + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ED25519: + /* For SK keys, signature data is already in raw_sig */ + sig_blob = ssh_string_copy(sig->raw_sig); + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", + sig->type_c); + return NULL; + } + + return sig_blob; +} + +static ssh_signature pki_signature_from_rsa_blob(const ssh_key pubkey, const + ssh_string sig_blob, ssh_signature sig) +{ + size_t pad_len = 0; + char *blob_orig = NULL; + char *blob_padded_data = NULL; + ssh_string sig_blob_padded = NULL; + + size_t rsalen = 0; + size_t len = ssh_string_len(sig_blob); + + if (pubkey->pk == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Pubkey RSA field NULL"); + goto errout; + } + + rsalen = mbedtls_pk_get_bitlen(pubkey->pk) / 8; + if (len > rsalen) { + SSH_LOG(SSH_LOG_TRACE, + "Signature is too big: %lu > %lu", + (unsigned long) len, + (unsigned long) rsalen); + goto errout; + } +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_TRACE, "RSA signature len: %lu", (unsigned long)len); + ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); +#endif + + if (len == rsalen) { + sig->rsa_sig = ssh_string_copy(sig_blob); + } else { + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len %lu < %lu", + (unsigned long) len, + (unsigned long) rsalen); + pad_len = rsalen - len; + + sig_blob_padded = ssh_string_new(rsalen); + if (sig_blob_padded == NULL) { + goto errout; + } + + blob_padded_data = (char *) ssh_string_data(sig_blob_padded); + blob_orig = (char *) ssh_string_data(sig_blob); + + ssh_burn(blob_padded_data, pad_len); + memcpy(blob_padded_data + pad_len, blob_orig, len); + + sig->rsa_sig = sig_blob_padded; + } + + return sig; + +errout: + ssh_signature_free(sig); + return NULL; +} +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + ssh_signature sig = NULL; + int rc; + + if (ssh_key_type_plain(pubkey->type) != type) { + SSH_LOG(SSH_LOG_TRACE, + "Incompatible public key provided (%d) expecting (%d)", + type, + pubkey->type); + return NULL; + } + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + sig->type_c = ssh_key_signature_to_char(type, hash_type); + sig->hash_type = hash_type; + + switch(type) { + case SSH_KEYTYPE_RSA: + sig = pki_signature_from_rsa_blob(pubkey, sig_blob, sig); + if (sig == NULL) { + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: { + ssh_buffer b = NULL; + ssh_string r = NULL; + ssh_string s = NULL; + size_t rlen; + + b = ssh_buffer_new(); + if (b == NULL) { + ssh_signature_free(sig); + return NULL; + } + + rc = ssh_buffer_add_data(b, ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + + if (rc < 0) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + r = ssh_buffer_get_ssh_string(b); + if (r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); +#endif + sig->ecdsa_sig.r = ssh_make_string_bn(r); + ssh_string_burn(r); + SSH_STRING_FREE(r); + if (sig->ecdsa_sig.r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + s = ssh_buffer_get_ssh_string(b); + rlen = ssh_buffer_get_len(b); + SSH_BUFFER_FREE(b); + if (s == NULL) { + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); +#endif + sig->ecdsa_sig.s = ssh_make_string_bn(s); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (sig->ecdsa_sig.s == NULL) { + ssh_signature_free(sig); + return NULL; + } + + if (rlen != 0) { + SSH_LOG(SSH_LOG_TRACE, "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + ssh_signature_free(sig); + return NULL; + } + + break; + } + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc == SSH_ERROR) { + ssh_signature_free(sig); + return NULL; + } + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); + return NULL; + } + + return sig; +} + +static ssh_string rsa_do_sign_hash(const unsigned char *digest, + int dlen, + mbedtls_pk_context *privkey, + enum ssh_digest_e hash_type) +{ + ssh_string sig_blob = NULL; + mbedtls_md_type_t md = 0; + unsigned char *sig = NULL; + size_t slen; + size_t sig_size; + int ok; + + switch (hash_type) { + case SSH_DIGEST_SHA1: + md = MBEDTLS_MD_SHA1; + break; + case SSH_DIGEST_SHA256: + md = MBEDTLS_MD_SHA256; + break; + case SSH_DIGEST_SHA512: + md = MBEDTLS_MD_SHA512; + break; + case SSH_DIGEST_AUTO: + default: + SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); + return NULL; + } + + sig_size = mbedtls_pk_get_bitlen(privkey) / 8; + sig = malloc(sig_size); + if (sig == NULL) { + return NULL; + } + + ok = mbedtls_pk_sign(privkey, + md, + digest, + dlen, + sig, +#if MBEDTLS_VERSION_MAJOR > 2 + sig_size, +#endif + &slen, + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + + if (ok != 0) { + SAFE_FREE(sig); + return NULL; + } + + sig_blob = ssh_string_new(slen); + if (sig_blob == NULL) { + SAFE_FREE(sig); + return NULL; + } + + ok = ssh_string_fill(sig_blob, sig, slen); + ssh_burn(sig, slen); + SAFE_FREE(sig); + if (ok < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + + return sig_blob; +} + + +ssh_signature pki_do_sign_hash(const ssh_key privkey, + const unsigned char *hash, + size_t hlen, + enum ssh_digest_e hash_type) +{ + ssh_signature sig = NULL; + int rc; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = privkey->type; + sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); + sig->hash_type = hash_type; + + switch(privkey->type) { + case SSH_KEYTYPE_RSA: + sig->rsa_sig = rsa_do_sign_hash(hash, hlen, privkey->pk, hash_type); + if (sig->rsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + sig->ecdsa_sig.r = bignum_new(); + if (sig->ecdsa_sig.r == NULL) { + return NULL; + } + + sig->ecdsa_sig.s = bignum_new(); + if (sig->ecdsa_sig.s == NULL) { + bignum_safe_free(sig->ecdsa_sig.r); + return NULL; + } + + rc = mbedtls_ecdsa_sign(&privkey->ecdsa->MBEDTLS_PRIVATE(grp), + sig->ecdsa_sig.r, + sig->ecdsa_sig.s, + &privkey->ecdsa->MBEDTLS_PRIVATE(d), + hash, + hlen, + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + if (rc != 0) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + rc = pki_ed25519_sign(privkey, sig, hash, hlen); + if (rc != SSH_OK) { + ssh_signature_free(sig); + return NULL; + } + break; + default: + ssh_signature_free(sig); + return NULL; + + } + + return sig; +} + +/** + * @internal + * + * @brief Sign the given input data. The digest of to be signed is calculated + * internally as necessary. + * + * @param[in] privkey The private key to be used for signing. + * @param[in] hash_type The digest algorithm to be used. + * @param[in] input The data to be signed. + * @param[in] input_len The length of the data to be signed. + * + * @return a newly allocated ssh_signature or NULL on error. + */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + unsigned char hash[SHA512_DIGEST_LEN] = {0}; + const unsigned char *sign_input = NULL; + uint32_t hlen = 0; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_sign_data()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + switch (hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_AUTO: + if (privkey->type == SSH_KEYTYPE_ED25519) { + /* SSH_DIGEST_AUTO should only be used with ed25519 */ + sign_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d", + hash_type); + return NULL; + } + + return pki_do_sign_hash(privkey, sign_input, hlen, hash_type); +} + +/** + * @internal + * + * @brief Verify the signature of a given input. The digest of the input is + * calculated internally as necessary. + * + * @param[in] signature The signature to be verified. + * @param[in] pubkey The public key used to verify the signature. + * @param[in] input The signed data. + * @param[in] input_len The length of the signed data. + * + * @return SSH_OK if the signature is valid; SSH_ERROR otherwise. + */ +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len) +{ + + unsigned char hash[SHA512_DIGEST_LEN] = {0}; + const unsigned char *verify_input = NULL; + uint32_t hlen = 0; + + mbedtls_md_type_t md = 0; + + int rc; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_verify_data_signature()"); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + switch (signature->hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + md = MBEDTLS_MD_SHA256; + verify_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + md = MBEDTLS_MD_SHA384; + verify_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + md = MBEDTLS_MD_SHA512; + verify_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + md = MBEDTLS_MD_SHA1; + verify_input = hash; + break; + case SSH_DIGEST_AUTO: + if (pubkey->type == SSH_KEYTYPE_ED25519 || + pubkey->type == SSH_KEYTYPE_ED25519_CERT01 || + pubkey->type == SSH_KEYTYPE_SK_ED25519 || + pubkey->type == SSH_KEYTYPE_ED25519_CERT01) + { + verify_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown sig->hash_type: %d", + signature->hash_type); + return SSH_ERROR; + } + + switch (pubkey->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + rc = mbedtls_pk_verify(pubkey->pk, + md, + hash, + hlen, + ssh_string_data(signature->rsa_sig), + ssh_string_len(signature->rsa_sig)); + if (rc != 0) { + char error_buf[100]; + mbedtls_strerror(rc, error_buf, 100); + SSH_LOG(SSH_LOG_TRACE, "RSA error: %s", error_buf); + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->MBEDTLS_PRIVATE(grp), hash, + hlen, &pubkey->ecdsa->MBEDTLS_PRIVATE(Q), + signature->ecdsa_sig.r, + signature->ecdsa_sig.s); + if (rc != 0) { + char error_buf[100]; + mbedtls_strerror(rc, error_buf, 100); + SSH_LOG(SSH_LOG_TRACE, "ECDSA error: %s", error_buf); + return SSH_ERROR; + + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + rc = pki_ed25519_verify(pubkey, signature, verify_input, hlen); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "ED25519 error: Signature invalid"); + return SSH_ERROR; + } + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type"); + return SSH_ERROR; + } + + return SSH_OK; +} + +const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_mbedtls_nistp256: + return "ecdsa-sha2-nistp256"; + case NID_mbedtls_nistp384: + return "ecdsa-sha2-nistp384"; + case NID_mbedtls_nistp521: + return "ecdsa-sha2-nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + if (strcmp(name, "nistp256") == 0) { + return NID_mbedtls_nistp256; + } else if (strcmp(name, "nistp384") == 0) { + return NID_mbedtls_nistp384; + } else if (strcmp(name, "nistp521") == 0) { + return NID_mbedtls_nistp521; + } + + return -1; +} + +static mbedtls_ecp_group_id pki_key_ecdsa_nid_to_mbed_gid(int nid) +{ + switch (nid) { + case NID_mbedtls_nistp256: + return MBEDTLS_ECP_DP_SECP256R1; + case NID_mbedtls_nistp384: + return MBEDTLS_ECP_DP_SECP384R1; + case NID_mbedtls_nistp521: + return MBEDTLS_ECP_DP_SECP521R1; + } + + return MBEDTLS_ECP_DP_NONE; +} + +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) +{ + int rc; + mbedtls_ecp_keypair keypair; + mbedtls_ecp_group group; + mbedtls_ecp_point Q; + + key->ecdsa_nid = nid; + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + mbedtls_ecdsa_init(key->ecdsa); + mbedtls_ecp_keypair_init(&keypair); + mbedtls_ecp_group_init(&group); + mbedtls_ecp_point_init(&Q); + + rc = mbedtls_ecp_group_load(&group, + pki_key_ecdsa_nid_to_mbed_gid(nid)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_point_read_binary(&group, &Q, ssh_string_data(e), + ssh_string_len(e)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_read_binary(&keypair.MBEDTLS_PRIVATE(d), + ssh_string_data(exp), + ssh_string_len(exp)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair); + if (rc != 0) { + goto fail; + } + + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + return SSH_OK; + +fail: + mbedtls_ecdsa_free(key->ecdsa); + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + SAFE_FREE(key->ecdsa); + return SSH_ERROR; +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + int rc; + mbedtls_ecp_keypair keypair; + mbedtls_ecp_group group; + mbedtls_ecp_point Q; + + key->ecdsa_nid = nid; + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + mbedtls_ecdsa_init(key->ecdsa); + mbedtls_ecp_keypair_init(&keypair); + mbedtls_ecp_group_init(&group); + mbedtls_ecp_point_init(&Q); + + rc = mbedtls_ecp_group_load(&group, + pki_key_ecdsa_nid_to_mbed_gid(nid)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_point_read_binary(&group, &Q, ssh_string_data(e), + ssh_string_len(e)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); + if (rc != 0) { + goto fail; + } + + mbedtls_mpi_init(&keypair.MBEDTLS_PRIVATE(d)); + + rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair); + if (rc != 0) { + goto fail; + } + + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + return SSH_OK; +fail: + mbedtls_ecdsa_free(key->ecdsa); + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + SAFE_FREE(key->ecdsa); + return SSH_ERROR; +} + +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ + int ok; + + switch (parameter) { + case 384: + key->ecdsa_nid = NID_mbedtls_nistp384; + key->type = SSH_KEYTYPE_ECDSA_P384; + break; + case 521: + key->ecdsa_nid = NID_mbedtls_nistp521; + key->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 256: + default: + key->ecdsa_nid = NID_mbedtls_nistp256; + key->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + mbedtls_ecdsa_init(key->ecdsa); + + ok = mbedtls_ecdsa_genkey(key->ecdsa, + pki_key_ecdsa_nid_to_mbed_gid(key->ecdsa_nid), + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + + if (ok != 0) { + mbedtls_ecdsa_free(key->ecdsa); + SAFE_FREE(key->ecdsa); + } + + return SSH_OK; +} + +int ssh_key_size(ssh_key key) +{ + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return mbedtls_pk_get_bitlen(key->pk); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return 256; + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return 384; + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return 521; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +#ifdef WITH_PKCS11_URI +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) +{ + (void) uri_name; + (void) key; + (void) key_type; + SSH_LOG(SSH_LOG_WARN, + "mbedcrypto does not support PKCS #11"); + return SSH_ERROR; +} +#endif /* WITH_PKCS11_URI */ +#endif /* HAVE_LIBMBEDCRYPTO */ diff --git a/src/libs/libssh-0.12.2/src/pki_sk.c b/src/libs/libssh-0.12.2/src/pki_sk.c new file mode 100644 index 000000000000..5a25251c2795 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_sk.c @@ -0,0 +1,971 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/buffer.h" +#include "libssh/pki_context.h" +#include "libssh/pki_priv.h" +#include "libssh/pki_sk.h" +#include "libssh/sk_common.h" + +#include +#include + +#define DEFAULT_PIN_PROMPT "Enter SK PIN: " +#define PIN_BUF_SIZE 64 + +/** + * @addtogroup libssh_pki + * @{ + */ + +/** + * @brief Serialize FIDO2 attestation data into an SSH buffer + * + * Serializes the attestation certificate, signature, and authenticator data + * from a FIDO2 enrollment response into an SSH buffer in the + * "ssh-sk-attest-v01" format. + * + * @param[in] enroll_response The sk_enroll_response struct containing + * attestation data from FIDO2 enrollment + * @param[in,out] attestation_buffer SSH buffer to store the serialized + * attestation data + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int pki_sk_serialise_attestation_cert( + const struct sk_enroll_response *enroll_response, + ssh_buffer attestation_buffer) +{ + int rc; + + if (attestation_buffer == NULL || enroll_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Parameters cannot be NULL"); + return SSH_ERROR; + } + + /* Check if attestation data is available */ + if (enroll_response->attestation_cert == NULL || + enroll_response->attestation_cert_len == 0) { + SSH_LOG(SSH_LOG_INFO, "No attestation certificate available"); + return SSH_ERROR; + } + + if (enroll_response->signature == NULL || + enroll_response->signature_len == 0) { + SSH_LOG(SSH_LOG_INFO, "No attestation signature available"); + return SSH_ERROR; + } + + if (enroll_response->authdata == NULL || + enroll_response->authdata_len == 0) { + SSH_LOG(SSH_LOG_INFO, "No authenticator data available"); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(attestation_buffer, + "sdPdPdPds", + "ssh-sk-attest-v01", + (uint32_t)enroll_response->attestation_cert_len, + enroll_response->attestation_cert_len, + enroll_response->attestation_cert, + (uint32_t)enroll_response->signature_len, + enroll_response->signature_len, + enroll_response->signature, + (uint32_t)enroll_response->authdata_len, + enroll_response->authdata_len, + enroll_response->authdata, + (uint32_t)0, /* reserved flags */ + ""); /* reserved */ + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack attestation data into buffer"); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Create an ssh_key from an sk_enroll_response struct + * + * Constructs an ssh_key structure from an sk_enroll_response + * struct for both ECDSA and Ed25519 algorithms. + * + * @param[in] algorithm The algorithm type (SSH_SK_ECDSA or + * SSH_SK_ED25519) + * @param[in] application The application string (relying party ID) + * @param[in] enroll_response The sk_enroll_response struct containing key data + * @param[out] ssh_key_result Pointer to store the newly created ssh_key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int pki_sk_enroll_response_to_ssh_key( + int algorithm, + const char *application, + const struct sk_enroll_response *enroll_response, + ssh_key *ssh_key_result) +{ + ssh_key key_to_build = NULL; + ssh_string public_key_string = NULL; + int rc, ret = SSH_ERROR; + + /* Validate input parameters */ + if (ssh_key_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "ssh_key pointer cannot be NULL"); + return SSH_ERROR; + } + + *ssh_key_result = NULL; + + if (enroll_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Enrollment response cannot be NULL"); + return SSH_ERROR; + } + + /* Validate response data */ + if (enroll_response->public_key == NULL || + enroll_response->key_handle == NULL) { + SSH_LOG( + SSH_LOG_WARN, + "Invalid enrollment response: missing public key or key handle"); + return SSH_ERROR; + } + + key_to_build = ssh_key_new(); + if (key_to_build == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate new ssh_key"); + return SSH_ERROR; + } + + /* Set key type based on algorithm */ + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + key_to_build->type = SSH_KEYTYPE_SK_ECDSA; + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + key_to_build->type = SSH_KEYTYPE_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + goto out; + } + key_to_build->type_c = ssh_key_type_to_char(key_to_build->type); + + public_key_string = ssh_string_from_data(enroll_response->public_key, + enroll_response->public_key_len); + if (public_key_string == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create public key string"); + goto out; + } + + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + rc = pki_pubkey_build_ecdsa(key_to_build, + pki_key_ecdsa_nid_from_name("nistp256"), + public_key_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA public key"); + goto out; + } + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + rc = pki_pubkey_build_ed25519(key_to_build, public_key_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to build ED25519 public key"); + goto out; + } + break; + } + + /* Set security key specific fields */ + key_to_build->sk_application = ssh_string_from_char(application); + if (key_to_build->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create sk_application string"); + goto out; + } + + /* Set key handle */ + key_to_build->sk_key_handle = + ssh_string_from_data(enroll_response->key_handle, + enroll_response->key_handle_len); + if (key_to_build->sk_key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create sk_key_handle string"); + goto out; + } + + key_to_build->sk_reserved = ssh_string_from_data(NULL, 0); + if (key_to_build->sk_reserved == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create sk_reserved string"); + goto out; + } + + key_to_build->sk_flags = enroll_response->flags; + key_to_build->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + *ssh_key_result = key_to_build; + key_to_build = NULL; + ret = SSH_OK; + +out: + ssh_string_burn(public_key_string); + SSH_STRING_FREE(public_key_string); + SSH_KEY_FREE(key_to_build); + + return ret; +} + +int pki_sk_enroll_key(ssh_pki_ctx context, + enum ssh_keytypes_e key_type, + ssh_key *enrolled_key_result) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + + struct sk_enroll_response *enroll_response = NULL; + ssh_key enrolled_key = NULL; + + char pin_buf[PIN_BUF_SIZE] = {0}; + const char *pin_to_use = NULL; + + unsigned char random_challenge[32]; + const unsigned char *challenge = NULL; + size_t challenge_length = 0; + + ssh_buffer challenge_buffer = NULL; + ssh_buffer attestation = NULL; + + int rc, ret = SSH_ERROR; + int algorithm; + + /* Validate input parameters */ + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "SK context cannot be NULL"); + return SSH_ERROR; + } + + if (enrolled_key_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "Enrolled key result pointer cannot be NULL"); + return SSH_ERROR; + } + + /* Initialize output parameter */ + *enrolled_key_result = NULL; + + /* Clear any existing attestation data */ + SSH_BUFFER_FREE(context->sk_attestation_buffer); + + /* Get security key callbacks from context */ + sk_callbacks = context->sk_callbacks; + if (sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL"); + return SSH_ERROR; + } + + if (!ssh_callbacks_exists(sk_callbacks, enroll)) { + SSH_LOG(SSH_LOG_WARN, + "Security key enroll callback is not implemented"); + return SSH_ERROR; + } + + /* Validate required fields */ + if (context->sk_application == NULL || *context->sk_application == '\0') { + SSH_LOG(SSH_LOG_WARN, "Application identifier cannot be NULL or empty"); + return SSH_ERROR; + } + + /* Extract parameters from context */ + challenge_buffer = context->sk_challenge_buffer; + + /* Determine algorithm based on key type */ + switch (key_type) { +#ifdef HAVE_ECC + case SSH_KEYTYPE_SK_ECDSA: + algorithm = SSH_SK_ECDSA; + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_SK_ED25519: + algorithm = SSH_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, + "Unsupported key type for security key enrollment"); + goto out; + } + + /* Determine challenge to use */ + if (challenge_buffer == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Using randomly generated challenge"); + + rc = ssh_get_random(random_challenge, sizeof(random_challenge), 0); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARN, "Failed to generate random challenge"); + goto out; + } + + challenge = random_challenge; + challenge_length = sizeof(random_challenge); + + } else { + challenge_length = ssh_buffer_get_len(challenge_buffer); + if (challenge_length == 0) { + SSH_LOG(SSH_LOG_WARN, "Challenge buffer cannot be empty"); + goto out; + } + + challenge = ssh_buffer_get(challenge_buffer); + SSH_LOG(SSH_LOG_DEBUG, + "Using provided challenge of length %zu", + challenge_length); + } + + if (context->sk_pin_callback != NULL) { + rc = context->sk_pin_callback(DEFAULT_PIN_PROMPT, + pin_buf, + sizeof(pin_buf), + 0, + 0, + context->sk_userdata); + if (rc == SSH_OK) { + pin_to_use = pin_buf; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback"); + ssh_burn(pin_buf, sizeof(pin_buf)); + goto out; + } + } else { + SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN"); + } + + rc = sk_callbacks->enroll(algorithm, + challenge, + challenge_length, + context->sk_application, + context->sk_flags, + pin_to_use, + context->sk_callbacks_options, + &enroll_response); + ssh_burn(pin_buf, sizeof(pin_buf)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Security key enroll callback failed: %s (%d)", + ssh_sk_err_to_string(rc), + rc); + goto out; + } + + /* Convert SK enroll response to ssh_key */ + rc = pki_sk_enroll_response_to_ssh_key(algorithm, + context->sk_application, + enroll_response, + &enrolled_key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert enroll response to ssh_key"); + goto out; + } + + /* Try to serialize attestation data and store in context */ + attestation = ssh_buffer_new(); + if (attestation == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate attestation buffer"); + goto out; + } else { + rc = pki_sk_serialise_attestation_cert(enroll_response, attestation); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to serialize attestation data, continuing without " + "attestation"); + } else { + context->sk_attestation_buffer = attestation; + attestation = NULL; + } + } + + *enrolled_key_result = enrolled_key; + enrolled_key = NULL; + ret = SSH_OK; + +out: + if (challenge == random_challenge) { + ssh_burn(random_challenge, sizeof(random_challenge)); + } + + SK_ENROLL_RESPONSE_FREE(enroll_response); + SSH_KEY_FREE(enrolled_key); + SSH_BUFFER_FREE(attestation); + + return ret; +} + +static int +pki_sk_pack_ecdsa_signature(const struct sk_sign_response *sign_response, + ssh_buffer sig_buffer) +{ + + bignum r_bn = NULL, s_bn = NULL; + ssh_buffer inner_buffer = NULL; + int rc = SSH_ERROR; + + /* Convert raw r and s bytes to bignums */ + bignum_bin2bn(sign_response->sig_r, (int)sign_response->sig_r_len, &r_bn); + if (r_bn == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert sig_r to bignum"); + goto out; + } + + bignum_bin2bn(sign_response->sig_s, (int)sign_response->sig_s_len, &s_bn); + if (s_bn == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert sig_s to bignum"); + goto out; + } + + /* Create inner buffer with r and s as SSH strings */ + inner_buffer = ssh_buffer_new(); + if (inner_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create inner buffer"); + goto out; + } + ssh_buffer_set_secure(inner_buffer); + + rc = ssh_buffer_pack(inner_buffer, "BB", r_bn, s_bn); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack r and s into inner buffer"); + goto out; + } + + rc = ssh_buffer_pack(sig_buffer, + "P", + (size_t)ssh_buffer_get_len(inner_buffer), + ssh_buffer_get(inner_buffer)); + if (rc != SSH_OK) { + goto out; + } + + rc = SSH_OK; + +out: + SSH_BUFFER_FREE(inner_buffer); + bignum_safe_free(s_bn); + bignum_safe_free(r_bn); + + return rc; +} + +static int +pki_sk_pack_ed25519_signature(const struct sk_sign_response *sign_response, + ssh_buffer sig_buffer) +{ + int rc = SSH_ERROR; + + rc = ssh_buffer_pack(sig_buffer, + "P", + sign_response->sig_r_len, + sign_response->sig_r); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Create an ssh_signature from a sk_sign_response structure + * + * Serializes a security key sign response into an ssh_signature structure + * for both ECDSA and Ed25519 algorithms. + * + * @param[in] algorithm The algorithm used (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param[in] key_type The SSH key type for setting signature type + * @param[in] sign_response The sk_sign_response containing signature data + * @param[out] ssh_signature_result Pointer to store the created ssh_signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int pki_sk_sign_response_to_ssh_signature( + int algorithm, + enum ssh_keytypes_e key_type, + const struct sk_sign_response *sign_response, + ssh_signature *ssh_signature_result) +{ + ssh_signature signature_to_build = NULL; + ssh_buffer sig_buffer = NULL; + int rc; + + /* Validate input parameters */ + if (ssh_signature_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "ssh_signature pointer cannot be NULL"); + return SSH_ERROR; + } + + *ssh_signature_result = NULL; + + if (sign_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Sign response cannot be NULL"); + return SSH_ERROR; + } + + /* Validate response data based on algorithm */ + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + if (sign_response->sig_r == NULL || sign_response->sig_s == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Invalid ECDSA sign response: missing sig_r or sig_s"); + return SSH_ERROR; + } + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + if (sign_response->sig_r == NULL || + sign_response->sig_r_len != ED25519_SIG_LEN) { + SSH_LOG(SSH_LOG_WARN, "Invalid sig_r in Ed25519 sign response"); + return SSH_ERROR; + } + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + return SSH_ERROR; + } + + /* Create new ssh_signature */ + signature_to_build = ssh_signature_new(); + if (signature_to_build == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate new ssh_signature"); + return SSH_ERROR; + } + + /* Set signature type and metadata */ + signature_to_build->type = key_type; + signature_to_build->type_c = ssh_key_type_to_char(key_type); + + /* Set security key specific fields */ + signature_to_build->sk_flags = sign_response->flags; + signature_to_build->sk_counter = sign_response->counter; + + /* Create a buffer to hold the signature data */ + sig_buffer = ssh_buffer_new(); + if (sig_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create signature buffer"); + goto error; + } + ssh_buffer_set_secure(sig_buffer); + + /* Build the signature based on algorithm */ + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + signature_to_build->hash_type = SSH_DIGEST_SHA256; + + rc = pki_sk_pack_ecdsa_signature(sign_response, sig_buffer); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack ECDSA signature"); + goto error; + } + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + signature_to_build->hash_type = SSH_DIGEST_AUTO; + + rc = pki_sk_pack_ed25519_signature(sign_response, sig_buffer); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack Ed25519 signature"); + goto error; + } + break; + } + + /* Set the signature data */ + signature_to_build->raw_sig = + ssh_string_from_data(ssh_buffer_get(sig_buffer), + ssh_buffer_get_len(sig_buffer)); + if (signature_to_build->raw_sig == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create raw signature string"); + goto error; + } + + *ssh_signature_result = signature_to_build; + SSH_BUFFER_FREE(sig_buffer); + + return SSH_OK; + +error: + SSH_SIGNATURE_FREE(signature_to_build); + SSH_BUFFER_FREE(sig_buffer); + + return SSH_ERROR; +} + +ssh_signature pki_sk_do_sign(ssh_pki_ctx context, + const ssh_key key, + const unsigned char *data, + size_t data_len) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + struct sk_sign_response *sign_response = NULL; + ssh_signature signature = NULL; + + char pin_buf[PIN_BUF_SIZE] = {0}; + const char *pin_to_use = NULL; + + int algorithm; + int rc = SSH_ERROR; + + /* Validate input parameters */ + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context cannot be NULL"); + return NULL; + } + + /* Get security key callbacks from context */ + sk_callbacks = context->sk_callbacks; + if (sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL"); + return NULL; + } + + if (!ssh_callbacks_exists(sk_callbacks, sign)) { + SSH_LOG(SSH_LOG_WARN, "Security key sign callback is not implemented"); + return NULL; + } + + if (key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Key cannot be NULL"); + return NULL; + } + + if (data == NULL || data_len == 0) { + SSH_LOG(SSH_LOG_WARN, "Data cannot be NULL or empty"); + return NULL; + } + + /* Validate key type and determine algorithm */ + switch (key->type) { +#ifdef HAVE_ECC + case SSH_KEYTYPE_SK_ECDSA: + algorithm = SSH_SK_ECDSA; + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_SK_ED25519: + algorithm = SSH_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported key type for security key signing"); + return NULL; + } + + /* Validate security key specific fields */ + if (key->sk_key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key handle cannot be NULL"); + return NULL; + } + + if (key->sk_application == NULL || + ssh_string_len(key->sk_application) == 0) { + SSH_LOG(SSH_LOG_WARN, + "Security key application cannot be NULL or empty"); + return NULL; + } + + if (context->sk_pin_callback != NULL) { + rc = context->sk_pin_callback(DEFAULT_PIN_PROMPT, + pin_buf, + sizeof(pin_buf), + 0, + 0, + context->sk_userdata); + if (rc == SSH_OK) { + pin_to_use = pin_buf; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback"); + ssh_burn(pin_buf, sizeof(pin_buf)); + goto error; + } + } else { + SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN"); + } + + rc = sk_callbacks->sign(algorithm, + data, + data_len, + ssh_string_get_char(key->sk_application), + ssh_string_data(key->sk_key_handle), + ssh_string_len(key->sk_key_handle), + key->sk_flags, + pin_to_use, + context->sk_callbacks_options, + &sign_response); + ssh_burn(pin_buf, sizeof(pin_buf)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Security key sign callback failed: %s (%d)", + ssh_sk_err_to_string(rc), + rc); + goto error; + } + + /* Convert SK sign response to ssh_signature */ + rc = pki_sk_sign_response_to_ssh_signature(algorithm, + key->type, + sign_response, + &signature); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert sign response to signature"); + goto error; + } + + SK_SIGN_RESPONSE_FREE(sign_response); + return signature; + +error: + SK_SIGN_RESPONSE_FREE(sign_response); + SSH_SIGNATURE_FREE(signature); + return NULL; +} + +/** + * @brief Load resident keys from FIDO2 security keys + * + * This function loads all resident keys (discoverable credentials) stored + * on FIDO2 security keys using the context's security key callbacks. + * Resident keys are credentials stored directly on the security key device + * and can be discovered without prior knowledge of key handles. + * + * Only resident keys with SSH application identifiers (starting with + * "ssh:") are returned. + * + * @param[in] pki_context The PKI context containing security key callbacks. + * Can be NULL, in which case a default context with + * default callbacks will be used. If provided, the context + * must have valid sk_callbacks configured. + * @param[out] resident_keys_result Array of ssh_key structs representing the + * resident keys found and loaded + * @param[out] num_keys_found_result Number of resident keys found and loaded + * + * @return SSH_OK on success, SSH_ERROR on error + * + * @note The resident_keys_result array and its contents must be freed by + * the caller using ssh_sk_resident_key_free() for each key and then + * freeing the array itself when no longer needed. + */ +int ssh_sk_resident_keys_load(const struct ssh_pki_ctx_struct *pki_context, + ssh_key **resident_keys_result, + size_t *num_keys_found_result) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + struct sk_resident_key **raw_resident_keys = NULL; + + ssh_key cur_resident_key = NULL, *result_keys = NULL, *temp_keys = NULL; + ssh_pki_ctx temp_ctx = NULL; + const struct ssh_pki_ctx_struct *ctx_to_use = NULL; + + size_t raw_keys_count = 0, result_keys_count = 0, i; + uint8_t sk_flags; + + char pin_buf[PIN_BUF_SIZE] = {0}; + const char *pin_to_use = NULL; + + int rc = SSH_ERROR; + + /* If no context provided, create a temporary default one */ + if (pki_context == NULL) { + SSH_LOG(SSH_LOG_INFO, "No PKI context provided, using the default one"); + + temp_ctx = ssh_pki_ctx_new(); + if (temp_ctx == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context"); + return SSH_ERROR; + } + ctx_to_use = temp_ctx; + } else { + ctx_to_use = pki_context; + } + + /* Get security key callbacks from context */ + sk_callbacks = ctx_to_use->sk_callbacks; + if (sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL"); + goto out; + } + + if (!ssh_callbacks_exists(sk_callbacks, load_resident_keys)) { + SSH_LOG(SSH_LOG_WARN, + "Security key load resident keys callback is not implemented"); + goto out; + } + + if (resident_keys_result == NULL || num_keys_found_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "Result pointers cannot be NULL"); + goto out; + } + + /* Initialize output parameters */ + *resident_keys_result = NULL; + *num_keys_found_result = 0; + + if (ctx_to_use->sk_pin_callback != NULL) { + rc = ctx_to_use->sk_pin_callback(DEFAULT_PIN_PROMPT, + pin_buf, + sizeof(pin_buf), + 0, + 0, + ctx_to_use->sk_userdata); + if (rc == SSH_OK) { + pin_to_use = pin_buf; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback"); + ssh_burn(pin_buf, sizeof(pin_buf)); + goto out; + } + } else { + SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN"); + } + + rc = sk_callbacks->load_resident_keys(pin_to_use, + ctx_to_use->sk_callbacks_options, + &raw_resident_keys, + &raw_keys_count); + ssh_burn(pin_buf, sizeof(pin_buf)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Security key load_resident_keys callback failed: %s (%d)", + ssh_sk_err_to_string(rc), + rc); + goto out; + } + + /* Process each raw resident key */ + for (i = 0; i < raw_keys_count; i++) { + SSH_LOG( + SSH_LOG_DEBUG, + "Processing resident key %zu: alg %d, app \"%s\", user_id_len %zu", + i, + raw_resident_keys[i]->alg, + raw_resident_keys[i]->application, + raw_resident_keys[i]->user_id_len); + + /* Filter out non-SSH applications */ + if (strncmp(raw_resident_keys[i]->application, "ssh:", 4) != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Skipping non-SSH application: %s", + raw_resident_keys[i]->application); + continue; + } + + /* Check supported algorithms */ + switch (raw_resident_keys[i]->alg) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + break; + default: + SSH_LOG(SSH_LOG_WARN, + "Unsupported algorithm %d, skipping", + raw_resident_keys[i]->alg); + continue; + } + + /* Set up security key flags */ + sk_flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY; + if (raw_resident_keys[i]->flags & SSH_SK_USER_VERIFICATION_REQD) { + sk_flags |= SSH_SK_USER_VERIFICATION_REQD; + } + + /* Convert raw resident key to libssh key structure */ + rc = + pki_sk_enroll_response_to_ssh_key(raw_resident_keys[i]->alg, + raw_resident_keys[i]->application, + &raw_resident_keys[i]->key, + &cur_resident_key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to convert resident key %zu to ssh_key", + i); + continue; + } + + /* Set the security key flags on the converted key */ + cur_resident_key->sk_flags = sk_flags; + + /* Copy user ID if present */ + if (raw_resident_keys[i]->user_id != NULL && + raw_resident_keys[i]->user_id_len > 0) { + + cur_resident_key->sk_user_id = + ssh_string_from_data(raw_resident_keys[i]->user_id, + raw_resident_keys[i]->user_id_len); + if (cur_resident_key->sk_user_id == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate user_id string for key %zu", + i); + goto out; + } + } + + /* Grow the result array */ + temp_keys = + realloc(result_keys, sizeof(ssh_key) * (result_keys_count + 1)); + if (temp_keys == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to reallocate result keys array"); + goto out; + } + + /* Add the current resident key to the result array */ + result_keys = temp_keys; + result_keys[result_keys_count] = cur_resident_key; + result_keys_count++; + cur_resident_key = NULL; + } + + /* Set output parameters */ + *resident_keys_result = result_keys; + *num_keys_found_result = result_keys_count; + result_keys = NULL; + result_keys_count = 0; + rc = SSH_OK; + +out: + + if (raw_resident_keys != NULL) { + for (i = 0; i < raw_keys_count; i++) { + SK_RESIDENT_KEY_FREE(raw_resident_keys[i]); + } + SAFE_FREE(raw_resident_keys); + } + + SSH_KEY_FREE(cur_resident_key); + for (i = 0; i < result_keys_count; i++) { + SSH_KEY_FREE(result_keys[i]); + } + SAFE_FREE(result_keys); + + /* Clean up temporary context if we created one */ + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + + return rc; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/poll.c b/src/libs/libssh-0.12.2/src/poll.c new file mode 100644 index 000000000000..23d18256cf24 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/poll.c @@ -0,0 +1,1216 @@ +/* + * poll.c - poll wrapper + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2003-2013 by Aris Adamantiadis + * Copyright (c) 2009 Aleksandar Kanchev + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/poll.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#endif + + +#ifndef SSH_POLL_CTX_CHUNK +#define SSH_POLL_CTX_CHUNK 5 +#endif + +/** + * @defgroup libssh_poll The SSH poll functions + * @ingroup libssh + * + * Add a generic way to handle sockets asynchronously. + * + * It's based on poll objects, each of which store a socket, its events and a + * callback, which gets called whenever an event is set. The poll objects are + * attached to a poll context, which should be allocated on a per thread basis. + * + * Polling the poll context will poll all the attached poll objects and call + * their callbacks (handlers) if any of the socket events are set. This should + * be done within the main loop of an application. + * + * @{ + */ + +struct ssh_poll_handle_struct { + ssh_poll_ctx ctx; + ssh_session session; + union { + socket_t fd; + size_t idx; + } x; + short events; + uint32_t lock_cnt; + ssh_poll_callback cb; + void *cb_data; +}; + +struct ssh_poll_ctx_struct { + ssh_poll_handle *pollptrs; + ssh_pollfd_t *pollfds; + size_t polls_allocated; + size_t polls_used; + size_t chunk_size; +}; + +#ifdef HAVE_POLL +#include + +void ssh_poll_init(void) +{ + return; +} + +void ssh_poll_cleanup(void) +{ + return; +} + +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ + return poll((struct pollfd *)fds, nfds, timeout); +} + +#else /* HAVE_POLL */ + +typedef int (*poll_fn)(ssh_pollfd_t *, nfds_t, int); +static poll_fn ssh_poll_emu; + +#include +#include + +#ifdef _WIN32 +#ifndef STRICT +#define STRICT +#endif /* STRICT */ + +#include +#include +#include +#else /* _WIN32 */ +#include +#include + +# ifdef HAVE_SYS_TIME_H +# include +# endif + +#endif /* _WIN32 */ + +#ifdef HAVE_UNISTD_H +#include +#endif + +static bool bsd_socket_not_connected(int sock_err) +{ + switch (sock_err) { +#ifdef _WIN32 + case WSAENOTCONN: +#else + case ENOTCONN: +#endif + return true; + default: + return false; + } + + return false; +} + +static bool bsd_socket_reset(int sock_err) +{ + switch (sock_err) { +#ifdef _WIN32 + case WSAECONNABORTED: + case WSAECONNRESET: + case WSAENETRESET: + case WSAESHUTDOWN: + case WSAECONNREFUSED: + case WSAETIMEDOUT: +#else + case ECONNABORTED: + case ECONNRESET: + case ENETRESET: + case ESHUTDOWN: +#endif + return true; + default: + return false; + } + + return false; +} + +static short bsd_socket_compute_revents(int fd, short events) +{ + int save_errno = errno; + int sock_errno = errno; + char data[64] = {0}; + short revents = 0; + int flags = MSG_PEEK; + int ret; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + /* support for POLLHUP */ +#ifdef _WIN32 + WSASetLastError(0); +#endif + + ret = recv(fd, data, 64, flags); + + errno = save_errno; + +#ifdef _WIN32 + sock_errno = WSAGetLastError(); + WSASetLastError(0); +#endif + + if (ret > 0 || bsd_socket_not_connected(sock_errno)) { + revents = (POLLIN | POLLRDNORM) & events; + } else if (ret == 0 || bsd_socket_reset(sock_errno)) { + errno = sock_errno; + revents = POLLHUP; + } else { + revents = POLLERR; + } + + return revents; +} + +/* + * This is a poll(2)-emulation using select for systems not providing a native + * poll implementation. + * + * Keep in mind that select is terribly inefficient. The interface is simply not + * meant to be used with maximum descriptor value greater than, say, 32 or so. + * With a value as high as 1024 on Linux you'll pay dearly in every single call. + * poll() will be orders of magnitude faster. + */ +static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ + fd_set readfds, writefds, exceptfds; + struct timeval tv, *ptv = NULL; + socket_t max_fd; + int rc; + nfds_t i; + + if (fds == NULL) { + errno = EFAULT; + return -1; + } + + ZERO_STRUCT(readfds); + FD_ZERO(&readfds); + ZERO_STRUCT(writefds); + FD_ZERO(&writefds); + ZERO_STRUCT(exceptfds); + FD_ZERO(&exceptfds); + + /* compute fd_sets and find largest descriptor */ + for (rc = -1, max_fd = 0, i = 0; i < nfds; i++) { + if (fds[i].fd == SSH_INVALID_SOCKET) { + continue; + } +#ifndef _WIN32 + if (fds[i].fd >= FD_SETSIZE) { + rc = -1; + break; + } +#endif + + // we use the readfds to get POLLHUP and POLLERR, which are provided + // even when not requested + FD_SET(fds[i].fd, &readfds); + + if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) { + FD_SET(fds[i].fd, &writefds); + } + if (fds[i].events & (POLLPRI | POLLRDBAND)) { + FD_SET(fds[i].fd, &exceptfds); + } + + if (fds[i].fd > max_fd) { + max_fd = fds[i].fd; + rc = 0; + } + } + + if (max_fd == SSH_INVALID_SOCKET || rc == -1) { + errno = EINVAL; + return -1; + } + + if (timeout < 0) { + ptv = NULL; + } else { + ptv = &tv; + if (timeout == 0) { + tv.tv_sec = 0; + tv.tv_usec = 0; + } else { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + } + } + + rc = select(max_fd + 1, &readfds, &writefds, &exceptfds, ptv); + if (rc < 0) { + return -1; + } + /* A timeout occurred */ + if (rc == 0) { + return 0; + } + + for (rc = 0, i = 0; i < nfds; i++) { + if (fds[i].fd >= 0) { + fds[i].revents = 0; + + if (FD_ISSET(fds[i].fd, &readfds)) { + fds[i].revents = + bsd_socket_compute_revents(fds[i].fd, fds[i].events); + } + if (FD_ISSET(fds[i].fd, &writefds)) { + fds[i].revents |= + fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND); + } + + if (FD_ISSET(fds[i].fd, &exceptfds)) { + fds[i].revents |= fds[i].events & (POLLPRI | POLLRDBAND); + } + + if (fds[i].revents != 0) { + rc++; + } + } else { + fds[i].revents = POLLNVAL; + } + } + + return rc; +} + +void ssh_poll_init(void) +{ + ssh_poll_emu = bsd_poll; +} + +void ssh_poll_cleanup(void) +{ + ssh_poll_emu = bsd_poll; +} + +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ + return (ssh_poll_emu)(fds, nfds, timeout); +} + +#endif /* HAVE_POLL */ + +/** + * @brief Allocate a new poll object, which could be used within a poll + * context. + * + * @param[in] fd Socket that will be polled. + * @param[in] events Poll events that will be monitored for the socket. + * i.e. POLLIN, POLLPRI, POLLOUT + * @param[in] cb Function to be called if any of the events are set. + * The prototype of cb is: + * int (*ssh_poll_callback)(ssh_poll_handle p, + * socket_t fd, + * int revents, + * void *userdata); + * @param[in] userdata Userdata to be passed to the callback function. + * NULL if not needed. + * + * @return A new poll object, NULL on error + */ + +ssh_poll_handle +ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, void *userdata) +{ + ssh_poll_handle p = NULL; + + p = malloc(sizeof(struct ssh_poll_handle_struct)); + if (p == NULL) { + return NULL; + } + ZERO_STRUCTP(p); + + p->x.fd = fd; + p->events = events; + p->cb = cb; + p->cb_data = userdata; + + return p; +} + +/** + * @brief Free a poll object. + * + * @param p Pointer to an already allocated poll object. + */ + +void ssh_poll_free(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + ssh_poll_ctx_remove(p->ctx, p); + p->ctx = NULL; + } + SAFE_FREE(p); +} + +/** + * @brief Get the poll context of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Poll context or NULL if the poll object isn't attached. + */ +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) +{ + return p->ctx; +} + +/** + * @brief Get the events of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Poll events. + */ +short ssh_poll_get_events(ssh_poll_handle p) +{ + return p->events; +} + +/** + * @brief Set the events of a poll object. The events will also be propagated + * to an associated poll context unless the fd is locked. In that case, + * only the POLLOUT can be set. + * + * @param p Pointer to an already allocated poll object. + * @param events Poll events. + */ +void ssh_poll_set_events(ssh_poll_handle p, short events) +{ + p->events = events; + if (p->ctx != NULL) { + if (!ssh_poll_is_locked(p)) { + p->ctx->pollfds[p->x.idx].events = events; + } else if (!(p->ctx->pollfds[p->x.idx].events & POLLOUT)) { + /* if locked, allow only setting POLLOUT to prevent recursive + * callbacks */ + p->ctx->pollfds[p->x.idx].events = events & POLLOUT; + } + } +} + +/** + * @brief Set the file descriptor of a poll object. The FD will also be + * propagated to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param fd New file descriptor. + */ +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) +{ + if (p->ctx != NULL) { + p->ctx->pollfds[p->x.idx].fd = fd; + } else { + p->x.fd = fd; + } +} + +/** + * @brief Add extra events to a poll object. Duplicates are ignored. + * The events will also be propagated to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param events Poll events. + */ +void ssh_poll_add_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) | events); +} + +/** + * @brief Remove events from a poll object. Non-existent are ignored. + * The events will also be propagated to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param events Poll events. + */ +void ssh_poll_remove_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events); +} + +/** + * @brief Get the raw socket of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Raw socket. + */ + +socket_t ssh_poll_get_fd(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + return p->ctx->pollfds[p->x.idx].fd; + } + + return p->x.fd; +} +/** + * @brief Set the callback of a poll object. + * + * @param p Pointer to an already allocated poll object. + * @param cb Function to be called if any of the events are set. + * @param userdata Userdata to be passed to the callback function. NULL if + * not needed. + */ +void ssh_poll_set_callback(ssh_poll_handle p, + ssh_poll_callback cb, + void *userdata) +{ + if (cb != NULL) { + p->cb = cb; + p->cb_data = userdata; + } +} + +/** + * @brief Create a new poll context. It could be associated with many poll object + * which are going to be polled at the same time as the poll context. You + * would need a single poll context per thread. + * + * @param chunk_size The size of the memory chunk that will be allocated, when + * more memory is needed. This is for efficiency reasons, + * i.e. don't allocate memory for each new poll object, but + * for the next 5. Set it to 0 if you want to use the + * library's default value. + */ +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) +{ + ssh_poll_ctx ctx; + + ctx = malloc(sizeof(struct ssh_poll_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + ZERO_STRUCTP(ctx); + + if (chunk_size == 0) { + chunk_size = SSH_POLL_CTX_CHUNK; + } + + ctx->chunk_size = chunk_size; + + return ctx; +} + +/** + * @brief Free a poll context. + * + * @param ctx Pointer to an already allocated poll context. + */ +void ssh_poll_ctx_free(ssh_poll_ctx ctx) +{ + if (ctx->polls_allocated > 0) { + while (ctx->polls_used > 0) { + ssh_poll_handle p = ctx->pollptrs[0]; + /* + * The free function calls ssh_poll_ctx_remove() and decrements + * ctx->polls_used + */ + ssh_poll_free(p); + } + + SAFE_FREE(ctx->pollptrs); + SAFE_FREE(ctx->pollfds); + } + + SAFE_FREE(ctx); +} + +static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) +{ + ssh_poll_handle *pollptrs = NULL; + ssh_pollfd_t *pollfds = NULL; + + pollptrs = realloc(ctx->pollptrs, sizeof(ssh_poll_handle) * new_size); + if (pollptrs == NULL) { + /* Fail, but keep the old value to be freed later */ + return SSH_ERROR; + } + ctx->pollptrs = pollptrs; + + pollfds = realloc(ctx->pollfds, sizeof(ssh_pollfd_t) * new_size); + if (pollfds == NULL) { + if (ctx->polls_allocated == 0) { + /* This was initial allocation -- just free what we allocated above + * and fail */ + SAFE_FREE(ctx->pollptrs); + return SSH_ERROR; + } + /* Try to realloc the pollptrs back to the original size */ + pollptrs = realloc(ctx->pollptrs, + sizeof(ssh_poll_handle) * ctx->polls_allocated); + if (pollptrs == NULL) { + return SSH_ERROR; + } + ctx->pollptrs = pollptrs; + return SSH_ERROR; + } + + ctx->pollfds = pollfds; + ctx->polls_allocated = new_size; + + return SSH_OK; +} + +/** + * @brief Add a poll object to a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param p Pointer to an already allocated poll object. + * + * @return 0 on success, < 0 on error + */ +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) +{ + socket_t fd; + + if (p->ctx != NULL) { + /* already attached to a context */ + return -1; + } + + if (ctx->polls_used == ctx->polls_allocated && + ssh_poll_ctx_resize(ctx, ctx->polls_allocated + ctx->chunk_size) < 0) { + return -1; + } + + fd = p->x.fd; + p->x.idx = ctx->polls_used++; + ctx->pollptrs[p->x.idx] = p; + ctx->pollfds[p->x.idx].fd = fd; + ctx->pollfds[p->x.idx].events = p->events; + ctx->pollfds[p->x.idx].revents = 0; + p->ctx = ctx; + + return 0; +} + +/** + * @brief Add a socket object to a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param s A SSH socket handle + * + * @return 0 on success, < 0 on error + */ +int ssh_poll_ctx_add_socket(ssh_poll_ctx ctx, ssh_socket s) +{ + ssh_poll_handle p = NULL; + + p = ssh_socket_get_poll_handle(s); + if (p == NULL) { + return -1; + } + return ssh_poll_ctx_add(ctx, p); +} + +/** + * @brief Remove a poll object from a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param p Pointer to an already allocated poll object. + */ +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) +{ + size_t i; + + i = p->x.idx; + p->x.fd = ctx->pollfds[i].fd; + p->ctx = NULL; + + ctx->polls_used--; + + /* fill the empty poll slot with the last one */ + if (ctx->polls_used > 0 && ctx->polls_used != i) { + ctx->pollfds[i] = ctx->pollfds[ctx->polls_used]; + ctx->pollptrs[i] = ctx->pollptrs[ctx->polls_used]; + ctx->pollptrs[i]->x.idx = i; + } + + /* this will always leave at least chunk_size polls allocated */ + if (ctx->polls_allocated - ctx->polls_used > ctx->chunk_size) { + ssh_poll_ctx_resize(ctx, ctx->polls_allocated - ctx->chunk_size); + } +} + +/** + * @brief Returns if a poll object is locked. + * + * @param p Pointer to an already allocated poll object. + * @returns true if the poll object is locked; false otherwise. + */ +bool ssh_poll_is_locked(ssh_poll_handle p) +{ + if (p == NULL) { + return false; + } + return p->lock_cnt > 0; +} + +/** + * @brief Poll all the sockets associated through a poll object with a + * poll context. If any of the events are set after the poll, the + * call back function of the socket will be called. + * This function should be called once within the program's main loop. + * + * @param ctx Pointer to an already allocated poll context. + * @param timeout An upper limit on the time for which ssh_poll_ctx() will + * block, in milliseconds. Specifying a negative value + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns SSH_OK No error. + * SSH_ERROR Error happened during the poll. + * SSH_AGAIN Timeout occurred + */ + +int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) +{ + int rc; + size_t i, used; + ssh_poll_handle p = NULL; + socket_t fd; + int revents; + struct ssh_timestamp ts; + + if (ctx->polls_used == 0) { + return SSH_ERROR; + } + + /* Allow only POLLOUT events on locked sockets as that means we are called + * recursively and we only want process the POLLOUT events here to flush + * output buffer */ + for (i = 0; i < ctx->polls_used; i++) { + /* The lock allows only POLLOUT events: drop the rest */ + if (ssh_poll_is_locked(ctx->pollptrs[i])) { + ctx->pollfds[i].events &= POLLOUT; + } + } + ssh_timestamp_init(&ts); + do { + int tm = ssh_timeout_update(&ts, timeout); + rc = ssh_poll(ctx->pollfds, ctx->polls_used, tm); + } while (rc == -1 && errno == EINTR); + + if (rc < 0) { + return SSH_ERROR; + } + if (rc == 0) { + return SSH_AGAIN; + } + + used = ctx->polls_used; + for (i = 0; i < used && rc > 0; ) { + revents = ctx->pollfds[i].revents; + /* Do not pass any other events except for POLLOUT to callback when + * called recursively more than 2 times. On s390x the poll will be + * spammed with POLLHUP events causing infinite recursion when the user + * callback issues some write/flush/poll calls. */ + if (ctx->pollptrs[i]->lock_cnt > 2) { + revents &= POLLOUT; + } + if (revents == 0) { + i++; + } else { + int ret; + + p = ctx->pollptrs[i]; + fd = ctx->pollfds[i].fd; + /* avoid having any event caught during callback */ + ctx->pollfds[i].events = 0; + p->lock_cnt++; + if (p->cb && (ret = p->cb(p, fd, revents, p->cb_data)) < 0) { + if (ret == -2) { + return -1; + } + /* the poll was removed, reload the used counter and start again + */ + used = ctx->polls_used; + i = 0; + } else { + ctx->pollfds[i].revents = 0; + ctx->pollfds[i].events = p->events; + p->lock_cnt--; + i++; + } + + rc--; + } + } + + return rc; +} + +/** + * @internal + * @brief gets the default poll structure for the current session, + * when used in blocking mode. + * @param session SSH session + * @returns the default ssh_poll_ctx + */ +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session) +{ + if (session->default_poll_ctx != NULL) { + return session->default_poll_ctx; + } + /* 2 is enough for the default one */ + session->default_poll_ctx = ssh_poll_ctx_new(2); + return session->default_poll_ctx; +} + +/* public event API */ + +struct ssh_event_fd_wrapper { + ssh_event_callback cb; + void *userdata; +}; + +struct ssh_event_struct { + ssh_poll_ctx ctx; +#ifdef WITH_SERVER + struct ssh_list *sessions; +#endif +}; + +/** + * @brief Create a new event context. It could be associated with many + * ssh_session objects and socket fd which are going to be polled at the + * same time as the event context. You would need a single event context + * per thread. + * + * @return The ssh_event object on success, NULL on failure. + */ +ssh_event ssh_event_new(void) +{ + ssh_event event; + + event = malloc(sizeof(struct ssh_event_struct)); + if (event == NULL) { + return NULL; + } + ZERO_STRUCTP(event); + + event->ctx = ssh_poll_ctx_new(2); + if (event->ctx == NULL) { + free(event); + return NULL; + } + +#ifdef WITH_SERVER + event->sessions = ssh_list_new(); + if (event->sessions == NULL) { + ssh_poll_ctx_free(event->ctx); + free(event); + return NULL; + } +#endif + + return event; +} + +static int ssh_event_fd_wrapper_callback(ssh_poll_handle p, + socket_t fd, + int revents, + void *userdata) +{ + struct ssh_event_fd_wrapper *pw = (struct ssh_event_fd_wrapper *)userdata; + + (void)p; + if (pw->cb != NULL) { + return pw->cb(fd, revents, pw->userdata); + } + return 0; +} + +/** + * @brief Add a fd to the event and assign it a callback, + * when used in blocking mode. + * @param event The ssh_event + * @param fd Socket that will be polled. + * @param events Poll events that will be monitored for the socket. i.e. + * POLLIN, POLLPRI, POLLOUT + * @param cb Function to be called if any of the events are set. + * The prototype of cb is: + * int (*ssh_event_callback)(socket_t fd, int revents, + * void *userdata); + * @param userdata Userdata to be passed to the callback function. NULL if + * not needed. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_fd(ssh_event event, + socket_t fd, + short events, + ssh_event_callback cb, + void *userdata) +{ + ssh_poll_handle p = NULL; + struct ssh_event_fd_wrapper *pw = NULL; + int rc; + + if (event == NULL || event->ctx == NULL || cb == NULL || + fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + pw = malloc(sizeof(struct ssh_event_fd_wrapper)); + if (pw == NULL) { + return SSH_ERROR; + } + + pw->cb = cb; + pw->userdata = userdata; + + /* pw is freed by ssh_event_remove_fd */ + p = ssh_poll_new(fd, events, ssh_event_fd_wrapper_callback, pw); + if (p == NULL) { + free(pw); + return SSH_ERROR; + } + + rc = ssh_poll_ctx_add(event->ctx, p); + if (rc < 0) { + free(pw); + ssh_poll_free(p); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Add a poll handle to the event. + * + * @param event the ssh_event + * + * @param p the poll handle + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_poll(ssh_event event, ssh_poll_handle p) +{ + return ssh_poll_ctx_add(event->ctx, p); +} + +/** + * @brief remove a poll handle to the event. + * + * @param event the ssh_event + * + * @param p the poll handle + */ +void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) +{ + ssh_poll_ctx_remove(event->ctx, p); +} + +/** + * @brief remove the poll handle from session and assign them to an event, + * when used in blocking mode. + * + * @param event The ssh_event object + * @param session The session to add to the event. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_session(ssh_event event, ssh_session session) +{ + ssh_poll_handle p = NULL; +#ifdef WITH_SERVER + struct ssh_iterator *iterator = NULL; +#endif + int rc; + + if (event == NULL || event->ctx == NULL || session == NULL) { + return SSH_ERROR; + } + if (session->default_poll_ctx == NULL) { + return SSH_ERROR; + } + while (session->default_poll_ctx->polls_used > 0) { + p = session->default_poll_ctx->pollptrs[0]; + /* + * ssh_poll_ctx_remove() decrements + * session->default_poll_ctx->polls_used + */ + ssh_poll_ctx_remove(session->default_poll_ctx, p); + rc = ssh_poll_ctx_add(event->ctx, p); + if (rc != SSH_OK) { + return rc; + } + /* associate the pollhandler with a session so we can put it back + * at ssh_event_free() + */ + p->session = session; + } +#ifdef WITH_SERVER + iterator = ssh_list_get_iterator(event->sessions); + while (iterator != NULL) { + if ((ssh_session)iterator->data == session) { + /* allow only one instance of this session */ + return SSH_OK; + } + iterator = iterator->next; + } + if (ssh_list_append(event->sessions, session) == SSH_ERROR) { + return SSH_ERROR; + } +#endif + return SSH_OK; +} + +/** + * @brief Add a connector to the SSH event loop + * + * @param[in] event The SSH event loop + * + * @param[in] connector The connector object + * + * @return SSH_OK + * + * @return SSH_ERROR in case of error + */ +int ssh_event_add_connector(ssh_event event, ssh_connector connector) +{ + return ssh_connector_set_event(connector, event); +} + +/** + * @brief Poll all the sockets and sessions associated through an event object. + * + * If any of the events are set after the poll, the call back functions of the + * sessions or sockets will be called. + * This function should be called once within the programs main loop. + * In case of failure, the errno should be consulted to find more information + * about the failure set by underlying poll imlpementation. + * + * @param event The ssh_event object to poll. + * + * @param timeout An upper limit on the time for which the poll will + * block, in milliseconds. Specifying a negative value + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns SSH_OK on success. + * SSH_ERROR Error happened during the poll. Check errno to get more + * details about why it failed. + * SSH_AGAIN Timeout occurred + */ +int ssh_event_dopoll(ssh_event event, int timeout) +{ + int rc; + + if (event == NULL || event->ctx == NULL) { + return SSH_ERROR; + } + rc = ssh_poll_ctx_dopoll(event->ctx, timeout); + return rc; +} + +/** + * @brief Remove a socket fd from an event context. + * + * @param event The ssh_event object. + * @param fd The fd to remove. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_remove_fd(ssh_event event, socket_t fd) +{ + register size_t i, used; + int rc = SSH_ERROR; + + if (event == NULL || event->ctx == NULL) { + return SSH_ERROR; + } + + used = event->ctx->polls_used; + for (i = 0; i < used; i++) { + if (fd == event->ctx->pollfds[i].fd) { + ssh_poll_handle p = event->ctx->pollptrs[i]; + if (p->session != NULL) { + /* we cannot free that handle, it's owned by its session */ + continue; + } + if (p->cb == ssh_event_fd_wrapper_callback) { + struct ssh_event_fd_wrapper *pw = p->cb_data; + SAFE_FREE(pw); + } + + /* + * The free function calls ssh_poll_ctx_remove() and decrements + * event->ctx->polls_used. + */ + ssh_poll_free(p); + rc = SSH_OK; + + /* restart the loop */ + used = event->ctx->polls_used; + i = 0; + } + } + + return rc; +} + +/** + * @brief Remove a session object from an event context. + * + * @param event The ssh_event object. + * @param session The session to remove. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_remove_session(ssh_event event, ssh_session session) +{ + ssh_poll_handle p = NULL; + register size_t i, used; + int rc = SSH_ERROR; +#ifdef WITH_SERVER + struct ssh_iterator *iterator = NULL; +#endif + + if (event == NULL || event->ctx == NULL || session == NULL) { + return SSH_ERROR; + } + + used = event->ctx->polls_used; + for (i = 0; i < used; i++) { + p = event->ctx->pollptrs[i]; + if (p->session == session) { + /* + * ssh_poll_ctx_remove() decrements + * event->ctx->polls_used + */ + ssh_poll_ctx_remove(event->ctx, p); + p->session = NULL; + rc = ssh_poll_ctx_add(session->default_poll_ctx, p); + if (rc != SSH_OK) { + return rc; + } + rc = SSH_OK; + /* + * Restart the loop! + * A session can initially have two pollhandlers. + */ + used = event->ctx->polls_used; + i = 0; + } + } +#ifdef WITH_SERVER + iterator = ssh_list_get_iterator(event->sessions); + while (iterator != NULL) { + if ((ssh_session)iterator->data == session) { + ssh_list_remove(event->sessions, iterator); + /* there should be only one instance of this session */ + break; + } + iterator = iterator->next; + } +#endif + + return rc; +} + +/** @brief Remove a connector from an event context + * @param[in] event The ssh_event object. + * @param[in] connector connector object to remove + * @return SSH_OK on success + * @return SSH_ERROR on failure + */ +int ssh_event_remove_connector(ssh_event event, ssh_connector connector) +{ + (void)event; + return ssh_connector_remove_event(connector); +} + +/** + * @brief Free an event context. + * + * @param event The ssh_event object to free. + * Note: you have to manually remove sessions and socket + * fds before freeing the event object. + * + */ +void ssh_event_free(ssh_event event) +{ + size_t used, i; + ssh_poll_handle p = NULL; + + if (event == NULL) { + return; + } + + if (event->ctx != NULL) { + used = event->ctx->polls_used; + for (i = 0; i < used; i++) { + p = event->ctx->pollptrs[i]; + if (p->session != NULL) { + ssh_poll_ctx_remove(event->ctx, p); + ssh_poll_ctx_add(p->session->default_poll_ctx, p); + p->session = NULL; + used = 0; + } + } + + ssh_poll_ctx_free(event->ctx); + } +#ifdef WITH_SERVER + if (event->sessions != NULL) { + ssh_list_free(event->sessions); + } +#endif + free(event); +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/scp.c b/src/libs/libssh-0.12.2/src/scp.c new file mode 100644 index 000000000000..ec2d1a2e9cbc --- /dev/null +++ b/src/libs/libssh-0.12.2/src/scp.c @@ -0,0 +1,1222 @@ +/* + * scp - SSH scp wrapper functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/scp.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +/** + * @defgroup libssh_scp The SSH scp functions + * @ingroup libssh + * + * SCP protocol over SSH functions + * + * @deprecated Please use SFTP instead + * + * @{ + */ + +/** + * @deprecated Please use SFTP instead + * + * @brief Create a new scp session. + * + * @param[in] session The SSH session to use. + * + * @param[in] mode One of SSH_SCP_WRITE or SSH_SCP_READ, depending if you + * need to drop files remotely or read them. + * It is not possible to combine read and write. + * SSH_SCP_RECURSIVE Flag can be or'ed to this to indicate + * that you're going to use recursion. Browsing through + * directories is not possible without this. + * + * @param[in] location The directory in which write or read will be done. Any + * push or pull will be relative to this place. + * This can also be a pattern of files to download (read). + * + * @returns A ssh_scp handle, NULL if the creation was impossible. + */ +ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location) +{ + ssh_scp scp = NULL; + + if (session == NULL || location == NULL) { + goto error; + } + + scp = (ssh_scp)calloc(1, sizeof(struct ssh_scp_struct)); + if (scp == NULL) { + ssh_set_error(session, SSH_FATAL, + "Error allocating memory for ssh_scp"); + goto error; + } + + if ((mode & ~SSH_SCP_RECURSIVE) != SSH_SCP_WRITE && + (mode & ~SSH_SCP_RECURSIVE) != SSH_SCP_READ) + { + ssh_set_error(session, SSH_FATAL, + "Invalid mode %d for ssh_scp_new()", mode); + goto error; + } + + if (strlen(location) > 32 * 1024) { + ssh_set_error(session, SSH_FATAL, + "Location path is too long"); + goto error; + } + + scp->location = strdup(location); + if (scp->location == NULL) { + ssh_set_error(session, SSH_FATAL, + "Error allocating memory for ssh_scp"); + goto error; + } + + scp->session = session; + scp->mode = mode & ~SSH_SCP_RECURSIVE; + scp->recursive = (mode & SSH_SCP_RECURSIVE) != 0; + scp->channel = NULL; + scp->state = SSH_SCP_NEW; + + return scp; + +error: + ssh_scp_free(scp); + return NULL; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Initialize the scp channel. + * + * @param[in] scp The scp context to initialize. + * + * @return SSH_OK on success or an SSH error code. + * + * @see ssh_scp_new() + */ +int ssh_scp_init(ssh_scp scp) +{ + int rc; + char execbuffer[PATH_MAX] = {0}; + char *quoted_location = NULL; + size_t quoted_location_len = 0; + size_t scp_location_len; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_NEW) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_init called under invalid state"); + return SSH_ERROR; + } + + if (scp->location == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Invalid scp context: location is NULL"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, "Initializing scp session %s %son location '%s'", + scp->mode == SSH_SCP_WRITE?"write":"read", + scp->recursive ? "recursive " : "", + scp->location); + + scp->channel = ssh_channel_new(scp->session); + if (scp->channel == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Channel creation failed for scp"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_channel_open_session(scp->channel); + if (rc == SSH_ERROR) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to open channel for scp"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + /* In the worst case, each character would be replaced by 3 plus the string + * terminator '\0' */ + scp_location_len = strlen(scp->location); + quoted_location_len = ((size_t)3 * scp_location_len) + 1; + /* Paranoia check */ + if (quoted_location_len < scp_location_len) { + ssh_set_error(scp->session, SSH_FATAL, + "Buffer overflow detected"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + quoted_location = (char *)calloc(1, quoted_location_len); + if (quoted_location == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to allocate memory for quoted location"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_quote_file_name(scp->location, quoted_location, + quoted_location_len); + if (rc <= 0) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to single quote command location"); + SAFE_FREE(quoted_location); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + /* Some servers do not handle the quoting well. Pass in the raw file + * location */ + if (scp->session->flags & SSH_SESSION_FLAG_SCP_QUOTING_BROKEN) { + free(quoted_location); + quoted_location = strdup(scp->location); + if (quoted_location == NULL) { + ssh_set_error_oom(scp->session); + return SSH_ERROR; + } + } + + if (scp->mode == SSH_SCP_WRITE) { + snprintf(execbuffer, sizeof(execbuffer), "scp -t %s %s", + scp->recursive ? "-r" : "", quoted_location); + } else { + snprintf(execbuffer, sizeof(execbuffer), "scp -f %s %s", + scp->recursive ? "-r" : "", quoted_location); + } + + SAFE_FREE(quoted_location); + + SSH_LOG(SSH_LOG_DEBUG, "Executing command: %s", execbuffer); + + rc = ssh_channel_request_exec(scp->channel, execbuffer); + if (rc == SSH_ERROR){ + ssh_set_error(scp->session, SSH_FATAL, + "Failed executing command: %s", execbuffer); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + if (scp->mode == SSH_SCP_WRITE) { + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + } else { + ssh_channel_write(scp->channel, "", 1); + } + + if (scp->mode == SSH_SCP_WRITE) { + scp->state = SSH_SCP_WRITE_INITED; + } else { + scp->state = SSH_SCP_READ_INITED; + } + + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Close the scp channel. + * + * @param[in] scp The scp context to close. + * + * @return SSH_OK on success or an SSH error code. + * + * @see ssh_scp_init() + */ +int ssh_scp_close(ssh_scp scp) +{ + char buffer[128] = {0}; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->channel != NULL) { + if (ssh_channel_send_eof(scp->channel) == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + /* avoid situations where data are buffered and + * not yet stored on disk. This can happen if the close is sent + * before we got the EOF back + */ + while (!ssh_channel_is_eof(scp->channel)) { + rc = ssh_channel_read(scp->channel, buffer, sizeof(buffer), 0); + if (rc == SSH_ERROR || rc == SSH_AGAIN || rc == 0) { + break; + } + } + + if (ssh_channel_close(scp->channel) == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + ssh_channel_free(scp->channel); + scp->channel = NULL; + } + + scp->state = SSH_SCP_NEW; + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Free a scp context. + * + * @param[in] scp The context to free. + * + * @see ssh_scp_new() + */ +void ssh_scp_free(ssh_scp scp) +{ + if (scp == NULL) { + return; + } + + if (scp->state != SSH_SCP_NEW) { + ssh_scp_close(scp); + } + + if (scp->channel) { + ssh_channel_free(scp->channel); + } + + SAFE_FREE(scp->location); + SAFE_FREE(scp->request_name); + SAFE_FREE(scp->warning); + SAFE_FREE(scp); +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Create a directory in a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] dirname The name of the directory being created. + * + * @param[in] mode The UNIX permissions for the new directory, e.g. 0755. + * + * @returns SSH_OK if the directory has been created, SSH_ERROR if + * an error occurred. + * + * @see ssh_scp_leave_directory() + */ +int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode) +{ + char buffer[PATH_MAX] = {0}; + int rc; + char *dir = NULL; + char *perms = NULL; + char *vis_encoded = NULL; + size_t vis_encoded_len; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_push_directory called under invalid state"); + return SSH_ERROR; + } + + dir = ssh_basename(dirname); + if (dir == NULL) { + ssh_set_error_oom(scp->session); + return SSH_ERROR; + } + + vis_encoded_len = (2 * strlen(dir)) + 1; + vis_encoded = (char *)calloc(1, vis_encoded_len); + if (vis_encoded == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to allocate buffer to vis encode directory name"); + goto error; + } + + rc = ssh_newline_vis(dir, vis_encoded, vis_encoded_len); + if (rc <= 0) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to vis encode directory name"); + goto error; + } + + perms = ssh_scp_string_mode(mode); + if (perms == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to get directory permission string"); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "SCP pushing directory %s with permissions '%s'", + vis_encoded, perms); + + /* Use vis encoded directory name */ + snprintf(buffer, sizeof(buffer), + "D%s 0 %s\n", + perms, vis_encoded); + + SAFE_FREE(dir); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; + +error: + SAFE_FREE(dir); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + return SSH_ERROR; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Leave a directory. + * + * @returns SSH_OK if the directory has been left, SSH_ERROR if an + * error occurred. + * + * @see ssh_scp_push_directory() + */ +int ssh_scp_leave_directory(ssh_scp scp) +{ + char buffer[] = "E\n"; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_leave_directory called under invalid state"); + return SSH_ERROR; + } + + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Initialize the sending of a file to a scp in sink mode, using a 64-bit + * size. + * + * @param[in] scp The scp handle. + * + * @param[in] filename The name of the file being sent. It should not contain + * any path indicator + * + * @param[in] size Exact size in bytes of the file being sent. + * + * @param[in] mode The UNIX permissions for the new file, e.g. 0644. + * + * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an + * error occurred. + * + * @see ssh_scp_push_file() + */ +int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, + int mode) +{ + char buffer[PATH_MAX] = {0}; + int rc; + char *file = NULL; + char *perms = NULL; + char *vis_encoded = NULL; + size_t vis_encoded_len; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_push_file called under invalid state"); + return SSH_ERROR; + } + + file = ssh_basename(filename); + if (file == NULL) { + ssh_set_error_oom(scp->session); + return SSH_ERROR; + } + + vis_encoded_len = (2 * strlen(file)) + 1; + vis_encoded = (char *)calloc(1, vis_encoded_len); + if (vis_encoded == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to allocate buffer to vis encode file name"); + goto error; + } + + rc = ssh_newline_vis(file, vis_encoded, vis_encoded_len); + if (rc <= 0) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to vis encode file name"); + goto error; + } + + perms = ssh_scp_string_mode(mode); + if (perms == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to get file permission string"); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "SCP pushing file %s, size %" PRIu64 " with permissions '%s'", + vis_encoded, size, perms); + + /* Use vis encoded file name */ + snprintf(buffer, sizeof(buffer), + "C%s %" PRIu64 " %s\n", + perms, size, vis_encoded); + + SAFE_FREE(file); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + + scp->filelen = size; + scp->processed = 0; + scp->state = SSH_SCP_WRITE_WRITING; + + return SSH_OK; + +error: + SAFE_FREE(file); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + return SSH_ERROR; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Initialize the sending of a file to a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] filename The name of the file being sent. It should not contain + * any path indicator + * + * @param[in] size Exact size in bytes of the file being sent. + * + * @param[in] mode The UNIX permissions for the new file, e.g. 0644. + * + * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an + * error occurred. + */ +int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) +{ + return ssh_scp_push_file64(scp, filename, (uint64_t) size, mode); +} + +/** + * @internal + * + * @deprecated Please use SFTP instead + * + * @brief Wait for a response of the scp server. + * + * @param[in] scp The scp handle. + * + * @param[out] response A pointer where the response message must be copied if + * any. This pointer must then be free'd. + * + * @returns The return code, SSH_ERROR a error occurred. + */ +int ssh_scp_response(ssh_scp scp, char **response) +{ + unsigned char code; + int rc; + char msg[128] = {0}; + + if (scp == NULL) { + return SSH_ERROR; + } + + rc = ssh_channel_read(scp->channel, &code, 1, 0); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + if (rc == SSH_AGAIN) { + ssh_set_error(scp->session, SSH_FATAL, "SCP: ssh_channel_read timeout"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + if (code == 0) { + return 0; + } + + if (code > 2) { + ssh_set_error(scp->session, SSH_FATAL, + "SCP: invalid status code %u received", code); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_read_string(scp, msg, sizeof(msg)); + if (rc == SSH_ERROR) { + return rc; + } + + /* Warning */ + if (code == 1) { + ssh_set_error(scp->session, SSH_REQUEST_DENIED, + "SCP: Warning: status code 1 received: %s", msg); + SSH_LOG(SSH_LOG_RARE, + "SCP: Warning: status code 1 received: %s", msg); + if (response) { + *response = strdup(msg); + } + return 1; + } + + if (code == 2) { + ssh_set_error(scp->session, SSH_FATAL, + "SCP: Error: status code 2 received: %s", msg); + if (response) { + *response = strdup(msg); + } + return 2; + } + + /* Not reached */ + return SSH_ERROR; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Write into a remote scp file. + * + * @param[in] scp The scp handle. + * + * @param[in] buffer The buffer to write. + * + * @param[in] len The number of bytes to write. + * + * @returns SSH_OK if the write was successful, SSH_ERROR an error + * occurred while writing. + */ +int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) +{ + int w; + int rc; + uint8_t code; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_WRITING) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_write called under invalid state"); + return SSH_ERROR; + } + + if (scp->processed + len > scp->filelen) { + len = (size_t) (scp->filelen - scp->processed); + } + + /* hack to avoid waiting for window change */ + rc = ssh_channel_poll(scp->channel, 0); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + w = ssh_channel_write(scp->channel, buffer, len); + if (w != SSH_ERROR) { + scp->processed += w; + } else { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + /* Far end sometimes send a status message, which we need to read + * and handle */ + rc = ssh_channel_poll(scp->channel, 0); + if (rc > 0) { + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + } + + /* Check if we arrived at end of file */ + if (scp->processed == scp->filelen) { + code = 0; + w = ssh_channel_write(scp->channel, &code, 1); + if (w == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + scp->processed = scp->filelen = 0; + scp->state = SSH_SCP_WRITE_INITED; + } + + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Read a string on a channel, terminated by '\n' + * + * @param[in] scp The scp handle. + * + * @param[out] buffer A pointer to a buffer to place the string. + * + * @param[in] len The size of the buffer in bytes. If the string is bigger + * than len-1, only len-1 bytes are read and the string is + * null-terminated. + * + * @returns SSH_OK if the string was read, SSH_ERROR if an error + * occurred while reading. + */ +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) +{ + size_t read = 0; + int err = SSH_OK; + + if (scp == NULL) { + return SSH_ERROR; + } + + while (read < len - 1) { + err = ssh_channel_read(scp->channel, &buffer[read], 1, 0); + if (err == SSH_ERROR) { + break; + } + + if (err == 0) { + ssh_set_error(scp->session, SSH_FATAL, + "End of file while reading string"); + err = SSH_ERROR; + break; + } + + if (err == SSH_AGAIN) { + ssh_set_error(scp->session, + SSH_FATAL, + "SCP: ssh_channel_read timeout"); + err = SSH_ERROR; + break; + } + + read++; + if (buffer[read - 1] == '\n') { + break; + } + } + + buffer[read] = 0; + return err; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Wait for a scp request (file, directory). + * + * @returns SSH_SCP_REQUEST_NEWFILE: The other side is sending + * a file + * SSH_SCP_REQUEST_NEWDIR: The other side is sending + * a directory + * SSH_SCP_REQUEST_ENDDIR: The other side has + * finished with the current + * directory + * SSH_SCP_REQUEST_WARNING: The other side sent us a warning + * SSH_SCP_REQUEST_EOF: The other side finished sending us + * files and data. + * SSH_ERROR: Some error happened + * + * @see ssh_scp_read() + * @see ssh_scp_deny_request() + * @see ssh_scp_accept_request() + * @see ssh_scp_request_get_warning() + */ +int ssh_scp_pull_request(ssh_scp scp) +{ + char buffer[PATH_MAX] = {0}; + char *mode = NULL; + char *p, *tmp; + uint64_t size; + char *name = NULL; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_READ_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_pull_request called under invalid state"); + return SSH_ERROR; + } + + rc = ssh_scp_read_string(scp, buffer, sizeof(buffer)); + if (rc == SSH_ERROR) { + if (ssh_channel_is_eof(scp->channel)) { + scp->state = SSH_SCP_TERMINATED; + return SSH_SCP_REQUEST_EOF; + } + return rc; + } + + p = strchr(buffer, '\n'); + if (p != NULL) { + *p = '\0'; + } + + SSH_LOG(SSH_LOG_DEBUG, "Received SCP request: '%s'", buffer); + switch(buffer[0]) { + case 'C': + /* File */ + case 'D': + /* Directory */ + p = strchr(buffer, ' '); + if (p == NULL) { + goto error; + } + *p = '\0'; + p++; + //mode = strdup(&buffer[1]); + scp->request_mode = ssh_scp_integer_mode(&buffer[1]); + tmp = p; + p = strchr(p, ' '); + if (p == NULL) { + goto error; + } + *p = 0; + size = strtoull(tmp, NULL, 10); + p++; + name = strdup(p); + /* Catch invalid name: + * - empty ones + * - containing any forward slash -- directory traversal handled + * differently + * - special names "." and ".." referring to the current and parent + * directories -- they are not expected either + */ + if (name == NULL || name[0] == '\0' || strchr(name, '/') || + strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { + ssh_set_error(scp->session, + SSH_FATAL, + "Received invalid filename: %s", + name == NULL ? "" : name); + SAFE_FREE(name); + goto error; + } + SAFE_FREE(scp->request_name); + scp->request_name = name; + if (buffer[0] == 'C') { + scp->filelen = size; + scp->request_type = SSH_SCP_REQUEST_NEWFILE; + } else { + scp->filelen = '0'; + scp->request_type = SSH_SCP_REQUEST_NEWDIR; + } + scp->state = SSH_SCP_READ_REQUESTED; + scp->processed = 0; + return scp->request_type; + break; + case 'E': + scp->request_type = SSH_SCP_REQUEST_ENDDIR; + ssh_channel_write(scp->channel, "", 1); + return scp->request_type; + case 0x1: + ssh_set_error(scp->session, SSH_REQUEST_DENIED, + "SCP: Warning: %s", &buffer[1]); + scp->request_type = SSH_SCP_REQUEST_WARNING; + SAFE_FREE(scp->warning); + scp->warning = strdup(&buffer[1]); + return scp->request_type; + case 0x2: + ssh_set_error(scp->session, SSH_FATAL, + "SCP: Error: %s", &buffer[1]); + return SSH_ERROR; + case 'T': + /* Timestamp */ + default: + ssh_set_error(scp->session, SSH_FATAL, + "Unhandled message: (%d)%s", buffer[0], buffer); + return SSH_ERROR; + } + + /* a parsing error occurred */ +error: + SAFE_FREE(name); + SAFE_FREE(mode); + ssh_set_error(scp->session, SSH_FATAL, + "Parsing error while parsing message: %s", buffer); + return SSH_ERROR; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Deny the transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * @param[in] reason A nul-terminated string with a human-readable + * explanation of the deny. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if the sending + * the message failed, or sending it in a bad state. + */ +int ssh_scp_deny_request(ssh_scp scp, const char *reason) +{ + char *buffer = NULL; + size_t len; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_READ_REQUESTED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + + len = strlen(reason) + 3; + buffer = malloc(len); + if (buffer == NULL) { + return SSH_ERROR; + } + + snprintf(buffer, len, "%c%s\n", 2, reason); + rc = ssh_channel_write(scp->channel, buffer, len - 1); + free(buffer); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + else { + scp->state = SSH_SCP_READ_INITED; + return SSH_OK; + } +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Accepts transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if sending the + * message failed, or sending it in a bad state. + */ +int ssh_scp_accept_request(ssh_scp scp) +{ + char buffer[] = {0x00}; + int rc; + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_READ_REQUESTED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + + rc = ssh_channel_write(scp->channel, buffer, 1); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + if (scp->request_type == SSH_SCP_REQUEST_NEWFILE) { + scp->state = SSH_SCP_READ_READING; + } else { + scp->state = SSH_SCP_READ_INITED; + } + + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Read from a remote scp file + * + * @param[in] scp The scp handle. + * + * @param[in] buffer The destination buffer. + * + * @param[in] size The size of the buffer. + * + * @returns The number of bytes read, SSH_ERROR if an error occurred + * while reading. + */ +int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) +{ + int rc; + int code; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state == SSH_SCP_READ_REQUESTED && + scp->request_type == SSH_SCP_REQUEST_NEWFILE) + { + rc = ssh_scp_accept_request(scp); + if (rc == SSH_ERROR) { + return rc; + } + } + + if (scp->state != SSH_SCP_READ_READING) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_read called under invalid state"); + return SSH_ERROR; + } + + if (scp->processed + size > scp->filelen) { + size = (size_t) (scp->filelen - scp->processed); + } + + if (size > 65536) { + size = 65536; /* avoid too large reads */ + } + + rc = ssh_channel_read(scp->channel, buffer, size, 0); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + if (rc == SSH_AGAIN) { + ssh_set_error(scp->session, SSH_FATAL, "SCP: ssh_channel_read timeout"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + scp->processed += rc; + + /* Check if we arrived at end of file */ + if (scp->processed == scp->filelen) { + scp->processed = scp->filelen = 0; + ssh_channel_write(scp->channel, "", 1); + code = ssh_scp_response(scp, NULL); + if (code == 0) { + scp->state = SSH_SCP_READ_INITED; + return rc; + } + if (code == 1) { + scp->state = SSH_SCP_READ_INITED; + return SSH_ERROR; + } + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + return rc; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the name of the directory or file being pushed from the other + * party. + * + * @returns The file name, NULL on error. The string should not be + * freed. + */ +const char *ssh_scp_request_get_filename(ssh_scp scp) +{ + if (scp == NULL) { + return NULL; + } + + return scp->request_name; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the permissions of the directory or file being pushed from the + * other party. + * + * @returns The UNIX permission, e.g 0644, -1 on error. + */ +int ssh_scp_request_get_permissions(ssh_scp scp) +{ + if (scp == NULL) { + return -1; + } + + return scp->request_mode; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. + * + * @returns The numeric size of the file being read. + * @warning The real size may not fit in a 32 bits field and may + * be truncated. + * @see ssh_scp_request_get_size64() + */ +size_t ssh_scp_request_get_size(ssh_scp scp) +{ + if (scp == NULL) { + return 0; + } + return (size_t)scp->filelen; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. + * + * @returns The numeric size of the file being read. + */ +uint64_t ssh_scp_request_get_size64(ssh_scp scp) +{ + if (scp == NULL) { + return 0; + } + return scp->filelen; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Convert a scp text mode to an integer. + * + * @param[in] mode The mode to convert, e.g. "0644". + * + * @returns An integer value, e.g. 420 for "0644". + */ +int ssh_scp_integer_mode(const char *mode) +{ + int value = strtoul(mode, NULL, 8) & 0xffff; + return value; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Convert a unix mode into a scp string. + * + * @param[in] mode The mode to convert, e.g. 420 or 0644. + * + * @returns A pointer to a malloc'ed string containing the scp mode, + * e.g. "0644". + */ +char *ssh_scp_string_mode(int mode) +{ + char buffer[16] = {0}; + snprintf(buffer, sizeof(buffer), "%.4o", mode); + return strdup(buffer); +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the warning string from a scp handle. + * + * @param[in] scp The scp handle. + * + * @returns A warning string, or NULL on error. The string should + * not be freed. + */ +const char *ssh_scp_request_get_warning(ssh_scp scp) +{ + if (scp == NULL) { + return NULL; + } + + return scp->warning; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/server.c b/src/libs/libssh-0.12.2/src/server.c new file mode 100644 index 000000000000..7873666f1278 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/server.c @@ -0,0 +1,1656 @@ +/* + * server.c - functions for creating a SSH server + * + * This file is part of the SSH Library + * + * Copyright (c) 2004-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include + + /* + * is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. + */ +# ifdef HAVE_WSPIAPI_H +# include +# endif +#else +# include +#endif + +#include "libssh/buffer.h" +#include "libssh/curve25519.h" +#include "libssh/dh.h" +#include "libssh/gssapi.h" +#include "libssh/kex.h" +#include "libssh/libssh.h" +#include "libssh/messages.h" +#include "libssh/misc.h" +#include "libssh/options.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/session.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#include "libssh/token.h" + +#define set_status(session, status) do {\ + if (session->common.callbacks && session->common.callbacks->connect_status_function) \ + session->common.callbacks->connect_status_function(session->common.callbacks->userdata, status); \ + } while (0) + +/** + * @addtogroup libssh_server + * + * @{ + */ + +/** + * @internal + * @brief Sets the server's key exchange, encryption, MAC, and compression + * algorithms. + * + * Prepares the server key exchange (KEX) proposals by prioritizing the + * available host keys (Ed25519, ECDSA, RSA) based on their strength and fills + * in the KEX method lists based on session options or defaults. This is + * essential for negotiating secure communication parameters in the SSH + * handshake. + * + * @param[in] session The SSH session to set up. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure (e.g., no host keys + * available, random number generation error, or memory allocation failure). + */ +int server_set_kex(ssh_session session) +{ + struct ssh_kex_struct *server = &session->next_crypto->server_kex; + int i, j, rc; + const char *wanted = NULL, *allowed = NULL; + char *kept = NULL; + char hostkeys[128] = {0}; + enum ssh_keytypes_e keytype; + size_t len; + int ok; +#ifdef WITH_GSSAPI + char *gssapi_algs = NULL; +#endif /* WITH_GSSAPI */ + + /* Skip if already set, for example for the rekey or when we do the guessing + * it could have been already used to make some protocol decisions. */ + if (server->methods[0] != NULL) { + return SSH_OK; + } + + ok = ssh_get_random(server->cookie, 16, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return -1; + } + + if (session->srv.ed25519_key != NULL) { + snprintf(hostkeys, + sizeof(hostkeys), + "%s", + ssh_key_type_to_char(ssh_key_type(session->srv.ed25519_key))); + } +#ifdef HAVE_ECC + if (session->srv.ecdsa_key != NULL) { + len = strlen(hostkeys); + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", session->srv.ecdsa_key->type_c); + } +#endif + if (session->srv.rsa_key != NULL) { + /* We support also the SHA2 variants */ + len = strlen(hostkeys); + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",rsa-sha2-512,rsa-sha2-256"); + + len = strlen(hostkeys); + keytype = ssh_key_type(session->srv.rsa_key); + + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", ssh_key_type_to_char(keytype)); + } + + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + allowed = session->opts.wanted_methods[SSH_HOSTKEYS]; + } else { + if (ssh_fips_mode()) { + allowed = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + allowed = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + + if (strlen(hostkeys) != 0) { + /* It is expected for the list of allowed hostkeys to be ordered by + * preference */ + kept = + ssh_find_all_matching(hostkeys[0] == ',' ? hostkeys + 1 : hostkeys, + allowed); + if (kept == NULL) { + /* Nothing was allowed */ + return -1; + } + + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + kept, + &session->opts.wanted_methods[SSH_HOSTKEYS]); + SAFE_FREE(kept); + if (rc < 0) { + return -1; + } + } +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + ok = ssh_gssapi_init(session); + if (ok != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + gssapi_algs = ssh_gssapi_kex_mechs(session); + if (gssapi_algs == NULL) { + return SSH_ERROR; + } + ssh_gssapi_free(session); + + /* Prefix the default algorithms with gsskex algs */ + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(ssh_kex_get_default_methods(SSH_KEX), + gssapi_algs); + + if (strlen(hostkeys) == 0) { + session->opts.wanted_methods[SSH_HOSTKEYS] = strdup("null"); + } + + SAFE_FREE(gssapi_algs); + } +#endif /* WITH_GSSAPI */ + + for (i = 0; i < SSH_KEX_METHODS; i++) { + wanted = session->opts.wanted_methods[i]; + if (wanted == NULL) { + if (ssh_fips_mode()) { + wanted = ssh_kex_get_fips_methods(i); + } else { + wanted = ssh_kex_get_default_methods(i); + } + } + if (wanted == NULL) { + for (j = 0; j < i; j++) { + SAFE_FREE(server->methods[j]); + } + return -1; + } + + server->methods[i] = strdup(wanted); + if (server->methods[i] == NULL) { + for (j = 0; j < i; j++) { + SAFE_FREE(server->methods[j]); + } + return -1; + } + } + + /* Do not append the extensions during rekey */ + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + return SSH_OK; + } + + rc = ssh_kex_append_extensions(session, server); + return rc; +} + +int ssh_server_init_kex(ssh_session session) { + int i; + + if (session->session_state > SSH_SESSION_STATE_BANNER_RECEIVED) { + return SSH_ERROR; + } + + /* free any currently-set methods: server_set_kex will allocate new ones */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + SAFE_FREE(session->next_crypto->server_kex.methods[i]); + } + + return server_set_kex(session); +} + +/** + * @internal + * + * @brief Sends SSH extension information from the server to client. + * + * A server may send this message (`SSH_MSG_EXT_INFO`) after its first + * `SSH_MSG_NEWKEYS` message or just before sending `SSH_MSG_USERAUTH_SUCCESS` + * to provide additional extensions support that are not meant for an + * unauthenticated client. + * + * If any error occurs during the packing or sending of the packet, the function + * aborts to avoid partial or corrupted sends. + * + * @param[in] session The SSH session. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure. + */ +static int ssh_server_send_extensions(ssh_session session) +{ + int rc; + const char *hostkey_algorithms = NULL; + + SSH_LOG(SSH_LOG_PACKET, "Sending SSH_MSG_EXT_INFO"); + + if (session->opts.pubkey_accepted_types) { + hostkey_algorithms = session->opts.pubkey_accepted_types; + } else { + if (ssh_fips_mode()) { + hostkey_algorithms = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + /* There are no restrictions to the accepted public keys */ + hostkey_algorithms = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdssss", + SSH2_MSG_EXT_INFO, + 2, /* nr. of extensions */ + "server-sig-algs", + hostkey_algorithms, + "publickey-hostbound@openssh.com", + "0"); + if (rc != SSH_OK) { + goto error; + } + + if (ssh_packet_send(session) == SSH_ERROR) { + goto error; + } + + return 0; +error: + ssh_buffer_reinit(session->out_buffer); + + return -1; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){ + (void)packet; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_KEXDH_INIT"); + if(session->dh_handshake_state != DH_STATE_INIT){ + SSH_LOG(SSH_LOG_RARE,"Invalid state for SSH_MSG_KEXDH_INIT"); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; + } + + /* If first_kex_packet_follows guess was wrong, ignore this message. */ + if (session->first_kex_follows_guess_wrong != 0) { + SSH_LOG(SSH_LOG_RARE, "first_kex_packet_follows guess was wrong, " + "ignoring first SSH_MSG_KEXDH_INIT message"); + session->first_kex_follows_guess_wrong = 0; + + return SSH_PACKET_USED; + } + SSH_LOG(SSH_LOG_DEBUG, "Calling next KEXDH handler"); + return SSH_PACKET_NOT_USED; +} + +/** + * @brief Prepares server host key parameters for the key exchange process. + * + * Selects the appropriate private host key (RSA, ECDSA, or Ed25519) based on + * the session's configured host key type, sets the corresponding @p digest + * algorithm, and imports the public key blob into the Diffie-Hellman key + * exchange state. + * + * @param[in] session The SSH session to which we are preparing host key + * parameters. + * @param[out] privkey Pointer to receive the selected private host key. + * @param[out] digest Pointer to receive the host key digest algorithm. + * + * @return `SSH_OK` on success; `SSH_ERROR` on failure (invalid key type, export + * failure, or DH import error). + */ +int +ssh_get_key_params(ssh_session session, + ssh_key *privkey, + enum ssh_digest_e *digest) +{ + ssh_key pubkey = NULL; + ssh_string pubkey_blob = NULL; + int rc; + + switch(session->srv.hostkey) { + case SSH_KEYTYPE_RSA: + *privkey = session->srv.rsa_key; + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + *privkey = session->srv.ecdsa_key; + break; + case SSH_KEYTYPE_ED25519: + *privkey = session->srv.ed25519_key; + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + *privkey = NULL; + } + + *digest = session->srv.hostkey_digest; + rc = ssh_pki_export_privkey_to_pubkey(*privkey, &pubkey); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Could not get the public key from the private key"); + + return -1; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc < 0) { + ssh_set_error_oom(session); + return -1; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Could not import server public key"); + return -1; + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief A function to be called each time a step has been done in the + * connection. + */ +static void ssh_server_connection_callback(ssh_session session) +{ + int rc; + + switch (session->session_state) { + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + case SSH_SESSION_STATE_SOCKET_CONNECTED: + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->clientbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + SSH_LOG(SSH_LOG_DEBUG, + "SSH client banner: %s", session->clientbanner); + + /* Here we analyze the different protocols the server allows. */ + rc = ssh_analyze_banner(session, 1); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "No version of SSH protocol usable (banner: %s)", + session->clientbanner); + goto error; + } + + /* from now, the packet layer is handling incoming packets */ + ssh_packet_register_socket_callback(session, session->socket); + + ssh_packet_set_default_callbacks(session); + set_status(session, 0.5f); + session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session, 0.6f); + if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + rc = server_set_kex(session); + if (rc == SSH_ERROR) { + goto error; + } + /* We are in a rekeying, so we need to send the server kex */ + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + } + ssh_list_kex(&session->next_crypto->client_kex); // log client kex + rc = ssh_kex_select_methods(session); + if (rc < 0) { + goto error; + } + rc = crypt_set_algorithms_server(session); + if (rc == SSH_ERROR) { + goto error; + } + set_status(session, 0.8f); + session->session_state = SSH_SESSION_STATE_DH; + break; + case SSH_SESSION_STATE_DH: + if (session->dh_handshake_state == DH_STATE_FINISHED) { + + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); + if (rc != SSH_OK) { + goto error; + } + + /* + * If the client supports extension negotiation, we will send + * our supported extensions now. This is the first message after + * sending NEWKEYS message and after turning on crypto. + */ + if (session->extensions & SSH_EXT_NEGOTIATION && + session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + /* + * Only send an SSH_MSG_EXT_INFO message the first time the + * client undergoes NEWKEYS. It is unexpected for this message + * to be sent upon rekey, and may cause clients to log error + * messages. + * + * The session_state can not be used for this purpose because it + * is re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. + * So, use the connected flag which transitions from non-zero + * below. + * + * See also: + * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 + */ + if (session->connected == 0) { + ssh_server_send_extensions(session); + } + } + + set_status(session, 1.0f); + session->connected = 1; + session->session_state = SSH_SESSION_STATE_AUTHENTICATING; + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state %d", + session->session_state); + } + + return; +error: + ssh_socket_close(session->socket); + session->alive = 0; + session->session_state = SSH_SESSION_STATE_ERROR; +} + +/** + * @internal + * + * @brief Gets the banner from socket and saves it in session. + * Updates the session state + * + * @param data pointer to the beginning of header + * @param len size of the banner + * @param user is a pointer to session + * @returns Number of bytes processed, or zero if the banner is not complete. + */ +static size_t callback_receive_banner(const void *data, size_t len, void *user) +{ + char *buffer = (char *)data; + ssh_session session = (ssh_session)user; + char *str = NULL; + size_t i; + size_t processed = 0; + + for (i = 0; i < len; i++) { +#ifdef WITH_PCAP + if (session->pcap_ctx && buffer[i] == '\n') { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + buffer, + (uint32_t)(i + 1), + (uint32_t)(i + 1)); + } +#endif + if (buffer[i] == '\r') { + buffer[i] = '\0'; + } + + if (buffer[i] == '\n') { + buffer[i] = '\0'; + + str = strdup(buffer); + if (str == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_set_error_oom(session); + return 0; + } + /* number of bytes read */ + processed = i + 1; + session->clientbanner = str; + session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED; + SSH_LOG(SSH_LOG_PACKET, "Received banner: %s", str); + session->ssh_connection_callback(session); + + return processed; + } + + if (i > 127) { + /* Too big banner */ + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_set_error(session, SSH_FATAL, + "Receiving banner: too large banner"); + + return 0; + } + } + + return processed; +} + +/* returns 0 until the key exchange is not finished */ +static int ssh_server_kex_termination(void *s){ + ssh_session session = s; + if (session->session_state != SSH_SESSION_STATE_ERROR && + session->session_state != SSH_SESSION_STATE_AUTHENTICATING && + session->session_state != SSH_SESSION_STATE_AUTHENTICATED && + session->session_state != SSH_SESSION_STATE_DISCONNECTED) + return 0; + else + return 1; +} + +/* FIXME: auth_methods should be unsigned */ +void ssh_set_auth_methods(ssh_session session, int auth_methods) +{ + /* accept only methods in range */ + session->auth.supported_methods = (uint32_t)auth_methods & 0x3fU; +} + +int ssh_send_issue_banner(ssh_session session, const ssh_string banner) +{ + int rc = SSH_ERROR; + + if (session == NULL) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sending a server issue banner"); + + rc = ssh_buffer_pack(session->out_buffer, + "bSs", + SSH2_MSG_USERAUTH_BANNER, + banner, + ""); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = ssh_packet_send(session); + return rc; +} + +/* Do the banner and key exchange */ +int ssh_handle_key_exchange(ssh_session session) +{ + int rc; + + if (session->session_state != SSH_SESSION_STATE_NONE) { + goto pending; + } + + rc = ssh_send_banner(session, 1); + if (rc < 0) { + return SSH_ERROR; + } + + session->alive = 1; + + session->ssh_connection_callback = ssh_server_connection_callback; + session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; + ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); + session->socket_callbacks.data = callback_receive_banner; + session->socket_callbacks.exception = ssh_socket_exception_callback; + session->socket_callbacks.userdata = session; + + rc = server_set_kex(session); + if (rc < 0) { + return SSH_ERROR; + } +pending: + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_server_kex_termination,session); + SSH_LOG(SSH_LOG_PACKET, "ssh_handle_key_exchange: current state : %d", + session->session_state); + if (rc != SSH_OK) { + return rc; + } + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->session_state == SSH_SESSION_STATE_DISCONNECTED) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/* messages */ + +/** @internal + * replies to an SSH_AUTH packet with a default (denied) response. + */ +int ssh_auth_reply_default(ssh_session session,int partial) { + char methods_c[128] = {0}; + int rc = SSH_ERROR; + + + if (session->auth.supported_methods == 0) { + session->auth.supported_methods = SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_PASSWORD; + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_PUBLICKEY) { + strncat(methods_c, "publickey,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_GSSAPI_MIC){ + strncat(methods_c,"gssapi-with-mic,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + /* Check if GSSAPI Key exchange was performed */ + if (session->auth.supported_methods & SSH_AUTH_METHOD_GSSAPI_KEYEX) { + if (ssh_session_kex_is_gss(session)) { + strncat(methods_c, + "gssapi-keyex,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_INTERACTIVE) { + strncat(methods_c, "keyboard-interactive,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_PASSWORD) { + strncat(methods_c, "password,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_HOSTBASED) { + strncat(methods_c, "hostbased,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + + if (methods_c[0] == '\0' || methods_c[strlen(methods_c)-1] != ',') { + return SSH_ERROR; + } + + /* Strip the comma. */ + methods_c[strlen(methods_c) - 1] = '\0'; // strip the comma. We are sure there is at + + SSH_LOG(SSH_LOG_PACKET, + "Sending a auth failure. methods that can continue: %s", methods_c); + + rc = ssh_buffer_pack(session->out_buffer, + "bsb", + SSH2_MSG_USERAUTH_FAILURE, + methods_c, + partial ? 1 : 0); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_packet_send(session); + return rc; +} + +/** + * @internal + * + * @brief Sends default refusal for a channel open request. + * + * Default handler that rejects incoming SSH channel open requests by sending + * a `SSH2_MSG_CHANNEL_OPEN_FAILURE` packet with "administratively prohibited" + * reason code. Used when no custom channel open handler is registered. + * + * @param[in] msg The SSH message containing the channel open request details. + * + * @return `SSH_OK` on successful packet send; `SSH_ERROR` on buffer allocation + * or packet send failure. + */ +static int ssh_message_channel_request_open_reply_default(ssh_message msg) { + int rc; + + SSH_LOG(SSH_LOG_FUNCTIONS, "Refusing a channel"); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bdddd", + SSH2_MSG_CHANNEL_OPEN_FAILURE, + msg->channel_request_open.sender, + SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, + 0, /* reason is empty string */ + 0); /* language string */ + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + rc = ssh_packet_send(msg->session); + return rc; +} + +/** + * @internal + * + * @brief Sends default refusal for a channel request. + * + * Default handler that rejects incoming SSH channel requests. If the client + * requested a reply (`want_reply`), sends `SSH2_MSG_CHANNEL_FAILURE` to the + * specific channel. If no reply requested, logs the refusal and returns + * success. + * + * @param[in] msg The SSH message containing the channel request details. + * + * @return `SSH_OK` on success; `SSH_ERROR` if buffer allocation or packet send + * fails. + */ +static int ssh_message_channel_request_reply_default(ssh_message msg) { + uint32_t channel; + int rc; + + if (msg->channel_request.want_reply) { + channel = msg->channel_request.channel->remote_channel; + + SSH_LOG(SSH_LOG_PACKET, + "Sending a default channel_request denied to channel %" PRIu32, channel); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_FAILURE, + channel); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + return ssh_packet_send(msg->session); + } + + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request failed!"); + + return SSH_OK; +} + +static int ssh_message_service_request_reply_default(ssh_message msg) { + /* The only return code accepted by specifications are success or disconnect */ + return ssh_message_service_reply_success(msg); +} + +/** + * @brief Sends `SSH2_MSG_SERVICE_ACCEPT` to the client + * + * @param msg The message to reply to + * + * @returns `SSH_OK` when success otherwise `SSH_ERROR` + */ +int ssh_message_service_reply_success(ssh_message msg) +{ + ssh_session session = NULL; + int rc; + + if (msg == NULL) { + return SSH_ERROR; + } + session = msg->session; + + SSH_LOG(SSH_LOG_PACKET, + "Sending a SERVICE_ACCEPT for service %s", msg->service_request.service); + + rc = ssh_buffer_pack(session->out_buffer, + "bs", + SSH2_MSG_SERVICE_ACCEPT, + msg->service_request.service); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_packet_send(msg->session); + return rc; +} + +/** + * @brief Send a global request success message + * + * @param msg The message + * + * @param bound_port The remote bind port + * + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` + */ +int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_port) { + int rc; + + SSH_LOG(SSH_LOG_FUNCTIONS, "Accepting a global request"); + + if (msg->global_request.want_reply) { + if (ssh_buffer_add_u8(msg->session->out_buffer + , SSH2_MSG_REQUEST_SUCCESS) < 0) { + goto error; + } + + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + && msg->global_request.bind_port == 0) { + rc = ssh_buffer_pack(msg->session->out_buffer, "d", bound_port); + if (rc != SSH_OK) { + ssh_set_error_oom(msg->session); + goto error; + } + } + + return ssh_packet_send(msg->session); + } + + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + && msg->global_request.bind_port == 0) { + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the remote port!"); + } + + return SSH_OK; +error: + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Sends default refusal for a global request. + * + * Default handler that rejects incoming SSH global requests. If the client + * requested a reply (`want_reply`), sends `SSH2_MSG_REQUEST_FAILURE`. If no + * reply requested, logs the refusal and returns success immediately. + * + * @param[in] msg The SSH message containing the global request details. + * + * @return `SSH_OK` on success; `SSH_ERROR` if buffer allocation or packet send + * fails. + */ +static int ssh_message_global_request_reply_default(ssh_message msg) { + SSH_LOG(SSH_LOG_FUNCTIONS, "Refusing a global request"); + + if (msg->global_request.want_reply) { + if (ssh_buffer_add_u8(msg->session->out_buffer + , SSH2_MSG_REQUEST_FAILURE) < 0) { + goto error; + } + return ssh_packet_send(msg->session); + } + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request failed!"); + + return SSH_OK; +error: + return SSH_ERROR; +} + +int ssh_message_reply_default(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + return ssh_auth_reply_default(msg->session, 0); + case SSH_REQUEST_CHANNEL_OPEN: + return ssh_message_channel_request_open_reply_default(msg); + case SSH_REQUEST_CHANNEL: + return ssh_message_channel_request_reply_default(msg); + case SSH_REQUEST_SERVICE: + return ssh_message_service_request_reply_default(msg); + case SSH_REQUEST_GLOBAL: + return ssh_message_global_request_reply_default(msg); + default: + SSH_LOG(SSH_LOG_PACKET, + "Don't know what to default reply to %d type", + msg->type); + break; + } + + return -1; +} + +/** + * @brief Gets the service name from the service request message + * + * @param msg The service request message + * + * @returns the service name from the message + */ +const char *ssh_message_service_service(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + return msg->service_request.service; +} + +const char *ssh_message_auth_user(ssh_message msg) { + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.username; +} + +const char *ssh_message_auth_password(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.password; +} + +ssh_key ssh_message_auth_pubkey(ssh_message msg) { + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.pubkey; +} + +ssh_public_key ssh_message_auth_publickey(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + + return ssh_pki_convert_key_to_publickey(msg->auth_request.pubkey); +} + +enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg){ + if (msg == NULL) { + return -1; + } + return msg->auth_request.signature_state; +} + +/** + * @brief Check if the message is a keyboard-interactive response + * + * @param msg The message to check + * + * @returns 1 if the message is a response, otherwise 0 + */ +int ssh_message_auth_kbdint_is_response(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + return msg->auth_request.kbdint_response != 0; +} + +/* FIXME: methods should be unsigned */ +/** + * @brief Sets the supported authentication methods to a message + * + * @param msg The message + * + * @param methods Methods to set to the message. + * The supported methods are listed in ssh_set_auth_methods + * @see ssh_set_auth_methods + * + * @returns 0 on success, otherwise -1 + */ +int ssh_message_auth_set_methods(ssh_message msg, int methods) { + if (msg == NULL || msg->session == NULL) { + return -1; + } + + if (methods < 0) { + return -1; + } + + msg->session->auth.supported_methods = (uint32_t)methods; + + return 0; +} + +/** + * @brief Sends an interactive authentication request message. + * + * Builds and sends an `SSH2_MSG_USERAUTH_INFO_REQUEST` packet containing the + * given name and @p instruction, followed by a number of @p prompts with + * associated @p echo flags to control whether user input is echoed. + * It initializes the keyboard-interactive state in the session. + * + * @param[in] msg The SSH message representing the client + * authentication request. + * @param[in] name The name of the authentication request. + * @param[in] instruction Instruction string with information for the user. + * @param[in] num_prompts Number of prompts to send. The arrays prompts and + * echo must both have num_prompts elements. + * @param[in] prompts Array of @p num_prompts prompt strings to display. + * @param[in] echo Array of num_prompts boolean values (0 or 1). A + * non-zero value means the user input for that prompt + * is echoed (visible); 0 means the input is hidden + * (typically for passwords). + * + * @return `SSH_OK` on successful send; `SSH_ERROR` on failure. + */ +int ssh_message_auth_interactive_request(ssh_message msg, const char *name, + const char *instruction, unsigned int num_prompts, + const char **prompts, char *echo) { + int rc; + unsigned int i = 0; + + if(name == NULL || instruction == NULL) { + return SSH_ERROR; + } + if(num_prompts > 0 && (prompts == NULL || echo == NULL)) { + return SSH_ERROR; + } + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bsssd", + SSH2_MSG_USERAUTH_INFO_REQUEST, + name, + instruction, + "", /* language tag */ + num_prompts); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + for(i = 0; i < num_prompts; i++) { + rc = ssh_buffer_pack(msg->session->out_buffer, + "sb", + prompts[i], + echo[i] ? 1 : 0); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + } + + rc = ssh_packet_send(msg->session); + + /* fill in the kbdint structure */ + if (msg->session->kbdint == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Warning: Got a keyboard-interactive response " + "but it seems we didn't send the request."); + + msg->session->kbdint = ssh_kbdint_new(); + if (msg->session->kbdint == NULL) { + ssh_set_error_oom(msg->session); + + return SSH_ERROR; + } + } else { + ssh_kbdint_clean(msg->session->kbdint); + } + + msg->session->kbdint->name = strdup(name); + if(msg->session->kbdint->name == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + msg->session->kbdint->instruction = strdup(instruction); + if(msg->session->kbdint->instruction == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + + msg->session->kbdint->nprompts = num_prompts; + if(num_prompts > 0) { + msg->session->kbdint->prompts = calloc(num_prompts, sizeof(char *)); + if (msg->session->kbdint->prompts == NULL) { + msg->session->kbdint->nprompts = 0; + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + msg->session->kbdint->echo = calloc(num_prompts, sizeof(unsigned char)); + if (msg->session->kbdint->echo == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + for (i = 0; i < num_prompts; i++) { + msg->session->kbdint->echo[i] = echo[i]; + msg->session->kbdint->prompts[i] = strdup(prompts[i]); + if (msg->session->kbdint->prompts[i] == NULL) { + ssh_set_error_oom(msg->session); + msg->session->kbdint->nprompts = i; + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + } + } else { + msg->session->kbdint->prompts = NULL; + msg->session->kbdint->echo = NULL; + } + msg->session->auth.state = SSH_AUTH_STATE_INFO; + + return rc; +} + +/** + * @brief Sends `SSH2_MSG_USERAUTH_SUCCESS` or `SSH2_MSG_USERAUTH_FAILURE` + * message depending on the success of the authentication method + * + * @param session The session to reply to + * + * @param partial Denotes if the authentication process was partially completed + * (unsuccessful) + * + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` + */ +int ssh_auth_reply_success(ssh_session session, int partial) +{ + struct ssh_crypto_struct *crypto = NULL; + int r; + + if (session == NULL) { + return SSH_ERROR; + } + + if (partial) { + return ssh_auth_reply_default(session, partial); + } + + r = ssh_buffer_add_u8(session->out_buffer,SSH2_MSG_USERAUTH_SUCCESS); + if (r < 0) { + return SSH_ERROR; + } + + r = ssh_packet_send(session); + + /* + * Consider the session as having been authenticated only after sending + * the `USERAUTH_SUCCESS` message. Setting these flags after + * ssh_packet_send ensures that a rekey is not triggered prematurely, + * causing the message to be queued. + */ + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto != NULL && crypto->delayed_compress_out) { + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression OUT"); + crypto->do_compress_out = 1; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL && crypto->delayed_compress_in) { + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression IN"); + crypto->do_compress_in = 1; + } + return r; +} + +/** + * @brief Replies to an authentication request with success. + * + * Sends an authentication success message (`SSH2_MSG_USERAUTH_SUCCESS`) to the + * client, or a partial success if further authentication steps are required. + * + * @param[in] msg The SSH authentication message being handled. + * @param[in] partial Set to nonzero if partial success (more auth needed), zero + * for full success. + * + * @return `SSH_OK` on success, `SSH_ERROR` if msg is NULL or on send failure. + */ +int ssh_message_auth_reply_success(ssh_message msg, int partial) { + if(msg == NULL) + return SSH_ERROR; + return ssh_auth_reply_success(msg->session, partial); +} + +/** + * @brief Answer `SSH2_MSG_USERAUTH_PK_OK` to a pubkey authentication request + * + * @param msg The message + * + * @param algo The algorithm of the accepted public key + * + * @param pubkey The accepted public key + * + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` + */ +int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey) { + int rc; + if (msg == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bSS", + SSH2_MSG_USERAUTH_PK_OK, + algo, + pubkey); + if(rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + rc = ssh_packet_send(msg->session); + return rc; +} + +/** + * @brief Answer `SSH2_MSG_USERAUTH_PK_OK` to a pubkey authentication request + * + * @param msg The message + * + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` + */ +int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) +{ + ssh_string algo = NULL; + ssh_string pubkey_blob = NULL; + int ret; + + algo = ssh_string_from_char(msg->auth_request.sigtype); + if (algo == NULL) { + return SSH_ERROR; + } + + ret = ssh_pki_export_pubkey_blob(msg->auth_request.pubkey, &pubkey_blob); + if (ret < 0) { + SSH_STRING_FREE(algo); + return SSH_ERROR; + } + + ret = ssh_message_auth_reply_pk_ok(msg, algo, pubkey_blob); + + SSH_STRING_FREE(algo); + SSH_STRING_FREE(pubkey_blob); + + return ret; +} + +/** + * @brief Get the originator address from the channel open message. + * + * @param[in] msg The message. + * + * @return The originator address, or NULL. + */ +const char *ssh_message_channel_request_open_originator(ssh_message msg){ + return msg->channel_request_open.originator; +} + +/** + * @brief Get the originator port from the channel open message. + * + * @param[in] msg The message. + * + * @return The originator port. + */ +int ssh_message_channel_request_open_originator_port(ssh_message msg){ + return msg->channel_request_open.originator_port; +} + +/** + * @brief Get the destination address from the channel open message. + * + * @param[in] msg The message. + * + * @return The destination address, or NULL. + */ +const char *ssh_message_channel_request_open_destination(ssh_message msg){ + return msg->channel_request_open.destination; +} + +/** + * @brief Get the destination port from the channel open message. + * + * @param[in] msg The message. + * + * @return The destination port. + */ +int ssh_message_channel_request_open_destination_port(ssh_message msg){ + return msg->channel_request_open.destination_port; +} + +/** + * @brief Get the channel associated with the message. + * + * @param[in] msg The message. + * + * @return The channel associated with the message. + */ +ssh_channel ssh_message_channel_request_channel(ssh_message msg){ + return msg->channel_request.channel; +} + +/** + * @brief Get the terminal type from the message. + * + * @param[in] msg The message. + * + * @return The terminal type (e.g. "xterm"), or NULL. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +const char *ssh_message_channel_request_pty_term(ssh_message msg){ + return msg->channel_request.TERM; +} + +/** + * @brief Get the terminal width from the message. + * + * @param[in] msg The message. + * + * @return The terminal width in characters. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +int ssh_message_channel_request_pty_width(ssh_message msg){ + return msg->channel_request.width; +} + +/** + * @brief Get the terminal height from the message. + * + * @param[in] msg The message. + * + * @return The terminal height in characters. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +int ssh_message_channel_request_pty_height(ssh_message msg){ + return msg->channel_request.height; +} + +/** + * @brief Get the terminal pixel width from the message. + * + * @param[in] msg The message. + * + * @return The pixel width. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +int ssh_message_channel_request_pty_pxwidth(ssh_message msg){ + return msg->channel_request.pxwidth; +} + +/** + * @brief Get the terminal pixel height from the message. + * + * @param[in] msg The message. + * + * @return The pixel height. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +int ssh_message_channel_request_pty_pxheight(ssh_message msg){ + return msg->channel_request.pxheight; +} + +/** + * @brief Get the name of the environment variable from the message. + * + * @param[in] msg The message. + * + * @return The variable name, or NULL. + */ +const char *ssh_message_channel_request_env_name(ssh_message msg){ + return msg->channel_request.var_name; +} + +/** + * @brief Get the value of the environment variable from the message. + * + * @param[in] msg The message. + * + * @return The variable value, or NULL. + */ +const char *ssh_message_channel_request_env_value(ssh_message msg){ + return msg->channel_request.var_value; +} + +/** + * @brief Get the command from a channel request message. + * + * @param[in] msg The message. + * + * @return The command, or NULL. + */ +const char *ssh_message_channel_request_command(ssh_message msg){ + return msg->channel_request.command; +} + +/** + * @brief Get the subsystem from a channel request message. + * + * @param[in] msg The message. + * + * @return The subsystem, or NULL. + */ +const char *ssh_message_channel_request_subsystem(ssh_message msg){ + return msg->channel_request.subsystem; +} + +/** + * @brief Check if the X11 request is for a single connection. + * + * @param[in] msg The message. + * + * @return 1 if single connection, 0 otherwise. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ +int ssh_message_channel_request_x11_single_connection(ssh_message msg){ + return msg->channel_request.x11_single_connection ? 1 : 0; +} + +/** + * @brief Get the X11 authentication protocol from the message. + * + * @param[in] msg The message. + * + * @return The authentication protocol, or NULL. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ +const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg){ + return msg->channel_request.x11_auth_protocol; +} + +/** + * @brief Get the X11 authentication cookie from the message. + * + * @param[in] msg The message. + * + * @return The authentication cookie, or NULL. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ +const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg){ + return msg->channel_request.x11_auth_cookie; +} + +/** + * @brief Get the X11 screen number from the message. + * + * @param[in] msg The message. + * + * @return The screen number. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ +int ssh_message_channel_request_x11_screen_number(ssh_message msg){ + return msg->channel_request.x11_screen_number; +} + +/** + * @brief Get the bind address from the global request message. + * + * @param[in] msg The message. + * + * @return The bind address, or NULL. + */ +const char *ssh_message_global_request_address(ssh_message msg){ + return msg->global_request.bind_address; +} + +/** + * @brief Get the bind port from the global request message. + * + * @param[in] msg The message. + * + * @return The bind port. + */ +int ssh_message_global_request_port(ssh_message msg){ + return msg->global_request.bind_port; +} + +/** @brief defines the ssh_message callback + * @param session the current ssh session + * @param[in] ssh_bind_message_callback a function pointer to a callback taking the + * current ssh session and received message as parameters. the function returns + * 0 if the message has been parsed and treated successfully, 1 otherwise (libssh + * must take care of the response). + * @param[in] data void pointer to be passed to callback functions + */ +void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data) { + session->ssh_message_callback = ssh_bind_message_callback; + session->ssh_message_callback_data = data; +} + +/** + * @brief Execute callbacks for the messages in the queue. + * + * @param[in] session The session. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + */ +int ssh_execute_message_callbacks(ssh_session session){ + ssh_message msg=NULL; + int ret; + ssh_handle_packets(session, SSH_TIMEOUT_NONBLOCKING); + if(!session->ssh_message_list) + return SSH_OK; + if(session->ssh_message_callback){ + while((msg=ssh_message_pop_head(session)) != NULL) { + ret=session->ssh_message_callback(session,msg, + session->ssh_message_callback_data); + if(ret==1){ + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) + return ret; + } else { + ssh_message_free(msg); + } + } + } else { + while((msg=ssh_message_pop_head(session)) != NULL) { + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) + return ret; + } + } + return SSH_OK; +} + +/** + * @brief Sends a keepalive message to the session + * + * @param session The session to send the message to + * + * @returns `SSH_OK` + */ +int ssh_send_keepalive(ssh_session session) +{ + /* Client denies the request, so the error code is not meaningful */ + (void)ssh_global_request(session, "keepalive@openssh.com", NULL, 1); + + return SSH_OK; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/session.c b/src/libs/libssh-0.12.2/src/session.c new file mode 100644 index 000000000000..e9e91f54d7af --- /dev/null +++ b/src/libs/libssh-0.12.2/src/session.c @@ -0,0 +1,1397 @@ +/* + * session.c - non-networking functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#ifdef _WIN32 +#include +#endif + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/crypto.h" +#include "libssh/server.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#include "libssh/agent.h" +#include "libssh/packet.h" +#include "libssh/kex.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" +#include "libssh/pki.h" +#include "libssh/gssapi.h" + +#define FIRST_CHANNEL 42 // why not ? it helps to find bugs. + +/** + * @defgroup libssh_session The SSH session functions + * @ingroup libssh + * + * Functions that manage a session. + * + * @{ + */ + +/** + * @brief Create a new ssh session. + * + * @returns A new ssh_session pointer, NULL on error. + */ +ssh_session ssh_new(void) +{ + ssh_session session = NULL; + char *id = NULL; + int rc; + + session = calloc(1, sizeof (struct ssh_session_struct)); + if (session == NULL) { + return NULL; + } + + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + goto err; + } + + session->socket = ssh_socket_new(session); + if (session->socket == NULL) { + goto err; + } + + session->out_buffer = ssh_buffer_new(); + if (session->out_buffer == NULL) { + goto err; + } + + session->in_buffer = ssh_buffer_new(); + if (session->in_buffer == NULL) { + goto err; + } + + session->out_queue = ssh_list_new(); + if (session->out_queue == NULL) { + goto err; + } + + session->alive = 0; + session->auth.supported_methods = 0; + ssh_set_blocking(session, 1); + session->maxchannel = FIRST_CHANNEL; + session->proxy_root = true; + + session->agent = ssh_agent_new(session); + if (session->agent == NULL) { + goto err; + } + + /* Initialise a default PKI context */ + session->pki_context = ssh_pki_ctx_new(); + if (session->pki_context == NULL) { + goto err; + } + + /* OPTIONS */ + session->opts.StrictHostKeyChecking = 1; + session->opts.port = 22; + session->opts.fd = -1; + session->opts.compressionlevel = 7; + session->opts.nodelay = 0; + session->opts.identities_only = false; + session->opts.control_master = SSH_CONTROL_MASTER_NO; + + session->opts.flags = SSH_OPT_FLAG_PASSWORD_AUTH | + SSH_OPT_FLAG_PUBKEY_AUTH | + SSH_OPT_FLAG_KBDINT_AUTH | + SSH_OPT_FLAG_GSSAPI_AUTH; + + session->opts.exp_flags = 0; + + session->opts.identity = ssh_list_new(); + if (session->opts.identity == NULL) { + goto err; + } + session->opts.identity_non_exp = ssh_list_new(); + if (session->opts.identity_non_exp == NULL) { + goto err; + } + + session->opts.certificate = ssh_list_new(); + if (session->opts.certificate == NULL) { + goto err; + } + session->opts.certificate_non_exp = ssh_list_new(); + if (session->opts.certificate_non_exp == NULL) { + goto err; + } + /* the default certificates are loaded automatically from the default + * identities later */ + + session->opts.proxy_jumps = ssh_list_new(); + if (session->opts.proxy_jumps == NULL) { + goto err; + } + + session->opts.proxy_jumps_user_cb = ssh_list_new(); + if (session->opts.proxy_jumps_user_cb == NULL) { + goto err; + } + +#ifdef WITH_GSSAPI + session->opts.gssapi_key_exchange_algs = + strdup(GSSAPI_KEY_EXCHANGE_SUPPORTED); + if (session->opts.gssapi_key_exchange_algs == NULL) { + goto err; + } +#endif /* WITH_GSSAPI */ + + id = strdup("%d/.ssh/id_ed25519"); + if (id == NULL) { + goto err; + } + + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef HAVE_ECC + id = strdup("%d/.ssh/id_ecdsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } +#endif + + id = strdup("%d/.ssh/id_rsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef WITH_FIDO2 + /* Add security key identities */ + id = strdup("%d/.ssh/id_ed25519_sk"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef HAVE_ECC + id = strdup("%d/.ssh/id_ecdsa_sk"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } +#endif /* HAVE_ECC */ +#endif /* WITH_FIDO2 */ + + /* Explicitly initialize states */ + session->session_state = SSH_SESSION_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + session->packet_state = PACKET_STATE_INIT; + session->dh_handshake_state = DH_STATE_INIT; + session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE; + + session->auth.state = SSH_AUTH_STATE_NONE; + session->auth.service_state = SSH_AUTH_SERVICE_NONE; + + return session; + +err: + free(id); + ssh_free(session); + return NULL; +} + +/** + * @brief Deallocate a SSH session handle. + * + * @param[in] session The SSH session to free. + * + * @see ssh_disconnect() + * @see ssh_new() + */ +void ssh_free(ssh_session session) +{ + int i; + struct ssh_iterator *it = NULL; + struct ssh_buffer_struct *b = NULL; + + if (session == NULL) { + return; + } + + /* + * Delete all channels + * + * This needs the first thing we clean up cause if there is still an open + * channel we call ssh_channel_close() first. So we need a working socket + * and poll context for it. + */ + for (it = ssh_list_get_iterator(session->channels); + it != NULL; + it = ssh_list_get_iterator(session->channels)) { + ssh_channel_do_free(ssh_iterator_value(ssh_channel,it)); + ssh_list_remove(session->channels, it); + } + ssh_list_free(session->channels); + session->channels = NULL; + +#ifdef WITH_PCAP + if (session->pcap_ctx) { + ssh_pcap_context_free(session->pcap_ctx); + session->pcap_ctx = NULL; + } +#endif + + ssh_socket_free(session->socket); + session->socket = NULL; + + if (session->default_poll_ctx) { + ssh_poll_ctx_free(session->default_poll_ctx); + } + + SSH_BUFFER_FREE(session->in_buffer); + SSH_BUFFER_FREE(session->out_buffer); + session->in_buffer = session->out_buffer = NULL; + + if (session->in_hashbuf != NULL) { + SSH_BUFFER_FREE(session->in_hashbuf); + } + if (session->out_hashbuf != NULL) { + SSH_BUFFER_FREE(session->out_hashbuf); + } + + crypto_free(session->current_crypto); + crypto_free(session->next_crypto); + + ssh_agent_free(session->agent); + + SSH_PKI_CTX_FREE(session->pki_context); + + ssh_key_free(session->srv.rsa_key); + session->srv.rsa_key = NULL; + ssh_key_free(session->srv.ecdsa_key); + session->srv.ecdsa_key = NULL; + ssh_key_free(session->srv.ed25519_key); + session->srv.ed25519_key = NULL; + + if (session->ssh_message_list) { + ssh_message msg; + + for (msg = ssh_list_pop_head(ssh_message, session->ssh_message_list); + msg != NULL; + msg = ssh_list_pop_head(ssh_message, session->ssh_message_list)) { + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + } + + if (session->kbdint != NULL) { + ssh_kbdint_free(session->kbdint); + } + + if (session->packet_callbacks) { + ssh_list_free(session->packet_callbacks); + } + +#ifdef WITH_GSSAPI + ssh_gssapi_free(session); + SAFE_FREE(session->opts.gssapi_key_exchange_algs); +#endif + + /* options */ + if (session->opts.identity) { + char *id = NULL; + + for (id = ssh_list_pop_head(char *, session->opts.identity); + id != NULL; + id = ssh_list_pop_head(char *, session->opts.identity)) { + SAFE_FREE(id); + } + ssh_list_free(session->opts.identity); + } + + if (session->opts.identity_non_exp) { + char *id = NULL; + + for (id = ssh_list_pop_head(char *, session->opts.identity_non_exp); + id != NULL; + id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) { + SAFE_FREE(id); + } + ssh_list_free(session->opts.identity_non_exp); + } + + if (session->opts.certificate) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate); + } + + if (session->opts.certificate_non_exp) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate_non_exp); + } + + ssh_proxyjumps_free(session->opts.proxy_jumps); + SSH_LIST_FREE(session->opts.proxy_jumps); + SSH_LIST_FREE(session->opts.proxy_jumps_user_cb); + SAFE_FREE(session->opts.proxy_jumps_str); + + while ((b = ssh_list_pop_head(struct ssh_buffer_struct *, + session->out_queue)) != NULL) { + SSH_BUFFER_FREE(b); + } + ssh_list_free(session->out_queue); + + ssh_agent_state_free(session->agent_state); + session->agent_state = NULL; + + SAFE_FREE(session->auth.auto_state); + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + SAFE_FREE(session->banner); + SAFE_FREE(session->disconnect_message); + SAFE_FREE(session->peer_discon_msg); + + SAFE_FREE(session->opts.agent_socket); + SAFE_FREE(session->opts.bindaddr); + SAFE_FREE(session->opts.username); + SAFE_FREE(session->opts.host); + SAFE_FREE(session->opts.homedir); + SAFE_FREE(session->opts.sshdir); + SAFE_FREE(session->opts.knownhosts); + SAFE_FREE(session->opts.global_knownhosts); + SAFE_FREE(session->opts.ProxyCommand); + SAFE_FREE(session->opts.gss_server_identity); + SAFE_FREE(session->opts.gss_client_identity); + SAFE_FREE(session->opts.pubkey_accepted_types); + SAFE_FREE(session->opts.control_path); + + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (session->opts.wanted_methods[i]) { + SAFE_FREE(session->opts.wanted_methods[i]); + } + } + + SAFE_FREE(session->server_opts.custombanner); + SAFE_FREE(session->server_opts.moduli_file); + + _ssh_remove_legacy_log_cb(); + + /* burn connection, it could contain sensitive data */ + ssh_burn(session, sizeof(struct ssh_session_struct)); + SAFE_FREE(session); +} + +/** + * @brief get the client banner + * + * @param[in] session The SSH session + * + * @return Returns the client banner string or NULL. + */ +const char* ssh_get_clientbanner(ssh_session session) { + if (session == NULL) { + return NULL; + } + + return session->clientbanner; +} + +/** + * @brief get the server banner + * + * @param[in] session The SSH session + * + * @return Returns the server banner string or NULL. + */ +const char* ssh_get_serverbanner(ssh_session session) { + if (!session) { + return NULL; + } + return session->serverbanner; +} + +/** + * @brief get the name of the current key exchange algorithm. + * + * @param[in] session The SSH session + * + * @return Returns the key exchange algorithm string or NULL. + */ +const char* ssh_get_kex_algo(ssh_session session) { + if ((session == NULL) || + (session->current_crypto == NULL)) { + return NULL; + } + + switch (session->current_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + return "diffie-hellman-group1-sha1"; + case SSH_KEX_DH_GROUP14_SHA1: + return "diffie-hellman-group14-sha1"; + case SSH_KEX_DH_GROUP14_SHA256: + return "diffie-hellman-group14-sha256"; + case SSH_GSS_KEX_DH_GROUP14_SHA256: + return "gss-group14-sha256-"; + case SSH_KEX_DH_GROUP16_SHA512: + return "diffie-hellman-group16-sha512"; + case SSH_GSS_KEX_DH_GROUP16_SHA512: + return "gss-group16-sha512-"; + case SSH_KEX_DH_GROUP18_SHA512: + return "diffie-hellman-group18-sha512"; + case SSH_KEX_ECDH_SHA2_NISTP256: + return "ecdh-sha2-nistp256"; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + return "gss-nistp256-sha256-"; + case SSH_KEX_ECDH_SHA2_NISTP384: + return "ecdh-sha2-nistp384"; + case SSH_KEX_ECDH_SHA2_NISTP521: + return "ecdh-sha2-nistp521"; + case SSH_KEX_CURVE25519_SHA256: + return "curve25519-sha256"; + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + return "curve25519-sha256@libssh.org"; + case SSH_GSS_KEX_CURVE25519_SHA256: + return "gss-curve25519-sha256-"; + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + return "sntrup761x25519-sha512@openssh.com"; + case SSH_KEX_SNTRUP761X25519_SHA512: + return "sntrup761x25519-sha512"; + case SSH_KEX_MLKEM768X25519_SHA256: + return "mlkem768x25519-sha256"; + case SSH_KEX_MLKEM768NISTP256_SHA256: + return "mlkem768nistp256-sha256"; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + return "mlkem1024nistp384-sha384"; +#endif /* HAVE_MLKEM1024 */ +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + return "diffie-hellman-group-exchange-sha1"; + case SSH_KEX_DH_GEX_SHA256: + return "diffie-hellman-group-exchange-sha256"; +#endif /* WITH_GEX */ + } + return NULL; +} + +/** + * @brief Check if current session keys were exchanged using + * a GSSAPI key exchange method. + * + * @param[in] session The SSH session. + * + * @return True if GSSAPI key exchange took place, false otherwise + * (other or no key exchange took place). + */ +bool ssh_session_kex_is_gss(ssh_session session) +{ + if (session == NULL || session->current_crypto == NULL) { + return false; + } + return ssh_kex_is_gss(session->current_crypto); +} +/** + * @brief get the name of the input cipher for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns cipher name or NULL. + */ +const char* ssh_get_cipher_in(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL) && + (session->current_crypto->in_cipher != NULL)) { + return session->current_crypto->in_cipher->name; + } + return NULL; +} + +/** + * @brief get the name of the output cipher for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns cipher name or NULL. + */ +const char* ssh_get_cipher_out(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL) && + (session->current_crypto->out_cipher != NULL)) { + return session->current_crypto->out_cipher->name; + } + return NULL; +} + +/** + * @brief get the name of the input HMAC algorithm for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns HMAC algorithm name or NULL if unknown. + */ +const char* ssh_get_hmac_in(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL)) { + return ssh_hmac_type_to_string(session->current_crypto->in_hmac, session->current_crypto->in_hmac_etm); + } + return NULL; +} + +/** + * @brief get the name of the output HMAC algorithm for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns HMAC algorithm name or NULL if unknown. + */ +const char* ssh_get_hmac_out(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL)) { + return ssh_hmac_type_to_string(session->current_crypto->out_hmac, session->current_crypto->out_hmac_etm); + } + return NULL; +} + +/** + * @internal + * @brief Close the connection socket if it is a socket created by us. + * Does not close the sockets provided by the user through options API. + */ +void +ssh_session_socket_close(ssh_session session) +{ + if (session->opts.fd == SSH_INVALID_SOCKET) { + ssh_socket_close(session->socket); + } + session->alive = 0; + session->session_state = SSH_SESSION_STATE_ERROR; +} + +/** + * @brief Disconnect impolitely from a remote host by closing the socket. + * + * Suitable if you forked and want to destroy this session. + * + * @param[in] session The SSH session to disconnect. + */ +void +ssh_silent_disconnect(ssh_session session) +{ + if (session == NULL) { + return; + } + + ssh_session_socket_close(session); + ssh_disconnect(session); +} + +/** + * @brief Set the session in blocking/nonblocking mode. + * + * @param[in] session The ssh session to change. + * + * @param[in] blocking Zero for nonblocking mode. + */ +void ssh_set_blocking(ssh_session session, int blocking) +{ + if (session == NULL) { + return; + } + session->flags &= ~SSH_SESSION_FLAG_BLOCKING; + session->flags |= blocking ? SSH_SESSION_FLAG_BLOCKING : 0; +} + +/** + * @brief Return the blocking mode of libssh + * @param[in] session The SSH session + * @returns 0 if the session is nonblocking, + * @returns 1 if the functions may block. + */ +int ssh_is_blocking(ssh_session session) +{ + return (session->flags & SSH_SESSION_FLAG_BLOCKING) ? 1 : 0; +} + +/* Waits until the output socket is empty */ +static int ssh_flush_termination(void *c){ + ssh_session session = c; + if (ssh_socket_buffered_write_bytes(session->socket) == 0 || + session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @brief Blocking flush of the outgoing buffer + * @param[in] session The SSH session + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying -1 + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns `SSH_OK` on success, `SSH_AGAIN` if timeout occurred, + * `SSH_ERROR` otherwise. + */ +int ssh_blocking_flush(ssh_session session, int timeout){ + int rc; + if (session == NULL) { + return SSH_ERROR; + } + + rc = ssh_handle_packets_termination(session, timeout, + ssh_flush_termination, session); + if (rc == SSH_ERROR) { + return rc; + } + if (!ssh_flush_termination(session)) { + rc = SSH_AGAIN; + } + + return rc; +} + +/** + * @brief Check if we are connected. + * + * @param[in] session The session to check if it is connected. + * + * @return 1 if we are connected, 0 if not. + */ +int ssh_is_connected(ssh_session session) { + if (session == NULL) { + return 0; + } + + return session->alive; +} + +/** + * @brief Get the fd of a connection. + * + * In case you'd need the file descriptor of the connection to the server/client. + * + * @param[in] session The ssh session to use. + * + * @return The file descriptor of the connection, or -1 if it is + * not connected + */ +socket_t ssh_get_fd(ssh_session session) { + if (session == NULL) { + return -1; + } + + return ssh_socket_get_fd(session->socket); +} + +/** + * @brief Tell the session it has data to read on the file descriptor without + * blocking. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_toread(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_read_wontblock(session->socket); +} + +/** + * @brief Tell the session it may write to the file descriptor without blocking. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_towrite(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_write_wontblock(session->socket); +} + +/** + * @brief Tell the session it has an exception to catch on the file descriptor. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_except(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_except(session->socket); +} + +/** + * @internal + * + * @brief Poll the current session for an event and call the appropriate + * callbacks. This function will not loop until the @p timeout is expired. + * + * This will block until one event happens. + * + * @param[in] session The session handle to use. + * + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying + * `SSH_TIMEOUT_INFINITE` + * (-1) means an infinite timeout. + * Specifying `SSH_TIMEOUT_USER` means to use the timeout + * specified in options. 0 means poll will return + * immediately. + * This parameter is passed to the poll() function. + * + * @return `SSH_OK` on success, `SSH_ERROR` otherwise. + */ +int ssh_handle_packets(ssh_session session, int timeout) +{ + ssh_poll_handle spoll = NULL; + ssh_poll_ctx ctx = NULL; + int tm = timeout; + int rc; + + if (session == NULL || session->socket == NULL) { + return SSH_ERROR; + } + + spoll = ssh_socket_get_poll_handle(session->socket); + if (spoll == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + ssh_poll_add_events(spoll, POLLIN); + ctx = ssh_poll_get_ctx(spoll); + + if (ctx == NULL) { + ctx = ssh_poll_get_default_ctx(session); + if (ctx == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_poll_ctx_add(ctx, spoll); + if (rc != SSH_OK) { + return SSH_ERROR; + } + } + + if (timeout == SSH_TIMEOUT_USER) { + if (ssh_is_blocking(session)) { + tm = ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + } else { + tm = 0; + } + } + rc = ssh_poll_ctx_dopoll(ctx, tm); + if (rc == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + + return rc; +} + +/** + * @internal + * + * @brief Poll the current session for an event and call the appropriate + * callbacks. + * + * This will block until termination function returns true, or @p timeout + * expired. + * + * @param[in] session The session handle to use. + * + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying + * `SSH_TIMEOUT_INFINITE` (-1) means an infinite timeout. + * Specifying SSH_TIMEOUT_USER means using the timeout + * specified in options. 0 means poll will return + * immediately. + * SSH_TIMEOUT_DEFAULT uses the session timeout if set or + * uses blocking parameters of the session. + * This parameter is passed to the poll() function. + * + * @param[in] fct Termination function to be used to determine if it is + * possible to stop polling. + * @param[in] user User parameter to be passed to fct termination function. + * @returns `SSH_OK` on success, `SSH_AGAIN` if timeout occurred, + * `SSH_ERROR` otherwise. + */ +int ssh_handle_packets_termination(ssh_session session, + int timeout, + ssh_termination_function fct, + void *user) +{ + struct ssh_timestamp ts; + int timeout_ms = SSH_TIMEOUT_INFINITE; + int tm; + int ret = SSH_OK; + + /* If a timeout has been provided, use it */ + if (timeout >= 0) { + timeout_ms = timeout; + } else { + if (ssh_is_blocking(session)) { + if (timeout == SSH_TIMEOUT_USER || timeout == SSH_TIMEOUT_DEFAULT) { + if (session->opts.timeout > 0 || + session->opts.timeout_usec > 0) { + timeout_ms = + ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + } + } + } else { + timeout_ms = SSH_TIMEOUT_NONBLOCKING; + } + } + + /* avoid unnecessary syscall for the SSH_TIMEOUT_NONBLOCKING case */ + if (timeout_ms != SSH_TIMEOUT_NONBLOCKING) { + ssh_timestamp_init(&ts); + } + + tm = timeout_ms; + while(!fct(user)) { + ret = ssh_handle_packets(session, tm); + if (ret == SSH_ERROR) { + break; + } + if (ssh_timeout_elapsed(&ts, timeout_ms)) { + ret = fct(user) ? SSH_OK : SSH_AGAIN; + break; + } + + tm = ssh_timeout_update(&ts, timeout_ms); + } + + return ret; +} + +/** + * @brief Get session status + * + * @param session The ssh session to use. + * + * @returns A bitmask including `SSH_CLOSED`, `SSH_READ_PENDING`, + * `SSH_WRITE_PENDING` or `SSH_CLOSED_ERROR` which + * respectively means the session is closed, has data to + * read on the connection socket and session was closed + * due to an error. + */ +int ssh_get_status(ssh_session session) { + int socketstate; + int r = 0; + + if (session == NULL) { + return 0; + } + + socketstate = ssh_socket_get_status(session->socket); + + if (session->session_state == SSH_SESSION_STATE_DISCONNECTED) { + r |= SSH_CLOSED; + } + if (socketstate & SSH_READ_PENDING) { + r |= SSH_READ_PENDING; + } + if (socketstate & SSH_WRITE_PENDING) { + r |= SSH_WRITE_PENDING; + } + if ((session->session_state == SSH_SESSION_STATE_DISCONNECTED && + (socketstate & SSH_CLOSED_ERROR)) || + session->session_state == SSH_SESSION_STATE_ERROR) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +/** + * @brief Get poll flags for an external mainloop + * + * @param session The ssh session to use. + * + * @returns A bitmask including `SSH_READ_PENDING` or `SSH_WRITE_PENDING`. + * For `SSH_READ_PENDING`, your invocation of poll() should include + * POLLIN. For `SSH_WRITE_PENDING`, your invocation of poll() should + * include POLLOUT. + */ +int ssh_get_poll_flags(ssh_session session) +{ + if (session == NULL) { + return 0; + } + + return ssh_socket_get_poll_flags (session->socket); +} + +/** + * @brief Get the disconnect message from the server. + * + * @param[in] session The ssh session to use. + * + * @return The message sent by the server along with the + * disconnect, or NULL in which case the reason of the + * disconnect may be found with ssh_get_error. + * + * @see ssh_get_error() + */ +const char *ssh_get_disconnect_message(ssh_session session) { + if (session == NULL) { + return NULL; + } + + if (session->session_state != SSH_SESSION_STATE_DISCONNECTED) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Connection not closed yet"); + } else if(!session->peer_discon_msg) { + ssh_set_error(session, SSH_FATAL, + "Connection correctly closed but no disconnect message"); + } else { + return session->peer_discon_msg; + } + + return NULL; +} + +/** + * @brief Get the protocol version of the session. + * + * @param session The ssh session to use. + * + * @return The SSH version as integer, < 0 on error. + */ +int ssh_get_version(ssh_session session) { + if (session == NULL) { + return -1; + } + + return 2; +} + +/** + * @internal + * @brief Callback to be called when the socket received an exception code. + * @param user is a pointer to session + */ +void ssh_socket_exception_callback(int code, int errno_code, void *user){ + ssh_session session = (ssh_session)user; + + SSH_LOG(SSH_LOG_RARE, + "Socket exception callback: %d (%d)", + code, + errno_code); + session->session_state = SSH_SESSION_STATE_ERROR; + if (errno_code == 0 && code == SSH_SOCKET_EXCEPTION_EOF) { + ssh_set_error(session, SSH_FATAL, "Socket error: disconnected"); +#ifdef _WIN32 + } else if (errno_code == WSAENETDOWN) { + ssh_set_error(session, SSH_FATAL, "Socket error: network down"); + } else if (errno_code == WSAENETUNREACH) { + ssh_set_error(session, SSH_FATAL, "Socket error: network unreachable"); + } else if (errno_code == WSAENETRESET) { + ssh_set_error(session, SSH_FATAL, "Socket error: network reset"); + } else if (errno_code == WSAECONNABORTED) { + ssh_set_error(session, SSH_FATAL, "Socket error: connection aborted"); + } else if (errno_code == WSAECONNRESET) { + ssh_set_error(session, + SSH_FATAL, + "Socket error: connection reset by peer"); + } else if (errno_code == WSAETIMEDOUT) { + ssh_set_error(session, SSH_FATAL, "Socket error: connection timed out"); + } else if (errno_code == WSAECONNREFUSED) { + ssh_set_error(session, SSH_FATAL, "Socket error: connection refused"); + } else if (errno_code == WSAEHOSTUNREACH) { + ssh_set_error(session, SSH_FATAL, "Socket error: host unreachable"); +#endif + } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_FATAL, + "Socket error: %s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); + } + + session->ssh_connection_callback(session); +} + +/** + * @brief Send a message that should be ignored + * + * @param[in] session The SSH session + * @param[in] data Data to be sent + * + * @return `SSH_OK` on success, `SSH_ERROR` otherwise. + */ +int ssh_send_ignore (ssh_session session, const char *data) { + const int type = SSH2_MSG_IGNORE; + int rc; + + if (ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bs", + type, + data); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + ssh_handle_packets(session, 0); + } + + return SSH_OK; + +error: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + +/** + * @brief Send a debug message + * + * @param[in] session The SSH session + * @param[in] message Data to be sent + * @param[in] always_display Message SHOULD be displayed by the server. It + * SHOULD NOT be displayed unless debugging + * information has been explicitly requested. + * + * @return `SSH_OK` on success, `SSH_ERROR` otherwise. + */ +int ssh_send_debug (ssh_session session, const char *message, int always_display) { + int rc; + + if (ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bbsd", + SSH2_MSG_DEBUG, + always_display != 0 ? 1 : 0, + message, + 0); /* empty language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + ssh_handle_packets(session, 0); + } + + return SSH_OK; + +error: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + + /** + * @brief Set the session data counters. + * + * This function sets the counter structures to be used to calculate data + * which comes in and goes out through the session at different levels. + * + * @code + * struct ssh_counter_struct scounter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * struct ssh_counter_struct rcounter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * ssh_set_counters(session, &scounter, &rcounter); + * @endcode + * + * @param[in] session The SSH session. + * + * @param[in] scounter Counter for byte data handled by the session sockets. + * + * @param[in] rcounter Counter for byte and packet data handled by the session, + * prior compression and SSH overhead. + */ +void ssh_set_counters(ssh_session session, ssh_counter scounter, + ssh_counter rcounter) { + if (session != NULL) { + session->socket_counter = scounter; + session->raw_counter = rcounter; + } +} + +/** + * @deprecated Use ssh_get_publickey_hash() + */ +int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) +{ + ssh_key pubkey = NULL; + ssh_string pubkey_blob = NULL; + MD5CTX ctx = NULL; + unsigned char *h = NULL; + int rc; + + if (session == NULL || hash == NULL) { + return SSH_ERROR; + } + + /* In FIPS mode, we cannot use MD5 */ + if (ssh_fips_mode()) { + ssh_set_error(session, + SSH_FATAL, + "In FIPS mode MD5 is not allowed." + "Try ssh_get_publickey_hash() with" + "SSH_PUBLICKEY_HASH_SHA256"); + return SSH_ERROR; + } + + *hash = NULL; + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + ssh_set_error(session, SSH_FATAL, "No current cryptographic context"); + return SSH_ERROR; + } + + rc = ssh_get_server_publickey(session, &pubkey); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); + if (h == NULL) { + SSH_STRING_FREE(pubkey_blob); + return SSH_ERROR; + } + + ctx = md5_init(); + if (ctx == NULL) { + SSH_STRING_FREE(pubkey_blob); + SAFE_FREE(h); + return SSH_ERROR; + } + + rc = md5_update(ctx, + ssh_string_data(pubkey_blob), + ssh_string_len(pubkey_blob)); + if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey_blob); + md5_ctx_free(ctx); + SAFE_FREE(h); + return rc; + } + SSH_STRING_FREE(pubkey_blob); + rc = md5_final(h, ctx); + if (rc != SSH_OK) { + SAFE_FREE(h); + return rc; + } + + *hash = h; + + return MD5_DIGEST_LEN; +} + +/** + * @brief Deallocate the hash obtained by ssh_get_pubkey_hash. + * + * This is required under Microsoft platform as this library might use a + * different C library than your software, hence a different heap. + * + * @param[in] hash The buffer to deallocate. + * + * @see ssh_get_pubkey_hash() + */ +void ssh_clean_pubkey_hash(unsigned char **hash) +{ + SAFE_FREE(*hash); +} + +/** + * @brief Get the server public key from a session. + * + * @param[in] session The session to get the key from. + * + * @param[out] key A pointer to store the allocated key. You need to free + * the key using ssh_key_free(). + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * + * @see ssh_key_free() + */ +int ssh_get_server_publickey(ssh_session session, ssh_key *key) +{ + ssh_key pubkey = NULL; + + if (session == NULL || + session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + return SSH_ERROR; + } + + pubkey = ssh_key_dup(session->current_crypto->server_pubkey); + if (pubkey == NULL) { + return SSH_ERROR; + } + + *key = pubkey; + return SSH_OK; +} + +/** + * @deprecated Use ssh_get_server_publickey() + */ +int ssh_get_publickey(ssh_session session, ssh_key *key) +{ + return ssh_get_server_publickey(session, key); +} + +/** + * @brief Allocates a buffer with the hash of the public key. + * + * This function allows you to get a @p hash of the public @p key. You can then + * print this hash in a human-readable form to the user so that he is able to + * verify it. Use ssh_get_hexa() or ssh_print_hash() to display it. + * + * @param[in] key The public key to create the hash for. + * + * @param[in] type The type of the hash you want. + * + * @param[out] hash A pointer to store the allocated buffer. It can be + * freed using ssh_clean_pubkey_hash(). + * + * @param[in] hlen The length of the hash. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + * + * @warning It is very important that you verify at some moment that the hash + * matches a known server. If you don't do it, cryptography won't help + * you at making things secure. + * OpenSSH uses SHA256 to print public key digests. + * + * @see ssh_session_update_known_hosts() + * @see ssh_get_hexa() + * @see ssh_print_hash() + * @see ssh_clean_pubkey_hash() + */ +int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, + unsigned char **hash, + size_t *hlen) +{ + ssh_string blob = NULL; + unsigned char *h = NULL; + int (*digest)(const unsigned char *, size_t, unsigned char *) = NULL; + int rc, ret = SSH_ERROR; + + rc = ssh_pki_export_pubkey_blob(key, &blob); + if (rc < 0) { + goto out; + } + + switch (type) { + case SSH_PUBLICKEY_HASH_SHA1: + digest = sha1; + *hlen = SHA_DIGEST_LEN; + break; + case SSH_PUBLICKEY_HASH_SHA256: + digest = sha256; + *hlen = SHA256_DIGEST_LEN; + break; + case SSH_PUBLICKEY_HASH_MD5: +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER < 0x30000000L + /* In FIPS mode without OpenSSL providers, we cannot use MD5 */ + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, + "In FIPS mode MD5 is not allowed." + "Try using SSH_PUBLICKEY_HASH_SHA256"); + goto out; + } +#endif + digest = md5; + *hlen = MD5_DIGEST_LEN; + break; + default: + goto out; + } + + h = calloc(1, *hlen); + if (h == NULL) { + goto out; + } + + rc = digest(ssh_string_data(blob), ssh_string_len(blob), h); + if (rc != SSH_OK) { + free(h); + goto out; + } + + *hash = h; + ret = SSH_OK; + +out: + SSH_STRING_FREE(blob); + return ret; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/sftp.c b/src/libs/libssh-0.12.2/src/sftp.c new file mode 100644 index 000000000000..562efbd08c39 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sftp.c @@ -0,0 +1,3631 @@ +/* + * sftp.c - Secure FTP functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* This file contains code written by Nick Zitzmann */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/bytearray.h" + +#ifdef WITH_SFTP + +struct sftp_ext_struct { + uint32_t count; + char **name; + char **data; +}; + +static sftp_ext sftp_ext_new(void) { + sftp_ext ext; + + ext = calloc(1, sizeof(struct sftp_ext_struct)); + if (ext == NULL) { + return NULL; + } + + return ext; +} + +static void sftp_ext_free(sftp_ext ext) +{ + size_t i; + + if (ext == NULL) { + return; + } + + if (ext->count > 0) { + if (ext->name != NULL) { + for (i = 0; i < ext->count; i++) { + SAFE_FREE(ext->name[i]); + } + SAFE_FREE(ext->name); + } + + if (ext->data != NULL) { + for (i = 0; i < ext->count; i++) { + SAFE_FREE(ext->data[i]); + } + SAFE_FREE(ext->data); + } + } + + SAFE_FREE(ext); +} + +sftp_session sftp_new(ssh_session session) +{ + sftp_session sftp = NULL; + int rc; + + if (session == NULL) { + return NULL; + } + + if (!ssh_is_blocking(session)) { + ssh_set_error(session, + SSH_FATAL, + "The SSH session needs to be set to blocking mode for " + "SFTP to work correctly."); + return NULL; + } + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + + return NULL; + } + + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->read_packet->payload = ssh_buffer_new(); + if (sftp->read_packet->payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->session = session; + sftp->channel = ssh_channel_new(session); + if (sftp->channel == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->outstanding_ids = ssh_list_new(); + if (sftp->outstanding_ids == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* + * The following two calls shouldn't return SSH_AGAIN + * as the code has validated above that the SSH session + * is in blocking mode. + */ + rc = ssh_channel_open_session(sftp->channel); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_channel_request_sftp(sftp->channel); + if (rc != SSH_OK) { + goto error; + } + + return sftp; +error: + if (sftp->ext != NULL) { + sftp_ext_free(sftp->ext); + } + if (sftp->channel != NULL) { + ssh_channel_free(sftp->channel); + } + ssh_list_free(sftp->outstanding_ids); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); + } + SAFE_FREE(sftp->read_packet); + } + SAFE_FREE(sftp); + return NULL; +} + +sftp_session +sftp_new_channel(ssh_session session, ssh_channel channel) +{ + sftp_session sftp = NULL; + + if (session == NULL) { + return NULL; + } + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->outstanding_ids = ssh_list_new(); + if (sftp->outstanding_ids == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->read_packet->payload = ssh_buffer_new(); + if (sftp->read_packet->payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->session = session; + sftp->channel = channel; + + return sftp; + +error: + if (sftp->ext != NULL) { + sftp_ext_free(sftp->ext); + } + ssh_list_free(sftp->outstanding_ids); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); + } + SAFE_FREE(sftp->read_packet); + } + SAFE_FREE(sftp); + return NULL; +} + +#ifdef WITH_SERVER +sftp_session +sftp_server_new(ssh_session session, ssh_channel chan) +{ + sftp_session sftp = NULL; + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + goto error; + } + + sftp->read_packet->payload = ssh_buffer_new(); + if (sftp->read_packet->payload == NULL) { + goto error; + } + + sftp->session = session; + sftp->channel = chan; + + return sftp; + +error: + ssh_set_error_oom(session); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); + } + SAFE_FREE(sftp->read_packet); + } + SAFE_FREE(sftp); + return NULL; +} + +/* @deprecated in favor of sftp_server_new() and callbacks based sftp server */ +int sftp_server_init(sftp_session sftp) +{ + ssh_session session = sftp->session; + sftp_client_message msg = NULL; + int rc; + + /* handles setting the sftp->client_version */ + msg = sftp_get_client_message(sftp); + if (msg == NULL) { + return -1; + } + + if (msg->type != SSH_FXP_INIT) { + ssh_set_error(session, + SSH_FATAL, + "Packet read of type %d instead of SSH_FXP_INIT", + msg->type); + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_FXP_INIT"); + + rc = sftp_reply_version(msg); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to process the SSH_FXP_INIT message"); + return -1; + } + + return 0; +} + +void sftp_server_free(sftp_session sftp) +{ + sftp_request_queue ptr; + + if (sftp == NULL) { + return; + } + + ptr = sftp->queue; + while(ptr) { + sftp_request_queue old; + sftp_message_free(ptr->message); + old = ptr->next; + SAFE_FREE(ptr); + ptr = old; + } + + SAFE_FREE(sftp->handles); + SSH_BUFFER_FREE(sftp->read_packet->payload); + SAFE_FREE(sftp->read_packet); + + sftp_ext_free(sftp->ext); + + SAFE_FREE(sftp); +} + +#endif /* WITH_SERVER */ + +void sftp_free(sftp_session sftp) +{ + sftp_request_queue ptr = NULL; + struct ssh_iterator *id_it = NULL; + + if (sftp == NULL) { + return; + } + + if (sftp->channel != NULL) { + ssh_channel_send_eof(sftp->channel); + ptr = sftp->queue; + while(ptr) { + sftp_request_queue old; + sftp_message_free(ptr->message); + old = ptr->next; + SAFE_FREE(ptr); + ptr = old; + } + + ssh_channel_free(sftp->channel); + sftp->channel = NULL; + } + + SAFE_FREE(sftp->handles); + SSH_BUFFER_FREE(sftp->read_packet->payload); + SAFE_FREE(sftp->read_packet); + + sftp_ext_free(sftp->ext); + sftp_limits_free(sftp->limits); + + id_it = ssh_list_get_iterator(sftp->outstanding_ids); + for (; id_it != NULL; id_it = id_it->next) { + free((uint32_t *)id_it->data); + } + ssh_list_free(sftp->outstanding_ids); + + SAFE_FREE(sftp); +} + +/* @internal + * Process the incoming data and copy them from the SSH packet buffer to the + * SFTP packet buffer. + * @returns number of decoded bytes. + */ +int +sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len) +{ + sftp_packet packet = sftp->read_packet; + size_t nread; + size_t payload_len; + size_t data_offset; + size_t to_read, rc; + + if (packet->sftp == NULL) { + packet->sftp = sftp; + } + + data_offset = sizeof(uint32_t) + sizeof(uint8_t); + /* not enough bytes to read */ + if (len < data_offset) { + return SSH_ERROR; + } + + payload_len = PULL_BE_U32(data, 0); + packet->type = PULL_BE_U8(data, 4); + + /* We should check the legality of payload length */ + if (payload_len > len - sizeof(uint32_t) || payload_len < sizeof(uint8_t)) { + return SSH_ERROR; + } + + to_read = payload_len - sizeof(uint8_t); + rc = ssh_buffer_add_data(packet->payload, + (void*)((uint8_t *)data + data_offset), + to_read); + if (rc != 0) { + return SSH_ERROR; + } + nread = ssh_buffer_get_len(packet->payload); + + /* We should check if we copied the whole data */ + if (nread != to_read) { + return SSH_ERROR; + } + + /* + * We should return how many bytes we decoded, including packet length + * header and the payload length. + * This can't overflow as we pulled this from unit32_t and checked this fits + * into the buffer's max size of 0x10000000 (256MB). + */ + return (int)(payload_len + sizeof(uint32_t)); +} + +/* Get the last sftp error */ +int sftp_get_error(sftp_session sftp) { + if (sftp == NULL) { + return -1; + } + + return sftp->errnum; +} + +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp); +static sftp_limits_t sftp_limits_use_default(sftp_session sftp); + +/* Initialize the sftp session with the server. */ +int sftp_init(sftp_session sftp) +{ + sftp_packet packet = NULL; + ssh_buffer buffer = NULL; + char *ext_name = NULL; + char *ext_data = NULL; + uint32_t version; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); + if (rc == SSH_ERROR) { + SSH_BUFFER_FREE(buffer); + return -1; + } + + SSH_BUFFER_FREE(buffer); + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + return -1; + } + + if (packet->type != SSH_FXP_VERSION) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a %d messages instead of SSH_FXP_VERSION", + packet->type); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* TODO: are we sure there are 4 bytes ready? */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Unable to unpack SSH_FXP_VERSION packet"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server version %" PRIu32, + version); + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); + while (rc == SSH_OK) { + uint32_t count = sftp->ext->count; + char **tmp; + + rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); + if (rc == SSH_ERROR) { + break; + } + + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server extension: %s, version: %s", + ext_name, ext_data); + + count++; + tmp = realloc(sftp->ext->name, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + tmp[count - 1] = ext_name; + sftp->ext->name = tmp; + + tmp = realloc(sftp->ext->data, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + tmp[count - 1] = ext_data; + sftp->ext->data = tmp; + + sftp->ext->count = count; + + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); + } + + sftp->version = sftp->server_version = (int)version; + + /* Set the limits */ + rc = sftp_extension_supported(sftp, "limits@openssh.com", "1"); + if (rc == 1) { + /* Get the ssh and sftp errors */ + const char *static_ssh_err_msg = ssh_get_error(sftp->session); + int ssh_err_code = ssh_get_error_code(sftp->session); + int sftp_err_code = sftp_get_error(sftp); + char *ssh_err_msg = strdup(static_ssh_err_msg); + if (ssh_err_msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + sftp->limits = sftp_limits_use_extension(sftp); + if (sftp->limits == NULL) { + /* fallback and use the default limits on failure */ + SSH_LOG(SSH_LOG_TRACE, + "Failed to get the limits from a server claiming to " + "support the limits@openssh.com extension, falling back " + "and using the default limits"); + + /* Restore the sftp and ssh errors to their previous state */ + ssh_set_error(sftp->session, ssh_err_code, "%s", ssh_err_msg); + sftp_set_error(sftp, sftp_err_code); + SAFE_FREE(ssh_err_msg); + + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } + } else { + SAFE_FREE(ssh_err_msg); + } + } else { + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } + } + + return 0; +} + +unsigned int sftp_extensions_get_count(sftp_session sftp) +{ + if (sftp == NULL || sftp->ext == NULL) { + return 0; + } + + return sftp->ext->count; +} + +const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) +{ + if (sftp == NULL) { + return NULL; + } + + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + return sftp->ext->name[idx]; +} + +const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) +{ + if (sftp == NULL) { + return NULL; + } + + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + return sftp->ext->data[idx]; +} + +int +sftp_extension_supported(sftp_session sftp, + const char *name, + const char *data) +{ + unsigned int i, n; + + if (sftp == NULL || name == NULL || data == NULL) { + return 0; + } + + n = sftp_extensions_get_count(sftp); + for (i = 0; i < n; i++) { + const char *ext_name = sftp_extensions_get_name(sftp, i); + const char *ext_data = sftp_extensions_get_data(sftp, i); + + if (ext_name != NULL && ext_data != NULL && + strcmp(ext_name, name) == 0 && + strcmp(ext_data, data) == 0) { + return 1; + } + } + + return 0; +} + +static sftp_file parse_handle_msg(sftp_message msg){ + sftp_file file; + + if(msg->packet_type != SSH_FXP_HANDLE) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_handle message passed in!"); + return NULL; + } + + file = calloc(1, sizeof(struct sftp_file_struct)); + if (file == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + file->handle = ssh_buffer_get_ssh_string(msg->payload); + if (file->handle == NULL) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_HANDLE message"); + SAFE_FREE(file); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + file->sftp = msg->sftp; + file->offset = 0; + file->eof = 0; + + return file; +} + +/* Open a directory */ +sftp_dir sftp_opendir(sftp_session sftp, const char *path) +{ + sftp_message msg = NULL; + sftp_file file = NULL; + sftp_dir dir = NULL; + sftp_status_message status; + ssh_buffer payload = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(payload, + "ds", + id, + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(payload); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_OPENDIR, payload); + SSH_BUFFER_FREE(payload); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return NULL; + case SSH_FXP_HANDLE: + file = parse_handle_msg(msg); + sftp_message_free(msg); + if (file != NULL) { + dir = calloc(1, sizeof(struct sftp_dir_struct)); + if (dir == NULL) { + ssh_set_error_oom(sftp->session); + free(file); + return NULL; + } + + dir->sftp = sftp; + dir->name = strdup(path); + if (dir->name == NULL) { + SAFE_FREE(dir); + SAFE_FREE(file); + return NULL; + } + dir->handle = file->handle; + SAFE_FREE(file); + } + return dir; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during opendir!", + msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +/* Get the version of the SFTP protocol supported by the server */ +int sftp_server_version(sftp_session sftp) { + return sftp->server_version; +} + +/* Get a single file attributes structure of a directory. */ +sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) +{ + sftp_message msg = NULL; + sftp_status_message status; + sftp_attributes attr; + ssh_buffer payload; + uint32_t id; + int rc; + + if (dir->buffer == NULL) { + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(payload, + "dS", + id, + dir->handle); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(payload); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_READDIR, payload); + SSH_BUFFER_FREE(payload); + if (rc < 0) { + return NULL; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a ssh_fxp_readdir with id %" PRIu32, id); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + switch (msg->packet_type){ + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_EOF: + dir->eof = 1; + status_msg_free(status); + return NULL; + default: + break; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Unknown error status: %" PRIu32, status->status); + status_msg_free(status); + + return NULL; + case SSH_FXP_NAME: + ssh_buffer_get_u32(msg->payload, &dir->count); + dir->count = ntohl(dir->count); + dir->buffer = msg->payload; + msg->payload = NULL; + sftp_message_free(msg); + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Unsupported message back %d", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + + return NULL; + } + } + + /* now dir->buffer contains a buffer and dir->count != 0 */ + if (dir->count == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Count of files sent by the server is zero, which is invalid, or " + "libsftp bug"); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, "Count is %" PRIu32, dir->count); + + attr = sftp_parse_attr(sftp, dir->buffer, 1); + if (attr == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } + + dir->count--; + if (dir->count == 0) { + SSH_BUFFER_FREE(dir->buffer); + dir->buffer = NULL; + } + + return attr; +} + +/* Tell if the directory has reached EOF (End Of File). */ +int sftp_dir_eof(sftp_dir dir) { + return dir->eof; +} + +/* Free a SFTP_ATTRIBUTE handle */ +void sftp_attributes_free(sftp_attributes file){ + if (file == NULL) { + return; + } + + SSH_STRING_FREE(file->acl); + SSH_STRING_FREE(file->extended_data); + SSH_STRING_FREE(file->extended_type); + + SAFE_FREE(file->name); + SAFE_FREE(file->longname); + SAFE_FREE(file->group); + SAFE_FREE(file->owner); + + SAFE_FREE(file); +} + +static int sftp_handle_close(sftp_session sftp, ssh_string handle) +{ + sftp_status_message status; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "dS", + id, + handle); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_CLOSE, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if(status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + break; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during sftp_handle_close!", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Close an open file handle. */ +int sftp_close(sftp_file file){ + int err = SSH_NO_ERROR; + + if (file == NULL) { + return err; + } + + SAFE_FREE(file->name); + if (file->handle){ + err = sftp_handle_close(file->sftp,file->handle); + SSH_STRING_FREE(file->handle); + } + /* FIXME: check server response and implement errno */ + SAFE_FREE(file); + + return err; +} + +/* Close an open directory. */ +int sftp_closedir(sftp_dir dir){ + int err = SSH_NO_ERROR; + + SAFE_FREE(dir->name); + if (dir->handle) { + err = sftp_handle_close(dir->sftp, dir->handle); + SSH_STRING_FREE(dir->handle); + } + /* FIXME: check server response and implement errno */ + SSH_BUFFER_FREE(dir->buffer); + SAFE_FREE(dir); + + return err; +} + +/* Open a file on the server. */ +sftp_file sftp_open(sftp_session sftp, + const char *file, + int flags, + mode_t mode) +{ + sftp_message msg = NULL; + sftp_status_message status; + struct sftp_attributes_struct attr; + sftp_file handle; + ssh_buffer buffer = NULL; + sftp_attributes stat_data; + uint32_t sftp_flags = 0; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + if ((flags & O_RDWR) == O_RDWR) { + sftp_flags |= (SSH_FXF_WRITE | SSH_FXF_READ); + } else if ((flags & O_WRONLY) == O_WRONLY) { + sftp_flags |= SSH_FXF_WRITE; + } else { + sftp_flags |= SSH_FXF_READ; + } + if ((flags & O_CREAT) == O_CREAT) + sftp_flags |= SSH_FXF_CREAT; + if ((flags & O_TRUNC) == O_TRUNC) + sftp_flags |= SSH_FXF_TRUNC; + if ((flags & O_EXCL) == O_EXCL) + sftp_flags |= SSH_FXF_EXCL; + if ((flags & O_APPEND) == O_APPEND) { + sftp_flags |= SSH_FXF_APPEND; + } + SSH_LOG(SSH_LOG_PACKET, "Opening file %s with sftp flags %" PRIx32, + file, sftp_flags); + + rc = ssh_buffer_pack(buffer, + "dsd", + id, + file, + sftp_flags); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = buffer_add_attributes(buffer, &attr); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_OPEN, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + + return NULL; + case SSH_FXP_HANDLE: + handle = parse_handle_msg(msg); + if (handle == NULL) { + return NULL; + } + sftp_message_free(msg); + if ((flags & O_APPEND) == O_APPEND) { + stat_data = sftp_stat(sftp, file); + if (stat_data == NULL) { + sftp_close(handle); + return NULL; + } + if ((stat_data->flags & SSH_FILEXFER_ATTR_SIZE) != SSH_FILEXFER_ATTR_SIZE) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Cannot open in append mode. Unknown file size."); + sftp_attributes_free(stat_data); + sftp_close(handle); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + handle->offset = stat_data->size; + sftp_attributes_free(stat_data); + } + return handle; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during open!", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +void sftp_file_set_nonblocking(sftp_file handle) +{ + handle->nonblocking = 1; +} +void sftp_file_set_blocking(sftp_file handle) +{ + handle->nonblocking = 0; +} + +/* Read from a file using an opened sftp file handle. */ +ssize_t +sftp_read(sftp_file handle, void *buf, size_t count) +{ + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring = NULL; + size_t datalen; + ssh_buffer buffer = NULL; + uint32_t id, read_len; + int rc; + + if (handle == NULL) { + return -1; + } + sftp = handle->sftp; + + if (handle->eof) { + return 0; + } + + /* + * limit the reads to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. + * + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + read_len = (uint32_t)MIN(sftp->limits->max_read_length, count); + + rc = sftp_get_new_id(handle->sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + handle->handle, + handle->offset, + read_len); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + if (sftp_packet_write(handle->sftp, SSH_FXP_READ, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(handle->sftp, id, !handle->nonblocking, &msg); + if (rc == SSH_ERROR) { + return -1; + } + + if (rc == SSH_AGAIN) { + /* + * file opened in non blocking mode and the response has not arrived + * yet. Since we cannot block, return 0 as the number of bytes read. + */ + return 0; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_EOF: + handle->eof = 1; + status_msg_free(status); + return 0; + default: + break; + } + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + return -1; + case SSH_FXP_DATA: + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return -1; + } + + datalen = ssh_string_len(datastring); + if (datalen > count) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%zu and asked for %zu", + datalen, + count); + SSH_STRING_FREE(datastring); + return -1; + } + handle->offset += (uint64_t)datalen; + memcpy(buf, ssh_string_data(datastring), datalen); + SSH_STRING_FREE(datastring); + return datalen; + default: + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d during read!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return -1; + } + + return -1; /* not reached */ +} + +/* Start an asynchronous read from a file using an opened sftp file handle. */ +int +sftp_async_read_begin(sftp_file file, uint32_t len) +{ + sftp_session sftp = file->sftp; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, "dSqd", id, file->handle, file->offset, len); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_READ, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + file->offset += len; /* assume we'll read len bytes */ + + return id; +} + +/* Wait for an asynchronous read to complete and save the data. */ +int +sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id) +{ + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring = NULL; + int rc, err = SSH_OK; + size_t len; + + if (file == NULL) { + return SSH_ERROR; + } + sftp = file->sftp; + + if (file->eof) { + return 0; + } + + /* handle an existing request */ + rc = sftp_recv_response_msg(sftp, id, !file->nonblocking, &msg); + if (rc == SSH_ERROR || rc == SSH_AGAIN) { + return rc; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + if (status->status != SSH_FX_EOF) { + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server : %s", + status->errormsg); + err = SSH_ERROR; + } else { + file->eof = 1; + } + status_msg_free(status); + return err; + case SSH_FXP_DATA: + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return SSH_ERROR; + } + if (ssh_string_len(datastring) > size) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%zu and asked for %" PRIu32, + ssh_string_len(datastring), + size); + SSH_STRING_FREE(datastring); + return SSH_ERROR; + } + len = ssh_string_len(datastring); + /* Update the offset with the correct value */ + file->offset = file->offset - (size - len); + memcpy(data, ssh_string_data(datastring), len); + SSH_STRING_FREE(datastring); + return (int)len; + default: + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d during read!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return SSH_ERROR; + } + + return SSH_ERROR; +} + +ssize_t +sftp_write(sftp_file file, const void *buf, size_t count) +{ + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status; + ssh_buffer buffer = NULL; + uint32_t id, write_len; + ssize_t len; + size_t packetlen; + int rc; + + if (file == NULL) { + return -1; + } + sftp = file->sftp; + + rc = sftp_get_new_id(file->sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* + * limit the writes to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. + * + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + write_len = (uint32_t)MIN(sftp->limits->max_write_length, count); + + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + write_len, /* len of datastring */ + (size_t)write_len, + buf); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer); + packetlen = ssh_buffer_get_len(buffer); + SSH_BUFFER_FREE(buffer); + if (len < 0) { + return -1; + } else if ((size_t)len != packetlen) { + SSH_LOG(SSH_LOG_PACKET, "Could not write as much data as expected"); + } + + /* Wait for the response in blocking mode */ + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + file->offset += write_len; + status_msg_free(status); + return write_len; + default: + break; + } + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + file->offset += write_len; + status_msg_free(status); + return -1; + default: + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d during write!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return -1; + } + + return -1; /* not reached */ +} + +/* Seek to a specific location in a file. */ +int +sftp_seek(sftp_file file, uint32_t new_offset) +{ + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +int +sftp_seek64(sftp_file file, uint64_t new_offset) +{ + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +/* Report current byte position in file. */ +unsigned long sftp_tell(sftp_file file) { + return (unsigned long)file->offset; +} +/* Report current byte position in file. */ +uint64_t sftp_tell64(sftp_file file) { + return (uint64_t) file->offset; +} + +/* Rewinds the position of the file pointer to the beginning of the file.*/ +void sftp_rewind(sftp_file file) { + file->offset = 0; + file->eof = 0; +} + +/* code written by Nick */ +int sftp_unlink(sftp_session sftp, const char *file) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + if (msg->packet_type == SSH_FXP_STATUS) { + /* by specification, this command's only supposed to return SSH_FXP_STATUS */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session,SSH_FATAL, + "Received message %d when attempting to remove file", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* code written by Nick */ +int sftp_rmdir(sftp_session sftp, const char *directory) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + directory); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to remove directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Code written by Nick */ +int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_attributes errno_attr = NULL; + struct sftp_attributes_struct attr; + ssh_buffer buffer; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + rc = ssh_buffer_pack(buffer, + "ds", + id, + directory); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, &attr); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_FAILURE: + /* + * mkdir always returns a failure, even if the path already exists. + * To be POSIX conform and to be able to map it to EEXIST a stat + * call is needed here. + */ + errno_attr = sftp_lstat(sftp, directory); + if (errno_attr != NULL) { + SAFE_FREE(errno_attr); + sftp_set_error(sftp, SSH_FX_FILE_ALREADY_EXISTS); + } + break; + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to make directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* code written by nick */ +int sftp_rename(sftp_session sftp, const char *original, const char *newname) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + const char *extension_name = "posix-rename@openssh.com"; + int request_type; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* + * posix-rename@openssh.com extension will be used + * if it is supported by sftp + */ + if (sftp_extension_supported(sftp, + extension_name, + "1")) { + rc = ssh_buffer_pack(buffer, + "dsss", + id, + extension_name, + original, + newname); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + request_type = SSH_FXP_EXTENDED; + } else { + rc = ssh_buffer_pack(buffer, + "dss", + id, + original, + newname); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if (sftp->version >= 4) { + /* + * POSIX rename atomically replaces newpath, + * we should do the same only available on >=v4 + */ + ssh_buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE); + } + + request_type = SSH_FXP_RENAME; + } + + rc = sftp_packet_write(sftp, request_type, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to rename", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Code written by Nick */ +/* Set file attributes on a file, directory or symbolic link. */ +int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) +{ + uint32_t id; + ssh_buffer buffer; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + if (sftp == NULL || file == NULL || attr == NULL) { + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, attr); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +int +sftp_lsetstat(sftp_session sftp, const char *file, sftp_attributes attr) +{ + uint32_t id; + ssh_buffer buffer = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + const char *extension_name = "lsetstat@openssh.com"; + int rc; + + if (sftp == NULL || file == NULL || attr == NULL) { + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, "dss", id, extension_name, file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, attr); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to lsetstat", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Change the file owner and group */ +int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.uid = owner; + attr.gid = group; + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change permissions of a file */ +int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change the last modification and access time of a file. */ +int sftp_utimes(sftp_session sftp, const char *file, + const struct timeval *times) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.atime = times[0].tv_sec; + attr.atime_nseconds = times[0].tv_usec; + + attr.mtime = times[1].tv_sec; + attr.mtime_nseconds = times[1].tv_usec; + + attr.flags |= SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME | + SSH_FILEXFER_ATTR_SUBSECOND_TIMES; + + return sftp_setstat(sftp, file, &attr); +} + +int sftp_symlink(sftp_session sftp, const char *target, const char *dest) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return -1; + if (target == NULL || dest == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* The OpenSSH sftp server has order of the arguments reversed, see the + * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in + * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL + * for more information */ + if (ssh_get_openssh_version(sftp->session)) { + rc = ssh_buffer_pack(buffer, + "dss", + id, + target, + dest); + } else { + rc = ssh_buffer_pack(buffer, + "dss", + id, + dest, + target); + } + if (rc != SSH_OK){ + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +char *sftp_readlink(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error_invalid(sftp); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + path); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_READLINK, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *lnk = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &lnk); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to retrieve link"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return lnk; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath) +{ + ssh_buffer buffer = NULL; + uint32_t id; + const char *extension_name = "hardlink@openssh.com"; + sftp_status_message status = NULL; + sftp_message msg = NULL; + int rc; + + if (sftp == NULL) { + return -1; + } + + if (oldpath == NULL || newpath == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "dsss", + id, + extension_name, + oldpath, + newpath); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to create hardlink", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { + sftp_statvfs_t statvfs; + int rc; + + statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); + if (statvfs == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqqqqqqqqq", + &statvfs->f_bsize, /* file system block size */ + &statvfs->f_frsize, /* fundamental fs block size */ + &statvfs->f_blocks, /* number of blocks (unit f_frsize) */ + &statvfs->f_bfree, /* free blocks in file system */ + &statvfs->f_bavail, /* free blocks for non-root */ + &statvfs->f_files, /* total file inodes */ + &statvfs->f_ffree, /* free file inodes */ + &statvfs->f_favail, /* free file inodes for to non-root */ + &statvfs->f_fsid, /* file system id */ + &statvfs->f_flag, /* bit mask of f_flag values */ + &statvfs->f_namemax/* maximum filename length */ + ); + if (rc != SSH_OK) { + SAFE_FREE(statvfs); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return statvfs; +} + +sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "statvfs@openssh.com", + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to get statvfs", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +int sftp_fsync(sftp_file file) +{ + sftp_session sftp; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return -1; + } + sftp = file->sftp; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "dsS", + id, + "fsync@openssh.com", + file->handle); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto done; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + goto done; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + sftp_status_message status; + + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + rc = -1; + goto done; + } + + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + /* SUCCESS, LEAVE */ + status_msg_free(status); + rc = 0; + goto done; + default: + break; + } + + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + + rc = -1; + goto done; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to set stats", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + rc = -1; +done: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +sftp_statvfs_t sftp_fstatvfs(sftp_file file) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_session sftp; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return NULL; + } + sftp = file->sftp; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dsS", + id, + "fstatvfs@openssh.com", + file->handle); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc == -1) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +void sftp_statvfs_free(sftp_statvfs_t statvfs) { + if (statvfs == NULL) { + return; + } + + SAFE_FREE(statvfs); +} + +static sftp_limits_t sftp_limits_new(void) +{ + return calloc(1, sizeof(struct sftp_limits_struct)); +} + +static sftp_limits_t sftp_parse_limits(sftp_session sftp, ssh_buffer buf) +{ + sftp_limits_t limits = NULL; + int rc; + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqq", + &limits->max_packet_length, /** maximum number of bytes in a single sftp packet */ + &limits->max_read_length, /** maximum length in a SSH_FXP_READ packet */ + &limits->max_write_length, /** maximum length in a SSH_FXP_WRITE packet */ + &limits->max_open_handles /** maximum number of active handles allowed by server */ + ); + if (rc != SSH_OK) { + SAFE_FREE(limits); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid limits structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + /* cap the max read and write length to UINT32_MAX as we really can not read + * nor write more as the len member of the SSH_FXP_READ/WRITE packets is + * uint32 */ + limits->max_read_length = MIN(limits->max_read_length, UINT32_MAX); + limits->max_write_length = MIN(limits->max_write_length, UINT32_MAX); + + return limits; +} + +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + "limits@openssh.com"); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_limits_t limits = sftp_parse_limits(sftp, msg->payload); + sftp_message_free(msg); + if (limits == NULL) { + return NULL; + } + + return limits; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to get limits", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +static sftp_limits_t sftp_limits_use_default(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits->max_packet_length = 34000; + limits->max_read_length = 32768; + limits->max_write_length = 32768; + + /* + * For max-open-handles field openssh says : + * If the server doesn't enforce a specific limit, then the field may + * be set to 0. This implies the server relies on the OS to enforce + * limits (e.g. available memory or file handles), and such limits + * might be dynamic. The client SHOULD take care to not try to exceed + * reasonable limits. + */ + limits->max_open_handles = 0; + + return limits; +} + +sftp_limits_t sftp_limits(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + if (sftp->limits == NULL) { + ssh_set_error(sftp, SSH_FATAL, + "Uninitialized sftp session, " + "sftp_init() was not called or failed"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + memcpy(limits, sftp->limits, sizeof(struct sftp_limits_struct)); + return limits; +} + +void sftp_limits_free(sftp_limits_t limits) +{ + if (limits == NULL) { + return; + } + + SAFE_FREE(limits); +} + +/* another code written by Nick */ +char *sftp_canonicalize_path(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + path); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_REALPATH, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc == -1) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *cname = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &cname); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse canonicalized path"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return cname; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +static sftp_attributes sftp_xstat(sftp_session sftp, + const char *path, + int param) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, param, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_ATTRS) { + sftp_attributes attr = sftp_parse_attr(sftp, msg->payload, 0); + sftp_message_free(msg); + + return attr; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return NULL; + } + ssh_set_error(sftp->session, SSH_FATAL, + "Received mesg %d during stat()", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + + return NULL; +} + +sftp_attributes sftp_stat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_STAT); +} + +sftp_attributes sftp_lstat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_LSTAT); +} + +sftp_attributes sftp_fstat(sftp_file file) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return NULL; + } + + rc = sftp_get_new_id(file->sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(file->sftp->session); + sftp_set_error(file->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dS", + id, + file->handle); + if (rc != SSH_OK) { + ssh_set_error_oom(file->sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(file->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(file->sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_ATTRS){ + sftp_attributes attr = sftp_parse_attr(file->sftp, msg->payload, 0); + sftp_message_free(msg); + + return attr; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(file->sftp, status->status); + ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + + return NULL; + } + ssh_set_error(file->sftp->session, SSH_FATAL, + "Received msg %d during fstat()", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(file->sftp, SSH_FX_BAD_MESSAGE); + + return NULL; +} + +char *sftp_expand_path(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "NULL received as an argument instead of the path to expand"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "expand-path@openssh.com", + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *cname = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &cname); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse expanded path"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return cname; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to expand path", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +char * +sftp_home_directory(sftp_session sftp, const char *username) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "home-directory", + username ? username : ""); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t count = 0; + char *homepath = NULL; + char *longpath = NULL; + sftp_attributes attr = NULL; + + rc = ssh_buffer_unpack(msg->payload, "ds", &count, &homepath); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to query user home directory"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + /* + for SFTP version > 3, longname field in SSH_FXP_NAME is omitted. + */ + if (sftp->version <= 3) { + rc = ssh_buffer_unpack(msg->payload, "s", &longpath); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to extract longname from payload"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } + attr = sftp_parse_attr(sftp, msg->payload, 0); + if (attr == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } + sftp_message_free(msg); + + if (count != 1) { + if (count > 1) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Multiple results returned"); + } else { + ssh_set_error(sftp->session, SSH_ERROR, "No result returned"); + } + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + if (longpath) { + free(longpath); + } + sftp_attributes_free(attr); + return homepath; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { + ssh_set_error( + sftp->session, + SSH_FATAL, + "Received message %d when attempting to query user home directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +sftp_name_id_map sftp_name_id_map_new(uint32_t count) +{ + sftp_name_id_map map = NULL; + + map = calloc(1, sizeof(struct sftp_name_id_map_struct)); + if (map == NULL) { + return NULL; + } + + map->count = count; + + map->ids = calloc(count, sizeof(uint32_t)); + if (map->ids == NULL) { + SAFE_FREE(map); + return NULL; + } + + map->names = calloc(count, sizeof(char *)); + if (map->names == NULL) { + SAFE_FREE(map->ids); + SAFE_FREE(map); + return NULL; + } + + return map; +} + +void sftp_name_id_map_free(sftp_name_id_map map) +{ + if (map == NULL) { + return; + } + + SAFE_FREE(map->ids); + + if (map->names != NULL) { + for (uint32_t i = 0; i < map->count; i++) { + SAFE_FREE(map->names[i]); + } + SAFE_FREE(map->names); + } + + SAFE_FREE(map); +} + +static int sftp_buffer_add_ids(ssh_buffer buffer, sftp_name_id_map map) +{ + uint32_t id_count = map ? map->count : 0; + int rc; + + rc = ssh_buffer_pack(buffer, "d", sizeof(uint32_t) * id_count); + if (rc != SSH_OK) { + return -1; + } + + for (uint32_t i = 0; i < id_count; i++) { + rc = ssh_buffer_pack(buffer, "d", map->ids[i]); + if (rc != SSH_OK) { + return -1; + } + } + + return 0; +} + +static int sftp_parse_names(ssh_buffer buffer, sftp_name_id_map map) +{ + uint32_t name_buf_len = 0; + char *name = NULL; + uint32_t id_count = map ? map->count : 0; + int rc; + + rc = ssh_buffer_unpack(buffer, "d", &name_buf_len); + if (rc != SSH_OK) { + return -1; + } + + for (uint32_t i = 0; i < id_count; i++) { + rc = ssh_buffer_unpack(buffer, "s", &name); + if (rc != SSH_OK) { + return -1; + } + + name_buf_len -= strlen(name) + sizeof(uint32_t); + map->names[i] = name; + } + + if (name_buf_len != 0) { + return -1; + } + + return 0; +} + +int sftp_get_users_groups_by_id(sftp_session sftp, + sftp_name_id_map users_map, + sftp_name_id_map groups_map) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return -1; + } + + /* check if the user has provided the correct arguments */ + if (users_map == NULL && groups_map == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Both users map and groups map cannot be NULL"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, "ds", id, "users-groups-by-id@openssh.com"); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* pack all uids */ + rc = sftp_buffer_add_ids(buffer, users_map); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* pack all gids */ + rc = sftp_buffer_add_ids(buffer, groups_map); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + rc = sftp_parse_names(msg->payload, users_map); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse usernames"); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return -1; + } + + rc = sftp_parse_names(msg->payload, groups_map); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse groupnames"); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return -1; + } + + sftp_message_free(msg); + return 0; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to get user and " + "group names by id", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +#endif /* WITH_SFTP */ diff --git a/src/libs/libssh-0.12.2/src/sftp_aio.c b/src/libs/libssh-0.12.2/src/sftp_aio.c new file mode 100644 index 000000000000..ae4f2d119989 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sftp_aio.c @@ -0,0 +1,501 @@ +/* + * sftp_aio.c - Secure FTP functions for asynchronous i/o + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider + * Copyright (c) 2023 by Eshan Kelkar + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" + +#ifdef WITH_SFTP + +struct sftp_aio_struct { + sftp_file file; + uint32_t id; + size_t len; +}; + +static sftp_aio sftp_aio_new(void) +{ + sftp_aio aio = NULL; + aio = calloc(1, sizeof(struct sftp_aio_struct)); + return aio; +} + +void sftp_aio_free(sftp_aio aio) +{ + SAFE_FREE(aio); +} + +ssize_t sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id, read_len; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number of " + "bytes to read"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + /* Apply a cap on the length a user is allowed to read + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + read_len = (uint32_t)MIN(sftp->limits->max_read_length, len); + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + file->handle, + file->offset, + read_len); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = read_len; + + rc = sftp_packet_write(sftp, SSH_FXP_READ, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we read len bytes from the file */ + file->offset += read_len; + *aio = aio_handle; + return read_len; +} + +ssize_t sftp_aio_wait_read(sftp_aio *aio, + void *buf, + size_t buf_size) +{ + sftp_file file = NULL; + size_t bytes_requested; + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + uint32_t string_len, host_len; + int rc, err; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed " + "instead of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf_size < bytes_requested) { + ssh_set_error(sftp->session, SSH_FATAL, + "Buffer size (%zu, passed by the caller) is " + "smaller than the number of bytes requested " + "to read (%zu, as per the supplied sftp aio)", + buf_size, bytes_requested); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + /* handle an existing request */ + rc = sftp_recv_response_msg(sftp, (*aio)->id, !file->nonblocking, &msg); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (rc == SSH_AGAIN) { + /* return without freeing the (*aio) */ + return SSH_AGAIN; + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status != SSH_FX_EOF) { + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server : %s", status->errormsg); + err = SSH_ERROR; + } else { + file->eof = 1; + /* Update the offset correctly */ + file->offset = file->offset - bytes_requested; + err = SSH_OK; + } + + status_msg_free(status); + return err; + + case SSH_FXP_DATA: + rc = ssh_buffer_get_u32(msg->payload, &string_len); + if (rc == 0) { + /* Insufficient data in the buffer */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + host_len = ntohl(string_len); + if (host_len > buf_size) { + /* + * This should never happen, as according to the + * SFTP protocol the server reads bytes less than + * or equal to the number of bytes requested to read. + * + * And we have checked before that the buffer size is + * greater than or equal to the number of bytes requested + * to read, hence code of this if block should never + * get executed. + */ + ssh_set_error(sftp->session, SSH_FATAL, + "DATA packet (%u bytes) received from sftp server " + "cannot fit into the supplied buffer (%zu bytes)", + host_len, buf_size); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return SSH_ERROR; + } + + string_len = ssh_buffer_get_data(msg->payload, buf, host_len); + if (string_len != host_len) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + /* Update the offset with the correct value */ + file->offset = file->offset - (bytes_requested - string_len); + sftp_message_free(msg); + return string_len; + + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during read!", msg->packet_type); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + return SSH_ERROR; /* not reached */ +} + +ssize_t sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id, write_len; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead " + "of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number " + "of bytes to write"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + /* Apply a cap on the length a user is allowed to write + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + write_len = (uint32_t)MIN(sftp->limits->max_write_length, len); + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + write_len, /* len of datastring */ + (size_t)write_len, + buf); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = write_len; + + rc = sftp_packet_write(sftp, SSH_FXP_WRITE, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we wrote len bytes to the file */ + file->offset += write_len; + *aio = aio_handle; + return write_len; +} + +ssize_t sftp_aio_wait_write(sftp_aio *aio) +{ + sftp_file file = NULL; + size_t bytes_requested; + + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* This should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + rc = sftp_recv_response_msg(sftp, (*aio)->id, !file->nonblocking, &msg); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (rc == SSH_AGAIN) { + /* Return without freeing the (*aio) */ + return SSH_AGAIN; + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status == SSH_FX_OK) { + status_msg_free(status); + return bytes_requested; + } + + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return SSH_ERROR; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during write!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return SSH_ERROR; +} + +#endif /* WITH_SFTP */ diff --git a/src/libs/libssh-0.12.2/src/sftp_common.c b/src/libs/libssh-0.12.2/src/sftp_common.c new file mode 100644 index 000000000000..006c41b15f90 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sftp_common.c @@ -0,0 +1,1082 @@ +/* + * sftp_common.c - Secure FTP functions which are private and are used + * internally by other sftp api functions spread across + * various source files. + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/bytearray.h" + +#ifdef WITH_SFTP + +/* Buffer size maximum is 256M */ +#define SFTP_PACKET_SIZE_MAX 0x10000000 + +sftp_packet sftp_packet_read(sftp_session sftp) +{ + uint8_t tmpbuf[4]; + uint8_t *buffer = NULL; + sftp_packet packet = sftp->read_packet; + uint32_t size; + int nread; + bool is_eof; + int rc; + + packet->sftp = sftp; + + /* + * If the packet has a payload, then just reinit the buffer, otherwise + * allocate a new one. + */ + if (packet->payload != NULL) { + rc = ssh_buffer_reinit(packet->payload); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } else { + packet->payload = ssh_buffer_new(); + if (packet->payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } + + nread = 0; + do { + int s; + + /* read from channel until 4 bytes have been read or an error occurs */ + s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); + if (s < 0) { + goto error; + } else if (s == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet size"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Timeout while reading sftp packet size"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + } else { + nread += s; + } + } while (nread < 4); + + size = PULL_BE_U32(tmpbuf, 0); + if (size == 0 || size > SFTP_PACKET_SIZE_MAX) { + ssh_set_error(sftp->session, SSH_FATAL, "Invalid sftp packet size!"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + + do { + nread = ssh_channel_read(sftp->channel, tmpbuf, 1, 0); + if (nread < 0) { + goto error; + } else if (nread == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet type"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Timeout while reading sftp packet type"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + } + } while (nread < 1); + + packet->type = tmpbuf[0]; + + /* Remove the packet type size */ + size -= sizeof(uint8_t); + + /* Allocate the receive buffer from payload */ + buffer = ssh_buffer_allocate(packet->payload, size); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + while (size > 0 && size < SFTP_PACKET_SIZE_MAX) { + nread = ssh_channel_read(sftp->channel, buffer, size, 0); + if (nread < 0) { + /* TODO: check if there are cases where an error needs to be set here */ + goto error; + } + + if (nread > 0) { + buffer += nread; + size -= nread; + } else { /* nread == 0 */ + /* Retry the reading unless the remote was closed */ + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "Received EOF while reading sftp packet"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Timeout while reading sftp packet"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + } + } + + return packet; +error: + ssh_buffer_reinit(packet->payload); + return NULL; +} + +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) +{ + uint8_t header[5] = {0}; + uint32_t payload_size; + int size; + int rc; + + /* Add size of type */ + payload_size = ssh_buffer_get_len(payload) + sizeof(uint8_t); + PUSH_BE_U32(header, 0, payload_size); + PUSH_BE_U8(header, 4, type); + + rc = ssh_buffer_prepend_data(payload, header, sizeof(header)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + size = ssh_channel_write(sftp->channel, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (size < 0) { + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if ((uint32_t)size != ssh_buffer_get_len(payload)) { + SSH_LOG(SSH_LOG_PACKET, + "Had to write %" PRIu32 " bytes, wrote only %d", + ssh_buffer_get_len(payload), + size); + } + + return size; +} + +void sftp_packet_free(sftp_packet packet) +{ + if (packet == NULL) { + return; + } + + SSH_BUFFER_FREE(packet->payload); + free(packet); +} + +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) +{ + uint32_t flags = (attr ? attr->flags : 0); + int rc; + + flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); + + rc = ssh_buffer_pack(buffer, "d", flags); + if (rc != SSH_OK) { + return -1; + } + + if (attr != NULL) { + if (flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_pack(buffer, "q", attr->size); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_pack(buffer, "d", attr->permissions); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); + if (rc != SSH_OK) { + return -1; + } + } + } + + return 0; +} + +/* + * Parse the attributes from a payload from some messages. It is coded on + * baselines from the protocol version 4. + * This code is more or less dead but maybe we will need it in the future. + */ +static sftp_attributes sftp_parse_attr_4(sftp_session sftp, + ssh_buffer buf, + int expectnames) +{ + sftp_attributes attr = NULL; + ssh_string owner = NULL; + ssh_string group = NULL; + uint32_t flags = 0; + int ok = 0; + + /* unused member variable */ + (void) expectnames; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + /* This isn't really a loop, but it is like a try..catch.. */ + do { + if (ssh_buffer_get_u32(buf, &flags) != 4) { + break; + } + + flags = ntohl(flags); + attr->flags = flags; + + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if (ssh_buffer_get_u64(buf, &attr->size) != 8) { + break; + } + attr->size = ntohll(attr->size); + } + + if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { + owner = ssh_buffer_get_ssh_string(buf); + if (owner == NULL) { + break; + } + attr->owner = ssh_string_to_char(owner); + SSH_STRING_FREE(owner); + if (attr->owner == NULL) { + break; + } + + group = ssh_buffer_get_ssh_string(buf); + if (group == NULL) { + break; + } + attr->group = ssh_string_to_char(group); + SSH_STRING_FREE(group); + if (attr->group == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { + break; + } + attr->permissions = ntohl(attr->permissions); + + /* FIXME on windows! */ + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { + if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { + break; + } + attr->atime64 = ntohll(attr->atime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { + break; + } + attr->atime_nseconds = ntohl(attr->atime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_CREATETIME) { + if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { + break; + } + attr->createtime = ntohll(attr->createtime); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { + break; + } + attr->createtime_nseconds = ntohl(attr->createtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { + if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { + break; + } + attr->mtime64 = ntohll(attr->mtime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { + break; + } + attr->mtime_nseconds = ntohl(attr->mtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_ACL) { + if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { + break; + } + attr->extended_count = ntohl(attr->extended_count); + + while (attr->extended_count && + (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && + (attr->extended_data = ssh_buffer_get_ssh_string(buf))) { + attr->extended_count--; + /* just ignore the extensions -- we can't interpret them */ + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + } + + if (attr->extended_count) { + break; + } + } + ok = 1; + } while (0); + + if (ok == 0) { + /* break issued somewhere */ + SSH_STRING_FREE(attr->acl); + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + + return NULL; + } + + return attr; +} + +enum sftp_longname_field_e { + SFTP_LONGNAME_PERM = 0, + SFTP_LONGNAME_FIXME, + SFTP_LONGNAME_OWNER, + SFTP_LONGNAME_GROUP, + SFTP_LONGNAME_SIZE, + SFTP_LONGNAME_DATE, + SFTP_LONGNAME_TIME, + SFTP_LONGNAME_NAME, +}; + +static char * sftp_parse_longname(const char *longname, + enum sftp_longname_field_e longname_field) +{ + const char *p = NULL, *q = NULL; + size_t len, field = 0; + + if (longname == NULL || longname_field < SFTP_LONGNAME_PERM || + longname_field > SFTP_LONGNAME_NAME) { + return NULL; + } + + p = longname; + /* + * Find the beginning of the field which is specified + * by sftp_longname_field_e. + */ + while (*p != '\0' && field != longname_field) { + if (isspace(*p)) { + field++; + p++; + while (*p != '\0' && isspace(*p)) { + p++; + } + } else { + p++; + } + } + + /* If we reached NULL before we got our field fail */ + if (field != longname_field) { + return NULL; + } + + q = p; + while (*q != '\0' && !isspace(*q)) { + q++; + } + + len = q - p; + + return strndup(p, len); +} + +/* sftp version 0-3 code. It is different from the v4 */ +/* maybe a paste of the draft is better than the code */ +/* + uint32 flags + uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE + uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS + uint32 atime present only if flag SSH_FILEXFER_ACMODTIME + uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME + uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED + string extended_type + string extended_data + ... more extended data (extended_type - extended_data pairs), + so that number of pairs equals extended_count */ +static sftp_attributes sftp_parse_attr_3(sftp_session sftp, + ssh_buffer buf, + int expectname) +{ + sftp_attributes attr; + int rc; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + if (expectname) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->name, + &attr->longname); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Name: %s", attr->name); + + /* Set owner and group if we talk to openssh and have the longname */ + if (ssh_get_openssh_version(sftp->session)) { + attr->owner = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_OWNER); + if (attr->owner == NULL) { + goto error; + } + + attr->group = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_GROUP); + if (attr->group == NULL) { + goto error; + } + } + } + + rc = ssh_buffer_unpack(buf, "d", &attr->flags); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Flags: %.8" PRIx32, attr->flags); + + if (attr->flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_unpack(buf, "q", &attr->size); + if(rc != SSH_OK) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Size: %" PRIu64, (uint64_t)attr->size); + } + + if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->uid, + &attr->gid); + if (rc != SSH_OK) { + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_unpack(buf, "d", &attr->permissions); + if (rc != SSH_OK) { + goto error; + } + + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->atime, + &attr->mtime); + if (rc != SSH_OK) { + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { + rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); + if (rc != SSH_OK) { + goto error; + } + + if (attr->extended_count > 0) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->extended_type, + &attr->extended_data); + if (rc != SSH_OK) { + goto error; + } + attr->extended_count--; + } + /* just ignore the remaining extensions */ + + while (attr->extended_count > 0) { + ssh_string tmp1,tmp2; + rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); + if (rc != SSH_OK){ + goto error; + } + SAFE_FREE(tmp1); + SAFE_FREE(tmp2); + attr->extended_count--; + } + } + + return attr; + +error: + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->name); + SAFE_FREE(attr->longname); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + + return NULL; +} + +sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname) +{ + switch (session->version) { + case 4: + return sftp_parse_attr_4(session, buf, expectname); + case 3: + case 2: + case 1: + case 0: + return sftp_parse_attr_3(session, buf, expectname); + default: + ssh_set_error(session->session, SSH_FATAL, + "Version %d unsupported by client", + session->server_version); + return NULL; + } + + return NULL; +} + +void sftp_set_error(sftp_session sftp, int errnum) +{ + if (sftp != NULL) { + sftp->errnum = errnum; + } +} + +void sftp_message_free(sftp_message msg) +{ + if (msg == NULL) { + return; + } + + SSH_BUFFER_FREE(msg->payload); + SAFE_FREE(msg); +} + +static sftp_request_queue request_queue_new(sftp_message msg) +{ + sftp_request_queue queue = NULL; + + queue = calloc(1, sizeof(struct sftp_request_queue_struct)); + if (queue == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + queue->message = msg; + + return queue; +} + +static void request_queue_free(sftp_request_queue queue) +{ + if (queue == NULL) { + return; + } + + ZERO_STRUCTP(queue); + SAFE_FREE(queue); +} + +static int +sftp_enqueue(sftp_session sftp, sftp_message msg) +{ + sftp_request_queue queue = NULL; + sftp_request_queue ptr; + + queue = request_queue_new(msg); + if (queue == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, + "Queued msg id %" PRIu32 " type %d", + msg->id, msg->packet_type); + + if(sftp->queue == NULL) { + sftp->queue = queue; + } else { + ptr = sftp->queue; + while(ptr->next) { + ptr=ptr->next; /* find end of linked list */ + } + ptr->next = queue; /* add it on bottom */ + } + + return 0; +} + +/* + * Pulls a message from the queue based on the ID. + * Returns NULL if no message has been found. + */ +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id) +{ + sftp_request_queue prev = NULL; + sftp_request_queue queue; + sftp_message msg; + + if(sftp->queue == NULL) { + return NULL; + } + + queue = sftp->queue; + while (queue) { + if (queue->message->id == id) { + /* remove from queue */ + if (prev == NULL) { + sftp->queue = queue->next; + } else { + prev->next = queue->next; + } + msg = queue->message; + request_queue_free(queue); + SSH_LOG(SSH_LOG_PACKET, + "Dequeued msg id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + return msg; + } + prev = queue; + queue = queue->next; + } + + return NULL; +} + +static sftp_message sftp_get_message(sftp_packet packet) +{ + sftp_session sftp = packet->sftp; + sftp_message msg = NULL; + struct ssh_iterator *id_it = NULL; + bool id_found = false; + int rc; + + switch (packet->type) { + case SSH_FXP_STATUS: + case SSH_FXP_HANDLE: + case SSH_FXP_DATA: + case SSH_FXP_ATTRS: + case SSH_FXP_NAME: + case SSH_FXP_EXTENDED_REPLY: + break; + default: + ssh_set_error(packet->sftp->session, + SSH_FATAL, + "Unknown packet type %d", + packet->type); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg = calloc(1, sizeof(struct sftp_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg->sftp = packet->sftp; + msg->packet_type = packet->type; + + /* Move the payload from the packet to the message */ + msg->payload = packet->payload; + packet->payload = NULL; + + rc = ssh_buffer_unpack(msg->payload, "d", &msg->id); + if (rc != SSH_OK) { + ssh_set_error(packet->sftp->session, SSH_FATAL, + "Invalid packet %d: no ID", packet->type); + sftp_message_free(msg); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + SSH_LOG(SSH_LOG_PACKET, + "Packet with id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + + /* Validate that this ID is in our outstanding requests list */ + id_it = ssh_list_get_iterator(sftp->outstanding_ids); + for (; id_it != NULL; id_it = id_it->next) { + uint32_t *stored_id = (uint32_t *)id_it->data; + if (*stored_id == msg->id) { + id_found = true; + ssh_list_remove(sftp->outstanding_ids, id_it); + free(stored_id); + break; + } + } + + if (!id_found) { + ssh_set_error(packet->sftp->session, + SSH_FATAL, + "Unknown request ID %" PRIu32, + msg->id); + sftp_message_free(msg); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + return msg; +} + +int sftp_get_new_id(sftp_session sftp, uint32_t *id_out) +{ + uint32_t *id = NULL; + int rc; + + if (id_out == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + id = malloc(sizeof(uint32_t)); + if (id == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + *id = ++sftp->id_counter; + rc = ssh_list_append(sftp->outstanding_ids, id); + if (rc != SSH_OK) { + free(id); + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + *id_out = *id; + + return SSH_OK; +} + +int sftp_read_and_dispatch(sftp_session sftp) +{ + sftp_packet packet = NULL; + sftp_message msg = NULL; + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + /* something nasty happened reading the packet */ + return -1; + } + + msg = sftp_get_message(packet); + if (msg == NULL) { + return -1; + } + + if (sftp_enqueue(sftp, msg) < 0) { + sftp_message_free(msg); + return -1; + } + + return 0; +} + +int sftp_recv_response_msg(sftp_session sftp, + uint32_t id, + bool blocking, + sftp_message *msg_ptr) +{ + sftp_message msg = NULL; + int rc; + + if (sftp == NULL) { + return SSH_ERROR; + } + + if (msg_ptr == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Trying to receive response of request id %" PRIu32 " in %s mode", + id, + blocking ? "blocking" : "non-blocking"); + + /* + * We deliberately check the queue first for the response before + * polling/blocking on the channel. The reason for this approach is + * explained by the following example (And a similar scenario can occur when + * the async sftp aio API is used, because it provides the control of which + * responses to receive (and in what order) to the user via the + * sftp_aio_wait_*() functions) + * + * Its possible that while this function is trying to receive some + * specific response (based on request id), other responses have already + * arrived (or may arrive) before that specific response on the channel. In + * that case, this function would collect those other responses from the + * channel, add them to the sftp response queue (using + * sftp_read_and_dipatch()) and finally provide the caller with the + * required specific response. + * + * Now, whenever the caller will call this function again to get one of + * those other responses, it won't be on the channel, instead it would be + * present in the queue. + * + * Assuming that no new response ever comes on the channel, if we don't + * check the queue first and instead: + * - (In non blocking mode) poll on the channel, then we'd always get 0 + * bytes of data and return SSH_AGAIN. + * - (In blocking mode) wait on the channel, then + * sftp_read_and_dispatch() would block infinitely by default if the + * user has not set any timeout. + * + * Hence checking the queue for the response first and if not found there, + * polling/blocking on the channel is advised. + */ + while (msg == NULL) { + /* + * Before trying to poll/block on the channel for data, probe the queue + * to check whether the response is already present in it. + */ + msg = sftp_dequeue(sftp, id); + if (msg != NULL) { + break; + } + + if (!blocking) { + rc = ssh_channel_poll(sftp->channel, 0); + if (rc == SSH_ERROR) { + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (rc == 0) { + /* nothing available and we cannot block */ + return SSH_AGAIN; + } + } + + rc = sftp_read_and_dispatch(sftp); + if (rc == -1) { + /* something nasty has happened */ + return SSH_ERROR; + } + } + + *msg_ptr = msg; + return SSH_OK; +} + +sftp_status_message parse_status_msg(sftp_message msg) +{ + sftp_status_message status = NULL; + int rc; + + if (msg->packet_type != SSH_FXP_STATUS) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_status message passed in!"); + sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); + return NULL; + } + + status = calloc(1, sizeof(struct sftp_status_message_struct)); + if (status == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + status->id = msg->id; + rc = ssh_buffer_unpack(msg->payload, "d", + &status->status); + if (rc != SSH_OK) { + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(msg->payload, "ss", + &status->errormsg, + &status->langmsg); + + if (rc != SSH_OK && msg->sftp->version >= 3) { + SSH_LOG(SSH_LOG_WARN, + "Invalid SSH_FXP_STATUS message. Missing error message."); + } + + if (status->errormsg == NULL) + status->errormsg = strdup("No error message in packet"); + + if (status->langmsg == NULL) + status->langmsg = strdup(""); + + if (status->errormsg == NULL || status->langmsg == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + status_msg_free(status); + return NULL; + } + + return status; +} + +void status_msg_free(sftp_status_message status) +{ + if (status == NULL) { + return; + } + + SAFE_FREE(status->errormsg); + SAFE_FREE(status->langmsg); + SAFE_FREE(status); +} + +#endif /* WITH_SFTP */ diff --git a/src/libs/libssh-0.12.2/src/sftpserver.c b/src/libs/libssh-0.12.2/src/sftpserver.c new file mode 100644 index 000000000000..e926ccc6f13d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sftpserver.c @@ -0,0 +1,2173 @@ +/* + * sftpserver.c - server based function for the sftp protocol + * + * This file is part of the SSH Library + * + * Copyright (c) 2005 Aris Adamantiadis + * Copyright (c) 2022 Zeyu Sheng + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + + +#ifndef _WIN32 +#include +#include +#include +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#ifdef HAVE_SYS_UTIME_H +#include +#endif /* HAVE_SYS_UTIME_H */ + +#include +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/sftpserver.h" +#include "libssh/ssh2.h" +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" + +#define SFTP_HANDLES 256 + +#define MAX_ENTRIES_NUM_IN_PACKET 50 +#define MAX_LONG_NAME_LEN 350 + +/** + * @internal + * + * @brief Creates an SFTP client message from a received packet. + * + * Allocates and initializes a sftp_client_message structure from a raw + * SFTP packet. Copies the complete @p packet payload and parses common + * fields such as message type, request id, and message-specific data + * (handle, filename, attributes, offsets, etc.) depending on the SFTP + * message type. + * + * On success, the returned message owns its internal buffers and must + * be freed with sftp_client_message_free(). + * + * @param[in] sftp The SFTP session associated with the packet. + * @param[in] packet The received SFTP packet to decode. + * + * @return A newly allocated sftp_client_message on success, or NULL on + * error (memory allocation failure, malformed packet, or + * unsupported message type). + */ +static sftp_client_message +sftp_make_client_message(sftp_session sftp, sftp_packet packet) +{ + ssh_session session = sftp->session; + sftp_client_message msg = NULL; + ssh_buffer payload = NULL; + int rc; + int version; + + msg = calloc(1, sizeof(struct sftp_client_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + payload = packet->payload; + msg->type = packet->type; + msg->sftp = sftp; + + /* take a copy of the whole packet */ + msg->complete_message = ssh_buffer_new(); + if (msg->complete_message == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_add_data(msg->complete_message, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (rc < 0) { + goto error; + } + + if (msg->type != SSH_FXP_INIT) { + rc = ssh_buffer_get_u32(payload, &msg->id); + if (rc != sizeof(uint32_t)) { + goto error; + } + } + + switch (msg->type) { + case SSH_FXP_INIT: + rc = ssh_buffer_unpack(payload, + "d", + &version); + if (rc != SSH_OK) { + printf("unpack init failed!\n"); + goto error; + } + sftp->client_version = version; + break; + case SSH_FXP_CLOSE: + case SSH_FXP_READDIR: + msg->handle = ssh_buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + goto error; + } + break; + case SSH_FXP_READ: + rc = ssh_buffer_unpack(payload, + "Sqd", + &msg->handle, + &msg->offset, + &msg->len); + if (rc != SSH_OK) { + goto error; + } + if (msg->len > MAX_PACKET_LEN - 1024) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Too large SSH_FXP_READ length: %" PRIu32, + msg->len); + goto error; + } + break; + case SSH_FXP_WRITE: + rc = ssh_buffer_unpack(payload, + "SqS", + &msg->handle, + &msg->offset, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_REMOVE: + case SSH_FXP_RMDIR: + case SSH_FXP_OPENDIR: + case SSH_FXP_READLINK: + case SSH_FXP_REALPATH: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_RENAME: + case SSH_FXP_SYMLINK: + rc = ssh_buffer_unpack(payload, + "sS", + &msg->filename, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_MKDIR: + case SSH_FXP_SETSTAT: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_FSETSTAT: + msg->handle = ssh_buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_LSTAT: + case SSH_FXP_STAT: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + if (sftp->version > 3) { + ssh_buffer_unpack(payload, "d", &msg->flags); + } + break; + case SSH_FXP_OPEN: + rc = ssh_buffer_unpack(payload, + "sd", + &msg->filename, + &msg->flags); + if (rc != SSH_OK) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_FSTAT: + rc = ssh_buffer_unpack(payload, + "S", + &msg->handle); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_EXTENDED: + rc = ssh_buffer_unpack(payload, + "s", + &msg->submessage); + if (rc != SSH_OK) { + goto error; + } + + if (strcmp(msg->submessage, "hardlink@openssh.com") == 0 || + strcmp(msg->submessage, "posix-rename@openssh.com") == 0) { + rc = ssh_buffer_unpack(payload, + "sS", + &msg->filename, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + } else if (strcmp(msg->submessage, "statvfs@openssh.com") == 0 ){ + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + } + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received unhandled sftp message %d", msg->type); + goto error; + } + + return msg; + +error: + sftp_client_message_free(msg); + return NULL; +} + +/** + * @brief Reads the next SFTP client message from the session. + * + * Reads a single SFTP packet from the given SFTP session and converts it + * into a parsed sftp_client_message structure. + * + * @param[in] sftp The SFTP session to read from. + * + * @return A newly allocated sftp_client_message on success; NULL if no packet + * is available or an error occurs. + */ +sftp_client_message sftp_get_client_message(sftp_session sftp) +{ + sftp_packet packet = NULL; + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + return NULL; + } + return sftp_make_client_message(sftp, packet); +} + +/** + * @brief Get the client message from a sftp packet. + * + * @param sftp The sftp session handle. + * + * @return The pointer to the generated sftp client message. + */ +static sftp_client_message +sftp_get_client_message_from_packet(sftp_session sftp) +{ + sftp_packet packet = NULL; + + packet = sftp->read_packet; + if (packet == NULL) { + return NULL; + } + return sftp_make_client_message(sftp, packet); +} + +/** + * @brief Send an SFTP client message. + * + * Writes the given client message as a packet using the stored message + * type and complete_message buffer. Can be used in case of proxying. + * + * @param[in] sftp The SFTP session. + * @param[in] msg The client message to send. + * + * @return 0 on success; -1 on error from sftp_packet_write(). + */ +int sftp_send_client_message(sftp_session sftp, sftp_client_message msg) +{ + return sftp_packet_write(sftp, msg->type, msg->complete_message); +} + +/** + * @brief Get the SFTP client message type. + * + * Returns the SFTP packet type associated with the given client message + * (for example `SSH_FXP_READ`, `SSH_FXP_WRITE`, `SSH_FXP_OPEN`, ...). + * + * @param[in] msg The SFTP client message. + * + * @return The SFTP message type as an unsigned 8-bit value. + */ +uint8_t sftp_client_message_get_type(sftp_client_message msg) +{ + return msg->type; +} + +/** + * @brief Get the filename associated with an SFTP client message. + * + * Returns the filename carried by the given SFTP client message, if the + * message type includes a filename field (for example OPEN, REMOVE, RENAME). + * + * @param[in] msg The SFTP client message. + * + * @return Filename string, or NULL if no filename + * is associated with the message. + */ +const char *sftp_client_message_get_filename(sftp_client_message msg) +{ + return msg->filename; +} + +/** + * @brief Set the filename associated with an SFTP client message. + * + * Replaces the current filename stored in the client message with a copy + * of the given @p newname string. + * + * @param[in] msg The SFTP client message to modify. + * @param[in] newname The new filename to store in the message. + * + * @warn On failure, the filename in the message is set to `NULL`. Users of + * sftp_client_message_get_filename() need to check the return value! + */ +void +sftp_client_message_set_filename(sftp_client_message msg, const char *newname) +{ + free(msg->filename); + msg->filename = strdup(newname); +} + +/** + * @brief Get the data field of an SFTP client message as a string. + * + * Converts the internal ssh_string data field to a string + * on first use and caches the result in the message. Subsequent calls + * return the cached pointer. + * + * @param[in] msg The SFTP client message. + * + * @return The data as string, or NULL on error. + */ +const char *sftp_client_message_get_data(sftp_client_message msg) +{ + if (msg->str_data == NULL) + msg->str_data = ssh_string_to_char(msg->data); + return msg->str_data; +} + +/** + * @brief Get the flags associated with an SFTP client message. + * + * Returns the flags field stored in the given SFTP client message. The exact + * meaning of the flags depends on the SFTP message type (for example, open + * or stat flags). + * + * @param[in] msg The SFTP client message. + * + * @return The flags value as an unsigned 32-bit integer. + */ +uint32_t sftp_client_message_get_flags(sftp_client_message msg) +{ + return msg->flags; +} + +/** + * @brief Get the submessage name associated with an SFTP client message. + * + * Returns the submessage string stored in the given SFTP client message. + * This is typically used for vendor-specific SFTP operations. + * + * @param[in] msg The SFTP client message. + * + * @return The submessage name as a string, or NULL if no + * submessage is associated with the message. + */ +const char *sftp_client_message_get_submessage(sftp_client_message msg) +{ + return msg->submessage; +} + +/** + * @brief Free an SFTP client message and its associated resources. + * + * Releases all dynamically allocated fields in the SFTP client message + * (such as filename, submessage, data, handle, attributes, and cached + * buffers) and then frees the message structure itself. The function + * does nothing if msg is NULL. + * + * @param[in] msg The SFTP client message to free, or NULL. + */ +void sftp_client_message_free(sftp_client_message msg) +{ + if (msg == NULL) { + return; + } + + SAFE_FREE(msg->filename); + SAFE_FREE(msg->submessage); + SSH_STRING_FREE(msg->data); + SSH_STRING_FREE(msg->handle); + sftp_attributes_free(msg->attr); + SSH_BUFFER_FREE(msg->complete_message); + SAFE_FREE(msg->str_data); + ZERO_STRUCTP(msg); + SAFE_FREE(msg); +} + +/** + * @brief Send an SFTP NAME reply for a client message. + * + * Builds and sends an `SSH_FXP_NAME` packet in response to the given + * SFTP client message, containing a single filename and its attributes. + * The function encodes the message id, the count of returned names + * (always 1), the filename fields and the provided attributes. + * + * @param[in] msg The SFTP client message being answered. + * @param[in] name The filename to return to the client. + * @param[in] attr The file attributes associated with the filename. + * + * @return 0 on success; -1 on memory allocation failure or packet send error. + */ +int +sftp_reply_name(sftp_client_message msg, const char *name, sftp_attributes attr) +{ + ssh_buffer out = NULL; + ssh_string file = NULL; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + file = ssh_string_from_char(name); + if (file == NULL) { + SSH_BUFFER_FREE(out); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending name %s", ssh_string_get_char(file)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(1)) < 0 || + ssh_buffer_add_ssh_string(out, file) < 0 || + ssh_buffer_add_ssh_string(out, file) < 0 || /* The protocol is broken here between 3 & 4 */ + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(file); + return -1; + } + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(file); + + return 0; +} + +/** + * @brief Send an SFTP HANDLE reply for a client message. + * + * Builds and sends an `SSH_FXP_HANDLE` packet in response to the given + * SFTP client message, containing the provided file @p handle. The message + * id is taken from the client message and the handle is encoded as an + * SSH string. + * + * @param[in] msg The SFTP client message being answered. + * @param[in] handle The file handle to return to the client. + * + * @return 0 on success; -1 on memory allocation failure or packet send error. + */ +int sftp_reply_handle(sftp_client_message msg, ssh_string handle) +{ + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + ssh_log_hexdump("Sending handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_ssh_string(out, handle) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_HANDLE, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } + SSH_BUFFER_FREE(out); + + return 0; +} + +/** + * @brief Send an SFTP ATTRS reply for a client message. + * + * Builds and sends an `SSH_FXP_ATTRS` packet in response to the given + * SFTP client message, encoding the message id and the provided file + * attributes. + * + * @param[in] msg The SFTP client message being answered. + * @param[in] attr The file attributes to return to the client. + * + * @return 0 on success; -1 on memory allocation failure or packet send error. + */ +int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) +{ + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending attr"); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_ATTRS, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } + SSH_BUFFER_FREE(out); + + return 0; +} + +/** + * @brief Add one name entry to a multi-name SFTP reply. + * + * Appends a @p file name, @p longname and attributes to the buffered NAME reply + * stored in the client message. Can be called multiple times before the + * reply is sent. + * + * @param[in] msg The SFTP client message being prepared. + * @param[in] file The filename to add. + * @param[in] longname The long name to add. + * @param[in] attr The file attributes for this entry. + * + * @return 0 on success; -1 on memory allocation or buffer write error. + */ +int +sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr) +{ + ssh_string name = NULL; + + name = ssh_string_from_char(file); + if (name == NULL) { + return -1; + } + + if (msg->attrbuf == NULL) { + msg->attrbuf = ssh_buffer_new(); + if (msg->attrbuf == NULL) { + SSH_STRING_FREE(name); + return -1; + } + } + + if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0) { + SSH_STRING_FREE(name); + return -1; + } + + SSH_STRING_FREE(name); + name = ssh_string_from_char(longname); + if (name == NULL) { + return -1; + } + if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0 || + buffer_add_attributes(msg->attrbuf, attr) < 0) { + SSH_STRING_FREE(name); + return -1; + } + SSH_STRING_FREE(name); + msg->attr_num++; + + return 0; +} + +/** + * @brief Send a multi-name SFTP reply. + * + * Sends an `SSH_FXP_NAME` packet for the given client message using the + * accumulated name entries stored in msg->attrbuf and msg->attr_num. + * After sending, the buffer and counter are reset. + * + * @param[in] msg The SFTP client message to reply to. + * + * @return 0 on success; -1 on memory allocation or packet send error. + */ +int sftp_reply_names(sftp_client_message msg) +{ + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + SSH_BUFFER_FREE(msg->attrbuf); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending %d names", msg->attr_num); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(msg->attr_num)) < 0 || + ssh_buffer_add_data(out, ssh_buffer_get(msg->attrbuf), + ssh_buffer_get_len(msg->attrbuf)) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_BUFFER_FREE(msg->attrbuf); + return -1; + } + + SSH_BUFFER_FREE(out); + SSH_BUFFER_FREE(msg->attrbuf); + + msg->attr_num = 0; + msg->attrbuf = NULL; + + return 0; +} + +/** + * @brief Send an SFTP STATUS reply. + * + * Sends an `SSH_FXP_STATUS` packet for the given client message, including + * the @p status code and an optional human readable @p message. The language + * tag is sent as an empty string. + * + * @param[in] msg The SFTP client message to reply to. + * @param[in] status The SFTP status code to send (e.g. `SSH_FX_OK`, + * `SSH_FX_FAILURE`). + * @param[in] message Optional text message describing the status, or NULL. + * + * @return 0 on success; -1 on memory allocation or packet send error. + */ +int +sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message) +{ + ssh_buffer out = NULL; + ssh_string s = NULL; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + s = ssh_string_from_char(message ? message : ""); + if (s == NULL) { + SSH_BUFFER_FREE(out); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending status %d, message: %s", status, + ssh_string_get_char(s)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(status)) < 0 || + ssh_buffer_add_ssh_string(out, s) < 0 || + ssh_buffer_add_u32(out, 0) < 0 || /* language string */ + sftp_packet_write(msg->sftp, SSH_FXP_STATUS, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(s); + return -1; + } + + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(s); + + return 0; +} + +/** + * @brief Send an SFTP DATA reply. + * + * Sends an `SSH_FXP_DATA` packet for the given client message, containing + * the provided data buffer and its length. + * + * @param[in] msg The SFTP client message to reply to. + * @param[in] data The data buffer to send. + * @param[in] len Number of bytes from data to send. + * + * @return 0 on success; -1 on memory allocation or packet send error. + */ +int sftp_reply_data(sftp_client_message msg, const void *data, int len) +{ + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending data, length: %d", len); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, ntohl(len)) < 0 || + ssh_buffer_add_data(out, data, len) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_DATA, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } + SSH_BUFFER_FREE(out); + + return 0; +} + +/** + * @brief Handle the statvfs request, return information the mounted file system. + * + * @param msg The sftp client message. + * + * @param st The statvfs state of target file. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +static int +sftp_reply_statvfs(sftp_client_message msg, sftp_statvfs_t st) +{ + int ret = 0; + ssh_buffer out; + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending statvfs reply"); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bsize)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_frsize)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_blocks)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bfree)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bavail)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_files)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_ffree)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_favail)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_fsid)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_flag)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_namemax)) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_EXTENDED_REPLY, out) < 0) { + ret = -1; + } + SSH_BUFFER_FREE(out); + + return ret; +} + +int sftp_reply_version(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_session session = sftp->session; + int version; + ssh_buffer reply; + int rc; + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending version packet"); + + /* The SSH_FXP_INIT can be received only once -- repeated initialization + * is not supported */ + if (sftp->version > 0) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received duplicate INIT message"); + return SSH_ERROR; + } + + /* from draft-spaghetti-sshm-filexfer-00 Section 4: + * > The server responds with a SSH_FXP_VERSION packet, supplying the + * > lowest of its own and the client's version number. + */ + version = MIN(sftp->client_version, LIBSFTP_VERSION); + + reply = ssh_buffer_new(); + if (reply == NULL) { + ssh_set_error_oom(session); + return -1; + } + + rc = ssh_buffer_pack(reply, + "dssssss", + version, + "posix-rename@openssh.com", + "1", + "hardlink@openssh.com", + "1", + "statvfs@openssh.com", + "2"); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(reply); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_VERSION, reply); + if (rc < 0) { + SSH_BUFFER_FREE(reply); + return -1; + } + SSH_BUFFER_FREE(reply); + + SSH_LOG(SSH_LOG_PROTOCOL, "Server version sent"); + + sftp->version = version; + + return SSH_OK; +} + +/** + * @brief Allocate a new SFTP handle slot. + * + * Finds a free handle slot in the SFTP session, stores the given @p info + * there and returns a 4-byte ssh_string that encodes the handle + * index. + * + * @param[in] sftp The SFTP session. + * @param[in] info Info to be stored in the handle slot. + * + * @return A new handle as ssh_string on success; NULL if no slot is + * available or on memory allocation failure. + */ +ssh_string sftp_handle_alloc(sftp_session sftp, void *info) +{ + ssh_string ret = NULL; + uint32_t val; + uint32_t i; + + if (sftp->handles == NULL) { + sftp->handles = calloc(SFTP_HANDLES, sizeof(void *)); + if (sftp->handles == NULL) { + return NULL; + } + } + + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == NULL) { + break; + } + } + + if (i == SFTP_HANDLES) { + return NULL; /* no handle available */ + } + + val = i; + ret = ssh_string_new(4); + if (ret == NULL) { + return NULL; + } + + memcpy(ssh_string_data(ret), &val, sizeof(uint32_t)); + sftp->handles[i] = info; + + return ret; +} + +/** + * @brief Resolve an SFTP handle to its stored info. + * + * Decodes the 4-byte @p handle value, checks bounds and returns the pointer + * stored in the corresponding handle slot in the SFTP session. + * + * @param[in] sftp The SFTP session. + * @param[in] handle The handle value as ssh_string. + * + * @return The stored pointer on success, or NULL if the handle table is + * not initialized, the handle size is invalid, or the index is + * out of range. + */ +void *sftp_handle(sftp_session sftp, ssh_string handle) +{ + uint32_t val; + + if (sftp->handles == NULL) { + return NULL; + } + + if (ssh_string_len(handle) != sizeof(uint32_t)) { + return NULL; + } + + memcpy(&val, ssh_string_data(handle), sizeof(uint32_t)); + + if (val >= SFTP_HANDLES) { + return NULL; + } + + return sftp->handles[val]; +} + +void sftp_handle_remove(sftp_session sftp, void *handle) +{ + int i; + + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == handle) { + sftp->handles[i] = NULL; + break; + } + } +} + +/* Default SFTP handlers */ + +static const char * +ssh_str_error(int u_errno) +{ + switch (u_errno) { + case SSH_FX_NO_SUCH_FILE: + return "No such file"; + case SSH_FX_PERMISSION_DENIED: + return "Permission denied"; + case SSH_FX_BAD_MESSAGE: + return "Bad message"; + case SSH_FX_OP_UNSUPPORTED: + return "Operation not supported"; + default: + return "Operation failed"; + } +} + +static int +unix_errno_to_ssh_stat(int u_errno) +{ + int ret = SSH_OK; + switch (u_errno) { + case 0: + break; + case ENOENT: + case ENOTDIR: + case EBADF: + case ELOOP: + ret = SSH_FX_NO_SUCH_FILE; + break; + case EPERM: + case EACCES: + case EFAULT: + ret = SSH_FX_PERMISSION_DENIED; + break; + case ENAMETOOLONG: + case EINVAL: + ret = SSH_FX_BAD_MESSAGE; + break; + case ENOSYS: + ret = SSH_FX_OP_UNSUPPORTED; + break; + default: + ret = SSH_FX_FAILURE; + break; + } + + return ret; +} + +static void +stat_to_filexfer_attrib(const struct stat *z_st, struct sftp_attributes_struct *z_attr) +{ + z_attr->flags = 0 | (uint32_t)SSH_FILEXFER_ATTR_SIZE; + z_attr->size = z_st->st_size; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_UIDGID; + z_attr->uid = z_st->st_uid; + z_attr->gid = z_st->st_gid; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS; + z_attr->permissions = z_st->st_mode; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_ACMODTIME; + z_attr->atime = (uint32_t)z_st->st_atime; + z_attr->mtime = (uint32_t)z_st->st_mtime; +} + +static void +clear_filexfer_attrib(struct sftp_attributes_struct *z_attr) +{ + z_attr->flags = 0; + z_attr->size = 0; + z_attr->uid = 0; + z_attr->gid = 0; + z_attr->permissions = 0; + z_attr->atime = 0; + z_attr->mtime = 0; +} + +#ifndef _WIN32 +/* internal */ +enum sftp_handle_type +{ + SFTP_NULL_HANDLE, + SFTP_DIR_HANDLE, + SFTP_FILE_HANDLE +}; + +struct sftp_handle +{ + enum sftp_handle_type type; + int fd; + DIR *dirp; + char *name; +}; + +SSH_SFTP_CALLBACK(process_unsupported); +SSH_SFTP_CALLBACK(process_open); +SSH_SFTP_CALLBACK(process_read); +SSH_SFTP_CALLBACK(process_write); +SSH_SFTP_CALLBACK(process_close); +SSH_SFTP_CALLBACK(process_opendir); +SSH_SFTP_CALLBACK(process_readdir); +SSH_SFTP_CALLBACK(process_rmdir); +SSH_SFTP_CALLBACK(process_realpath); +SSH_SFTP_CALLBACK(process_mkdir); +SSH_SFTP_CALLBACK(process_lstat); +SSH_SFTP_CALLBACK(process_stat); +SSH_SFTP_CALLBACK(process_readlink); +SSH_SFTP_CALLBACK(process_symlink); +SSH_SFTP_CALLBACK(process_remove); +SSH_SFTP_CALLBACK(process_extended_statvfs); +SSH_SFTP_CALLBACK(process_setstat); + +const struct sftp_message_handler message_handlers[] = { + {"open", NULL, SSH_FXP_OPEN, process_open}, + {"close", NULL, SSH_FXP_CLOSE, process_close}, + {"read", NULL, SSH_FXP_READ, process_read}, + {"write", NULL, SSH_FXP_WRITE, process_write}, + {"lstat", NULL, SSH_FXP_LSTAT, process_lstat}, + {"fstat", NULL, SSH_FXP_FSTAT, process_unsupported}, + {"setstat", NULL, SSH_FXP_SETSTAT, process_setstat}, + {"fsetstat", NULL, SSH_FXP_FSETSTAT, process_unsupported}, + {"opendir", NULL, SSH_FXP_OPENDIR, process_opendir}, + {"readdir", NULL, SSH_FXP_READDIR, process_readdir}, + {"remove", NULL, SSH_FXP_REMOVE, process_remove}, + {"mkdir", NULL, SSH_FXP_MKDIR, process_mkdir}, + {"rmdir", NULL, SSH_FXP_RMDIR, process_rmdir}, + {"realpath", NULL, SSH_FXP_REALPATH, process_realpath}, + {"stat", NULL, SSH_FXP_STAT, process_stat}, + {"rename", NULL, SSH_FXP_RENAME, process_unsupported}, + {"readlink", NULL, SSH_FXP_READLINK, process_readlink}, + {"symlink", NULL, SSH_FXP_SYMLINK, process_symlink}, + {"init", NULL, SSH_FXP_INIT, sftp_reply_version}, + {NULL, NULL, 0, NULL}, +}; + +const struct sftp_message_handler extended_handlers[] = { + /* here are some extended type handlers */ + {"statvfs", "statvfs@openssh.com", 0, process_extended_statvfs}, + {NULL, NULL, 0, NULL}, +}; + +static int +process_open(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + uint32_t msg_flag = sftp_client_message_get_flags(client_msg); + uint32_t mode = client_msg->attr->permissions; + ssh_string handle_s = NULL; + struct sftp_handle *h = NULL; + int file_flag; + int fd = -1; + int status; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing open: filename %s, mode=0%o" PRIu32, + filename, mode); + + if ((msg_flag & (uint32_t)SSH_FXF_WRITE) == SSH_FXF_WRITE) { + if ((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) { + /* Both read and write */ + file_flag = O_RDWR; + } else { + /* Only write */ + file_flag = O_WRONLY; + } + + if ((msg_flag & (uint32_t)SSH_FXF_APPEND) == SSH_FXF_APPEND) { + file_flag |= O_APPEND; + } + + if ((msg_flag & (uint32_t)SSH_FXF_CREAT) == SSH_FXF_CREAT) { + file_flag |= O_CREAT; + } + + if ((msg_flag & (uint32_t)SSH_FXF_TRUNC) == SSH_FXF_TRUNC) { + file_flag |= O_TRUNC; + } + } else if ((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) { + file_flag = O_RDONLY; + } else { + SSH_LOG(SSH_LOG_PROTOCOL, "undefined message flag: %" PRIu32, msg_flag); + sftp_reply_status(client_msg, SSH_FX_FAILURE, "Flag error"); + return SSH_ERROR; + } + + fd = open(filename, file_flag, mode); + if (fd == -1) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "error open file with error: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, "Write error"); + return SSH_OK; + } + + h = calloc(1, sizeof (struct sftp_handle)); + if (h == NULL) { + close(fd); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to allocate a new handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->fd = fd; + h->type = SFTP_FILE_HANDLE; + handle_s = sftp_handle_alloc(client_msg->sftp, h); + if (handle_s != NULL) { + sftp_reply_handle(client_msg, handle_s); + ssh_string_free(handle_s); + } else { + free(h); + close(fd); + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate handle"); + } + + return SSH_OK; +} + +static int +process_read(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + ssize_t readn = 0; + int fd = -1; + char *buffer = NULL; + off_t off; + + ssh_log_hexdump("Processing read: handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h != NULL && h->type == SFTP_FILE_HANDLE) { + fd = h->fd; + } + + if (fd < 0) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "invalid fd (%d) received from handle", fd); + return SSH_ERROR; + } + off = lseek(fd, client_msg->offset, SEEK_SET); + if (off == -1) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, + "error seeking file fd: %d at offset: %" PRIu64, + fd, client_msg->offset); + return SSH_OK; + } + + buffer = malloc(client_msg->len); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate memory for read data"); + return SSH_ERROR; + } + readn = ssh_readn(fd, buffer, client_msg->len); + if (readn < 0) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "read file error!"); + free(buffer); + return SSH_OK; + } else if (readn > 0) { + sftp_reply_data(client_msg, buffer, readn); + } else { + sftp_reply_status(client_msg, SSH_FX_EOF, NULL); + } + + free(buffer); + return SSH_OK; +} + +static int +process_write(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + ssize_t written = 0; + int fd = -1; + const char *msg_data = NULL; + uint32_t len; + off_t off; + + ssh_log_hexdump("Processing write: handle", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h != NULL && h->type == SFTP_FILE_HANDLE) { + fd = h->fd; + } + if (fd < 0) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "write file fd error!"); + return SSH_ERROR; + } + + msg_data = ssh_string_get_char(client_msg->data); + len = ssh_string_len(client_msg->data); + + off = lseek(fd, client_msg->offset, SEEK_SET); + if (off == -1) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, + "error seeking file at offset: %" PRIu64, + client_msg->offset); + return SSH_OK; + } + written = ssh_writen(fd, msg_data, len); + if (written != (ssize_t)len) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, "Write error"); + SSH_LOG(SSH_LOG_PROTOCOL, "file write error!"); + return SSH_OK; + } + + sftp_reply_status(client_msg, SSH_FX_OK, NULL); + + return SSH_OK; +} + +static int +process_close(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + int ret; + + ssh_log_hexdump("Processing close: handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "invalid handle"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, "Invalid handle"); + return SSH_OK; + } else if (h->type == SFTP_FILE_HANDLE) { + int fd = h->fd; + close(fd); + ret = SSH_OK; + } else if (h->type == SFTP_DIR_HANDLE) { + DIR *dir = h->dirp; + closedir(dir); + ret = SSH_OK; + } else { + ret = SSH_ERROR; + } + SAFE_FREE(h->name); + sftp_handle_remove(sftp, h); + SAFE_FREE(h); + + if (ret == SSH_OK) { + sftp_reply_status(client_msg, SSH_FX_OK, NULL); + } else { + SSH_LOG(SSH_LOG_PROTOCOL, "closing file failed"); + sftp_reply_status(client_msg, SSH_FX_BAD_MESSAGE, "Invalid handle"); + } + + return ret; +} + +static int +process_opendir(sftp_client_message client_msg) +{ + DIR *dir = NULL; + const char *dir_name = sftp_client_message_get_filename(client_msg); + ssh_string handle_s = NULL; + struct sftp_handle *h = NULL; + + if (dir_name == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing dir_name from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing opendir %s", dir_name); + + dir = opendir(dir_name); + if (dir == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "No such directory"); + return SSH_OK; + } + + h = calloc(1, sizeof (struct sftp_handle)); + if (h == NULL) { + closedir(dir); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to allocate a new handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->dirp = dir; + h->name = strdup(dir_name); + if (h->name == NULL) { + free(h); + closedir(dir); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to duplicate directory name"); + sftp_reply_status(client_msg, + SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->type = SFTP_DIR_HANDLE; + handle_s = sftp_handle_alloc(client_msg->sftp, h); + + if (handle_s != NULL) { + sftp_reply_handle(client_msg, handle_s); + ssh_string_free(handle_s); + } else { + SAFE_FREE(h->name); + free(h); + closedir(dir); + sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available"); + } + + return SSH_OK; +} + +static int +readdir_long_name(char *z_file_name, struct stat *z_st, char *z_long_name) +{ + char time[50]; + char *ptr = z_long_name, *nl = NULL; + int mode = z_st->st_mode; + + *ptr = '\0'; + + switch (mode & S_IFMT) { + case S_IFDIR: + *ptr++ = 'd'; + break; + default: + *ptr++ = '-'; + break; + } + + /* user */ + if (mode & 0400) + *ptr++ = 'r'; + else + *ptr++ = '-'; + + if (mode & 0200) + *ptr++ = 'w'; + else + *ptr++ = '-'; + + if (mode & 0100) { + if (mode & S_ISUID) + *ptr++ = 's'; + else + *ptr++ = 'x'; + } else + *ptr++ = '-'; + + /* group */ + if (mode & 040) + *ptr++ = 'r'; + else + *ptr++ = '-'; + if (mode & 020) + *ptr++ = 'w'; + else + *ptr++ = '-'; + if (mode & 010) + *ptr++ = 'x'; + else + *ptr++ = '-'; + + /* other */ + if (mode & 04) + *ptr++ = 'r'; + else + *ptr++ = '-'; + if (mode & 02) + *ptr++ = 'w'; + else + *ptr++ = '-'; + if (mode & 01) + *ptr++ = 'x'; + else + *ptr++ = '-'; + + *ptr++ = ' '; + *ptr = '\0'; + + ctime_r(&z_st->st_mtime, time); + if ((nl = strchr(time, '\n'))) { + *nl = '\0'; + } + snprintf(ptr, + MAX_LONG_NAME_LEN - strlen(z_long_name), + "%3d %d %d %d %s %s", + (int)z_st->st_nlink, + (int)z_st->st_uid, + (int)z_st->st_gid, + (int)z_st->st_size, + time + 4, + z_file_name); + + return SSH_OK; +} + +static int +process_readdir(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + int ret = SSH_OK; + int entries = 0; + struct dirent *dentry = NULL; + DIR *dir = NULL; + char long_path[PATH_MAX]; + int srclen; + const char *handle_name = NULL; + + ssh_log_hexdump("Processing readdir: handle", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, client_msg->handle); + if (h != NULL && h->type == SFTP_DIR_HANDLE) { + dir = h->dirp; + handle_name = h->name; + } + if (dir == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "got wrong handle from msg"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + if (handle_name == NULL) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + srclen = strlen(handle_name); + if (srclen + 2 >= PATH_MAX) { + SSH_LOG(SSH_LOG_PROTOCOL, "handle string length exceed max length!"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + for (int i = 0; i < MAX_ENTRIES_NUM_IN_PACKET; i++) { + dentry = readdir(dir); + + if (dentry != NULL) { + struct sftp_attributes_struct attr; + struct stat st; + char long_name[MAX_LONG_NAME_LEN]; + + if (strlen(dentry->d_name) + srclen + 1 >= PATH_MAX) { + SSH_LOG(SSH_LOG_PROTOCOL, + "handle string length exceed max length!"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + snprintf(long_path, PATH_MAX, "%s/%s", handle_name, dentry->d_name); + + if (lstat(long_path, &st) == 0) { + stat_to_filexfer_attrib(&st, &attr); + } else { + clear_filexfer_attrib(&attr); + } + + if (readdir_long_name(dentry->d_name, &st, long_name) == 0) { + sftp_reply_names_add(client_msg, dentry->d_name, long_name, &attr); + } else { + printf("readdir long name error\n"); + } + + entries++; + } else { + break; + } + } + + if (entries > 0) { + ret = sftp_reply_names(client_msg); + } else { + sftp_reply_status(client_msg, SSH_FX_EOF, NULL); + } + + return ret; +} + +static int +process_mkdir(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + uint32_t msg_flags = client_msg->attr->flags; + uint32_t permission = client_msg->attr->permissions; + uint32_t mode = (msg_flags & (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS) + ? permission & (uint32_t)07777 : 0777; + int status = SSH_FX_OK; + int rv; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing mkdir %s, mode=0%o" PRIu32, + filename, mode); + + rv = mkdir(filename, mode); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "failed to mkdir: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + } + + sftp_reply_status(client_msg, status, NULL); + + return SSH_OK; +} + +static int +process_rmdir(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + int status = SSH_FX_OK; + int rv; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing rmdir %s", filename); + + rv = rmdir(filename); + if (rv < 0) { + status = unix_errno_to_ssh_stat(errno); + } + + sftp_reply_status(client_msg, status, NULL); + + return SSH_OK; +} + +static int +process_realpath(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + char *path = NULL; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing realpath %s", filename); + + if (filename[0] == '\0') { + path = realpath(".", NULL); + } else { + path = realpath(filename, NULL); + } + if (path == NULL) { + int saved_errno = errno; + int status = unix_errno_to_ssh_stat(saved_errno); + const char *err_msg = ssh_str_error(status); + + SSH_LOG(SSH_LOG_PROTOCOL, "realpath failed: %s", strerror(saved_errno)); + sftp_reply_status(client_msg, status, err_msg); + return SSH_ERROR; + } + sftp_reply_name(client_msg, path, NULL); + free(path); + return SSH_OK; +} + +static int +process_lstat(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + struct sftp_attributes_struct attr; + struct stat st; + int status = SSH_FX_OK; + int rv; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing lstat %s", filename); + + rv = lstat(filename, &st); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + } else { + stat_to_filexfer_attrib(&st, &attr); + sftp_reply_attr(client_msg, &attr); + } + + return SSH_OK; +} + +static int +process_stat(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + struct sftp_attributes_struct attr; + struct stat st; + int status = SSH_FX_OK; + int rv; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing stat %s", filename); + + rv = stat(filename, &st); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + } else { + stat_to_filexfer_attrib(&st, &attr); + sftp_reply_attr(client_msg, &attr); + } + + return SSH_OK; +} + +static int +process_setstat(sftp_client_message client_msg) +{ + int rv; + int status = SSH_FX_OK; + uint32_t msg_flags = client_msg->attr->flags; + const char *filename = sftp_client_message_get_filename(client_msg); + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing setstat %s", filename); + + if (msg_flags & SSH_FILEXFER_ATTR_SIZE) { + rv = truncate(filename, client_msg->attr->size); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "changing size failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rv = chmod(filename, client_msg->attr->permissions); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "chmod failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_UIDGID) { + rv = chown(filename, client_msg->attr->uid, client_msg->attr->gid); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "chwon failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_ACMODTIME) { +#ifdef HAVE_SYS_TIME_H + struct timeval tv[2]; + + tv[0].tv_sec = client_msg->attr->atime; + tv[0].tv_usec = 0; + tv[1].tv_sec = client_msg->attr->mtime; + tv[1].tv_usec = 0; + + rv = utimes(filename, tv); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "utimes failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } +#else + struct _utimbuf tf; + + tf.actime = client_msg->attr->atime; + tf.modtime = client_msg->attr->mtime; + + rv = _utime(filename, &tf); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "utimes failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } +#endif + } + + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; +} + +static int +process_readlink(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + char buf[PATH_MAX]; + int len = -1; + const char *err_msg = NULL; + int status = SSH_FX_OK; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing readlink %s", filename); + + len = readlink(filename, buf, sizeof(buf) - 1); + if (len < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "readlink failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + err_msg = ssh_str_error(status); + sftp_reply_status(client_msg, status, err_msg); + } else { + buf[len] = '\0'; + sftp_reply_name(client_msg, buf, NULL); + } + + return SSH_OK; +} + +/* Note, that this function is using reversed order of the arguments than the + * OpenSSH sftp server as they have the arguments switched. See + * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in + * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL + * for more information */ +static int +process_symlink(sftp_client_message client_msg) +{ + const char *destpath = sftp_client_message_get_filename(client_msg); + const char *srcpath = ssh_string_get_char(client_msg->data); + int status = SSH_FX_OK; + int rv; + + if (srcpath == NULL || destpath == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "processing symlink: src=%s dest=%s", + srcpath, destpath); + + rv = symlink(srcpath, destpath); + if (rv < 0) { + int saved_errno = errno; + status = unix_errno_to_ssh_stat(saved_errno); + SSH_LOG(SSH_LOG_PROTOCOL, "symlink failed: %s", strerror(saved_errno)); + sftp_reply_status(client_msg, status, "Write error"); + } else { + sftp_reply_status(client_msg, SSH_FX_OK, "write success"); + } + + return SSH_OK; +} + +static int +process_remove(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + int rv; + int status = SSH_FX_OK; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "processing remove: %s", filename); + + rv = unlink(filename); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "unlink failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + } + + sftp_reply_status(client_msg, status, NULL); + + return SSH_OK; +} + +static int +process_unsupported(sftp_client_message client_msg) +{ + sftp_reply_status(client_msg, SSH_FX_OP_UNSUPPORTED, + "Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Message type %d not implemented", + sftp_client_message_get_type(client_msg)); + return SSH_OK; +} + +static int +process_extended_statvfs(sftp_client_message client_msg) +{ + const char *path = sftp_client_message_get_filename(client_msg); + sftp_statvfs_t sftp_statvfs; + struct statvfs st; + uint64_t flag; + int status; + int rv; + + if (path == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "processing extended statvfs: %s", path); + + rv = statvfs(path, &st); + if (rv != 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "statvfs failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } + + sftp_statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); + if (sftp_statvfs == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate statvfs structure"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + return SSH_ERROR; + } + flag = (st.f_flag & ST_RDONLY) ? SSH_FXE_STATVFS_ST_RDONLY : 0; + flag |= (st.f_flag & ST_NOSUID) ? SSH_FXE_STATVFS_ST_NOSUID : 0; + + sftp_statvfs->f_bsize = st.f_bsize; + sftp_statvfs->f_frsize = st.f_frsize; + sftp_statvfs->f_blocks = st.f_blocks; + sftp_statvfs->f_bfree = st.f_bfree; + sftp_statvfs->f_bavail = st.f_bavail; + sftp_statvfs->f_files = st.f_files; + sftp_statvfs->f_ffree = st.f_ffree; + sftp_statvfs->f_favail = st.f_favail; + sftp_statvfs->f_fsid = st.f_fsid; + sftp_statvfs->f_flag = flag; + sftp_statvfs->f_namemax = st.f_namemax; + + rv = sftp_reply_statvfs(client_msg, sftp_statvfs); + free(sftp_statvfs); + if (rv == 0) { + return SSH_OK; + } + return SSH_ERROR; +} + +static int +process_extended(sftp_client_message sftp_msg) +{ + int status = SSH_ERROR; + const char *subtype = sftp_msg->submessage; + sftp_server_message_callback handler = NULL; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing extended message: %s", subtype); + + for (int i = 0; extended_handlers[i].cb != NULL; i++) { + if (strcmp(subtype, extended_handlers[i].extended_name) == 0) { + handler = extended_handlers[i].cb; + break; + } + } + if (handler != NULL) { + status = handler(sftp_msg); + return status; + } + + sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, + "Extended Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Extended Message type %s not implemented", + subtype); + return SSH_OK; +} + +static int +dispatch_sftp_request(sftp_client_message sftp_msg) +{ + int status = SSH_ERROR; + sftp_server_message_callback handler = NULL; + uint8_t type = sftp_client_message_get_type(sftp_msg); + + SSH_LOG(SSH_LOG_PROTOCOL, "processing request type: %u", type); + + for (int i = 0; message_handlers[i].cb != NULL; i++) { + if (type == message_handlers[i].type) { + handler = message_handlers[i].cb; + break; + } + } + + if (handler != NULL) { + status = handler(sftp_msg); + } else { + sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, + "Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Message type %u not implemented", type); + return SSH_OK; + } + + return status; +} + +static int +process_client_message(sftp_client_message client_msg) +{ + int status = SSH_OK; + if (client_msg == NULL) { + return SSH_ERROR; + } + + switch (client_msg->type) { + case SSH_FXP_EXTENDED: + status = process_extended(client_msg); + break; + default: + status = dispatch_sftp_request(client_msg); + } + + if (status != SSH_OK) + SSH_LOG(SSH_LOG_PROTOCOL, + "error occurred during processing client message!"); + + return status; +} + +/** + * @brief Default subsystem request handler for SFTP subsystem + * + * @param[in] session The ssh session + * @param[in] channel The existing ssh channel + * @param[in] subsystem The subsystem name. Only "sftp" is handled + * @param[out] userdata The pointer to sftp_session which will get the + * resulting SFTP session + * + * @return `SSH_OK` when the SFTP server was successfully initialized, + * `SSH_ERROR` otherwise. + */ +int +sftp_channel_default_subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + if (strcmp(subsystem, "sftp") == 0) { + sftp_session *sftp = (sftp_session *)userdata; + + /* The SFTP subsystem was already initialized on this channel */ + if (*sftp != NULL) { + return SSH_ERROR; + } + + /* initialize sftp session and file handler */ + *sftp = sftp_server_new(session, channel); + if (*sftp == NULL) { + return SSH_ERROR; + } + + return SSH_OK; + } + return SSH_ERROR; +} + +/** + * @brief Default data callback for sftp server + * + * @param[in] session The ssh session + * @param[in] channel The ssh channel with SFTP session opened + * @param[in] data The data to be processed. + * @param[in] len The length of input data to be processed + * @param[in] is_stderr Unused channel flag for stderr flagging + * @param[in] userdata The pointer to sftp_session + * + * @return number of bytes processed, -1 when error occurs. + */ +int +sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + sftp_session *sftpp = (sftp_session *)userdata; + sftp_session sftp = NULL; + sftp_client_message msg; + uint32_t undecoded_len = len; + int rc; + + if (sftpp == NULL || *sftpp == NULL) { + SSH_LOG(SSH_LOG_WARNING, "invalid userdata passed to callback"); + return SSH_ERROR; + } + sftp = *sftpp; + + do { + int decode_len = + sftp_decode_channel_data_to_packet(sftp, data, undecoded_len); + if (decode_len == SSH_ERROR) { + return SSH_ERROR; + } + + msg = sftp_get_client_message_from_packet(sftp); + rc = process_client_message(msg); + sftp_client_message_free(msg); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PROTOCOL, "process sftp failed!"); + ssh_channel_send_eof(sftp->channel); + ssh_channel_close(sftp->channel); + // Leave freeing resources on caller + return rc; + } + + undecoded_len -= decode_len; + data = (uint8_t *)data + decode_len; + } while (undecoded_len > 0); + + return len; +} +#else +/* Not available on Windows for now */ +int +sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(void *data), + UNUSED_PARAM(uint32_t len), + UNUSED_PARAM(int is_stderr), + UNUSED_PARAM(void *userdata)) +{ + return -1; +} + +int +sftp_channel_default_subsystem_request(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *subsystem), + UNUSED_PARAM(void *userdata)) +{ + return SSH_ERROR; +} +#endif diff --git a/src/libs/libssh-0.12.2/src/sk_common.c b/src/libs/libssh-0.12.2/src/sk_common.c new file mode 100644 index 000000000000..883d15689c32 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sk_common.c @@ -0,0 +1,290 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "libssh/callbacks.h" +#include "libssh/priv.h" +#include "libssh/sk_common.h" + +#ifdef HAVE_LIBFIDO2 +#include "libssh/sk_usbhid.h" +#endif + +const char *ssh_sk_err_to_string(int sk_err) +{ + switch (sk_err) { + case SSH_SK_ERR_UNSUPPORTED: + return "Unsupported operation"; + case SSH_SK_ERR_PIN_REQUIRED: + return "PIN required but is either missing or invalid"; + case SSH_SK_ERR_DEVICE_NOT_FOUND: + return "No suitable device found"; + case SSH_SK_ERR_CREDENTIAL_EXISTS: + return "Credential already exists"; + case SSH_SK_ERR_GENERAL: + return "General error"; + default: + return "Unknown error"; + } +} + +void sk_enroll_response_burn(struct sk_enroll_response *enroll_response) +{ + if (enroll_response == NULL) { + return; + } + + BURN_FREE(enroll_response->public_key, enroll_response->public_key_len); + BURN_FREE(enroll_response->key_handle, enroll_response->key_handle_len); + BURN_FREE(enroll_response->signature, enroll_response->signature_len); + BURN_FREE(enroll_response->attestation_cert, + enroll_response->attestation_cert_len); + BURN_FREE(enroll_response->authdata, enroll_response->authdata_len); + + ssh_burn(enroll_response, sizeof(*enroll_response)); +} + +void sk_enroll_response_free(struct sk_enroll_response *enroll_response) +{ + sk_enroll_response_burn(enroll_response); + SAFE_FREE(enroll_response); +} + +void sk_sign_response_free(struct sk_sign_response *sign_response) +{ + if (sign_response == NULL) { + return; + } + + BURN_FREE(sign_response->sig_r, sign_response->sig_r_len); + BURN_FREE(sign_response->sig_s, sign_response->sig_s_len); + SAFE_FREE(sign_response); +} + +void sk_resident_key_free(struct sk_resident_key *resident_key) +{ + if (resident_key == NULL) { + return; + } + + SAFE_FREE(resident_key->application); + BURN_FREE(resident_key->user_id, resident_key->user_id_len); + sk_enroll_response_burn(&resident_key->key); + SAFE_FREE(resident_key); +} + +void sk_options_free(struct sk_option **options) +{ + size_t i; + + if (options == NULL) { + return; + } + + for (i = 0; options[i] != NULL; i++) { + SAFE_FREE(options[i]->name); + SAFE_FREE(options[i]->value); + SAFE_FREE(options[i]); + } + SAFE_FREE(options); +} + +int sk_options_validate_get(const struct sk_option **options, + const char **keys, + char ***values) +{ + size_t i, j; + size_t key_count = 0; + int found; + + if (keys == NULL || values == NULL || options == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid parameter(s) provided"); + return SSH_ERROR; + } + + while (keys[key_count] != NULL) { + key_count++; + } + + *values = calloc(key_count + 1, sizeof(char *)); + if (*values == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate values array"); + return SSH_ERROR; + } + + for (i = 0; options[i] != NULL; i++) { + const struct sk_option *option = options[i]; + + found = 0; + + /* Look for this option name in the supported keys */ + for (j = 0; j < key_count; j++) { + + if (strcmp(option->name, keys[j]) == 0) { + /* Copy the value string if it exists */ + if (option->value != NULL) { + (*values)[j] = strdup(option->value); + if ((*values)[j] == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy option value"); + goto error; + } + + } else { + (*values)[j] = NULL; + } + found = 1; + break; + } + } + + /* If option is required but not supported, fail */ + if (!found && option->required) { + SSH_LOG(SSH_LOG_WARN, + "Required option '%s' is not supported", + option->name); + goto error; + } + } + + return SSH_OK; + +error: + for (j = 0; j < key_count; j++) { + SAFE_FREE((*values)[j]); + } + + SAFE_FREE(*values); + return SSH_ERROR; +} + +struct sk_option **sk_options_dup(const struct sk_option **options) +{ + struct sk_option **new_options = NULL; + size_t count = 0; + size_t i; + + if (options == NULL) { + return NULL; + } + + /* Count the number of options */ + while (options[count] != NULL) { + count++; + } + + new_options = calloc(count + 1, sizeof(struct sk_option *)); + if (new_options == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for options array"); + return NULL; + } + + /* Copy each option */ + for (i = 0; i < count; i++) { + const struct sk_option *option = options[i]; + struct sk_option *new_option = NULL; + + new_option = calloc(1, sizeof(struct sk_option)); + if (new_option == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for option"); + goto error; + } + + /* Copy option name */ + if (option->name != NULL) { + new_option->name = strdup(option->name); + if (new_option->name == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy option name"); + SAFE_FREE(new_option); + goto error; + } + } + + /* Copy option value */ + if (option->value != NULL) { + new_option->value = strdup(option->value); + if (new_option->value == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy option value"); + SAFE_FREE(new_option->name); + SAFE_FREE(new_option); + goto error; + } + } + + new_option->required = option->required; + new_options[i] = new_option; + } + + new_options[count] = NULL; + return new_options; + +error: + SK_OPTIONS_FREE(new_options); + return NULL; +} + +bool sk_callbacks_check_compatibility( + const struct ssh_sk_callbacks_struct *callbacks) +{ + uint32_t callback_version; + uint32_t callback_version_major; + uint32_t libssh_version_major; + + if (callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "SK callbacks cannot be NULL"); + return false; + } + + /* Check if the api_version callback is provided */ + if (!ssh_callbacks_exists(callbacks, api_version)) { + SSH_LOG(SSH_LOG_WARN, "SK callbacks missing api_version callback"); + return false; + } + + /* Extract major version from callback provider */ + callback_version = callbacks->api_version(); + callback_version_major = callback_version & SSH_SK_VERSION_MAJOR_MASK; + + libssh_version_major = SSH_SK_VERSION_MAJOR; + + /* Check if major versions are compatible */ + if (callback_version_major != libssh_version_major) { + SSH_LOG(SSH_LOG_WARN, + "SK API major version mismatch: callback provides 0x%08x, " + "libssh supports 0x%08x", + callback_version_major, + libssh_version_major); + return false; + } + + return true; +} + +const struct ssh_sk_callbacks_struct *ssh_sk_get_default_callbacks(void) +{ +#ifdef HAVE_LIBFIDO2 + return ssh_sk_get_usbhid_callbacks(); +#else + return NULL; +#endif /* HAVE_LIBFIDO2 */ +} diff --git a/src/libs/libssh-0.12.2/src/sk_usbhid.c b/src/libs/libssh-0.12.2/src/sk_usbhid.c new file mode 100644 index 000000000000..782df0ae8fa4 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sk_usbhid.c @@ -0,0 +1,2239 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/callbacks.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/sk_api.h" +#include "libssh/sk_common.h" +#include "libssh/sk_usbhid.h" + +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#endif + +#define SK_USBHID_API_VERSION 0x000a0000 + +#define ECDSA_P256_PUBKEY_LEN 64 + +/* Maximum number of FIDO2/U2F devices that can be connected */ +#define MAX_FIDO_DEVICES 8 + +/* Timeout for touch detection on single FIDO2/U2F device during each polling */ +#define FIDO_POLL_MS 50 + +/* Sleep between each consecutive polling */ +#define POLL_SLEEP_NS 200000000 + +/* The entire timeout for user to touch any of the connected devices */ +#define SELECT_MS 15000 + +/* DER encoding constants */ +#define DER_SEQUENCE_TAG 0x30 +#define DER_INTEGER_TAG 0x02 +#define DER_MAX_LEN_BYTES 2 + +struct sk_device { + char *path; + fido_dev_t *fido_device; +}; + +/** + * libfido2 log handler that prints libfido2 debug messages. + * + * @param msg The log message from libfido2 + */ +static void fido_log_handler(const char *msg) +{ + if (msg == NULL) { + return; + } + + SSH_LOG(SSH_LOG_TRACE, "libfido2: %s", msg); +} + +/** + * Initialize libfido2 with appropriate logging settings based on + * current libssh log level. + */ +static void sk_fido_init(void) +{ + int fido_flags = 0; + int log_level = ssh_get_log_level(); + + /* Enable libfido2 debug output if libssh is at TRACE level */ + if (log_level == SSH_LOG_TRACE) { + fido_flags |= FIDO_DEBUG; + fido_set_log_handler(fido_log_handler); + } + + fido_init(fido_flags); +} + +/** + * Convert a libfido2 error code to a libssh security key error code. + * + * @param fido_err The FIDO error code to convert + * + * @return The corresponding SSH_SK_ERR_* error code + */ +static int fido_err_to_ssh_sk_err(int fido_err) +{ + switch (fido_err) { + case FIDO_ERR_UNSUPPORTED_OPTION: + case FIDO_ERR_UNSUPPORTED_ALGORITHM: + case FIDO_ERR_UNSUPPORTED_EXTENSION: + return SSH_SK_ERR_UNSUPPORTED; + case FIDO_ERR_PIN_REQUIRED: + case FIDO_ERR_PIN_INVALID: + return SSH_SK_ERR_PIN_REQUIRED; + default: + return SSH_SK_ERR_GENERAL; + } +} + +static void sk_device_close(struct sk_device *device) +{ + int rc; + + if (device == NULL) { + return; + } + + if (device->fido_device != NULL) { + rc = fido_dev_cancel(device->fido_device); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to cancel device operations: %s", + fido_strerr(rc)); + } + + rc = fido_dev_close(device->fido_device); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to close device: %s", + fido_strerr(rc)); + } + + fido_dev_free(&device->fido_device); + } + + SAFE_FREE(device->path); + SAFE_FREE(device); +} + +static struct sk_device *sk_device_open(const char *device_path) +{ + int rc; + struct sk_device *device = NULL; + + if (device_path == NULL) { + SSH_LOG(SSH_LOG_WARN, "Device path cannot be NULL"); + goto error; + } + + device = calloc(1, sizeof(struct sk_device)); + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for sk_device"); + goto error; + } + + device->fido_device = fido_dev_new(); + if (device->fido_device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new fido device instance"); + goto error; + } + + device->path = strdup(device_path); + if (device->path == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for device path"); + goto error; + } + + rc = fido_dev_open(device->fido_device, device->path); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to open FIDO2/U2F device at %s: %s", + device->path, + fido_strerr(rc)); + goto error; + } + + return device; + +error: + sk_device_close(device); + return NULL; +} + +static void sk_device_close_list(struct sk_device **devices, size_t num_devices) +{ + size_t i; + + if (devices == NULL) { + return; + } + + for (i = 0; i < num_devices; i++) { + sk_device_close(devices[i]); + } + SAFE_FREE(devices); +} + +static struct sk_device ** +sk_device_open_list(const fido_dev_info_t *device_list, + size_t num_devices, + size_t *num_opened) +{ + size_t i; + const char *device_path = NULL; + struct sk_device **devices = NULL; + const fido_dev_info_t *device_info = NULL; + + *num_opened = 0; + + devices = calloc(num_devices, sizeof(struct sk_device *)); + if (devices == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for device list"); + return NULL; + } + + for (i = 0; i < num_devices; i++) { + device_info = fido_dev_info_ptr(device_list, i); + if (device_info == NULL) { + SSH_LOG(SSH_LOG_INFO, "Failed to get device info for index %zu", i); + continue; + } + + device_path = fido_dev_info_path(device_info); + devices[*num_opened] = sk_device_open(device_path); + if (devices[*num_opened] == NULL) { + SSH_LOG(SSH_LOG_INFO, + "Failed to open device %zu at %s", + *num_opened, + device_path); + } else { + (*num_opened)++; + } + } + + if (*num_opened == 0) { + sk_device_close_list(devices, num_devices); + devices = NULL; + } + + return devices; +} + +/** + * Check if given device has the credentials corresponding to the given + * key_handle. + * + * @param device The security key device to check + * @param application The application identifier (relying party ID) + * @param key_handle The key handle to check for + * @param key_handle_len The length of the key handle in bytes + * + * @return FIDO_OK if resident key exists, FIDO_ERR_NO_CREDENTIALS if it + * doesn't, other FIDO_ERR_* codes on failure + */ +static int sk_device_check_key_handle(const struct sk_device *device, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len) +{ + int ret = FIDO_ERR_INTERNAL; + uint8_t dummy_data[32] = {0}; + fido_assert_t *assert = NULL; + bool is_dev_fido2 = false; + + /* + * We make use of the pre-flight checking as described in + * https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pre-flight + * to identify whether the device knows of the passed key_handle. + */ + + assert = fido_assert_new(); + if (assert == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO assertion"); + return FIDO_ERR_INTERNAL; + } + + ret = fido_assert_set_clientdata(assert, dummy_data, sizeof(dummy_data)); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set client data for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_assert_set_rp(assert, application); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set Relying Party for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_assert_set_up(assert, FIDO_OPT_FALSE); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user presence for assertion: %s", + fido_strerr(ret)); + goto out; + } + + /* Allow assertions only from this particular key_handle */ + ret = fido_assert_allow_cred(assert, key_handle, key_handle_len); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allow credential for assertion: %s", + fido_strerr(ret)); + goto out; + } + + is_dev_fido2 = fido_dev_is_fido2(device->fido_device); + + ret = fido_dev_get_assert(device->fido_device, assert, NULL); + + if (!is_dev_fido2 && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) { + /* U2F devices might return this */ + ret = FIDO_OK; + } else if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to get assertion from device: %s", + fido_strerr(ret)); + } + +out: + fido_assert_free(&assert); + return ret; +} + +/** + * Check if given device has the resident key with given user_id and + * application. + * + * @param device The security key device to check + * @param application The application identifier (relying party ID) + * @param user_id The binary user ID to search for in resident keys + * @param user_id_len Length of binary user_id + * @param pin The PIN for the device (can be NULL if not required) + * + * @return FIDO_OK if resident key exists, FIDO_ERR_NO_CREDENTIALS if it + * doesn't, other FIDO_ERR_* codes on failure + */ +static int sk_device_check_resident_key(const struct sk_device *device, + const char *application, + const uint8_t *user_id, + size_t user_id_len, + const char *pin) +{ + int rc, ret = FIDO_ERR_INTERNAL; + bool supports_uv = false; + size_t i, num_asserts = 0, len; + const uint8_t *ptr = NULL; + uint8_t dummy_data[32] = {0}; + fido_opt_t user_verification = FIDO_OPT_OMIT; + fido_assert_t *assert = NULL; + + /* If no user_id or zero length provided, nothing to compare */ + if (user_id == NULL || user_id_len == 0) { + return FIDO_ERR_NO_CREDENTIALS; + } + + assert = fido_assert_new(); + if (assert == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO assertion"); + goto out; + } + + ret = fido_assert_set_clientdata(assert, dummy_data, sizeof(dummy_data)); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set client data for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_assert_set_rp(assert, application); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set Relying Party for assertion: %s", + fido_strerr(ret)); + goto out; + } + + /* Check if device supports internal user verification methods such as + * biometric */ + supports_uv = fido_dev_supports_uv(device->fido_device); + + /* + * Determine user verification strategy for resident key enumeration: + * - If PIN is provided, rely on PIN-based authentication (UV = OMIT) + * + * - If no PIN is provided but device supports internal UV (biometric/etc), + * enable UV to ensure we can access all resident keys regardless of their + * credential protection while minimising user friction. + * + * - If no PIN is provided and device does not support internal UV, we will + * only be able to access resident keys without user-verification + * protection. + * + * Read about credential protection and resident keys: + * (https://developers.yubico.com/WebAuthn/WebAuthn_Developer_Guide/Resident_Keys.html) + */ + user_verification = + (pin == NULL && supports_uv) ? FIDO_OPT_TRUE : FIDO_OPT_OMIT; + ret = fido_assert_set_uv(assert, user_verification); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user verification for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_dev_get_assert(device->fido_device, assert, pin); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to get assertion from device: %s", + fido_strerr(ret)); + goto out; + } + + ret = FIDO_ERR_NO_CREDENTIALS; + + num_asserts = fido_assert_count(assert); + for (i = 0; i < num_asserts; i++) { + ptr = fido_assert_user_id_ptr(assert, i); + len = fido_assert_user_id_len(assert, i); + + if (len != user_id_len) { + continue; + } + + rc = memcmp(ptr, user_id, user_id_len); + if (rc == 0) { + SSH_LOG(SSH_LOG_INFO, "Resident key with given user ID exists"); + ret = FIDO_OK; + break; + } + } + +out: + fido_assert_free(&assert); + return ret; +} + +/** + * Begin touch detection on all devices in the provided list. + * + * @param devices Array of device pointers + * @param num_devices Number of devices in the array + * + * @return SSH_OK if at least one device started touch detection successfully, + * SSH_ERROR if all devices failed + */ +static int sk_device_touch_begin(struct sk_device **devices, size_t num_devices) +{ + int rc; + size_t i, num_success = 0; + + for (i = 0; i < num_devices; i++) { + rc = fido_dev_get_touch_begin(devices[i]->fido_device); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to begin touch on device %s: %s", + devices[i]->path, + fido_strerr(rc)); + } else { + num_success++; + } + } + + return (num_success > 0) ? SSH_OK : SSH_ERROR; +} + +/** + * Poll the touch status on all devices and return the index of the device + * on which touch was detected. + * + * @param devices Array of device pointers to poll for touch status + * @param num_devices Number of devices in the array + * @param touch_detected Pointer to store whether touch was detected (0 or 1) + * @param chosen_idx Pointer to store the index of the device that was touched + * + * @return SSH_OK on successful polling (regardless of whether touch was + * detected), SSH_ERROR if no devices left to poll. + * + * @warning Automatically closes the device if any error occurs + * while detecting if it was touched. + */ +static int sk_device_touch_poll(struct sk_device **devices, + size_t num_devices, + int *touch_detected, + size_t *chosen_idx) +{ + int rc; + size_t i, n_failed = 0; + + for (i = 0; i < num_devices; i++) { + if (devices[i] == NULL) { + continue; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Polling touch status on device %s", + devices[i]->path); + + rc = fido_dev_get_touch_status(devices[i]->fido_device, + touch_detected, + FIDO_POLL_MS); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to get touch status on device %s: %s", + devices[i]->path, + fido_strerr(rc)); + sk_device_close(devices[i]); + devices[i] = NULL; + + n_failed++; + if (n_failed == num_devices) { + SSH_LOG(SSH_LOG_WARN, "No devices left to poll"); + return SSH_ERROR; + } + } else if (*touch_detected) { + *chosen_idx = i; + return SSH_OK; + } + } + + *touch_detected = 0; + return SSH_OK; +} + +/** + * Select a device from the list of devices which has the given + * application and key handle. + * + * @param device_list Array of device information structures + * @param num_devices Number of devices in the device_list array + * @param application The application identifier (relying party ID) + * @param key_handle The key handle to look for + * @param key_handle_len The length of the key handle in bytes + * + * @return The selected device on success, NULL if no device found or on error. + */ +static struct sk_device * +sk_device_select_by_credential(const fido_dev_info_t *device_list, + size_t num_devices, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len) +{ + int rc; + size_t num_opened = 0, i; + struct sk_device **devices = NULL, *selected_device = NULL; + + devices = sk_device_open_list(device_list, num_devices, &num_opened); + if (devices == NULL) { + SSH_LOG(SSH_LOG_WARN, "No FIDO2/U2F devices opened"); + return NULL; + } + + selected_device = NULL; + for (i = 0; i < num_opened; i++) { + rc = sk_device_check_key_handle(devices[i], + application, + key_handle, + key_handle_len); + if (rc == FIDO_OK) { + selected_device = devices[i]; + devices[i] = NULL; + SSH_LOG(SSH_LOG_DEBUG, + "Selected device %s for key handle", + selected_device->path); + break; + } + } + + sk_device_close_list(devices, num_opened); + return selected_device; +} + +/** + * Select a device by touch, where the user touches the key they want to use. + * The function will block until a touch is detected or the timeout is reached. + * + * @param device_list Array of device information structures + * @param num_devices Number of devices in the device_list array + * + * @return The selected device on success, NULL if no device found or on error. + */ +static struct sk_device * +sk_device_select_by_touch(const fido_dev_info_t *device_list, + size_t num_devices) +{ + int rc, touch = 0; + size_t num_opened = 0, chosen_idx; + struct sk_device **devices = NULL, *selected_device = NULL; + struct ssh_timestamp ts; + +#ifndef _WIN32 + struct timespec poll_sleep = {.tv_sec = 0, .tv_nsec = POLL_SLEEP_NS}; +#endif + + devices = sk_device_open_list(device_list, num_devices, &num_opened); + if (devices == NULL) { + SSH_LOG(SSH_LOG_WARN, "No FIDO2/U2F devices opened"); + return NULL; + } + + if (num_opened == 1) { + selected_device = devices[0]; + devices[0] = NULL; + SSH_LOG(SSH_LOG_DEBUG, + "Only one device opened, automatically selected %s", + selected_device->path); + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "%zu FIDO2/U2F device(s) opened", num_opened); + + rc = sk_device_touch_begin(devices, num_opened); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to begin touch on any device"); + goto out; + } + + ssh_timestamp_init(&ts); + do { + rc = sk_device_touch_poll(devices, num_opened, &touch, &chosen_idx); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to poll touch status"); + goto out; + } else if (touch) { + selected_device = devices[chosen_idx]; + devices[chosen_idx] = NULL; + goto out; + } + + if (ssh_timeout_elapsed(&ts, SELECT_MS)) { + SSH_LOG(SSH_LOG_WARN, "Touch selection timed out"); + break; + } + +#ifdef _WIN32 + /* Sleep expects milliseconds; convert nanoseconds (round down). */ + Sleep((DWORD)(POLL_SLEEP_NS / 1000000)); +#else + nanosleep(&poll_sleep, NULL); +#endif /* _WIN32 */ + + } while (true); + +out: + sk_device_close_list(devices, num_opened); + return selected_device; +} + +/** + * Probe for FIDO2/U2F devices and choose one based on the provided application + * and key handle. If application or key handle are NULL, the user will be + * prompted to touch the key they want to use. + * + * @param application The application identifier (relying party ID), can be NULL + * @param key_handle The key handle to look for, can be NULL + * @param key_handle_len The length of the key handle in bytes + * @param probe_resident Whether to probe for resident keys + * + * @return The selected device on success, NULL if no device found or on error. + */ +static struct sk_device *sk_device_probe(const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + bool probe_resident) +{ + int rc; + size_t num_devices = 0; + struct sk_device *device = NULL; + fido_dev_info_t *device_list = NULL; + +#ifdef _WIN32 + if (!probe_resident) { + device = sk_device_open("windows://hello"); + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open Windows Hello device"); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, "Using Windows Hello device"); + return device; + } +#endif /* _WIN32 */ + + device_list = fido_dev_info_new(MAX_FIDO_DEVICES); + if (device_list == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create device info list"); + return NULL; + } + + rc = fido_dev_info_manifest(device_list, MAX_FIDO_DEVICES, &num_devices); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to get device info manifest: %s", + fido_strerr(rc)); + goto out; + } + if (num_devices == 0) { + SSH_LOG(SSH_LOG_WARN, "No FIDO2/U2F devices found"); + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "%zu FIDO2/U2F device(s) detected", num_devices); + + /* + * If key_handle and application are specified, then we find the key which + * has the corresponding credentials, otherwise, we rely on the user to + * touch the key that they want to use. + */ + if (application != NULL && key_handle != NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Selecting device by credential"); + device = sk_device_select_by_credential(device_list, + num_devices, + application, + key_handle, + key_handle_len); + } else { + SSH_LOG(SSH_LOG_DEBUG, "Selecting device by touch"); + device = sk_device_select_by_touch(device_list, num_devices); + } + +out: + fido_dev_info_free(&device_list, MAX_FIDO_DEVICES); + return device; +} + +/** + * Export an ECDSA public key from a FIDO2/U2F credential. + * + * The format returned by libfido2 is different from the expected SEC1 octet + * string representation, so this function performs the necessary conversion. + * + * @param credential The FIDO2/U2F credential containing the ECDSA public key + * @param response The enrollment response structure to fill with the public key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_public_key_ecdsa(const fido_cred_t *credential, + struct sk_enroll_response *response) +{ + size_t len; + const uint8_t *ptr = NULL; + + response->public_key = NULL; + response->public_key_len = 0; + + ptr = fido_cred_pubkey_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get FIDO2/U2F credential public key"); + return SSH_ERROR; + } + + len = fido_cred_pubkey_len(credential); + if (len != ECDSA_P256_PUBKEY_LEN) { + SSH_LOG(SSH_LOG_WARN, + "Bad FIDO2/U2F credential public key length %zu" + "(expected ecdsa public key length %d)", + len, + ECDSA_P256_PUBKEY_LEN); + return SSH_ERROR; + } + + /* + * Convert from libfido2's raw coordinate format to SEC1 octet string + * format. + * + * libfido2 returns: x_coordinate (32 bytes) + y_coordinate (32 + * bytes) + * + * SEC1 format expects: 0x04 + x_coordinate (32 bytes) + y_coordinate (32 + * bytes) + */ + response->public_key_len = 1 + ECDSA_P256_PUBKEY_LEN; + response->public_key = calloc(response->public_key_len, sizeof(uint8_t)); + if (response->public_key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for public key"); + return SSH_ERROR; + } + + /* SEC1 uncompressed point format: 0x04 prefix + raw coordinates */ + response->public_key[0] = 0x04; + memcpy(response->public_key + 1, ptr, ECDSA_P256_PUBKEY_LEN); + + return SSH_OK; +} + +/** + * Export an Ed25519 public key from a FIDO2 credential. + * + * @param credential The FIDO2 credential containing the Ed25519 public key + * @param response The enrollment response structure to fill with the public key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_public_key_ed25519(const fido_cred_t *credential, + struct sk_enroll_response *response) +{ + size_t len; + const uint8_t *ptr = NULL; + + response->public_key = NULL; + response->public_key_len = 0; + + ptr = fido_cred_pubkey_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get FIDO2 credential public key"); + return SSH_ERROR; + } + + len = fido_cred_pubkey_len(credential); + if (len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_WARN, + "Bad FIDO2 credential public key length %zu" + " (expected ed25519 public key length %d)", + len, + ED25519_KEY_LEN); + return SSH_ERROR; + } + + response->public_key_len = len; + response->public_key = calloc(response->public_key_len, sizeof(uint8_t)); + if (response->public_key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for public key"); + return SSH_ERROR; + } + + memcpy(response->public_key, ptr, len); + return SSH_OK; +} + +/** + * Export a public key from a FIDO2/U2F credential based on the specified + * algorithm. + * + * @param algorithm The key algorithm (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param credential The FIDO2/U2F credential containing the public key + * @param response The enrollment response structure to fill with the public key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_public_key(int algorithm, + const fido_cred_t *credential, + struct sk_enroll_response *response) +{ + int ret; + + switch (algorithm) { + case SSH_SK_ECDSA: + ret = export_public_key_ecdsa(credential, response); + break; + case SSH_SK_ED25519: + ret = export_public_key_ed25519(credential, response); + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + ret = SSH_ERROR; + } + + return ret; +} + +/** + * Parse DER length encoding. + * + * @param p Pointer to the current position in DER data (updated on success) + * @param end Pointer to the end of DER data + * @param length Pointer to store the parsed length + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int +parse_der_length(const uint8_t **p, const uint8_t *end, size_t *length) +{ + int len_bytes = 0; + + if (*p >= end) { + SSH_LOG(SSH_LOG_WARN, "Insufficient data for DER length"); + return SSH_ERROR; + } + + /* If the MSB is set, it indicates a long form length + * where the lower 7 bits indicate the number of + * subsequent bytes that represent the length. + * + * If the MSB is not set, it indicates a short form + * length where the length is directly represented + * in the the byte itself. + */ + if (**p & 0x80) { + /* Long form length */ + len_bytes = **p & 0x7f; + (*p)++; + + if (len_bytes > DER_MAX_LEN_BYTES) { + SSH_LOG( + SSH_LOG_WARN, + "Invalid DER length bytes: %d. Should not be greater than %d", + len_bytes, + DER_MAX_LEN_BYTES); + return SSH_ERROR; + } + + if (*p + len_bytes > end) { + SSH_LOG(SSH_LOG_WARN, "Insufficient data for length bytes"); + return SSH_ERROR; + } + + *length = 0; + while (len_bytes--) { + *length = (*length << 8) | **p; + (*p)++; + } + } else { + /* Short form length */ + *length = **p; + (*p)++; + } + + return SSH_OK; +} + +/** + * Parse a single DER-encoded INTEGER. + * + * @param p Pointer to the current position in DER data (updated on success) + * @param end Pointer to the end of DER data + * @param component_name Name of the component for error messages + * @param int_ptr Pointer to store the integer data + * @param int_len Pointer to store the integer length + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int parse_der_integer(const uint8_t **p, + const uint8_t *end, + const char *component_name, + uint8_t **int_ptr, + size_t *int_len) +{ + size_t length = 0; + const uint8_t *data_ptr = NULL; + int rc = SSH_ERROR; + + if (int_ptr == NULL || int_len == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Invalid arguments provided for %s component", + component_name); + return SSH_ERROR; + } + + *int_ptr = NULL; + *int_len = 0; + + /* Check for INTEGER tag */ + if (*p >= end || **p != DER_INTEGER_TAG) { + SSH_LOG(SSH_LOG_WARN, + "Expected INTEGER tag for %s component", + component_name); + return SSH_ERROR; + } + (*p)++; + + /* Parse length */ + rc = parse_der_length(p, end, &length); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Invalid %s component length", component_name); + return SSH_ERROR; + } + + /* Verify we have enough data */ + if (*p + length > end) { + SSH_LOG(SSH_LOG_WARN, + "%s component extends beyond signature", + component_name); + return SSH_ERROR; + } + + /* Skip leading zero if present (The leading zero is placed when the MSB of + * the actual number is 1, so that it is not confused as a negative number + * in 2's complement) */ + data_ptr = *p; + if (length > 0 && **p == 0x00) { + data_ptr++; + length--; + } + + /* Allocate memory for the integer data */ + if (length > 0) { + *int_ptr = calloc(length, sizeof(uint8_t)); + if (*int_ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for %s component", + component_name); + return SSH_ERROR; + } + memcpy(*int_ptr, data_ptr, length); + } + + *int_len = length; + *p = data_ptr + length; + + return SSH_OK; +} + +/** + * Parse DER-encoded ECDSA signature and extract r and s components. + * + * @param der_sig DER-encoded signature data + * @param der_len Length of DER data + * @param r_ptr Pointer to store r component + * @param r_len Pointer to store r length + * @param s_ptr Pointer to store s component + * @param s_len Pointer to store s length + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int parse_ecdsa_der_signature(const uint8_t *der_sig, + size_t der_len, + uint8_t **r_ptr, + size_t *r_len, + uint8_t **s_ptr, + size_t *s_len) +{ + const uint8_t *p = der_sig; + const uint8_t *end = der_sig + der_len; + size_t seq_len = 0; + int rc; + + if (r_ptr == NULL || r_len == NULL || s_ptr == NULL || s_len == NULL || + der_sig == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid arguments provided"); + return SSH_ERROR; + } + + *r_ptr = NULL; + *r_len = 0; + *s_ptr = NULL; + *s_len = 0; + + /* Parse SEQUENCE tag */ + if (p >= end || *(p++) != DER_SEQUENCE_TAG) { + SSH_LOG(SSH_LOG_WARN, "Expected SEQUENCE tag in DER signature"); + return SSH_ERROR; + } + + /* Parse sequence length */ + rc = parse_der_length(&p, end, &seq_len); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Invalid DER sequence length"); + return SSH_ERROR; + } + + /* Verify sequence length matches remaining data */ + if (p + seq_len != end) { + SSH_LOG(SSH_LOG_WARN, "DER sequence length mismatch"); + return SSH_ERROR; + } + + /* Parse first INTEGER (r component) */ + rc = parse_der_integer(&p, end, "r", r_ptr, r_len); + if (rc != SSH_OK) { + goto error; + } + + /* Parse second INTEGER (s component) */ + rc = parse_der_integer(&p, end, "s", s_ptr, s_len); + if (rc != SSH_OK) { + goto error; + } + + /* Verify we consumed all data */ + if (p != end) { + SSH_LOG(SSH_LOG_WARN, "Unexpected data after s component"); + goto error; + } + + return SSH_OK; + +error: + SAFE_FREE(*r_ptr); + *r_len = 0; + SAFE_FREE(*s_ptr); + *s_len = 0; + return SSH_ERROR; +} + +/** + * Export an ECDSA signature from a FIDO2/U2F assertion. + * + * @param assert The FIDO2/U2F assertion containing the signature + * @param response The sign response structure to fill with the signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_signature_ecdsa(fido_assert_t *assert, + struct sk_sign_response *response) +{ + size_t len = 0; + const uint8_t *ptr = NULL; + int rc; + + len = fido_assert_sig_len(assert, 0); + ptr = fido_assert_sig_ptr(assert, 0); + + if (ptr == NULL || len == 0) { + SSH_LOG(SSH_LOG_WARN, + "Invalid signature data from FIDO2/U2F assertion"); + return SSH_ERROR; + } + + /* This will allocate and populate response->sig_r/_s (+ lengths) */ + rc = parse_ecdsa_der_signature(ptr, + len, + &response->sig_r, + &response->sig_r_len, + &response->sig_s, + &response->sig_s_len); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to parse DER ECDSA signature"); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * Export an Ed25519 signature from a FIDO2 assertion. + * + * @param assert The FIDO2 assertion containing the signature + * @param response The sign response structure to fill with the signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_signature_ed25519(fido_assert_t *assert, + struct sk_sign_response *response) +{ + const uint8_t *ptr = NULL; + size_t len; + + ptr = fido_assert_sig_ptr(assert, 0); + len = fido_assert_sig_len(assert, 0); + if (len != ED25519_SIG_LEN) { + SSH_LOG(SSH_LOG_WARN, "Bad ED25519 signature length %zu", len); + return SSH_ERROR; + } + + response->sig_r_len = len; + response->sig_r = calloc(response->sig_r_len, sizeof(uint8_t)); + if (response->sig_r == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for signature"); + return SSH_ERROR; + } + + memcpy(response->sig_r, ptr, len); + response->sig_s = NULL; + response->sig_s_len = 0; + return SSH_OK; +} + +/** + * Export a signature from a FIDO2/U2F assertion based on the specified + * algorithm. + * + * @param algorithm The signature algorithm (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param assert The FIDO2/U2F assertion containing the signature + * @param response The sign response structure to fill with the signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_signature(int algorithm, + fido_assert_t *assert, + struct sk_sign_response *response) +{ + int ret; + + switch (algorithm) { + case SSH_SK_ECDSA: + ret = export_signature_ecdsa(assert, response); + break; + case SSH_SK_ED25519: + ret = export_signature_ed25519(assert, response); + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + ret = SSH_ERROR; + } + + return ret; +} + +static uint32_t ssh_sk_usbhid_api_version(void) +{ + return SK_USBHID_API_VERSION; +} + +/** + * Create and configure a new FIDO2/U2F credential for enrollment. + * + * @param device The FIDO2/U2F device to use + * @param alg The algorithm to use (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param challenge The challenge data + * @param challenge_len The length of the challenge data + * @param application The application identifier (relying party ID) + * @param flags The enrollment flags + * @param pin The PIN for the device (can be NULL) + * @param user_id The binary user ID buffer (can be NULL) + * @param user_id_len Length of user_id buffer in bytes (0 if none) + * @param credential_ptr Pointer to store the created credential + * + * @return FIDO_OK on success, FIDO_ERR_* codes on failure + */ +static int create_new_fido_credential(struct sk_device *device, + uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + const uint8_t *user_id, + size_t user_id_len, + fido_cred_t **credential_ptr) +{ + int ret = FIDO_ERR_INTERNAL; + int cose_algorithm, cred_protection; + bool cred_prot_support = false; + fido_opt_t set_resident_key = FIDO_OPT_OMIT; + fido_cred_t *credential = NULL; + + /* Set the COSE algorithm based on the requested algorithm */ + switch (alg) { + case SSH_SK_ECDSA: + cose_algorithm = COSE_ES256; + break; + case SSH_SK_ED25519: + cose_algorithm = COSE_EDDSA; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %u", alg); + return FIDO_ERR_UNSUPPORTED_ALGORITHM; + } + + credential = fido_cred_new(); + if (credential == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO2/U2F credential"); + goto error; + } + + ret = fido_cred_set_type(credential, cose_algorithm); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set credential type: %s", + fido_strerr(ret)); + goto error; + } + + ret = fido_cred_set_clientdata(credential, challenge, challenge_len); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set client data: %s", + fido_strerr(ret)); + goto error; + } + + if (flags & SSH_SK_RESIDENT_KEY) { + set_resident_key = FIDO_OPT_TRUE; + } + ret = fido_cred_set_rk(credential, set_resident_key); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set resident key option: %s", + fido_strerr(ret)); + goto error; + } + + /* TODO: Add an additional option to set display_name, icon ..etc */ + ret = + fido_cred_set_user(credential, user_id, user_id_len, NULL, NULL, NULL); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user information: %s", + fido_strerr(ret)); + goto error; + } + + ret = fido_cred_set_rp(credential, application, NULL); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set Relying Party: %s", + fido_strerr(ret)); + goto error; + } + + if (flags & (SSH_SK_USER_VERIFICATION_REQD | SSH_SK_RESIDENT_KEY)) { + cred_prot_support = fido_dev_supports_cred_prot(device->fido_device); + if (!cred_prot_support) { + SSH_LOG(SSH_LOG_WARN, + "Device does not support credential protection"); + ret = FIDO_ERR_UNSUPPORTED_EXTENSION; + goto error; + } + + if (flags & SSH_SK_USER_VERIFICATION_REQD) { + cred_protection = FIDO_CRED_PROT_UV_REQUIRED; + } else { + cred_protection = FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID; + } + + ret = fido_cred_set_prot(credential, cred_protection); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set credential protection: %s", + fido_strerr(ret)); + goto error; + } + } + + ret = fido_dev_make_cred(device->fido_device, credential, pin); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to make credential: %s", + fido_strerr(ret)); + goto error; + } + + *credential_ptr = credential; + return FIDO_OK; + +error: + fido_cred_free(&credential); + return ret; +} + +/** + * Construct an enrollment response from a FIDO2/U2F credential. + * This function extracts and copies all necessary data from the fido_cred_t + * into the response structure. + * + * @param alg The algorithm used (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param credential The FIDO2/U2F credential containing the enrollment data + * @param flags The enrollment flags + * @param response_ptr Pointer to store the constructed enrollment response + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int +fido_cred_export_sk_enroll_response(uint32_t alg, + const fido_cred_t *credential, + uint8_t flags, + struct sk_enroll_response **response_ptr) +{ + const uint8_t *ptr = NULL; + const char *fmt = NULL; + struct sk_enroll_response *response = NULL; + int rc; + + response = calloc(1, sizeof(struct sk_enroll_response)); + if (response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for enroll response"); + return SSH_ERROR; + } + + response->flags = flags; + + /* Export public key */ + rc = export_public_key(alg, credential, response); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to export public key from credential"); + goto error; + } + + /* Export the key handle */ + ptr = fido_cred_id_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get key handle"); + goto error; + } + + response->key_handle_len = fido_cred_id_len(credential); + response->key_handle = calloc(response->key_handle_len, sizeof(uint8_t)); + if (response->key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for key handle"); + goto error; + } + memcpy(response->key_handle, ptr, response->key_handle_len); + + /* Export challenge signature */ + fmt = fido_cred_fmt(credential); + if (fmt == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get attestation format"); + goto error; + } + + ptr = fido_cred_sig_ptr(credential); + if (ptr != NULL) { + response->signature_len = fido_cred_sig_len(credential); + response->signature = calloc(response->signature_len, sizeof(uint8_t)); + if (response->signature == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for signature"); + goto error; + } + memcpy(response->signature, ptr, response->signature_len); + } else if (strcmp(fmt, "none") == 0) { + /* No signature for "none" attestation format */ + response->signature = NULL; + response->signature_len = 0; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to get signature"); + goto error; + } + + /* Export attestation information if available */ + ptr = fido_cred_x5c_ptr(credential); + if (ptr != NULL) { + response->attestation_cert_len = fido_cred_x5c_len(credential); + response->attestation_cert = + calloc(response->attestation_cert_len, sizeof(uint8_t)); + if (response->attestation_cert == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for attestation cert"); + goto error; + } + memcpy(response->attestation_cert, ptr, response->attestation_cert_len); + } + + /* Export authdata */ + ptr = fido_cred_authdata_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get authdata"); + goto error; + } + + response->authdata_len = fido_cred_authdata_len(credential); + response->authdata = calloc(response->authdata_len, sizeof(uint8_t)); + if (response->authdata == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for authdata"); + goto error; + } + memcpy(response->authdata, ptr, response->authdata_len); + + *response_ptr = response; + response = NULL; + return SSH_OK; + +error: + sk_enroll_response_free(response); + return SSH_ERROR; +} + +static int ssh_sk_usbhid_enroll(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response) +{ + int rc, ret = SSH_SK_ERR_GENERAL; + const uint8_t *ptr = NULL; + const char *attestation_format = NULL; + const char *supported_options[] = {SSH_SK_OPTION_NAME_DEVICE_PATH, + SSH_SK_OPTION_NAME_USER_ID, + NULL}; + char **option_values = NULL; + const char *device_path = NULL; + uint8_t user_id[SK_MAX_USER_ID_LEN] = {0}; + size_t user_id_len = 0; + size_t j; + + struct sk_device *device = NULL; + struct sk_enroll_response *response = NULL; + + fido_cred_t *credential = NULL; + + if (enroll_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "enroll_response cannot be NULL"); + goto out; + } + *enroll_response = NULL; + + switch (alg) { + case SSH_SK_ECDSA: + case SSH_SK_ED25519: + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %u", alg); + ret = SSH_SK_ERR_UNSUPPORTED; + goto out; + } + + if (challenge == NULL || challenge_len == 0) { + SSH_LOG(SSH_LOG_WARN, "challenge cannot be NULL or empty"); + goto out; + } + + if (application == NULL || application[0] == '\0') { + SSH_LOG(SSH_LOG_WARN, "application cannot be NULL or empty"); + goto out; + } + + /* Extract device path from options if provided */ + rc = sk_options_validate_get((const struct sk_option **)options, + supported_options, + &option_values); + if (rc == SSH_OK && option_values != NULL) { + device_path = option_values[0]; /* device path is first in the array */ + + /* + * The user id is actually binary data according to the FIDO2 + * specification, but since we want to remain compatible with OpenSSH + * sk-api, so we are restricted to only obtain the user_id as a char * + * from the sk_option struct. + */ + if (option_values[1] != NULL) { + user_id_len = strlen(option_values[1]); + + if (user_id_len > SK_MAX_USER_ID_LEN) { + SSH_LOG(SSH_LOG_WARN, + "user_id length exceeds maximum of %d characters", + SK_MAX_USER_ID_LEN); + goto out; + } + + memcpy((char *)user_id, option_values[1], user_id_len); + } + } + + sk_fido_init(); + + if (device_path != NULL) { + device = sk_device_open(device_path); + } else { + device = sk_device_probe(NULL, NULL, 0, false); + } + + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open FIDO2/U2F device"); + ret = SSH_SK_ERR_DEVICE_NOT_FOUND; + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "Using FIDO2/U2F device: %s", device->path); + + /* + * Check whether a resident key with same user_id exists to avoid + * overwriting, unless operation is marked as forceful. + */ + if ((flags & SSH_SK_RESIDENT_KEY) != 0 && + (flags & SSH_SK_FORCE_OPERATION) == 0) { + + rc = sk_device_check_resident_key(device, + application, + (uint8_t *)user_id, + SK_MAX_USER_ID_LEN, + pin); + if (rc == FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, "Resident key already exists"); + ret = SSH_SK_ERR_CREDENTIAL_EXISTS; + goto out; + } else if (rc != FIDO_ERR_NO_CREDENTIALS) { + SSH_LOG(SSH_LOG_WARN, + "Failed to check for resident key: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + } + + /* Create and configure the FIDO2/U2F credential */ + ret = create_new_fido_credential(device, + alg, + challenge, + challenge_len, + application, + flags, + pin, + (uint8_t *)user_id, + SK_MAX_USER_ID_LEN, + &credential); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO2/U2F credential"); + ret = fido_err_to_ssh_sk_err(ret); + goto out; + } + + ptr = fido_cred_x5c_ptr(credential); + attestation_format = fido_cred_fmt(credential); + + if (attestation_format == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get attestation format"); + goto out; + } + + rc = strcmp(attestation_format, "none"); + + /* + * If the x509 certificate is available, we can assume attestation type to + * be Basic Attestation and verify the attestation using the + * fido_cred_verify function, which checks the attestation signature using + * the attestation key mentioned in the x509 certificate. + * + * If the x509 certificate is not available, we check the attestation format + * to see whether it's type is Self attestation or None. If it + * is Self Attestation, we use fido_cred_verify_self to verify the + * credential, which checks the attestation signature against the public key + * of the credential itself. + * + * For more details, refer: + * https://developers.yubico.com/libfido2/Manuals/fido_cred_verify.html + * https://www.w3.org/TR/webauthn-2/#sctn-attestation + */ + if (ptr != NULL) { + SSH_LOG(SSH_LOG_DEBUG, + "Verifying attestation (type: Basic Attestation)"); + rc = fido_cred_verify(credential); + } else if (rc != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Verifying attestation (type: Self attestation)"); + rc = fido_cred_verify_self(credential); + } else { + SSH_LOG(SSH_LOG_DEBUG, "No attestation data available"); + rc = FIDO_OK; + } + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to verify credential: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + /* Construct the enrollment response from the credential data */ + rc = fido_cred_export_sk_enroll_response(alg, credential, flags, &response); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to export public key from credential"); + goto out; + } + + *enroll_response = response; + response = NULL; + ret = SSH_OK; + +out: + + /* Clean up extracted values */ + if (option_values != NULL) { + for (j = 0; supported_options[j] != NULL; j++) { + SAFE_FREE(option_values[j]); + } + SAFE_FREE(option_values); + } + sk_enroll_response_free(response); + sk_device_close(device); + fido_cred_free(&credential); + + return ret; +} + +static int ssh_sk_usbhid_sign(uint32_t alg, + const uint8_t *data, + size_t data_len, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_sign_response **sign_response) +{ + int rc, ret = SSH_SK_ERR_GENERAL; + size_t i; + + const char *supported_options[] = {SSH_SK_OPTION_NAME_DEVICE_PATH, NULL}; + char **option_values = NULL; + const char *device_path = NULL; + + struct sk_device *device = NULL; + struct sk_sign_response *response = NULL; + + bool has_internal_uv = false, is_winhello = false; + fido_opt_t user_presence = FIDO_OPT_FALSE; + fido_assert_t *assert = NULL; + + if (sign_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "sign_response cannot be NULL"); + goto out; + } + *sign_response = NULL; + + switch (alg) { + case SSH_SK_ECDSA: + case SSH_SK_ED25519: + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %u", alg); + ret = SSH_SK_ERR_UNSUPPORTED; + goto out; + } + + if (data == NULL || data_len == 0) { + SSH_LOG(SSH_LOG_WARN, "data to sign cannot be NULL or empty"); + goto out; + } + + if (application == NULL || application[0] == '\0') { + SSH_LOG(SSH_LOG_WARN, "application cannot be NULL or empty"); + goto out; + } + + if (key_handle == NULL || key_handle_len == 0) { + SSH_LOG(SSH_LOG_WARN, "key_handle cannot be NULL or empty"); + goto out; + } + + /* Extract device path from options if provided */ + rc = sk_options_validate_get((const struct sk_option **)options, + supported_options, + &option_values); + if (rc == SSH_OK && option_values != NULL) { + device_path = option_values[0]; + } + + sk_fido_init(); + + /* + * We directly open the device if path is given. + * + * Otherwise, If PIN supplied or UV required, we avoid credential probing + * across multiple devices (which could trigger multiple UV prompts). + * Instead, we select by user touch first. + * + * For presence-only (UP) cases, credential-based probing is silent (see the + * comment in the sk_device_check_key_handle function about pre-flight + * checking), so we keep it to reduce touches. + */ + if (device_path != NULL) { + device = sk_device_open(device_path); + } else if (pin != NULL || (flags & SSH_SK_USER_VERIFICATION_REQD)) { + /* Touch based selection */ + device = sk_device_probe(NULL, NULL, 0, false); + } else { + /* Credential based selection */ + device = + sk_device_probe(application, key_handle, key_handle_len, false); + } + + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open FIDO2/U2F device"); + ret = SSH_SK_ERR_DEVICE_NOT_FOUND; + goto out; + } + + assert = fido_assert_new(); + if (assert == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO2/U2F assertion"); + goto out; + } + + rc = fido_assert_set_clientdata(assert, data, data_len); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to set client data: %s", fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + rc = fido_assert_set_rp(assert, application); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set relying party: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + rc = fido_assert_allow_cred(assert, key_handle, key_handle_len); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allow credential: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + user_presence = + (flags & SSH_SK_USER_PRESENCE_REQD) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; + rc = fido_assert_set_up(assert, user_presence); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user presence: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + /* + * WinHello always requests the pin, unless we explicitly specify that we + * don't expect user verification. + */ + is_winhello = fido_dev_is_winhello(device->fido_device); + if (pin == NULL && is_winhello) { + rc = fido_assert_set_uv(assert, FIDO_OPT_FALSE); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user verification: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + } + } + + /* + * pin can be NULL if device internally has user verification capabilities + * such as biometric. + */ + if (pin == NULL && (flags & SSH_SK_USER_VERIFICATION_REQD)) { + has_internal_uv = fido_dev_has_uv(device->fido_device); + if (!has_internal_uv) { + SSH_LOG(SSH_LOG_WARN, + "User Verification requirement cannot be satisfied as " + "device lacks internal user verification and PIN is also " + "not provided"); + ret = SSH_SK_ERR_PIN_REQUIRED; + goto out; + } + + rc = fido_assert_set_uv(assert, FIDO_OPT_TRUE); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user verification: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + } + + rc = fido_dev_get_assert(device->fido_device, assert, pin); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to get assertion: %s", fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + response = calloc(1, sizeof(struct sk_sign_response)); + if (response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate sign response"); + goto out; + } + + response->flags = fido_assert_flags(assert, 0); + response->counter = fido_assert_sigcount(assert, 0); + + rc = export_signature(alg, assert, response); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to export signature"); + goto out; + } + + *sign_response = response; + response = NULL; + ret = SSH_OK; + +out: + if (option_values != NULL) { + for (i = 0; supported_options[i] != NULL; i++) { + SAFE_FREE(option_values[i]); + } + SAFE_FREE(option_values); + } + + fido_assert_free(&assert); + sk_device_close(device); + sk_sign_response_free(response); + + return ret; +} + +/** + * Export a single resident credential into an allocated sk_resident_key. + */ +static int fido_cred_export_sk_resident_key(const fido_cred_t *credential, + const char *relying_party_id, + bool has_internal_uv, + struct sk_resident_key **out_key) +{ + struct sk_resident_key *resident_key = NULL; + const uint8_t *ptr = NULL; + size_t len; + int algorithm; + int rc; + + resident_key = calloc(1, sizeof(struct sk_resident_key)); + if (resident_key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for resident key"); + goto error; + } + + /* application */ + resident_key->application = strdup(relying_party_id); + if (resident_key->application == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for application"); + goto error; + } + + /* key handle */ + len = fido_cred_id_len(credential); + ptr = fido_cred_id_ptr(credential); + resident_key->key.key_handle_len = len; + resident_key->key.key_handle = calloc(len, sizeof(uint8_t)); + if (resident_key->key.key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for key handle"); + goto error; + } + memcpy(resident_key->key.key_handle, ptr, len); + + /* user id */ + len = fido_cred_user_id_len(credential); + ptr = fido_cred_user_id_ptr(credential); + resident_key->user_id_len = len; + resident_key->user_id = calloc(len, sizeof(uint8_t)); + if (resident_key->user_id == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for user ID"); + goto error; + } + memcpy(resident_key->user_id, ptr, len); + + /* algorithm */ + algorithm = fido_cred_type(credential); + switch (algorithm) { + case COSE_ES256: + resident_key->alg = SSH_SK_ECDSA; + break; + case COSE_EDDSA: + resident_key->alg = SSH_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm %d", algorithm); + goto error; + } + + rc = fido_cred_prot(credential); + if (rc == FIDO_CRED_PROT_UV_REQUIRED && !has_internal_uv) { + resident_key->flags |= SSH_SK_USER_VERIFICATION_REQD; + } + + rc = export_public_key(resident_key->alg, credential, &resident_key->key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to export public key for credential: %d", + rc); + goto error; + } + + *out_key = resident_key; + return SSH_OK; + +error: + SK_RESIDENT_KEY_FREE(resident_key); + return SSH_ERROR; +} + +/** + * Load resident keys from a specific security key device. + * + * @param device The security key device to load keys from + * @param pin The PIN for the device (required for loading resident keys) + * @param resident_keys_ptr Pointer to store the array of loaded resident keys + * @param num_keys_found_ptr Pointer to store the number of keys found + * + * @return SSH_SK_ERR_* error code (SSH_OK on success) + * + * @note This function only considers resident keys that belong to + * relying parties starting with "ssh:". + */ +static int +sk_device_load_resident_keys(struct sk_device *device, + const char *pin, + struct sk_resident_key ***resident_keys_ptr, + size_t *num_keys_found_ptr) +{ + int ret = SSH_SK_ERR_GENERAL, rc; + bool has_internal_uv = false; + size_t i, j, keys_count, num_relying_parties; + const char *relying_party_id = NULL; + + struct sk_resident_key *cur_resident_key = NULL, **temp_ptr = NULL; + fido_credman_metadata_t *metadata = NULL; + fido_credman_rp_t *relying_parties = NULL; + fido_credman_rk_t *resident_keys = NULL; + const fido_cred_t *credential = NULL; + + has_internal_uv = fido_dev_has_uv(device->fido_device); + + metadata = fido_credman_metadata_new(); + if (metadata == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create FIDO2/U2F metadata"); + goto out; + } + + rc = fido_credman_get_dev_metadata(device->fido_device, metadata, pin); + if (rc != FIDO_OK) { + if (rc == FIDO_ERR_INVALID_COMMAND) { + SSH_LOG(SSH_LOG_WARN, "Device does not support resident keys"); + ret = SSH_SK_ERR_UNSUPPORTED; + } else { + SSH_LOG(SSH_LOG_WARN, + "Failed to get device metadata: %s for device at %s", + fido_strerr(rc), + device->path); + ret = fido_err_to_ssh_sk_err(rc); + } + goto out; + } + + relying_parties = fido_credman_rp_new(); + if (relying_parties == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create relying parties list"); + goto out; + } + + rc = fido_credman_get_dev_rp(device->fido_device, relying_parties, pin); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to get relying party: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + num_relying_parties = fido_credman_rp_count(relying_parties); + + SSH_LOG(SSH_LOG_DEBUG, + "Device %s has key(s) for %zu relying party(ies).", + device->path, + num_relying_parties); + + /* + * Check all resident keys belonging to relying parties starting with "ssh:" + */ + for (i = 0; i < num_relying_parties; i++) { + relying_party_id = fido_credman_rp_id(relying_parties, i); + if (relying_party_id != NULL) { + rc = strncasecmp(relying_party_id, "ssh:", 4); + if (rc != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Skipping non-SSH relying party: %s", + relying_party_id); + continue; + } + } else { + SSH_LOG(SSH_LOG_DEBUG, + "Relying party ID is NULL, skipping RP %zu", + i); + continue; + } + + fido_credman_rk_free(&resident_keys); + resident_keys = fido_credman_rk_new(); + if (resident_keys == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create FIDO2 resident key"); + goto out; + } + + rc = fido_credman_get_dev_rk(device->fido_device, + relying_party_id, + resident_keys, + pin); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to get resident key for RP %s: %s", + relying_party_id, + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + continue; + } + + keys_count = fido_credman_rk_count(resident_keys); + if (keys_count == 0) { + SSH_LOG(SSH_LOG_INFO, + "No resident keys found for RP %s", + relying_party_id); + continue; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Found %zu resident key(s) for RP %s", + keys_count, + relying_party_id); + + for (j = 0; j < keys_count; j++) { + credential = fido_credman_rk(resident_keys, j); + if (credential == NULL) { + SSH_LOG(SSH_LOG_INFO, "No resident key in slot %zu", j); + continue; + } + + rc = fido_cred_export_sk_resident_key(credential, + relying_party_id, + has_internal_uv, + &cur_resident_key); + if (rc != SSH_OK) { + goto out; + } + + temp_ptr = realloc(*resident_keys_ptr, + sizeof(struct sk_resident_key *) * + (*num_keys_found_ptr + 1)); + if (temp_ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for resident keys list"); + goto out; + } + + *resident_keys_ptr = temp_ptr; + (*resident_keys_ptr)[*num_keys_found_ptr] = cur_resident_key; + (*num_keys_found_ptr)++; + cur_resident_key = NULL; + } + } + + ret = SSH_OK; + +out: + SK_RESIDENT_KEY_FREE(cur_resident_key); + fido_credman_rp_free(&relying_parties); + fido_credman_rk_free(&resident_keys); + fido_credman_metadata_free(&metadata); + return ret; +} + +static int +ssh_sk_usbhid_load_resident_keys(const char *pin, + struct sk_option **options, + struct sk_resident_key ***resident_keys_ptr, + size_t *num_keys_found_ptr) +{ + int rc, ret = SSH_SK_ERR_GENERAL; + size_t i, j, keys_count = 0; + const char *supported_options[] = {SSH_SK_OPTION_NAME_DEVICE_PATH, NULL}; + char **option_values = NULL; + const char *device_path = NULL; + + struct sk_resident_key **resident_keys = NULL; + struct sk_device *device = NULL; + + if (resident_keys_ptr == NULL || num_keys_found_ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, + "resident_keys_ptr and num_keys_found_ptr cannot be NULL"); + return SSH_SK_ERR_GENERAL; + } + + /* + * To load device metadata and resident keys, a valid pin must be provided + * regardless of internal uv support. + */ + if (pin == NULL) { + SSH_LOG(SSH_LOG_WARN, "PIN cannot be NULL for loading resident keys"); + return SSH_SK_ERR_PIN_REQUIRED; + } + + *resident_keys_ptr = NULL; + *num_keys_found_ptr = 0; + + sk_fido_init(); + + rc = sk_options_validate_get((const struct sk_option **)options, + supported_options, + &option_values); + if (rc == SSH_OK && option_values != NULL) { + device_path = option_values[0]; + } + + if (device_path != NULL) { + device = sk_device_open(device_path); + } else { + device = sk_device_probe(NULL, NULL, 0, 1); + } + + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open FIDO2 device"); + ret = SSH_SK_ERR_DEVICE_NOT_FOUND; + goto out; + } + + rc = sk_device_load_resident_keys(device, pin, &resident_keys, &keys_count); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to load resident keys: %d", rc); + ret = rc; + goto out; + } + + *resident_keys_ptr = resident_keys; + *num_keys_found_ptr = keys_count; + resident_keys = NULL; + keys_count = 0; + ret = SSH_OK; + +out: + if (option_values != NULL) { + for (j = 0; supported_options[j] != NULL; j++) { + SAFE_FREE(option_values[j]); + } + SAFE_FREE(option_values); + } + + sk_device_close(device); + + for (i = 0; i < keys_count; i++) { + SK_RESIDENT_KEY_FREE(resident_keys[i]); + } + SAFE_FREE(resident_keys); + return ret; +} + +static struct ssh_sk_callbacks_struct sk_usbhid_callbacks = { + .api_version = ssh_sk_usbhid_api_version, + .enroll = ssh_sk_usbhid_enroll, + .sign = ssh_sk_usbhid_sign, + .load_resident_keys = ssh_sk_usbhid_load_resident_keys, +}; + +const struct ssh_sk_callbacks_struct *ssh_sk_get_usbhid_callbacks(void) +{ + ssh_callbacks_init(&sk_usbhid_callbacks); + return &sk_usbhid_callbacks; +} diff --git a/src/libs/libssh-0.12.2/src/sntrup761.c b/src/libs/libssh-0.12.2/src/sntrup761.c new file mode 100644 index 000000000000..a67de4372231 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sntrup761.c @@ -0,0 +1,524 @@ +/* + * sntrup761.c - SNTRUP761x25519 ECDH functions for key exchange + * sntrup761x25519-sha512@openssh.com - based on curve25519.c. + * + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * Copyright (c) 2023 Simon Josefsson + * Copyright (c) 2025 Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/sntrup761.h" +#ifdef HAVE_SNTRUP761 + +#include "libssh/bignum.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +#ifndef HAVE_LIBGCRYPT +static void crypto_random(void *ctx, size_t length, uint8_t *dst) +{ + int *err = ctx; + *err = ssh_get_random(dst, length, 1); +} +#endif /* HAVE_LIBGCRYPT */ + +static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply); + +static ssh_packet_callback dh_client_callbacks[] = { + ssh_packet_client_sntrup761x25519_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_client_callbacks = + { + .start = SSH2_MSG_KEX_ECDH_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL, +}; + +static int ssh_sntrup761x25519_init(ssh_session session) +{ + int rc; + + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + return rc; + } + + if (!session->server) { +#ifdef HAVE_LIBGCRYPT + gcry_error_t err; + + err = gcry_kem_keypair(GCRY_KEM_SNTRUP761, + session->next_crypto->sntrup761_client_pubkey, + SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_privkey, + SNTRUP761_SECRETKEY_SIZE); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate sntrup761 key: %s", + gpg_strerror(err)); + return SSH_ERROR; + } +#else + sntrup761_keypair(session->next_crypto->sntrup761_client_pubkey, + session->next_crypto->sntrup761_privkey, + &rc, + crypto_random); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate sntrup761 key: PRNG failure"); + return SSH_ERROR; + } +#endif /* HAVE_LIBGCRYPT */ + } + + return SSH_OK; +} + +/** @internal + * @brief Starts sntrup761x25519-sha512@openssh.com key exchange + */ +int ssh_client_sntrup761x25519_init(ssh_session session) +{ + int rc; + + rc = ssh_sntrup761x25519_init(session); + if (rc != SSH_OK) { + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdPP", + SSH2_MSG_KEX_ECDH_INIT, + CURVE25519_PUBKEY_SIZE + SNTRUP761_PUBLICKEY_SIZE, + (size_t)SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_client_pubkey, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + rc = ssh_packet_send(session); + + return rc; +} + +void ssh_client_sntrup761x25519_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_client_callbacks); +} + +static int ssh_sntrup761x25519_build_k(ssh_session session) +{ + unsigned char ssk[SNTRUP761_SIZE + CURVE25519_PUBKEY_SIZE]; + unsigned char *k = ssk + SNTRUP761_SIZE; + unsigned char hss[SHA512_DIGEST_LEN]; + int rc; + + rc = ssh_curve25519_create_k(session, k); + if (rc != SSH_OK) { + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 shared secret", k, CURVE25519_PUBKEY_SIZE); +#endif + +#ifdef HAVE_LIBGCRYPT + if (session->server) { + gcry_error_t err; + err = gcry_kem_encap(GCRY_KEM_SNTRUP761, + session->next_crypto->sntrup761_client_pubkey, + SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE, + ssk, + SNTRUP761_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to encapsulate sntrup761 shared secret: %s", + gpg_strerror(err)); + rc = SSH_ERROR; + goto cleanup; + } + } else { + gcry_error_t err; + err = gcry_kem_decap(GCRY_KEM_SNTRUP761, + session->next_crypto->sntrup761_privkey, + SNTRUP761_SECRETKEY_SIZE, + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE, + ssk, + SNTRUP761_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to decapsulate sntrup761 shared secret: %s", + gpg_strerror(err)); + rc = SSH_ERROR; + goto cleanup; + } + } +#else + if (session->server) { + sntrup761_enc(session->next_crypto->sntrup761_ciphertext, + ssk, + session->next_crypto->sntrup761_client_pubkey, + &rc, + crypto_random); + if (rc != 1) { + rc = SSH_ERROR; + goto cleanup; + } + } else { + sntrup761_dec(ssk, + session->next_crypto->sntrup761_ciphertext, + session->next_crypto->sntrup761_privkey); + } +#endif /* HAVE_LIBGCRYPT */ + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("server cipher text", + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE); + ssh_log_hexdump("kem key", ssk, SNTRUP761_SIZE); +#endif + + sha512(ssk, sizeof ssk, hss); + + bignum_bin2bn(hss, sizeof hss, &session->next_crypto->shared_secret); + if (session->next_crypto->shared_secret == NULL) { + rc = SSH_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); +#endif + + return 0; +cleanup: + ssh_burn(ssk, sizeof ssk); + ssh_burn(hss, sizeof hss); + + return rc; +} + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply) +{ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_client_sntrup761x25519_remove_callbacks(session); + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "No public key in packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Failed to import next public key"); + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No sntrup761x25519 Q_S in packet"); + goto error; + } + if (ssh_string_len(q_s_string) != (SNTRUP761_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE)) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server sntrup761x25519 ciphertext+key: %d", + (int)ssh_string_len(q_s_string)); + SSH_STRING_FREE(q_s_string); + goto error; + } + memcpy(session->next_crypto->sntrup761_ciphertext, + ssh_string_data(q_s_string), + SNTRUP761_CIPHERTEXT_SIZE); + memcpy(session->next_crypto->curve25519_server_pubkey, + (char *)ssh_string_data(q_s_string) + SNTRUP761_CIPHERTEXT_SIZE, + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_s_string); + + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature = NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ssh_sntrup761x25519_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; + +error: + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_sntrup761x25519_init, +}; + +static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_server_callbacks = + { + .start = SSH2_MSG_KEX_ECDH_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL, +}; + +/** @internal + * @brief sets up the sntrup761x25519-sha512@openssh.com kex callbacks + */ +void ssh_server_sntrup761x25519_init(ssh_session session) +{ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_server_callbacks); +} + +/** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init) +{ + /* ECDH/SNTRUP761 keys */ + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; + ssh_string server_pubkey_blob = NULL; + + /* SSH host keys (rsa, ed25519 and ecdsa) */ + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_server_callbacks); + + /* Extract the client pubkey from the init packet */ + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No sntrup761x25519 Q_C in packet"); + goto error; + } + if (ssh_string_len(q_c_string) != (SNTRUP761_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE)) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server sntrup761x25519 public key: %zu", + ssh_string_len(q_c_string)); + goto error; + } + + memcpy(session->next_crypto->sntrup761_client_pubkey, + ssh_string_data(q_c_string), + SNTRUP761_PUBLICKEY_SIZE); + memcpy(session->next_crypto->curve25519_client_pubkey, + ((char *)ssh_string_data(q_c_string)) + SNTRUP761_PUBLICKEY_SIZE, + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_c_string); + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("client public key sntrup761", + session->next_crypto->sntrup761_client_pubkey, + SNTRUP761_PUBLICKEY_SIZE); + ssh_log_hexdump("client public key c25519", + session->next_crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + + /* Build server's key pair */ + rc = ssh_sntrup761x25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate sntrup761 keys"); + goto error; + } + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_REPLY); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* build k and session_id */ + rc = ssh_sntrup761x25519_build_k(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + goto error; + } + + /* add host's public key */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob); + SSH_STRING_FREE(server_pubkey_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* add ecdh public key */ + rc = ssh_buffer_add_u32(session->out_buffer, + ntohl(SNTRUP761_CIPHERTEXT_SIZE + + CURVE25519_PUBKEY_SIZE)); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_add_data(session->out_buffer, + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_add_data(session->out_buffer, + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("server public key c25519", + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + + /* add signature blob */ + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH_REPLY:", + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); +#endif + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_ECDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); + if (rc < 0) { + goto error; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + + return SSH_PACKET_USED; +error: + SSH_STRING_FREE(q_c_string); + SSH_STRING_FREE(q_s_string); + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_SNTRUP761 */ diff --git a/src/libs/libssh-0.12.2/src/socket.c b/src/libs/libssh-0.12.2/src/socket.c new file mode 100644 index 000000000000..bebd98c50190 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/socket.c @@ -0,0 +1,1764 @@ +/* + * socket.c - socket functions for the library + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#ifdef _WIN32 +#include +#include +#ifndef UNIX_PATH_MAX + /* Inlining the key portions of afunix.h in Windows 10 SDK; + * that header isn't available in the mingw environment. */ +#define UNIX_PATH_MAX 108 +struct sockaddr_un { + ADDRESS_FAMILY sun_family; + char sun_path[UNIX_PATH_MAX]; +}; +#endif +#else /* _WIN32 */ +#include +#include +#include +#include +#include +#include +#ifdef HAVE_PTHREAD +#include +#endif +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" +#include "libssh/socket.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" +#include "libssh/session.h" + +/** + * @defgroup libssh_socket The SSH socket functions. + * @ingroup libssh + * + * Functions for handling sockets. + * + * @{ + */ + +enum ssh_socket_states_e { + SSH_SOCKET_NONE, + SSH_SOCKET_CONNECTING, + SSH_SOCKET_CONNECTED, + SSH_SOCKET_EOF, + SSH_SOCKET_ERROR, + SSH_SOCKET_CLOSED +}; + +struct ssh_socket_struct { + socket_t fd; + int fd_is_socket; + int last_errno; + int read_wontblock; /* reading now on socket will + not block */ + int write_wontblock; + int data_except; + enum ssh_socket_states_e state; + ssh_buffer out_buffer; + ssh_buffer in_buffer; + ssh_session session; + ssh_socket_callbacks callbacks; + ssh_poll_handle poll_handle; +#ifndef _WIN32 + pid_t proxy_pid; +#endif +}; + +#ifdef HAVE_PTHREAD +struct jump_thread_data_struct { + ssh_session session; + socket_t fd; + char *next_hostname; + uint16_t next_port; + struct ssh_jump_info_struct *next_jump; + struct ssh_jump_callbacks_struct *next_cb; +}; + +int proxy_disconnect = 0; +#endif /* HAVE_PTHREAD */ + +static int sockets_initialized = 0; + +static ssize_t ssh_socket_unbuffered_read(ssh_socket s, + void *buffer, + uint32_t len); +static ssize_t ssh_socket_unbuffered_write(ssh_socket s, + const void *buffer, + uint32_t len); + +/** + * @internal + * + * @brief Initialize socket support for libssh. + * + * Initializes the socket subsystem, calling WSAStartup() on Windows and + * ssh_poll_init() on all platforms. Can be called multiple times. + * + * @return 0 on success; -1 on Windows socket initialization failure. + */ +int ssh_socket_init(void) +{ + if (sockets_initialized == 0) { +#ifdef _WIN32 + struct WSAData wsaData; + + /* Initiates use of the Winsock DLL by a process. */ + if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) { + return -1; + } +#endif + ssh_poll_init(); + + sockets_initialized = 1; + } + + return 0; +} + +/** + * @internal + * + * @brief Cleanup socket support for libssh. + * + * Cleans up the socket subsystem, calling ssh_poll_cleanup() on all platforms + * and WSACleanup() on Windows. Can be called multiple times. + */ +void ssh_socket_cleanup(void) +{ + if (sockets_initialized == 1) { + ssh_poll_cleanup(); +#ifdef _WIN32 + WSACleanup(); +#endif + sockets_initialized = 0; + } +} + +/** + * @internal + * + * @brief Allocate and initialize a new SSH socket structure. + * + * Creates a new ssh_socket structure associated with the given session, + * initializes input/output buffers and sets default socket state. + * + * @param[in] session The SSH session to associate with the socket. + * + * @return A new ssh_socket on success; NULL on memory allocation failure. + */ +ssh_socket ssh_socket_new(ssh_session session) +{ + ssh_socket s; + + s = calloc(1, sizeof(struct ssh_socket_struct)); + if (s == NULL) { + ssh_set_error_oom(session); + return NULL; + } + s->fd = SSH_INVALID_SOCKET; + s->last_errno = -1; + s->fd_is_socket = 1; + s->session = session; + s->in_buffer = ssh_buffer_new(); + if (s->in_buffer == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(s); + return NULL; + } + s->out_buffer=ssh_buffer_new(); + if (s->out_buffer == NULL) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(s->in_buffer); + SAFE_FREE(s); + return NULL; + } + s->read_wontblock = 0; + s->write_wontblock = 0; + s->data_except = 0; + s->poll_handle = NULL; + s->state=SSH_SOCKET_NONE; + return s; +} + +/** + * @internal + * + * @brief Reset the state of a socket, so it looks brand new. + * + * Clears the file descriptor, reinitializes input/output buffers, frees + * the poll handle if present, and resets all socket state flags. + * + * @param[in] s The SSH socket to reset. + */ +void ssh_socket_reset(ssh_socket s) +{ + s->fd = SSH_INVALID_SOCKET; + s->last_errno = -1; + s->fd_is_socket = 1; + ssh_buffer_reinit(s->in_buffer); + ssh_buffer_reinit(s->out_buffer); + s->read_wontblock = 0; + s->write_wontblock = 0; + s->data_except = 0; + if (s->poll_handle != NULL) { + ssh_poll_free(s->poll_handle); + s->poll_handle = NULL; + } + s->state=SSH_SOCKET_NONE; +#ifndef _WIN32 + s->proxy_pid = 0; +#endif +} + +/** + * @internal + * @brief the socket callbacks, i.e. callbacks to be called + * upon a socket event. + * @param s socket to set callbacks on. + * @param callbacks a ssh_socket_callback object reference. + */ +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks) +{ + s->callbacks = callbacks; +} + +/** + * @internal + * + * @brief Mark an SSH socket as connected. + * + * Sets the socket state to connected and configures the poll handle + * to wait for `POLLIN` and `POLLOUT` events (needed for non-blocking connect). + * + * @param[in] s The SSH socket. + * @param[in] p The poll handle to configure, or NULL. + */ +void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p) +{ + s->state = SSH_SOCKET_CONNECTED; + /* `POLLOUT` is the event to wait for in a non-blocking connect */ + if (p != NULL) { + ssh_poll_set_events(p, POLLIN | POLLOUT); + } +} + +/** + * @internal + * + * @brief SSH poll callback. This callback will be used when an + * event caught on the socket. + * + * @param p Poll object this callback belongs to. + * @param fd The raw socket. + * @param revents The current poll events on the socket. + * @param v_s Userdata to be passed to the callback function, + * in this case the socket object. + * + * @return 0 on success, < 0 when the poll object has been removed + * from its poll context. + */ +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, + socket_t fd, + int revents, + void *v_s) +{ + ssh_socket s = (ssh_socket)v_s; + void *buffer = NULL; + ssize_t nread = 0; + int rc; + int err = 0; + socklen_t errlen = sizeof(err); + + /* Do not do anything if this socket was already closed */ + if (!ssh_socket_is_open(s)) { + return -1; + } + SSH_LOG(SSH_LOG_TRACE, + "Poll callback on socket %d (%s%s%s), out buffer %" PRIu32, fd, + (revents & POLLIN) ? "POLLIN ":"", + (revents & POLLOUT) ? "POLLOUT ":"", + (revents & POLLERR) ? "POLLERR":"", + ssh_buffer_get_len(s->out_buffer)); + if ((revents & POLLERR) || (revents & POLLHUP)) { + /* Check if we are in a connecting state */ + if (s->state == SSH_SOCKET_CONNECTING) { + s->state = SSH_SOCKET_ERROR; + rc = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); + if (rc < 0) { + err = errno; + } + ssh_socket_close(s); + /* Overwrite ssh_socket_close() error with the real socket error */ + s->last_errno = err; + errno = err; + + if (s->callbacks != NULL && s->callbacks->connected != NULL) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_ERROR, + err, + s->callbacks->userdata); + } + + return -1; + } + /* Then we are in a more standard kind of error */ + /* force a read to get an explanation */ + revents |= POLLIN; + } + if ((revents & POLLIN) && s->state == SSH_SOCKET_CONNECTED) { + s->read_wontblock = 1; + buffer = ssh_buffer_allocate(s->in_buffer, MAX_BUF_SIZE); + if (buffer) { + nread = ssh_socket_unbuffered_read(s, buffer, MAX_BUF_SIZE); + } + if (nread < 0) { + ssh_buffer_pass_bytes_end(s->in_buffer, MAX_BUF_SIZE); + if (p != NULL) { + ssh_poll_remove_events(p, POLLIN); + } + + if (s->callbacks != NULL && s->callbacks->exception != NULL) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno, + s->callbacks->userdata); + } + return -2; + } + + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(s->in_buffer, + (uint32_t)(MAX_BUF_SIZE - nread)); + + if (nread == 0) { + if (p != NULL) { + ssh_poll_remove_events(p, POLLIN); + } + if (s->callbacks != NULL && s->callbacks->exception != NULL) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_EOF, + 0, + s->callbacks->userdata); + } + return -2; + } + + if (s->session->socket_counter != NULL) { + s->session->socket_counter->in_bytes += nread; + } + + /* Call the callback */ + if (s->callbacks != NULL && s->callbacks->data != NULL) { + size_t processed; + do { + processed = s->callbacks->data(ssh_buffer_get(s->in_buffer), + ssh_buffer_get_len(s->in_buffer), + s->callbacks->userdata); + ssh_buffer_pass_bytes(s->in_buffer, (uint32_t)processed); + } while ((processed > 0) && (s->state == SSH_SOCKET_CONNECTED)); + + /* p may have been freed, so don't use it + * anymore in this function */ + p = NULL; + } + } +#ifdef _WIN32 + if (revents & POLLOUT || revents & POLLWRNORM) { +#else + if (revents & POLLOUT) { +#endif + uint32_t len; + + /* First, POLLOUT is a sign we may be connected */ + if (s->state == SSH_SOCKET_CONNECTING) { + SSH_LOG(SSH_LOG_PACKET, "Received POLLOUT in connecting state"); + ssh_socket_set_connected(s, p); + + rc = ssh_socket_set_blocking(ssh_socket_get_fd(s)); + if (rc < 0) { + return -1; + } + + if (s->callbacks != NULL && s->callbacks->connected != NULL) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, + 0, + s->callbacks->userdata); + } + + return 0; + } + + /* So, we can write data */ + s->write_wontblock = 1; + if (p != NULL) { + ssh_poll_remove_events(p, POLLOUT); + } + + /* If buffered data is pending, write it */ + len = ssh_buffer_get_len(s->out_buffer); + if (len > 0) { + ssh_socket_nonblocking_flush(s); + } else if (s->callbacks != NULL && s->callbacks->controlflow != NULL) { + /* Otherwise advertise the upper level that write can be done */ + SSH_LOG(SSH_LOG_TRACE, "sending control flow event"); + s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK, + s->callbacks->userdata); + } + /* TODO: Find a way to put back POLLOUT when buffering occurs */ + } + + /* Return -1 if the poll handler disappeared */ + if (s->poll_handle == NULL) { + return -1; + } + + return 0; +} + +/** @internal + * @brief returns the poll handle corresponding to the socket, + * creates it if it does not exist. + * @returns allocated and initialized ssh_poll_handle object + */ +ssh_poll_handle ssh_socket_get_poll_handle(ssh_socket s) +{ + if (s->poll_handle) { + return s->poll_handle; + } + s->poll_handle = ssh_poll_new(s->fd, 0, ssh_socket_pollcallback, s); + return s->poll_handle; +} + +/** + * @internal + * + * @brief Deletes a socket object. + * + * Closes the socket connection, frees input/output buffers and + * releases the socket structure memory. + * + * @param[in] s The SSH socket to free, or NULL. + */ +void ssh_socket_free(ssh_socket s) +{ + if (s == NULL) { + return; + } + ssh_socket_close(s); + SSH_BUFFER_FREE(s->in_buffer); + SSH_BUFFER_FREE(s->out_buffer); + SAFE_FREE(s); +} + +/** + * @internal + * + * @brief Connect an SSH socket to a Unix domain socket. + * + * Creates a Unix domain socket connection to the given @p path and associates + * it with the SSH socket. + * + * @param[in] s The SSH socket to connect. + * @param[in] path Path to the Unix domain socket. + * + * @return `SSH_OK` on success; `SSH_ERROR` on socket creation, connect, or fd + * setup failure. + */ +int ssh_socket_unix(ssh_socket s, const char *path) +{ + struct sockaddr_un sunaddr; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socket_t fd; + sunaddr.sun_family = AF_UNIX; + snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == SSH_INVALID_SOCKET) { + ssh_set_error(s->session, SSH_FATAL, + "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + +#ifndef _WIN32 + if (fcntl(fd, F_SETFD, 1) == -1) { + ssh_set_error(s->session, SSH_FATAL, + "Error from fcntl(fd, F_SETFD, 1): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); + return SSH_ERROR; + } +#endif + + if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { + ssh_set_error(s->session, SSH_FATAL, "Error from connect(%s): %s", + path, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); + return SSH_ERROR; + } + return ssh_socket_set_fd(s, fd); +} + +/** + * @internal + * + * @brief Close an SSH socket. + * + * Closes the socket file descriptor if open, saves the last error code, + * frees the poll handle if unlocked, and marks the socket state as closed. + * On Unix, attempts to terminate and wait for any running proxy command + * process. + * + * @param[in] s The SSH socket to close. + */ +void ssh_socket_close(ssh_socket s) +{ + if (ssh_socket_is_open(s)) { +#ifdef _WIN32 + CLOSE_SOCKET(s->fd); + s->last_errno = WSAGetLastError(); +#else + CLOSE_SOCKET(s->fd); + s->last_errno = errno; +#endif + } + + if (s->poll_handle != NULL && !ssh_poll_is_locked(s->poll_handle)) { + ssh_poll_free(s->poll_handle); + s->poll_handle = NULL; + } + + s->state = SSH_SOCKET_CLOSED; + +#ifndef _WIN32 + /* If the proxy command still runs try to kill it */ + if (s->proxy_pid != 0) { + int status; + pid_t pid = s->proxy_pid; + + s->proxy_pid = 0; + kill(pid, SIGTERM); + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_TRACE, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return; + } + } + if (!WIFEXITED(status)) { + SSH_LOG(SSH_LOG_TRACE, "Proxy command exited abnormally"); + return; + } + SSH_LOG(SSH_LOG_TRACE, "Proxy command returned %d", WEXITSTATUS(status)); + } +#endif +} + +/** + * @internal + * @brief sets the file descriptor of the socket. + * @param[out] s ssh_socket to update + * @param[in] fd file descriptor to set + * @warning this function updates both the input and output + * file descriptors + */ +int ssh_socket_set_fd(ssh_socket s, socket_t fd) +{ + ssh_poll_handle h = NULL; + + s->fd = fd; + + if (s->poll_handle) { + ssh_poll_set_fd(s->poll_handle,fd); + } else { + s->state = SSH_SOCKET_CONNECTING; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return SSH_ERROR; + } + + /* POLLOUT is the event to wait for in a nonblocking connect */ + ssh_poll_set_events(h, POLLOUT); +#ifdef _WIN32 + ssh_poll_add_events(h, POLLWRNORM); +#endif + } + return SSH_OK; +} + +/** + * @internal + * + * @brief Returns the input file descriptor of a socket. + * + * @param[in] s The SSH socket. + * + * @return The socket file descriptor (socket_t). + */ +socket_t ssh_socket_get_fd(ssh_socket s) +{ + return s->fd; +} + +/** + * @internal + * + * @brief Check if an SSH socket is open. + * + * @param[in] s The SSH socket. + * + * @return Non-zero if socket is open, 0 if closed or invalid. + */ +int ssh_socket_is_open(ssh_socket s) +{ + return s->fd != SSH_INVALID_SOCKET; +} + +/** + * @internal + * + * @brief Perform an unbuffered read from an SSH socket. + * + * Reads @p len bytes from the socket file descriptor directly into @p buffer, + * using `recv()` if the descriptor is a socket, or `read()` otherwise. + * Updates internal error and state flags based on the result. + * + * @param[in] s The SSH socket. + * @param[out] buffer Buffer to read data into. + * @param[in] len Maximum number of bytes to read. + * + * @return Number of bytes read on success, or -1 on error. + */ +static ssize_t ssh_socket_unbuffered_read(ssh_socket s, + void *buffer, + uint32_t len) +{ + ssize_t rc = -1; + + if (s->data_except) { + return -1; + } + if (s->fd_is_socket) { + rc = recv(s->fd, buffer, len, 0); + } else { + rc = read(s->fd, buffer, len); + } +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->read_wontblock = 0; + + if (rc < 0) { + s->data_except = 1; + } else { + SSH_LOG(SSH_LOG_TRACE, "read %zd", rc); + } + + return rc; +} + +/** + * @internal + * + * @brief Perform an unbuffered write to an SSH socket. + * + * Writes @p len bytes from @p buffer to the socket file descriptor, + * using `send()` if the descriptor is a socket or `write()` otherwise. + * Updates internal error and state flags, and re-enables POLLOUT + * polling if a poll handle exists. + * + * @param[in] s The SSH socket. + * @param[in] buffer Buffer containing data to write. + * @param[in] len Number of bytes to write. + * + * @return Number of bytes written on success, or -1 on error. + */ +static ssize_t ssh_socket_unbuffered_write(ssh_socket s, + const void *buffer, + uint32_t len) +{ + ssize_t w = -1; + int flags = 0; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + if (s->data_except) { + return -1; + } + + if (s->fd_is_socket) { + w = send(s->fd, buffer, len, flags); + } else { + w = write(s->fd, buffer, len); + } +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->write_wontblock = 0; + /* Reactive the POLLOUT detector in the poll multiplexer system */ + if (s->poll_handle) { + SSH_LOG(SSH_LOG_PACKET, "Enabling POLLOUT for socket"); + ssh_poll_add_events(s->poll_handle, POLLOUT); + } + if (w < 0) { + s->data_except = 1; + } + + SSH_LOG(SSH_LOG_TRACE, "wrote %zd", w); + return w; +} + +/** + * @internal + * + * @brief Check if SSH socket file descriptor is set in an fd_set. + * + * Tests if the socket's file descriptor is present in the + * given @p set (fd_set) . Returns 0 if the socket has no valid file descriptor. + * + * @param[in] s The SSH socket. + * @param[in] set The fd_set to test against. + * + * @return Non-zero if the socket fd is set in the fd_set, 0 otherwise. + */ +int ssh_socket_fd_isset(ssh_socket s, fd_set *set) +{ + if(s->fd == SSH_INVALID_SOCKET) { + return 0; + } + return FD_ISSET(s->fd,set); +} + +/** + * @internal + * + * @brief Add SSH socket file descriptor to an fd_set. + * + * Adds the socket's file descriptor to the given @p set (fd_set) + * and updates @p max_fd if this socket has the highest file descriptor number. + * @param[in] s The SSH socket. + * @param[in,out] set The fd_set to add the socket to. + * @param[in,out] max_fd the maximum fd value. + */ +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) +{ + if (s->fd == SSH_INVALID_SOCKET) { + return; + } + + FD_SET(s->fd,set); + + if (s->fd >= 0 && + s->fd >= *max_fd && + s->fd != SSH_INVALID_SOCKET) { + *max_fd = s->fd + 1; + } +} + +/** + * @internal + * + * @brief Write data to an SSH socket output buffer. + * + * Adds the data to the socket's output @p buffer and calls a nonblocking + * flush attempt to send buffered data. + * + * @param[in] s The SSH socket. + * @param[in] buffer Data to write. + * @param[in] len Number of bytes to write. + * + * @return `SSH_OK` on success; `SSH_ERROR` on buffer allocation failure. + * + * @warning It has no effect on socket before a flush. + */ +int ssh_socket_write(ssh_socket s, const void *buffer, uint32_t len) +{ + if (len > 0) { + if (ssh_buffer_add_data(s->out_buffer, buffer, len) < 0) { + ssh_set_error_oom(s->session); + return SSH_ERROR; + } + ssh_socket_nonblocking_flush(s); + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief Starts a nonblocking flush of the output buffer. + * + * Sends all buffered data from the socket's output buffer. + * If the socket is not open, marks the session as dead and calls an + * exception callback or sets a fatal error. If the socket cannot currently + * accept data, polls for writable events and returns `SSH_AGAIN`. + * On write errors, closes the socket and signals the error. Updates + * byte counters on successful writes. + * + * @param[in] s The SSH socket. + * + * @return `SSH_OK` if all data was sent; `SSH_AGAIN` if the operation should + * be retried later; `SSH_ERROR` on fatal socket error. + */ +int ssh_socket_nonblocking_flush(ssh_socket s) +{ + ssh_session session = s->session; + uint32_t len; + + if (!ssh_socket_is_open(s)) { + session->alive = 0; + if (s->callbacks && s->callbacks->exception) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno, + s->callbacks->userdata); + } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_FATAL, + "Writing packet: error on socket (or connection " + "closed): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } + + return SSH_ERROR; + } + + len = ssh_buffer_get_len(s->out_buffer); + if (!s->write_wontblock && s->poll_handle && len > 0) { + /* force the poll system to catch pollout events */ + ssh_poll_add_events(s->poll_handle, POLLOUT); + + return SSH_AGAIN; + } + + if (s->write_wontblock && len > 0) { + ssize_t bwritten; + + bwritten = ssh_socket_unbuffered_write(s, + ssh_buffer_get(s->out_buffer), + len); + if (bwritten < 0) { + session->alive = 0; + ssh_socket_close(s); + + if (s->callbacks && s->callbacks->exception) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno, + s->callbacks->userdata); + } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_FATAL, + "Writing packet: error on socket (or connection " + "closed): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } + + return SSH_ERROR; + } + + ssh_buffer_pass_bytes(s->out_buffer, (uint32_t)bwritten); + if (s->session->socket_counter != NULL) { + s->session->socket_counter->out_bytes += bwritten; + } + } + + /* Is there some data pending? */ + len = ssh_buffer_get_len(s->out_buffer); + if (s->poll_handle && len > 0) { + SSH_LOG(SSH_LOG_TRACE, + "did not send all the data, queuing pollout event"); + /* force the poll system to catch pollout events */ + ssh_poll_add_events(s->poll_handle, POLLOUT); + + return SSH_AGAIN; + } + + /* all data written */ + return SSH_OK; +} + +/** + * @internal + * + * @brief Set the SSH socket write_wontblock flag. + * + * Marks the socket as ready for nonblocking writes (`write_wontblock = 1`). + * Used by the poll system when POLLOUT becomes available. + * + * @param[in] s The SSH socket. + */ +void ssh_socket_set_write_wontblock(ssh_socket s) +{ + s->write_wontblock = 1; +} + +/** + * @internal + * + * @brief Set the SSH socket read_wontblock flag. + * + * Marks the socket as ready for nonblocking reads (`read_wontblock = 1`). + * Used by the poll system when POLLIN becomes available. + * + * @param[in] s The SSH socket. + */ +void ssh_socket_set_read_wontblock(ssh_socket s) +{ + s->read_wontblock = 1; +} + +/** + * @internal + * + * @brief Set the SSH socket exception flag. + * + * Marks the socket as having an exception condition (`data_except = 1`). + * + * @param[in] s The SSH socket. + */ +void ssh_socket_set_except(ssh_socket s) +{ + s->data_except = 1; +} + +/** + * @internal + * + * @brief Check if SSH socket data is available for reading. + * + * Returns true if the socket is ready for nonblocking reads + * (`read_wontblock` flag is set). + * + * @param[in] s The SSH socket. + * + * @return 1 if data is available, 0 otherwise. + */ +int ssh_socket_data_available(ssh_socket s) +{ + return s->read_wontblock; +} + +/** + * @internal + * + * @brief Check if SSH socket is writable. + * + * Returns true if the socket is ready for nonblocking writes + * (`write_wontblock` flag is set). + * + * @param[in] s The SSH socket. + * + * @return 1 if socket is writable, 0 otherwise. + */ +int ssh_socket_data_writable(ssh_socket s) +{ + return s->write_wontblock; +} + +/** @internal + * @brief returns the number of outgoing bytes currently buffered + * @param s the socket + * @returns numbers of bytes buffered, or 0 if the socket isn't connected + */ +int ssh_socket_buffered_write_bytes(ssh_socket s) +{ + if (s==NULL || s->out_buffer == NULL) { + return 0; + } + + return ssh_buffer_get_len(s->out_buffer); +} + +/** + * @internal + * + * @brief Get the current status of an SSH socket. + * + * Checks the input/output buffers and exception flag to determine socket + * status: `SSH_READ_PENDING` if input data available, `SSH_WRITE_PENDING` + * if output data pending, `SSH_CLOSED_ERROR` if exception occurred. + * + * @param[in] s The SSH socket. + * + * @return Socket status flags. + */ +int ssh_socket_get_status(ssh_socket s) +{ + int r = 0; + + if (ssh_buffer_get_len(s->in_buffer) > 0) { + r |= SSH_READ_PENDING; + } + + if (ssh_buffer_get_len(s->out_buffer) > 0) { + r |= SSH_WRITE_PENDING; + } + + if (s->data_except) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +/** + * @internal + * + * @brief Get SSH socket poll flags from the poll handle. + * + * Checks the poll handle events and returns `SSH_READ_PENDING` if POLLIN + * is set, `SSH_WRITE_PENDING` if POLLOUT is set. + * + * @param[in] s The SSH socket. + * + * @return Socket status flags based on poll events. + */ +int ssh_socket_get_poll_flags(ssh_socket s) +{ + int r = 0; + if (s->poll_handle != NULL && (ssh_poll_get_events (s->poll_handle) & POLLIN) > 0) { + r |= SSH_READ_PENDING; + } + if (s->poll_handle != NULL && (ssh_poll_get_events (s->poll_handle) & POLLOUT) > 0) { + r |= SSH_WRITE_PENDING; + } + return r; +} + +#ifdef _WIN32 +int ssh_socket_set_nonblocking(socket_t fd) +{ + u_long nonblocking = 1; + return ioctlsocket(fd, FIONBIO, &nonblocking); +} + +int ssh_socket_set_blocking(socket_t fd) +{ + u_long nonblocking = 0; + return ioctlsocket(fd, FIONBIO, &nonblocking); +} + +#else /* _WIN32 */ +int ssh_socket_set_nonblocking(socket_t fd) +{ + return fcntl(fd, F_SETFL, O_NONBLOCK); +} + +int ssh_socket_set_blocking(socket_t fd) +{ + return fcntl(fd, F_SETFL, 0); +} +#endif /* _WIN32 */ + +/** + * @internal + * @brief Launches a socket connection + * If the socket connected callback has been defined and + * a poll object exists, this call will be non blocking. + * @param s socket to connect. + * @param host hostname or ip address to connect to. + * @param port port number to connect to. + * @param bind_addr address to bind to, or NULL for default. + * @returns `SSH_OK` socket is being connected. + * @returns `SSH_ERROR` error while connecting to remote host. + */ +int ssh_socket_connect(ssh_socket s, + const char *host, + uint16_t port, + const char *bind_addr) +{ + socket_t fd; + + if (s->state != SSH_SOCKET_NONE) { + ssh_set_error(s->session, SSH_FATAL, + "ssh_socket_connect called on socket not unconnected"); + return SSH_ERROR; + } + fd = ssh_connect_host_nonblocking(s->session, host, bind_addr, port); + SSH_LOG(SSH_LOG_DEBUG, "Nonblocking connection socket: %d", fd); + if (fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + return ssh_socket_set_fd(s, fd); +} + +#ifdef WITH_EXEC +/** + * @internal + * @brief executes a command and redirect input and outputs + * @param command command to execute + * @param in input file descriptor + * @param out output file descriptor + */ +void +ssh_execute_command(const char *command, socket_t in, socket_t out) +{ + const char *shell = NULL; + const char *args[] = {NULL/*shell*/, "-c", command, NULL}; + int devnull; + int rc; + + /* Prepare /dev/null socket for the stderr redirection */ + devnull = open("/dev/null", O_WRONLY); + if (devnull == -1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to open /dev/null"); + exit(1); + } + + /* + * By default, use the current users shell. This could fail with some + * shells like zsh or dash ... + */ + shell = getenv("SHELL"); + if (shell == NULL || shell[0] == '\0') { + /* Fall back to the /bin/sh only if the bash is not available. But there are + * issues with dash or whatever people tend to link to /bin/sh */ + rc = access("/bin/bash", 0); + if (rc != 0) { + shell = "/bin/sh"; + } else { + shell = "/bin/bash"; + } + } + args[0] = shell; + + /* redirect in and out to stdin, stdout */ + dup2(in, 0); + dup2(out, 1); + /* Ignore anything on the stderr */ + dup2(devnull, STDERR_FILENO); + close(in); + close(out); + rc = execv(args[0], (char * const *)args); + if (rc < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + SSH_LOG(SSH_LOG_WARN, "Failed to execute command %s: %s", + command, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } + exit(1); +} + +/** + * @internal + * @brief Open a socket on a ProxyCommand + * This call will always be nonblocking. + * @param s socket to connect. + * @param command Command to execute. + * @returns `SSH_OK` socket is being connected. + * @returns `SSH_ERROR` error while executing the command. + */ +int +ssh_socket_connect_proxycommand(ssh_socket s, const char *command) +{ + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socket_t pair[2]; + ssh_poll_handle h = NULL; + int pid; + int rc; + + if (s->state != SSH_SOCKET_NONE) { + return SSH_ERROR; + } + + rc = socketpair(PF_UNIX, SOCK_STREAM, 0, pair); + if (rc < 0) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, "Executing proxycommand '%s'", command); + pid = fork(); + if (pid == 0) { + ssh_execute_command(command, pair[0], pair[0]); + /* child: Does not return */ + } + /* parent */ + if (pid == -1) { + close(pair[0]); + close(pair[1]); + ssh_set_error(s->session, + SSH_FATAL, + "fork failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + s->proxy_pid = pid; + close(pair[0]); + SSH_LOG(SSH_LOG_DEBUG, + "ProxyCommand connection pipe: [%d,%d]", + pair[0], + pair[1]); + + rc = ssh_socket_set_fd(s, pair[1]); + if (rc != SSH_OK) { + return rc; + } + + s->fd_is_socket = 0; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return SSH_ERROR; + } + ssh_socket_set_connected(s, h); + if (s->callbacks && s->callbacks->connected) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, 0, s->callbacks->userdata); + } + + return SSH_OK; +} +#endif /* WITH_EXEC */ + +#ifndef _WIN32 +#ifdef HAVE_PTHREAD +static int +verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + + state = ssh_session_is_known_server(session); + + switch (state) { + case SSH_KNOWN_HOSTS_OK: + break; /* ok */ + default: + SSH_LOG(SSH_LOG_WARN, "Couldn't verify knownhost during proxyjump."); + return SSH_ERROR; + } + + return SSH_OK; +} + +static void free_jump_thread_data(struct jump_thread_data_struct *data) +{ + if (data == NULL) { + return; + } + + ssh_free(data->session); + SAFE_FREE(data->next_hostname); + if (data->next_jump != NULL) { + SAFE_FREE(data->next_jump->hostname); + SAFE_FREE(data->next_jump->username); + } + SAFE_FREE(data->next_jump); + SAFE_FREE(data); +} + +static void * +jump_thread_func(void *arg) +{ + struct jump_thread_data_struct *jump_thread_data = NULL; + struct ssh_jump_info_struct *jis = NULL; + struct ssh_jump_callbacks_struct *cb = NULL; + ssh_session jump_session = NULL; + ssh_channel caa = NULL; + int rc; + ssh_event event = NULL; + ssh_connector connector_in = NULL, connector_out = NULL; + uint16_t next_port; + char *next_hostname = NULL; + + jump_thread_data = (struct jump_thread_data_struct *)arg; + jump_session = jump_thread_data->session; + + /* First thing we need to do is to set the right level as its kept in + * thread local variable, therefore reset to 0 after spawning new thread. + */ + ssh_set_log_level(jump_session->common.log_verbosity); + + cb = jump_thread_data->next_cb; + jis = jump_thread_data->next_jump; + + /* This is the calling thread target where we will eventually initialize + * forwarding */ + next_port = jump_thread_data->next_port; + next_hostname = jump_thread_data->next_hostname; + + ssh_options_set(jump_session, SSH_OPTIONS_HOST, jis->hostname); + + /* + * Only propagate the username and port that the ProxyJump specification + * actually provided. Setting them unconditionally would inject internal + * defaults (the local username and port 22) as if the application had + * chosen them, which then prevents the jump host's own configuration from + * supplying these values (issue #365). When they are omitted here, the jump + * host config and the connection internals fill them in. + */ + if (jis->username != NULL) { + ssh_options_set(jump_session, SSH_OPTIONS_USER, jis->username); + } + if (jis->port > 0) { + ssh_options_set(jump_session, SSH_OPTIONS_PORT, &jis->port); + } + + if (cb != NULL && cb->before_connection != NULL) { + rc = cb->before_connection(jump_session, cb->userdata); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); + goto exit; + } + } + + SSH_LOG(SSH_LOG_PACKET, + "Proxy connecting to host %s port %d user %s, callbacks=%p", + jis->hostname, + jis->port, + jis->username, + (void *)cb); + + /* If there are more jumps then this will make a new thread and call the + * current function again, until there are no jumps. When there are no jumps + * it connects normally. */ + rc = ssh_connect(jump_session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); + goto exit; + } + + /* Use the callback or default implementation for verifying knownhost */ + if (cb != NULL && cb->verify_knownhost != NULL) { + rc = cb->verify_knownhost(jump_session, cb->userdata); + } else { + rc = verify_knownhost(jump_session); + } + if (rc != SSH_OK) { + goto exit; + } + + /* Use the callback or publickey method to authenticate */ + if (cb != NULL && cb->authenticate != NULL) { + rc = cb->authenticate(jump_session, cb->userdata); + } else { + rc = ssh_userauth_publickey_auto(jump_session, NULL, NULL); + } + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); + goto exit; + } + + caa = ssh_channel_new(jump_session); + if (caa == NULL) { + goto exit; + } + /* The origin hostname and port are set to match OpenSSH implementation + * they are only used for logging on the server */ + rc = ssh_channel_open_forward(caa, + next_hostname, + next_port, + "127.0.0.1", + 65535); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Error opening port forwarding channel: %s", + ssh_get_error(jump_session)); + goto exit; + } + + event = ssh_event_new(); + if (event == NULL) { + goto exit; + } + + connector_in = ssh_connector_new(jump_session); + if (connector_in == NULL) { + goto exit; + } + ssh_connector_set_out_channel(connector_in, caa, SSH_CONNECTOR_STDINOUT); + ssh_connector_set_in_fd(connector_in, jump_thread_data->fd); + ssh_event_add_connector(event, connector_in); + + connector_out = ssh_connector_new(jump_session); + if (connector_out == NULL) { + goto exit; + } + ssh_connector_set_out_fd(connector_out, jump_thread_data->fd); + ssh_connector_set_in_channel(connector_out, caa, SSH_CONNECTOR_STDINOUT); + ssh_event_add_connector(event, connector_out); + + while (ssh_channel_is_open(caa)) { + if (proxy_disconnect == 1) { + break; + } + rc = ssh_event_dopoll(event, 60000); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_WARN, + "Error in ssh_event_dopoll() during proxy jump"); + break; + } + } + +exit: + if (connector_in != NULL) { + ssh_event_remove_connector(event, connector_in); + ssh_connector_free(connector_in); + } + if (connector_out != NULL) { + ssh_event_remove_connector(event, connector_out); + ssh_connector_free(connector_out); + } + + ssh_disconnect(jump_session); + ssh_event_free(event); + + shutdown(jump_thread_data->fd, SHUT_RDWR); + close(jump_thread_data->fd); + + free_jump_thread_data(jump_thread_data); + pthread_exit(NULL); +} + +int +ssh_socket_connect_proxyjump(ssh_socket s) +{ + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_poll_handle h = NULL; + int rc; + pthread_t jump_thread; + struct ssh_jump_info_struct *jis = NULL; + struct ssh_jump_callbacks_struct *cb = NULL; + struct jump_thread_data_struct *jump_thread_data = NULL; + ssh_session jump_session = NULL, session = NULL; + struct ssh_list *empty_list = NULL; + socket_t pair[2] = {SSH_INVALID_SOCKET, SSH_INVALID_SOCKET}; + + session = s->session; + + SSH_LOG(SSH_LOG_INFO, + "Connecting to host %s port %d user %s through ProxyJump", + session->opts.host, + session->opts.port, + session->opts.username); + + if (s->state != SSH_SOCKET_NONE) { + ssh_set_error( + session, + SSH_FATAL, + "ssh_socket_connect_proxyjump called on socket not unconnected"); + return SSH_ERROR; + } + + jump_thread_data = calloc(1, sizeof(struct jump_thread_data_struct)); + if (jump_thread_data == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = socketpair(PF_UNIX, SOCK_STREAM, 0, pair); + if (rc == -1) { + ssh_set_error(session, + SSH_FATAL, + "Creating socket pair failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + goto fail; + } + + jump_session = ssh_new(); + if (jump_session == NULL) { + ssh_set_error_oom(session); + goto fail; + } + + jump_session->proxy_root = false; + /* Reset the global variable if it was previously 1 */ + if (session->proxy_root) { + proxy_disconnect = 0; + } + + /* Pop first jump that will be used by the following thread */ + jis = ssh_list_pop_head(struct ssh_jump_info_struct *, + session->opts.proxy_jumps); + if (jis == NULL) { + SSH_LOG(SSH_LOG_WARN, "Inconsistent list of proxy jumps received"); + ssh_free(jump_session); + goto fail; + } + jump_thread_data->next_jump = jis; + /* Move remaining to the jump session without reallocation. + * The list in the new jump_session is just allocated so empty */ + empty_list = jump_session->opts.proxy_jumps; + jump_session->opts.proxy_jumps = session->opts.proxy_jumps; + session->opts.proxy_jumps = empty_list; + + /* Pop the callbacks for the first jump */ + cb = ssh_list_pop_head(struct ssh_jump_callbacks_struct *, + session->opts.proxy_jumps_user_cb); + /* empty is ok */ + jump_thread_data->next_cb = cb; + /* Move remaining to the jump session without reallocation. + * The list in the new jump_session is just allocated so empty */ + empty_list = jump_session->opts.proxy_jumps_user_cb; + jump_session->opts.proxy_jumps_user_cb = session->opts.proxy_jumps_user_cb; + session->opts.proxy_jumps_user_cb = empty_list; + + ssh_options_set(jump_session, + SSH_OPTIONS_LOG_VERBOSITY, + &session->common.log_verbosity); + + jump_thread_data->next_port = session->opts.port; + jump_thread_data->next_hostname = strdup(session->opts.host); + + jump_thread_data->fd = pair[0]; + pair[0] = SSH_INVALID_SOCKET; + jump_thread_data->session = jump_session; + /* transferred to the jump_thread_data */ + jump_session = NULL; + + SSH_LOG(SSH_LOG_INFO, + "Starting proxy thread to host %s port %d user %s, callbacks=%p", + jump_thread_data->next_jump->hostname, + jump_thread_data->next_jump->port, + jump_thread_data->next_jump->username, + (void *)jump_thread_data->next_cb); + + rc = pthread_create(&jump_thread, NULL, jump_thread_func, jump_thread_data); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Creating new thread failed: %s", + ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX)); + goto fail; + } + /* ownership passed to the thread */ + jump_thread_data = NULL; + + rc = pthread_detach(jump_thread); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Failed to detach thread: %s", + ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX)); + goto fail; + } + + SSH_LOG(SSH_LOG_DEBUG, + "ProxyJump connection thread %lu started pipe: [%d,%d]", + (unsigned long)jump_thread, + pair[0], + pair[1]); + + rc = ssh_socket_set_fd(s, pair[1]); + if (rc != SSH_OK) { + return rc; + } + pair[1] = SSH_INVALID_SOCKET; + + s->fd_is_socket = 1; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return SSH_ERROR; + } + ssh_socket_set_connected(s, h); + if (s->callbacks && s->callbacks->connected) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, + 0, + s->callbacks->userdata); + } + + return SSH_OK; + +fail: + if (pair[0] != SSH_INVALID_SOCKET) { + close(pair[0]); + } + if (pair[1] != SSH_INVALID_SOCKET) { + close(pair[1]); + } + free_jump_thread_data(jump_thread_data); + return SSH_ERROR; +} + +#endif /* HAVE_PTHREAD */ + +#endif /* _WIN32 */ + +#ifndef _WIN32 +#define SOCKADDR struct sockaddr +#define SOCKADDR_IN struct sockaddr_in +#define closesocket close +#endif +/** + * @internal + * @brief Open a socket with VirtualBox-specific ProxyCommand. + * This call will always be nonblocking. + * @param s socket to connect. + * @param command Command to execute. + * @returns SSH_OK socket is being connected. + * @returns SSH_ERROR error while executing the command. + */ + +int +ssh_socket_connect_proxycommand_vbox(ssh_socket s, const char *host, + uint16_t port, const char *params) +{ + socket_t fd; + SOCKADDR_IN sa_proxy; + int rc; + char buffer[2048]; + + if (s->state != SSH_SOCKET_NONE) { + return SSH_ERROR; + } + + /* Use buffer to convert parameters */ + strncpy(buffer, params, sizeof(buffer)); + char *proxy_addr = strchr(buffer, ' '); + if (!proxy_addr) + { + SSH_LOG(SSH_LOG_WARNING, "Invalid proxy parameter string '%s'", params); + return SSH_ERROR; + } + else + *proxy_addr++ = 0; /* Terminate proxy type */ + + if (strcmp(buffer, "HTTP")) + { + SSH_LOG(SSH_LOG_WARNING, "Unsupported proxy type '%s'", buffer); + return SSH_ERROR; + } + + /* Continue to parse parameters */ + char *proxy_auth = NULL; + char *proxy_user = NULL; + char *proxy_password = NULL; + char *pszPort = strchr(proxy_addr, ' '); + if (pszPort) + { + *pszPort++ = 0; /* Terminate IP address */ + + proxy_user = strchr(pszPort, ' '); + if (proxy_user) + { + *proxy_user++ = 0; /* Terminate port */ + proxy_password = strchr(proxy_user, ' '); + if (proxy_password) + { + *proxy_password++ = ':'; /* Create user:password string */ + proxy_auth = bin_to_base64(proxy_user, strlen(proxy_user)); + } + } + + sa_proxy.sin_port = htons(atoi(pszPort)); + } + else + sa_proxy.sin_port = htons(80); /* Is this a reasonable default? */ + sa_proxy.sin_family = AF_INET; + if (!inet_pton(sa_proxy.sin_family, proxy_addr, &sa_proxy.sin_addr)) + { + SSH_LOG(SSH_LOG_WARNING, "Failed to convert '%s' to IPv4 address", proxy_addr); + if (proxy_auth) + free(proxy_auth); + return SSH_ERROR; + } + + /* Invalidate temporary pointers */ + proxy_addr = pszPort = proxy_user = proxy_password = NULL; + /* Use buffer to compose HTTP CONNECT request */ + if (proxy_auth) + { + sprintf(buffer, + "CONNECT %s:%u HTTP/1.1\r\n" + "Host: %s:%u\r\n" + "Proxy-Authorization: basic %s\r\n" + "\r\n", + host, port, + host, port, + proxy_auth); + free(proxy_auth); + } + else + { + sprintf(buffer, + "CONNECT %s:%u HTTP/1.1\r\n" + "Host: %s:%u\r\n" + "\r\n", + host, port, + host, port); + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Connecting to '%s:%u' via proxy at '%s'", host, port, params); + fd = socket(sa_proxy.sin_family, SOCK_STREAM, IPPROTO_TCP); + if (fd < 0) { + return SSH_ERROR; + } + + rc = connect(fd, (SOCKADDR*)&sa_proxy, sizeof(sa_proxy)); + if (rc != 0) + { + SSH_LOG(SSH_LOG_WARNING, "Failed to connect to '%s:%u' via proxy at '%s'", host, port, params); + closesocket(fd); + return SSH_ERROR; + } + + /* Send HTTP CONNECT */ + ssize_t cb = send(fd, buffer, strlen(buffer), 0); + if (cb < 0) + { + SSH_LOG(SSH_LOG_WARNING, "Failed to send connect request to proxy at '%s'", params); + closesocket(fd); + return SSH_ERROR; + } + + /* Recieve reply */ + cb = recv(fd, buffer, sizeof(buffer) - 1, 0); /* Limit to have enough room for ensuring zero termination of the response. */ + if (cb <= 0) + { + SSH_LOG(SSH_LOG_WARNING, "Failed to receive connect response from proxy at '%s'", params); + closesocket(fd); + return SSH_ERROR; + } + + buffer[cb] = 0; /* Make sure it is zero-terminated */ + SSH_LOG(SSH_LOG_PACKET, "Received response from proxy:\n%s", buffer); + + /* Is this really necessary? */ + rc = ssh_socket_set_nonblocking(fd); + if (rc < 0) { + ssh_set_error(s->session, SSH_FATAL, + "Failed to set socket non-blocking for %s:%d", + host, port); + closesocket(fd); + return SSH_ERROR; + } + + ssh_socket_set_fd(s, fd); + + return SSH_OK; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/string.c b/src/libs/libssh-0.12.2/src/string.c new file mode 100644 index 000000000000..ba9283b27d9e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/string.c @@ -0,0 +1,375 @@ +/* + * string.c - ssh string functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/string.h" + +/* String maximum size is 256M */ +#define STRING_SIZE_MAX 0x10000000 + +/** + * @defgroup libssh_string The SSH string functions + * @ingroup libssh + * + * @brief String manipulations used in libssh. + * + * @{ + */ + +/** + * @brief Create a new SSH String object. + * + * @param[in] size The size of the string. + * + * @return The newly allocated string, NULL on error. + */ +struct ssh_string_struct *ssh_string_new(size_t size) +{ + struct ssh_string_struct *str = NULL; + + if (size > STRING_SIZE_MAX) { + errno = EINVAL; + return NULL; + } + + str = calloc(1, sizeof(struct ssh_string_struct) + size); + if (str == NULL) { + return NULL; + } + + str->size = htonl((uint32_t)size); + + return str; +} + +/** + * @brief Fill a string with given data. The string should be big enough. + * + * @param s An allocated string to fill with data. + * + * @param data The data to fill the string with. + * + * @param len Size of data. + * + * @return 0 on success, < 0 on error. + */ +int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) +{ + if ((s == NULL) || (data == NULL) || (len == 0) || + (len > ssh_string_len(s))) { + return -1; + } + + memcpy(s->data, data, len); + + return 0; +} + +/** + * @brief Create a ssh string using a C string + * + * @param[in] what The source 0-terminated C string. + * + * @return The newly allocated string, NULL on error with errno + * set. + * + * @note The null byte is not copied nor counted in the output string. + */ +struct ssh_string_struct *ssh_string_from_char(const char *what) +{ + struct ssh_string_struct *ptr = NULL; + size_t len; + + if (what == NULL) { + errno = EINVAL; + return NULL; + } + + len = strlen(what); + + ptr = ssh_string_new(len); + if (ptr == NULL) { + return NULL; + } + + memcpy(ptr->data, what, len); + + return ptr; +} + +/** + * @brief Create a ssh string from an arbitrary data buffer. + * + * Allocates a new SSH string of length `len` and copies the provided data + * into it. If len is 0, returns an empty SSH string. When len > 0, data + * must not be NULL. + * + * @param[in] data Pointer to the data buffer to copy from. May be NULL + * only when len == 0. + * @param[in] len Length of the data buffer to copy. + * + * @return The newly allocated string, NULL on error. + */ +struct ssh_string_struct *ssh_string_from_data(const void *data, size_t len) +{ + struct ssh_string_struct *s = NULL; + int rc; + + if (len > 0 && data == NULL) { + errno = EINVAL; + return NULL; + } + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + if (len > 0) { + rc = ssh_string_fill(s, data, len); + if (rc != 0) { + ssh_string_free(s); + return NULL; + } + } + + return s; +} + +/** + * @brief Return the size of a SSH string. + * + * @param[in] s The input SSH string. + * + * @return The size of the content of the string, 0 on error. + */ +size_t ssh_string_len(struct ssh_string_struct *s) +{ + size_t size; + + if (s == NULL) { + return 0; + } + + size = ntohl(s->size); + if (size > 0 && size <= STRING_SIZE_MAX) { + return size; + } + + return 0; +} + +/** + * @brief Get the string as a C null-terminated string. + * + * This is only available as long as the SSH string exists. + * + * @param[in] s The SSH string to get the C string from. + * + * @return The char pointer, NULL on error. + */ +const char *ssh_string_get_char(struct ssh_string_struct *s) +{ + if (s == NULL) { + return NULL; + } + s->data[ssh_string_len(s)] = '\0'; + + return (const char *)s->data; +} + +/** + * @brief Convert a SSH string to a C null-terminated string. + * + * @param[in] s The SSH input string. + * + * @return An allocated string pointer, NULL on error with errno + * set. + * + * @note If the input SSH string contains zeroes, some parts of the output + * string may not be readable with regular libc functions. + */ +char *ssh_string_to_char(struct ssh_string_struct *s) +{ + size_t len; + char *new = NULL; + + if (s == NULL) { + return NULL; + } + + len = ssh_string_len(s); + if (len + 1 < len) { + return NULL; + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + memcpy(new, s->data, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief Deallocate a char string object. + * + * @param[in] s The string to delete. + */ +void ssh_string_free_char(char *s) +{ + SAFE_FREE(s); +} + +/** + * @brief Copy a string, return a newly allocated string. The caller has to + * free the string. + * + * @param[in] s String to copy. + * + * @return Newly allocated copy of the string, NULL on error. + */ +struct ssh_string_struct *ssh_string_copy(struct ssh_string_struct *s) +{ + struct ssh_string_struct *new = NULL; + size_t len; + + if (s == NULL) { + return NULL; + } + + len = ssh_string_len(s); + + new = ssh_string_new(len); + if (new == NULL) { + return NULL; + } + + memcpy(new->data, s->data, len); + + return new; +} + +/** + * @brief Compare two SSH strings. + * + * @param[in] s1 The first SSH string to compare. + * @param[in] s2 The second SSH string to compare. + * + * @return 0 if the strings are equal, + * < 0 if s1 is less than s2, + * > 0 if s1 is greater than s2. + */ +int ssh_string_cmp(struct ssh_string_struct *s1, struct ssh_string_struct *s2) +{ + size_t len1, len2, min_len; + int cmp; + + /* Both are NULL */ + if (s1 == NULL && s2 == NULL) { + return 0; + } + + /* Only one is NULL - NULL is considered "less than" non-NULL */ + if (s1 == NULL) { + return -1; + } else if (s2 == NULL) { + return 1; + } + + /* Get lengths */ + len1 = ssh_string_len(s1); + len2 = ssh_string_len(s2); + min_len = MIN(len1, len2); + + /* Compare data up to the shorter length */ + if (min_len > 0) { + cmp = memcmp(s1->data, s2->data, min_len); + if (cmp != 0) { + return cmp; + } + } + + /* If common prefix is equal, compare lengths */ + if (len1 < len2) { + return -1; + } else if (len1 > len2) { + return 1; + } + + return 0; +} + +/** + * @brief Destroy the data in a string so it couldn't appear in a core dump. + * + * @param[in] s The string to burn. + */ +void ssh_string_burn(struct ssh_string_struct *s) +{ + if (s == NULL || s->size == 0) { + return; + } + + ssh_burn(s->data, ssh_string_len(s)); +} + +/** + * @brief Get the payload of the string. + * + * @param s The string to get the data from. + * + * @return Return the data of the string or NULL on error. + */ +void *ssh_string_data(struct ssh_string_struct *s) +{ + if (s == NULL) { + return NULL; + } + + return s->data; +} + +/** + * @brief Deallocate a SSH string object. + * + * \param[in] s The SSH string to delete. + */ +void ssh_string_free(struct ssh_string_struct *s) +{ + SAFE_FREE(s); +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/threads.c b/src/libs/libssh-0.12.2/src/threads.c new file mode 100644 index 000000000000..7660b972d96f --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads.c @@ -0,0 +1,97 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** + * @defgroup libssh_threads The SSH threading functions + * @ingroup libssh + * + * Threading with libssh + * @{ + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" + +static struct ssh_threads_callbacks_struct *user_callbacks = NULL; + +/** @internal + * @brief inits the threading with the backend cryptographic libraries + */ + +int ssh_threads_init(void) +{ + static int threads_initialized = 0; + int rc; + + if (threads_initialized) { + return SSH_OK; + } + + /* first initialize the user_callbacks with our default handlers if not + * already the case + */ + if (user_callbacks == NULL){ + user_callbacks = ssh_threads_get_default(); + } + + /* Then initialize the crypto libraries threading callbacks */ + rc = crypto_thread_init(user_callbacks); + if (rc == SSH_OK) { + threads_initialized = 1; + } + return rc; +} + +void ssh_threads_finalize(void) +{ + crypto_thread_finalize(); +} + +int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct *cb) +{ + + int rc; + + if (user_callbacks != NULL) { + crypto_thread_finalize(); + } + + user_callbacks = cb; + + rc = crypto_thread_init(user_callbacks); + + return rc; +} + +const char *ssh_threads_get_type(void) +{ + if (user_callbacks != NULL) { + return user_callbacks->type; + } + return NULL; +} + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/threads/libcrypto.c b/src/libs/libssh-0.12.2/src/threads/libcrypto.c new file mode 100644 index 000000000000..18951b6ad667 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/libcrypto.c @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" +#include + +int crypto_thread_init(struct ssh_threads_callbacks_struct *cb) +{ + (void) cb; + return SSH_OK; +} + +void crypto_thread_finalize(void) +{ + return; +} diff --git a/src/libs/libssh-0.12.2/src/threads/libgcrypt.c b/src/libs/libssh-0.12.2/src/threads/libgcrypt.c new file mode 100644 index 000000000000..3560dc55f532 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/libgcrypt.c @@ -0,0 +1,74 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" +#include + +#if (GCRYPT_VERSION_NUMBER >= 0x010600) +/* libgcrypt >= 1.6 does not support custom callbacks */ +GCRY_THREAD_OPTION_PTHREAD_IMPL; + +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) +{ + (void) user_callbacks; + + return SSH_OK; +} + +#else +/* Libgcrypt < 1.6 specific way of handling thread callbacks */ + +static struct gcry_thread_cbs gcrypt_threads_callbacks; + +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) +{ + int cmp; + + if (user_callbacks == NULL) { + return SSH_OK; + } + + cmp = strcmp(user_callbacks->type, "threads_noop"); + if (cmp == 0) { + gcrypt_threads_callbacks.option= GCRY_THREAD_OPTION_VERSION << 8 || + GCRY_THREAD_OPTION_DEFAULT; + } else { + gcrypt_threads_callbacks.option= GCRY_THREAD_OPTION_VERSION << 8 || + GCRY_THREAD_OPTION_USER; + } + + gcrypt_threads_callbacks.mutex_init = user_callbacks->mutex_init; + gcrypt_threads_callbacks.mutex_destroy = user_callbacks->mutex_destroy; + gcrypt_threads_callbacks.mutex_lock = user_callbacks->mutex_lock; + gcrypt_threads_callbacks.mutex_unlock = user_callbacks->mutex_unlock; + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcrypt_threads_callbacks); + + return SSH_OK; +} + +#endif /* GCRYPT_VERSION_NUMBER */ + +void crypto_thread_finalize(void) +{ + return; +} diff --git a/src/libs/libssh-0.12.2/src/threads/mbedtls.c b/src/libs/libssh-0.12.2/src/threads/mbedtls.c new file mode 100644 index 000000000000..62e1db875665 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/mbedtls.c @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" +#include + +#include + +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) +{ + int cmp; + + if (user_callbacks == NULL) { + return SSH_OK; + } + + cmp = strcmp(user_callbacks->type, "threads_noop"); + if (cmp == 0) { + return SSH_OK; + } +#ifdef MBEDTLS_THREADING_ALT + else { + if (user_callbacks != NULL) { + crypto_thread_finalize(); + } + + mbedtls_threading_set_alt(user_callbacks->mutex_init, + user_callbacks->mutex_destroy, + user_callbacks->mutex_lock, + user_callbacks->mutex_unlock); + } +#elif defined MBEDTLS_THREADING_PTHREAD + return SSH_OK; +#else + fprintf(stderr, + "MbedTLS needs to have threading enabled with " + "MBEDTLS_THREADING_PTHREAD or MBEDTLS_THREADING_ALT " + "in mbedtls_config.h\n"); +#warn "MbedTLS needs to have threading enabled with " \ + "MBEDTLS_THREADING_PTHREAD or MBEDTLS_THREADING_ALT in mbedtls_config.h" + return SSH_ERROR; +#endif +} + +void crypto_thread_finalize(void) +{ +#ifdef MBEDTLS_THREADING_ALT + mbedtls_threading_free_alt(); +#endif + return; +} diff --git a/src/libs/libssh-0.12.2/src/threads/noop.c b/src/libs/libssh-0.12.2/src/threads/noop.c new file mode 100644 index 000000000000..63aca1e7cbbe --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/noop.c @@ -0,0 +1,74 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/threads.h" +#include + +static int threads_noop(void **lock) +{ + (void)lock; + + return 0; +} + +static unsigned long threads_id_noop (void) +{ + return 1; +} + +static struct ssh_threads_callbacks_struct ssh_threads_noop = +{ + .type = "threads_noop", + .mutex_init = threads_noop, + .mutex_destroy = threads_noop, + .mutex_lock = threads_noop, + .mutex_unlock = threads_noop, + .thread_id = threads_id_noop +}; + +/* Threads interface implementation */ + +#if !(HAVE_PTHREAD) && !(defined _WIN32 || defined _WIN64) +void ssh_mutex_lock(SSH_MUTEX *mutex) +{ + (void) mutex; + + return; +} + +void ssh_mutex_unlock(SSH_MUTEX *mutex) +{ + (void) mutex; + + return; +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void) +{ + return &ssh_threads_noop; +} +#endif + +struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void) +{ + return &ssh_threads_noop; +} diff --git a/src/libs/libssh-0.12.2/src/threads/pthread.c b/src/libs/libssh-0.12.2/src/threads/pthread.c new file mode 100644 index 000000000000..422dd8549b84 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/pthread.c @@ -0,0 +1,140 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/threads.h" +#include + +#include +#include +#include + +static int ssh_pthread_mutex_init (void **mutex) +{ + int rc = 0; + + if (mutex == NULL) { + return EINVAL; + } + + *mutex = malloc(sizeof(pthread_mutex_t)); + if (*mutex == NULL) { + return ENOMEM; + } + + rc = pthread_mutex_init ((pthread_mutex_t *)*mutex, NULL); + if (rc){ + free (*mutex); + *mutex = NULL; + } + + return rc; +} + +static int ssh_pthread_mutex_destroy (void **mutex) +{ + + int rc = 0; + + if (mutex == NULL) { + return EINVAL; + } + + rc = pthread_mutex_destroy ((pthread_mutex_t *)*mutex); + + free (*mutex); + *mutex = NULL; + + return rc; +} + +static int ssh_pthread_mutex_lock (void **mutex) +{ + return pthread_mutex_lock((pthread_mutex_t *)*mutex); +} + +static int ssh_pthread_mutex_unlock (void **mutex) +{ + return pthread_mutex_unlock((pthread_mutex_t *)*mutex); +} + +static unsigned long ssh_pthread_thread_id (void) +{ +#if defined(_WIN32) && !defined(__WINPTHREADS_VERSION) + return (unsigned long) pthread_self().p; +#else + return (unsigned long) pthread_self(); +#endif +} + +static struct ssh_threads_callbacks_struct ssh_threads_pthread = +{ + .type = "threads_pthread", + .mutex_init = ssh_pthread_mutex_init, + .mutex_destroy = ssh_pthread_mutex_destroy, + .mutex_lock = ssh_pthread_mutex_lock, + .mutex_unlock = ssh_pthread_mutex_unlock, + .thread_id = ssh_pthread_thread_id +}; + +/* Threads interface implementation */ + +#if (HAVE_PTHREAD) +void ssh_mutex_lock(SSH_MUTEX *mutex) +{ + int rc; + + if (mutex == NULL) { + exit(EINVAL); + } + + rc = pthread_mutex_lock(mutex); + + if (rc) { + exit(rc); + } +} + +void ssh_mutex_unlock(SSH_MUTEX *mutex) +{ + int rc; + + if (mutex == NULL) { + exit(EINVAL); + } + + rc = pthread_mutex_unlock(mutex); + + if (rc) { + exit(rc); + } +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void) +{ + return &ssh_threads_pthread; +} +#endif + +struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void) +{ + return &ssh_threads_pthread; +} diff --git a/src/libs/libssh-0.12.2/src/threads/winlocks.c b/src/libs/libssh-0.12.2/src/threads/winlocks.c new file mode 100644 index 000000000000..e63635e74728 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/winlocks.c @@ -0,0 +1,124 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/threads.h" +#include + +#include +#include +#include +#include + +static int ssh_winlock_mutex_init (void **priv) +{ + CRITICAL_SECTION *lock = malloc(sizeof(CRITICAL_SECTION)); + + if (lock == NULL) { + return ENOMEM; + } + + InitializeCriticalSection(lock); + + *priv = lock; + + return 0; +} + +static int ssh_winlock_mutex_destroy (void **lock) +{ + DeleteCriticalSection((CRITICAL_SECTION *) *lock); + free(*lock); + + return 0; +} + +static int ssh_winlock_mutex_lock (void **lock) +{ + EnterCriticalSection((CRITICAL_SECTION *) *lock); + return 0; +} + +static int ssh_winlock_mutex_unlock (void **lock) +{ + LeaveCriticalSection((CRITICAL_SECTION *) *lock); + return 0; +} + +static unsigned long ssh_winlock_thread_id (void) +{ + return GetCurrentThreadId(); +} + +static struct ssh_threads_callbacks_struct ssh_threads_winlock = +{ + .type = "threads_winlock", + .mutex_init = ssh_winlock_mutex_init, + .mutex_destroy = ssh_winlock_mutex_destroy, + .mutex_lock = ssh_winlock_mutex_lock, + .mutex_unlock = ssh_winlock_mutex_unlock, + .thread_id = ssh_winlock_thread_id +}; + +/* Threads interface implementation */ + +void ssh_mutex_lock(SSH_MUTEX *mutex) +{ + void *rc = NULL; + + CRITICAL_SECTION *mutex_tmp = NULL; + + if (*mutex == NULL) { + mutex_tmp = malloc(sizeof(CRITICAL_SECTION)); + + if (mutex_tmp == NULL) { + exit(ENOMEM); + } + + InitializeCriticalSection(mutex_tmp); + + rc = InterlockedCompareExchangePointer((PVOID*)mutex, + (PVOID)mutex_tmp, + NULL); + if (rc != NULL) { + DeleteCriticalSection(mutex_tmp); + free(mutex_tmp); + exit(ENOMEM); + } + } + + EnterCriticalSection(*mutex); +} + +void ssh_mutex_unlock(SSH_MUTEX *mutex) +{ + LeaveCriticalSection(*mutex); +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_winlock(void) +{ + return &ssh_threads_winlock; +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void) +{ + return &ssh_threads_winlock; +} diff --git a/src/libs/libssh-0.12.2/src/token.c b/src/libs/libssh-0.12.2/src/token.c new file mode 100644 index 000000000000..3fe1745c6f08 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/token.c @@ -0,0 +1,545 @@ +/* + * token.c - Token list handling functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2019 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/token.h" + +/** + * @internal + * + * @brief Free the given tokens list structure. The used buffer is overwritten + * with zeroes before freed. + * + * @param[in] tokens The pointer to a structure to be freed; + */ +void ssh_tokens_free(struct ssh_tokens_st *tokens) +{ + int i; + if (tokens == NULL) { + return; + } + + if (tokens->tokens != NULL) { + for (i = 0; tokens->tokens[i] != NULL; i++) { + ssh_burn(tokens->tokens[i], strlen(tokens->tokens[i])); + } + } + + SAFE_FREE(tokens->buffer); + SAFE_FREE(tokens->tokens); + SAFE_FREE(tokens); +} + +/** + * @internal + * + * @brief Split a given string on the given separator character. The returned + * structure holds an array of pointers (tokens) pointing to the obtained + * parts and a buffer where all the content of the list is stored. The last + * element of the array will always be set as NULL. + * + * @param[in] chain The string to split + * @param[in] separator The character used to separate the tokens. + * + * @return A newly allocated tokens list structure; NULL in case of error. + */ +struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator) +{ + + struct ssh_tokens_st *tokens = NULL; + size_t num_tokens = 1, i = 1; + + char *found, *c; + + if (chain == NULL) { + return NULL; + } + + tokens = calloc(1, sizeof(struct ssh_tokens_st)); + if (tokens == NULL) { + return NULL; + } + + tokens->buffer = strdup(chain); + if (tokens->buffer == NULL) { + goto error; + } + + c = tokens->buffer; + do { + found = strchr(c, separator); + if (found != NULL) { + c = found + 1; + num_tokens++; + } + } while(found != NULL); + + /* Allocate tokens list */ + tokens->tokens = calloc(num_tokens + 1, sizeof(char *)); + if (tokens->tokens == NULL) { + goto error; + } + + /* First token starts in the beginning of the chain */ + tokens->tokens[0] = tokens->buffer; + c = tokens->buffer; + + for (i = 1; i < num_tokens; i++) { + /* Find next separator */ + found = strchr(c, separator); + if (found == NULL) { + break; + } + + /* Replace it with a string terminator */ + *found = '\0'; + + /* The next token starts in the next byte */ + c = found + 1; + + /* If we did not reach the end of the chain yet, set the next token */ + if (*c != '\0') { + tokens->tokens[i] = c; + } else { + break; + } + } + + return tokens; + +error: + ssh_tokens_free(tokens); + return NULL; +} + +/** + * @internal + * + * @brief Given two strings, the first containing a list of available tokens and + * the second containing a list of tokens to be searched ordered by preference, + * returns a copy of the first preferred token present in the available list. + * + * @param[in] available_list The list of available tokens + * @param[in] preferred_list The list of tokens to search, ordered by + * preference + * + * @return A newly allocated copy of the token if found; NULL otherwise + */ +char *ssh_find_matching(const char *available_list, + const char *preferred_list) +{ + struct ssh_tokens_st *a_tok = NULL, *p_tok = NULL; + + int i, j; + char *ret = NULL; + + if ((available_list == NULL) || (preferred_list == NULL)) { + return NULL; + } + + a_tok = ssh_tokenize(available_list, ','); + if (a_tok == NULL) { + return NULL; + } + + p_tok = ssh_tokenize(preferred_list, ','); + if (p_tok == NULL) { + goto out; + } + + for (i = 0; p_tok->tokens[i]; i++) { + for (j = 0; a_tok->tokens[j]; j++) { + if (strcmp(a_tok->tokens[j], p_tok->tokens[i]) == 0) { + ret = strdup(a_tok->tokens[j]); + goto out; + } + } + } + +out: + ssh_tokens_free(a_tok); + ssh_tokens_free(p_tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings, the first containing a list of available tokens and + * the second containing a list of tokens to be searched ordered by preference, + * returns a list of all matching tokens ordered by preference. + * + * @param[in] available_list The list of available tokens + * @param[in] preferred_list The list of tokens to search, ordered by + * preference + * + * @return A newly allocated string containing the list of all matching tokens; + * NULL otherwise + */ +char *ssh_find_all_matching(const char *available_list, + const char *preferred_list) +{ + struct ssh_tokens_st *a_tok = NULL, *p_tok = NULL; + int i, j; + char *ret = NULL; + size_t max, len, pos = 0; + int match; + + if ((available_list == NULL) || (preferred_list == NULL)) { + return NULL; + } + + max = MAX(strlen(available_list), strlen(preferred_list)); + + ret = calloc(1, max + 1); + if (ret == NULL) { + return NULL; + } + + a_tok = ssh_tokenize(available_list, ','); + if (a_tok == NULL) { + SAFE_FREE(ret); + goto out; + } + + p_tok = ssh_tokenize(preferred_list, ','); + if (p_tok == NULL) { + SAFE_FREE(ret); + goto out; + } + + for (i = 0; p_tok->tokens[i] ; i++) { + for (j = 0; a_tok->tokens[j]; j++) { + match = !strcmp(a_tok->tokens[j], p_tok->tokens[i]); + if (match) { + if (pos != 0) { + ret[pos] = ','; + pos++; + } + + len = strlen(a_tok->tokens[j]); + memcpy(&ret[pos], a_tok->tokens[j], len); + pos += len; + ret[pos] = '\0'; + } + } + } + + if (ret[0] == '\0') { + SAFE_FREE(ret); + } + +out: + ssh_tokens_free(a_tok); + ssh_tokens_free(p_tok); + return ret; +} + +/** + * @internal + * + * @brief Given a string containing a list of elements, remove all duplicates + * and return in a newly allocated string. + * + * @param[in] list The list to be freed of duplicates + * + * @return A newly allocated copy of the string free of duplicates; NULL in + * case of error. + */ +char *ssh_remove_duplicates(const char *list) +{ + struct ssh_tokens_st *tok = NULL; + + size_t i, j, num_tokens, max_len; + char *ret = NULL; + bool *should_copy = NULL, need_comma = false; + + if (list == NULL) { + return NULL; + } + + /* The maximum number of tokens is the size of the list */ + max_len = strlen(list); + if (max_len == 0) { + return NULL; + } + + /* Add space for ending '\0' */ + max_len++; + + tok = ssh_tokenize(list, ','); + if ((tok == NULL) || (tok->tokens == NULL) || (tok->tokens[0] == NULL)) { + goto out; + } + + should_copy = calloc(1, max_len); + if (should_copy == NULL) { + goto out; + } + + if (strlen(tok->tokens[0]) > 0) { + should_copy[0] = true; + } + + for (i = 1; tok->tokens[i]; i++) { + for (j = 0; j < i; j++) { + if (strcmp(tok->tokens[i], tok->tokens[j]) == 0) { + /* Found a duplicate; do not copy */ + should_copy[i] = false; + break; + } + } + + /* No matching token before */ + if (j == i) { + /* Only copy if it is not an empty string */ + if (strlen(tok->tokens[i]) > 0) { + should_copy[i] = true; + } else { + should_copy[i] = false; + } + } + } + + num_tokens = i; + + ret = calloc(1, max_len); + if (ret == NULL) { + goto out; + } + + for (i = 0; i < num_tokens; i++) { + if (should_copy[i]) { + if (need_comma) { + strncat(ret, ",", (max_len - strlen(ret) - 1)); + } + strncat(ret, tok->tokens[i], (max_len - strlen(ret) - 1)); + need_comma = true; + } + } + + /* If no comma is needed, nothing was copied */ + if (!need_comma) { + SAFE_FREE(ret); + } + +out: + SAFE_FREE(should_copy); + ssh_tokens_free(tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing all the elements of the first list appended with + * all the elements of the second list, without duplicates. The order of the + * elements will be preserved. + * + * @param[in] list The first list + * @param[in] appended_list The list to be appended + * + * @return A newly allocated copy list containing all the elements of the + * kept_list appended with the elements of the appended_list without duplicates; + * NULL in case of error. + */ +char *ssh_append_without_duplicates(const char *list, + const char *appended_list) +{ + size_t concat_len = 0; + char *ret = NULL, *concat = NULL; + int rc = 0; + + if (list != NULL) { + concat_len = strlen(list); + } + + if (appended_list != NULL) { + concat_len += strlen(appended_list); + } + + if (concat_len == 0) { + return NULL; + } + + /* Add room for ending '\0' and for middle ',' */ + concat_len += 2; + concat = calloc(1, concat_len); + if (concat == NULL) { + return NULL; + } + + rc = snprintf(concat, concat_len, "%s%s%s", + list == NULL ? "" : list, + list == NULL ? "" : ",", + appended_list == NULL ? "" : appended_list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; + } + + ret = ssh_remove_duplicates(concat); + + SAFE_FREE(concat); + + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing the elements of the first list without the + * elements of the second list. The order of the elements will be preserved. + * + * @param[in] list The first list + * @param[in] remove_list The list to be removed + * + * @return A newly allocated copy list containing elements of the + * list without the elements of remove_list; NULL in case of error. + */ +char *ssh_remove_all_matching(const char *list, + const char *remove_list) +{ + struct ssh_tokens_st *l_tok = NULL, *r_tok = NULL; + int i, j, cmp; + char *ret = NULL; + size_t len, pos = 0; + bool exclude; + + if (list == NULL) { + return NULL; + } + if (remove_list == NULL) { + return strdup (list); + } + + l_tok = ssh_tokenize(list, ','); + if (l_tok == NULL) { + goto out; + } + + r_tok = ssh_tokenize(remove_list, ','); + if (r_tok == NULL) { + goto out; + } + + ret = calloc(1, strlen(list) + 1); + if (ret == NULL) { + goto out; + } + + for (i = 0; l_tok->tokens[i]; i++) { + exclude = false; + for (j = 0; r_tok->tokens[j]; j++) { + cmp = strcmp(l_tok->tokens[i], r_tok->tokens[j]); + if (cmp == 0) { + exclude = true; + break; + } + } + if (exclude == false) { + if (pos != 0) { + ret[pos] = ','; + pos++; + } + + len = strlen(l_tok->tokens[i]); + memcpy(&ret[pos], l_tok->tokens[i], len); + pos += len; + } + } + + if (ret[0] == '\0') { + SAFE_FREE(ret); + } + +out: + ssh_tokens_free(l_tok); + ssh_tokens_free(r_tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing all the elements of the first list prefixed at + * the beginning of the second list, without duplicates. + * + * @param[in] list The first list + * @param[in] prefixed_list The list to use as a prefix + * + * @return A newly allocated list containing all the elements + * of the list prefixed with the elements of the prefixed_list without + * duplicates; NULL in case of error. + */ +char *ssh_prefix_without_duplicates(const char *list, + const char *prefixed_list) +{ + size_t concat_len = 0; + char *ret = NULL, *concat = NULL; + int rc = 0; + + if (list != NULL) { + concat_len = strlen(list); + } + + if (prefixed_list != NULL) { + concat_len += strlen(prefixed_list); + } + + if (concat_len == 0) { + return NULL; + } + + /* Add room for ending '\0' and for middle ',' */ + concat_len += 2; + concat = calloc(concat_len, 1); + if (concat == NULL) { + return NULL; + } + + rc = snprintf(concat, concat_len, "%s%s%s", + prefixed_list == NULL ? "" : prefixed_list, + prefixed_list == NULL ? "" : ",", + list == NULL ? "" : list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; + } + + ret = ssh_remove_duplicates(concat); + + SAFE_FREE(concat); + + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/ttyopts.c b/src/libs/libssh-0.12.2/src/ttyopts.c new file mode 100644 index 000000000000..251a988bb758 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ttyopts.c @@ -0,0 +1,476 @@ +/* + * ttyopts.c - encoding of TTY modes. + * + * This file is part of the SSH Library + * + * Copyright (c) 2023 by Utimaco TS GmbH + * Author: Daniel Evers + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include +#include + +#ifdef HAVE_TERMIOS_H +#include +#endif + +/** Terminal mode opcodes */ +enum { + TTY_OP_END = 0, + TTY_OP_VINTR = 1, + TTY_OP_VQUIT = 2, + TTY_OP_VERASE = 3, + TTY_OP_VKILL = 4, + TTY_OP_VEOF = 5, + TTY_OP_VEOL = 6, + TTY_OP_VEOL2 = 7, + TTY_OP_VSTART = 8, + TTY_OP_VSTOP = 9, + TTY_OP_VSUSP = 10, + TTY_OP_VDSUSP = 11, + TTY_OP_VREPRINT = 12, + TTY_OP_VWERASE = 13, + TTY_OP_VLNEXT = 14, + TTY_OP_VFLUSH = 15, + TTY_OP_VSWTC = 16, + TTY_OP_VSTATUS = 17, + TTY_OP_VDISCARD = 18, + TTY_OP_IGNPAR = 30, + TTY_OP_PARMRK = 31, + TTY_OP_INPCK = 32, + TTY_OP_ISTRIP = 33, + TTY_OP_INLCR = 34, + TTY_OP_IGNCR = 35, + TTY_OP_ICRNL = 36, + TTY_OP_IUCLC = 37, + TTY_OP_IXON = 38, + TTY_OP_IXANY = 39, + TTY_OP_IXOFF = 40, + TTY_OP_IMAXBEL = 41, + TTY_OP_IUTF8 = 42, + TTY_OP_ISIG = 50, + TTY_OP_ICANON = 51, + TTY_OP_XCASE = 52, + TTY_OP_ECHO = 53, + TTY_OP_ECHOE = 54, + TTY_OP_ECHOK = 55, + TTY_OP_ECHONL = 56, + TTY_OP_NOFLSH = 57, + TTY_OP_TOSTOP = 58, + TTY_OP_IEXTEN = 59, + TTY_OP_ECHOCTL = 60, + TTY_OP_ECHOKE = 61, + TTY_OP_PENDIN = 62, + TTY_OP_OPOST = 70, + TTY_OP_OLCUC = 71, + TTY_OP_ONLCR = 72, + TTY_OP_OCRNL = 73, + TTY_OP_ONOCR = 74, + TTY_OP_ONLRET = 75, + TTY_OP_CS7 = 90, + TTY_OP_CS8 = 91, + TTY_OP_PARENB = 92, + TTY_OP_PARODD = 93, + TTY_OP_ISPEED = 128, + TTY_OP_OSPEED = 129, +}; + +/** + * Encodes a single SSH terminal mode option into the buffer. + * + * @param[in] attr The mode's opcode value. + * + * @param[in] value The mode's value. + * + * @param[out] buf Destination buffer to encode into. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes written to the buffer on success, -1 on + * error. + */ +static int +encode_termios_opt(unsigned char opcode, + uint32_t value, + unsigned char *buf, + size_t buflen) +{ + int offset = 0; + + /* always need 5 bytes */ + if (buflen < 5) { + return -1; + } + + /* 1 byte opcode */ + buf[offset++] = opcode; + + /* 4 bytes value (big endian) */ + value = htonl(value); + memcpy(buf + offset, &value, sizeof(value)); + offset += sizeof(value); + + return offset; +} + +#ifdef HAVE_TERMIOS_H +/** Converts a baudrate constant (Bxxxx) to a numeric value. */ +static int +baud2speed(int baudrate) +{ + switch (baudrate) { + default: + case B0: + return 0; + case B50: + return 50; + case B75: + return 75; + case B110: + return 110; + case B134: + return 134; + case B150: + return 150; + case B200: + return 200; + case B300: + return 300; + case B600: + return 600; + case B1200: + return 1200; + case B1800: + return 1800; + case B2400: + return 2400; + case B4800: + return 4800; + case B9600: + return 9600; + case B19200: + return 19200; + case B38400: + return 38400; +#ifdef B57600 + case B57600: + return 57600; +#endif +#ifdef B115200 + case B115200: + return 115200; +#endif +#ifdef B230400 + case B230400: + return 230400; +#endif + } +} + +/** + * Encodes all terminal options from the given \c termios structure + * into the buffer. + * + * @param[in] attr The terminal options to encode. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + +#define SSH_ENCODE_INPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_iflag & opt) ? 1 : 0) + SSH_ENCODE_INPUT_OPT(IGNPAR) + SSH_ENCODE_INPUT_OPT(PARMRK) + SSH_ENCODE_INPUT_OPT(INPCK) + SSH_ENCODE_INPUT_OPT(ISTRIP) + SSH_ENCODE_INPUT_OPT(INLCR) + SSH_ENCODE_INPUT_OPT(IGNCR) + SSH_ENCODE_INPUT_OPT(ICRNL) +#ifdef IUCLC + SSH_ENCODE_INPUT_OPT(IUCLC) +#endif + SSH_ENCODE_INPUT_OPT(IXON) + SSH_ENCODE_INPUT_OPT(IXANY) + SSH_ENCODE_INPUT_OPT(IXOFF) +#ifdef IMAXBEL + SSH_ENCODE_INPUT_OPT(IMAXBEL) +#endif +#ifdef IUTF8 + SSH_ENCODE_INPUT_OPT(IUTF8) +#endif +#undef SSH_ENCODE_INPUT_OPT + +#define SSH_ENCODE_OUTPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_oflag & opt) ? 1 : 0) + SSH_ENCODE_OUTPUT_OPT(OPOST) +#ifdef OLCUC + SSH_ENCODE_OUTPUT_OPT(OLCUC) +#endif + SSH_ENCODE_OUTPUT_OPT(ONLCR) + SSH_ENCODE_OUTPUT_OPT(OCRNL) + SSH_ENCODE_OUTPUT_OPT(ONOCR) + SSH_ENCODE_OUTPUT_OPT(ONLRET) +#undef SSH_ENCODE_OUTPUT_OPT + +#define SSH_ENCODE_CONTROL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_cflag & opt) ? 1 : 0) + SSH_ENCODE_CONTROL_OPT(CS7) + SSH_ENCODE_CONTROL_OPT(CS8) + SSH_ENCODE_CONTROL_OPT(PARENB) + SSH_ENCODE_CONTROL_OPT(PARODD) +#undef SSH_ENCODE_CONTROL_OPT + +#define SSH_ENCODE_LOCAL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_lflag & opt) ? 1 : 0) + SSH_ENCODE_LOCAL_OPT(ISIG) + SSH_ENCODE_LOCAL_OPT(ICANON) +#ifdef XCASE + SSH_ENCODE_LOCAL_OPT(XCASE) +#endif + SSH_ENCODE_LOCAL_OPT(ECHO) + SSH_ENCODE_LOCAL_OPT(ECHOE) + SSH_ENCODE_LOCAL_OPT(ECHOK) + SSH_ENCODE_LOCAL_OPT(ECHONL) + SSH_ENCODE_LOCAL_OPT(NOFLSH) + SSH_ENCODE_LOCAL_OPT(TOSTOP) + SSH_ENCODE_LOCAL_OPT(IEXTEN) +#ifdef ECHOCTL + SSH_ENCODE_LOCAL_OPT(ECHOCTL) +#endif +#ifdef ECHOKE + SSH_ENCODE_LOCAL_OPT(ECHOKE) +#endif +#ifdef PENDIN + SSH_ENCODE_LOCAL_OPT(PENDIN) +#endif +#undef SSH_ENCODE_LOCAL_OPT + +#define SSH_ENCODE_CC_OPT(opt) SSH_ENCODE_OPT(TTY_OP_##opt, attr->c_cc[opt]) + SSH_ENCODE_CC_OPT(VINTR) + SSH_ENCODE_CC_OPT(VQUIT) + SSH_ENCODE_CC_OPT(VERASE) + SSH_ENCODE_CC_OPT(VKILL) + SSH_ENCODE_CC_OPT(VEOF) + SSH_ENCODE_CC_OPT(VEOL) +#ifdef VEOL2 + SSH_ENCODE_CC_OPT(VEOL2) +#endif + SSH_ENCODE_CC_OPT(VSTART) + SSH_ENCODE_CC_OPT(VSTOP) + SSH_ENCODE_CC_OPT(VSUSP) +#ifdef VDSUSP + SSH_ENCODE_CC_OPT(VDSUSP) +#endif +#ifdef VREPRINT + SSH_ENCODE_CC_OPT(VREPRINT) +#endif +#ifdef VWERASE + SSH_ENCODE_CC_OPT(VWERASE) +#endif +#ifdef VLNEXT + SSH_ENCODE_CC_OPT(VLNEXT) +#endif +#ifdef VFLUSH + SSH_ENCODE_CC_OPT(VFLUSH) +#endif +#ifdef VSWTC + SSH_ENCODE_CC_OPT(VSWTC) +#endif +#ifdef VSTATUS + SSH_ENCODE_CC_OPT(VSTATUS) +#endif +#ifdef VDISCARD + SSH_ENCODE_CC_OPT(VDISCARD) +#endif +#undef SSH_ENCODE_CC_OPT + + SSH_ENCODE_OPT(TTY_OP_ISPEED, baud2speed(cfgetispeed(attr))) + SSH_ENCODE_OPT(TTY_OP_OSPEED, baud2speed(cfgetospeed(attr))) +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} +#endif + +/** + * Encodes a set of default options to ensure "sane" PTY behavior. + * This function intentionally doesn't use the \c termios structure + * to allow it to work on Windows as well. + * + * The "sane" default set is derived from the `stty sane`, but iutf8 support is + * added on top of that. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_default_opts(unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + + SSH_ENCODE_OPT(TTY_OP_VINTR, 003) + SSH_ENCODE_OPT(TTY_OP_VQUIT, 034) + SSH_ENCODE_OPT(TTY_OP_VERASE, 0177) + SSH_ENCODE_OPT(TTY_OP_VKILL, 025) + SSH_ENCODE_OPT(TTY_OP_VEOF, 004) + SSH_ENCODE_OPT(TTY_OP_VEOL, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL2, 0) + SSH_ENCODE_OPT(TTY_OP_VSTART, 021) + SSH_ENCODE_OPT(TTY_OP_VSTOP, 023) + SSH_ENCODE_OPT(TTY_OP_VSUSP, 032) + SSH_ENCODE_OPT(TTY_OP_VDSUSP, 031) + SSH_ENCODE_OPT(TTY_OP_VREPRINT, 022) + SSH_ENCODE_OPT(TTY_OP_VWERASE, 027) + SSH_ENCODE_OPT(TTY_OP_VLNEXT, 026) + SSH_ENCODE_OPT(TTY_OP_VDISCARD, 017) + SSH_ENCODE_OPT(TTY_OP_IGNPAR, 0) + SSH_ENCODE_OPT(TTY_OP_PARMRK, 0) + SSH_ENCODE_OPT(TTY_OP_INPCK, 0) + SSH_ENCODE_OPT(TTY_OP_ISTRIP, 0) + SSH_ENCODE_OPT(TTY_OP_INLCR, 0) + SSH_ENCODE_OPT(TTY_OP_IGNCR, 0) + SSH_ENCODE_OPT(TTY_OP_ICRNL, 1) + SSH_ENCODE_OPT(TTY_OP_IUCLC, 0) + SSH_ENCODE_OPT(TTY_OP_IXON, 1) + SSH_ENCODE_OPT(TTY_OP_IXANY, 0) + SSH_ENCODE_OPT(TTY_OP_IXOFF, 0) + SSH_ENCODE_OPT(TTY_OP_IMAXBEL, 0) + SSH_ENCODE_OPT(TTY_OP_IUTF8, 1) + SSH_ENCODE_OPT(TTY_OP_ISIG, 1) + SSH_ENCODE_OPT(TTY_OP_ICANON, 1) + SSH_ENCODE_OPT(TTY_OP_XCASE, 0) + SSH_ENCODE_OPT(TTY_OP_ECHO, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOE, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOK, 1) + SSH_ENCODE_OPT(TTY_OP_ECHONL, 0) + SSH_ENCODE_OPT(TTY_OP_NOFLSH, 0) + SSH_ENCODE_OPT(TTY_OP_TOSTOP, 0) + SSH_ENCODE_OPT(TTY_OP_IEXTEN, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOCTL, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOKE, 1) + SSH_ENCODE_OPT(TTY_OP_PENDIN, 0) + SSH_ENCODE_OPT(TTY_OP_OPOST, 1) + SSH_ENCODE_OPT(TTY_OP_OLCUC, 0) + SSH_ENCODE_OPT(TTY_OP_ONLCR, 1) + SSH_ENCODE_OPT(TTY_OP_OCRNL, 0) + SSH_ENCODE_OPT(TTY_OP_ONOCR, 0) + SSH_ENCODE_OPT(TTY_OP_ONLRET, 0) + SSH_ENCODE_OPT(TTY_OP_CS7, 1) + SSH_ENCODE_OPT(TTY_OP_CS8, 1) + SSH_ENCODE_OPT(TTY_OP_PARENB, 0) + SSH_ENCODE_OPT(TTY_OP_PARODD, 0) + SSH_ENCODE_OPT(TTY_OP_ISPEED, 38400); + SSH_ENCODE_OPT(TTY_OP_OSPEED, 38400); + +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} + +/** + * @ingroup libssh_misc + * + * @brief Encode the current TTY options as SSH modes. + * + * Call this function to determine the settings of the process' TTY and + * encode them as SSH Terminal Modes according to RFC 4254 section 8. + * + * If STDIN isn't connected to a TTY, this function fills the buffer with + * "sane" default modes. + * + * The encoded modes can be passed to \c ssh_channel_request_pty_size_modes . + * + * @code + * unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + * encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + * @endcode + * + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +int +encode_current_tty_opts(unsigned char *buf, size_t buflen) +{ +#ifdef HAVE_TERMIOS_H + struct termios attr; + ZERO_STRUCT(attr); + + if (isatty(STDIN_FILENO)) { + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + return encode_termios_opts(&attr, buf, buflen); + } +#endif + + /* use "sane" default attributes */ + return encode_default_opts(buf, buflen); +} diff --git a/src/libs/libssh-0.12.2/src/wrapper.c b/src/libs/libssh-0.12.2/src/wrapper.c new file mode 100644 index 000000000000..a172f9014bb9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/wrapper.c @@ -0,0 +1,646 @@ +/* + * wrapper.c - wrapper for crypto functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* + * Why a wrapper? + * + * Let's say you want to port libssh from libcrypto of openssl to libfoo + * you are going to spend hours removing every reference to SHA1_Update() + * to libfoo_sha1_update after the work is finished, you're going to have + * only this file to modify it's not needed to say that your modifications + * are welcome. + */ + +#include "config.h" + + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/pki.h" +#include "libssh/poly1305.h" +#include "libssh/dh.h" +#ifdef WITH_GEX +#include "libssh/dh-gex.h" +#endif /* WITH_GEX */ +#include "libssh/curve25519.h" +#include "libssh/kex-gss.h" +#include "libssh/ecdh.h" +#include "libssh/hybrid_mlkem.h" +#include "libssh/sntrup761.h" + +static struct ssh_hmac_struct ssh_hmac_tab[] = { + { "hmac-sha1", SSH_HMAC_SHA1, false }, + { "hmac-sha2-256", SSH_HMAC_SHA256, false }, + { "hmac-sha2-512", SSH_HMAC_SHA512, false }, + { "hmac-md5", SSH_HMAC_MD5, false }, + { "aead-poly1305", SSH_HMAC_AEAD_POLY1305, false }, + { "aead-gcm", SSH_HMAC_AEAD_GCM, false }, + { "hmac-sha1-etm@openssh.com", SSH_HMAC_SHA1, true }, + { "hmac-sha2-256-etm@openssh.com", SSH_HMAC_SHA256, true }, + { "hmac-sha2-512-etm@openssh.com", SSH_HMAC_SHA512, true }, + { "hmac-md5-etm@openssh.com", SSH_HMAC_MD5, true }, +#ifdef WITH_INSECURE_NONE + { "none", SSH_HMAC_NONE, false }, +#endif /* WITH_INSECURE_NONE */ + { NULL, 0, false } +}; + +struct ssh_hmac_struct *ssh_get_hmactab(void) { + return ssh_hmac_tab; +} + +size_t hmac_digest_len(enum ssh_hmac_e type) { + switch(type) { + case SSH_HMAC_SHA1: + return SHA_DIGEST_LEN; + case SSH_HMAC_SHA256: + return SHA256_DIGEST_LEN; + case SSH_HMAC_SHA512: + return SHA512_DIGEST_LEN; + case SSH_HMAC_MD5: + return MD5_DIGEST_LEN; + case SSH_HMAC_AEAD_POLY1305: + return POLY1305_TAGLEN; + case SSH_HMAC_AEAD_GCM: + return AES_GCM_TAGLEN; + default: + return 0; + } +} + +const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm) +{ + int i = 0; + struct ssh_hmac_struct *ssh_hmactab = ssh_get_hmactab(); + while (ssh_hmactab[i].name && + ((ssh_hmactab[i].hmac_type != hmac_type) || + (ssh_hmactab[i].etm != etm))) { + i++; + } + return ssh_hmactab[i].name; +} + +/* it allocates a new cipher structure based on its offset into the global table */ +static struct ssh_cipher_struct *cipher_new(uint8_t offset) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = malloc(sizeof(struct ssh_cipher_struct)); + if (cipher == NULL) { + return NULL; + } + + /* note the memcpy will copy the pointers : so, you shouldn't free them */ + memcpy(cipher, &ssh_get_ciphertab()[offset], sizeof(*cipher)); + + return cipher; +} + +void ssh_cipher_clear(struct ssh_cipher_struct *cipher){ +#ifdef HAVE_LIBGCRYPT + unsigned int i; +#endif + + if (cipher == NULL) { + return; + } + +#ifdef HAVE_LIBGCRYPT + if (cipher->key) { + for (i = 0; i < (cipher->keylen / sizeof(gcry_cipher_hd_t)); i++) { + gcry_cipher_close(cipher->key[i]); + } + SAFE_FREE(cipher->key); + } +#endif + + if (cipher->cleanup != NULL) { + cipher->cleanup(cipher); + } +} + +static void cipher_free(struct ssh_cipher_struct *cipher) { + ssh_cipher_clear(cipher); + SAFE_FREE(cipher); +} + +struct ssh_crypto_struct *crypto_new(void) +{ + struct ssh_crypto_struct *crypto = NULL; + + crypto = calloc(1, sizeof(struct ssh_crypto_struct)); + if (crypto == NULL) { + return NULL; + } + return crypto; +} + +void crypto_free(struct ssh_crypto_struct *crypto) +{ + size_t i; + + if (crypto == NULL) { + return; + } + + ssh_key_free(crypto->server_pubkey); + + ssh_dh_cleanup(crypto); + bignum_safe_free(crypto->shared_secret); +#ifdef HAVE_ECDH + SAFE_FREE(crypto->ecdh_client_pubkey); + SAFE_FREE(crypto->ecdh_server_pubkey); + if (crypto->ecdh_privkey != NULL) { +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(crypto->ecdh_privkey); +#else + EVP_PKEY_free(crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ +#elif defined HAVE_GCRYPT_ECC + gcry_sexp_release(crypto->ecdh_privkey); +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_ecp_keypair_free(crypto->ecdh_privkey); + SAFE_FREE(crypto->ecdh_privkey); +#endif /* HAVE_LIBGCRYPT */ + crypto->ecdh_privkey = NULL; + } +#endif +#ifdef HAVE_LIBCRYPTO + EVP_PKEY_free(crypto->curve25519_privkey); +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_sexp_release(crypto->curve25519_privkey); +#endif + SAFE_FREE(crypto->dh_server_signature); + if (crypto->session_id != NULL) { + ssh_burn(crypto->session_id, crypto->session_id_len); + SAFE_FREE(crypto->session_id); + } + if (crypto->secret_hash != NULL) { + ssh_burn(crypto->secret_hash, crypto->digest_len); + SAFE_FREE(crypto->secret_hash); + } + compress_cleanup(crypto); + SAFE_FREE(crypto->encryptIV); + SAFE_FREE(crypto->decryptIV); + SAFE_FREE(crypto->encryptMAC); + SAFE_FREE(crypto->decryptMAC); + if (crypto->encryptkey != NULL) { + ssh_burn(crypto->encryptkey, crypto->out_cipher->keysize / 8); + SAFE_FREE(crypto->encryptkey); + } + if (crypto->decryptkey != NULL) { + ssh_burn(crypto->decryptkey, crypto->in_cipher->keysize / 8); + SAFE_FREE(crypto->decryptkey); + } + + cipher_free(crypto->in_cipher); + cipher_free(crypto->out_cipher); + + for (i = 0; i < SSH_KEX_METHODS; i++) { + SAFE_FREE(crypto->client_kex.methods[i]); + SAFE_FREE(crypto->server_kex.methods[i]); + SAFE_FREE(crypto->kex_methods[i]); + } + +#ifdef HAVE_OPENSSL_MLKEM + EVP_PKEY_free(crypto->mlkem_privkey); +#else + if (crypto->mlkem_privkey != NULL) { + ssh_burn(crypto->mlkem_privkey, crypto->mlkem_privkey_len); + SAFE_FREE(crypto->mlkem_privkey); + crypto->mlkem_privkey_len = 0; + } +#endif + ssh_string_burn(crypto->hybrid_shared_secret); + ssh_string_free(crypto->mlkem_client_pubkey); + ssh_string_free(crypto->mlkem_ciphertext); + ssh_string_free(crypto->hybrid_client_init); + ssh_string_free(crypto->hybrid_server_reply); + ssh_string_free(crypto->hybrid_shared_secret); + + ssh_burn(crypto, sizeof(struct ssh_crypto_struct)); + + SAFE_FREE(crypto); +} + +static void +compression_enable(ssh_session session, + enum ssh_crypto_direction_e direction, + bool delayed) +{ + /* The delayed compression is turned on AFTER authentication. This means + * that we need to turn it on immediately in case of rekeying */ + if (delayed && !(session->flags & SSH_SESSION_FLAG_AUTHENTICATED)) { + if (direction == SSH_DIRECTION_IN) { + session->next_crypto->delayed_compress_in = 1; + } else { /* SSH_DIRECTION_OUT */ + session->next_crypto->delayed_compress_out = 1; + } + } else { + if (direction == SSH_DIRECTION_IN) { + session->next_crypto->do_compress_in = 1; + } else { /* SSH_DIRECTION_OUT */ + session->next_crypto->do_compress_out = 1; + } + } +} + +static int crypt_set_algorithms2(ssh_session session) +{ + const char *wanted = NULL; + const char *method = NULL; + struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); + struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); + uint8_t i = 0; + int cmp; + + /* + * We must scan the kex entries to find crypto algorithms and set their + * appropriate structure. + */ + + /* out */ + wanted = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + for (i = 0; i < 64 && ssh_ciphertab[i].name != NULL; ++i) { + cmp = strcmp(wanted, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no crypto algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set output algorithm to %s", wanted); + + session->next_crypto->out_cipher = cipher_new(i); + if (session->next_crypto->out_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->out_cipher->aead_encrypt != NULL) { + /* this cipher has integrated MAC */ + if (session->next_crypto->out_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + wanted = "aead-poly1305"; + } else { + wanted = "aead-gcm"; + } + } else { + /* + * We must scan the kex entries to find hmac algorithms and set their + * appropriate structure. + */ + + /* out */ + wanted = session->next_crypto->kex_methods[SSH_MAC_C_S]; + } + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(wanted, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no hmac algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", wanted); + + session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->out_hmac_etm = ssh_hmactab[i].etm; + + /* in */ + wanted = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + cmp = strcmp(wanted, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "Crypt_set_algorithms: no crypto algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set input algorithm to %s", wanted); + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->in_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + if (session->next_crypto->in_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + wanted = "aead-poly1305"; + } else { + wanted = "aead-gcm"; + } + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + wanted = session->next_crypto->kex_methods[SSH_MAC_S_C]; + } + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(wanted, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no hmac algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC input algorithm to %s", wanted); + + session->next_crypto->in_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->in_hmac_etm = ssh_hmactab[i].etm; + + /* compression: client */ + method = session->next_crypto->kex_methods[SSH_COMP_C_S]; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S compression"); + compression_enable(session, SSH_DIRECTION_OUT, false); + } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S delayed compression"); + compression_enable(session, SSH_DIRECTION_OUT, true); + } + + method = session->next_crypto->kex_methods[SSH_COMP_S_C]; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C compression"); + compression_enable(session, SSH_DIRECTION_IN, false); + } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C delayed compression"); + compression_enable(session, SSH_DIRECTION_IN, true); + } + + return SSH_OK; +} + +int crypt_set_algorithms_client(ssh_session session) +{ + return crypt_set_algorithms2(session); +} + +#ifdef WITH_SERVER +int crypt_set_algorithms_server(ssh_session session){ + const char *method = NULL; + uint8_t i = 0; + struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); + struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); + int cmp; + + if (session == NULL) { + return SSH_ERROR; + } + + /* + * We must scan the kex entries to find crypto algorithms and set their + * appropriate structure + */ + /* out */ + method = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + cmp = strcmp(method, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session,SSH_FATAL,"crypt_set_algorithms_server : " + "no crypto algorithm function found for %s",method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET,"Set output algorithm %s",method); + + session->next_crypto->out_cipher = cipher_new(i); + if (session->next_crypto->out_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->out_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + if (session->next_crypto->out_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + method = "aead-poly1305"; + } else { + method = "aead-gcm"; + } + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + /* out */ + method = session->next_crypto->kex_methods[SSH_MAC_S_C]; + } + /* HMAC algorithm selection */ + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(method, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms_server: no hmac algorithm function found for %s", + method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", method); + + session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->out_hmac_etm = ssh_hmactab[i].etm; + + /* in */ + method = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + + for (i = 0; ssh_ciphertab[i].name; i++) { + cmp = strcmp(method, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server :" + "no crypto algorithm function found for %s",method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET,"Set input algorithm %s",method); + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->in_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + if (session->next_crypto->in_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + method = "aead-poly1305"; + } else { + method = "aead-gcm"; + } + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + method = session->next_crypto->kex_methods[SSH_MAC_C_S]; + } + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(method, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms_server: no hmac algorithm function found for %s", + method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC input algorithm to %s", method); + + session->next_crypto->in_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->in_hmac_etm = ssh_hmactab[i].etm; + + /* compression */ + method = session->next_crypto->kex_methods[SSH_COMP_C_S]; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S compression"); + compression_enable(session, SSH_DIRECTION_IN, false); + } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S delayed compression"); + compression_enable(session, SSH_DIRECTION_IN, true); + } + + method = session->next_crypto->kex_methods[SSH_COMP_S_C]; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C compression"); + compression_enable(session, SSH_DIRECTION_OUT, false); + } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C delayed compression"); + compression_enable(session, SSH_DIRECTION_OUT, true); + } + + method = session->next_crypto->kex_methods[SSH_HOSTKEYS]; + session->srv.hostkey = ssh_key_type_from_signature_name(method); + session->srv.hostkey_digest = ssh_key_hash_from_name(method); + + /* setup DH key exchange type */ + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + ssh_server_dh_init(session); + break; +#ifdef WITH_GSSAPI + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: + ssh_server_gss_kex_init(session); + break; +#endif /* WITH_GSSAPI */ +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + ssh_server_dhgex_init(session); + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + ssh_server_ecdh_init(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + ssh_server_curve25519_init(session); + break; +#endif +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + ssh_server_sntrup761x25519_init(session); + break; +#endif + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ssh_server_hybrid_mlkem_init(session); + break; + default: + ssh_set_error(session, + SSH_FATAL, + "crypt_set_algorithms_server: could not find init " + "handler for kex type %d", + session->next_crypto->kex_type); + return SSH_ERROR; + } + return SSH_OK; +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/tests/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/CMakeLists.txt new file mode 100644 index 000000000000..cf1adf567327 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/CMakeLists.txt @@ -0,0 +1,473 @@ +project(libssh-tests C) + +if (BSD OR SOLARIS OR OSX) + find_package(Argp) +endif (BSD OR SOLARIS OR OSX) + +set(TORTURE_LIBRARY torture) + +include_directories(${CMOCKA_INCLUDE_DIR} + ${libssh_BINARY_DIR}/include + ${libssh_BINARY_DIR} + ${libssh_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR}/tests) + +set(TORTURE_LINK_LIBRARIES + ${CMOCKA_LIBRARY} + ssh::static) + +if (NOT WIN32) + set(TORTURE_LINK_LIBRARIES + ${TORTURE_LINK_LIBRARIES} + pthread) +endif(NOT WIN32) +if (WITH_GSSAPI AND GSSAPI_FOUND) + find_package(OpenSSL 1.1.1 REQUIRED) + set(TORTURE_LINK_LIBRARIES + ${TORTURE_LINK_LIBRARIES} + OpenSSL::Crypto) +endif (WITH_GSSAPI AND GSSAPI_FOUND) + +# Check for sk-dummy library if FIDO2 support is enabled +if (WITH_FIDO2) + find_file(SK_DUMMY_LIBRARY + NAMES sk-dummy.so + PATHS /usr/lib64/sshtest /usr/lib/sshtest + NO_DEFAULT_PATH + ) +endif (WITH_FIDO2) + +# create test library +add_library(${TORTURE_LIBRARY} + STATIC + cmdline.c + torture.c + torture_key.c + torture_pki.c + torture_sk.c + torture_cmocka.c) +target_link_libraries(${TORTURE_LIBRARY} PRIVATE ${TORTURE_LINK_LIBRARIES}) +target_compile_options(${TORTURE_LIBRARY} PRIVATE + -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" +) + +# Check for sk-dummy and add HAVE_SK_DUMMY definition if available +if (SK_DUMMY_LIBRARY) + add_library(sk-dummy SHARED IMPORTED) + set_target_properties(sk-dummy PROPERTIES IMPORTED_LOCATION "${SK_DUMMY_LIBRARY}") + target_link_libraries(${TORTURE_LIBRARY} PRIVATE sk-dummy) + set(HAVE_SK_DUMMY 1) + set(SK_DUMMY_LIBRARY_PATH ${SK_DUMMY_LIBRARY}) +endif() + +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(${TORTURE_LIBRARY}) +endif (WITH_COVERAGE) + +# The shared version of the library is only useful when client testing is +# enabled +if (CLIENT_TESTING) + # create shared test library + set(TORTURE_SHARED_LIBRARY torture_shared) + + # Create a list of symbols that should be wrapped for override test + set(WRAP_SYMBOLS "") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=chacha_keysetup" + "-Wl,--wrap=chacha_ivsetup" + "-Wl,--wrap=chacha_encrypt_bytes") + list(APPEND WRAP_SYMBOLS "-Wl,--wrap=poly1305_auth") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=crypto_sign_ed25519_keypair" + "-Wl,--wrap=crypto_sign_ed25519" + "-Wl,--wrap=crypto_sign_ed25519_open") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=crypto_scalarmult_base" + "-Wl,--wrap=crypto_scalarmult") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=sntrup761_keypair" + "-Wl,--wrap=sntrup761_enc" + "-Wl,--wrap=sntrup761_dec") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_generate_key_pair" + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_validate_public_key" + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_encapsulate" + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_decapsulate") + + add_library(${TORTURE_SHARED_LIBRARY} + SHARED + cmdline.c + torture.c + torture_key.c + torture_pki.c + torture_sk.c + torture_cmocka.c + ) + target_link_libraries(${TORTURE_SHARED_LIBRARY} PUBLIC + ${CMOCKA_LIBRARY} + ssh::static + ${WRAP_SYMBOLS} + ) + + # Link sk-dummy to torture_shared library if available + if (SK_DUMMY_LIBRARY) + target_link_libraries(${TORTURE_SHARED_LIBRARY} PRIVATE sk-dummy) + endif (SK_DUMMY_LIBRARY) + + target_compile_options(${TORTURE_SHARED_LIBRARY} PRIVATE + -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" + -DTORTURE_SHARED + ) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(${TORTURE_SHARED_LIBRARY}) + endif (WITH_COVERAGE) +endif () + +if (ARGP_LIBRARIES) + target_link_libraries(${TORTURE_LIBRARY} + PUBLIC ${ARGP_LIBRARIES} + ) +endif() + +set(TEST_TARGET_LIBRARIES + ${TORTURE_LIBRARY} + ${TORTURE_LINK_LIBRARIES} +) + +add_subdirectory(unittests) + +# OpenSSH Capabilities are required for all unit tests +find_program(SSH_EXECUTABLE NAMES ssh) +find_program(SSH_KEYGEN_EXECUTABLE NAMES ssh-keygen) +if (SSH_EXECUTABLE) + file(SIZE ${SSH_EXECUTABLE} SSH_EXECUTABLE_SIZE) + execute_process(COMMAND ${SSH_EXECUTABLE} -V ERROR_VARIABLE OPENSSH_VERSION_STR) + string(REGEX REPLACE "^.*OpenSSH_([0-9]+).[0-9].*$" "\\1" OPENSSH_VERSION_MAJOR "${OPENSSH_VERSION_STR}") + string(REGEX REPLACE "^.*OpenSSH_[0-9]+.([0-9]).*$" "\\1" OPENSSH_VERSION_MINOR "${OPENSSH_VERSION_STR}") + set(OPENSSH_VERSION "${OPENSSH_VERSION_MAJOR}.${OPENSSH_VERSION_MINOR}") + add_definitions(-DOPENSSH_VERSION_MAJOR=${OPENSSH_VERSION_MAJOR} -DOPENSSH_VERSION_MINOR=${OPENSSH_VERSION_MINOR}) + if("${OPENSSH_VERSION}" VERSION_GREATER_EQUAL "8.1" AND SSH_KEYGEN_EXECUTABLE) + set(OPENSSH_SUPPORTS_SSHSIG 1) + message(STATUS "OpenSSH ${OPENSSH_VERSION} supports SSH signatures") + else() + set(OPENSSH_SUPPORTS_SSHSIG 0) + message(STATUS "OpenSSH ${OPENSSH_VERSION} does not support SSH signatures (requires 8.1+)") + endif() + if("${OPENSSH_VERSION}" VERSION_LESS "6.3") + # ssh - Q was introduced in 6.3 + message("Version less than 6.3, hardcoding cipher list") + set(OPENSSH_CIPHERS "aes128-ctr\naes192-ctr\naes256-ctr\narcfour256\narcfour128\naes128-gcm@openssh.com\naes256-gcm@openssh.com\naes128-cbc\n3des-cbc\nblowfish-cbc\ncast128-cbc\naes192-cbc\naes256-cbc\narcfour\nrijndael-cbc@lysator.liu.se\n") + set(OPENSSH_MACS "hmac-md5-etm@openssh.com\nhmac-sha1-etm@openssh.com\numac-64-etm@openssh.com\numac-128-etm@openssh.com\nhmac-sha2-256-etm@openssh.com\nhmac-sha2-512-etm@openssh.com\nhmac-ripemd160-etm@openssh.com\nhmac-sha1-96-etm@openssh.com\nhmac-md5-96-etm@openssh.com\nhmac-md5\nhmac-sha1\numac-64@openssh.com\numac-128@openssh.com\nhmac-sha2-256\nhmac-sha2-512\nhmac-ripemd160\nhmac-ripemd160@openssh.com\nhmac-sha1-96\nhmac-md5-96\n") + set(OPENSSH_KEX "ecdh-sha2-nistp256\necdh-sha2-nistp384\necdh-sha2-nistp521\ndiffie-hellman-group-exchange-sha256\ndiffie-hellman-group-exchange-sha1\ndiffie-hellman-group14-sha1\ndiffie-hellman-group1-sha1\n") + set(OPENSSH_KEYS "ssh-rsa\necdsa-sha2-nistp256\n") + else() + execute_process(COMMAND ${SSH_EXECUTABLE} -Q cipher OUTPUT_VARIABLE OPENSSH_CIPHERS) + execute_process(COMMAND ${SSH_EXECUTABLE} -Q mac OUTPUT_VARIABLE OPENSSH_MACS) + execute_process(COMMAND ${SSH_EXECUTABLE} -Q kex OUTPUT_VARIABLE OPENSSH_KEX) + execute_process(COMMAND ${SSH_EXECUTABLE} -Q key OUTPUT_VARIABLE OPENSSH_KEYS) + execute_process(COMMAND ${SSH_EXECUTABLE} -Q sig OUTPUT_VARIABLE OPENSSH_SIGS ERROR_QUIET) + + # We need both of them, but lets get rid of duplicate items presented in both lists + # to avoid processing too long arguments in pkd + set(OPENSSH_KEYS "${OPENSSH_KEYS}${OPENSSH_SIGS}") + string(REPLACE "\n" ";" OPENSSH_KEYS "${OPENSSH_KEYS}") + list(REMOVE_DUPLICATES OPENSSH_KEYS) + string(REPLACE ";" "\n" OPENSSH_KEYS "${OPENSSH_KEYS}") + endif() + + set(SSH_ALGORITHMS + 3des-cbc aes128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se aes128-ctr aes192-ctr + aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com + hmac-sha1 hmac-sha1-96 hmac-sha2-256 hmac-sha2-512 hmac-md5 hmac-md5-96 umac-64@openssh.com + umac-128@openssh.com hmac-sha1-etm@openssh.com hmac-sha1-96-etm@openssh.com + hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-md5-etm@openssh.com + hmac-md5-96-etm@openssh.com umac-64-etm@openssh.com umac-128-etm@openssh.com + diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256 + diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1 + diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 + sntrup761x25519-sha512@openssh.com sntrup761x25519-sha512 + mlkem768x25519-sha256 mlkem768nistp256-sha256 mlkem1024nistp384-sha384 + curve25519-sha256 curve25519-sha256@libssh.org + ssh-ed25519 ssh-ed25519-cert-v01@openssh.com ssh-rsa + ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 + ssh-rsa-cert-v01@openssh.com + ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com + ecdsa-sha2-nistp521-cert-v01@openssh.com + sk-ssh-ed25519@openssh.com sk-ecdsa-sha2-nistp256@openssh.com + sk-ssh-ed25519-cert-v01@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com + ) + foreach(ALGORITHM ${SSH_ALGORITHMS}) + string(TOUPPER ${ALGORITHM} VARNAME) + string(REGEX REPLACE "[-@.]" "_" VARNAME "OPENSSH_${VARNAME}") + + # Match the current algorithm into the complete list of OpenSSH supported algorithms. + # If matching, create an OPENSSH_CIPHER_NAME variable. + string(REGEX MATCH ".*${ALGORITHM}\n" "${VARNAME}" "${OPENSSH_CIPHERS}${OPENSSH_MACS}${OPENSSH_KEX}${OPENSSH_KEYS}") + endforeach(ALGORITHM) + + string(STRIP "${OPENSSH_CIPHERS}" OPENSSH_CIPHERS) + string(STRIP "${OPENSSH_MACS}" OPENSSH_MACS) + string(STRIP "${OPENSSH_KEX}" OPENSSH_KEX) + string(STRIP "${OPENSSH_KEYS}" OPENSSH_KEYS) + string(REPLACE "\n" "," OPENSSH_CIPHERS "${OPENSSH_CIPHERS}") + string(REPLACE "\n" "," OPENSSH_MACS "${OPENSSH_MACS}") + string(REPLACE "\n" "," OPENSSH_KEX "${OPENSSH_KEX}") + string(REPLACE "\n" "," OPENSSH_KEYS "${OPENSSH_KEYS}") + +endif() + +find_program(DROPBEAR_EXECUTABLE NAMES dbclient) +if (DROPBEAR_EXECUTABLE) + execute_process(COMMAND ${DROPBEAR_EXECUTABLE} -V ERROR_VARIABLE DROPBEAR_VERSION_STR) + string(REGEX REPLACE "^.*Dropbear v([0-9]+)\\.([0-9]+).*$" "\\1.\\2" DROPBEAR_VERSION "${DROPBEAR_VERSION_STR}") + set(DROPBEAR_VERSION "${DROPBEAR_VERSION}") + + # HMAC-SHA1 support was removed in version 2025.87 + if("${DROPBEAR_VERSION}" VERSION_LESS "2025.87") + message("Dropbear Version less than 2025.87, enabling dropbear HMAC-SHA1 tests") + add_definitions(-DDROPBEAR_SUPPORTS_HMAC_SHA1) + endif() +else() + message(STATUS "Could NOT find Dropbear (missing: dbclient executable)") + set(DROPBEAR_EXECUTABLE "/bin/false") +endif() + +find_program(PUTTY_EXECUTABLE + NAMES + plink + plink.exe + putty # Fallback for systems where plink isn't separate + DOC "Path to PuTTY plink executable for automated tests") + +if (PUTTY_EXECUTABLE) + message(STATUS "Found PuTTY client: ${PUTTY_EXECUTABLE}") +else() + set(PUTTY_EXECUTABLE "/bin/putty-not-found") +endif() + +find_program(PUTTYGEN_EXECUTABLE + NAMES + puttygen + puttygen.exe + DOC "Path to PuTTYgen executable for key conversion") + +if (PUTTYGEN_EXECUTABLE) + message(STATUS "Found PuTTY keygen: ${PUTTYGEN_EXECUTABLE}") +else() + set(PUTTYGEN_EXECUTABLE "/bin/puttygen-not-found") +endif() + +find_program(SSHD_EXECUTABLE + NAME + sshd + PATHS + /sbin + /usr/sbin + /usr/local/sbin) + +if (WITH_PKCS11_URI) + find_package(softhsm) + if (NOT SOFTHSM_FOUND) + message(SEND_ERROR "Could not find softhsm module!") + endif (NOT SOFTHSM_FOUND) + find_library(PKCS11SPY + NAMES + pkcs11-spy.so + ) + + #Copy the script to setup PKCS11 tokens + file(COPY pkcs11/setup-softhsm-tokens.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pkcs11 FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) +endif (WITH_PKCS11_URI) + +if (CLIENT_TESTING OR SERVER_TESTING) + find_package(socket_wrapper 1.1.5 REQUIRED) + find_package(nss_wrapper 1.1.2 REQUIRED) + find_package(uid_wrapper 1.2.0 REQUIRED) + find_package(pam_wrapper 1.0.1 REQUIRED) + find_package(priv_wrapper 1.0.0) + + if (NOT SSHD_EXECUTABLE) + message(SEND_ERROR "Could not find sshd which is required for client testing") + endif() + find_program(NCAT_EXECUTABLE + NAME + ncat + PATHS + /bin + /usr/bin + /usr/local/bin) + + set(LOCAL_USER "nobody") + set(LOCAL_UID "65533") + find_program(ID_EXECUTABLE NAMES id) + find_program(WHO_EXECUTABLE NAMES whoami) + if (ID_EXECUTABLE AND WHO_EXECUTABLE) + execute_process(COMMAND ${WHO_EXECUTABLE} OUTPUT_VARIABLE LOCAL_USER OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${ID_EXECUTABLE} -u OUTPUT_VARIABLE LOCAL_UID OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + + find_program(TIMEOUT_EXECUTABLE + NAME + timeout + PATHS + /bin + /usr/bin + /usr/local/bin) + if (TIMEOUT_EXECUTABLE) + set(WITH_TIMEOUT "1") + endif() + + # For chroot() use priv_wrapper package if found, or internal chroot_wrapper + if (priv_wrapper_FOUND) + set(CHROOT_WRAPPER "${PRIV_WRAPPER_LIBRARY}") + else() + add_library(chroot_wrapper SHARED chroot_wrapper.c) + set(CHROOT_WRAPPER_LIBRARY ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chroot_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(TEST_TARGET_LIBRARIES + ${TEST_TARGET_LIBRARIES} + chroot_wrapper + ) + set(CHROOT_WRAPPER "${CHROOT_WRAPPER_LIBRARY}") + endif() + + # fs wrapper + add_library(fs_wrapper SHARED fs_wrapper.c) + set(FS_WRAPPER_LIBRARY + ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}fs_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(TEST_TARGET_LIBRARIES + ${TEST_TARGET_LIBRARIES} + fs_wrapper + ) + set(FS_WRAPPER "${FS_WRAPPER_LIBRARY}") + + # ssh_ping + add_executable(ssh_ping ssh_ping.c) + target_compile_options(ssh_ping PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh_ping ssh::static pthread) + + # homedir will be used in passwd + set(HOMEDIR ${CMAKE_CURRENT_BINARY_DIR}/home) + + ### Setup nss_wrapper + configure_file(etc/passwd.in ${CMAKE_CURRENT_BINARY_DIR}/etc/passwd @ONLY) + configure_file(etc/shadow.in ${CMAKE_CURRENT_BINARY_DIR}/etc/shadow @ONLY) + configure_file(etc/group.in ${CMAKE_CURRENT_BINARY_DIR}/etc/group @ONLY) + configure_file(etc/hosts.in ${CMAKE_CURRENT_BINARY_DIR}/etc/hosts @ONLY) + + ### Setup pam_wrapper + configure_file(etc/pam_matrix_passdb.in ${CMAKE_CURRENT_BINARY_DIR}/etc/pam_matrix_passdb @ONLY) + configure_file(etc/pam.d/sshd.in ${CMAKE_CURRENT_BINARY_DIR}/etc/pam.d/sshd @ONLY) + + + set(TORTURE_ENVIRONMENT + "LD_PRELOAD=${FS_WRAPPER}:${SOCKET_WRAPPER_LIBRARY}:${NSS_WRAPPER_LIBRARY}:${UID_WRAPPER_LIBRARY}:${PAM_WRAPPER_LIBRARY}:${CHROOT_WRAPPER}") + if (priv_wrapper_FOUND) + list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER=1 PRIV_WRAPPER_CHROOT_DISABLE=1) + list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER_PRCTL_DISABLE="ALL" PRIV_WRAPPER_SETRLIMIT_DISABLE="ALL") + endif() + list(APPEND TORTURE_ENVIRONMENT UID_WRAPPER=1 UID_WRAPPER_ROOT=1) + list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_PASSWD=${CMAKE_CURRENT_BINARY_DIR}/etc/passwd) + list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_SHADOW=${CMAKE_CURRENT_BINARY_DIR}/etc/shadow) + list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_GROUP=${CMAKE_CURRENT_BINARY_DIR}/etc/group) + list(APPEND TORTURE_ENVIRONMENT PAM_WRAPPER_SERVICE_DIR=${CMAKE_CURRENT_BINARY_DIR}/etc/pam.d) + list(APPEND TORTURE_ENVIRONMENT LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/suppressions/lsan.supp) + list(APPEND TORTURE_ENVIRONMENT OPENSSL_ENABLE_SHA1_SIGNATURES=1) + + # Give bob some keys + file(COPY keys/id_rsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_rsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + # Same as id_rsa, protected with passphrase "secret" + file(COPY keys/id_rsa_protected DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_rsa_protected.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ecdsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ecdsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519 DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + # Security key support + file(COPY keys/id_ecdsa_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ecdsa_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + + # Allow to auth with bob's public keys on alice and doe account + configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys @ONLY) + configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/authorized_keys @ONLY) + configure_file(keys/id_ecdsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/frank/.ssh/authorized_keys @ONLY) + + # append ECDSA public key + file(READ keys/id_ecdsa.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # append ed25519 public key + file(READ keys/id_ed25519.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # append sk-ecdsa public key + file(READ keys/id_ecdsa_sk.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # append sk-ed25519 public key + file(READ keys/id_ed25519_sk.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # Allow to auth with bob his public keys on charlie account + configure_file(keys/pkcs11/id_pkcs11_rsa_openssh.pub ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys @ONLY) + + # append ECDSA public key + file(READ keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + file(READ keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + file(READ keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + file(READ keys/pkcs11/id_pkcs11_ed25519_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + # Copy the signed key to an doe's homedir. + file(COPY keys/certauth/id_rsa DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa.pub DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa-cert.pub DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) +endif () + +file(COPY gss/kdcsetup.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/gss FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) + +message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") + +configure_file(tests_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/tests_config.h) + +if (WITH_BENCHMARKS) + add_subdirectory(benchmarks) +endif () + +if (CLIENT_TESTING) + add_subdirectory(client) + + # Only add override testing if testing the client + add_subdirectory(external_override) +endif () + +if (WITH_SERVER AND SERVER_TESTING) + add_subdirectory(pkd) + add_subdirectory(server) +endif () + +if (FUZZ_TESTING) + add_subdirectory(fuzz) +endif() + +add_custom_target(test_memcheck + # FIXME: The pkd_hello_i1 test is skipped under valgrind as it times out + # Passing suppression file is also stupid so lets go with override here: + # https://stackoverflow.com/a/56116311 + COMMAND ${CMAKE_CTEST_COMMAND} -E pkd_hello_i1 + --output-on-failure --force-new-ctest-process --test-action memcheck + --overwrite MemoryCheckSuppressionFile=${CMAKE_SOURCE_DIR}/tests/valgrind.supp + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/benchmarks/CMakeLists.txt new file mode 100644 index 000000000000..c7b245a5e924 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/CMakeLists.txt @@ -0,0 +1,17 @@ +project(libssh-benchmarks C) + +set(benchmarks_SRCS + bench_scp.c bench_raw.c benchmarks.c latency.c +) +if (WITH_SFTP) + set(benchmarks_SRCS + ${benchmarks_SRCS} + bench_sftp.c + ) +endif (WITH_SFTP) + +include_directories(${libssh_BINARY_DIR}) + +add_executable(benchmarks ${benchmarks_SRCS}) + +target_link_libraries(benchmarks ssh::static pthread) diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench1.sh b/src/libs/libssh-0.12.2/tests/benchmarks/bench1.sh new file mode 100755 index 000000000000..6b3b20f32190 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench1.sh @@ -0,0 +1,14 @@ +#!/bin/bash +export CIPHER=aes128-cbc +export DEST=localhost + +echo "Upload raw SSH statistics" +echo "local machine: $(uname -a)" +echo "Cipher : $CIPHER ; Destination : $DEST ($(ssh $DEST uname -a))" +echo "Local ssh version: $(ssh -V 2>&1)" +echo "Ping latency to $DEST": +ping -q -c 1 -n $DEST +echo "Destination $DEST SSHD version : $(echo | nc $DEST 22 | head -n1)" +echo "ssh login latency :$( (command time -f user:%U ssh $DEST 'id > /dev/null') 2>&1)" +./generate.py | dd bs=4096 count=100000 | time ssh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 + diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench2.sh b/src/libs/libssh-0.12.2/tests/benchmarks/bench2.sh new file mode 100755 index 000000000000..cf240fda3566 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench2.sh @@ -0,0 +1,14 @@ +#!/bin/bash +export CIPHER=aes128-cbc +export DEST=localhost + +echo "Upload raw SSH statistics" +echo "local machine: $(uname -a)" +echo "Cipher : $CIPHER ; Destination : $DEST ($(ssh $DEST uname -a))" +echo "Local ssh version: $(samplessh -V 2>&1)" +echo "Ping latency to $DEST": +ping -q -c 1 -n $DEST +echo "Destination $DEST SSHD version : $(echo | nc $DEST 22 | head -n1)" +echo "ssh login latency :$( (command time -f user:%U samplessh $DEST 'id > /dev/null') 2>&1)" +./generate.py | dd bs=4096 count=100000 | strace samplessh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 + diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench_raw.c b/src/libs/libssh-0.12.2/tests/benchmarks/bench_raw.c new file mode 100644 index 000000000000..05a6ceb06a5d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench_raw.c @@ -0,0 +1,310 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include +#include +#include + +#define PYTHON_PATH "/usr/bin/python" + +const char python_eater[]= +"#!/usr/bin/python\n" +"import sys\n" +"print 'go'\n" +"sys.stdout.flush()\n" +"toread=XXXXXXXXXX\n" +"read=0\n" +"while(read < toread):\n" +" buffersize=toread-read\n" +" if(buffersize > 4096):\n" +" buffersize=4096\n" +" r=len(sys.stdin.read(buffersize))\n" +" read+=r\n" +" if(r<=0):\n" +" print 'error'\n" +" exit()\n" +"print 'done'\n"; + +static char *get_python_eater(unsigned long bytes){ + char *eater=malloc(sizeof(python_eater)); + char *ptr; + char buf[12]; + + if (eater == NULL) { + return NULL; + } + memcpy(eater,python_eater,sizeof(python_eater)); + ptr=strstr(eater,"XXXXXXXXXX"); + if(!ptr){ + free(eater); + return NULL; + } + sprintf(buf,"0x%.8lx",bytes); + memcpy(ptr,buf,10); + return eater; +} + +/** @internal + * @brief uploads a script (python or other) at a specific path on the + * remote host + * @param[in] session an active SSH session + * @param[in] path to copy the file + * @param[in] content of the file to copy + * @return 0 on success, -1 on error + */ +static int upload_script(ssh_session session, const char *path, + const char *script){ + ssh_channel channel; + char cmd[128]; + int err; + + channel=ssh_channel_new(session); + if(!channel) + goto error; + if(ssh_channel_open_session(channel) == SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"cat > %s",path); + if(ssh_channel_request_exec(channel,cmd) == SSH_ERROR) + goto error; + err=ssh_channel_write(channel,script,strlen(script)); + if(err == SSH_ERROR) + goto error; + if(ssh_channel_send_eof(channel) == SSH_ERROR) + goto error; + if(ssh_channel_close(channel) == SSH_ERROR) + goto error; + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error while copying script : %s\n",ssh_get_error(session)); + return -1; +} + +/** @internal + * @brief benchmarks a raw upload (simple upload in a SSH channel) using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_raw_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + char *script; + char cmd[128]; + int err; + ssh_channel channel; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + + bytes = args->datasize * 1024 * 1024; + script = get_python_eater(bytes); + if (script == NULL) { + return -1; + } + err=upload_script(session,"/tmp/eater.py",script); + free(script); + if(err<0) + return err; + channel=ssh_channel_new(session); + if(channel == NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"%s /tmp/eater.py", PYTHON_PATH); + if(ssh_channel_request_exec(channel,cmd)==SSH_ERROR) + goto error; + err = ssh_channel_read(channel, buffer, sizeof(buffer) - 1, 0); + if (err == SSH_ERROR) + goto error; + if (err == SSH_AGAIN) { + fprintf(stderr, "ssh_channel_read timeout"); + goto error; + } + buffer[err]=0; + if(!strstr(buffer,"go")){ + fprintf(stderr,"parse error : %s\n",buffer); + ssh_channel_close(channel); + ssh_channel_free(channel); + return -1; + } + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=ssh_channel_write(channel,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += w; + } + + if(args->verbose>0) + fprintf(stdout,"Finished upload, now waiting the ack\n"); + err = ssh_channel_read(channel, buffer, 5, 0); + if (err == SSH_ERROR) + goto error; + if (err == SSH_AGAIN) { + fprintf(stderr, "ssh_channel_read timeout"); + goto error; + } + buffer[err]=0; + if(!strstr(buffer,"done")){ + fprintf(stderr,"parse error : %s\n",buffer); + ssh_channel_close(channel); + ssh_channel_free(channel); + return -1; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_channel_close(channel); + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error during raw upload : %s\n",ssh_get_error(session)); + if(channel){ + ssh_channel_close(channel); + ssh_channel_free(channel); + } + return -1; +} + +const char python_giver[] = +"#!/usr/bin/python\n" +"import sys\n" +"r=sys.stdin.read(2)\n" +"towrite=XXXXXXXXXX\n" +"wrote=0\n" +"mtu = 32786\n" +"buf = 'A'*mtu\n" +"while(wrote < towrite):\n" +" buffersize=towrite-wrote\n" +" if(buffersize > mtu):\n" +" buffersize=mtu\n" +" if(buffersize == mtu):\n" +" sys.stdout.write(buf)\n" +" else:\n" +" sys.stdout.write('A'*buffersize)\n" +" wrote+=buffersize\n" +"sys.stdout.flush()\n"; + +static char *get_python_giver(unsigned long bytes){ + char *giver=malloc(sizeof(python_giver)); + char *ptr; + char buf[12]; + + if (giver == NULL) { + return NULL; + } + memcpy(giver,python_giver,sizeof(python_giver)); + ptr=strstr(giver,"XXXXXXXXXX"); + if(!ptr){ + free(giver); + return NULL; + } + sprintf(buf,"0x%.8lx",bytes); + memcpy(ptr,buf,10); + return giver; +} + +/** @internal + * @brief benchmarks a raw download (simple upload in a SSH channel) using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_raw_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + char *script; + char cmd[128]; + int err; + ssh_channel channel; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + + bytes = args->datasize * 1024 * 1024; + script = get_python_giver(bytes); + if (script == NULL) { + return -1; + } + err=upload_script(session,"/tmp/giver.py",script); + free(script); + if(err<0) + return err; + channel=ssh_channel_new(session); + if(channel == NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"%s /tmp/giver.py", PYTHON_PATH); + if(ssh_channel_request_exec(channel,cmd)==SSH_ERROR) + goto error; + if((err=ssh_channel_write(channel,"go",2))==SSH_ERROR) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long toread = bytes - total; + int r; + if(toread > args->chunksize) + toread = args->chunksize; + r=ssh_channel_read(channel,buffer,toread,0); + if (r == SSH_ERROR) + goto error; + if (r == SSH_AGAIN) { + fprintf(stderr, "ssh_channel_read timeout"); + goto error; + } + total += r; + } + + if(args->verbose>0) + fprintf(stdout,"Finished download\n"); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_channel_close(channel); + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error during raw upload : %s\n",ssh_get_error(session)); + if(channel){ + ssh_channel_close(channel); + ssh_channel_free(channel); + } + return -1; +} diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench_scp.c b/src/libs/libssh-0.12.2/tests/benchmarks/bench_scp.c new file mode 100644 index 000000000000..71aba2854020 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench_scp.c @@ -0,0 +1,150 @@ +/* bench_scp.c + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include +#include + +#define SCPDIR "/tmp/" +#define SCPFILE "scpbenchmark" + +/** @internal + * @brief benchmarks a scp upload using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_scp_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + ssh_scp scp; + + bytes = args->datasize * 1024 * 1024; + scp = ssh_scp_new(session,SSH_SCP_WRITE,SCPDIR); + if(scp == NULL) + goto error; + if(ssh_scp_init(scp)==SSH_ERROR) + goto error; + if(ssh_scp_push_file(scp,SCPFILE,bytes,0777) != SSH_OK) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=ssh_scp_write(scp,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += towrite; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +error: + fprintf(stderr,"Error during scp upload : %s\n",ssh_get_error(session)); + if(scp){ + ssh_scp_close(scp); + ssh_scp_free(scp); + } + return -1; +} + +/** @internal + * @brief benchmarks a scp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_scp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + ssh_scp scp; + int r; + size_t size; + + bytes = args->datasize * 1024 * 1024; + scp = ssh_scp_new(session,SSH_SCP_READ,SCPDIR SCPFILE); + if(scp == NULL) + goto error; + if(ssh_scp_init(scp)==SSH_ERROR) + goto error; + r=ssh_scp_pull_request(scp); + if(r == SSH_SCP_REQUEST_NEWFILE){ + size=ssh_scp_request_get_size(scp); + if(bytes > size){ + printf("Only %zd bytes available (on %lu requested).\n",size,bytes); + bytes = size; + } + if(size > bytes){ + printf("File is %zd bytes (on %lu requested). Will cut the end\n",size,bytes); + } + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + ssh_scp_accept_request(scp); + while(total < bytes){ + unsigned long toread = bytes - total; + if(toread > args->chunksize) + toread = args->chunksize; + r=ssh_scp_read(scp,buffer,toread); + if(r == SSH_ERROR || r == 0) + goto error; + total += r; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + } else { + fprintf(stderr,"Expected SSH_SCP_REQUEST_NEWFILE, got %d\n",r); + goto error; + } + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +error: + fprintf(stderr,"Error during scp download : %s\n",ssh_get_error(session)); + if(scp){ + ssh_scp_close(scp); + ssh_scp_free(scp); + } + return -1; +} diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench_sftp.c b/src/libs/libssh-0.12.2/tests/benchmarks/bench_sftp.c new file mode 100644 index 000000000000..e6c6824847f9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench_sftp.c @@ -0,0 +1,636 @@ +/* bench_sftp.c + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include +#include +#include +#include +#include +#include + +#define SFTPDIR "/tmp/" +#define SFTPFILE "scpbenchmark" + +/** @internal + * @brief benchmarks a synchronous sftp upload using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_sync_sftp_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file = NULL; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDWR | O_CREAT | O_TRUNC, 0777); + if(!file) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=sftp_write(file,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += w; + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + return 0; +error: + fprintf(stderr,"Error during scp upload : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + return -1; +} + +/** @internal + * @brief benchmarks a synchronous sftp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_sync_sftp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file = NULL; + int r; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDONLY,0); + if(!file) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long toread = bytes - total; + if(toread > args->chunksize) + toread = args->chunksize; + r=sftp_read(file,buffer,toread); + if(r == SSH_ERROR) + goto error; + total += r; + /* we had a smaller file */ + if(r==0){ + fprintf(stdout,"File smaller than expected : %lu (expected %lu).\n",total,bytes); + bytes = total; + break; + } + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + return 0; +error: + fprintf(stderr,"Error during sftp download : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + return -1; +} + +/** @internal + * @brief benchmarks an asynchronous sftp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file = NULL; + int r,i; + int warned = 0; + unsigned long toread; + int *ids=NULL; + int concurrent_downloads = args->concurrent_requests; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDONLY,0); + if(!file) + goto error; + ids = malloc(concurrent_downloads * sizeof(int)); + if (ids == NULL) { + return -1; + } + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now, using %d concurrent downloads\n",bytes, + concurrent_downloads); + timestamp_init(&ts); + for (i=0;ichunksize); + if(ids[i]==SSH_ERROR) + goto error; + } + i=0; + while(total < bytes){ + r = sftp_async_read(file, buffer, args->chunksize, ids[i]); + if(r == SSH_ERROR) + goto error; + total += r; + if(r != (int)args->chunksize && total != bytes && !warned){ + fprintf(stderr,"async_sftp_download : receiving short reads (%d, requested %d) " + "the received file will be corrupted and shorted. Adapt chunksize to %d\n", + r, args->chunksize,r); + warned = 1; + } + /* we had a smaller file */ + if(r==0){ + fprintf(stdout,"File smaller than expected : %lu (expected %lu).\n",total,bytes); + bytes = total; + break; + } + toread = bytes - total; + if(toread < args->chunksize * concurrent_downloads){ + /* we've got enough launched downloads */ + ids[i]=-1; + } + if(toread > args->chunksize) + toread = args->chunksize; + ids[i]=sftp_async_read_begin(file,toread); + if(ids[i] == SSH_ERROR) + goto error; + i = (i+1) % concurrent_downloads; + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + free(ids); + return 0; +error: + fprintf(stderr,"Error during sftp download : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + free(ids); + return -1; +} + +int benchmarks_async_sftp_aio_down(ssh_session session, + struct argument_s *args, + float *bps) +{ + sftp_session sftp = NULL; + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + + struct ssh_list *aio_queue = NULL; + + int concurrent_downloads = args->concurrent_requests; + size_t chunksize; + struct timestamp_struct ts = {0}; + float ms = 0.0f; + + size_t total_bytes = args->datasize * 1024 * 1024; + size_t total_bytes_requested = 0, total_bytes_read = 0; + size_t bufsize = args->chunksize; + size_t to_read; + ssize_t bytes_read, bytes_requested; + int warned = 0, i, rc; + + sftp = sftp_new(session); + if (sftp == NULL) { + fprintf(stderr, "Error during sftp aio download: %s\n", + ssh_get_error(session)); + return -1; + } + + /* + * Errors which are logged in the ssh session are reported after + * jumping to the goto label, errors which aren't logged inside the + * ssh session are reported before jumping to that label + */ + + rc = sftp_init(sftp); + if (rc == SSH_ERROR) { + goto error; + } + + li = sftp_limits(sftp); + if (li == NULL) { + goto error; + } + + if (args->chunksize > li->max_read_length) { + chunksize = li->max_read_length; + if (args->verbose > 0) { + fprintf(stdout, + "Using the chunk size %zu (not the set size %u), " + "to respect the max data limit for read packet\n", + chunksize, args->chunksize); + } + } else { + chunksize = args->chunksize; + } + + file = sftp_open(sftp, SFTPDIR SFTPFILE, O_RDONLY, 0); + if (file == NULL) { + goto error; + } + + aio_queue = ssh_list_new(); + if (aio_queue == NULL) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory\n"); + goto error; + } + + if (args->verbose > 0) { + fprintf(stdout, + "Starting download of %zu bytes now, " + "using %d concurrent downloads.\n", + total_bytes, concurrent_downloads); + } + + timestamp_init(&ts); + + for (i = 0; + i < concurrent_downloads && total_bytes_requested < total_bytes; + ++i) { + to_read = total_bytes - total_bytes_requested; + if (to_read > chunksize) { + to_read = chunksize; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_read) { + fprintf(stderr, + "Error during sftp aio download: sftp_aio_begin_read() " + "requesting less bytes even when the number of bytes " + "asked to read are within the max limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory"); + sftp_aio_free(aio); + goto error; + } + } + + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + bytes_read = sftp_aio_wait_read(&aio, buffer, bufsize); + if (bytes_read == -1) { + goto error; + } + + total_bytes_read += (size_t)bytes_read; + if (bytes_read == 0) { + fprintf(stdout , + "File smaller than expected: %zu bytes (expected %zu).\n", + total_bytes_read, total_bytes); + break; + } + + if (total_bytes_read != total_bytes && + (size_t)bytes_read != chunksize && + warned != 1) { + fprintf(stderr, + "async_sftp_aio_download: Receiving short reads " + "(%zu, expected %zu) before encountering eof, " + "the received file will be corrupted and shorted. " + "Adapt chunksize to %zu.\n", + bytes_read, chunksize, bytes_read); + warned = 1; + } + + if (total_bytes_requested == total_bytes) { + /* No need to issue more requests */ + continue; + } + + /* else issue a request */ + to_read = total_bytes - total_bytes_requested; + if (to_read > chunksize) { + to_read = chunksize; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_read) { + fprintf(stderr, + "Error during sftp aio download: sftp_aio_begin_read() " + "requesting less bytes even when the number of bytes " + "asked to read are within the max limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory\n"); + sftp_aio_free(aio); + goto error; + } + } + + sftp_close(file); + ms = elapsed_time(&ts); + *bps = (float)(8000 * total_bytes_read) / ms; + if (args->verbose > 0) { + fprintf(stdout, "Download took %f ms for %zu bytes at %f bps.\n", + ms, total_bytes_read, *bps); + } + + ssh_list_free(aio_queue); + sftp_limits_free(li); + sftp_free(sftp); + return 0; + +error: + rc = ssh_get_error_code(session); + if (rc != SSH_NO_ERROR) { + fprintf(stderr, "Error during sftp aio download: %s\n", + ssh_get_error(session)); + } + + /* Release aio structures corresponding to outstanding requests */ + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + sftp_aio_free(aio); + } + + ssh_list_free(aio_queue); + sftp_close(file); + sftp_limits_free(li); + sftp_free(sftp); + return -1; +} + +int benchmarks_async_sftp_aio_up(ssh_session session, + struct argument_s *args, + float *bps) +{ + sftp_session sftp = NULL; + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; + + int concurrent_uploads = args->concurrent_requests; + size_t chunksize; + struct timestamp_struct ts = {0}; + float ms = 0.0f; + + size_t total_bytes = args->datasize * 1024 * 1024; + size_t to_write, total_bytes_requested = 0; + ssize_t bytes_written, bytes_requested; + int i, rc; + + sftp = sftp_new(session); + if (sftp == NULL) { + fprintf(stderr, "Error during sftp aio upload: %s\n", + ssh_get_error(session)); + return -1; + } + + /* + * Errors which are logged in the ssh session are reported after + * jumping to the goto label, errors which aren't logged inside the + * ssh session are reported before jumping to that label + */ + + rc = sftp_init(sftp); + if (rc == SSH_ERROR) { + goto error; + } + + li = sftp_limits(sftp); + if (li == NULL) { + goto error; + } + + if (args->chunksize > li->max_write_length) { + chunksize = li->max_write_length; + if (args->verbose > 0) { + fprintf(stdout, + "Using the chunk size %zu (not the set size %u), " + "to respect the max data limit for write packet\n", + chunksize, args->chunksize); + } + } else { + chunksize = args->chunksize; + } + + file = sftp_open(sftp, SFTPDIR SFTPFILE, + O_WRONLY | O_CREAT | O_TRUNC, 0777); + if (file == NULL) { + goto error; + } + + aio_queue = ssh_list_new(); + if (aio_queue == NULL) { + fprintf(stderr, "Error during sftp aio upload: Insufficient memory\n"); + goto error; + } + + if (args->verbose > 0) { + fprintf(stdout, + "Starting upload of %zu bytes now, " + "using %d concurrent uploads.\n", + total_bytes, concurrent_uploads); + } + + timestamp_init(&ts); + + for (i = 0; + i < concurrent_uploads && total_bytes_requested < total_bytes; + ++i) { + to_write = total_bytes - total_bytes_requested; + if (to_write > chunksize) { + to_write = chunksize; + } + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_write) { + fprintf(stderr, + "Error during sftp aio upload: sftp_aio_begin_write() " + "requesting less bytes even when the number of bytes " + "asked to write are within the max write limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio upload: Insufficient memory\n"); + sftp_aio_free(aio); + goto error; + } + } + + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + goto error; + } + + if (total_bytes_requested == total_bytes) { + /* No need to issue more requests */ + continue; + } + + /* else issue a request */ + to_write = total_bytes - total_bytes_requested; + if (to_write > chunksize) { + to_write = chunksize; + } + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_write) { + fprintf(stderr, + "Error during sftp aio upload: sftp_aio_begin_write() " + "requesting less bytes even when the number of bytes " + "asked to write are within the max write limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio upload: Insufficient memory\n"); + sftp_aio_free(aio); + goto error; + } + } + + sftp_close(file); + ms = elapsed_time(&ts); + *bps = (float)(8000 * total_bytes) / ms; + if (args->verbose > 0) { + fprintf(stdout, "Upload took %f ms for %zu bytes at %f bps.\n", + ms, total_bytes, *bps); + } + + ssh_list_free(aio_queue); + sftp_limits_free(li); + sftp_free(sftp); + return 0; + +error: + rc = ssh_get_error_code(session); + if (rc != SSH_NO_ERROR) { + fprintf(stderr, "Error during sftp aio upload: %s\n", + ssh_get_error(session)); + } + + /* Release aio structures corresponding to outstanding requests */ + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + sftp_aio_free(aio); + } + + ssh_list_free(aio_queue); + sftp_close(file); + sftp_limits_free(li); + sftp_free(sftp); + return -1; +} diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.c b/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.c new file mode 100644 index 000000000000..4229d5a5bde0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.c @@ -0,0 +1,494 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define LIBSSH_STATIC + +#include "config.h" +#include "benchmarks.h" +#include + +#include +#include +#include + +struct benchmark benchmarks[] = { + { + .name = "benchmark_raw_upload", + .fct = benchmarks_raw_up, + .enabled = 0 + }, + { + .name = "benchmark_raw_download", + .fct = benchmarks_raw_down, + .enabled = 0 + }, + { + .name = "benchmark_scp_upload", + .fct = benchmarks_scp_up, + .enabled = 0 + }, + { + .name = "benchmark_scp_download", + .fct = benchmarks_scp_down, + .enabled = 0 + }, +#ifdef WITH_SFTP + { + .name = "benchmark_sync_sftp_upload", + .fct = benchmarks_sync_sftp_up, + .enabled = 0 + }, + { + .name = "benchmark_sync_sftp_download", + .fct = benchmarks_sync_sftp_down, + .enabled = 0 + }, + { + .name="benchmark_async_sftp_download", + .fct=benchmarks_async_sftp_down, + .enabled=0 + }, + { + .name = "benchmark_async_sftp_aio_download", + .fct = benchmarks_async_sftp_aio_down, + .enabled = 0 + }, + { + .name = "benchmark_async_sftp_aio_upload", + .fct = benchmarks_async_sftp_aio_up, + .enabled = 0 + } +#endif /* WITH_SFTP */ +}; + +#ifdef HAVE_ARGP_H +#include + +const char *argp_program_version = "libssh benchmarks 2011-08-28"; +const char *argp_program_bug_address = "Aris Adamantiadis "; + +static char **cmdline = NULL; + +/* Program documentation. */ +static char doc[] = "libssh benchmarks"; + + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make libssh benchmark more verbose", + .group = 0 + }, + { + .name = "raw-upload", + .key = '1', + .arg = NULL, + .flags = 0, + .doc = "Upload raw data using channel", + .group = 0 + }, + { + .name = "raw-download", + .key = '2', + .arg = NULL, + .flags = 0, + .doc = "Download raw data using channel", + .group = 0 + }, + { + .name = "scp-upload", + .key = '3', + .arg = NULL, + .flags = 0, + .doc = "Upload data using SCP", + .group = 0 + }, + { + .name = "scp-download", + .key = '4', + .arg = NULL, + .flags = 0, + .doc = "Download data using SCP", + .group = 0 + }, + { + .name = "sync-sftp-upload", + .key = '5', + .arg = NULL, + .flags = 0, + .doc = "Upload data using synchronous SFTP", + .group = 0 + }, + { + .name = "sync-sftp-download", + .key = '6', + .arg = NULL, + .flags = 0, + .doc = "Download data using synchronous SFTP (slow)", + .group = 0 + }, + { + .name = "async-sftp-download", + .key = '7', + .arg = NULL, + .flags = 0, + .doc = "Download data using asynchronous SFTP (fast)", + .group = 0 + }, + { + .name = "async-sftp-aio-download", + .key = '8', + .arg = NULL, + .flags = 0, + .doc = "Download data using asynchronous SFTP AIO api (fast)", + .group = 0 + }, + { + .name = "async-sftp-aio-upload", + .key = '9', + .arg = NULL, + .flags = 0, + .doc = "Upload data using asynchronous SFTP AIO api (fast)", + .group = 0 + }, + { + .name = "host", + .key = 'h', + .arg = "HOST", + .flags = 0, + .doc = "Add a host to connect for benchmark (format user@hostname)", + .group = 0 + }, + { + .name = "size", + .key = 's', + .arg = "MBYTES", + .flags = 0, + .doc = "MBytes of data to send/receive per test", + .group = 0 + }, + { + .name = "chunk", + .key = 'c', + .arg = "bytes", + .flags = 0, + .doc = "size of data chunks to send/receive", + .group = 0 + }, + { + .name = "prequests", + .key = 'p', + .arg = "number [20]", + .flags = 0, + .doc = "[async SFTP] number of concurrent requests", + .group = 0 + }, + { + .name = "cipher", + .key = 'C', + .arg = "cipher", + .flags = 0, + .doc = "Cryptographic cipher to be used", + .group = 0 + }, + + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; + + /* arg is currently not used */ + (void) arg; + + switch (key) { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + benchmarks[key - '1'].enabled = 1; + arguments->ntests++; + break; + case 'v': + arguments->verbose++; + break; + case 's': + arguments->datasize = atoi(arg); + break; + case 'p': + arguments->concurrent_requests = atoi(arg); + break; + case 'c': + arguments->chunksize = atoi(arg); + break; + case 'C': + arguments->cipher = arg; + break; + case 'h': + if (arguments->nhosts >= MAX_HOSTS_CONNECT) { + fprintf(stderr, "Too much hosts\n"); + return ARGP_ERR_UNKNOWN; + } + + arguments->hosts[arguments->nhosts] = arg; + arguments->nhosts++; + break; + case ARGP_KEY_ARG: + /* End processing here. */ + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; + +#endif /* HAVE_ARGP_H */ + +static void cmdline_parse(int argc, char **argv, struct argument_s *arguments) +{ + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, arguments); +#else /* HAVE_ARGP_H */ + (void) argc; + (void) argv; + arguments->hosts[0] = "localhost"; + arguments->nhosts = 1; +#endif /* HAVE_ARGP_H */ +} + +static void arguments_init(struct argument_s *arguments) +{ + memset(arguments, 0, sizeof(*arguments)); + arguments->chunksize = 32758; + arguments->concurrent_requests = 20; + arguments->datasize = 10; +} + +static ssh_session connect_host(const char *host, int verbose, char *cipher) +{ + ssh_session session = NULL; + int rc; + + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Error connecting to \"%s\": %s\n", + host, "Unable to create a new ssh session"); + return NULL; + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, host); + if (rc < 0) + goto error; + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + if (rc < 0) + goto error; + + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + if (rc < 0) + goto error; + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + if (rc < 0) + goto error; + } + + rc = ssh_options_parse_config(session, NULL); + if (rc < 0) + goto error; + + rc = ssh_connect(session); + if (rc == SSH_ERROR) + goto error; + + rc = ssh_userauth_autopubkey(session, NULL); + if (rc != SSH_AUTH_SUCCESS) + goto error; + + return session; + +error: + fprintf(stderr, "Error connecting to \"%s\": %s\n", + host, ssh_get_error(session)); + ssh_free(session); + return NULL; +} + +static char *network_speed(float bps) +{ + static char buf[128]; + if (bps > 1000 * 1000 * 1000) { + /* Gbps */ + snprintf(buf, sizeof(buf), "%f Gbps", bps / (1000 * 1000 * 1000)); + } else if (bps > 1000 * 1000) { + /* Mbps */ + snprintf(buf, sizeof(buf), "%f Mbps", bps / (1000 * 1000)); + } else if (bps > 1000) { + snprintf(buf, sizeof(buf), "%f Kbps", bps / 1000); + } else { + snprintf(buf, sizeof(buf), "%f bps", bps); + } + + return buf; +} + +static void do_benchmarks(ssh_session session, struct argument_s *arguments, + const char *hostname) +{ + float ping_rtt = 0.0; + float ssh_rtt = 0.0; + float bps = 0.0; + int i; + int err; + struct benchmark *b = NULL; + + if (arguments->verbose > 0) + fprintf(stdout, "Testing ICMP RTT\n"); + + err = benchmarks_ping_latency(hostname, &ping_rtt); + if (err == 0) { + fprintf(stdout, "ping RTT : %f ms\n", ping_rtt); + } + + err = benchmarks_ssh_latency(session, &ssh_rtt); + if (err == 0) { + fprintf(stdout, + "SSH RTT : %f ms. Theoretical max BW (win=128K) : %s\n", + ssh_rtt, network_speed(128000.0 / (ssh_rtt / 1000.0))); + } + + for (i=0; i < BENCHMARK_NUMBER; ++i){ + b = &benchmarks[i]; + if (b->enabled) { + err=b->fct(session, arguments, &bps); + + if (err == 0) { + fprintf(stdout, + "%s : %s : %s\n", + hostname, b->name, network_speed(bps)); + } + } + } +} + +char *buffer = NULL; + +int main(int argc, char **argv) +{ + struct argument_s arguments; + ssh_session session = NULL; + int i, r; + + arguments_init(&arguments); + cmdline_parse(argc, argv, &arguments); + if (arguments.nhosts == 0) { + fprintf(stderr, "At least one host (-h) must be specified\n"); + return EXIT_FAILURE; + } + + if (arguments.ntests == 0) { + for (i=0; i < BENCHMARK_NUMBER; ++i) { + benchmarks[i].enabled = 1; + } + arguments.ntests = BENCHMARK_NUMBER; + } + + buffer = malloc(arguments.chunksize > 1024 ? arguments.chunksize : 1024); + if (buffer == NULL) { + fprintf(stderr, "Allocation of chunk buffer failed\n"); + return EXIT_FAILURE; + } + + if (arguments.verbose > 0) { + fprintf(stdout, "Will try hosts "); + for (i=0; i < arguments.nhosts; ++i) { + fprintf(stdout, "\"%s\" ", arguments.hosts[i]); + } + + fprintf(stdout, "with benchmarks "); + for (i = 0; i < BENCHMARK_NUMBER; ++i) { + if (benchmarks[i].enabled) + fprintf(stdout, "\"%s\" ", benchmarks[i].name); + } + + fprintf(stdout,"\n"); + } + + r = ssh_init(); + if (r == SSH_ERROR) { + fprintf(stderr, "Failed to initialize libssh\n"); + return EXIT_FAILURE; + } + + for (i = 0; i < arguments.nhosts; ++i) { + if (arguments.verbose > 0) + fprintf(stdout, "Connecting to \"%s\"...\n", arguments.hosts[i]); + + session = connect_host(arguments.hosts[i], + arguments.verbose, + arguments.cipher); + if (session != NULL && arguments.verbose > 0) + fprintf(stdout, "Success\n"); + + if (session == NULL) { + fprintf(stderr, "Errors occurred, stopping\n"); + return EXIT_FAILURE; + } + + do_benchmarks(session, &arguments, arguments.hosts[i]); + ssh_disconnect(session); + ssh_free(session); + } + + r = ssh_finalize(); + if (r == SSH_ERROR) { + fprintf(stderr, "Failed to finalize libssh\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.h b/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.h new file mode 100644 index 000000000000..5f54a950d90f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.h @@ -0,0 +1,105 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef BENCHMARKS_H_ +#define BENCHMARKS_H_ + +#include + +/* benchmarks.c */ + +/* maximum number of parallel hosts that may be checked */ +#define MAX_HOSTS_CONNECT 20 + +enum libssh_benchmarks { + BENCHMARK_RAW_UPLOAD=0, + BENCHMARK_RAW_DOWNLOAD, + BENCHMARK_SCP_UPLOAD, + BENCHMARK_SCP_DOWNLOAD, + BENCHMARK_SYNC_SFTP_UPLOAD, + BENCHMARK_SYNC_SFTP_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_AIO_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_AIO_UPLOAD, + BENCHMARK_NUMBER +}; + +struct argument_s { + const char *hosts[MAX_HOSTS_CONNECT]; + int verbose; + int nhosts; + int ntests; + unsigned int datasize; + unsigned int chunksize; + int concurrent_requests; + char *cipher; +}; + +extern char *buffer; + +typedef int (*bench_fct)(ssh_session session, struct argument_s *args, + float *bps); + +struct benchmark { + const char *name; + bench_fct fct; + int enabled; +}; + +/* latency.c */ + +struct timestamp_struct { + struct timeval timestamp; +}; + +int benchmarks_ping_latency (const char *host, float *average); +int benchmarks_ssh_latency (ssh_session session, float *average); + +void timestamp_init(struct timestamp_struct *ts); +float elapsed_time(struct timestamp_struct *ts); + +/* bench_raw.c */ + +int benchmarks_raw_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_raw_down (ssh_session session, struct argument_s *args, + float *bps); + +/* bench_scp.c */ + +int benchmarks_scp_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_scp_down (ssh_session session, struct argument_s *args, + float *bps); + +/* bench_sftp.c */ + +int benchmarks_sync_sftp_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_sync_sftp_down (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_async_sftp_aio_down(ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_async_sftp_aio_up(ssh_session session, struct argument_s *args, + float *bps); +#endif /* BENCHMARKS_H_ */ diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/latency.c b/src/libs/libssh-0.12.2/tests/benchmarks/latency.c new file mode 100644 index 000000000000..09c50a0474a5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/latency.c @@ -0,0 +1,148 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include + +#include +#include +#include +#include +#include + +#define PING_PROGRAM "/bin/ping" + +/** @internal + * @brief Calculates the RTT of the host with ICMP ping, and returns the + * average of the calculated RTT. + * @param[in] host hostname to ping. + * @param[out] average average RTT in milliseconds. + * @returns 0 on success, -1 if there is an error. + * @warning relies on an external ping program which may not exist on + * certain OS. + */ +int benchmarks_ping_latency (const char *host, float *average){ + const char *ptr; + char cmd[256]; + char line[1024]; + FILE *fd; + int found=0; + + /* strip out the hostname */ + ptr=strchr(host,'@'); + if(ptr) + ptr++; + else + ptr=host; + + snprintf(cmd,sizeof(cmd),"%s -n -q -c3 %s",PING_PROGRAM, ptr); + fd=popen(cmd,"r"); + if(fd==NULL){ + fprintf(stderr,"Error executing command : %s\n", strerror(errno)); + return -1; + } + + while(!found && fgets(line,sizeof(line),fd)!=NULL){ + if(strstr(line,"rtt")){ + ptr=strchr(line,'='); + if(ptr==NULL) + goto parseerror; + ptr=strchr(ptr,'/'); + if(ptr==NULL) + goto parseerror; + *average=strtof(ptr+1,NULL); + found=1; + break; + } + } + if(!found) + goto parseerror; + pclose(fd); + return 0; + +parseerror: + fprintf(stderr,"Parse error : couldn't locate average in %s",line); + pclose(fd); + return -1; +} + +/** @internal + * @brief initialize a timestamp to the current time. + * @param[out] ts A timestamp_struct pointer. + */ +void timestamp_init(struct timestamp_struct *ts){ + gettimeofday(&ts->timestamp,NULL); +} + +/** @internal + * @brief return the elapsed time since now and the moment ts was initialized. + * @param[in] ts An initialized timestamp_struct pointer. + * @return Elapsed time in milliseconds. + */ +float elapsed_time(struct timestamp_struct *ts){ + struct timeval now; + time_t secdiff; + long usecdiff; /* may be negative */ + + gettimeofday(&now,NULL); + secdiff=now.tv_sec - ts->timestamp.tv_sec; + usecdiff=now.tv_usec - ts->timestamp.tv_usec; + //printf("%d sec diff, %d usec diff\n",secdiff, usecdiff); + return (float) (secdiff*1000) + ((float)usecdiff)/1000; +} + +/** @internal + * @brief Calculates the RTT of the host with SSH channel operations, and + * returns the average of the calculated RTT. + * @param[in] session active SSH session to test. + * @param[out] average average RTT in milliseconds. + * @returns 0 on success, -1 if there is an error. + */ +int benchmarks_ssh_latency(ssh_session session, float *average){ + float times[3]; + struct timestamp_struct ts; + int i; + ssh_channel channel; + channel=ssh_channel_new(session); + if(channel==NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + + for(i=0;i<3;++i){ + timestamp_init(&ts); + if(ssh_channel_request_env(channel,"TEST","test")==SSH_ERROR && + ssh_get_error_code(session)==SSH_FATAL) + goto error; + times[i]=elapsed_time(&ts); + } + ssh_channel_close(channel); + ssh_channel_free(channel); + channel=NULL; + printf("SSH request times : %f ms ; %f ms ; %f ms\n", times[0], times[1], times[2]); + *average=(times[0]+times[1]+times[2])/3; + return 0; +error: + fprintf(stderr,"Error calculating SSH latency : %s\n",ssh_get_error(session)); + if(channel) + ssh_channel_free(channel); + return -1; +} diff --git a/src/libs/libssh-0.12.2/tests/chmodtest.c b/src/libs/libssh-0.12.2/tests/chmodtest.c new file mode 100644 index 000000000000..60ac6a5bfb5f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/chmodtest.c @@ -0,0 +1,33 @@ +#include + +#include +#include "examples_common.h" +#include + +int main(void) { + ssh_session session; + sftp_session sftp; + char buffer[1024*1024]; + int rc; + + session = connect_ssh("localhost", NULL, NULL, 0); + if (session == NULL) { + return 1; + } + + sftp=sftp_new(session); + sftp_init(sftp); + rc=sftp_rename(sftp,"/tmp/test","/tmp/test"); + rc=sftp_rename(sftp,"/tmp/test","/tmp/test"); + rc=sftp_chmod(sftp,"/tmp/test",0644); + if (rc < 0) { + printf("error : %s\n",ssh_get_error(sftp)); + + ssh_disconnect(session); + return 1; + } + + ssh_disconnect(session); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/chroot_wrapper.c b/src/libs/libssh-0.12.2/tests/chroot_wrapper.c new file mode 100644 index 000000000000..3545f3975640 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/chroot_wrapper.c @@ -0,0 +1,8 @@ +/* silent gcc */ +int chroot(const char *); + +int chroot(const char *path) +{ + (void)path; + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/client/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/client/CMakeLists.txt new file mode 100644 index 000000000000..b14b91ffc38d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/CMakeLists.txt @@ -0,0 +1,109 @@ +project(clienttests C) + +find_package(socket_wrapper) + +set(LIBSSH_CLIENT_TESTS + torture_algorithms + torture_auth + torture_auth_cert + torture_auth_agent_forwarding + torture_client_callbacks + torture_client_config + torture_connect + torture_hostkey + torture_rekey + torture_forward + torture_knownhosts + torture_knownhosts_verify + torture_proxycommand + torture_session + torture_request_env + torture_request_pty_modes + torture_client_global_requests + torture_get_kex_algo + ) + +find_program(SCP_EXECUTABLE NAMES scp) +if (SCP_EXECUTABLE) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_scp) +endif() + +if (WITH_PKCS11_URI) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_auth_pkcs11) +endif() + +if (HAVE_PTHREAD) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_proxyjump) +endif() + +if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_gssapi_auth + torture_gssapi_key_exchange + torture_gssapi_key_exchange_null) +endif() + +if (DEFAULT_C_NO_DEPRECATION_FLAGS) + set_source_files_properties(torture_knownhosts.c + PROPERTIES + COMPILE_FLAGS ${DEFAULT_C_NO_DEPRECATION_FLAGS}) +endif() + +if (WITH_SFTP) + if (WITH_BENCHMARKS) + set(SFTP_BENCHMARK_TESTS + torture_sftp_benchmark) + endif() + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_sftp_init + torture_sftp_ext + torture_sftp_canonicalize_path + torture_sftp_dir + torture_sftp_read + torture_sftp_fsync + torture_sftp_hardlink + torture_sftp_limits + torture_sftp_rename + torture_sftp_expand_path + torture_sftp_aio + torture_sftp_home_directory + torture_sftp_setstat + torture_sftp_packet_read + torture_sftp_recv_response_msg + torture_sftp_get_users_groups_by_id + torture_sftp_request_id + ${SFTP_BENCHMARK_TESTS}) +endif (WITH_SFTP) + +set(TORTURE_CLIENT_ENVIRONMENT ${TORTURE_ENVIRONMENT}) +list(APPEND TORTURE_CLIENT_ENVIRONMENT NSS_WRAPPER_HOSTS=${CMAKE_BINARY_DIR}/tests/etc/hosts) + +foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS}) + add_cmocka_test(${_CLI_TEST} + SOURCES ${_CLI_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TORTURE_LIBRARY} util + ) + + if (OSX) + set_property( + TEST + ${_CLI_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${SOCKET_WRAPPER_LIBRARY}) + else () + set_property( + TEST + ${_CLI_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_CLIENT_ENVIRONMENT}) + endif() +endforeach() diff --git a/src/libs/libssh-0.12.2/tests/client/torture_algorithms.c b/src/libs/libssh-0.12.2/tests/client/torture_algorithms.c new file mode 100644 index 000000000000..f0f8ac056237 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_algorithms.c @@ -0,0 +1,1128 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int sshd_setup_hmac(void **state) +{ + torture_setup_sshd_server(state, false); + /* Set MAC to be something other than what the client will offer */ + torture_update_sshd_config(state, "MACs hmac-sha2-512"); + + return 0; +} + + +static int session_setup(void **state) { + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool false_v = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Prevent parsing configuration files that can introduce different + * algorithms then we want to test */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &false_v); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void test_algorithm(ssh_session session, + const char *kex, + const char *cipher, + const char *hmac) { + int rc; + char data[256]; + size_t len_to_test[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 10, + 12, 15, 16, 20, + 31, 32, 33, + 63, 64, 65, + 100, 127, 128 + }; + unsigned int i; + + if (kex != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + assert_ssh_return_code(session, rc); + } + + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + assert_ssh_return_code(session, rc); + } + + if (hmac != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, hmac); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, hmac); + assert_ssh_return_code(session, rc); + } + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* send ignore packets of all sizes */ + memset(data, 0, sizeof(data)); + for (i = 0; i < (sizeof(len_to_test) / sizeof(size_t)); i++) { + memset(data, 'A', len_to_test[i]); + ssh_send_ignore(session, data); + ssh_handle_packets(session, 50); + } + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +static void torture_algorithms_aes128_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha1"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha1"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha1"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha1"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-256"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-512"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha1"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-256"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-512"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha1"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-256"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-512"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes128_gcm(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-gcm@openssh.com", NULL); +} + +static void torture_algorithms_aes256_gcm(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-gcm@openssh.com", NULL); +} + +static void torture_algorithms_aes128_gcm_mac(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-gcm@openssh.com", "hmac-sha1"); +} + +static void torture_algorithms_aes256_gcm_mac(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-gcm@openssh.com", "hmac-sha1"); +} + +static void torture_algorithms_3des_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha1"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_3des_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +#if defined(HAVE_BLOWFISH) && defined(OPENSSH_BLOWFISH_CBC) +static void torture_algorithms_blowfish_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha1"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-512-etm@openssh.com"); +} +#endif /* HAVE_BLOWFISH && defined(OPENSSH_BLOWFISH_CBC) */ + +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM +static void torture_algorithms_chacha20_poly1305(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /*kex*/ + "chacha20-poly1305@openssh.com", + NULL); +} +static void torture_algorithms_chacha20_poly1305_mac(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /*kex*/ + "chacha20-poly1305@openssh.com", + "hmac-sha1"); /* different from the server */ +} +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ + +static void torture_algorithms_zlib(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib"); +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, SSH_OK); + } +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib"); +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, SSH_OK); + } +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_connect(session); +#ifdef WITH_ZLIB + if (!ssh_fips_mode()) { + if (ssh_get_openssh_version(session)) { + assert_false(rc == SSH_OK); + ssh_disconnect(session); + return; + } + } +#endif + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +static void torture_algorithms_zlib_openssh(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib@openssh.com"); +#ifdef WITH_ZLIB + assert_int_equal(rc, SSH_OK); +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib@openssh.com"); +#ifdef WITH_ZLIB + assert_int_equal(rc, SSH_OK); +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_connect(session); +#ifdef WITH_ZLIB + if (ssh_get_openssh_version(session)) { + assert_true(rc==SSH_OK); + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + ssh_disconnect(session); + return; + } + assert_false(rc == SSH_OK); +#else + assert_int_equal(rc, SSH_OK); +#endif + + ssh_disconnect(session); +} + +#if defined(HAVE_ECC) +static void torture_algorithms_ecdh_sha2_nistp256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "ecdh-sha2-nistp256", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_ecdh_sha2_nistp384(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "ecdh-sha2-nistp384", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_ecdh_sha2_nistp521(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "ecdh-sha2-nistp521", NULL/*cipher*/, NULL/*hmac*/); +} +#endif + +#ifdef OPENSSH_CURVE25519_SHA256 +static void torture_algorithms_ecdh_curve25519_sha256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "curve25519-sha256", NULL/*cipher*/, NULL/*hmac*/); +} +#endif /* OPENSSH_CURVE25519_SHA256 */ + +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG +static void torture_algorithms_ecdh_curve25519_sha256_libssh_org(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "curve25519-sha256@libssh.org", NULL/*cipher*/, NULL/*hmac*/); +} +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM +static void +torture_algorithms_ecdh_sntrup761x25519_sha512_openssh_com(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512@openssh.com", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512 +static void +torture_algorithms_ecdh_sntrup761x25519_sha512(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ + +#if defined(OPENSSH_MLKEM768X25519_SHA256) +static void torture_algorithms_ecdh_mlkem768x25519_sha256(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "mlkem768x25519-sha256", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* defined(OPENSSH_MLKEM768X25519_SHA256) */ + +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) +static void torture_algorithms_ecdh_mlkem768nistp256_sha256(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, + "mlkem768nistp256-sha256", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* defined(OPENSSH_MLKEM768NISTP256_SHA256) */ + +#if defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) +static void torture_algorithms_ecdh_mlkem1024nistp384_sha384(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, + "mlkem1024nistp384-sha384", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* HAVE_MLKEM1024 && defined(OPENSSH_MLKEM1024NISTP384_SHA384) */ + +static void torture_algorithms_dh_group1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "diffie-hellman-group1-sha1", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group14(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "diffie-hellman-group14-sha1", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group14_sha256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "diffie-hellman-group14-sha256", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group16(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "diffie-hellman-group16-sha512", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group18(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "diffie-hellman-group18-sha512", NULL/*cipher*/, NULL/*hmac*/); +} + +#ifdef WITH_GEX +static void torture_algorithms_dh_gex_sha1(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "diffie-hellman-group-exchange-sha1", + NULL, /* cipher */ + NULL); /* hmac */ +} + +static void torture_algorithms_dh_gex_sha256(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, + "diffie-hellman-group-exchange-sha256", + NULL, /* cipher */ + NULL); /* hmac */ +} +#endif /* WITH_GEX */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_gcm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_gcm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), +#if defined(HAVE_BLOWFISH) && defined(OPENSSH_BLOWFISH_CBC) + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), +#endif /* HAVE_BLOWFISH_CIPHER && defined(OPENSSH_BLOWFISH_CBC) */ +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_algorithms_chacha20_poly1305, + session_setup, + session_teardown), +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ + cmocka_unit_test_setup_teardown(torture_algorithms_zlib, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_zlib_openssh, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group14, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group16, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group18, + session_setup, + session_teardown), +#ifdef WITH_GEX + cmocka_unit_test_setup_teardown(torture_algorithms_dh_gex_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_gex_sha256, + session_setup, + session_teardown), +#endif /* WITH_GEX */ +#ifdef OPENSSH_CURVE25519_SHA256 + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_curve25519_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256 */ +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_curve25519_sha256_libssh_org, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sntrup761x25519_sha512_openssh_com, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512 + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sntrup761x25519_sha512, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ +#if defined(OPENSSH_MLKEM768X25519_SHA256) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem768x25519_sha256, + session_setup, + session_teardown), +#endif /* defined(OPENSSH_MLKEM768X25519_SHA256) */ +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem768nistp256_sha256, + session_setup, + session_teardown), +#endif /* defined(OPENSSH_MLKEM768NISTP256_SHA256) */ +#if defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem1024nistp384_sha384, + session_setup, + session_teardown), +#endif /* defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) */ +#if defined(HAVE_ECC) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp384, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp521, + session_setup, + session_teardown), +#endif + }; + + struct CMUnitTest tests_hmac[] = { + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_gcm_mac, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_gcm_mac, + session_setup, + session_teardown), +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_algorithms_chacha20_poly1305_mac, + session_setup, + session_teardown), +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + if (rc != 0) { + return rc; + } + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests_hmac, sshd_setup_hmac, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth.c b/src/libs/libssh-0.12.2/tests/client/torture_auth.c new file mode 100644 index 000000000000..99d59b94b124 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth.c @@ -0,0 +1,1473 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#include "torture_auth_common.c" + +#ifdef WITH_FIDO2 +#include "torture_sk.h" +#endif + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, true); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + const char *all_keytypes = NULL; + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Enable all hostkeys */ + all_keytypes = ssh_get_supported_methods(SSH_HOSTKEYS); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, all_keytypes); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int pubkey_setup(void **state) +{ + int rc; + + rc = session_setup(state); + if (rc != 0) { + return rc; + } + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int agent_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + char ssh_key_path[1024]; + int rc; + + rc = pubkey_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + /* Use the common function to set up the SSH agent with Bob's key */ + snprintf(ssh_key_path, sizeof(ssh_key_path), "%s/.ssh/id_rsa", pwd->pw_dir); + rc = torture_setup_ssh_agent(s, ssh_key_path); + if (rc != 0) { + return rc; + } + + return 0; +} + +static int agent_teardown(void **state) +{ + int rc; + + rc = session_teardown(state); + if (rc != 0) { + return rc; + } + + /* Use the common function to clean up the SSH agent */ + rc = torture_cleanup_ssh_agent(); + if (rc != 0) { + return rc; + } + + return 0; +} + +static void torture_auth_none(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } +} + +static void torture_auth_none_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + ssh_set_blocking(session,0); + + do { + rc = ssh_userauth_none(session,NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + +} + +/* Setting MaxAuthTries 0 makes libssh hang. The option is not practical, + * but simulates setting low value and requiring multiple authentication + * methods to succeed (T233) + */ +static void torture_auth_none_max_tries(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + const char *sshd_config = "MaxAuthTries 0"; + + torture_update_sshd_config(state, sshd_config); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + /* Reset config back to defaults */ + torture_update_sshd_config(state, ""); +} + + +static void torture_auth_pubkey(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* negative tests */ + rc = ssh_userauth_try_publickey(NULL, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_try_publickey(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + + rc = ssh_userauth_try_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* negative tests */ + rc = ssh_userauth_publickey(NULL, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_publickey(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_none(session,NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_try_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_autopubkey(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +struct torture_auth_autopubkey_protected_data { + ssh_session session; + int n_calls; +}; + +static int +torture_auth_autopubkey_protected_auth_function (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata) +{ + int rc; + char *id, *expected_id; + struct torture_auth_autopubkey_protected_data *data = userdata; + + assert_true(prompt != NULL); + assert_int_equal(echo, 0); + assert_int_equal(verify, 0); + + expected_id = ssh_path_expand_escape(data->session, "%d/.ssh/id_rsa_protected"); + assert_true(expected_id != NULL); + + rc = ssh_userauth_publickey_auto_get_current_identity(data->session, &id); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(expected_id, id); + + ssh_string_free_char(id); + ssh_string_free_char(expected_id); + + data->n_calls += 1; + strncpy(buf, "secret", len); + return 0; +} + +static void torture_auth_autopubkey_protected(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char *id; + int rc; + + struct torture_auth_autopubkey_protected_data data = { + .session = session, + .n_calls = 0 + }; + + struct ssh_callbacks_struct callbacks = { + .userdata = &data, + .auth_function = torture_auth_autopubkey_protected_auth_function + }; + + /* no session pointer */ + rc = ssh_userauth_publickey_auto_get_current_identity(NULL, &id); + assert_int_equal(rc, SSH_ERROR); + + /* no result pointer */ + rc = ssh_userauth_publickey_auto_get_current_identity(session, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* no auto auth going on */ + rc = ssh_userauth_publickey_auto_get_current_identity(session, &id); + assert_int_equal(rc, SSH_ERROR); + + ssh_callbacks_init(&callbacks); + ssh_set_callbacks(session, &callbacks); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Try id_rsa_protected first. + */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "%d/.ssh/id_rsa_protected"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + assert_int_equal (data.n_calls, 1); +} + +static void torture_auth_autopubkey_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session,0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void +torture_auth_kbdint(void **state, + const char *password, + enum ssh_auth_e res) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + + rc = ssh_userauth_kbdint_setanswer(session, 0, password); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + /* Sometimes, SSH server send an empty query at the end of exchange */ + if (rc == SSH_AUTH_INFO) { + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + assert_int_equal(rc, res); +} + +static void +torture_auth_kbdint_good(void **state) +{ + torture_auth_kbdint(state, TORTURE_SSH_USER_BOB_PASSWORD, SSH_AUTH_SUCCESS); +} + +static void +torture_auth_kbdint_bad(void **state) +{ + torture_auth_kbdint(state, "bad password stample", SSH_AUTH_DENIED); +} + +static void +torture_auth_kbdint_nonblocking(void **state, + const char *password, + enum ssh_auth_e res) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + rc = ssh_userauth_kbdint_setanswer(session, 0, password); + assert_false(rc < 0); + + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* Sometimes, SSH server send an empty query at the end of exchange */ + if (rc == SSH_AUTH_INFO) { + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + } + assert_int_equal(rc, res); +} + +static void +torture_auth_kbdint_nonblocking_good(void **state) +{ + torture_auth_kbdint_nonblocking(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); +} + +static void +torture_auth_kbdint_nonblocking_bad(void **state) +{ + torture_auth_kbdint_nonblocking(state, + "bad password stample", + SSH_AUTH_DENIED); +} + +static void +torture_auth_password(void **state, const char *password, enum ssh_auth_e res) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + rc = ssh_userauth_password(session, NULL, password); + assert_int_equal(rc, res); +} + +static void +torture_auth_password_good(void **state) +{ + torture_auth_password(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); +} + +static void +torture_auth_password_bad(void **state) +{ + torture_auth_password(state, "bad password stample", SSH_AUTH_DENIED); +} + +static void +torture_auth_password_nonblocking(void **state, + const char *password, + enum ssh_auth_e res) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session,0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + do { + rc = ssh_userauth_password(session, NULL, password); + } while (rc == SSH_AUTH_AGAIN); + + assert_int_equal(rc, res); +} + +static void +torture_auth_password_nonblocking_good(void **state) +{ + torture_auth_password_nonblocking(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); +} + +static void +torture_auth_password_nonblocking_bad(void **state) +{ + torture_auth_password_nonblocking(state, + "bad password stample", + SSH_AUTH_DENIED); +} + +/* TODO cover the case: + * * when there is accompanying certificate (identities only + agent) + * * export private key to public key during _auto() authentication. + * this needs to be a encrypted private key in PEM format without + * accompanying public key. + */ +static void torture_auth_agent_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + struct passwd *pwd = NULL; + int rc; + bool identities_only = true; + char *id = NULL; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key */ + rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_agent_identities_only_protected(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + struct passwd *pwd; + int rc; + bool identities_only = true; + char *id = NULL; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa_protected", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key */ + rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_pubkey_types(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Disable RSA key types for authentication */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp384"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Now enable it and retry */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "rsa-sha2-512,ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_pubkey_types_ecdsa(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* We have only the 256b key -- allowlisting only larger should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp384"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ECDSA keys with their various names */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp256"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +static void torture_auth_pubkey_types_ed25519(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_ed25519", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Import the ED25519 private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* Enable only RSA keys -- authentication should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ed25519 keys */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_types_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Disable RSA key types for authentication */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Now enable it and retry */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "rsa-sha2-512,ssh-rsa"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +static void torture_auth_pubkey_types_ecdsa_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* We have only the 256b key -- allowlisting only larger should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp384"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ECDSA key to authenticate */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp256"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +static void torture_auth_pubkey_types_ed25519_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_ed25519", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Import the ED25519 private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* Enable only RSA keys -- authentication should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ED25519 key to authenticate */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +#ifdef WITH_FIDO2 + +static void torture_auth_pubkey_types_sk_key(void **state, + enum ssh_keytypes_e key_type) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *privkey_file_name = NULL; + const char *key_type_str = NULL; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + const struct ssh_sk_callbacks_struct *sk_dummy_callbacks = NULL; + ssh_pki_ctx pki_context = NULL; + int rc; + + /* Conditions to skip the test */ + sk_dummy_callbacks = torture_get_sk_dummy_callbacks(); + if (sk_dummy_callbacks == NULL) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Key type specific setup */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + privkey_file_name = "id_ecdsa_sk"; + key_type_str = "sk-ecdsa-sha2-nistp256@openssh.com"; + break; + + case SSH_KEYTYPE_SK_ED25519: + privkey_file_name = "id_ed25519_sk"; + key_type_str = "sk-ssh-ed25519@openssh.com"; + break; + + default: + /* should never reach here */ + assert_true(0); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/%s", + pwd->pw_dir, + privkey_file_name); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + pki_context = ssh_pki_ctx_new(); + assert_non_null(pki_context); + + rc = ssh_pki_ctx_options_set(pki_context, + SSH_PKI_OPTION_SK_CALLBACKS, + sk_dummy_callbacks); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_PKI_CONTEXT, pki_context); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Import the private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* Enable only RSA keys -- authentication should fail */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use the SK key */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + key_type_str); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); + SSH_PKI_CTX_FREE(pki_context); +} + +static void torture_auth_pubkey_types_sk_ecdsa(void **state) +{ + torture_auth_pubkey_types_sk_key(state, SSH_KEYTYPE_SK_ECDSA); +} + +static void torture_auth_pubkey_types_sk_ed25519(void **state) +{ + torture_auth_pubkey_types_sk_key(state, SSH_KEYTYPE_SK_ED25519); +} + +#endif /* WITH_FIDO2 */ + +static void torture_auth_pubkey_rsa_key_size(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_rsa_key_size_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_skip_none(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Skip the ssh_userauth_none() here */ + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_none, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_none_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_none_max_tries, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_protected, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_nonblocking, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only_protected, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking, + pubkey_setup, + session_teardown), +#ifdef WITH_FIDO2 + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_sk_ecdsa, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_sk_ed25519, + pubkey_setup, + session_teardown), +#endif /* WITH_FIDO2 */ + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_skip_none, + pubkey_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth_agent_forwarding.c b/src/libs/libssh-0.12.2/tests/client/torture_auth_agent_forwarding.c new file mode 100644 index 000000000000..cdde532849cb --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth_agent_forwarding.c @@ -0,0 +1,369 @@ +#include "config.h" + +#if !defined(_WIN32) || (defined(WITH_SERVER) && defined(HAVE_PTHREAD)) + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include /* For calloc/free */ + +#include "libssh/callbacks.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" + +#include +#include +#include +#include +#include /* usleep */ + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +/* struct to store the state of the test */ +struct agent_callback_state { + int called; + ssh_session expected_session; + ssh_channel created_channel; +}; + +/* Agent callback function that will be triggered when a channel open request is + * received */ +static ssh_channel agent_callback(ssh_session session, void *userdata) +{ + struct agent_callback_state *state = + (struct agent_callback_state *)userdata; + ssh_channel channel = NULL; /* Initialize to NULL */ + + /* Increment call counter */ + state->called++; + + /* Verify session matches what we expect */ + assert_ptr_equal(session, state->expected_session); + + /* Create a new channel for agent forwarding */ + channel = ssh_channel_new(session); + if (channel == NULL) { + return NULL; + } + + /* Make the channel non-blocking */ + ssh_channel_set_blocking(channel, 0); + + /* Store the channel for verification and later cleanup */ + state->created_channel = channel; + + return channel; +} + +static int sshd_setup_agent_forwarding(void **state) +{ + int rc; + + /* Use the standard server setup function */ + torture_setup_sshd_server(state, false); + + /* Override the default configuration with our own, adding agent forwarding + * support */ + rc = torture_update_sshd_config(state, "AllowAgentForwarding yes\n"); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +/* Only free the session - nothing else */ +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + if (s != NULL && s->ssh.ssh.session != NULL) { + /* Clean up callback resources first */ + if (s->ssh.ssh.cb_state != NULL) { + struct agent_callback_state *cb_state = s->ssh.ssh.cb_state; + + /* Close and free any open channel from the callback */ + if (cb_state->created_channel != NULL) { + ssh_channel_close(cb_state->created_channel); + ssh_channel_free(cb_state->created_channel); + } + + free(cb_state); + s->ssh.ssh.cb_state = NULL; + } + + if (s->ssh.ssh.callbacks != NULL) { + free(s->ssh.ssh.callbacks); + s->ssh.ssh.callbacks = NULL; + } + + /* Disconnect and free the session */ + ssh_disconnect(s->ssh.ssh.session); + ssh_free(s->ssh.ssh.session); + s->ssh.ssh.session = NULL; + } + + return 0; +} + +static int torture_teardown_ssh_agent(void **state) +{ + struct torture_state *s = *state; + int rc; + + if (s == NULL) { + return 0; + } + + /* Kill the SSH agent */ + rc = torture_cleanup_ssh_agent(); + assert_return_code(rc, errno); + + /* Use the standard teardown function which will properly clean up */ + torture_teardown_sshd_server(state); + + return 0; +} + +/* Test function to verify if agent forwarding callback works */ +static void torture_auth_agent_forwarding(void **state) +{ + struct torture_state *s = *state; + struct agent_callback_state *cb_state; + ssh_session session = NULL; + ssh_channel channel = NULL; /* Initialize to NULL */ + int rc; + int port = torture_server_port(); + char buffer[4096] = {0}; + int nbytes; + int max_read_attempts = 10; /* Limit the number of read attempts */ + int read_count = 0; + bool agent_available = false; + bool agent_not_available_found = false; + size_t exp_socket_len; + + /* The forwarded agent socket is created under the home directory, which + * might easily extend the maximum unix domain socket path length. + * If we see this, just skip the test as it will not work */ + exp_socket_len = strlen(BINARYDIR) + + strlen("/home/bob/.ssh/agent.1234567890.sshd.XXXXXXXXXX"); + if (exp_socket_len > UNIX_PATH_MAX) { + SSH_LOG(SSH_LOG_WARNING, + "The working directory is too long for agent forwarding to work" + ": Skipping the test"); + skip(); + } + + assert_non_null(s); + session = s->ssh.ssh.session; + assert_non_null(session); + + /* Get our callback state */ + cb_state = (struct agent_callback_state *)s->ssh.ssh.cb_state; + assert_non_null(cb_state); + + /* Set username */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + /* Set server address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + /* Set port */ + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &port); + assert_ssh_return_code(session, rc); + + /* Connect to server */ + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Authenticate */ + rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Create a single channel that we'll use for all tests */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Request agent forwarding */ + rc = ssh_channel_request_auth_agent(channel); + assert_ssh_return_code(session, rc); + + /* Running a command that will try to use the SSH agent */ + rc = ssh_channel_request_exec( + channel, + "echo 'Simple command'; " + "echo 'ENV SSH_AUTH_SOCK=>['$SSH_AUTH_SOCK']<'; " /* Use boundary + markers */ + "ssh-add -l || echo 'Agent not available'; " + "echo 'Done'"); /* Marker for command completion */ + assert_ssh_return_code(session, rc); + + /* Set to non-blocking mode with manual timeout implementation + * This prevents the test from hanging indefinitely if there's an issue with + * the channel communication. We implement our own timeout logic using a + * counter and sleep, which gives the server time to process our request + * while still ensuring the test will eventually terminate even if no EOF is + * received. + */ + ssh_channel_set_blocking(channel, 0); + + /* Read with safety counter to prevent infinite loops */ + while (!ssh_channel_is_eof(channel) && read_count < max_read_attempts) { + nbytes = ssh_channel_read_nonblocking(channel, + buffer, + sizeof(buffer) - 1, + 0); + + if (nbytes > 0) { + buffer[nbytes] = 0; + ssh_log_hexdump("Read bytes:", (unsigned char *)buffer, nbytes); + + /* Process the command output to check for three key conditions: + * 1. If SSH_AUTH_SOCK is properly set (meaning agent forwarding + * works) + * 2. If "Agent not available" message appears (indicating failure) + * 3. If we've seen the "Done" marker (to know when to stop reading) + */ + /* Check if SSH_AUTH_SOCK has a non-empty value by looking for + * boundary markers with content between them */ + if (strstr(buffer, "ENV SSH_AUTH_SOCK=>[") != NULL && + strstr(buffer, "]<") != NULL && + strstr(buffer, "ENV SSH_AUTH_SOCK=>[]<") == NULL) { + agent_available = true; + } + + if (strstr(buffer, "Agent not available") != NULL) { + agent_not_available_found = true; + } + + if (strstr(buffer, "Done") != NULL) { + break; + } + } else if (nbytes == SSH_ERROR) { + break; + } else if (nbytes == SSH_EOF) { + break; + } + + /* Short sleep between reads to avoid spinning */ + usleep(100000); /* 100ms */ + read_count++; + } + + /* Trying to read from stderr as well */ + ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer) - 1, 1); + + /* Close the channel */ + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + /* Verify agent forwarding worked correctly */ + + /* Verify callback was called exactly once */ + assert_int_equal(cb_state->called, 1); + + /* Verify "Agent not available" was not found + * The agent should be available - we should never see "Agent not available" + * output + */ + assert_false(agent_not_available_found); + + /* Verify SSH_AUTH_SOCK is set */ + assert_true(agent_available); + + /* Any channel created in the callback is freed */ + if (cb_state->created_channel) { + ssh_channel_close(cb_state->created_channel); + ssh_channel_free(cb_state->created_channel); + cb_state->created_channel = NULL; + } +} + +/* Session setup function that configures SSH agent */ +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct agent_callback_state *cb_state = NULL; + struct ssh_callbacks_struct *callbacks = NULL; + char key_path[1024]; + struct passwd *pw = NULL; + int rc; + + /* Create a new session */ + s->ssh.ssh.session = ssh_new(); + assert_non_null(s->ssh.ssh.session); + + rc = ssh_options_set(s->ssh.ssh.session, + SSH_OPTIONS_LOG_VERBOSITY, + &verbosity); + assert_int_equal(rc, SSH_OK); + + /* Create and initialize the callback state */ + cb_state = calloc(1, sizeof(struct agent_callback_state)); + assert_non_null(cb_state); + + cb_state->expected_session = s->ssh.ssh.session; + cb_state->created_channel = NULL; + + /* Set up the callbacks */ + callbacks = calloc(1, sizeof(struct ssh_callbacks_struct)); + assert_non_null(callbacks); + + callbacks->userdata = cb_state; + callbacks->channel_open_request_auth_agent_function = agent_callback; + + ssh_callbacks_init(callbacks); + rc = ssh_set_callbacks(s->ssh.ssh.session, callbacks); + assert_int_equal(rc, SSH_OK); + + /* Store callback state and callbacks */ + s->ssh.ssh.cb_state = cb_state; + s->ssh.ssh.callbacks = callbacks; + + /* Set up SSH agent with Bob's key */ + pw = getpwnam("bob"); + assert_non_null(pw); + snprintf(key_path, sizeof(key_path), "%s/.ssh/id_rsa", pw->pw_dir); + rc = torture_setup_ssh_agent(s, key_path); + assert_return_code(rc, errno); + + return 0; +} + +/* Main test function */ +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_agent_forwarding, + session_setup, + session_teardown), + }; + + ssh_init(); + + /* Simplify the CMocka test filter handling */ +#if defined HAVE_CMOCKA_SET_TEST_FILTER + cmocka_set_message_output(CM_OUTPUT_STDOUT); +#endif + + torture_filter_tests(tests); + + rc = cmocka_run_group_tests(tests, + sshd_setup_agent_forwarding, + torture_teardown_ssh_agent); + + ssh_finalize(); + + return rc; +} + +#endif diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth_cert.c b/src/libs/libssh-0.12.2/tests/client/torture_auth_cert.c new file mode 100644 index 000000000000..9a6990def697 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth_cert.c @@ -0,0 +1,1125 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * Copyright (c) 2023 by Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "torture.h" + +#include +#include +#include +#include + +#include "torture_auth_common.c" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, true); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + const char *all_keytypes = NULL; + struct passwd *pwd = NULL; + bool b = false; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Enable all hostkeys */ + all_keytypes = ssh_get_supported_methods(SSH_HOSTKEYS); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + all_keytypes); + assert_ssh_return_code(s->ssh.session, rc); + + /* certs have been signed for login as alice */ + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options or from agent explicitly. */ +static int session_setup_ssh_dir(void **state) +{ + struct torture_state *s = *state; + + session_setup(state); + + s->ssh.session->opts.homedir = strdup("~/.no_ssh"); + + return 0; +} + +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options. It also changes the target user to one that does + * not accept authentication using this certificate and moves private key to + * different location so the default matching does not work */ +static int session_setup_bob_cert(void **state) +{ + struct torture_state *s = *state; + char doe_ssh_key[1024]; + char new_ssh_key[1024]; + char doe_ssh_cert[2048]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd; + int rc; + + session_setup(state); + + s->ssh.session->opts.homedir = strdup("~/.no_ssh"); + + /* certs won't log in for bob */ + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(new_ssh_key, sizeof(new_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(new_ssh_key, keydata); + + /* Explicit private key and cert */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_IDENTITY, new_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options. It also changes the target user to one that does + * not accept authentication using this certificate and sets non-existing + * certificate path to trigger the right code path */ +static int session_setup_bob_cert_bad(void **state) +{ + struct torture_state *s = *state; + char doe_ssh_key[1024]; + char new_ssh_key[1024]; + char doe_ssh_cert[2048]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd; + int rc; + + session_setup(state); + + s->ssh.session->opts.homedir = strdup("~/.no_ssh"); + + /* certs won't log in for bob */ + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(new_ssh_key, sizeof(new_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert1.pub", doe_ssh_key); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(new_ssh_key, keydata); + + /* Explicit private key and cert */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_IDENTITY, new_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int agent_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + char key_path[1024]; + int rc; + + rc = session_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(key_path, sizeof(key_path), "%s/.ssh/id_rsa", pwd->pw_dir); + + /* run ssh-agent and add the key */ + rc = torture_setup_ssh_agent(s, key_path); + assert_int_equal(rc, 0); + + return 0; +} + +static int agent_cert_setup(void **state) +{ + char ssh_key_cmd[1024]; + struct passwd *pwd; + int rc; + + rc = agent_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + /* remove all keys, load alternative key + cert */ + snprintf(ssh_key_cmd, + sizeof(ssh_key_cmd), + "ssh-add -D && ssh-add %s/.ssh/id_rsa", + pwd->pw_dir); + + rc = system(ssh_key_cmd); + assert_return_code(rc, errno); + + return 0; +} + +static int agent_cert_setup_explicit(void **state) +{ + char orig_doe_ssh_key[1024]; + char doe_ssh_key[1024]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd; + int rc; + + rc = agent_cert_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(orig_doe_ssh_key, + sizeof(orig_doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(orig_doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(doe_ssh_key, keydata); + + return 0; +} + +static int agent_teardown(void **state) +{ + int rc; + + rc = session_teardown(state); + if (rc != 0) { + return rc; + } + + rc = torture_cleanup_ssh_agent(); + assert_int_equal(rc, 0); + + return 0; +} + +static void torture_auth_cert(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_key privkey = NULL; + ssh_key cert = NULL; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_privkey_file(doe_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_cert_file(doe_ssh_cert, &cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_try_publickey(session, NULL, cert); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); +} + +static void torture_auth_cert_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_key privkey = NULL; + ssh_key cert = NULL; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + rc = ssh_pki_import_privkey_file(doe_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_cert_file(doe_ssh_cert, &cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_try_publickey(session, NULL, cert); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); +} + +/* Same as torture_auth_cert, but without explicitly loading certificate to the + * private key file, keeping libssh to use default cert path when done with + * _auto(). */ +static void torture_auth_cert_default_non_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* the cert is in the default location (~/.ssh/id_rsa-cert.pub) */ + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert_nonblocking, but without explicitly loading + * certificate to the private key file, keeping libssh to use default cert path + * when done with _auto(). + * Non-blocking version */ +static void torture_auth_cert_default_non_explicit_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* the cert is in the default location (~/.ssh/id_rsa-cert.pub) */ + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Sanity test that there are no default identities available and the automatic + * pubkey authentication fails without any explicit identities */ +static void torture_auth_auto_fail(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); +} + +/* Sanity test that there are no default identities available and the automatic + * pubkey authentication fails without any explicit identities + * Non-blocking version */ +static void torture_auth_auto_fail_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. */ +static void torture_auth_cert_options_private(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + /* the cert has default naming relative to the private key (*-cert.pub) */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. + * Non-blocking version */ +static void torture_auth_cert_options_private_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + /* the cert has default naming relative to the private key (*-cert.pub) */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, also the certificate file + */ +static void torture_auth_cert_options_cert(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + /* Explicit private key and cert */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. + * Non-blocking version */ +static void torture_auth_cert_options_cert_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + /* Explicit private key and cert */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void workaround_old_openssh_bug(void **state) +{ +#if OPENSSH_VERSION_MAJOR < 8 || \ + (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* Skip this test if in FIPS mode. + * + * OpenSSH agent has a bug which makes it to not use SHA2 in signatures when + * using certificates. It always uses SHA1. + * + * This should be removed as soon as OpenSSH agent bug is fixed. + * (see https://gitlab.com/libssh/libssh-mirror/merge_requests/34) */ + if (ssh_fips_mode()) { + skip(); + } else { + /* After the bug is solved, this also should be removed */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa-cert-v01@openssh.com"); + assert_int_equal(rc, SSH_OK); + } +#else + (void)state; +#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ +} + +static void torture_auth_agent_cert(void **state) +{ + workaround_old_openssh_bug(state); + + /* Setup loads a different key, tests are exactly the same. */ + torture_auth_agent(state); +} + +static void torture_auth_agent_cert_nonblocking(void **state) +{ + workaround_old_openssh_bug(state); + + torture_auth_agent_nonblocking(state); +} + +static void torture_auth_agent_cert_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + return; + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key -- the cert in default location should be loaded + * automatically */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_agent_cert_identities_only_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + return; + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key -- the cert in default location should be loaded + * automatically */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_agent_cert_identities_only_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key and cert */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_identities_only_nonblocking_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key and cert */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_agent_cert_only_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a cert: key is in the agent */ + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_only_identities_only_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a cert: key is in the agent */ + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +#define GROUP_TEST(TEST_NAME, SETUP) \ + { \ + #TEST_NAME "_" #SETUP, \ + TEST_NAME, \ + session_setup##_##SETUP, \ + session_teardown, \ + NULL, \ + } + + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_cert, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_default_non_explicit, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_cert_default_non_explicit_nonblocking, + session_setup, + session_teardown), + GROUP_TEST(torture_auth_auto_fail, ssh_dir), + GROUP_TEST(torture_auth_auto_fail_nonblocking, ssh_dir), + GROUP_TEST(torture_auth_auto_fail, bob_cert), + GROUP_TEST(torture_auth_auto_fail_nonblocking, bob_cert), + GROUP_TEST(torture_auth_auto_fail, bob_cert_bad), + GROUP_TEST(torture_auth_auto_fail_nonblocking, bob_cert_bad), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_private, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_cert_options_private_nonblocking, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_cert, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_cert_options_cert_nonblocking, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_nonblocking, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_identities_only, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_identities_only_nonblocking, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_identities_only_explicit, + agent_cert_setup_explicit, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_identities_only_nonblocking_explicit, + agent_cert_setup_explicit, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_only_identities_only, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_only_identities_only_nonblocking, + agent_cert_setup, + agent_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth_common.c b/src/libs/libssh-0.12.2/tests/client/torture_auth_common.c new file mode 100644 index 000000000000..8a4f2854488c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth_common.c @@ -0,0 +1,94 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "torture.h" +#include "libssh/libssh.h" + +/* agent_is_running */ +#include "agent.c" + +void torture_auth_agent(void **state); +void torture_auth_agent(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* negative test case */ + rc = ssh_userauth_agent(NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +void torture_auth_agent_nonblocking(void **state); +void torture_auth_agent_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + ssh_set_blocking(session,0); + + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth_pkcs11.c b/src/libs/libssh-0.12.2/tests/client/torture_auth_pkcs11.c new file mode 100644 index 000000000000..d33a400be5a2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth_pkcs11.c @@ -0,0 +1,294 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +/* agent_is_running */ +#include "agent.c" + +#define LIBSSH_RSA_TESTKEY "id_pkcs11_rsa" +#define LIBSSH_ECDSA_256_TESTKEY "id_pkcs11_ecdsa_256" +#define LIBSSH_ECDSA_384_TESTKEY "id_pkcs11_ecdsa_384" +#define LIBSSH_ECDSA_521_TESTKEY "id_pkcs11_ecdsa_521" +#define LIBSSH_ED25519_TESTKEY "id_pkcs11_ed25519" + +const char template[] = "/tmp/temp_dir_XXXXXX"; + +struct pki_st { + char *temp_dir; + char *orig_dir; + char *keys_dir; +}; + +static int setup_tokens(void **state, const char *type, const char *obj_name) +{ + struct torture_state *s = *state; + struct pki_st *test_state = s->private_data; + char priv_filename[1024]; + char *cwd = NULL; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + snprintf(priv_filename, + sizeof(priv_filename), + "%s%s", + test_state->keys_dir, + type); + + torture_setup_tokens(cwd, priv_filename, obj_name, "1"); + + return 0; +} + +static int session_setup(void **state) +{ + int verbosity = torture_libssh_verbosity(); + struct torture_state *s = *state; + struct passwd *pwd = NULL; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int setup_pkcs11(void **state) +{ + struct torture_state *s = *state; + struct pki_st *test_state = NULL; + int rc; + char keys_dir[1024] = {0}; + char *temp_dir = NULL; + + test_state = malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + s->private_data = test_state; + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + snprintf(keys_dir, sizeof(keys_dir), "%s/tests/keys/pkcs11/", SOURCEDIR); + + test_state->keys_dir = strdup(keys_dir); + + setup_tokens(state, LIBSSH_RSA_TESTKEY, "rsa"); + setup_tokens(state, LIBSSH_ECDSA_256_TESTKEY, "ecdsa256"); + setup_tokens(state, LIBSSH_ECDSA_384_TESTKEY, "ecdsa384"); + setup_tokens(state, LIBSSH_ECDSA_521_TESTKEY, "ecdsa521"); + if (!ssh_fips_mode()) { + setup_tokens(state, LIBSSH_ED25519_TESTKEY, "ed25519"); + } + + return 0; +} + +static int sshd_setup(void **state) +{ + + torture_setup_sshd_server(state, true); + setup_pkcs11(state); + + return 0; +} + +static int sshd_teardown(void **state) +{ + struct torture_state *s = *state; + struct pki_st *test_state = s->private_data; + int rc; + + if (test_state != NULL) { + torture_cleanup_tokens(test_state->temp_dir); + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state->keys_dir); + SAFE_FREE(test_state); + } + + torture_teardown_sshd_server(state); + + return 0; +} + +static void +torture_auth_autopubkey(void **state, const char *obj_name, const char *pin) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + char priv_uri[1042]; + + /* Authenticate as charlie with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_CHARLIE); + assert_int_equal(rc, SSH_OK); + + snprintf(priv_uri, + sizeof(priv_uri), + "pkcs11:token=%s;object=%s;type=private?pin-value=%s", + obj_name, + obj_name, + pin); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, priv_uri); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->opts.identity_non_exp->root->data, priv_uri); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_autopubkey_rsa(void **state) +{ + torture_auth_autopubkey(state, "rsa", "1234"); +} + +static void torture_auth_autopubkey_ecdsa_key_256(void **state) +{ + torture_auth_autopubkey(state, "ecdsa256", "1234"); +} + +static void torture_auth_autopubkey_ecdsa_key_384(void **state) +{ + torture_auth_autopubkey(state, "ecdsa384", "1234"); +} + +static void torture_auth_autopubkey_ecdsa_key_521(void **state) +{ + torture_auth_autopubkey(state, "ecdsa521", "1234"); +} + +#ifdef WITH_PKCS11_PROVIDER +static void torture_auth_autopubkey_ed25519(void **state) +{ + /* The Ed25519 keys are not supported in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + torture_auth_autopubkey(state, "ed25519", "1234"); +} +#endif /* WITH_PKCS11_PROVIDER */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_rsa, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_384, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_521, + session_setup, + session_teardown), +#ifdef WITH_PKCS11_PROVIDER + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ed25519, + session_setup, + session_teardown), +#endif /* WITH_PKCS11_PROVIDER */ + }; + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_client_callbacks.c b/src/libs/libssh-0.12.2/tests/client/torture_client_callbacks.c new file mode 100644 index 000000000000..498783e3aad0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_client_callbacks.c @@ -0,0 +1,261 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2012 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/callbacks.h" + +#include +#include +#include + +#define STATE_SUCCESS (1) +#define STATE_FAILURE (2) + +struct callback_state +{ + int open_response; + int request_response; + ssh_session expected_session; + ssh_channel expected_channel; + struct ssh_channel_callbacks_struct *callback; +}; + +static void on_open_response(ssh_session session, ssh_channel channel, bool is_success, void *userdata) +{ + struct callback_state *state = (struct callback_state*)userdata; + assert_ptr_equal(state->expected_session, session); + assert_ptr_equal(state->expected_channel, channel); + state->open_response = is_success ? STATE_SUCCESS : STATE_FAILURE; +} + +static void on_request_response(ssh_session session, ssh_channel channel, void *userdata) +{ + struct callback_state *state = (struct callback_state*)userdata; + assert_ptr_equal(state->expected_session, session); + assert_ptr_equal(state->expected_channel, channel); + state->request_response = STATE_SUCCESS; +} + +static struct callback_state *set_callbacks(ssh_session session, ssh_channel channel) +{ + int rc; + struct ssh_channel_callbacks_struct *cb; + struct callback_state *cb_state = NULL; + + cb_state = (struct callback_state *)calloc(1, + sizeof(struct callback_state)); + assert_non_null(cb_state); + cb_state->expected_session = session; + cb_state->expected_channel = channel; + + cb = (struct ssh_channel_callbacks_struct *)calloc(1, + sizeof(struct ssh_channel_callbacks_struct)); + assert_non_null(cb); + ssh_callbacks_init(cb); + cb->userdata = cb_state; + cb->channel_open_response_function = on_open_response; + cb->channel_request_response_function = on_request_response; + rc = ssh_set_channel_callbacks(channel, cb); + assert_ssh_return_code(session, rc); + + /* Keep the reference so it can be cleaned up later */ + cb_state->callback = cb; + return cb_state; +} + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_open_success(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + assert_int_equal(STATE_SUCCESS, cb_state->open_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +static void torture_open_failure(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_forward(channel, "0.0.0.0", 0, "0.0.0.0", 0); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + assert_int_equal(STATE_FAILURE, cb_state->open_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +static void torture_request_success(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(channel, "echo -n ABCD"); + assert_ssh_return_code(session, rc); + + assert_int_equal(STATE_SUCCESS, cb_state->request_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +static void torture_request_failure(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_env(channel, "NOT_ACCEPTED", "VALUE"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + assert_int_equal(STATE_SUCCESS, cb_state->request_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_open_success, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_open_failure, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_success, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_failure, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_client_config.c b/src/libs/libssh-0.12.2/tests/client/torture_client_config.c new file mode 100644 index 000000000000..61b4bedbf0af --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_client_config.c @@ -0,0 +1,487 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include "torture.h" +#include "libssh/session.h" +#include "libssh/options.h" +#include "libssh/misc.h" + +#define LIBSSH_SSH_CONFIG "libssh_config" + +#define TORTURE_CONFIG_USER "test-user" + +#define CIPHERS "aes256-gcm@openssh.com,chacha20-poly1305@openssh.com" +#define CIPHERS2 "aes256-cbc,aes128-ctr" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int setup_config_files(void **state) +{ + struct torture_state *s = *state; + int verbosity; + struct passwd *pwd; + char *filename = NULL; + int rc; + + /* Work under the bob's UID to be able to load his configuration file */ + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + filename = ssh_path_expand_tilde("~/.ssh/config"); + torture_write_file(filename, "Ciphers "CIPHERS"\nTestBogus1\nUser "TORTURE_CONFIG_USER); + free(filename); + + torture_write_file(LIBSSH_SSH_CONFIG, "Ciphers "CIPHERS2"\nTestBogus2\n"); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + return 0; +} + +static int setup_session(void **state) +{ + struct torture_state *s = *state; + int verbosity; + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + setenv("NSS_WRAPPER_HOSTNAME", "client.libssh.site", 1); + + return 0; +} + +static int teardown(void **state) +{ + struct torture_state *s = *state; + char *filename; + + filename = ssh_path_expand_tilde("~/.ssh/config"); + if (filename != NULL) { + if (strlen(filename) > 0) { + unlink(filename); + } + SAFE_FREE(filename); + } + + unlink(LIBSSH_SSH_CONFIG); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int teardown_session(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* This tests makes sure that parsing both system-wide and per-user + * configuration files retains OpenSSH semantics (the per-user overrides + * the system-wide values). + * This function ssh_options_parse_config() has hardcoded path to the + * system-wide configuration file so this might not test anything at all + * if this system-wide file does not overwrite this option. + */ +static void torture_client_config_system(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + char *fips_ciphers = NULL; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + } + + /* The first tests assumes there is system-wide configuration file + * setting Ciphers to some non-default value. We do not have any control + * of that in this test case. + */ + ret = ssh_options_parse_config(s->ssh.session, NULL); + assert_ssh_return_code(s->ssh.session, ret); + + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + fips_ciphers); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + fips_ciphers); + } else { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + CIPHERS); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + CIPHERS); + } + + /* Make sure the configuration was processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, TORTURE_CONFIG_USER); + + SAFE_FREE(fips_ciphers); +} + +/* This tests makes sure that parsing both system-wide and per-user + * configuration files retains OpenSSH semantics (the per-user overrides + * the system-wide values). + * The function ssh_options_parse_config() has hardcoded path to the + * system-wide configuration file so we try to emulate the behavior by parsing + * the files separately in the same order. + */ +static void torture_client_config_emulate(void **state) +{ + struct torture_state *s = *state; + char *filename = NULL; + int ret = 0; + + char *fips_ciphers = NULL; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + } + + /* The first tests assumes there is system-wide configuration file + * setting Ciphers to some non-default value. We do not have any control + * of that in this test case + */ + filename = ssh_path_expand_tilde("~/.ssh/config"); + ret = ssh_options_parse_config(s->ssh.session, filename); + free(filename); + assert_ssh_return_code(s->ssh.session, ret); + + ret = ssh_options_parse_config(s->ssh.session, LIBSSH_SSH_CONFIG); + assert_ssh_return_code(s->ssh.session, ret); + + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + fips_ciphers); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + fips_ciphers); + } else { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + CIPHERS); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + CIPHERS); + } + /* Make sure the configuration was processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, TORTURE_CONFIG_USER); + + SAFE_FREE(fips_ciphers); +} + +/* This verifies that configuration files are parsed by default. + */ +static void torture_client_config_autoparse(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + ret = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* Make sure the configuration was processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, TORTURE_CONFIG_USER); +} + +/* This verifies that we are able to suppress parsing of the configuration files + * on connect using an option. + */ +static void torture_client_config_suppress(void **state) +{ + struct torture_state *s = *state; + bool b = false; + int ret = 0; + + ret = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, ret); + + ret = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* Make sure the configuration was not processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, "bob"); +} + +static void torture_client_config_expand_bad(void **state) +{ + ssh_session session = ssh_new(); + int ret = 0; + + (void)state; + + assert_non_null(session); + + /* The hash without host fails, but does not crash */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(session); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* The hash without host fails, but does not crash */ + ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(session); + assert_ssh_return_code_equal(session, ret, SSH_OK); + + ssh_free(session); +} + +static void torture_client_config_expand(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + /* TEST: user home directory */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%d"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, + BINARYDIR "/tests/home"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: target host name */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%h"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, TORTURE_SSH_SERVER); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: local username */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%u"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "root"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: local hostname */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "client.libssh.site"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: remote username */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, "alice"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%r"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "alice"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: remote port */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT_STR, "2222"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%p"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "2222"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: empty proxyjump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%j"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* No proxyjump string should not explode */ + assert_string_equal(s->ssh.session->opts.knownhosts, ""); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: proxyjump string present */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%j"); + ssh_options_set(s->ssh.session, + SSH_OPTIONS_PROXYJUMP, + "user@" TORTURE_SSH_SERVER ":22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, + "user@" TORTURE_SSH_SERVER ":22"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: separate list %l-%h-%p-%r-%j with empty ProxyJump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l-%h-%p-%r-%j"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYJUMP, "none"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT_STR, "22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%l-%h-%p-%r-%j\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "client.libssh.site-127.0.0.10-22-alice-"); + + + /* TEST: hash of %l%h%p%r%j with empty ProxyJump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%C\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "133e3957ff9d01fdcf1f6c7f83325a8ce49bf850"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: separate list %l-%h-%p-%r-%j */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l-%h-%p-%r-%j"); + ssh_options_set(s->ssh.session, + SSH_OPTIONS_PROXYJUMP, + "user@" TORTURE_SSH_SERVER ":22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -oProxyJump=user@" TORTURE_SSH_SERVER ":22" + // " -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%l-%h-%p-%r-%j\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "client.libssh.site-127.0.0.10-22-alice-user@" + TORTURE_SSH_SERVER ":22"); + + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: hash of %l%h%p%r%j */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -oProxyJump=user@" TORTURE_SSH_SERVER ":22" + // " -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%C\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "adf0b7c4e71a0fee85fd97506507ba8591f3663b"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + +} + + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + /* Keep these first -- following setup is changing user to bob, which we + * do not want */ + cmocka_unit_test(torture_client_config_expand_bad), + cmocka_unit_test_setup_teardown(torture_client_config_expand, + setup_session, + teardown_session), + cmocka_unit_test_setup_teardown(torture_client_config_system, + setup_config_files, + teardown), + cmocka_unit_test_setup_teardown(torture_client_config_emulate, + setup_config_files, + teardown), + cmocka_unit_test_setup_teardown(torture_client_config_autoparse, + setup_config_files, + teardown), + cmocka_unit_test_setup_teardown(torture_client_config_suppress, + setup_config_files, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_client_global_requests.c b/src/libs/libssh-0.12.2/tests/client/torture_client_global_requests.c new file mode 100644 index 000000000000..1320ea0051c4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_client_global_requests.c @@ -0,0 +1,152 @@ +/* + * torture_client_global_requests.c - Tests for client global requests + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/channels.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, true); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int authenticate(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + return rc; +} + +static void torture_unknown_request(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + + rc = authenticate(state); + assert_ssh_return_code(session, rc); + + /* Request asking for reply */ + rc = ssh_global_request(session, "unknown-request-00@test.com", NULL, 1); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* Request and don't ask for reply */ + rc = ssh_global_request(session, "another-bad-req-00@test.com", NULL, 0); + assert_ssh_return_code(session, rc); + + /* Open channel to make sure the session is still working */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + ssh_channel_close(channel); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_unknown_request, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_connect.c b/src/libs/libssh-0.12.2/tests/client/torture_connect.c new file mode 100644 index 000000000000..b34e5bfe6f6b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_connect.c @@ -0,0 +1,401 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "torture_cmocka.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include +#include +#include +#include +#include + +/* Should work until Apnic decides to assign it :) */ +#define BLACKHOLE "1.1.1.1" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, BLACKHOLE); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_connect_peer_discon_msg(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_service_request(session, "wrong-service"); + assert_int_not_equal(rc, SSH_OK); + + ssh_disconnect(session); + assert_non_null(session->peer_discon_msg); + assert_non_null(ssh_get_disconnect_message(session)); +} + +static void torture_connect_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + ssh_set_blocking(session,0); + + do { + rc = ssh_connect(session); + assert_ssh_return_code_not_equal(session, rc, SSH_ERROR); + } while(rc == SSH_AGAIN); + + assert_ssh_return_code(session, rc); +} + +static void torture_connect_ipv6(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "testing"); + assert_ssh_return_code(session, rc); + /* set non-blocking mode */ + ssh_set_blocking(session, 0); + + do { + rc = ssh_connect(session); + } while (rc == SSH_AGAIN); + + assert_ssh_return_code(session, rc); + + /* should work for blocking mode too */ + ssh_disconnect(session); + ssh_set_blocking(session, 1); + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_connect_addrfamily(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + struct aftest { + enum ssh_address_family_options_e family; + char const *host; + int return_code; + }; + static struct aftest aftests[] = { + {SSH_ADDRESS_FAMILY_ANY, "afboth", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET, "afboth", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET6, "afboth", SSH_OK}, + {SSH_ADDRESS_FAMILY_ANY, "afinet", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET, "afinet", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET6, "afinet", SSH_ERROR}, + {SSH_ADDRESS_FAMILY_ANY, "afinet6", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET, "afinet6", SSH_ERROR}, + {SSH_ADDRESS_FAMILY_INET6, "afinet6", SSH_OK}, + }; + + int aftest_count = sizeof(aftests) / sizeof(aftests[0]); + for (int i = 0; i < aftest_count; ++i) { + struct aftest const *t = &aftests[i]; + + rc = ssh_options_set(session, SSH_OPTIONS_ADDRESS_FAMILY, &t->family); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, t->host); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_connect(session); + } while (rc == SSH_AGAIN); + + assert_ssh_return_code_equal(session, rc, t->return_code); + ssh_disconnect(session); + } +} + +#if 0 /* This does not work with socket_wrapper */ +static void torture_connect_timeout(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + struct timeval before, after; + int rc; + long timeout = 2; + time_t sec; + suseconds_t usec; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, BLACKHOLE); + assert_true(rc == SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &timeout); + assert_true(rc == SSH_OK); + + rc = gettimeofday(&before, NULL); + assert_true(rc == 0); + rc = ssh_connect(session); + assert_true(rc == SSH_ERROR); + rc = gettimeofday(&after, NULL); + assert_true(rc == 0); + sec = after.tv_sec - before.tv_sec; + usec = after.tv_usec - before.tv_usec; + /* Borrow a second for the missing usecs, but don't bother calculating */ + if (usec < 0) + sec--; + assert_in_range(sec, 1, 3); +} +#endif + +static void torture_connect_double(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_connect_failure(void **state) { + /* + * The intent of this test is to check that a fresh + * ssh_new/ssh_disconnect/ssh_free sequence doesn't crash/leak + * and the behavior of a double ssh_disconnect + */ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + ssh_disconnect(session); +} + +static void torture_connect_socket(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int rc; + int sock_fd = 0; + struct sockaddr_in server_addr = { + .sin_family = AF_INET, + .sin_port = htons(22), + .sin_addr.s_addr = inet_addr(TORTURE_SSH_SERVER), + }; + + sock_fd = socket(AF_INET, SOCK_STREAM, 0); + assert_true(sock_fd > 2); + + rc = connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); + assert_return_code(rc, errno); + + ssh_options_set(session, SSH_OPTIONS_FD, &sock_fd); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_connect_uninitialized(UNUSED_PARAM(void **state)) +{ + int rc; + ssh_session session; + struct passwd *pwd; + + /* Make sure the library is uninitialized */ + while (is_ssh_initialized()) { + rc = ssh_finalize(); + assert_return_code(rc, errno); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + session = ssh_new(); + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + /* Expect error from ssh_connect */ + rc = ssh_connect(session); + assert_false(rc == SSH_OK); + assert_string_equal(ssh_get_error(session), "Library not initialized."); + + ssh_free(session); +} + +static void +internal_log(ssh_session session, + int priority, + const char *message, + void *userdata) +{ + (void)session; + (void)priority; + (void)message; + (void)userdata; + + return; +} + +static void +torture_legacy_callback(void **state) +{ + struct ssh_callbacks_struct cb[2] = {0}; + int rc, verbosity = SSH_LOG_WARNING; + ssh_session session = NULL; + + /* unused. */ + (void)state; + + /* + * Legacy code in 'ssh_set_callbacks' used to + * create the conditions for a use-after-free + * issue, in multi-session programs, by failing + * to update a pointer with the new session. + * + * To verify it won't happen again, this test + * creates two consecutive sessions and frees + * them; if any fault occurs then the pointer + * remained at the previous session, failing + * to be updated. + */ + for (int i = 0; i < 2; i++) { + session = ssh_new(); + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(session, rc); + + cb[i].log_function = internal_log; + + ssh_callbacks_init(&cb[i]); + ssh_set_callbacks(session, &cb[i]); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + ssh_disconnect(session); + + ssh_free(session); + } +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_connect_peer_discon_msg, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_ipv6, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_addrfamily, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_double, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_failure, + session_setup, + session_teardown), +#if 0 + cmocka_unit_test_setup_teardown(torture_connect_timeout, session_setup, session_teardown), +#endif + cmocka_unit_test_setup_teardown(torture_connect_socket, + session_setup, + session_teardown), + cmocka_unit_test(torture_legacy_callback), + cmocka_unit_test(torture_connect_uninitialized), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_forward.c b/src/libs/libssh-0.12.2/tests/client/torture_forward.c new file mode 100644 index 000000000000..18b8c85feb00 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_forward.c @@ -0,0 +1,119 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_ssh_forward(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int dport; + int bound_port; + char *originator_host = NULL; + int originator_port; + int rc; + int verbosity = SSH_LOG_TRACE; + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + rc = ssh_channel_listen_forward(session, "127.0.0.21", 8080, &bound_port); + assert_ssh_return_code(session, rc); + + c = ssh_channel_open_forward_port(session, 10, &dport, &originator_host, &originator_port); + /* We do not get a listener and run into the timeout here */ + assert_null(c); + + ssh_channel_send_eof(c); + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_ssh_forward, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_get_kex_algo.c b/src/libs/libssh-0.12.2/tests/client/torture_get_kex_algo.c new file mode 100644 index 000000000000..f25e65553ff2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_get_kex_algo.c @@ -0,0 +1,253 @@ +#include "config.h" +#include "libssh/libcrypto.h" +#include +#define LIBSSH_STATIC +#include "torture.h" +#include +#include + +#define ECDH_SHA2_NISTP256 "ecdh-sha2-nistp256" +#define CURVE25519_SHA256 "curve25519-sha256" +#define DIFFIE_HELLMAN_GROUP_14_SHA_1 "diffie-hellman-group14-sha1" +#define KEX_DH_GEX_SHA1 "diffie-hellman-group-exchange-sha1" +#define KEX_DH_GEX_SHA256 "diffie-hellman-group-exchange-sha256" +#define SNTRUP761X25519 "sntrup761x25519-sha512" +#define SNTRUP761X25519_OPENSSH "sntrup761x25519-sha512@openssh.com" +#define MLKEM768X25519 "mlkem768x25519-sha256" +#define MLKEM768NISTP256 "mlkem768nistp256-sha256" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + bool false_v = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &false_v); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_kex_basic_functionality(void **state) +{ + struct torture_state *s = *state; + ssh_session session = NULL; + const char *kex_algo = NULL; + const char *valid_algorithms[] = { + SNTRUP761X25519, + SNTRUP761X25519_OPENSSH, + MLKEM768X25519, + MLKEM768NISTP256, + CURVE25519_SHA256, + ECDH_SHA2_NISTP256, + DIFFIE_HELLMAN_GROUP_14_SHA_1, + }; + size_t valid_algorithms_count, i; + int rc; + bool is_valid_algo; + + session = s->ssh.session; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + kex_algo = ssh_get_kex_algo(session); + assert_non_null(kex_algo); + + is_valid_algo = false; + valid_algorithms_count = + sizeof(valid_algorithms) / sizeof(valid_algorithms[0]); + for (i = 0; i < valid_algorithms_count; i++) { + if (strcmp(kex_algo, valid_algorithms[i]) == 0) { + is_valid_algo = true; + break; + } + } + assert_true(is_valid_algo); +} + +static void torture_kex_algo_preference(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *expected_kex = NULL; + const char *actual_kex = NULL; + int rc; + + if (ssh_fips_mode()) { + expected_kex = ECDH_SHA2_NISTP256; + } else { + expected_kex = CURVE25519_SHA256; + } + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, expected_kex); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + actual_kex = ssh_get_kex_algo(session); + assert_non_null(actual_kex); + assert_string_equal(actual_kex, expected_kex); +} + +static void torture_kex_algo_negotiation(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *kex_list = + "non-existent-algo,not-supported-kex," CURVE25519_SHA256 + "," ECDH_SHA2_NISTP256 "," DIFFIE_HELLMAN_GROUP_14_SHA_1; + int rc, cmp; + const char *negotiated_kex = NULL; + bool found; + char *temp_list = NULL; + char *token = NULL; + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex_list); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + negotiated_kex = ssh_get_kex_algo(session); + assert_non_null(negotiated_kex); + + assert_string_not_equal(negotiated_kex, "non-existent-algo"); + assert_string_not_equal(negotiated_kex, "not-supported-kex"); + + found = false; + temp_list = strdup(kex_list); + + for (token = strtok(temp_list, ","); token != NULL; + token = strtok(NULL, ",")) { + cmp = strcmp(token, negotiated_kex); + if (cmp == 0) { + found = true; + break; + } + } + + free(temp_list); + assert_true(found); +} + +static void torture_kex_algo_before_connect(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *kex_algo = NULL; + + kex_algo = ssh_get_kex_algo(session); + assert_null(kex_algo); +} + +#ifdef WITH_GEX +static void torture_dgex_algo(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *kex_list = KEX_DH_GEX_SHA1 "," KEX_DH_GEX_SHA256; + int rc, cmp; + const char *negotiated_kex = NULL; + bool found; + char *temp_list = NULL; + char *token = NULL; + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex_list); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + negotiated_kex = ssh_get_kex_algo(session); + assert_non_null(negotiated_kex); + + found = false; + temp_list = strdup(kex_list); + + for (token = strtok(temp_list, ","); token != NULL; + token = strtok(NULL, ",")) { + cmp = strcmp(token, negotiated_kex); + if (cmp == 0) { + found = true; + break; + } + } + + free(temp_list); + assert_true(found); +} +#endif /* WITH_GEX */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_kex_basic_functionality, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_kex_algo_preference, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_kex_algo_negotiation, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_kex_algo_before_connect, + session_setup, + session_teardown), +#ifdef WITH_GEX + cmocka_unit_test_setup_teardown(torture_dgex_algo, + session_setup, + session_teardown), +#endif /* WITH_GEX */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_gssapi_auth.c b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_auth.c new file mode 100644 index 000000000000..d73cad64725d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_auth.c @@ -0,0 +1,305 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPICleanupCredentials yes\n" + "GSSAPIStrictAcceptorCheck yes\n"); + + return 0; +} + +static int +sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_gssapi_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* No client credential */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + /* Invalid host principal */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_client_identity(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid client identity option */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, "bob"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + + /* Valid client identity option*/ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, "alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_server_identity(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid server identity option */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "invalid.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server(state); + + /* Valid server identity option*/ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "server.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_delegate_creds(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + OM_uint32 maj_stat, min_stat; + gss_cred_id_t client_creds = GSS_C_NO_CREDENTIAL; + gss_OID_set no_mechs = GSS_C_NO_OID_SET; + int t = 1; + + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &t); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + maj_stat = gss_acquire_cred(&min_stat, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + GSS_C_INITIATE, + &client_creds, + &no_mechs, + NULL); + assert_int_equal(GSS_ERROR(maj_stat), 0); + + ssh_gssapi_set_creds(session, client_creds); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + gss_release_cred(&min_stat, &client_creds); + gss_release_oid_set(&min_stat, &no_mechs); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_auth_bad_user(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_client_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_server_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_delegate_creds, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_bad_user, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange.c b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange.c new file mode 100644 index 000000000000..b61acc40cb61 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange.c @@ -0,0 +1,348 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/crypto.h" +#include "torture.h" +#include + +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPIKeyExchange yes\n"); + + return 0; +} + +static int sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_gssapi_key_exchange(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_true(ssh_session_kex_is_gss(session)); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_no_tgt(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Don't run kinit */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_false(ssh_session_kex_is_gss(session)); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_alg(void **state, + const char *kex_string, + enum ssh_key_exchange_e kex_type) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + kex_string); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_int_equal(session->current_crypto->kex_type, kex_type); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_gss_group14_sha256(void **state) +{ + torture_gssapi_key_exchange_alg(state, + "gss-group14-sha256-", + SSH_GSS_KEX_DH_GROUP14_SHA256); +} + +static void torture_gssapi_key_exchange_gss_group16_sha512(void **state) +{ + torture_gssapi_key_exchange_alg(state, + "gss-group16-sha512-", + SSH_GSS_KEX_DH_GROUP16_SHA512); +} + +static void torture_gssapi_key_exchange_gss_nistp256_sha256(void **state) +{ + torture_gssapi_key_exchange_alg(state, + "gss-nistp256-sha256-", + SSH_GSS_KEX_ECDH_NISTP256_SHA256); +} + +static void torture_gssapi_key_exchange_gss_curve25519_sha256(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + torture_gssapi_key_exchange_alg(state, + "gss-curve25519-sha256-", + SSH_GSS_KEX_CURVE25519_SHA256); +} + +static void torture_gssapi_key_exchange_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_auth_bad_user(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_no_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool f = false; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + /* Don't do GSSAPI Key Exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &f); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_false(ssh_session_kex_is_gss(session)); + + /* Still try to do "gssapi-keyex" auth */ + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + + torture_teardown_kdc_server(state); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_no_tgt, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_group16_sha512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_nistp256_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_curve25519_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_auth_bad_user, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_no_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange_null.c b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange_null.c new file mode 100644 index 000000000000..125fa39e0022 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange_null.c @@ -0,0 +1,181 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + struct torture_state *s = NULL; + torture_setup_sshd_server(state, false); + + s = *state; + s->disable_hostkeys = true; + + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPIKeyExchange yes\n"); + + torture_teardown_kdc_server(state); + + return 0; +} + +static int sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_gssapi_key_exchange_null(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(s->ssh.session, rc); + + assert_string_equal(session->current_crypto->kex_methods[SSH_HOSTKEYS], + "null"); + + assert_true(ssh_session_kex_is_gss(session)); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_null_pubkey_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(s->ssh.session, rc); + + assert_string_equal(session->current_crypto->kex_methods[SSH_HOSTKEYS], + "null"); + + assert_true(ssh_session_kex_is_gss(session)); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_kdc_server(state); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_null, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_null_pubkey_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_hostkey.c b/src/libs/libssh-0.12.2/tests/client/torture_hostkey.c new file mode 100644 index 000000000000..88b657b60f97 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_hostkey.c @@ -0,0 +1,214 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_hostkey_rsa(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "ssh-rsa"; + + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_hostkey_ed25519(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char ed[] = "ssh-ed25519"; + + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &ed); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +#ifdef HAVE_ECC +static void torture_hostkey_ecdsa(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char ecdsa[] = "ecdsa-sha2-nistp521"; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &ecdsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} +#endif + +static void torture_hostkey_rsa_sha256(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "rsa-sha2-256"; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_hostkey_rsa_sha512(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "rsa-sha2-512"; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_hostkey_rsa, session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_hostkey_ed25519, session_setup, + session_teardown), +#ifdef HAVE_ECC + cmocka_unit_test_setup_teardown(torture_hostkey_ecdsa, session_setup, + session_teardown), +#endif + /* the client is able to handle SHA2 extension (if negotiated) */ + cmocka_unit_test_setup_teardown(torture_hostkey_rsa_sha256, + session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_hostkey_rsa_sha512, + session_setup, session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_knownhosts.c b/src/libs/libssh-0.12.2/tests/client/torture_knownhosts.c new file mode 100644 index 000000000000..55aee217905e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_knownhosts.c @@ -0,0 +1,513 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" + +#include +#include +#include + +#include "session.c" +#include "known_hosts.c" + +#define TMP_FILE_TEMPLATE "known_hosts_XXXXXX" + +#define BADRSA "AAAAB3NzaC1yc2EAAAADAQABAAABAQChm5" \ + "a6Av65O8cKtx5YXOnui3wJnYE6A6J/I4kZSAibbn14Jcl+34VJQwv96f25AxNmo" \ + "NwoiZV93IzdypQmiuieh6s6wB9WhYjU9K/6CkIpNhpCxswA90b3ePjS7LnR9B9J" \ + "slPSbG1H0KC1c5lb7G3utXteXtM+4YvCvpN5VdC4CpghT+p0cwN2Na8Md5vRItz" \ + "YgIytryNn7LLiwYfoSxvWigFrTTZsrVtCOYyNgklmffpGdzuC43wdANvTewfI9G" \ + "o71r8EXmEc228CrYPmb8Scv3mpXFK/BosohSGkPlEHu9lf3YjnknBicDaVtJOYp" \ + "wnXJPjZo2EhG79HxDRpjJHH" +#define BADED25519 "AAAAC3NzaC1lZDI1NTE5AAAAIE74wHmKKkrxpW/dZ69pKPlMoWG9VvWfrNnUkWRQqaDa" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool process_config = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, + &process_config); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + + +static void torture_knownhosts_port(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + char buffer[200]; + char *p; + FILE *file; + int rc; + bool process_config = false; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + session->opts.port = 1234; + rc = ssh_write_knownhost(session); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "r"); + assert_non_null(file); + p = fgets(buffer, sizeof(buffer), file); + assert_non_null(p); + fclose(file); + buffer[sizeof(buffer) - 1] = '\0'; + assert_non_null(strstr(buffer,"[127.0.0.10]:1234 ")); + + ssh_disconnect(session); + ssh_free(session); + + /* Now, connect back to the ssh server and verify the known host line */ + s->ssh.session = session = ssh_new(); + + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + session->opts.port = 1234; + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); +} + +static void torture_knownhosts_wildcard(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + const char *key = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + key = torture_get_testkey_pub(SSH_KEYTYPE_RSA); + fprintf(file, "[127.0.0.10]:* %s\n", key); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); +} + +static void torture_knownhosts_standard_port(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + const char *key = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + key = torture_get_testkey_pub(SSH_KEYTYPE_RSA); + fprintf(file, "[127.0.0.10]:22 %s\n", key); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); +} + +static void torture_knownhosts_fail(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + free(known_hosts_file); + + fprintf(file, "127.0.0.10 ssh-rsa %s\n", BADRSA); + fclose(file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_CHANGED); +} + +static void torture_knownhosts_other(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + free(known_hosts_file); + + fprintf(file, "127.0.0.10 ssh-rsa %s\n", BADRSA); + fclose(file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_FOUND_OTHER); +} + +static void torture_knownhosts_other_auto(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + int rc; + bool process_config = false; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_NOT_KNOWN); + + rc = ssh_write_knownhost(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* ssh-rsa is the default but libssh should try ssh-ed25519 instead */ + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_conflict(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + bool process_config = false; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, "127.0.0.10 ssh-rsa %s\n", BADRSA); + fprintf(file, "127.0.0.10 ssh-ed25519 %s\n", BADED25519); + fclose(file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_CHANGED); + + rc = ssh_write_knownhost(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + assert_ssh_return_code(session, rc); + + ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_no_hostkeychecking(void **state) +{ + + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + int strict_host_key_checking = 0; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + rc = ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &strict_host_key_checking); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_knownhosts_wildcard, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_standard_port, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_port, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_fail, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_other, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_other_auto, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_conflict, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_no_hostkeychecking, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_knownhosts_verify.c b/src/libs/libssh-0.12.2/tests/client/torture_knownhosts_verify.c new file mode 100644 index 000000000000..85963345614e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_knownhosts_verify.c @@ -0,0 +1,519 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" + +#include +#include + +#include "knownhosts.c" + +#define TMP_FILE_TEMPLATE "known_hosts_XXXXXX" + +#define BAD_RSA "AAAAB3NzaC1yc2EAAAADAQABAAABAQDXvXuawzaArEwkLIXTz/EWywLOC" \ + "tqQL3P9yKkrhz6AplXP2PhOh5pyxa1VfGKe453jNeYBJ0ROto3BshXgZX" \ + "bo86oLXTkbe0gO5xi3r5WjXxjOFvRRTLot5fPLNDOv9+TnsPmkNn0iIey" \ + "PnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpbJvTXSDipukpUy41d7jg4uWGu" \ + "onMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdcARyhix4RMWZZqVY20" \ + "H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1TjzkrBZ6B6" \ + "thmZtyeb9IsiB0tg2g0JN2VTAGkxqp" + +const char template[] = "temp_dir_XXXXXX"; + +static int sshd_group_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_group_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + bool process_config = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, + &process_config); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +#define KNOWN_HOST_ENTRY_ECDSA "127.0.0.10 ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd18g==\n" +#define KNOWN_HOST_ENTRY_ED25519 "127.0.0.10 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqX\n" +static void torture_knownhosts_export(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char *entry = NULL; + char *p = NULL; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_session_export_known_hosts_entry(session, &entry); + assert_ssh_return_code(session, rc); + + p = strstr(entry, "ssh-ed25519"); + if (p != NULL) { + assert_string_equal(entry, KNOWN_HOST_ENTRY_ED25519); + } else { + assert_string_equal(entry, KNOWN_HOST_ENTRY_ECDSA); + } + SAFE_FREE(entry); +} + +static void torture_knownhosts_write_and_verify(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e found; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + +static void torture_knownhosts_precheck(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + struct ssh_list *algo_list = NULL; + struct ssh_iterator *it = NULL; + size_t algo_count; + const char *algo = NULL; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_ECDSA_P521)); + + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + algo_list = ssh_known_hosts_get_algorithms(session); + assert_non_null(algo_list); + + algo_count = ssh_list_count(algo_list); + assert_int_equal(algo_count, 3); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ssh-rsa"); + + ssh_list_remove(algo_list, it); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ssh-ed25519"); + + ssh_list_remove(algo_list, it); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ecdsa-sha2-nistp521"); + + ssh_list_free(algo_list); +} + +static void torture_knownhosts_duplicate(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + struct ssh_list *algo_list = NULL; + struct ssh_iterator *it = NULL; + size_t algo_count; + const char *algo = NULL; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + algo_list = ssh_known_hosts_get_algorithms(session); + assert_non_null(algo_list); + + algo_count = ssh_list_count(algo_list); + assert_int_equal(algo_count, 1); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ssh-rsa"); + + ssh_list_free(algo_list); +} + +static void torture_knownhosts_other(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + FILE *file = NULL; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + fclose(file); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OTHER); +} + +static void torture_knownhosts_unknown(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* ssh-rsa is the default but libssh should try ssh-ed25519 instead */ + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_conflict(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + FILE *file = NULL; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s %s\n", + "ssh-rsa", + BAD_RSA); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_CHANGED); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_new_file(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e found; + int rc; + + char new_known_hosts[256]; + char *tmp_dir = NULL; + ssize_t count = 0; + + /* Create a disposable directory */ + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + count = snprintf(new_known_hosts, sizeof(new_known_hosts), + "%s/a/b/c/d/known_hosts", tmp_dir); + assert_return_code(count, errno); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, new_known_hosts); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* Cleanup */ + torture_rmdirs(tmp_dir); + + SAFE_FREE(tmp_dir); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_knownhosts_export, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_write_and_verify, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_precheck, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_other, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_unknown, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_conflict, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_duplicate, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_new_file, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_group_setup, sshd_group_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_proxycommand.c b/src/libs/libssh-0.12.2/tests/client/torture_proxycommand.c new file mode 100644 index 000000000000..232080eb0d47 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_proxycommand.c @@ -0,0 +1,265 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include "libssh/priv.h" + +#include +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +#ifdef NCAT_EXECUTABLE +static void torture_options_set_proxycommand(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *address = torture_server_address(AF_INET); + int port = torture_server_port(); + char command[255] = {0}; + struct stat sb; + int rc; +#ifdef WITH_EXEC + socket_t fd; +#endif + + rc = stat(NCAT_EXECUTABLE, &sb); + if (rc != 0 || (sb.st_mode & S_IXOTH) == 0) { + SSH_LOG(SSH_LOG_WARNING, + "Could not find " NCAT_EXECUTABLE ": Skipping the test"); + skip(); + } + + rc = snprintf(command, + sizeof(command), + NCAT_EXECUTABLE " %s %d", + address, + port); + assert_true((size_t)rc < sizeof(command)); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(session); +#ifdef WITH_EXEC + assert_ssh_return_code(session, rc); + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); +#else + assert_int_equal(rc, SSH_ERROR); +#endif /* WITH_EXEC */ +} + +#else /* NCAT_EXECUTABLE */ + +static void torture_options_set_proxycommand(void **state) +{ + (void) state; + skip(); +} + +#endif /* NCAT_EXECUTABLE */ + +static void torture_options_set_proxycommand_notexist(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "this_command_does_not_exist"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +static void torture_options_set_proxycommand_ssh(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *address = torture_server_address(AF_INET); + char command[255] = {0}; + int rc; +#ifdef WITH_EXEC + socket_t fd; +#endif + + rc = snprintf(command, sizeof(command), + "ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -W [%%h]:%%p alice@%s", + address); + assert_true((size_t)rc < sizeof(command)); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(session); +#ifdef WITH_EXEC + assert_ssh_return_code(session, rc); + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); +#else + assert_int_equal(rc, SSH_ERROR); +#endif /* WITH_EXEC */ +} + +static void torture_options_set_proxycommand_ssh_stderr(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *address = torture_server_address(AF_INET); + char command[255] = {0}; + int rc; +#ifdef WITH_EXEC + socket_t fd; +#endif + + /* The -vvv switches produce the desired output on the standard error */ + rc = snprintf(command, sizeof(command), + "ssh -vvv -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -W [%%h]:%%p alice@%s", + address); + assert_true((size_t)rc < sizeof(command)); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(session); +#ifdef WITH_EXEC + assert_ssh_return_code(session, rc); + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); +#else + assert_int_equal(rc, SSH_ERROR); +#endif /* WITH_EXEC */ +} + +static void torture_options_proxycommand_injection(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + const char *malicious_host = "`echo foo > mfile`"; + const char *command = "nc %h %p"; + char *current_dir = NULL; + char *malicious_file_path = NULL; + int mfp_len; + int verbosity = torture_libssh_verbosity(); + struct stat sb; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + // if we would be checking the rc, this should fail + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, malicious_host); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code_equal(s->ssh.session, rc, SSH_ERROR); + + current_dir = torture_get_current_working_dir(); + assert_non_null(current_dir); + mfp_len = strlen(current_dir) + 6; + malicious_file_path = malloc(mfp_len); + assert_non_null(malicious_file_path); + rc = snprintf(malicious_file_path, mfp_len, + "%s/mfile", current_dir); + assert_int_equal(rc, mfp_len); + free(current_dir); + rc = stat(malicious_file_path, &sb); + assert_int_not_equal(rc, 0); + + // cleanup + remove(malicious_file_path); + free(malicious_file_path); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_notexist, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh_stderr, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_proxycommand_injection, + NULL, + session_teardown), + }; + + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_proxyjump.c b/src/libs/libssh-0.12.2/tests/client/torture_proxyjump.c new file mode 100644 index 000000000000..cc2d8d64801f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_proxyjump.c @@ -0,0 +1,362 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(s->ssh.session, rc); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_proxyjump_single_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + int rc; + socket_t fd; + + rc = snprintf(proxyjump_buf, sizeof(proxyjump_buf), "alice@%s:22", address); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static int before_connection(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int verify_knownhost(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int authenticate(ssh_session jump_session, void *user) +{ + (void)user; + + return ssh_userauth_publickey_auto(jump_session, NULL, NULL); +} + +static int authenticate_doe(ssh_session jump_session, void *user) +{ + ssh_key pkey = NULL; + char bob_ssh_key[1024]; + struct passwd *pwd = NULL; + int rc; + + (void)user; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &pkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(jump_session, NULL, pkey); + ssh_key_free(pkey); + return rc; +} + +static int authenticate_frank(ssh_session jump_session, void *user) +{ + ssh_key pkey = NULL; + char bob_ssh_key[1024]; + struct passwd *pwd = NULL; + int rc; + + (void)user; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_ecdsa", pwd->pw_dir); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &pkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(jump_session, NULL, pkey); + ssh_key_free(pkey); + return rc; +} + +static void torture_proxyjump_multiple_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + int rc; + socket_t fd; + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate, + }; + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "alice@%s:22,alice@%s:22", + address, + address); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_proxyjump_multiple_sshd_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + const char *address1 = torture_server1_address(AF_INET6); + int rc; + socket_t fd; + + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate_doe, + }; + + torture_setup_sshd_servers(state, false); + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "doe@%s:22,doe@%s:22", + address, + address1); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_sshd_server1(state); +} + +static void torture_proxyjump_multiple_sshd_users_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET6); + const char *address1 = torture_server1_address(AF_INET); + int rc; + socket_t fd; + + struct ssh_jump_callbacks_struct c1 = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate_doe, + }; + struct ssh_jump_callbacks_struct c2 = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate_frank, + }; + + torture_setup_sshd_servers(state, false); + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "doe@%s:22,frank@%s:22", + address, + address1); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c1); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c2); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_sshd_server1(state); +} + +static void torture_proxyjump_invalid_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + int rc; + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "doesnotexist@%s:54", + address); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_proxyjump_single_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_proxyjump_multiple_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_proxyjump_multiple_sshd_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_proxyjump_multiple_sshd_users_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_proxyjump_invalid_jump, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_rekey.c b/src/libs/libssh-0.12.2/tests/client/torture_rekey.c new file mode 100644 index 000000000000..d458975cff43 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_rekey.c @@ -0,0 +1,1013 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Authors: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/sftp.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/token.h" + +#include +#include +#include +#include +#include + +#define KEX_RETRY 32 + +static uint64_t bytes = 2048; /* 2KB (more than the authentication phase) */ + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + /* Authenticate as alice with bob's pubkey */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_free(s->ssh.session); + s->ssh.session = NULL; + + return 0; +} + +/* Check that the default limits for rekeying are enforced. + * the limits are too high for testsuite to verify so + * we should be fine with checking the values in internal + * structures + */ +static void torture_rekey_default(void **state) +{ + struct torture_state *s = *state; + int rc; + struct ssh_crypto_struct *c = NULL; + + /* Define preferred ciphers: */ + if (ssh_fips_mode()) { + /* We do not have any FIPS allowed cipher with different block size */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_C_S, + "aes128-gcm@openssh.com"); + } else { + /* (out) C->S has 8B block */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_C_S, + "chacha20-poly1305@openssh.com"); + } + assert_ssh_return_code(s->ssh.session, rc); + /* (in) S->C has 16B block */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_S_C, + "aes128-cbc"); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + c = s->ssh.session->current_crypto; + /* The blocks limit is set correctly */ + /* For S->C (in) we have 16B block => 2**(L/4) blocks */ + assert_int_equal(c->in_cipher->max_blocks, + (uint64_t)1 << (2 * c->in_cipher->blocksize)); + if (ssh_fips_mode()) { + /* We do not have any FIPS allowed cipher with different block size */ + assert_int_equal(c->in_cipher->max_blocks, + (uint64_t)1 << (2 * c->in_cipher->blocksize)); + } else { + /* The C->S (out) we have 8B block => 1 GB limit */ + assert_int_equal(c->out_cipher->max_blocks, + ((uint64_t)1 << 30) / c->out_cipher->blocksize); + } + + ssh_disconnect(s->ssh.session); +} + +static void sanity_check_session_size(void **state, uint64_t rekey_limit) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + + c = s->ssh.session->current_crypto; + assert_non_null(c); + assert_int_equal(c->in_cipher->max_blocks, + rekey_limit / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, + rekey_limit / c->out_cipher->blocksize); + /* when strict kex is used, the newkeys reset the sequence number */ + if ((s->ssh.session->flags & SSH_SESSION_FLAG_KEX_STRICT) != 0) { + assert_int_equal(c->out_cipher->packets, s->ssh.session->send_seq); + assert_int_equal(c->in_cipher->packets, s->ssh.session->recv_seq); + } else { + /* Otherwise we have less encrypted packets than transferred + * (first are not encrypted) */ + assert_true(c->out_cipher->packets < s->ssh.session->send_seq); + assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); + } +} +static void sanity_check_session(void **state) +{ + sanity_check_session_size(state, bytes); +} + +/* We lower the rekey limits manually and check that the rekey + * really happens when sending data + */ +static void torture_rekey_send(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + sanity_check_session(state); + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* send ignore packets of up to 1KB to trigger rekey. Send little bit more + * to make sure it completes with all different ciphers */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* The rekey limit was restored in the new crypto to the same value */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); + /* Check that the secret hash is different than initially */ + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + +#ifdef WITH_SFTP +static void session_setup_sftp(void **state) +{ + struct torture_state *s = *state; + int rc; + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Initialize SFTP session */ + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); +} + +static int session_setup_sftp_client(void **state) +{ + struct torture_state *s = *state; + int rc; + + session_setup(state); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + session_setup_sftp(state); + + return 0; +} + +#define MAX_XFER_BUF_SIZE 16384 + +/* To trigger rekey by receiving data, the easiest thing is probably to + * use sftp + */ +static void torture_rekey_recv_size(void **state, uint64_t rekey_limit) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + int rc; + + sanity_check_session_size(state, rekey_limit); + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* Download a file */ + file = sftp_open(s->ssh.tsftp->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + unlink(libssh_tmp_file); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + if (bytesread == 0) { + break; /* EOF */ + } + assert_false(bytesread < 0); + + byteswritten = write(fd, buf, bytesread); + assert_int_equal(byteswritten, bytesread); + } + + rc = sftp_close(file); + assert_int_equal(rc, SSH_NO_ERROR); + close(fd); + + /* The rekey limit was restored in the new crypto to the same value */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, + rekey_limit / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, + rekey_limit / c->out_cipher->blocksize); + /* Check that the secret hash is different than initially */ + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); +} + +static void torture_rekey_recv(void **state) +{ + torture_rekey_recv_size(state, bytes); +} +#endif /* WITH_SFTP */ + +/* Rekey time requires rekey after specified time and is off by default. + * Setting the time to small enough value and waiting, we should trigger + * rekey on the first sent packet afterward. + */ +static void torture_rekey_time(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + uint32_t time = 3; /* 3 seconds */ + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_TIME, &time); + assert_ssh_return_code(s->ssh.session, rc); + /* The time is internally stored in microseconds */ + assert_int_equal(time * 1000, s->ssh.session->opts.rekey_time); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Send some data. This should not trigger rekey yet */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 8); + for (i = 0; i < 3; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* Check that the secret hash is the same */ + c = s->ssh.session->current_crypto; + assert_memory_equal(secret_hash, c->secret_hash, c->digest_len); + + /* Wait some more time */ + sleep(3); + + /* send some more data to trigger rekey and handle the + * key exchange "in background" */ + for (i = 0; i < 8; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + +/* We lower the rekey limits manually and check that the rekey + * really happens when sending data + */ +static void torture_rekey_server_send(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + const char *sshd_config = "RekeyLimit 2K none"; + + torture_update_sshd_config(state, sshd_config); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* send ignore packets of up to 1KB to trigger rekey */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < 20; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + +static void torture_rekey_different_kex(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + size_t secret_hash_len = 0; + const char *kex1 = "diffie-hellman-group14-sha256,curve25519-sha256,ecdh-sha2-nistp256"; + const char *kex2 = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,ecdh-sha2-nistp521"; + + /* Use short digest for initial key exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex1); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* The blocks limit is set correctly */ + sanity_check_session(state); + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + secret_hash_len = c->digest_len; + assert_int_equal(secret_hash_len, 32); /* SHA256 len */ + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Now try to change preference of key exchange algorithm to something with larger digest */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex2); + assert_ssh_return_code(s->ssh.session, rc); + + /* send ignore packets of up to 1KB to trigger rekey. Send little bit more + * to make sure the rekey it completes with all different ciphers (paddings */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 1000); + + c = s->ssh.session->current_crypto; + /* SHA256 len */ + if (c->digest_len != 32) { + break; + } + } + + /* The rekey limit was restored in the new crypto to the same value */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); + /* Check that the secret hash is different than initially */ + assert_int_equal(c->digest_len, 64); /* SHA512 len */ + assert_memory_not_equal(secret_hash, c->secret_hash, secret_hash_len); + /* Session ID stays same after one rekey */ + assert_memory_equal(secret_hash, c->session_id, secret_hash_len); + free(secret_hash); + + assert_int_equal(ssh_is_connected(s->ssh.session), 1); + assert_int_equal(s->ssh.session->session_state, SSH_SESSION_STATE_AUTHENTICATED); + + ssh_disconnect(s->ssh.session); +} + +static void torture_rekey_server_different_kex(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + size_t secret_hash_len = 0; + const char *sshd_config = "RekeyLimit 2K none"; + const char *kex1 = "diffie-hellman-group14-sha256,curve25519-sha256,ecdh-sha2-nistp256"; + const char *kex2 = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512"; + + /* Use short digest for initial key exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex1); + assert_ssh_return_code(s->ssh.session, rc); + + torture_update_sshd_config(state, sshd_config); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + secret_hash_len = c->digest_len; + assert_int_equal(secret_hash_len, 32); /* SHA256 len */ + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Now try to change preference of key exchange algorithm to something with larger digest */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex2); + assert_ssh_return_code(s->ssh.session, rc); + + /* send ignore packets of up to 1KB to trigger rekey. Send little bit more + * to make sure the rekey it completes with all different ciphers (paddings */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 1000); + + c = s->ssh.session->current_crypto; + /* SHA256 len */ + if (c->digest_len != 32) { + break; + } + } + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->digest_len, 64); /* SHA512 len */ + assert_memory_not_equal(secret_hash, c->secret_hash, secret_hash_len); + /* Session ID stays same after one rekey */ + assert_memory_equal(secret_hash, c->session_id, secret_hash_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + + +#ifdef WITH_SFTP +static int session_setup_sftp_server(void **state) +{ + const char *sshd_config = "RekeyLimit 2K none"; + + session_setup(state); + + torture_update_sshd_config(state, sshd_config); + + session_setup_sftp(state); + + return 0; +} + +static void torture_rekey_server_recv(void **state) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + int rc; + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* Download a file */ + file = sftp_open(s->ssh.tsftp->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + unlink(libssh_tmp_file); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + if (bytesread == 0) { + break; /* EOF */ + } + assert_false(bytesread < 0); + + byteswritten = write(fd, buf, bytesread); + assert_int_equal(byteswritten, bytesread); + } + + rc = sftp_close(file); + assert_int_equal(rc, SSH_NO_ERROR); + close(fd); + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); +} +#endif /* WITH_SFTP */ + +#ifdef WITH_ZLIB +/* This is disabled by OpenSSH since OpenSSH 7.4p1 */ +#if (OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR < 4) || OPENSSH_VERSION_MAJOR < 7 +/* Compression can be funky to get right after rekey + */ +static void torture_rekey_send_compression(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_send(state); +} + +#ifdef WITH_SFTP +static void torture_rekey_recv_compression(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_recv(state); +} +#endif /* WITH_SFTP */ +#endif + +/* Especially the delayed compression by openssh. + */ +static void torture_rekey_send_compression_delayed(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib@openssh.com"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_send(state); +} + +#ifdef WITH_SFTP +static void torture_rekey_recv_compression_delayed(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib@openssh.com"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_recv(state); +} +#endif /* WITH_SFTP */ +#endif /* WITH_ZLIB */ + +static void setup_server_for_good_guess(void *state) +{ + const char *default_sshd_config = "KexAlgorithms curve25519-sha256"; + const char *fips_sshd_config = "KexAlgorithms ecdh-sha2-nistp256"; + const char *sshd_config = default_sshd_config; + + if (ssh_fips_mode()) { + sshd_config = fips_sshd_config; + } + /* This sets an only supported kex algorithm that we do not have as a first + * option */ + torture_update_sshd_config(state, sshd_config); +} + +static void torture_rekey_guess_send(void **state) +{ + struct torture_state *s = *state; + + setup_server_for_good_guess(state); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + torture_rekey_send(state); +} + +static void torture_rekey_guess_wrong_send(void **state) +{ + struct torture_state *s = *state; + const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; + + /* This sets an only supported kex algorithm that we do not have as a first + * option */ + torture_update_sshd_config(state, sshd_config); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + torture_rekey_send(state); +} + +#ifdef WITH_SFTP +static void torture_rekey_guess_recv(void **state) +{ + struct torture_state *s = *state; + int rc; + + setup_server_for_good_guess(state); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + session_setup_sftp(state); + + torture_rekey_recv(state); +} + +static void torture_rekey_guess_wrong_recv(void **state) +{ + struct torture_state *s = *state; + const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; + int rc; + + /* This sets an only supported kex algorithm that we do not have as a first + * option */ + torture_update_sshd_config(state, sshd_config); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + session_setup_sftp(state); + + torture_rekey_recv(state); +} + +static void torture_rekey_guess_all_combinations(void **state) +{ + struct torture_state *s = *state; + char sshd_config[256] = ""; + char client_kex[256] = ""; + const char *supported = NULL; + struct ssh_tokens_st *s_tok = NULL; + uint64_t rekey_limit = 0; + const char *p = NULL; + int rc, i, j; + + /* The rekey limit is 1/2 of the transferred file size so we will likely get + * 2 rekeys per test, which still runs for acceptable time */ + rekey_limit = atoll(SSH_EXECUTABLE_SIZE); + rekey_limit /= 2; + + if (ssh_fips_mode()) { + supported = ssh_kex_get_fips_methods(SSH_KEX); + } else { + supported = ssh_kex_get_supported_method(SSH_KEX); + } + assert_non_null(supported); + + s_tok = ssh_tokenize(supported, ','); + assert_non_null(s_tok); + for (i = 0; s_tok->tokens[i]; i++) { + /* Skip algorithms not supported by the OpenSSH server. + * Check also for prefix matches to distinguish vendor-specific names + * such as sntrup761x25519-sha512 and the @openssh.com alias */ + if ((p = strstr(OPENSSH_KEX, s_tok->tokens[i])) == NULL || + (*(p + strlen(s_tok->tokens[i])) != ',' && + *(p + strlen(s_tok->tokens[i])) != '\0')) { + SSH_LOG(SSH_LOG_INFO, "Server: %s [skipping]", s_tok->tokens[i]); + continue; + } + SSH_LOG(SSH_LOG_INFO, "Server: %s", s_tok->tokens[i]); + snprintf(sshd_config, + sizeof(sshd_config), + "KexAlgorithms %s", + s_tok->tokens[i]); + /* This sets an only supported kex algorithm that we do not have as + * a first option in the client */ + torture_update_sshd_config(state, sshd_config); + + for (j = 0; s_tok->tokens[j]; j++) { + if (i == j) { + continue; + } + + session_setup(state); + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_REKEY_DATA, + &rekey_limit); + assert_ssh_return_code(s->ssh.session, rc); + + /* Client kex preference will have the second of the pair and the + * server one as a second to negotiate on the second attempt */ + snprintf(client_kex, + sizeof(client_kex), + "%s,%s", + s_tok->tokens[j], + s_tok->tokens[i]); + SSH_LOG(SSH_LOG_INFO, "Client: %s", client_kex); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_KEY_EXCHANGE, + client_kex); + assert_ssh_return_code(s->ssh.session, rc); + session_setup_sftp(state); + torture_rekey_recv_size(state, rekey_limit); + session_teardown(state); + } + } + + ssh_tokens_free(s_tok); +} +#endif /* WITH_SFTP */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_rekey_default, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_time, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_recv, + session_setup_sftp_client, + session_teardown), +#endif /* WITH_SFTP */ + cmocka_unit_test_setup_teardown(torture_rekey_send, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_different_kex, + session_setup, + session_teardown), +#ifdef WITH_ZLIB +#if (OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR < 4) || OPENSSH_VERSION_MAJOR < 7 + cmocka_unit_test_setup_teardown(torture_rekey_send_compression, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_recv_compression, + session_setup_sftp_client, + session_teardown), +#endif /* WITH_SFTP */ +#endif + cmocka_unit_test_setup_teardown(torture_rekey_send_compression_delayed, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_recv_compression_delayed, + session_setup_sftp_client, + session_teardown), +#endif /* WITH_SFTP */ +#endif /* WITH_ZLIB */ + /* TODO verify the two rekey are possible and the states are not broken after rekey */ + + cmocka_unit_test_setup_teardown(torture_rekey_server_different_kex, + session_setup, + session_teardown), + /* Note, that these tests modify the sshd_config so follow-up tests + * might get unexpected behavior if they do not update the server with + * torture_update_sshd_config() too */ + cmocka_unit_test_setup_teardown(torture_rekey_server_send, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_send, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_send, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_server_recv, + session_setup_sftp_server, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_recv, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_recv, + session_setup, + session_teardown), + cmocka_unit_test(torture_rekey_guess_all_combinations), +#endif /* WITH_SFTP */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_request_env.c b/src/libs/libssh-0.12.2/tests/client/torture_request_env.c new file mode 100644 index 000000000000..21806dfb6430 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_request_env.c @@ -0,0 +1,137 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_request_env(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + char buffer[4096] = {0}; + int nbytes; + int rc; + int lang_found = 0; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_env(c, "LC_LIBSSH", "LIBSSH_EXPORTED_VARIABLE"); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "echo $LC_LIBSSH"); + assert_ssh_return_code(session, rc); + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + printf("nbytes=%d\n", nbytes); + while (nbytes > 0) { +#if 1 + rc = fwrite(buffer, 1, nbytes, stdout); + assert_int_equal(rc, nbytes); +#endif + buffer[nbytes]='\0'; + if (strstr(buffer, "LIBSSH_EXPORTED_VARIABLE")) { + lang_found = 1; + break; + } + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer), 0); + } + assert_int_equal(lang_found, 1); + + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_request_env, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/client/torture_request_pty_modes.c b/src/libs/libssh-0.12.2/tests/client/torture_request_pty_modes.c new file mode 100644 index 000000000000..16d9b19cfdcc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_request_pty_modes.c @@ -0,0 +1,264 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* reads from the channel, expecting the given output */ +static int check_channel_output(ssh_channel c, const char *expected) +{ + char buffer[4096] = {0}; + int nbytes, offset = 0; + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + while (nbytes > 0) { + buffer[offset + nbytes] = '\0'; + ssh_log_hexdump("Read bytes:", + (unsigned char *)buffer, + offset + nbytes); + if (strstr(buffer, expected) != NULL) + { + return 1; + } + /* read on */ + offset = nbytes; + nbytes = ssh_channel_read(c, + buffer + offset, + sizeof(buffer) - offset - 1, + 0); + } + return 0; +} + +/* set explicit TTY modes and validate that the server uses them */ +static void torture_request_pty_modes_translate_ocrnl(void **state) +{ + const unsigned char modes[] = { + /* enable OCRNL */ + 73, 0, 0, 0, 1, + /* disable all other CR/NL handling */ + 34, 0, 0, 0, 0, + 35, 0, 0, 0, 0, + 36, 0, 0, 0, 0, + 72, 0, 0, 0, 0, + 74, 0, 0, 0, 0, + 75, 0, 0, 0, 0, + 0, /* TTY_OP_END */ + }; + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_pty_size_modes(c, "xterm", 80, 25, modes, sizeof(modes)); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect 2 newline characters */ + string_found = check_channel_output(c, ">TEST\n\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +/* if stdin is a TTY, its modes are passed to the server */ +static void torture_request_pty_modes_use_stdin_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + struct termios modes; + int stdin_backup_fd = -1; + int master_fd, slave_fd; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must be a TTY, so open one and replace the FD */ + stdin_backup_fd = dup(STDIN_FILENO); + rc = openpty(&master_fd, &slave_fd, NULL, NULL, NULL); + assert_int_equal(rc, 0); + dup2(master_fd, STDIN_FILENO); + assert_true(isatty(STDIN_FILENO)); + /* translate NL to CRNL on output to see a noticeable effect */ + memset(&modes, 0, sizeof(modes)); + tcgetattr(STDIN_FILENO, &modes); + modes.c_oflag |= ONLCR; + modes.c_iflag &= ~(ICRNL | INLCR | IGNCR); + tcsetattr(STDIN_FILENO, TCSANOW, &modes); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + close(master_fd); + close(slave_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect 2 carriage return characters + newline */ + string_found = check_channel_output(c, ">TEST\r\r\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +/* if stdin is NOT a TTY, default modes are passed to the server */ +static void torture_request_pty_modes_use_default_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + int stdin_backup_fd = -1; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must not a TTY - change the FD to something else */ + stdin_backup_fd = dup(STDIN_FILENO); + close(STDIN_FILENO); + rc = open("/dev/null", O_RDONLY); // reuses FD 0 now + assert_int_equal(rc, STDIN_FILENO); + assert_false(isatty(STDIN_FILENO)); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect the CRLF translated to newline */ + string_found = check_channel_output(c, ">TEST\r\r\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_request_pty_modes_translate_ocrnl, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_stdin_modes, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_default_modes, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/client/torture_scp.c b/src/libs/libssh-0.12.2/tests/client/torture_scp.c new file mode 100644 index 000000000000..fe3f239b34a1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_scp.c @@ -0,0 +1,664 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "libssh/scp.h" + +#include +#include +#include +#include +#include + +#define BUF_SIZE 1024 + +#define TEMPLATE BINARYDIR "/tests/home/alice/temp_dir_XXXXXX" +#define ALICE_HOME BINARYDIR "/tests/home/alice" + +/* store the original umask */ +mode_t old; + +struct scp_st { + struct torture_state *s; + char *tmp_dir; + char *tmp_dir_basename; +}; + +static int sshd_setup(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ts = (struct scp_st *)calloc(1, sizeof(struct scp_st)); + assert_non_null(ts); + + torture_setup_sshd_server((void **)&s, false); + assert_non_null(s); + + ts->s = s; + + *state = ts; + + return 0; +} + +static int sshd_teardown(void **state) +{ + struct scp_st *ts = NULL; + + ts = *((struct scp_st **)state); + assert_non_null(ts); + assert_non_null(ts->s); + + torture_teardown_sshd_server((void **)&(ts->s)); + + SAFE_FREE(ts); + + return 0; +} + +static int session_setup(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + char *tmp_dir = NULL; + char *tmp_dir_basename = NULL; + + struct passwd *pwd; + + int rc; + + assert_non_null(state); + + ts = *state; + + assert_non_null(ts); + assert_non_null(ts->s); + + s = ts->s; + + /* store the original umask and set a new one */ + old = umask(0022); + + /* Create temporary directory for alice */ + tmp_dir = torture_make_temp_dir(TEMPLATE); + assert_non_null(tmp_dir); + ts->tmp_dir = tmp_dir; + + tmp_dir_basename = ssh_basename(tmp_dir); + assert_non_null(tmp_dir_basename); + ts->tmp_dir_basename = tmp_dir_basename; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + assert_non_null(state); + ts = *((struct scp_st **)state); + + assert_non_null(ts->s); + s = ts->s; + + /* restore the umask */ + umask(old); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + assert_non_null(ts->tmp_dir); + torture_rmdirs(ts->tmp_dir); + + SAFE_FREE(ts->tmp_dir); + SAFE_FREE(ts->tmp_dir_basename); + + return 0; +} + +static void torture_scp_upload(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_a[BUF_SIZE]; + char buf[BUF_SIZE]; + FILE *file = NULL; + size_t len = 0; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload file "a" to alice's temp dir */ + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE, ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Init buffer content to be written */ + memset(expected_a, 'A', BUF_SIZE); + + /* For ssh_scp_push_file(), the file_name is the name of the file without + * path */ + rc = ssh_scp_push_file(scp, "a", BUF_SIZE, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, expected_a, BUF_SIZE); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Open file and check content */ + snprintf(buf, BUF_SIZE, "%s/a", ts->tmp_dir); + + file = fopen(buf, "r"); + assert_non_null(file); + + len = fread(buf, BUF_SIZE, 1, file); + assert_int_equal(len, 1); + assert_memory_equal(buf, expected_a, BUF_SIZE); + + fclose(file); +} + +static void torture_scp_upload_recursive(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_b[BUF_SIZE]; + char buf[BUF_SIZE]; + FILE *file = NULL; + size_t len = 0; + + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload directory "test_dir" containing file "b" to alice's temp dir */ + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, + ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Push directory where the new file will be copied */ + rc = ssh_scp_push_directory(scp, "test_dir", 0755); + assert_ssh_return_code(session, rc); + + memset(expected_b, 'B', BUF_SIZE); + + /* For ssh_scp_push_file(), the file_name is the name of the file without + * path */ + rc = ssh_scp_push_file(scp, "b", BUF_SIZE, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, expected_b, BUF_SIZE); + assert_ssh_return_code(session, rc); + + /* Leave the directory */ + rc = ssh_scp_leave_directory(scp); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Open file and check content */ + snprintf(buf, BUF_SIZE, "%s/test_dir/b", ts->tmp_dir); + + file = fopen(buf, "r"); + assert_non_null(file); + + len = fread(buf, BUF_SIZE, 1, file); + assert_int_equal(len, 1); + assert_memory_equal(buf, expected_b, BUF_SIZE); + + fclose(file); +} + +static void torture_scp_download(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_a[BUF_SIZE]; + char buf[BUF_SIZE]; + const char *remote_file = NULL; + + FILE *file = NULL; + int fd = 0; + + size_t size; + + int mode; + int rc; + + assert_non_null(state); + + ts = *state; + + assert_non_null(ts); + assert_non_null(ts->s); + + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Create file "a" for alice */ + memset(expected_a, 'A', BUF_SIZE); + + snprintf(buf, BUF_SIZE, "%s/a", ts->tmp_dir); + + fd = open(buf, O_WRONLY | O_CREAT, 0644); + assert_true(fd > 0); + + file = fdopen(fd, "w"); + assert_non_null(file); + + size = fwrite(expected_a, 1, BUF_SIZE, file); + assert_int_equal(size, BUF_SIZE); + fclose(file); + + /* Construct the file path */ + snprintf(buf, BUF_SIZE, "%s/a", ts->tmp_dir_basename); + + /* When reading, the location is the file path */ + scp = ssh_scp_new(session, SSH_SCP_READ, buf); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_NEWFILE); + + size = ssh_scp_request_get_size(scp); + assert_int_equal(size, BUF_SIZE); + + mode = ssh_scp_request_get_permissions(scp); + assert_int_equal(mode, 0644); + + remote_file = ssh_scp_request_get_filename(scp); + assert_non_null(remote_file); + assert_string_equal(remote_file, "a"); + + rc = ssh_scp_accept_request(scp); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_read(scp, buf, BUF_SIZE); + assert_int_equal(rc, size); + + assert_memory_equal(expected_a, buf, BUF_SIZE); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); +} + +static void torture_scp_download_recursive(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_b[BUF_SIZE]; + char buf[BUF_SIZE]; + const char *remote_file = NULL; + FILE *file = NULL; + int fd = 0; + + size_t size; + + int mode; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Create file "b" for alice */ + memset(expected_b, 'B', BUF_SIZE); + + snprintf(buf, BUF_SIZE, "%s/b", ts->tmp_dir); + + fd = open(buf, O_WRONLY | O_CREAT, 0644); + assert_true(fd > 0); + + file = fdopen(fd, "w"); + assert_non_null(file); + + size = fwrite(expected_b, 1, BUF_SIZE, file); + assert_int_equal(size, BUF_SIZE); + fclose(file); + + /* Copy the directory containing the file "b" */ + scp = ssh_scp_new(session, SSH_SCP_READ | SSH_SCP_RECURSIVE, + ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Receive the directory */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_NEWDIR); + + mode = ssh_scp_request_get_permissions(scp); + assert_int_equal(mode, 0700); + + remote_file = ssh_scp_request_get_filename(scp); + assert_non_null(remote_file); + assert_string_equal(remote_file, ts->tmp_dir_basename); + + rc = ssh_scp_accept_request(scp); + assert_ssh_return_code(session, rc); + + /* Receive the file "b" */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_NEWFILE); + + size = ssh_scp_request_get_size(scp); + assert_int_equal(size, BUF_SIZE); + + mode = ssh_scp_request_get_permissions(scp); + assert_int_equal(mode, 0644); + + remote_file = ssh_scp_request_get_filename(scp); + assert_non_null(remote_file); + assert_string_equal(remote_file, "b"); + + rc = ssh_scp_accept_request(scp); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_read(scp, buf, BUF_SIZE); + assert_int_equal(rc, size); + + /* Check if the content was the expected */ + assert_memory_equal(expected_b, buf, BUF_SIZE); + + /* Receive end of directory */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_ENDDIR); + + /* Receive end of communication */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_EOF); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); +} + +static void torture_scp_upload_newline(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + FILE *file = NULL; + + char buf[1024]; + char *rs = NULL; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload recursively trying to inject protocol messages */ + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, + ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Push directory where the new file will be copied */ + rc = ssh_scp_push_directory(scp, "test_inject", 0755); + assert_ssh_return_code(session, rc); + + /* Try to push file with injected protocol messages */ + rc = ssh_scp_push_file(scp, "original\nreplacedC0777 8 injected", 8, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, "original", 8); + assert_ssh_return_code(session, rc); + + /* Leave the directory */ + rc = ssh_scp_leave_directory(scp); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Open the file and check content */ + snprintf(buf, BUF_SIZE, "%s/test_inject/" + "original\\nreplacedC0777 8 injected", + ts->tmp_dir); + file = fopen(buf, "r"); + assert_non_null(file); + + rs = fgets(buf, 1024, file); + assert_non_null(rs); + assert_string_equal(buf, "original"); + + fclose(file); +} + +static void torture_scp_upload_appended_command(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + FILE *file = NULL; + + char buf[1024]; + char *rs = NULL; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload a file path with a command appended */ + + /* Append a command to the file path */ + snprintf(buf, BUF_SIZE, "%s" + "/;touch hack", + ts->tmp_dir); + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, + buf); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Push directory where the new file will be copied */ + rc = ssh_scp_push_directory(scp, ";touch hack", 0755); + assert_ssh_return_code(session, rc); + + /* Try to push file */ + rc = ssh_scp_push_file(scp, "original", 8, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, "original", 8); + assert_ssh_return_code(session, rc); + + /* Leave the directory */ + rc = ssh_scp_leave_directory(scp); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Make sure the command was not executed */ + snprintf(buf, BUF_SIZE, ALICE_HOME "/hack"); + file = fopen(buf, "r"); + assert_null(file); + + /* Open the file and check content */ + snprintf(buf, BUF_SIZE, "%s" + "/;touch hack/original", + ts->tmp_dir); + + file = fopen(buf, "r"); + assert_non_null(file); + + rs = fgets(buf, 1024, file); + assert_non_null(rs); + assert_string_equal(buf, "original"); + + fclose(file); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_scp_upload, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_upload_recursive, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_download, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_download_recursive, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_upload_newline, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_upload_appended_command, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_session.c b/src/libs/libssh-0.12.2/tests/client/torture_session.c new file mode 100644 index 000000000000..f3d3ead260d9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_session.c @@ -0,0 +1,551 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2012 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#define BUFLEN 4096 +static char buffer[BUFLEN]; + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_channel_read_error(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + int fd; + int i; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(channel, "hexdump -C /dev/urandom"); + assert_ssh_return_code(session, rc); + + /* send crap and wait for server to send us a disconnect */ + fd = ssh_get_fd(session); + assert_true(fd > 2); + rc = write(fd, "AAAA", 4); + assert_int_equal(rc, 4); + + for (i=0;i<20;++i){ + rc = ssh_channel_read(channel,buffer,sizeof(buffer),0); + if (rc == SSH_ERROR) + break; + } +#if OPENSSH_VERSION_MAJOR == 6 && OPENSSH_VERSION_MINOR >= 7 + /* With openssh 6.7 this doesn't produce and error anymore */ + assert_ssh_return_code(session, rc); +#else + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +#endif + + ssh_channel_free(channel); +} + +static void torture_channel_poll_timeout_valid(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(channel, "echo -n ABCD"); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, strlen("ABCD")); +} + +static void torture_channel_poll_timeout(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + int fd; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_true(fd > 2); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_OK); + + /* send crap and for server to send us a disconnect */ + rc = write(fd, "AAAA", 4); + assert_int_equal(rc, 4); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_ERROR); + + ssh_channel_free(channel); +} + +/* + * Check that the client can properly handle the error returned from the server + * when the maximum number of sessions is exceeded. + * + * Related: T75, T239 + * + */ +static void torture_max_sessions(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char max_session_config[32] = {0}; +#define MAX_CHANNELS 10 + ssh_channel channels[MAX_CHANNELS + 1]; + size_t i; + int rc; + + snprintf(max_session_config, + sizeof(max_session_config), + "MaxSessions %u", + MAX_CHANNELS); + + /* Update server configuration to limit number of sessions */ + torture_update_sshd_config(state, max_session_config); + + /* Open the maximum number of channel sessions */ + for (i = 0; i < MAX_CHANNELS; i++) { + channels[i] = ssh_channel_new(session); + assert_non_null(channels[i]); + + rc = ssh_channel_open_session(channels[i]); + assert_ssh_return_code(session, rc); + } + + /* Try to open an extra session and expect failure */ + channels[i] = ssh_channel_new(session); + assert_non_null(channels[i]); + + rc = ssh_channel_open_session(channels[i]); + assert_int_equal(rc, SSH_ERROR); + + /* Free the unused channel */ + ssh_channel_free(channels[i]); + + /* Close and free channels */ + for (i = 0; i < MAX_CHANNELS; i++) { + ssh_channel_close(channels[i]); + ssh_channel_free(channels[i]); + } +#undef MAX_CHANNELS +} + +static void torture_no_more_sessions(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channels[2]; + int rc; + + /* Open a channel session */ + channels[0] = ssh_channel_new(session); + assert_non_null(channels[0]); + + rc = ssh_channel_open_session(channels[0]); + assert_ssh_return_code(session, rc); + + /* Send no-more-sessions@openssh.com global request */ + rc = ssh_request_no_more_sessions(session); + assert_ssh_return_code(session, rc); + + /* Try to open an extra session and expect failure */ + channels[1] = ssh_channel_new(session); + assert_non_null(channels[1]); + + rc = ssh_channel_open_session(channels[1]); + assert_int_equal(rc, SSH_ERROR); + + /* Free the unused channel */ + ssh_channel_free(channels[1]); + + /* Close and free open channel */ + ssh_channel_close(channels[0]); + ssh_channel_free(channels[0]); +} + +static void torture_channel_delayed_close(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + char buff[256] = {0}; + + int rc; + int fd; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_true(fd > 2); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_channel_read(channel, buff, 256, 0); + } while(rc > 0); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_EOF); + + ssh_channel_free(channel); + +} + +/* Ensure that calling 'ssh_channel_poll' on a freed channel does not lead to + * segmentation faults. */ +static void torture_freed_channel_poll(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_poll(channel, 0); + assert_int_equal(rc, SSH_ERROR); +} + +/* Ensure that calling 'ssh_channel_read_nonblocking' on a freed channel does + * not lead to segmentation faults. */ +static void torture_freed_channel_read_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + char buff[256] = {0}; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_read_nonblocking(channel, buff, 256, 0); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +static void torture_channel_exit_status(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel = NULL; + char request[256]; + uint32_t exit_status = (uint32_t)-1; + int rc; + + rc = snprintf(request, sizeof(request), "true"); + assert_return_code(rc, errno); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + exit_status = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + assert_ssh_return_code(session, rc); + assert_int_equal(exit_status, 0); +} + +static void torture_channel_exit_signal(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel = NULL; + char request[256]; + uint32_t exit_status = (uint32_t)-1; + char *exit_signal = NULL; + int core_dumped = false; + int rc; + + rc = snprintf(request, sizeof(request), "cat"); + assert_return_code(rc, errno); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + rc = ssh_channel_request_send_signal(channel, "TERM"); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_get_exit_state(channel, + &exit_status, + &exit_signal, + &core_dumped); + + assert_ssh_return_code(session, rc); + assert_int_equal(exit_status, (uint32_t)-1); + assert_string_equal(exit_signal, "TERM"); + SAFE_FREE(exit_signal); +} + +static void +torture_channel_read_stderr(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* This writes to standard error "pipe" */ + rc = ssh_channel_request_exec(channel, "echo -n ABCD >&2"); + assert_ssh_return_code(session, rc); + + /* No data in stdout */ + rc = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + assert_int_equal(rc, 0); + + /* poll should say how much we can read */ + rc = ssh_channel_poll(channel, 1); + assert_int_equal(rc, strlen("ABCD")); + + /* Everything in stderr */ + rc = ssh_channel_read(channel, buffer, sizeof(buffer), 1); + assert_int_equal(rc, strlen("ABCD")); + + buffer[rc] = '\0'; + assert_string_equal("ABCD", buffer); + + ssh_channel_free(channel); +} + +static void torture_pubkey_hash(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char *hash = NULL; + char *hexa = NULL; + int rc = 0; + + /* bad arguments */ + rc = ssh_get_pubkey_hash(session, NULL); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_get_pubkey_hash(NULL, (unsigned char **)&hash); + assert_int_equal(rc, SSH_ERROR); + + /* deprecated, but should be covered by tests! */ + rc = ssh_get_pubkey_hash(session, (unsigned char **)&hash); + if (ssh_fips_mode()) { + /* When in FIPS mode, expect the call to fail */ + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, MD5_DIGEST_LEN); + + hexa = ssh_get_hexa((unsigned char *)hash, rc); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(hexa, + "ee:80:7f:61:f9:d5:be:f1:96:86:cc:96:7a:db:7a:7b"); + + SSH_STRING_FREE_CHAR(hexa); + } +} + +static void torture_openssh_banner_version(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int openssh_version = ssh_get_openssh_version(session); + int cmake_openssh_version = SSH_VERSION_INT(OPENSSH_VERSION_MAJOR, OPENSSH_VERSION_MINOR, 0); + + assert_int_equal(openssh_version, cmake_openssh_version); +} + + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_channel_read_error, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_poll_timeout_valid, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_poll_timeout, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_max_sessions, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_no_more_sessions, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_delayed_close, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_poll, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_read_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_exit_status, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_exit_signal, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_read_stderr, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_pubkey_hash, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_openssh_banner_version, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_aio.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_aio.c new file mode 100644 index 000000000000..9bd5ff4e2795 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_aio.c @@ -0,0 +1,778 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +#define DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(TEST_NAME) \ + { \ + #TEST_NAME, \ + TEST_NAME, \ + session_setup, \ + session_teardown, \ + NULL \ + }, \ + { \ + #TEST_NAME"_proxyjump", \ + TEST_NAME, \ + session_proxyjump_setup, \ + session_teardown, \ + NULL \ + } + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_setup_sshd_servers(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + /* this will take care of the server1 teardown too */ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup_helper(void **state, bool with_proxyjump) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + if (with_proxyjump) { + s->ssh.session = torture_ssh_session_proxyjump(); + } else { + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + } + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_setup(void **state) +{ + return session_setup_helper(state, false); +} + +static int session_proxyjump_setup(void **state) +{ + return session_setup_helper(state, true); +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_aio_read_file(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + struct { + char *buf; + ssize_t bytes_read; + } a = {0}, b = {0}; + + sftp_file file = NULL; + sftp_attributes file_attr = NULL; + int fd; + + size_t chunk_size; + int in_flight_requests = 20; + + sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; + sftp_limits_t li = NULL; + + size_t file_size; + size_t total_bytes_requested; + size_t to_read, total_bytes_read; + ssize_t bytes_requested; + + int i, rc; + + /* Get the max limit for reading, use it as the chunk size */ + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_read_length; + + a.buf = calloc(chunk_size, 1); + assert_non_null(a.buf); + + b.buf = calloc(chunk_size, 1); + assert_non_null(b.buf); + + aio_queue = ssh_list_new(); + assert_non_null(aio_queue); + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + fd = open(SSH_EXECUTABLE, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + /* Get the file size */ + file_attr = sftp_stat(t->sftp, SSH_EXECUTABLE); + assert_non_null(file_attr); + file_size = file_attr->size; + + total_bytes_requested = 0; + for (i = 0; + i < in_flight_requests && total_bytes_requested < file_size; + ++i) { + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(bytes_requested, to_read); + total_bytes_requested += bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + assert_int_equal(rc, SSH_OK); + } + + total_bytes_read = 0; + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, chunk_size); + assert_int_not_equal(a.bytes_read, SSH_ERROR); + + total_bytes_read += (size_t)a.bytes_read; + if (total_bytes_read != file_size) { + assert_int_equal((size_t)a.bytes_read, chunk_size); + /* + * Failure of this assertion means that a short + * read is encountered but we have not reached + * the end of file yet. A short read before reaching + * the end of file should not occur for our test where + * the chunk size respects the max limit for reading. + */ + } + + /* + * Check whether the bytes read above are bytes + * present in the file or some garbage was stored + * in the buffer supplied to sftp_aio_wait_read(). + */ + b.bytes_read = read(fd, b.buf, a.bytes_read); + assert_int_equal(a.bytes_read, b.bytes_read); + + rc = memcmp(a.buf, b.buf, (size_t)a.bytes_read); + assert_int_equal(rc, 0); + + /* Issue more read requests if needed */ + if (total_bytes_requested == file_size) { + continue; + } + + /* else issue more requests */ + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(bytes_requested, to_read); + total_bytes_requested += bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + assert_int_equal(rc, SSH_OK); + } + + /* + * Check whether sftp server responds with an + * eof for more requests. + */ + bytes_requested = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes_requested, chunk_size); + + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, chunk_size); + assert_int_equal(a.bytes_read, 0); + + /* Clean up */ + sftp_attributes_free(file_attr); + close(fd); + sftp_close(file); + ssh_list_free(aio_queue); + free(b.buf); + free(a.buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_read_more_than_cap(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + + char *buf = NULL; + ssize_t bytes; + + /* Get the max limit for reading */ + li = sftp_limits(t->sftp); + assert_non_null(li); + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Try reading more than the max limit */ + bytes = sftp_aio_begin_read(file, + li->max_read_length * 2, + &aio); + assert_int_equal(bytes, li->max_read_length); + + buf = calloc(li->max_read_length, 1); + assert_non_null(buf); + + bytes = sftp_aio_wait_read(&aio, buf, li->max_read_length); + assert_int_not_equal(bytes, SSH_ERROR); + + free(buf); + sftp_close(file); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_file(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char file_path[128] = {0}; + sftp_file file = NULL; + int fd; + + struct { + char *buf; + ssize_t bytes; + } wr = {0}, rd = {0}; + + size_t chunk_size; + ssize_t bytes_requested; + int in_flight_requests = 2; + + sftp_limits_t li = NULL; + sftp_aio *aio_queue = NULL; + int rc, i; + + /* Get the max limit for writing, use it as the chunk size */ + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_write_length; + + rd.buf = calloc(chunk_size, 1); + assert_non_null(rd.buf); + + wr.buf = calloc(chunk_size, 1); + assert_non_null(wr.buf); + + aio_queue = malloc(sizeof(sftp_aio) * in_flight_requests); + assert_non_null(aio_queue); + + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + fd = open(file_path, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + for (i = 0; i < in_flight_requests; ++i) { + bytes_requested = sftp_aio_begin_write(file, + wr.buf, + chunk_size, + &aio_queue[i]); + assert_int_equal(bytes_requested, chunk_size); + } + + for (i = 0; i < in_flight_requests; ++i) { + wr.bytes = sftp_aio_wait_write(&aio_queue[i]); + assert_int_equal(wr.bytes, chunk_size); + + /* + * Check whether the bytes written to the file + * by SFTP AIO write api were the bytes present + * in the buffer to write or some garbage was + * written to the file. + */ + rd.bytes = read(fd, rd.buf, wr.bytes); + assert_int_equal(rd.bytes, wr.bytes); + + rc = memcmp(rd.buf, wr.buf, wr.bytes); + assert_int_equal(rc, 0); + } + + /* Clean up */ + close(fd); + sftp_close(file); + free(aio_queue); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(wr.buf); + free(rd.buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_more_than_cap(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_limits_t li = NULL; + char *buf = NULL; + size_t buf_size; + + char file_path[128] = {0}; + sftp_file file = NULL; + + sftp_aio aio = NULL; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + + buf_size = li->max_write_length * 2; + buf = calloc(buf_size, 1); + assert_non_null(buf); + + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test_cap", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* Try writing more than the max limit for writing */ + bytes = sftp_aio_begin_write(file, buf, buf_size, &aio); + assert_int_equal(bytes, li->max_write_length); + + bytes = sftp_aio_wait_write(&aio); + assert_int_equal(bytes, li->max_write_length); + + /* Clean up */ + sftp_close(file); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_read_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char *buf = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_limits_t li = NULL; + + size_t chunk_size; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_read_length; + + buf = calloc(chunk_size, 1); + assert_non_null(buf); + + /* Open a file for reading */ + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Passing NULL as the sftp file handle */ + bytes = sftp_aio_begin_read(NULL, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing 0 as the number of bytes to read */ + bytes = sftp_aio_begin_read(file, 0, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* + * Passing NULL instead of a pointer to a location to + * store an aio handle. + */ + bytes = sftp_aio_begin_read(file, chunk_size, NULL); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL instead of a pointer to an aio handle */ + bytes = sftp_aio_wait_read(NULL, buf, sizeof(buf)); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL as the buffer's address */ + bytes = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes, chunk_size); + + bytes = sftp_aio_wait_read(&aio, NULL, sizeof(buf)); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing 0 as the buffer size */ + bytes = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes, chunk_size); + + bytes = sftp_aio_wait_read(&aio, buf, 0); + assert_int_equal(bytes, SSH_ERROR); + + /* + * Test for the scenario when the number + * of bytes read exceed the buffer size. + */ + rc = sftp_seek(file, 0); /* Seek to the start of file */ + assert_int_equal(rc, 0); + + bytes = sftp_aio_begin_read(file, 2, &aio); + assert_int_equal(bytes, 2); + + bytes = sftp_aio_wait_read(&aio, buf, 1); + assert_int_equal(bytes, SSH_ERROR); + + sftp_close(file); + free(buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char *buf = NULL; + + char file_path[128] = {0}; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_limits_t li = NULL; + + size_t chunk_size; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_write_length; + + buf = calloc(chunk_size, 1); + assert_non_null(buf); + + /* Open a file for writing */ + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test_negative", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* Passing NULL as the sftp file handle */ + bytes = sftp_aio_begin_write(NULL, buf, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL as the buffer's address */ + bytes = sftp_aio_begin_write(file, NULL, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing 0 as the size of buffer */ + bytes = sftp_aio_begin_write(file, buf, 0, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL instead of a pointer to a location to store an aio handle */ + bytes = sftp_aio_begin_write(file, buf, chunk_size, NULL); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL instead of a pointer to an aio handle */ + bytes = sftp_aio_wait_write(NULL); + assert_int_equal(bytes, SSH_ERROR); + + sftp_close(file); + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(buf); + sftp_limits_free(li); +} + +/* + * Test that waiting for read responses in an order different from the + * sending order of corresponding read requests works properly. + * + * (For example, if Requests Rq1 and Rq2 have responses Rs1 and Rs2 + * respectively, and Rq1 is sent first followed by Rq2. Then waiting for + * response Rs2 first and then Rs1 should work properly) + */ +static void torture_sftp_aio_read_unordered_wait(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_file file = NULL; + sftp_aio aio_1 = NULL, aio_2 = NULL; + ssize_t bytes_requested, bytes_read; + + struct { + /* buffer to store read data */ + char *buf; + + /* buffer to store data expected to be read */ + char *expected; + + /* + * length of the data to read. Keep this length small enough so that we + * don't get short reads due to the sftp limits. + */ + size_t len; + } r1 = {0}, r2 = {0}; + + int fd, rc; + + /* Initialize r1 */ + r1.len = 10; + + r1.buf = calloc(r1.len, 1); + assert_non_null(r1.buf); + + r1.expected = calloc(r1.len, 1); + assert_non_null(r1.expected); + + /* Initialize r2 */ + r2.len = 20; + + r2.buf = calloc(r2.len, 1); + assert_non_null(r2.buf); + + r2.expected = calloc(r2.len, 1); + assert_non_null(r2.expected); + + /* Get data that is expected to be read from the file */ + fd = open(SSH_EXECUTABLE, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + bytes_read = read(fd, r1.expected, r1.len); + assert_int_equal(bytes_read, r1.len); + + bytes_read = read(fd, r2.expected, r2.len); + assert_int_equal(bytes_read, r2.len); + + /* Open an sftp file for reading */ + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* + * Issue 2 consecutive read requests (send the second request immediately + * after sending the first without waiting for the first's response) + */ + bytes_requested = sftp_aio_begin_read(file, r1.len, &aio_1); + assert_int_equal(bytes_requested, r1.len); + + bytes_requested = sftp_aio_begin_read(file, r2.len, &aio_2); + assert_int_equal(bytes_requested, r2.len); + + /* + * Wait for the responses in opposite order (Instead of waiting for response + * 1 first and then response 2, wait for response 2 first and then wait for + * response 1) + */ + bytes_read = sftp_aio_wait_read(&aio_2, r2.buf, r2.len); + assert_int_equal(bytes_read, r2.len); + assert_memory_equal(r2.buf, r2.expected, r2.len); + + bytes_read = sftp_aio_wait_read(&aio_1, r1.buf, r1.len); + assert_int_equal(bytes_read, r1.len); + assert_memory_equal(r1.buf, r1.expected, r1.len); + + /* Clean up */ + sftp_close(file); + + rc = close(fd); + assert_int_equal(rc, 0); + + free(r2.expected); + free(r2.buf); + + free(r1.expected); + free(r1.buf); +} + +/* + * Test that waiting for write responses in an order different from the + * sending order of corresponding write requests works properly. + * + * (For example, if Requests Rq1 and Rq2 have responses Rs1 and Rs2 + * respectively, and Rq1 is sent first followed by Rq2. Then waiting for + * response Rs2 first and then Rs1 should work properly) + */ +static void torture_sftp_aio_write_unordered_wait(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char file_path[128] = {0}; + sftp_file file = NULL; + sftp_aio aio_1 = NULL, aio_2 = NULL; + ssize_t bytes_requested, bytes_written, bytes_read; + size_t i; + int rc, fd; + + struct { + /* + * length of the data to write. Keep this length small enough so that we + * don't get short writes due to the sftp limits + */ + size_t len; + + /* data to write */ + char *data; + + /* buffer used to validate the written data */ + char *buf; + } r1 = {0}, r2 = {0}; + + /* Initialize r1 */ + r1.len = 10; + + r1.data = calloc(r1.len, 1); + assert_non_null(r1.data); + + for (i = 0; i < r1.len; ++i) { + r1.data[i] = (char)rand(); + } + + r1.buf = calloc(r1.len, 1); + assert_non_null(r1.buf); + + /* Initialize r2 */ + r2.len = 20; + + r2.data = calloc(r2.len, 1); + assert_non_null(r2.data); + + for (i = 0; i < r2.len; ++i) { + r2.data[i] = (char)rand(); + } + + r2.buf = calloc(r2.len, 1); + assert_non_null(r2.buf); + + /* Open an sftp file for writing */ + snprintf(file_path, + sizeof(file_path), + "%s/libssh_sftp_aio_write_unordered_wait", + t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* + * Issue two consecutive write requests (send the second request immediately + * after sending the first without waiting for the first's response) + */ + bytes_requested = sftp_aio_begin_write(file, r1.data, r1.len, &aio_1); + assert_int_equal(bytes_requested, r1.len); + + bytes_requested = sftp_aio_begin_write(file, r2.data, r2.len, &aio_2); + assert_int_equal(bytes_requested, r2.len); + + /* + * Wait for the responses in opposite order (Instead of waiting for response + * 1 first and then response 2, wait for response 2 first and then wait for + * response 1) + */ + bytes_written = sftp_aio_wait_write(&aio_2); + assert_int_equal(bytes_written, r2.len); + + bytes_written = sftp_aio_wait_write(&aio_1); + assert_int_equal(bytes_written, r1.len); + + /* + * Validate that the data has been written to the file correctly by reading + * from the file. + */ + fd = open(file_path, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + /* Validate that write request 1's data has been written to file */ + bytes_read = read(fd, r1.buf, r1.len); + assert_int_equal(bytes_read, r1.len); + assert_memory_equal(r1.data, r1.buf, r1.len); + + /* Validate that write request 2's data has been written to file */ + bytes_read = read(fd, r2.buf, r2.len); + assert_int_equal(bytes_read, r2.len); + assert_memory_equal(r2.data, r2.buf, r2.len); + + /* Clean up */ + rc = close(fd); + assert_int_equal(rc, 0); + + sftp_close(file); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(r2.buf); + free(r2.data); + + free(r1.buf); + free(r1.data); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_read_file), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_read_more_than_cap), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_write_file), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_write_more_than_cap), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_read_negative), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_write_negative), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_read_unordered_wait), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_write_unordered_wait), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_benchmark.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_benchmark.c new file mode 100644 index 000000000000..6b2bea5b3ee5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_benchmark.c @@ -0,0 +1,133 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_benchmark_write_read(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + ssh_session session = s->ssh.session; + sftp_file file = NULL; + struct stat sb = { + .st_size = 0, + }; + uint8_t buf_16k[MAX_XFER_BUF_SIZE]; + char local_path[1024] = {0}; + ssize_t bwritten, nread; + size_t i; + int rc; + + memset(buf_16k, 'X', sizeof(buf_16k)); + + snprintf(local_path, sizeof(local_path), "%s/128M.dat", t->testdir); + + file = sftp_open(sftp, local_path, O_CREAT|O_WRONLY|O_TRUNC, 0644); + assert_non_null(file); + + /* Write 128M */ + for (i = 0; i < 0x2000; i++) { + bwritten = sftp_write(file, buf_16k, sizeof(buf_16k)); + assert_int_equal(bwritten, sizeof(buf_16k)); + } + + rc = sftp_close(file); + assert_ssh_return_code(session, rc); + + /* Check that 128M has been written */ + rc = stat(local_path, &sb); + assert_int_equal(sb.st_size, 0x8000000); + + file = sftp_open(sftp, local_path, O_RDONLY, 0); + assert_non_null(file); + + for (;;) { + nread = sftp_read(file, buf_16k, sizeof(buf_16k)); + if (nread == 0) { + break; /* EOF */ + } + assert_int_equal(nread, sizeof(buf_16k)); + } + + rc = sftp_close(file); + assert_ssh_return_code(session, rc); + + unlink(local_path); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_benchmark_write_read, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_canonicalize_path.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_canonicalize_path.c new file mode 100644 index 000000000000..8d5f90744e94 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_canonicalize_path.c @@ -0,0 +1,97 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_canonicalize_path(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *canonicalized_path = NULL; + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + canonicalized_path = sftp_canonicalize_path(t->sftp, "."); + assert_non_null(canonicalized_path); + + assert_string_equal(canonicalized_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(canonicalized_path); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_canonicalize_path, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_dir.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_dir.c new file mode 100644 index 000000000000..1c4f8391b7e2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_dir.c @@ -0,0 +1,104 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_mkdir(void **state) { + struct torture_state *s = *state; + + struct torture_sftp *t = s->ssh.tsftp; + char tmpdir[128] = {0}; + int rc; + + assert_non_null(t); + + snprintf(tmpdir, sizeof(tmpdir) - 1, "%s/mkdir_test", t->testdir); + + rc = sftp_mkdir(t->sftp, tmpdir, 0755); + if(rc != SSH_OK) + fprintf(stderr,"error:%s\n",ssh_get_error(t->sftp->session)); + assert_true(rc == 0); + + /* check if it really has been created */ + assert_true(torture_isdir(tmpdir)); + + rc = sftp_rmdir(t->sftp, tmpdir); + assert_true(rc == 0); + + /* check if it has been deleted */ + assert_false(torture_isdir(tmpdir)); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_mkdir, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_expand_path.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_expand_path.c new file mode 100644 index 000000000000..85ef01084bf9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_expand_path.c @@ -0,0 +1,125 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_expand_path(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *expanded_path = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, "expand-path@openssh.com", "1"); + if (rc == 0) { + skip(); + } + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + /* testing for a absolute path */ + expanded_path = sftp_expand_path(t->sftp, "~/."); + assert_non_null(expanded_path); + + assert_string_equal(expanded_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(expanded_path); + + /* testing for a relative path */ + expanded_path = sftp_expand_path(t->sftp, "."); + assert_non_null(expanded_path); + + assert_string_equal(expanded_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(expanded_path); + + /* passing a NULL sftp session */ + expanded_path = sftp_expand_path(NULL, "~/."); + assert_null(expanded_path); + + /* passing an invalid path */ + expanded_path = sftp_expand_path(t->sftp, "/...//"); + assert_null(expanded_path); + + /* passing null path */ + expanded_path = sftp_expand_path(t->sftp, NULL); + assert_null(expanded_path); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_expand_path, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_ext.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_ext.c new file mode 100644 index 000000000000..ad4bbaa1c255 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_ext.c @@ -0,0 +1,35 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +static void torture_sftp_ext_new(void **state) { + sftp_ext x; + + (void) state; + + x = sftp_ext_new(); + assert_non_null(x); + assert_int_equal(x->count, 0); + assert_null(x->name); + assert_null(x->data); + + sftp_ext_free(x); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_sftp_ext_new), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_fsync.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_fsync.c new file mode 100644 index 000000000000..ac685b94cdac --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_fsync.c @@ -0,0 +1,137 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_fsync(void **state) { + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE] = {0}; + char buf_verify[MAX_XFER_BUF_SIZE] = {0}; + size_t count; + size_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + int rc; + FILE *fp; + struct stat sb; + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + assert_return_code(fd, errno); + close(fd); + unlink(libssh_tmp_file); + + file = sftp_open(t->sftp, libssh_tmp_file, O_WRONLY | O_CREAT, 0600); + assert_non_null(file); + + rc = lstat(libssh_tmp_file, &sb); + assert_return_code(rc, errno); + + snprintf(buf, sizeof(buf), "libssh fsync test\n"); + count = strlen(buf) + 1; + + byteswritten = sftp_write(file, buf, count); + assert_int_equal(byteswritten, count); + + rc = sftp_fsync(file); + assert_return_code(rc, errno); + + fp = fopen(libssh_tmp_file, "r"); + assert_non_null(fp); + + rc = fstat(fileno(fp), &sb); + assert_return_code(rc, errno); + + bytesread = fread(buf_verify, sizeof(buf_verify), 1, fp); + if (bytesread == 0) { + if (!feof(fp)) { + assert_int_equal(bytesread, count); + } + } + assert_string_equal(buf, buf_verify); + + sftp_close(file); + fclose(fp); + unlink(libssh_tmp_file); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_fsync, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_get_users_groups_by_id.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_get_users_groups_by_id.c new file mode 100644 index 000000000000..ed85dcbb654a --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_get_users_groups_by_id.c @@ -0,0 +1,263 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + /* + * The SFTP server used for testing is executed as a separate binary, which + * is making the uid_wrapper lose information about what user is used, and + * therefore, pwd is initialized to some bad value. + * If the embedded version using internal-sftp is used in sshd, it works ok. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + unsetenv("TORTURE_SFTP_SERVER"); + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_get_users_by_id(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *alice_pwd = NULL; + struct passwd *bob_pwd = NULL; + struct passwd *root_pwd = NULL; + sftp_name_id_map users_map = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, + "users-groups-by-id@openssh.com", + "1"); + if (rc == 0) { + skip(); + } + + alice_pwd = getpwnam("alice"); + assert_non_null(alice_pwd); + + bob_pwd = getpwnam("bob"); + assert_non_null(bob_pwd); + + root_pwd = getpwnam("root"); + assert_non_null(root_pwd); + + /* test for null */ + rc = sftp_get_users_groups_by_id(t->sftp, NULL, NULL); + assert_int_equal(rc, -1); + + /* test for 0 users */ + users_map = sftp_name_id_map_new(0); + + rc = sftp_get_users_groups_by_id(t->sftp, users_map, NULL); + assert_int_equal(rc, 0); + + sftp_name_id_map_free(users_map); + + /* test for 3 users */ + users_map = sftp_name_id_map_new(3); + + users_map->ids[0] = alice_pwd->pw_uid; + users_map->ids[1] = bob_pwd->pw_uid; + users_map->ids[2] = root_pwd->pw_uid; + + rc = sftp_get_users_groups_by_id(t->sftp, users_map, NULL); + assert_int_equal(rc, 0); + assert_string_equal(users_map->names[0], "alice"); + assert_string_equal(users_map->names[1], "bob"); + assert_string_equal(users_map->names[2], "root"); + + sftp_name_id_map_free(users_map); + + /* test for invalid uids */ + users_map = sftp_name_id_map_new(2); + + users_map->ids[0] = alice_pwd->pw_uid; + users_map->ids[1] = 42; /* invalid uid */ + rc = sftp_get_users_groups_by_id(t->sftp, users_map, NULL); + + assert_int_equal(rc, 0); + assert_string_equal(users_map->names[0], "alice"); + assert_string_equal(users_map->names[1], ""); + + sftp_name_id_map_free(users_map); +} + +static void torture_sftp_get_groups_by_id(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *alice_pwd = NULL; + struct passwd *root_pwd = NULL; + sftp_name_id_map groups_map = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, + "users-groups-by-id@openssh.com", + "1"); + + if (rc == 0) { + skip(); + } + + alice_pwd = getpwnam("alice"); + assert_non_null(alice_pwd); + + root_pwd = getpwnam("root"); + assert_non_null(root_pwd); + + /* test for 2 groups */ + groups_map = sftp_name_id_map_new(2); + + groups_map->ids[0] = alice_pwd->pw_gid; + groups_map->ids[1] = root_pwd->pw_gid; + + rc = sftp_get_users_groups_by_id(t->sftp, NULL, groups_map); + assert_int_equal(rc, 0); + assert_string_equal(groups_map->names[0], "users"); + assert_string_equal(groups_map->names[1], "root"); + + sftp_name_id_map_free(groups_map); + + /* test for invalid gids */ + groups_map = sftp_name_id_map_new(2); + + groups_map->ids[0] = alice_pwd->pw_gid; + groups_map->ids[1] = 42; /* invalid gid */ + + rc = sftp_get_users_groups_by_id(t->sftp, NULL, groups_map); + assert_int_equal(rc, 0); + assert_string_equal(groups_map->names[0], "users"); + assert_string_equal(groups_map->names[1], ""); + + sftp_name_id_map_free(groups_map); +} + +static void torture_sftp_get_users_groups_by_id(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *alice_pwd = NULL; + struct passwd *bob_pwd = NULL; + struct passwd *root_pwd = NULL; + sftp_name_id_map users_map = NULL; + sftp_name_id_map groups_map = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, + "users-groups-by-id@openssh.com", + "1"); + + if (rc == 0) { + skip(); + } + + alice_pwd = getpwnam("alice"); + assert_non_null(alice_pwd); + + bob_pwd = getpwnam("bob"); + assert_non_null(bob_pwd); + + root_pwd = getpwnam("root"); + assert_non_null(root_pwd); + + users_map = sftp_name_id_map_new(4); + groups_map = sftp_name_id_map_new(3); + + users_map->ids[0] = alice_pwd->pw_uid; + users_map->ids[1] = bob_pwd->pw_uid; + users_map->ids[2] = root_pwd->pw_uid; + users_map->ids[3] = 42; /* invalid uid */ + + groups_map->ids[0] = alice_pwd->pw_gid; + groups_map->ids[1] = root_pwd->pw_gid; + groups_map->ids[2] = 42; /* invalid gid */ + + rc = sftp_get_users_groups_by_id(t->sftp, users_map, groups_map); + + assert_int_equal(rc, 0); + assert_string_equal(users_map->names[0], "alice"); + assert_string_equal(users_map->names[1], "bob"); + assert_string_equal(users_map->names[2], "root"); + assert_string_equal(users_map->names[3], ""); + assert_string_equal(groups_map->names[0], "users"); + assert_string_equal(groups_map->names[1], "root"); + assert_string_equal(groups_map->names[2], ""); + + sftp_name_id_map_free(users_map); + sftp_name_id_map_free(groups_map); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_get_users_by_id, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_get_groups_by_id, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_get_users_groups_by_id, + session_setup, + session_teardown)}; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_hardlink.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_hardlink.c new file mode 100644 index 000000000000..379636368d1e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_hardlink.c @@ -0,0 +1,114 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_hardlink(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char link_1[128] = {0}; + char link_2[128] = {0}; + int fd; + int rc; + + snprintf(link_1, sizeof(link_1), + "%s/libssh_sftp_hardlink_test_1", t->testdir); + snprintf(link_2, sizeof(link_2), + "%s/libssh_sftp_hardlink_test_2", t->testdir); + + fd = open(link_1, O_CREAT, S_IRWXU); + assert_return_code(fd, errno); + close(fd); + + rc = sftp_hardlink(t->sftp, link_1, link_2); + assert_int_equal(rc, SSH_OK); + + /* check whether the file got associated with link_2 */ + rc = access(link_2, F_OK); + assert_int_equal(rc, 0); + + unlink(link_1); + unlink(link_2); + + /* + * try to create a hardlink for a file that does not + * exist, this should fail + */ + rc = sftp_hardlink(t->sftp, link_1, link_2); + assert_int_not_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_hardlink, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_home_directory.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_home_directory.c new file mode 100644 index 000000000000..a2399f02f898 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_home_directory.c @@ -0,0 +1,142 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + /* + The SFTP server used for testing is executed as a separate binary, which + is making the uid_wrapper lose information about what user is used, and + therefore, pwd is initialized to some bad value. + If the embedded version using internal-sftp is used in sshd, it works ok. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + torture_setup_sshd_server(state, false); + return 0; +} + +static int +sshd_teardown(void **state) +{ + unsetenv("TORTURE_SFTP_SERVER"); + torture_teardown_sshd_server(state); + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_sftp_home_directory(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *home_path = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, "home-directory", "1"); + if (!rc) { + skip(); + } + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + /* testing for NULL sftp session */ + home_path = sftp_home_directory(NULL, NULL); + assert_null(home_path); + + /* testing for ~ */ + /* + home_path = sftp_home_directory(t->sftp, NULL); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); + + home_path = sftp_home_directory(t->sftp, ""); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); + */ + + /* + OpenSSH code handling this extension does not handle empty string for + username. getpwnam() also does not handle empty string. + PR in OpenSSH for fix: + https://github.com/openssh/openssh-portable/pull/477/ + */ + + /* testing for ~user */ + home_path = sftp_home_directory(t->sftp, pwd->pw_name); + fprintf(stderr, + "sftp error: %d, ssh error: %s\n", + sftp_get_error(t->sftp), + ssh_get_error(t->sftp->session)); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_home_directory, + session_setup, + session_teardown)}; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_init.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_init.c new file mode 100644 index 000000000000..cdc2442639cb --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_init.c @@ -0,0 +1,166 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static void session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); +} + +static void session_setup_channel(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + ssh_channel c = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + c = ssh_channel_new(s->ssh.session); + assert_non_null(c); + + s->ssh.tsftp = torture_sftp_session_channel(s->ssh.session, c); + assert_non_null(s->ssh.tsftp); +} + +static void session_setup_extensions(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc, count; + const char *name = NULL, *data = NULL; + sftp_session sftp = NULL; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + sftp = s->ssh.tsftp->sftp; + + /* null parameter */ + count = sftp_extensions_get_count(NULL); + assert_int_equal(count, 0); + + count = sftp_extensions_get_count(sftp); + assert_int_not_equal(count, 0); + + /* first null parameter */ + name = sftp_extensions_get_name(NULL, 0); + assert_null(name); + data = sftp_extensions_get_data(NULL, 0); + assert_null(data); + + /* First extension */ + name = sftp_extensions_get_name(sftp, 0); + assert_non_null(name); + data = sftp_extensions_get_data(sftp, 0); + assert_non_null(data); + + /* Last extension */ + name = sftp_extensions_get_name(sftp, count - 1); + assert_non_null(name); + data = sftp_extensions_get_data(sftp, count - 1); + assert_non_null(data); + + /* Overrun */ + name = sftp_extensions_get_name(sftp, count); + assert_null(name); + data = sftp_extensions_get_data(sftp, count); + assert_null(data); +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(session_setup, + NULL, + session_teardown), + cmocka_unit_test_setup_teardown(session_setup_channel, + NULL, + session_teardown), + cmocka_unit_test_setup_teardown(session_setup_extensions, + NULL, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_limits.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_limits.c new file mode 100644 index 000000000000..07ef9928cf3b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_limits.c @@ -0,0 +1,177 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include +#include + +#if HAVE_VALGRIND_VALGRIND_H + #include +#endif + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_limits(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_limits_t li = NULL; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + + rc = sftp_extension_supported(t->sftp, "limits@openssh.com", "1"); + if (rc == 1) { + /* + * Tests are run against the OpenSSH server, hence we check for the + * specific limits used by OpenSSH. + */ + uint64_t openssh_max_packet_length = 256 * 1024; + uint64_t openssh_max_read_length = openssh_max_packet_length - 1024; + uint64_t openssh_max_write_length = openssh_max_packet_length - 1024; + size_t vg = 0; + + assert_int_equal(li->max_packet_length, openssh_max_packet_length); + assert_int_equal(li->max_read_length, openssh_max_read_length); + assert_int_equal(li->max_write_length, openssh_max_write_length); + + /* + * fds - File descriptors, w.r.to - With respect to + * + * Valgrind reserves some fds for itself and changes the rlimits + * w.r.to fds for the process its inspecting. Due to this reservation + * the rlimits w.r.to fds for our test may not be the same as the + * rlimits w.r.to fds seen by OpenSSH server (which Valgrind isn't + * inspecting). + * + * Valgrind changes the limits in such a way that after seeing the + * changed limits, the test cannot predict the original unchanged + * limits (which OpenSSH would be using). Hence, the test cannot + * determine the correct value of "max_open_handles" that the OpenSSH + * server should've sent. + * + * So if Valgrind is running our test, we don't provide any kind of + * check for max_open_handles. Check for >= 0 is also not provided in + * this case since that's always true for an uint64_t (an unsigned type) + */ +#if HAVE_VALGRIND_VALGRIND_H + vg = RUNNING_ON_VALGRIND; +#endif + + if (vg == 0) { + struct rlimit rlim = {0}; + uint64_t openssh_max_open_handles = 0; + + /* + * Get the resource limit for max file descriptors that a process + * can open. Since the client and the server run on the same machine + * in case of tests, this limit should be same for both (except the + * case when Valgrind runs the test) + */ + rc = getrlimit(RLIMIT_NOFILE, &rlim); + assert_int_equal(rc, 0); + if (rlim.rlim_cur > 5) { + /* + * Leaving file handles for stdout, stdin, stderr, syslog and + * a spare file handle, OpenSSH server allows the client to open + * at max (rlim.rlim_cur - 5) handles. + */ + openssh_max_open_handles = rlim.rlim_cur - 5; + } + + assert_int_equal(li->max_open_handles, openssh_max_open_handles); + } + } else { + /* Check for the default limits */ + assert_int_equal(li->max_packet_length, 34000); + assert_int_equal(li->max_read_length, 32768); + assert_int_equal(li->max_write_length, 32768); + assert_int_equal(li->max_open_handles, 0); + } + + sftp_limits_free(li); +} + +static void torture_sftp_limits_negative(void **state) +{ + sftp_limits_t li = NULL; + + (void)state; + li = sftp_limits(NULL); + assert_null(li); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_limits, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_limits_negative, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_packet_read.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_packet_read.c new file mode 100644 index 000000000000..4eaba301253e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_packet_read.c @@ -0,0 +1,118 @@ +/* + * This is a regression test to make sure that sftp_read_packet times out + * properly in blocking mode + */ + +#define LIBSSH_STATIC + +#include "config.h" + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int +sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_sftp_packet_read(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_packet packet = NULL; + + int fds[2]; + int rc; + + /* creating blocking fd is the default pipe behaviour */ + rc = pipe(fds); + assert_return_code(rc, errno); + + t->ssh->opts.timeout = 1; + ssh_socket_set_fd(t->ssh->socket, fds[0]); + + /* + * Making sure that the sftp_packet_read function times out and returns + * NULL. + */ + packet = sftp_packet_read(t->sftp); + assert_null(packet); + + close(fds[0]); + close(fds[1]); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_packet_read, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_read.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_read.c new file mode 100644 index 000000000000..c6ec4b9122b5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_read.c @@ -0,0 +1,121 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_read_blocking(void **state) { + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + unlink(libssh_tmp_file); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + if (bytesread == 0) { + break; /* EOF */ + } + assert_false(bytesread < 0); + + byteswritten = write(fd, buf, bytesread); + assert_int_equal(byteswritten, bytesread); + } + + close(fd); + sftp_close(file); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + /* This test is intentionally running twice to trigger a bug in OpenSSH + * or in pam_wrapper, causing the second invocation to fail. + * See: https://gitlab.com/libssh/libssh-mirror/-/issues/23 + */ + cmocka_unit_test_setup_teardown(torture_sftp_read_blocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_read_blocking, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_recv_response_msg.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_recv_response_msg.c new file mode 100644 index 000000000000..c7bda318bc82 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_recv_response_msg.c @@ -0,0 +1,197 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2024 Eshan Kelkar + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include +#include + +#include +#include +#include +#include + +/* For the ability to access the members of the sftp_aio_struct in the test */ +#include "sftp_aio.c" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* Test that sftp_recv_response_msg() works properly in blocking mode */ +static void torture_sftp_recv_response_msg_blocking(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_message msg = NULL; + ssize_t bytes_requested; + int rc; + + /* + * For sending an sftp request and obtaining its request id, this test uses + * the sftp aio API + */ + file = sftp_open(sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Send an sftp read request */ + bytes_requested = sftp_aio_begin_read(file, 16, &aio); + assert_int_equal(bytes_requested, 16); + assert_non_null(aio); + + /* Wait for the response (blocking mode) */ + rc = sftp_recv_response_msg(sftp, aio->id, true, &msg); + assert_int_equal(rc, SSH_OK); + + sftp_message_free(msg); + sftp_aio_free(aio); + sftp_close(file); +} + +/* Test that sftp_recv_response_msg() works properly in non blocking mode */ +static void torture_sftp_recv_response_msg_non_blocking(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + sftp_message msg = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + ssize_t bytes_requested; + int rc; + + /* + * At this point, the sftp channel shouldn't contain any outstanding + * responses. + * + * Hence, sftp_recv_response_msg() should return SSH_AGAIN immediately when + * we try to receive a response for any request ID in non-blocking mode. + */ + rc = sftp_recv_response_msg(sftp, 1984, false, &msg); + assert_int_equal(rc, SSH_AGAIN); + + /* + * Validate that after a response arrives in the sftp channel, trying to + * receive the response in non-blocking mode works properly. + * + * For sending an sftp request and obtaining its request id, this test uses + * the sftp aio API + */ + file = sftp_open(sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + bytes_requested = sftp_aio_begin_read(file, 16, &aio); + assert_int_equal(bytes_requested, 16); + assert_non_null(aio); + + /* Poll the sftp channel for the response */ + rc = ssh_channel_poll_timeout(sftp->channel, 60000, 0); + assert_int_not_equal(rc, SSH_ERROR); + assert_int_not_equal(rc, SSH_EOF); + assert_int_not_equal(rc, 0); + + /* + * The response has arrived, trying to obtain it in non blocking mode + * should work + */ + rc = sftp_recv_response_msg(sftp, aio->id, false, &msg); + assert_int_equal(rc, SSH_OK); + + sftp_message_free(msg); + sftp_aio_free(aio); + sftp_close(file); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_recv_response_msg_blocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_sftp_recv_response_msg_non_blocking, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_rename.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_rename.c new file mode 100644 index 000000000000..c2d11a3137c1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_rename.c @@ -0,0 +1,120 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_rename(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char name_1[128] = {0}; + char name_2[128] = {0}; + + int fd; + int rc; + + snprintf(name_1, sizeof(name_1), + "%s/libssh_sftp_rename_test_1", t->testdir); + snprintf(name_2, sizeof(name_2), + "%s/libssh_sftp_rename_test_2", t->testdir); + + fd = open(name_1, O_CREAT, S_IRWXU); + assert_return_code(fd, errno); + close(fd); + + /* try to rename an existing file */ + rc = sftp_rename(t->sftp, name_1, name_2); + assert_int_equal(rc, SSH_OK); + + /* check whether any file with name_1 exists, it shouldn't */ + rc = access(name_1, F_OK); + assert_int_not_equal(rc, 0); + + /* check whether file with name_2 exists, it should */ + rc = access(name_2, F_OK); + assert_int_equal(rc, 0); + + unlink(name_2); + + /* + * try to rename a file that does not exist, + * this should fail (-ve test case) + */ + rc = sftp_rename(t->sftp, name_1, name_2); + assert_int_not_equal(rc, 0); +} + + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_rename, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_request_id.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_request_id.c new file mode 100644 index 000000000000..21f2774a5dfc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_request_id.c @@ -0,0 +1,183 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_request_id_null(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + int rc; + + rc = sftp_get_new_id(sftp, NULL); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_sftp_request_id_add(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + uint32_t id1, id2; + int rc; + size_t count; + + /* The list of IDs should be empty at first */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + /* Request a new ID */ + rc = sftp_get_new_id(sftp, &id1); + assert_int_equal(rc, SSH_OK); + + /* Check that the list has one ID now */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 1); + + /* Request another ID */ + rc = sftp_get_new_id(sftp, &id2); + assert_int_equal(rc, SSH_OK); + + /* Check that the IDs differ */ + assert_int_not_equal(id1, id2); + + /* Check that the list has two IDs now */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 2); +} + +static void torture_sftp_request_id_remove(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + sftp_attributes attr = NULL; + size_t count; + + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + /* We send a request and receive a response */ + attr = sftp_stat(sftp, SSH_EXECUTABLE); + assert_non_null(attr); + + /* The number of outstanding requests should be back to 0 */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + sftp_attributes_free(attr); +} + +static void torture_sftp_request_id_unknown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + ssh_buffer buffer = NULL; + sftp_message msg = NULL; + uint32_t id = 0; + int rc; + size_t count; + + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + rc = ssh_buffer_pack(buffer, "ds", id, "/tmp"); + assert_int_equal(rc, SSH_OK); + + /* Send a request without saving the request ID */ + rc = sftp_packet_write(sftp, SSH_FXP_OPENDIR, buffer); + assert_int_not_equal(rc, -1); + SSH_BUFFER_FREE(buffer); + + /* An attempt to receive the response should fail */ + rc = sftp_recv_response_msg(sftp, id, true, &msg); + assert_int_equal(rc, SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_request_id_null, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_request_id_add, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_request_id_remove, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_request_id_unknown, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_setstat.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_setstat.c new file mode 100644 index 000000000000..736e47c17c93 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_setstat.c @@ -0,0 +1,382 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "libssh/sftp.h" +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + /* + * The OpenSSH invokes the sftp server command with execve(), which does + * not inherit the environment variables (including LD_PRELOAD, which + * is needed for the fs_wrapper). Using `internal-sftp` works around this, + * keeping the old environment around. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + + torture_setup_sshd_server(state, false); + return 0; +} + +static int +sshd_teardown(void **state) +{ + unsetenv("TORTURE_SFTP_SERVER"); + torture_teardown_sshd_server(state); + return 0; +} + +static int +session_setup_setstat(void **state) +{ + + struct torture_state *s = *state; + struct torture_sftp *t = NULL; + struct passwd *pwd = NULL; + static char name[128] = {0}; + const char *test_1 = "l&setstat_test\n"; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + t = s->ssh.tsftp; + + snprintf(name, sizeof(name), "%s/libssh_sftp_setstat_test", t->testdir); + torture_write_file(name, test_1); + s->private_data = name; + + return 0; +} + +static int +session_setup_lsetstat(void **state) +{ + + struct torture_state *s = *state; + struct torture_sftp *t = NULL; + struct passwd *pwd = NULL; + static char path[128] = {0}; + const char *test_1 = "lsetstat_test_1\n"; + int rc; + + char tmp_file[128] = {0}; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + t = s->ssh.tsftp; + + rc = sftp_extension_supported(t->sftp, "lsetstat@openssh.com", "1"); + if (rc == 0) { + skip(); + } + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", t->testdir); + torture_write_file(tmp_file, test_1); + + snprintf(path, sizeof(path), "%s/linkname", t->testdir); + rc = symlink(tmp_file, path); + assert_int_equal(rc, SSH_OK); + s->private_data = path; + + return 0; +} +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/*setstat tests*/ +static void +torture_sftp_setstat_chown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct sftp_attributes_struct attr; + struct passwd *pwd = NULL; + sftp_attributes tmp_attr = NULL; + const char *name = (char *)s->private_data; + int rc; + + ZERO_STRUCT(attr); + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + tmp_attr = sftp_stat(t->sftp, name); + assert_non_null(tmp_attr); + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + sftp_attributes_free(tmp_attr); +} + +static void +torture_sftp_setstat_size(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + size_t len = 30; + struct sftp_attributes_struct attr; + struct stat sb; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE; + attr.size = len; + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(len, sb.st_size); +} + +static void +torture_sftp_setstat_chmod(void **state) +{ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + attr.permissions = mode; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(sb.st_mode & ACCESSPERMS, mode); +} + +static void +torture_sftp_setstat_utimes(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + int atime = 10676, mtime = 13467; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attr.mtime = mtime; + attr.atime = atime; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + assert_int_equal(sb.st_mtime, mtime); + assert_int_equal(sb.st_atime, atime); +} + +static void +torture_sftp_setstat_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_SIZE; + + /* testing null sftp */ + rc = sftp_setstat(NULL, name, &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing non-existing file */ + rc = sftp_setstat(t->sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing null attributes */ + rc = sftp_setstat(t->sftp, name, NULL); + assert_int_equal(rc, SSH_ERROR); +} + +/*lsetstat tests*/ +static void +torture_sftp_lsetstat_chown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + sftp_attributes tmp_attr = NULL; + + ZERO_STRUCT(attr); + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + rc = sftp_lsetstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + tmp_attr = sftp_lstat(t->sftp, name); + assert_non_null(tmp_attr); + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + sftp_attributes_free(tmp_attr); +} + +static void +torture_sftp_lsetstat_utimes(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + int atime = 10676, mtime = 13467; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attr.mtime = mtime; + attr.atime = atime; + + rc = sftp_lsetstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = lstat(name, &sb); + assert_int_equal(rc, SSH_OK); + assert_int_equal(sb.st_mtime, mtime); + assert_int_equal(sb.st_atime, atime); +} + +static void +torture_sftp_lsetstat_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME | SSH_FILEXFER_ATTR_UIDGID; + + /* testing non-existing file */ + rc = sftp_lsetstat(t->sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing null attributes */ + rc = sftp_lsetstat(t->sftp, name, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* testing null sftp */ + rc = sftp_lsetstat(NULL, name, &attr); + assert_int_equal(rc, SSH_ERROR); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_setstat_chown, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_chmod, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_utimes, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_size, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_negative, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_utimes, + session_setup_lsetstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_chown, + session_setup_lsetstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_negative, + session_setup_lsetstat, + session_teardown)}; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/cmdline.c b/src/libs/libssh-0.12.2/tests/cmdline.c new file mode 100644 index 000000000000..ad58af78a1fe --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/cmdline.c @@ -0,0 +1,72 @@ +#include "config.h" +#include "torture.h" + +#ifdef HAVE_ARGP_H +#include + +const char *argp_program_version = "libssh test 0.2"; +const char *argp_program_bug_address = ""; + +static char **cmdline; + +/* Program documentation. */ +static char doc[] = "libssh test test"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make libssh test more verbose", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; + + /* arg is currently not used */ + (void) arg; + + switch (key) { + case 'v': + arguments->verbose++; + break; + case ARGP_KEY_ARG: + /* End processing here. */ + arguments->pattern = state->argv[state->next - 1]; + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +/* static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments) { + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, arguments); +#else + (void) argc; + (void) argv; + (void) arguments; +#endif /* HAVE_ARGP_H */ +} diff --git a/src/libs/libssh-0.12.2/tests/ctest-default.cmake b/src/libs/libssh-0.12.2/tests/ctest-default.cmake new file mode 100644 index 000000000000..51bbebabdc97 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/ctest-default.cmake @@ -0,0 +1,72 @@ +## The directory to run ctest in. +set(CTEST_DIRECTORY "$ENV{HOME}/workspace/tmp/dashboards/libssh") + +## The hostname of the machine +set(CTEST_SITE "host.libssh.org") +## The buildname +set(CTEST_BUILD_NAME "Linux_2.6-GCC_4.5-x86_64-default") + +## The Makefile generator to use +set(CTEST_CMAKE_GENERATOR "Unix Makefiles") + +## The Build configuration to use. +set(CTEST_BUILD_CONFIGURATION "Debug") + +## The build options for the project +set(CTEST_BUILD_OPTIONS "-DUNIT_TESTING=ON -WITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DDEBUG_CRYPTO=ON -DWITH_GCRYPT=OFF") + +#set(CTEST_CUSTOM_MEMCHECK_IGNORE torture_rand) + +## The Model to set: Nightly, Continuous, Experimental +set(CTEST_MODEL "Experimental") + +## The branch +#set(CTEST_GIT_BRANCH "--branch v0-5") + +## Whether to enable memory checking. +set(WITH_MEMCHECK FALSE) + +## Whether to enable code coverage. +set(WITH_COVERAGE FALSE) + +####################################################################### + +if (WITH_COVERAGE AND NOT WIN32) + set(CTEST_BUILD_CONFIGURATION "Profiling") +endif (WITH_COVERAGE AND NOT WIN32) + +set(CTEST_SOURCE_DIRECTORY "${CTEST_DIRECTORY}/${CTEST_BUILD_NAME}/source") +set(CTEST_BINARY_DIRECTORY "${CTEST_DIRECTORY}/${CTEST_BUILD_NAME}/build") + +set(CTEST_MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_SOURCE_DIR}/tests/valgrind.supp) +set(CTEST_MEMORYCHECK_COMMAND_OPTIONS " --trace-children-skip=${SSHD_EXECUTABLE}") + +find_program(CTEST_GIT_COMMAND NAMES git) +find_program(CTEST_COVERAGE_COMMAND NAMES gcov) +find_program(CTEST_MEMORYCHECK_COMMAND NAMES valgrind) + +if(NOT EXISTS "${CTEST_SOURCE_DIRECTORY}") + set(CTEST_CHECKOUT_COMMAND "${CTEST_GIT_COMMAND} clone ${CTEST_GIT_BRANCH} git://git.libssh.org/projects/libssh.git ${CTEST_SOURCE_DIRECTORY}") +endif() + +set(CTEST_UPDATE_COMMAND "${CTEST_GIT_COMMAND}") + +set(CTEST_CONFIGURE_COMMAND "${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE:STRING=${CTEST_BUILD_CONFIGURATION}") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DUNIT_TESTING:BOOL=ON ${CTEST_BUILD_OPTIONS}") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} \"-G${CTEST_CMAKE_GENERATOR}\"") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} \"${CTEST_SOURCE_DIRECTORY}\"") + +ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY}) + +ctest_start(${CTEST_MODEL} TRACK ${CTEST_MODEL}) +ctest_update(SOURCE ${CTEST_SOURCE_DIRECTORY}) +ctest_configure(BUILD ${CTEST_BINARY_DIRECTORY}) +ctest_build(BUILD ${CTEST_BINARY_DIRECTORY}) +ctest_test(BUILD ${CTEST_BINARY_DIRECTORY}) +if (WITH_COVERAGE) + ctest_coverage(BUILD ${CTEST_BINARY_DIRECTORY}) +endif () +if (WITH_MEMCHECK) + ctest_memcheck(BUILD ${CTEST_BINARY_DIRECTORY}) +endif () +ctest_submit() diff --git a/src/libs/libssh-0.12.2/tests/etc/group.in b/src/libs/libssh-0.12.2/tests/etc/group.in new file mode 100644 index 000000000000..df5ae8ab3730 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/group.in @@ -0,0 +1,5 @@ +users:x:9000: +sshd:x:65531: +nobody:x:65533: +nogroup:x:65534:nobody +root:x:0: diff --git a/src/libs/libssh-0.12.2/tests/etc/hosts.in b/src/libs/libssh-0.12.2/tests/etc/hosts.in new file mode 100644 index 000000000000..ea519350f432 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/hosts.in @@ -0,0 +1,12 @@ +127.0.0.10 server.libssh.site +127.0.0.21 client.libssh.site + +127.0.0.11 kdc.libssh.site + +123.0.0.11 testing +fd00::5357:5f0a testing + +127.0.0.10 afboth +fd00::5357:5f0a afboth +127.0.0.10 afinet +fd00::5357:5f0a afinet6 diff --git a/src/libs/libssh-0.12.2/tests/etc/openssl.cnf b/src/libs/libssh-0.12.2/tests/etc/openssl.cnf new file mode 100644 index 000000000000..7149b6bf7cc6 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/openssl.cnf @@ -0,0 +1,11 @@ +openssl_conf = openssl_init +[openssl_init] +providers = provider_sect +[provider_sect] +default = default_sect +pkcs11 = pkcs11_sect +[default_sect] +activate = 1 +[pkcs11_sect] +activate = 1 +pkcs11-module-assume-fips = true diff --git a/src/libs/libssh-0.12.2/tests/etc/pam.d/sshd.in b/src/libs/libssh-0.12.2/tests/etc/pam.d/sshd.in new file mode 100644 index 000000000000..57c66f9413b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/pam.d/sshd.in @@ -0,0 +1,4 @@ +auth required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb +account required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb +password required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb +session required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb diff --git a/src/libs/libssh-0.12.2/tests/etc/pam_matrix_passdb.in b/src/libs/libssh-0.12.2/tests/etc/pam_matrix_passdb.in new file mode 100644 index 000000000000..9404bc0e702c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/pam_matrix_passdb.in @@ -0,0 +1,4 @@ +bob:secret:sshd +alice:secret:sshd +charlie:secret:sshd +doe:secret:sshd diff --git a/src/libs/libssh-0.12.2/tests/etc/passwd.in b/src/libs/libssh-0.12.2/tests/etc/passwd.in new file mode 100644 index 000000000000..fff7878030f2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/passwd.in @@ -0,0 +1,9 @@ +bob:x:5000:9000:bob gecos:@HOMEDIR@/bob:/bin/sh +alice:x:5001:9000:alice gecos:@HOMEDIR@/alice:/bin/sh +charlie:x:5002:9000:charlie gecos:@HOMEDIR@/charlie:/bin/sh +doe:x:5003:9000:doe gecos:@HOMEDIR@/doe:/bin/sh +frank:x:5003:9000:doe gecos:@HOMEDIR@/frank:/bin/sh +sshd:x:65530:65531:sshd:@HOMEDIR@:/sbin/nologin +nobody:x:65533:65534:nobody gecos:@HOMEDIR@:/bin/false +root:x:0:0:root gecos:@HOMEDIR@:/bin/false +@LOCAL_USER@:x:@LOCAL_UID@:9000:local user:@HOMEDIR@:/bin/false diff --git a/src/libs/libssh-0.12.2/tests/etc/shadow.in b/src/libs/libssh-0.12.2/tests/etc/shadow.in new file mode 100644 index 000000000000..0f03b1499195 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/shadow.in @@ -0,0 +1,4 @@ +alice:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: +bob:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: +charlie:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: +doe:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: diff --git a/src/libs/libssh-0.12.2/tests/external_override/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/external_override/CMakeLists.txt new file mode 100644 index 000000000000..fcce52c7b030 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/CMakeLists.txt @@ -0,0 +1,205 @@ +project(external-override C) + +include_directories(${CMAKE_SOURCE_DIR}/include) + +set(LIBSSH_OVERRIDE_TESTS + torture_override +) + +# chacha20_override +add_library(chacha20_override SHARED + chacha20_override.c + ${libssh_SOURCE_DIR}/src/external/chacha.c + ) +set(CHACHA20_OVERRIDE_LIBRARY + ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chacha20_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# poly1305_override +add_library(poly1305_override SHARED + poly1305_override.c + ${libssh_SOURCE_DIR}/src/external/poly1305.c + ) +set(POLY1305_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}poly1305_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +if (WITH_GCRYPT) + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_gcrypt.c + ${libssh_SOURCE_DIR}/src/md_gcrypt.c + ) + set(override_libs + ${GCRYPT_LIBRARIES} + ) +elseif (WITH_MBEDTLS) + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_mbedcrypto.c + ${libssh_SOURCE_DIR}/src/md_mbedcrypto.c + ) + set(override_libs + ${MBEDTLS_CRYPTO_LIBRARY} + ) +else () + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_crypto.c + ${libssh_SOURCE_DIR}/src/md_crypto.c + ) + set(override_libs + OpenSSL::Crypto + ) +endif (WITH_GCRYPT) + +# ed25519_override +add_library(ed25519_override SHARED + ed25519_override.c + ${libssh_SOURCE_DIR}/src/external/fe25519.c + ${libssh_SOURCE_DIR}/src/external/ge25519.c + ${libssh_SOURCE_DIR}/src/external/sc25519.c + ${libssh_SOURCE_DIR}/src/external/ed25519.c + ${override_src} + ) +target_link_libraries(ed25519_override + PRIVATE ${override_libs}) +set(ED25519_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}ed25519_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# curve25519_override +add_library(curve25519_override SHARED + curve25519_override.c + ${libssh_SOURCE_DIR}/src/external/curve25519_ref.c + ${libssh_SOURCE_DIR}/src/external/fe25519.c + ${libssh_SOURCE_DIR}/src/external/ge25519.c + ${libssh_SOURCE_DIR}/src/external/sc25519.c + ${libssh_SOURCE_DIR}/src/external/ed25519.c + ${override_src} +) +target_link_libraries(curve25519_override + PRIVATE ${override_libs}) +set(CURVE25519_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}curve25519_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# sntrup761_override +add_library(sntrup761_override SHARED + sntrup761_override.c + ${libssh_SOURCE_DIR}/src/external/sntrup761.c + ${override_src} +) +target_link_libraries(sntrup761_override + PRIVATE ${override_libs}) +set(SNTRUP761_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}sntrup761_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# mlkem768_override +add_library(mlkem768_override SHARED + mlkem768_override.c + ${libssh_SOURCE_DIR}/src/external/libcrux_mlkem768_sha3.c + ${override_src} +) +target_link_libraries(mlkem768_override + PRIVATE ${override_libs}) +set(MLKEM768_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}mlkem768_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +set(OVERRIDE_LIBRARIES + ${CHACHA20_OVERRIDE_LIBRARY}:${POLY1305_OVERRIDE_LIBRARY}:${ED25519_OVERRIDE_LIBRARY}:${CURVE25519_OVERRIDE_LIBRARY}:${SNTRUP761_OVERRIDE_LIBRARY}:${MLKEM768_OVERRIDE_LIBRARY} +) + +if (WITH_MBEDTLS) + if (HAVE_MBEDTLS_CHACHA20_H AND HAVE_MBEDTLS_POLY1305_H) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") + endif () + + if(HAVE_MBEDTLS_CURVE25519) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") + endif() + + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=1") +elseif (WITH_GCRYPT) + if (HAVE_GCRYPT_CHACHA_POLY) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") + endif () + + if(HAVE_GCRYPT_CURVE25519) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") + else() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") + endif() + + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=0") + if(HAVE_GCRYPT_MLKEM) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=0") + else() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=1") + endif() +else () + if (HAVE_OPENSSL_EVP_CHACHA20) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") + endif () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=0") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=1") + if(HAVE_OPENSSL_MLKEM) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=0") + else() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=1") + endif() +endif () + +if (NOT OSX) + # Remove any preload string from the environment variables list + foreach(env_string ${TORTURE_ENVIRONMENT}) + if (${env_string} MATCHES "^LD_PRELOAD=*") + list(REMOVE_ITEM TORTURE_ENVIRONMENT ${env_string}) + set(PRELOAD_STRING "${env_string}:") + endif () + endforeach () + + if ("${PRELOAD_STRING}" STREQUAL "") + set(PRELOAD_STRING "LD_PRELOAD=") + endif () + + list(APPEND TORTURE_ENVIRONMENT + "${PRELOAD_STRING}${OVERRIDE_LIBRARIES}") +endif() + +foreach(_OVERRIDE_TEST ${LIBSSH_OVERRIDE_TESTS}) + add_cmocka_test(${_OVERRIDE_TEST} + SOURCES ${_OVERRIDE_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + ${OVERRIDE_RESULTS} + LINK_LIBRARIES + ${TORTURE_SHARED_LIBRARY} + chacha20_override + poly1305_override + ed25519_override + curve25519_override + sntrup761_override + mlkem768_override + ) + + if (OSX) + set_property( + TEST + ${_OVERRIDE_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${OVERRIDE_LIBRARIES}) + + else () + set_property( + TEST + ${_OVERRIDE_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_ENVIRONMENT}) + + endif() +endforeach() diff --git a/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.c b/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.c new file mode 100644 index 000000000000..7e166e7c8abd --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.c @@ -0,0 +1,80 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "chacha20_override.h" + +static bool internal_function_called = false; + +void __wrap_chacha_keysetup(struct chacha_ctx *x, + const uint8_t *k, + uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + chacha_keysetup(x, k, kbits); +} + +void __wrap_chacha_ivsetup(struct chacha_ctx *x, + const uint8_t *iv, + const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + chacha_ivsetup(x, iv, ctr); +} + +void __wrap_chacha_encrypt_bytes(struct chacha_ctx *x, + const uint8_t *m, + uint8_t *c, + uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + chacha_encrypt_bytes(x, m, c, bytes); +} + +bool internal_chacha20_function_called(void) +{ + return internal_function_called; +} + +void reset_chacha20_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.h b/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.h new file mode 100644 index 000000000000..58f8f211e4ec --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.h @@ -0,0 +1,51 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/chacha.h" + +void __wrap_chacha_keysetup(struct chacha_ctx *x, + const uint8_t *k, + uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif +; + +void __wrap_chacha_ivsetup(struct chacha_ctx *x, + const uint8_t *iv, + const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif +; + +void __wrap_chacha_encrypt_bytes(struct chacha_ctx *x, + const uint8_t *m, + uint8_t *c, + uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif +; + +bool internal_chacha20_function_called(void); +void reset_chacha20_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.c b/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.c new file mode 100644 index 000000000000..983d57cdf410 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.c @@ -0,0 +1,58 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "curve25519_override.h" + +static bool internal_function_called = false; + +int __wrap_crypto_scalarmult_base(unsigned char *q, + const unsigned char *n) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_scalarmult_base(q, n); +} + +int __wrap_crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_scalarmult(q, n, p); +} + +bool internal_curve25519_function_called(void) +{ + return internal_function_called; +} + +void reset_curve25519_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.h b/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.h new file mode 100644 index 000000000000..634c1c40c8ce --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.h @@ -0,0 +1,31 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/curve25519.h" + +int __wrap_crypto_scalarmult_base(unsigned char *q, + const unsigned char *n); + +int __wrap_crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p); + +bool internal_curve25519_function_called(void); +void reset_curve25519_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.c b/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.c new file mode 100644 index 000000000000..439a5dab41fa --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.c @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "ed25519_override.h" + +static bool internal_function_called = false; + +int __wrap_crypto_sign_ed25519_keypair(ed25519_pubkey pk, + ed25519_privkey sk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_sign_ed25519_keypair(pk, sk); +} + +int __wrap_crypto_sign_ed25519(unsigned char *sm, + uint64_t *smlen, + const unsigned char *m, + uint64_t mlen, + const ed25519_privkey sk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_sign_ed25519(sm, smlen, m, mlen, sk); +} + +int __wrap_crypto_sign_ed25519_open(unsigned char *m, + uint64_t *mlen, + const unsigned char *sm, + uint64_t smlen, + const ed25519_pubkey pk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_sign_ed25519_open(m, mlen, sm, smlen, pk); +} + +bool internal_ed25519_function_called(void) +{ + return internal_function_called; +} + +void reset_ed25519_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.h b/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.h new file mode 100644 index 000000000000..0abe2e249e39 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.h @@ -0,0 +1,39 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/ed25519.h" + +int __wrap_crypto_sign_ed25519_keypair(ed25519_pubkey pk, + ed25519_privkey sk); + +int __wrap_crypto_sign_ed25519(unsigned char *sm, + uint64_t *smlen, + const unsigned char *m, + uint64_t mlen, + const ed25519_privkey sk); + +int __wrap_crypto_sign_ed25519_open(unsigned char *m, + uint64_t *mlen, + const unsigned char *sm, + uint64_t smlen, + const ed25519_pubkey pk); + +bool internal_ed25519_function_called(void); +void reset_ed25519_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.c b/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.c new file mode 100644 index 000000000000..be135cda6f0b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.c @@ -0,0 +1,83 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * Jakub Jelen + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include + +#include + +#include "libssh/mlkem_native.h" +#include "mlkem768_override.h" + +static bool internal_function_called = false; + +libcrux_ml_kem_mlkem768_MlKem768KeyPair +__wrap_libcrux_ml_kem_mlkem768_portable_generate_key_pair( + uint8_t randomness[64U]) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_generate_key_pair(randomness); +} + +bool __wrap_libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_validate_public_key(public_key); +} + +tuple_c2 __wrap_libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_encapsulate(public_key, randomness); +} + +void __wrap_libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, + uint8_t ret[32U]) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_decapsulate(private_key, + ciphertext, + ret); +} + +bool internal_mlkem768_function_called(void) +{ + return internal_function_called; +} + +void reset_mlkem768_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.h b/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.h new file mode 100644 index 000000000000..ca9522e39db3 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.h @@ -0,0 +1,43 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * Jakub Jelen + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/mlkem_native.h" + +libcrux_ml_kem_mlkem768_MlKem768KeyPair +__wrap_libcrux_ml_kem_mlkem768_portable_generate_key_pair( + uint8_t randomness[64U]); + +bool __wrap_libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key); + +tuple_c2 __wrap_libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]); + +void __wrap_libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, + uint8_t ret[32U]); + +bool internal_mlkem768_function_called(void); +void reset_mlkem768_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.c b/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.c new file mode 100644 index 000000000000..4d78272f99b2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.c @@ -0,0 +1,54 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +static bool internal_function_called = false; + +void __wrap_poly1305_auth(uint8_t out[POLY1305_TAGLEN], + const uint8_t *m, + size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + poly1305_auth(out, m, inlen, key); +} + +bool internal_poly1305_function_called(void) +{ + return internal_function_called; +} + +void reset_poly1305_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.h b/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.h new file mode 100644 index 000000000000..877178006ee1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.h @@ -0,0 +1,35 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/poly1305.h" + +void __wrap_poly1305_auth(uint8_t out[POLY1305_TAGLEN], + const uint8_t *m, + size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif +; + +bool internal_poly1305_function_called(void); +void reset_poly1305_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.c b/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.c new file mode 100644 index 000000000000..dcdca326cf15 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.c @@ -0,0 +1,73 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * Jakub Jelen + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include "sntrup761_override.h" + +static bool internal_function_called = false; + +void __wrap_sntrup761_keypair(uint8_t *pk, + uint8_t *sk, + void *random_ctx, + sntrup761_random_func *random) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return sntrup761_keypair(pk, sk, random_ctx, random); +} + +void __wrap_sntrup761_enc(uint8_t *c, + uint8_t *k, + const uint8_t *pk, + void *random_ctx, + sntrup761_random_func *random) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return sntrup761_enc(c, k, pk, random_ctx, random); +} + +void __wrap_sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return sntrup761_dec(k, c, sk); +} + +bool internal_sntrup761_function_called(void) +{ + return internal_function_called; +} + +void reset_sntrup761_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.h b/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.h new file mode 100644 index 000000000000..4ec9cb5ebc06 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.h @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * Jakub Jelen + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/sntrup761.h" + +void __wrap_sntrup761_keypair(uint8_t *pk, + uint8_t *sk, + void *random_ctx, + sntrup761_random_func *random); + +void __wrap_sntrup761_enc(uint8_t *c, + uint8_t *k, + const uint8_t *pk, + void *random_ctx, + sntrup761_random_func *random); + +void __wrap_sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk); + +bool internal_sntrup761_function_called(void); +void reset_sntrup761_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/torture_override.c b/src/libs/libssh-0.12.2/tests/external_override/torture_override.c new file mode 100644 index 000000000000..160864cc935e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/torture_override.c @@ -0,0 +1,466 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#include "chacha20_override.h" +#include "curve25519_override.h" +#include "ed25519_override.h" +#include "mlkem768_override.h" +#include "poly1305_override.h" +#include "sntrup761_override.h" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_st { + char *temp_dir; + char *orig_dir; +}; + +static int sshd_setup(void **state) +{ + struct torture_state *s; + struct test_st *test_state = NULL; + char *temp_dir; + int rc; + + torture_setup_sshd_server(state, false); + + test_state = malloc(sizeof(struct test_st)); + assert_non_null(test_state); + + s = *((struct torture_state **)state); + s->private_data = test_state; + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + test_state->temp_dir = temp_dir; + + return 0; +} + +static int sshd_teardown(void **state) +{ + struct torture_state *s = *state; + struct test_st *test_state = s->private_data; + int rc; + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool false_v = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Prevent parsing configuration files that can introduce different + * algorithms then we want to test */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &false_v); + + reset_chacha20_function_called(); + reset_poly1305_function_called(); + reset_curve25519_function_called(); + reset_ed25519_function_called(); + reset_sntrup761_function_called(); + reset_mlkem768_function_called(); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void test_algorithm(ssh_session session, + const char *kex, + const char *cipher, + const char *hostkey) +{ + char data[256] = {0}; + int rc; + + if (kex != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + assert_ssh_return_code(session, rc); + } + + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + assert_ssh_return_code(session, rc); + } + + if (hostkey != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkey); + assert_ssh_return_code(session, rc); + } + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* send ignore packets of all sizes */ + memset(data, 'A', sizeof(data) - 1); + ssh_send_ignore(session, data); + ssh_handle_packets(session, 50); + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM +static void torture_override_chacha20_poly1305(void **state) +{ + struct torture_state *s = *state; + + bool internal_chacha20_called; + bool internal_poly1305_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /* kex */ + "chacha20-poly1305@openssh.com", + NULL /* hostkey */); + + internal_chacha20_called = internal_chacha20_function_called(); + internal_poly1305_called = internal_poly1305_function_called(); + +#if SHOULD_CALL_INTERNAL_CHACHAPOLY + assert_true(internal_chacha20_called); + assert_true(internal_poly1305_called); +#else + assert_false(internal_chacha20_called || + internal_poly1305_called); +#endif + +} +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ + +#ifdef OPENSSH_CURVE25519_SHA256 +static void torture_override_ecdh_curve25519_sha256(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "curve25519-sha256", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_CURVE25519_SHA256 */ + +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG +static void torture_override_ecdh_curve25519_sha256_libssh_org(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "curve25519-sha256@libssh.org", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM +static void +torture_override_ecdh_sntrup761x25519_sha512_openssh_com(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + bool internal_sntrup761_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512@openssh.com", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + internal_sntrup761_called = internal_sntrup761_function_called(); + +#if SHOULD_CALL_INTERNAL_SNTRUP761 + assert_true(internal_sntrup761_called); +#else + assert_false(internal_sntrup761_called); +#endif + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512 +static void +torture_override_ecdh_sntrup761x25519_sha512(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + bool internal_sntrup761_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + internal_sntrup761_called = internal_sntrup761_function_called(); + +#if SHOULD_CALL_INTERNAL_SNTRUP761 + assert_true(internal_sntrup761_called); +#else + assert_false(internal_sntrup761_called); +#endif + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ + +#ifdef OPENSSH_MLKEM768X25519_SHA256 +static void torture_override_mlkem768x25519_sha256(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + bool internal_mlkem768_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "mlkem768x25519-sha256", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + internal_mlkem768_called = internal_mlkem768_function_called(); + +#if SHOULD_CALL_INTERNAL_MLKEM + assert_true(internal_mlkem768_called); +#else + assert_false(internal_mlkem768_called); +#endif + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_MLKEM768X25519_SHA256 */ + +#ifdef OPENSSH_MLKEM768NISTP256_SHA256 +static void torture_override_mlkem768nistp256_sha256(void **state) +{ + struct torture_state *s = *state; + bool internal_mlkem768_called; + + test_algorithm(s->ssh.session, + "mlkem768nistp256-sha256", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_mlkem768_called = internal_mlkem768_function_called(); + +#if SHOULD_CALL_INTERNAL_MLKEM + assert_true(internal_mlkem768_called); +#else + assert_false(internal_mlkem768_called); +#endif +} +#endif /* OPENSSH_MLKEM768NISTP256_SHA256 */ + +#ifdef OPENSSH_SSH_ED25519 +static void torture_override_ed25519(void **state) +{ + struct torture_state *s = *state; + bool internal_ed25519_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /* kex */ + NULL, /* cipher */ + "ssh-ed25519"); + + internal_ed25519_called = internal_ed25519_function_called(); + +#if SHOULD_CALL_INTERNAL_ED25519 + assert_true(internal_ed25519_called); +#else + assert_false(internal_ed25519_called); +#endif +} +#endif /* OPENSSH_SSH_ED25519 */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_override_chacha20_poly1305, + session_setup, + session_teardown), +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ +#ifdef OPENSSH_CURVE25519_SHA256 + cmocka_unit_test_setup_teardown(torture_override_ecdh_curve25519_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256 */ +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG + cmocka_unit_test_setup_teardown(torture_override_ecdh_curve25519_sha256_libssh_org, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_override_ecdh_sntrup761x25519_sha512_openssh_com, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512 + cmocka_unit_test_setup_teardown(torture_override_ecdh_sntrup761x25519_sha512, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ +#ifdef OPENSSH_MLKEM768X25519_SHA256 + cmocka_unit_test_setup_teardown(torture_override_mlkem768x25519_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_MLKEM768X25519_SHA256 */ +#ifdef OPENSSH_MLKEM768NISTP256_SHA256 + cmocka_unit_test_setup_teardown(torture_override_mlkem768nistp256_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_MLKEM768NISTP256_SHA256 */ +#ifdef OPENSSH_SSH_ED25519 + cmocka_unit_test_setup_teardown(torture_override_ed25519, + session_setup, + session_teardown), +#endif /* OPENSSH_SSH_ED25519 */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + if (rc != 0) { + return rc; + } + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/fs_wrapper.c b/src/libs/libssh-0.12.2/tests/fs_wrapper.c new file mode 100644 index 000000000000..b718eff09b83 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fs_wrapper.c @@ -0,0 +1,255 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/******************************************************************************* + * Structs + ******************************************************************************/ +struct file { + char *name; + uid_t uid; + gid_t gid; +} file = {0}; + +/******************************************************************************* + * Destructor + ******************************************************************************/ + +void destructor(void) __attribute__((destructor)); + +void +destructor(void) +{ + free(file.name); +} + +/******************************************************************************* + * Chown wrapping + ******************************************************************************/ + +/** Records the UID and GID and pretend syscall worked */ +static int +chown_helper(const char *pathname, uid_t owner, gid_t group) +{ + if (strlen(pathname) > 7 && strncmp(pathname, "/dev/pt", 7) == 0) { + /* + * The OpenSSH server modified the PTY which requires root permissions + * see torture_request_pty_modes + * */ + return 0; + } + if (strlen(pathname) > 4 && strncmp(pathname, "/tmp", 4) == 0) { + /* + * faking chown because It requires root permissions to modify the owner + * under /tmp + * It's also a helper for torture_sftp_setstat + * */ + if (file.name != NULL) { + free((char *)file.name); + } + file.name = strdup(pathname); + file.uid = owner; + file.gid = group; + return 0; + } + return -1; +} + +#define WRAP_CHOWN(syscall_name) \ + typedef int (*__libc_##syscall_name)(const char *pathname, \ + uid_t owner, \ + gid_t group); \ + int syscall_name(const char *pathname, uid_t owner, gid_t group); \ + int syscall_name(const char *pathname, uid_t owner, gid_t group) \ + { \ + __libc_##syscall_name original_##syscall_name = NULL; \ + int rc; \ + \ + rc = chown_helper(pathname, owner, group); \ + if (rc == 0) { \ + return 0; \ + } \ + original_##syscall_name = \ + (__libc_##syscall_name)dlsym(RTLD_NEXT, #syscall_name); \ + return (*original_##syscall_name)(pathname, owner, group); \ + } + +WRAP_CHOWN(chown) +WRAP_CHOWN(chown32) +WRAP_CHOWN(lchown) + +/* fchownat */ +typedef int (*__libc_fchownat)(int dirfd, + const char *pathname, + uid_t owner, + gid_t group, + int flags); + +int +fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags); + +int +fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags) +{ + __libc_fchownat original_fchownat = NULL; + int rc; + + rc = chown_helper(pathname, owner, group); + if (rc == 0) { + return 0; + } + + original_fchownat = (__libc_fchownat)dlsym(RTLD_NEXT, "fchownat"); + return (*original_fchownat)(dirfd, pathname, owner, group, flags); +} + +/******************************************************************************* + * Stat wrapping + ******************************************************************************/ + +/** Returns previously set UID/GID for the filename */ +static void +stat_helper(const char *pathname, struct stat *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->st_uid = file.uid; + statbuf->st_gid = file.gid; + } +} + +static void +stat64_helper(const char *pathname, struct stat64 *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->st_uid = file.uid; + statbuf->st_gid = file.gid; + } +} + +#define WRAP_STAT(syscall_name, struct_name) \ + typedef int (*__libc_##syscall_name)(const char *pathname, \ + struct struct_name *statbuf); \ + int syscall_name(const char *pathname, struct struct_name *statbuf); \ + int syscall_name(const char *pathname, struct struct_name *statbuf) \ + { \ + int rc; \ + __libc_##syscall_name original_##syscall_name = NULL; \ + \ + original_##syscall_name = \ + (__libc_##syscall_name)dlsym(RTLD_NEXT, #syscall_name); \ + rc = (*original_##syscall_name)(pathname, statbuf); \ + struct_name##_helper(pathname, statbuf); \ + \ + return rc; \ + } + +WRAP_STAT(stat, stat) +WRAP_STAT(lstat, stat) +/* i686 arch */ +WRAP_STAT(stat64, stat64) +WRAP_STAT(lstat64, stat64) + +#define WRAP_XSTAT(syscall_name) \ + typedef int (*__libc_##syscall_name)(int ver, \ + const char *pathname, \ + struct stat *statbuf); \ + int syscall_name(int ver, const char *pathname, struct stat *statbuf); \ + int syscall_name(int ver, const char *pathname, struct stat *statbuf) \ + { \ + int rc; \ + __libc_##syscall_name original_##syscall_name = NULL; \ + \ + original_##syscall_name = \ + (__libc_##syscall_name)dlsym(RTLD_NEXT, #syscall_name); \ + rc = (*original_##syscall_name)(ver, pathname, statbuf); \ + stat_helper(pathname, statbuf); \ + \ + return rc; \ + } + +WRAP_XSTAT(__xstat) /* CentOS8 */ +WRAP_XSTAT(__lxstat) + +/* i686 arch (likely not wrappable) */ +static void +statx_helper(const char *pathname, struct statx *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->stx_uid = file.uid; + statbuf->stx_gid = file.gid; + } +} + +typedef int (*__libc_statx)(int dirfd, + const char *pathname, + int flags, + unsigned int mask, + struct statx *statbuf); +int statx(int dirfd, + const char *pathname, + int flags, + unsigned int mask, + struct statx *statbuf); +int +statx(int dirfd, + const char *pathname, + int flags, + unsigned int mask, + struct statx *statbuf) +{ + int rc; + __libc_statx original_statx = NULL; + + original_statx = (__libc_statx)dlsym(RTLD_NEXT, "statx"); + rc = (*original_statx)(dirfd, pathname, flags, mask, statbuf); + statx_helper(pathname, statbuf); + + return rc; +} + +static int is_file_blocked(const char *pathname) +{ + if (pathname == NULL) { + return 0; + } + + static const char *blocked_files[] = { + /* Block for torture_gssapi_server_key_exchange_null */ + "/etc/ssh/ssh_host_ecdsa_key", + "/etc/ssh/ssh_host_rsa_key", + "/etc/ssh/ssh_host_ed25519_key", + }; + + for (size_t i = 0; i < sizeof(blocked_files) / sizeof(blocked_files[0]); + i++) { + if (strcmp(pathname, blocked_files[i]) == 0) { + errno = ENOENT; /* No such file or directory */ + return 1; + } + } + return 0; +} + +#define WRAP_FOPEN(func_name) \ + FILE *func_name(const char *pathname, const char *mode) \ + { \ + typedef FILE *(*orig_func_t)(const char *pathname, const char *mode); \ + static orig_func_t orig_func = NULL; \ + if (orig_func == NULL) { \ + orig_func = (orig_func_t)dlsym(RTLD_NEXT, #func_name); \ + } \ + if (is_file_blocked(pathname)) { \ + return NULL; \ + } \ + return orig_func(pathname, mode); \ + } + +WRAP_FOPEN(fopen) +WRAP_FOPEN(fopen64) diff --git a/src/libs/libssh-0.12.2/tests/fuzz/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/fuzz/CMakeLists.txt new file mode 100644 index 000000000000..8f2f97e6d1dd --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/CMakeLists.txt @@ -0,0 +1,40 @@ +project(fuzzing CXX) + +macro(fuzzer name) + add_executable(${name} ${name}.c) + target_link_libraries(${name} PRIVATE ${TORTURE_LINK_LIBRARIES}) + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set_target_properties(${name} + PROPERTIES + COMPILE_FLAGS "-fsanitize=fuzzer" + LINK_FLAGS "-fsanitize=fuzzer") + # Run the fuzzer to make sure it works + add_test(${name} ${CMAKE_CURRENT_BINARY_DIR}/${name} -runs=1) + # Run the fuzzer with nalloc to make sure it works + add_test(${name}_nalloc ${CMAKE_CURRENT_BINARY_DIR}/${name} -runs=1) + set_property(TEST ${name}_nalloc PROPERTY ENVIRONMENT NALLOC_FREQ 32) + else() + target_sources(${name} PRIVATE fuzzer.c) + # Run the fuzzer to make sure it works + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus") + file(GLOB files "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus/*") + set(i 0) + foreach(file ${files}) + add_test(${name}_${i} + ${CMAKE_CURRENT_BINARY_DIR}/${name} ${file}) + math(EXPR i "${i} + 1") + endforeach() + endif() + endif() +endmacro() + +fuzzer(ssh_client_fuzzer) +fuzzer(ssh_client_config_fuzzer) +fuzzer(ssh_known_hosts_fuzzer) +fuzzer(ssh_privkey_fuzzer) +fuzzer(ssh_pubkey_fuzzer) +fuzzer(ssh_sshsig_fuzzer) +if (WITH_SERVER) + fuzzer(ssh_server_fuzzer) + fuzzer(ssh_bind_config_fuzzer) +endif (WITH_SERVER) diff --git a/src/libs/libssh-0.12.2/tests/fuzz/README.md b/src/libs/libssh-0.12.2/tests/fuzz/README.md new file mode 100644 index 000000000000..a8f9a1be68d8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/README.md @@ -0,0 +1,146 @@ +# Simple fuzzers for libssh + +This directory contains fuzzer programs, that are usable either in +oss-fuzz infrastructure or suitable for running fuzzing locally or +even for reproducing crashes with given trace files. + +When building with clang, fuzzers are automatically built with address +sanitizer. With gcc, they are built as they are without instrumentation, +but they are suitable for debugging. + +## Background + +### Turn off encryption + +Fuzzing ssh protocol is complicated by the way that all the communication +between client and server is encrypted and authenticated using keys based +on random data, making it impossible to fuzz the actual underlying protocol +as every change in the encrypted data causes integrity errors. For that reason, +libssh needs to implement "none" cipher and MAC as described in RFC 4253 +and these need to be used during fuzzing to be able to accomplish +reproducibility and for fuzzers to be able to progress behind key exchange. +This is enabled with the `WITH_INSECURE_NONE` CMake option. + +### Do not allow filesystem modification + +The OpenSSH configuration files are quite rich and expects users to know what +they do when they write their configuration files. The fuzzer driver is not an +average user so it is very happy to try whatever commands come to its "mind", +including `rm -rf /` and libssh would be very happy to run it by default. This +might remove some parts of the system that are mandatory for fuzzing. +To avoid executing dangerous commands like this, the `WITH_EXEC=OFF` CMake +option prevents invoking any external command through `exec()` syscall. + +## Corpus creation + +For effective fuzzing, we need to provide corpus of initial (valid) inputs that +can be used for deriving other inputs. libssh already supports creation of pcap +files (packet capture), which include all the information we need for fuzzing. +This file is also created from date before encryption and after decryption so +it is in plain text as we expect it, but we still need to adjust configuration +to use none cipher for the key exchange to be plausible. + +### Creating packet capture using example libssh client + + * Compile libssh with support for none cipher and pcap: + + cmake -DWITH_INSECURE_NONE=ON -DWITH_EXEC=OFF -DWITH_PCAP=ON ../ + + * Create a configuration file enabling none cipher and mac: + + printf 'Ciphers none\nMACs none' > /tmp/ssh_config + + * Generate test host key: + + ./examples/keygen2 -f /tmp/hostkey -t rsa + + * Run example libssh server: + + ./examples/samplesshd-cb -f /tmp/ssh_config -k /tmp/hostkey -p 22222 127.0.0.1 + + * In other terminal, run the example libssh client with pcap enabled (use mypassword for password): + + ./examples/ssh-client -F /tmp/ssh_config -l myuser -P /tmp/ssh.pcap -p 22222 127.0.0.1 + + * Kill the server (in the first terminal, press Ctrl+C) + + * Convert the pcap file to raw traces (separate client and server messages) usable by fuzzer: + + tshark -r /tmp/ssh.pcap -T fields -e data -Y "tcp.dstport==22222" | tr -d '\n',':' | xxd -r -ps > /tmp/ssh_server + tshark -r /tmp/ssh.pcap -T fields -e data -Y "tcp.dstport!=22222" | tr -d '\n',':' | xxd -r -ps > /tmp/ssh_client + + * Now we should be able to "replay" the sessions in respective fuzzers, getting some more coverage: + + LIBSSH_VERBOSITY=9 ./tests/fuzz/ssh_client_fuzzer /tmp/ssh_client + LIBSSH_VERBOSITY=9 ./tests/fuzz/ssh_server_fuzzer /tmp/ssh_server + + (note, that the client fuzzer fails now because of invalid hostkey signature; TODO) + + * Store the appropriately named traces in the fuzers directory: + + cp /tmp/ssh_client tests/fuzz/ssh_client_fuzzer_corpus/$(sha1sum /tmp/ssh_client | cut -d ' ' -f 1) + cp /tmp/ssh_server tests/fuzz/ssh_server_fuzzer_corpus/$(sha1sum /tmp/ssh_server | cut -d ' ' -f 1) + +## Debugging issues reported by oss-fuzz + +OSS Fuzz provides helper scripts to reproduce issues locally. Even though the +fuzzing scripts can ran anywhere, the best bet for reproducing is to use +their container infrastructure. There is a +[complete documentation](https://google.github.io/oss-fuzz/advanced-topics/reproducing/) +but I will try to focus here on the workflow I use and libssh specifics. + +### Environment + +The helper scripts are written in Python and use docker to run containers +so these needs to be installed. I am using podman instead of docker for +some time, but it has some quirks that needs to be addressed in advance +and that I describe in the rejected [PR](https://github.com/google/oss-fuzz/pull/4774). +You can either pick up my branch or workaround them locally: + + * Package `podman-docker` installs symlink from `/bin/docker` to `/bin/podman` + * The directories mounted to the containers need to have `container_file_t` + SELinux labels -- this is needed for the `build` directory that is created + under the oss-fuzz repository, for testcases and for source files + * `podman` does not like combination of `--privileged` and + `--cap-add SYS_PTRACE` flags. Podman can work with non-privileged containers + so you can just remove the `--privileged` from the `infra/helper.py` + +### Reproduce locally + +Clone the above repository from https://github.com/google/oss-fuzz/, apply +changes from previous section if needed, setup local clone of libssh repository +and build the fuzzers locally (where `~/devel/libssh` is path to local libssh +checkout): + + python infra/helper.py build_fuzzers libssh ~/devel/libssh/ + +Now, download the testcase from oss-fuzz.com (the file under `~/Downloads`) +and we are ready to reproduce the issue locally (replace the `ssh_client_fuzzer` +with the fuzzer name if the issue happens in other fuzzer): + + python infra/helper.py reproduce libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264 + +This should give you the same error/leak/crash as you see on the testcase +detail in oss-fuzz.com. + +I find it very useful to run libssh in debug mode, to see what happened and +what exit path was taken to get to the error. Fortunately, we can simply +pass environment variables to the container: + + python infra/helper.py reproduce -eLIBSSH_VERBOSITY=9 libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264 + +### Fix the issue and verify the fix + +Now, we can properly investigate the issue and once we have a fix, we can +make changes in our local checkout and repeat the steps above (from building +fuzzers) to verify the issue is no longer present. + +### Fuzzing locally + +We can use the oss-fuzz tools even further and run the fuzzing process +locally, to verify there are no similar issues happening very close to +existing code paths and which would cause more reports very soon after +we would fix the current issue. The following command will run fuzzer +until it finds an issue or until killed: + + python infra/helper.py run_fuzzer libssh ssh_client_fuzzer diff --git a/src/libs/libssh-0.12.2/tests/fuzz/fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/fuzzer.c new file mode 100644 index 000000000000..bd7a9edbcd50 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/fuzzer.c @@ -0,0 +1,49 @@ +/* Simpler gnu89 version of StandaloneFuzzTargetMain.c from LLVM */ + +#include "config.h" + +#include +#include +#include +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + +int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size); +__attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv); + +int +main (int argc, char **argv) +{ + FILE *f = NULL; + size_t n_read, len; + unsigned char *buf = NULL; + + if (argc < 2) { + return 1; + } + + if (LLVMFuzzerInitialize) { + LLVMFuzzerInitialize(&argc, &argv); + } + + f = fopen (argv[1], "r"); + assert (f); + fseek (f, 0, SEEK_END); + len = ftell (f); + fseek (f, 0, SEEK_SET); + buf = (unsigned char*) malloc (len); + n_read = fread (buf, 1, len, f); + fclose (f); + assert (n_read == len); + LLVMFuzzerTestOneInput (buf, len); + + free (buf); + printf ("Done!\n"); + +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/nallocinc.c b/src/libs/libssh-0.12.2/tests/fuzz/nallocinc.c new file mode 100644 index 000000000000..6ce3c442beb1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/nallocinc.c @@ -0,0 +1,344 @@ +/* + MIT License + + Copyright (c) 2025 Catena cyber + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* Nalloc fuzz : framework to make allocations and IO fail while fuzzing */ + +/* Environment variables to control nalloc fuzz behavior : + * NALLOC_VERBOSE: set it to log failed allocations with their stacktraces + * NALLOC_FREQ: set it to control how frequently allocations fail + * value 0 disables nalloc (no allocations fail) + * value 1..31 : allocations fail always (1) or very rarely (31 -> 1 / 2^31) + * value 32 : allocations fail at a random rate between 5 and 20 for each run + */ +#if defined(__clang__) && defined(__has_feature) +#if __has_feature(address_sanitizer) +#define NALLOC_ASAN 1 +#endif +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static const uint32_t nalloc_crc32_table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4}; + +// Nallocfuzz data to take a decision +uint32_t nalloc_random_state = 0; +__thread unsigned int nalloc_running = 0; +bool nalloc_initialized = false; +uint32_t nalloc_runs = 0; + +// Nalloc fuzz parameters +uint32_t nalloc_bitmask = 0xFF; +bool nalloc_random_bitmask = true; +uint32_t nalloc_magic = 0x294cee63; +bool nalloc_verbose = false; + +#ifdef NALLOC_ASAN +extern void __sanitizer_print_stack_trace(void); +#endif + +// Generic init, using env variables to get parameters +void nalloc_init(const char *prog) +{ + if (nalloc_initialized) { + return; + } + nalloc_initialized = true; + char *bitmask = getenv("NALLOC_FREQ"); + if (bitmask) { + int shift = atoi(bitmask); + if (shift > 0 && shift < 31) { + nalloc_bitmask = 1 << shift; + nalloc_random_bitmask = false; + } else if (shift == 0) { + nalloc_random_bitmask = false; + nalloc_bitmask = 0; + } + } else if (prog == NULL || strstr(prog, "nalloc") == NULL) { + nalloc_random_bitmask = false; + nalloc_bitmask = 0; + return; + } + + char *verbose = getenv("NALLOC_VERBOSE"); + if (verbose) { + nalloc_verbose = true; + } +} + +// add one byte to the CRC +static inline void nalloc_random_update(uint8_t b) +{ + nalloc_random_state = + ((uint32_t)((uint32_t)nalloc_random_state << 8)) ^ + nalloc_crc32_table[((nalloc_random_state >> 24) ^ b) & 0xFF]; +} + +// Start the failure injections, using a buffer as seed +static int nalloc_start(const uint8_t *data, size_t size) +{ + if (nalloc_random_bitmask) { + if (nalloc_random_state & 0x10) { + nalloc_bitmask = 0xFFFFFFFF; + } else { + nalloc_bitmask = 1 << (5 + (nalloc_random_state & 0xF)); + } + } else if (nalloc_bitmask == 0) { + // nalloc disabled + return 2; + } + nalloc_random_state = 0; + for (size_t i = 0; i < size; i++) { + nalloc_random_update(data[i]); + } + if (__sync_fetch_and_add(&nalloc_running, 1)) { + __sync_fetch_and_sub(&nalloc_running, 1); + return 0; + } + nalloc_runs++; + return 1; +} + +// Stop the failure injections +static void nalloc_end() +{ + __sync_fetch_and_sub(&nalloc_running, 1); +} + +static bool nalloc_backtrace_exclude(size_t size, const char *op) +{ + if (nalloc_verbose) { + fprintf(stderr, "failed %s(%zu) \n", op, size); +#ifdef NALLOC_ASAN + __sanitizer_print_stack_trace(); +#endif + } + + return false; +} + +// +static bool nalloc_fail(size_t size, const char *op) +{ + // do not fail before thread init + if (nalloc_runs == 0) { + return false; + } + if (__sync_fetch_and_add(&nalloc_running, 1) != 1) { + // do not fail allocations outside of fuzzer input + // and do not fail inside of this function + __sync_fetch_and_sub(&nalloc_running, 1); + return false; + } + nalloc_random_update((uint8_t)size); + if (size >= 0x100) { + nalloc_random_update((uint8_t)(size >> 8)); + if (size >= 0x10000) { + nalloc_random_update((uint8_t)(size >> 16)); + // bigger may already fail or oom + } + } + if (((nalloc_random_state ^ nalloc_magic) & nalloc_bitmask) == 0) { + if (nalloc_backtrace_exclude(size, op)) { + __sync_fetch_and_sub(&nalloc_running, 1); + return false; + } + __sync_fetch_and_sub(&nalloc_running, 1); + return true; + } + __sync_fetch_and_sub(&nalloc_running, 1); + return false; +} + +// ASAN interceptor for libc routines +#ifdef NALLOC_ASAN +extern void *__interceptor_malloc(size_t); +extern void *__interceptor_calloc(size_t, size_t); +extern void *__interceptor_realloc(void *, size_t); +extern void *__interceptor_reallocarray(void *, size_t, size_t); + +extern ssize_t __interceptor_read(int, void *, size_t); +extern ssize_t __interceptor_write(int, const void *, size_t); +extern ssize_t __interceptor_recv(int, void *, size_t, int); +extern ssize_t __interceptor_send(int, const void *, size_t, int); + +#define nalloc_malloc(s) __interceptor_malloc(s) +#define nalloc_calloc(s, n) __interceptor_calloc(s, n) +#define nalloc_realloc(p, s) __interceptor_realloc(p, s) +#define nalloc_reallocarray(p, s, n) __interceptor_reallocarray(p, s, n) + +#define nalloc_read(f, b, s) __interceptor_read(f, b, s) +#define nalloc_write(f, b, s) __interceptor_write(f, b, s) +#define nalloc_recv(f, b, s, x) __interceptor_recv(f, b, s, x) +#define nalloc_send(f, b, s, x) __interceptor_send(f, b, s, x) + +#else +extern void *__libc_malloc(size_t); +extern void *__libc_calloc(size_t, size_t); +extern void *__libc_realloc(void *, size_t); +extern void *__libc_reallocarray(void *, size_t, size_t); + +extern ssize_t __read(int, void *, size_t); +extern ssize_t __write(int, const void *, size_t); +extern ssize_t __recv(int, void *, size_t, int); +extern ssize_t __send(int, const void *, size_t, int); + +#define nalloc_malloc(s) __libc_malloc(s) +#define nalloc_calloc(s, n) __libc_calloc(s, n) +#define nalloc_realloc(p, s) __libc_realloc(p, s) +#define nalloc_reallocarray(p, s, n) __libc_reallocarray(p, s, n) + +#define nalloc_read(f, b, s) __read(f, b, s) +#define nalloc_write(f, b, s) __write(f, b, s) +#define nalloc_recv(f, b, s, x) __recv(f, b, s, x) +#define nalloc_send(f, b, s, x) __send(f, b, s, x) +#endif + +// nalloc standard function overwrites with pseudo-random failures +ssize_t read(int fd, void *buf, size_t count) +{ + if (nalloc_fail(count, "read")) { + errno = EIO; + return -1; + } + return nalloc_read(fd, buf, count); +} + +ssize_t write(int fd, const void *buf, size_t count) +{ + if (nalloc_fail(count, "write")) { + errno = EIO; + return -1; + } + return nalloc_write(fd, buf, count); +} + +ssize_t recv(int fd, void *buf, size_t count, int flags) +{ + if (nalloc_fail(count, "recv")) { + errno = EIO; + return -1; + } + return nalloc_recv(fd, buf, count, flags); +} + +ssize_t send(int fd, const void *buf, size_t count, int flags) +{ + if (nalloc_fail(count, "send")) { + errno = EIO; + return -1; + } + return nalloc_send(fd, buf, count, flags); +} + +void *calloc(size_t nmemb, size_t size) +{ + if (nalloc_fail(size, "calloc")) { + errno = ENOMEM; + return NULL; + } + return nalloc_calloc(nmemb, size); +} + +void *malloc(size_t size) +{ + if (nalloc_fail(size, "malloc")) { + errno = ENOMEM; + return NULL; + } + return nalloc_malloc(size); +} + +void *realloc(void *ptr, size_t size) +{ + if (nalloc_fail(size, "realloc")) { + errno = ENOMEM; + return NULL; + } + return nalloc_realloc(ptr, size); +} + +void *reallocarray(void *ptr, size_t nmemb, size_t size) +{ + if (nalloc_fail(size, "reallocarray")) { + errno = ENOMEM; + return NULL; + } + return nalloc_reallocarray(ptr, nmemb, size); +} + +#ifdef __cplusplus +} // extern "C" { +#endif diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_bind_config_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_bind_config_fuzzer.c new file mode 100644 index 000000000000..fb5e03ee1098 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_bind_config_fuzzer.c @@ -0,0 +1,75 @@ +/* + * Copyright 2021 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/server.h" +#include "libssh/bind_config.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_bind bind = NULL; + char *input = NULL; + + input = (char *)malloc(size + 1); + if (!input) { + return 1; + } + strncpy(input, (const char *)data, size); + input[size] = '\0'; + + assert(nalloc_start(data, size) > 0); + + bind = ssh_bind_new(); + if (bind == NULL) { + goto out; + } + + ssh_bind_config_parse_string(bind, input); + + ssh_bind_free(bind); + +out: + free(input); + + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer.c new file mode 100644 index 000000000000..a994f807e0f7 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer.c @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Stanislav Zidek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/options.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_session session = NULL; + char *input = NULL; + + input = (char *)malloc(size+1); + if (!input) { + return 1; + } + strncpy(input, (const char *)data, size); + input[size] = '\0'; + + assert(nalloc_start(data, size) > 0); + + session = ssh_new(); + if (session == NULL) { + goto out; + } + + /* Make sure we have default options set */ + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + + ssh_config_parse_string(session, input); + + ssh_free(session); + +out: + free(input); + + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop new file mode 100644 index 000000000000..f1b37a527a4c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop @@ -0,0 +1,5 @@ +Host ssh-host +Hostname 10.1.1.1 + +Host 10.1.1.* 10.1.20.* +ProxyJump ssh-host \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username new file mode 100644 index 000000000000..d766c61cdd1f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username @@ -0,0 +1,7 @@ +Host jumpbox +User oliverw +Hostname jumpbox.example.org + +Host myserver.example.org +Hostname 1.2.3.4 +ProxyJump jumpbox \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer.c new file mode 100644 index 000000000000..4f70282c32f8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer.c @@ -0,0 +1,224 @@ +/* + * Copyright 2019 Andreas Schneider + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include +#include + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +static int auth_callback(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void)prompt; /* unused */ + (void)echo; /* unused */ + (void)verify; /* unused */ + (void)userdata; /* unused */ + + snprintf(buf, len, "secret"); + + return 0; +} + +struct ssh_callbacks_struct cb = { + .userdata = NULL, + .auth_function = auth_callback, +}; + +static void select_loop(ssh_session session, ssh_channel channel) +{ + ssh_connector connector_in, connector_out, connector_err; + + ssh_event event = ssh_event_new(); + + /* stdin */ + connector_in = ssh_connector_new(session); + ssh_connector_set_out_channel(connector_in, channel, SSH_CONNECTOR_STDINOUT); + ssh_connector_set_in_fd(connector_in, 0); + ssh_event_add_connector(event, connector_in); + + /* stdout */ + connector_out = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_out, 1); + ssh_connector_set_in_channel(connector_out, channel, SSH_CONNECTOR_STDINOUT); + ssh_event_add_connector(event, connector_out); + + /* stderr */ + connector_err = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_err, 2); + ssh_connector_set_in_channel(connector_err, channel, SSH_CONNECTOR_STDERR); + ssh_event_add_connector(event, connector_err); + + while (ssh_channel_is_open(channel)) { + ssh_event_dopoll(event, 60000); + } + ssh_event_remove_connector(event, connector_in); + ssh_event_remove_connector(event, connector_out); + ssh_event_remove_connector(event, connector_err); + + ssh_connector_free(connector_in); + ssh_connector_free(connector_out); + ssh_connector_free(connector_err); + + ssh_event_free(event); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_session session = NULL; + ssh_channel channel = NULL; + const char *env = NULL; + int socket_fds[2] = {-1, -1}; + ssize_t nwritten; + bool no = false; + int rc; + long timeout = 1; /* use short timeout to avoid timeouts during fuzzing */ + + /* This is the maximum that can be handled by the socket buffer before the + * other side will read some data. Other option would be feeding the socket + * from different thread which would not mind if it would be blocked, but I + * believe all the important inputs should fit into this size */ + if (size > 219264) { + return -1; + } + + /* Set up the socket to send data */ + rc = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); + assert(rc == 0); + + nwritten = send(socket_fds[1], data, size, 0); + assert((size_t)nwritten == size); + + rc = shutdown(socket_fds[1], SHUT_WR); + assert(rc == 0); + + assert(nalloc_start(data, size) > 0); + + session = ssh_new(); + if (session == NULL) { + goto out; + } + + env = getenv("LIBSSH_VERBOSITY"); + if (env != NULL && strlen(env) > 0) { + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY_STR, env); + } + rc = ssh_options_set(session, SSH_OPTIONS_FD, &socket_fds[0]); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.0.0.1"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, "alice"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &no); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &timeout); + if (rc != SSH_OK) { + goto out; + } + + ssh_callbacks_init(&cb); + ssh_set_callbacks(session, &cb); + + rc = ssh_connect(session); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + goto out; + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + goto out; + } + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_channel_request_exec(channel, "ls"); + if (rc != SSH_OK) { + goto out; + } + + select_loop(session, channel); + +out: + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + + close(socket_fds[0]); + close(socket_fds[1]); + + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer_corpus/0f9d75a6c1d365115772a502d42b6e48f453198a b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer_corpus/0f9d75a6c1d365115772a502d42b6e48f453198a new file mode 100644 index 0000000000000000000000000000000000000000..2667602969d625ccc92e0d300379dcf28021c8fb GIT binary patch literal 2055 zcmWFz_RuxbGtkY+Oe!wUh&Rx)(6cmP;9_84oWd@`Zr!$3Zjx(mrSFV&bAPQ^4iuG5 zE-flcH8M3dwA3xmNHj7v)4>pNfEcEiUzDzsnw*jWR;ZhoSzJ;8QjaENY+-^fWNKun zlaiU1mYJ%Xk(!f}o0zAYUX)*2U}ym{%GA&ZyQ~>LS>4o%V|oW zTihl@Z9BF2{1YQVmL&$K7B2lP+|cb48hGLSgS`c3Oy8XC{xgSHAy;Qw<1d>af2IR+ zFF!y1xunbO9*<&tft<(I1tnkCT;8zxP1$5qOP71!z4q^2C&nf2n88$dYz8okFer#I zrd;7UUfVOR-~h|LASVflrqsVPgf|+@R1Esa<*%Ru%3MHoJn+N;OesLI2A31zYvf0iuq}vA*mfcP9 zb(?doUeH-+;*s6#^=kiPS9|;D-prE{5<0TE<@D)B!P9e8_6B9YQ-9ijZ1G`}bw9Zt zX!}ZwM4UKSIzi7MOlybp;iD>gODrRb{1rc0Puk>J^m2~4_+_z`x2EaN%l5jHGjHdn z|3z1s6pI#Z)YlY${6}1VOGS+3R`vrXi$$7E#NHkBX+Jf?WEGRZV{Yl2OD|6H{Ju_O z)2_q2G&f5n=9ZLg`Y9Z?Yr>Ds#xwVZCa=n5t5P$VQg^bazC_62?Y=qt;|wZ`dz96B zFEa)0`Y5Z$Fnbxh;a)`(#VrP6!OY7o&2<0th}4Q5{khb?#YFYLWyr?V;!lg@F1fpE zhTS*o>09|AN3l&i<$v#W54(k1Laxjgzs&vByxR2Niv@QW|1V{Daj>uM*qi#2;~iUt zj?EBv`SP`9dhn#3Z(X}g7z{X@GVCVJ`FcG4dgQU|EnqDVXj#uPSoARm~^ z82F1*fn{KkZgFP1ZemV)F;LVIloeA`Amw6mYEg-9nSr50enDy;P(m*`KNnQQ!3qX& zNeC?Cbcs?3EFXze2rOc-D@0Cb1XSQO1Id5byn-PHE!9!H3@ZGQ-Gbl%WoFObmtDsT z3|$#6HXz9Zval2w(21ob8CxGR+&#bcx$wD%Kp7QQV<0J!UR<15kf~dqS(2ffo0+Ur zkXT$?o?n#0aPsN#22~=|NgK$kOd>*ED34tC1fV(|E)#?I2i~?B#-}Mb0QoXp(Lhog zf@^_P$Bw7>XE@cC|0;Yp1IXoJO9shpxv%idq94K6$#9g}mjdLAaK(b;8HBkRxpzA4 zo1yV7ET;)1&KU<52d1i=`X_v49oNp9E<+VB4tuN8?#Ow@?i#8%?}N;+?A@N4AMT-w cb4`u + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "knownhosts.c" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *hostname = NULL; + const uint8_t *hostname_end = NULL; + size_t hostname_len = 0; + char filename[256]; + struct ssh_list *entries = NULL; + struct ssh_iterator *it = NULL; + FILE *fp = NULL; + + /* Interpret the first part of the string (until the first NULL byte) + * as a hostname we are searching for in the file */ + hostname_end = memchr(data, '\0', size); + if (hostname_end == NULL) { + return 1; + } + hostname_len = hostname_end - data + 1; + if (hostname_len > 253) { + /* This is the maximum valid length of a hostname */ + return 1; + } + hostname = malloc(hostname_len); + if (hostname == NULL) { + return 1; + } + memcpy(hostname, data, hostname_len); + + snprintf(filename, sizeof(filename), "/tmp/libfuzzer.%d", getpid()); + fp = fopen(filename, "wb"); + if (!fp) { + free(hostname); + return 1; + } + fwrite(data + hostname_len, size - hostname_len, 1, fp); + fclose(fp); + + assert(nalloc_start(data, size) > 0); + + ssh_known_hosts_read_entries(hostname, filename, &entries); + for (it = ssh_list_get_iterator(entries); + it != NULL; + it = ssh_list_get_iterator(entries)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entries, it); + } + ssh_list_free(entries); + + ssh_finalize(); + + free(hostname); + unlink(filename); + + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 b/src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 new file mode 100644 index 0000000000000000000000000000000000000000..18e779e09d32610498d48f90eb5c45600fde64df GIT binary patch literal 189 zcmY+(&k{mF0D$q%Jw-1-?H0qK!|otEgV{oZ+xjy$6k{noeRZqv_L~prPzI_d0~u&w z1%9`6_i~TZfUujXox({Q0r|9P<%*G}uFJa{ijw}M$MqYTQacm? literal 0 HcmV?d00001 diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer.c new file mode 100644 index 000000000000..ff79103d5986 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer.c @@ -0,0 +1,72 @@ +/* + * Copyright 2023 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/priv.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + uint8_t *input = NULL; + int rc; + + assert(nalloc_start(data, size) > 0); + + input = bin_to_base64(data, size); + if (input == NULL) { + goto out; + } + + rc = ssh_pki_import_privkey_base64((char *)input, NULL, NULL, NULL, &pkey); + free(input); + if (rc != SSH_OK) { + goto out; + } + ssh_key_free(pkey); + +out: + nalloc_end(); + return 0; +} + diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a b/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a new file mode 100644 index 000000000000..2759f43e9856 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVgAAAKg+IRNSPiET +UgAAAAtzc2gtZWQyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVg +AAAED2zFg52qYItoZaSUnir4VKubTxJveL9D2oWK7Prg/O24ujq/HWVfplmh7TmVZOS7Cs +lRk3RPyER6vlQX0o+E5WAAAAHmpqZWxlbkB0NDcwcy5qamVsZW4ucmVkaGF0LmNvbQECAw +QFBgc= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer.c new file mode 100644 index 000000000000..bb96dcc33c34 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer.c @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/misc.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + char *filename = NULL; + int fd; + int rc; + ssize_t sz; + + filename = strdup("/tmp/libssh_pubkey_XXXXXX"); + if (filename == NULL) { + return -1; + } + fd = mkstemp(filename); + if (fd == -1) { + free(filename); + close(fd); + return -1; + } + sz = ssh_writen(fd, data, size); + close(fd); + if (sz == SSH_ERROR) { + unlink(filename); + free(filename); + return -1; + } + + assert(nalloc_start(data, size) > 0); + + rc = ssh_pki_import_pubkey_file(filename, &pkey); + if (rc != SSH_OK) { + goto out; + } + ssh_key_free(pkey); + +out: + unlink(filename); + free(filename); + nalloc_end(); + return 0; +} + diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 b/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 new file mode 100644 index 000000000000..accd5b65a629 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIujq/HWVfplmh7TmVZOS7CslRk3RPyER6vlQX0o+E5W jjelen@t470s.jjelen.redhat.com diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer.c new file mode 100644 index 000000000000..e5504c7242f1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer.c @@ -0,0 +1,281 @@ +/* +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include +#include +#include + +#include "nallocinc.c" + +static const char kRSAPrivateKeyPEM[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" + "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" + "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" + "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" + "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" + "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" + "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" + "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" + "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" + "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" + "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" + "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" + "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" + "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" + "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" + "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" + "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" + "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" + "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" + "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" + "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" + "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" + "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" + "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" + "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" + "-----END RSA PRIVATE KEY-----\n"; + +/* A userdata struct for session. */ +struct session_data_struct { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + size_t auth_attempts; + bool authenticated; +}; + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +static int auth_none(ssh_session session, const char *user, void *userdata) +{ + struct session_data_struct *sdata = + (struct session_data_struct *)userdata; + + (void)session; + (void)user; + + if (sdata->auth_attempts > 0) { + sdata->authenticated = true; + } + sdata->auth_attempts++; + + if (!sdata->authenticated) { + return SSH_AUTH_PARTIAL; + } + + return SSH_AUTH_SUCCESS; +} + +static ssh_channel channel_open(ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = + (struct session_data_struct *)userdata; + + sdata->channel = ssh_channel_new(session); + + return sdata->channel; +} + +static int write_rsa_hostkey(const char *rsakey_path) +{ + FILE *fp = NULL; + size_t nwritten; + + fp = fopen(rsakey_path, "wb"); + if (fp == NULL) { + return -1; + } + + nwritten = fwrite(kRSAPrivateKeyPEM, 1, strlen(kRSAPrivateKeyPEM), fp); + fclose(fp); + + if (nwritten != strlen(kRSAPrivateKeyPEM)) { + return -1; + } + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + int socket_fds[2] = {-1, -1}; + ssize_t nwritten; + bool no = false; + const char *env = NULL; + int rc; + ssh_bind sshbind = NULL; + ssh_session session = NULL; + ssh_event event = NULL; + + /* Our struct holding information about the session. */ + struct session_data_struct sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = false, + }; + + struct ssh_server_callbacks_struct server_cb = { + .userdata = &sdata, + .auth_none_function = auth_none, + .channel_open_request_session_function = channel_open, + }; + + /* This is the maximum that can be handled by the socket buffer before the + * other side will read some data. Other option would be feeding the socket + * from different thread which would not mind if it would be blocked, but I + * believe all the important inputs should fit into this size */ + if (size > 219264) { + return -1; + } + + /* Write SSH RSA host key to disk */ + rc = write_rsa_hostkey("/tmp/libssh_fuzzer_private_key"); + assert(rc == 0); + + /* Set up the socket to send data */ + rc = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); + assert(rc == 0); + + nwritten = send(socket_fds[1], data, size, 0); + assert((size_t)nwritten == size); + + rc = shutdown(socket_fds[1], SHUT_WR); + assert(rc == 0); + + assert(nalloc_start(data, size) > 0); + + /* Set up the libssh server */ + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + goto out; + } + + session = ssh_new(); + if (session == NULL) { + goto out; + } + + env = getenv("LIBSSH_VERBOSITY"); + if (env != NULL && strlen(env) > 0) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + env); + if (rc != SSH_OK) { + goto out; + } + } + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + "/tmp/libssh_fuzzer_private_key"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_S_C, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_C_S, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_S_C, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &no); + if (rc != SSH_OK) { + goto out; + } + + ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE); + + ssh_callbacks_init(&server_cb); + ssh_set_server_callbacks(session, &server_cb); + + rc = ssh_bind_accept_fd(sshbind, session, socket_fds[0]); + if (rc != SSH_OK) { + goto out; + } + + event = ssh_event_new(); + if (event == NULL) { + goto out; + } + + if (ssh_handle_key_exchange(session) == SSH_OK) { + ssh_event_add_session(event, session); + + size_t n = 0; + while (sdata.authenticated == false || sdata.channel == NULL) { + if (sdata.auth_attempts >= 3 || n >= 100) { + break; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + break; + } + + n++; + } + } + +out: + nalloc_end(); + + ssh_event_free(event); + + close(socket_fds[0]); + close(socket_fds[1]); + + ssh_disconnect(session); + ssh_free(session); + ssh_bind_free(sshbind); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer_corpus/fd7bd24a85e712fb59159a512b69d34ca21c8383 b/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer_corpus/fd7bd24a85e712fb59159a512b69d34ca21c8383 new file mode 100644 index 0000000000000000000000000000000000000000..2667602969d625ccc92e0d300379dcf28021c8fb GIT binary patch literal 2055 zcmWFz_RuxbGtkY+Oe!wUh&Rx)(6cmP;9_84oWd@`Zr!$3Zjx(mrSFV&bAPQ^4iuG5 zE-flcH8M3dwA3xmNHj7v)4>pNfEcEiUzDzsnw*jWR;ZhoSzJ;8QjaENY+-^fWNKun zlaiU1mYJ%Xk(!f}o0zAYUX)*2U}ym{%GA&ZyQ~>LS>4o%V|oW zTihl@Z9BF2{1YQVmL&$K7B2lP+|cb48hGLSgS`c3Oy8XC{xgSHAy;Qw<1d>af2IR+ zFF!y1xunbO9*<&tft<(I1tnkCT;8zxP1$5qOP71!z4q^2C&nf2n88$dYz8okFer#I zrd;7UUfVOR-~h|LASVflrqsVPgf|+@R1Esa<*%Ru%3MHoJn+N;OesLI2A31zYvf0iuq}vA*mfcP9 zb(?doUeH-+;*s6#^=kiPS9|;D-prE{5<0TE<@D)B!P9e8_6B9YQ-9ijZ1G`}bw9Zt zX!}ZwM4UKSIzi7MOlybp;iD>gODrRb{1rc0Puk>J^m2~4_+_z`x2EaN%l5jHGjHdn z|3z1s6pI#Z)YlY${6}1VOGS+3R`vrXi$$7E#NHkBX+Jf?WEGRZV{Yl2OD|6H{Ju_O z)2_q2G&f5n=9ZLg`Y9Z?Yr>Ds#xwVZCa=n5t5P$VQg^bazC_62?Y=qt;|wZ`dz96B zFEa)0`Y5Z$Fnbxh;a)`(#VrP6!OY7o&2<0th}4Q5{khb?#YFYLWyr?V;!lg@F1fpE zhTS*o>09|AN3l&i<$v#W54(k1Laxjgzs&vByxR2Niv@QW|1V{Daj>uM*qi#2;~iUt zj?EBv`SP`9dhn#3Z(X}g7z{X@GVCVJ`FcG4dgQU|EnqDVXj#uPSoARm~^ z82F1*fn{KkZgFP1ZemV)F;LVIloeA`Amw6mYEg-9nSr50enDy;P(m*`KNnQQ!3qX& zNeC?Cbcs?3EFXze2rOc-D@0Cb1XSQO1Id5byn-PHE!9!H3@ZGQ-Gbl%WoFObmtDsT z3|$#6HXz9Zval2w(21ob8CxGR+&#bcx$wD%Kp7QQV<0J!UR<15kf~dqS(2ffo0+Ur zkXT$?o?n#0aPsN#22~=|NgK$kOd>*ED34tC1fV(|E)#?I2i~?B#-}Mb0QoXp(Lhog zf@^_P$Bw7>XE@cC|0;Yp1IXoJO9shpxv%idq94K6$#9g}mjdLAaK(b;8HBkRxpzA4 zo1yV7ET;)1&KU<52d1i=`X_v49oNp9E<+VB4tuN8?#Ow@?i#8%?}N;+?A@N4AMT-w cb4`u + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + const char input[] = "badc0de"; + const char namespace[] = "namespace"; + char *signature = NULL; + int rc; + + assert(nalloc_start(data, size) > 0); + + signature = (char *)malloc(size + 1); + if (signature == NULL) { + goto out; + } + strncpy(signature, (const char *)data, size); + signature[size] = '\0'; + + rc = sshsig_verify(input, sizeof(input), signature, namespace, &pkey); + free(signature); + if (rc != SSH_OK) { + goto out; + } + ssh_key_free(pkey); + +out: + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 b/src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 new file mode 100644 index 000000000000..d9de5df5e6e4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 @@ -0,0 +1,14 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBALP3yM/hsvPV41IV3mzatq +7NStESRGVw233KH29dxEgyfX0m3fkZQlDOovn6BFVdt8VnWp3bNgZJ+9rRopyWnSIDllPp +KMafoEZrSSxPzjYgCiUKkSt3jiTQR+gLfejTKieBsL+ehuFuvLj4A8FFUMFSHOhHOkcqYs ++wxPkvvoErwUCFVELe15D3Fzsjec7o+ag4WTOJelezoPS1o+P9iBeWnLyo3yDKXqpp6fc+ +gU2GULbkFOm9VbhGIV8rzOi5DMJ3bFRoeOpAyjJkUIcgPAOqrywJYjDKvPJOYEeAHiXk56 +g0f0NdtCOjzKmDZeky05PPyqJzjjw0f11xm94heu8AAAAJbmFtZXNwYWNlAAAAAAAAAAZz +aGE1MTIAAAEUAAAADHJzYS1zaGEyLTUxMgAAAQApuWdMEHGcQgCagN8Tgcs72DEuLMBp/v +DXbjHbSyGRrcWcusZEvLClWkEJaouuvf7Vpqs1SaJvwW9nIcK0Md9UgZMXFOFMbKGg8LzC +YKp7O6Qud7skUgWclP4qyQrFWhYOfuijNY2rWajy+F42DI28j84CYx9bvHHWtqCEGihKdn +KLJltw/D7T3GnoKOeknOUl1Kr4Ca3G+qxSLxNsu0sa6TtP7ZnH+75tSlHunhVhOKHKf/f4 +YpjMCjuPIOolMbFm+UFojZcGMVvyZKelV2m4dPQ7OMpGcl7KTRMAbzm7yfsQeHSc132pnn +OwfsIiy75wDBtvudMSFOYftG1EeEzN +-----END SSH SIGNATURE----- diff --git a/src/libs/libssh-0.12.2/tests/generate.py b/src/libs/libssh-0.12.2/tests/generate.py new file mode 100755 index 000000000000..08c2d5b1a8b7 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/generate.py @@ -0,0 +1,10 @@ +#!/usr/bin/python +import os +a="" +for i in xrange(4096): + a+=chr(i % 256); +while True: + try: + os.write(1,a) + except: + exit(0) diff --git a/src/libs/libssh-0.12.2/tests/gss/kdcsetup.sh b/src/libs/libssh-0.12.2/tests/gss/kdcsetup.sh new file mode 100755 index 000000000000..c5e38bc883c2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/gss/kdcsetup.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +SOCKDIR=$1 +WORKDIR=$SOCKDIR/gss + +mkdir "$WORKDIR"/k "$WORKDIR"/d + +cat< "$WORKDIR"/k/kdc.conf +[realms] + LIBSSH.SITE = { + database_name = $WORKDIR/principal + key_stash_file = $WORKDIR/stash + kdc_listen = $(hostname -f) + kdc_tcp_listen = $(hostname -f) + default_principal_flags = +preauth,+forwardable + } +[logging] + kdc = FILE:$WORKDIR/kdc.log + debug = true +EOF + +cat< "$WORKDIR"/k/krb5.conf +[libdefaults] + default_realm = LIBSSH.SITE + forwardable = true + +[realms] + LIBSSH.SITE = { + kdc = $(hostname -f) + } +[domain_realm] + .$(hostname -d) = LIBSSH.SITE + +EOF + +kdb5_util -P foo create -s + +bash "$WORKDIR"/kadmin.sh + +krb5kdc -w 1 -P "$WORKDIR"/pid + +# Wait till KDC binds to the ports, 0x58 is port 88 +i=0 +while [ ! -S "$SOCKDIR"/T0B0058 ] && [ ! -S "$SOCKDIR"/U0B0058 ]; do + i=$((i + 1)) + [ "$i" -eq 5 ] && exit 1 + sleep 1 +done + +bash "$WORKDIR"/kinit.sh + +klist +exit 0 diff --git a/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa new file mode 100644 index 000000000000..a0b679c209bc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEA0DehrU/ohoimMKojFXdo1uEAcqx4fS87AjDUz8t4s436ppP+0+U3 ++qrhOCE/mXZvXewjTHltmEtCHNSbsWhYTjwrEQUDRNOVahn2PEQTcX/9itvsv9PX79Imbv +ZsLR0f1FsmorkWpnDQmuga7hYBFBj4sV+VML5ieoK2OraUEq46ILsxqRgUTetkErlzX7S2 +SPE7vNM0ahmA+HNBuKNUD+BOtCzkqN54flGA9TZ7kapC7xqiRHK+ZzahQ2PFR4BxbVP1uT +DsanbjKOpBC4hISao3hi4iUnyj0gJ8itmkhQS+oI/2KWSGW01/k9W7jOUXDSt7LGUTSW6s +ILYHzmefCwAAA9B/6IFvf+iBbwAAAAdzc2gtcnNhAAABAQDQN6GtT+iGiKYwqiMVd2jW4Q +ByrHh9LzsCMNTPy3izjfqmk/7T5Tf6quE4IT+Zdm9d7CNMeW2YS0Ic1JuxaFhOPCsRBQNE +05VqGfY8RBNxf/2K2+y/09fv0iZu9mwtHR/UWyaiuRamcNCa6BruFgEUGPixX5UwvmJ6gr +Y6tpQSrjoguzGpGBRN62QSuXNftLZI8Tu80zRqGYD4c0G4o1QP4E60LOSo3nh+UYD1NnuR +qkLvGqJEcr5nNqFDY8VHgHFtU/W5MOxqduMo6kELiEhJqjeGLiJSfKPSAnyK2aSFBL6gj/ +YpZIZbTX+T1buM5RcNK3ssZRNJbqwgtgfOZ58LAAAAAwEAAQAAAQEAxjzxFU0LGWtortSN +apaxnkPCZWuHm8gn6kILm3shg/IdPhORfrSxw1qF6ybcooN8LHPyd5D0oxaj70cMpK+vw2 +zNo/qdzh2UF9x375Dw4hL1lgslMM3EvXPbW7IJ9DnSYCAYfLyzr+ug8JsjaKJSjIvp2xYh +uLLKl9FzJhtGhzDaJr9FCbSmd5R7Telz4En0Lwo/VxYvyzCoRwzhVUVqJZpdtF7/1du4tT +agfPzPYY9zM9muR7AawtzMc4UFvMzl1OtjOHYtqSMVBZx44fRpXT3/fy7A98+7erd0zTWj +s6gaz6I8VmRPk4iTdBH4KBzC8dGZQNMrY9SQ/ZANet8eYQAAAIBI8hS6bX00NpNXwOaEqr +jZKf/u1W71KHpYBAY1w3xanGqPVOX5PEsFH6NLjqSLF75Bk22pvJvZ8EAaoISyvLSqqO5t +1vvjCjVKALSaVIFDcA20NpCXRugmVT1HeQNKHCTt3yOoraL8Sh9wlRbxLmlxgISYS6uH6e +dEPa6qFshMVwAAAIEA7RFx7+mZJfrSJUu9pYiZJc6+Ns2WrSA2mgI+mIhWqreDK4Kw0a5g +akqD0mb9oPHySnf3lCe+17yqNxH2fcX0G3B5LxiRnFVNm2wC4ZGvb+yMU2+0uSI/Sf8L1N +sfWm+z4VC93Qhe0fIpuk8JAMNOwCvFcEFu5rr9sxHtPWjWtj0AAACBAODYXjs6jqsvW+3P +e0efFp9kIezi8CejSxVXX0/zmWMCpTw1laiUmK41coKTKBSDZcNgrsF/ns1uCrPg6C2u/y +evF8J+DeqU3vo1QhnRAJA2fLZk1Dr/GfAsp9mS9w6FdIQiQjQ0f/X9rYgAr9x4qMogYgrG +zkb7k6FUoGgD7sbnAAAAE2xpYnNzaF90b3J0dXJlX2F1dGgBAgMEBQYH +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa-cert.pub b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa-cert.pub new file mode 100644 index 000000000000..ceb440399915 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa-cert.pub @@ -0,0 +1 @@ +ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgllK1Wz9hM1kUks5QXU/vXbDmzpQWMtFWObMvi9ymg38AAAADAQABAAABAQDQN6GtT+iGiKYwqiMVd2jW4QByrHh9LzsCMNTPy3izjfqmk/7T5Tf6quE4IT+Zdm9d7CNMeW2YS0Ic1JuxaFhOPCsRBQNE05VqGfY8RBNxf/2K2+y/09fv0iZu9mwtHR/UWyaiuRamcNCa6BruFgEUGPixX5UwvmJ6grY6tpQSrjoguzGpGBRN62QSuXNftLZI8Tu80zRqGYD4c0G4o1QP4E60LOSo3nh+UYD1NnuRqkLvGqJEcr5nNqFDY8VHgHFtU/W5MOxqduMo6kELiEhJqjeGLiJSfKPSAnyK2aSFBL6gj/YpZIZbTX+T1buM5RcNK3ssZRNJbqwgtgfOZ58LAAAAAAAAAAAAAAABAAAAE3RvcnR1cmVfYXV0aF9jYXJsb3MAAAAJAAAABWFsaWNlAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCnA2n5vHzZbs/GvRkGloJNV1CXHIS5Xnrm05HusUJSWyPq3I1iCMHdYA7oezHa9GCFYbIenaYPy+G6USQRjYQz8SvAZo06SFNeJSsa1kAIqxzdPT9kBrRrYK39PZQPsYVfRPqZBdmc+jwrfz97IFEJyXMI47FoTGkgEq7eu3z2px/tdIZ34I5Hr5DDBxicZi4jluyRUJHfSPoBxyhF7OkPX4bYkrc691jeIQDxubl650WYLHgFfad0xTzBIFE6XUb55Dp5AgRdevSoso1Pe0IKFxxMVpP664LCbYK06Lv6kcotfFlpvUtR1yx8jToGcSoq5sSzTwvXSHCQQ9ZA1hvFAAABFAAAAAxyc2Etc2hhMi01MTIAAAEAeI9eUAWyL5DVWBj0vU2kKdGxMXQx1Y8R68DXwXhnxfLwilJPa6IFg+g988lpF5aZzvAiX6TgDtJAhzfuBU+ZREGfdclUQIpz3xwDG76Gmg/DpQHdmqU76n2Na32s6+4SsSmWWKx6cPPdjbCRS0VMSrMohLuDyPGMoC7RjLfDDxqW5TIbMtqQdOiPl/0PpR73Q0FjB50Ec12buQDkExlEOi2Y+yB830vuTJN3ds7bx6NXM1Jjftg/8D0SzNRAIYDQFnpyXKO6kNrN66o48E3mrnVHXuFBTf+kpdYrK+1LKQVk/hVLBHr+NuVmQltL0zcjJfiXj7i5ZqcsqR1UAU5hKg== libssh_torture_auth diff --git a/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa.pub b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa.pub new file mode 100644 index 000000000000..3f83c6fcc0e1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQN6GtT+iGiKYwqiMVd2jW4QByrHh9LzsCMNTPy3izjfqmk/7T5Tf6quE4IT+Zdm9d7CNMeW2YS0Ic1JuxaFhOPCsRBQNE05VqGfY8RBNxf/2K2+y/09fv0iZu9mwtHR/UWyaiuRamcNCa6BruFgEUGPixX5UwvmJ6grY6tpQSrjoguzGpGBRN62QSuXNftLZI8Tu80zRqGYD4c0G4o1QP4E60LOSo3nh+UYD1NnuRqkLvGqJEcr5nNqFDY8VHgHFtU/W5MOxqduMo6kELiEhJqjeGLiJSfKPSAnyK2aSFBL6gj/YpZIZbTX+T1buM5RcNK3ssZRNJbqwgtgfOZ58L libssh_torture_auth diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ecdsa b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa new file mode 100644 index 000000000000..7a1827c699d7 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHbL0nzpzLS3ImIlhEffbDzPlIw/tn5QcfB64PbSiBl6oAoGCCqGSM49 +AwEHoUQDQgAERzA8X8OP7C3W/e1UNLh+21xIZVBiQ7i4Qb4xoOebRWuwzitEZon/ +8Dz+VpE29krJgCagqSt5RLllOx8eS2i8fw== +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ecdsa.pub b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa.pub new file mode 100644 index 000000000000..43b613bd8f01 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEcwPF/Dj+wt1v3tVDS4fttcSGVQYkO4uEG+MaDnm0VrsM4rRGaJ//A8/laRNvZKyYAmoKkreUS5ZTsfHktovH8= comment diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk new file mode 100644 index 000000000000..4c35105ab87f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk @@ -0,0 +1,14 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAjwAAACJzay1lY2 +RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQRv1/dD0qNb +Bbm4JmHwa9AQdHwzYOBDkptAAUJcyLX3kc8koKLoQF6rhUKGeZP6pv+AanVRTyOd/ITGUm +Zbgt7hAAAAFHNzaDp0ZXN0QGV4YW1wbGUuY29tAAABkH7S+n5+0vp+AAAAInNrLWVjZHNh +LXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBG/X90PSo1sFub +gmYfBr0BB0fDNg4EOSm0ABQlzItfeRzySgouhAXquFQoZ5k/qm/4BqdVFPI538hMZSZluC +3uEAAAAUc3NoOnRlc3RAZXhhbXBsZS5jb20BAAAA4y0tLS0tQkVHSU4gRUMgUFJJVkFURS +BLRVktLS0tLQpNSGNDQVFFRUlIRGZSL1NqWkRlczZrUmtTM0dLQTZoTUtSYmxRQjFWQlp3 +KzdqR2pIWU5xb0FvR0NDcUdTTTQ5CkF3RUhvVVFEUWdBRWI5ZjNROUtqV3dXNXVDWmg4R3 +ZRRUhSOE0yRGdRNUtiUUFGQ1hNaTE5NUhQSktDaTZFQmUKcTRWQ2hubVQrcWIvZ0dwMVVV +OGpuZnlFeGxKbVc0TGU0UT09Ci0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0KAAAAAA +AAAAABAgMEBQYHCAk= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk.pub b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk.pub new file mode 100644 index 000000000000..fbb35ffd6b79 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk.pub @@ -0,0 +1 @@ +sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBG/X90PSo1sFubgmYfBr0BB0fDNg4EOSm0ABQlzItfeRzySgouhAXquFQoZ5k/qm/4BqdVFPI538hMZSZluC3uEAAAAUc3NoOnRlc3RAZXhhbXBsZS5jb20= phoenix@phoenix-pc diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ed25519 b/src/libs/libssh-0.12.2/tests/keys/id_ed25519 new file mode 100644 index 000000000000..2759f43e9856 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ed25519 @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVgAAAKg+IRNSPiET +UgAAAAtzc2gtZWQyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVg +AAAED2zFg52qYItoZaSUnir4VKubTxJveL9D2oWK7Prg/O24ujq/HWVfplmh7TmVZOS7Cs +lRk3RPyER6vlQX0o+E5WAAAAHmpqZWxlbkB0NDcwcy5qamVsZW4ucmVkaGF0LmNvbQECAw +QFBgc= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ed25519.pub b/src/libs/libssh-0.12.2/tests/keys/id_ed25519.pub new file mode 100644 index 000000000000..accd5b65a629 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIujq/HWVfplmh7TmVZOS7CslRk3RPyER6vlQX0o+E5W jjelen@t470s.jjelen.redhat.com diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk b/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk new file mode 100644 index 000000000000..1622d28a7024 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAWgAAABpzay1zc2 +gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACDa9bna/CE9hXZDMX6I37Re6AlzNVZh0yB8D/U3 +8SS2vgAAABRzc2g6dGVzdEBleGFtcGxlLmNvbQAAALC75D22u+Q9tgAAABpzay1zc2gtZW +QyNTUxOUBvcGVuc3NoLmNvbQAAACDa9bna/CE9hXZDMX6I37Re6AlzNVZh0yB8D/U38SS2 +vgAAABRzc2g6dGVzdEBleGFtcGxlLmNvbQEAAABA7QoCSXA/S9yF96YpCLNTVap+mYg0vH +yhKlMAUNnPqeXa9bna/CE9hXZDMX6I37Re6AlzNVZh0yB8D/U38SS2vgAAAAAAAAAAAQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk.pub b/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk.pub new file mode 100644 index 000000000000..e31326e8389b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAINr1udr8IT2FdkMxfojftF7oCXM1VmHTIHwP9TfxJLa+AAAAFHNzaDp0ZXN0QGV4YW1wbGUuY29t phoenix@phoenix-pc diff --git a/src/libs/libssh-0.12.2/tests/keys/id_rsa b/src/libs/libssh-0.12.2/tests/keys/id_rsa new file mode 100644 index 000000000000..0e3db26b47ef --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAs/fIz+Gy89XjUhXebNq2rs1K0RJEZXDbfcofb13ESDJ9fSbd ++RlCUM6i+foEVV23xWdands2Bkn72tGinJadIgOWU+koxp+gRmtJLE/ONiAKJQqR +K3eOJNBH6At96NMqJ4Gwv56G4W68uPgDwUVQwVIc6Ec6Rypiz7DE+S++gSvBQIVU +Qt7XkPcXOyN5zuj5qDhZM4l6V7Og9LWj4/2IF5acvKjfIMpeqmnp9z6BTYZQtuQU +6b1VuEYhXyvM6LkMwndsVGh46kDKMmRQhyA8A6qvLAliMMq88k5gR4AeJeTnqDR/ +Q120I6PMqYNl6TLTk8/KonOOPDR/XXGb3iF67wIDAQABAoIBAAVoL2dXf5nl1jOU +Jp+cnpp33oSTiOyHTIDl/rXI2mnU4oJNFaQzRxPIcYsTIOgzrZ7HsShG+sOLm36C +h+EugUARXYXd3nTBPP6AoK0tJKPpqIReYegtal7exxpIphrFpWGUeuv25lSFkDP6 +d5pp67gzMF0mLrEOq/NTe0eFULLuwa6+IKXU7deiU90pzi4jrjcIWNoGHSw1YYAZ +TC8KAxA/tYH9myya5krRCjA9B345DJ9Wd71wX+RZNgbSkIri/6dDTtvsYvqcQKo0 +OZ3MUDJnKmkfPLP84qZPRoEwUI1gts1WUdoNK6LK7yOJmPL5FMyTwZx3XtDw3gAv +TVhI7ikCgYEA5Ay0TCySPQAaC14WtjgIAmTa19mAtOFpbRxToi40WjXk3R6mMqyp +biAcNecdZRC6zzgAUp8g1O3Yc1d9fG/3FpM5eUbIer7mMLTRuQQysoJY2Ayw9OEA +qPHS/K6LPOD09aZo14fRUqVO8rwMbHtq2yhH8p3FM8WZRe5ms8zpyLUCgYEAygZ3 +RTMWbgcGdNoaPa5Ms9KRqAxKJLin2fE99KowZeJfvZN24sXExawQdy4BKVYT0H6e +MNEIPiEBVA4a1GDk/tyOrEt684IsidROngJaGbqb+SYm6feQAioYu0wkG/I2hS12 +/Z/aK6wFz5hWzBv/YvJqC7xD1YwZm1QXDyAiL5MCgYAXz8fHqGPAoNEXXMSsVB9p ++JPtM9W/jUXP0cRdy8tFnBkAiaG66tJqIEoxyqcEFYIb/vHxrpHkCc2vBXSh2KMJ +JWg75IssXeB1N3wqgGi2wOt7659SgmfqPA3WunbpbWfGepC56IGPypj6uW3mqeBX +b9ZLW/PqWviNF757iarjfQKBgGxKBPqRxM8bcumF0xUG7dRh5XN3ivKeDFL1Tels +pF6odftPJSWvLqdqcLUBctvuaNaUWEUAdvOei3C70sPOYFEAdnWCTBhkyWzj4XQu +/I7YCS0Gt0soSQfv+qvCx4Q3U+QVF7ghTDemkMLS/IuR4lXubMt3kcDQxRUOgQG5 +jrmDAoGALauF7ZyzEnQgsgMVzfm9znl5I2aIsLgdsAv3lINVrvtTKhddp7cdd+2j +dwZlaMnLET/3MY/Cvf13vEsS+bdNXjsdQidqBL8pe5PXY/pafBhtduQuvGzlHJA5 +CEBnwB0SdtsXbzSpOAPZqea4Nz9MkQ8LMsINdPpxCuFhjeYa9Ow= +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_rsa.pub b/src/libs/libssh-0.12.2/tests/keys/id_rsa.pub new file mode 100644 index 000000000000..15a35b3ad11f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz98jP4bLz1eNSFd5s2rauzUrREkRlcNt9yh9vXcRIMn19Jt35GUJQzqL5+gRVXbfFZ1qd2zYGSfva0aKclp0iA5ZT6SjGn6BGa0ksT842IAolCpErd44k0EfoC33o0yongbC/nobhbry4+APBRVDBUhzoRzpHKmLPsMT5L76BK8FAhVRC3teQ9xc7I3nO6PmoOFkziXpXs6D0taPj/YgXlpy8qN8gyl6qaen3PoFNhlC25BTpvVW4RiFfK8zouQzCd2xUaHjqQMoyZFCHIDwDqq8sCWIwyrzyTmBHgB4l5OeoNH9DXbQjo8ypg2XpMtOTz8qic448NH9dcZveIXrv asn@krikkit.cryptomilk.site diff --git a/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected b/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected new file mode 100644 index 000000000000..034cb2875289 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,0B181CC88F75C33B7DEBE5C18B481F18 + +rYtUw8FhEv48JmNTm3i1TEqEgElC629iaMQu/YzRV5zL/n83HwMKbRpAZ31Cch2a +8thZRQ6YsL/56vr+fKKVgDF7y3wmStp5sVkOQXMeZ9D746ZEGcYGnYH8JQhibDDB +sTK2kQrmBERg7H8rOoNCzxxoK+VZl2Z+S+yLVq0//qxBfuluZwCdk9Tie69Cd/Dk +PeBjOVPnFCavCKCWpUs/So8VQq9jXG84hRltwC0htSTEq+xfgNtS64f63WL6gEnB +WZ5KSO1gyzKC5/YAB6LXPxIIVzfZYXiuOWV/t8DXZK/lvhqQ3gSyPZezSrX8wEMC +xQeX41etQGjCcgxWH41iPCNTuoIKo2t/BPlfLJilJotmUSnYOxDmkZbLabuyS+0p +WGtnEwFSrxQosx6u9GBHX94Ikex0bf00KzNpKExzAIRqTdesaviJ1QX/pRsvT/Xp +TtH2aWV5kYNc+B+BrCQU7mlx/eEtXR2H5zJQxLSrTVKb1vUIHytufnPePk2BkcQ2 +CTE1xT+ZkUaY1WiCBxWgVTflL5FY9E6BerKEGVSfloso8tGCgsoO/Fch0Ho5/bXp +T+3nQEY780KduKJ8xCJJDQgD8GbjNR6sCtcPrewqEsgrpAbJUKyXhU7klGC09zzI +/JnNmdd10w2l/5A92GGrCgXnTYb8/w9J/qa6qyAAYU9/8rPo7ErGb7mKclmzz63j +cksImoExfrr9CIr7wjrXFO0OoupmMegNOZtgwsN7i0FI8vWYc6a3IaFWSWfE29Ux +rw9TK9L9pDvhCqS/WjW86S25muqnTSMQ/bhmiPw8z8tOjdi2YRqNcU2TyWoB2Mct +W+w9G5dSukMwkXQ2RNjDo2GfuXLXpUe5zCVixI2wxYGvIqTGkDZn/u1Jdxy1IxNc +qEsEZAOCVnJU1cQpB9ENsyrRUIsdQVWNQSvsUZz2XSELULwIFTcCTHr2PAJ5xzZ6 +VQy3DGEpZf7+yGACoi8LY8f5Ve5C9NciyA4/C/uvOUd7PhAf4g41mKw8+bAr8NFt +ubeXTo0iI29FkmmebfM1sRBHvomGT7qYsHBW2pgqBrm3X9kFcQ9EFhr6S2ULMcIn +4iX1mbqvC0c1CUmZakkNg94FQp2zbUclAuDkg3BTA0gwbyudvx0ccBmzQ43/6AJ5 +xz1hrfusX5Vcjz6+i5WHJDK/mlUDwTV5GAhcmar9eEcFXJEosD+mrAalflz3Vc2X +5A9plGfKkaFdth8YUGjLr+O2O5ggkDpCMbjYo4HQ6/dslYvqvnavJYrRKrEZbtvj +8fR5E11tPrK1aKzPHO0VLKf4UHs57JNqicSlYGy78FSCPG4d17KQlFyzbXsfbsvp +9EQK4N2jwRNZAOHuTuoqQ8TNzDahdlmbBS2Akd3rVV9H1/eNeN3r6Demww+yixoy +uPhjofn0P28eH7Gqiyhh20QYYqG7aky9IYMPnIBtA1hJp9MtMa1m8aHGxxZrUigj +S62Q34JzA8A6Rwc2kTHRzXG2o6oQ3vCQfy0JGlmDlG2yofcn7YgrMCv+srTniuiA +YBnOeic5cllYnDB9bpF2kufJT6CigoxP18HIw+jhYabuOTHO67MYf2En+is8vlQS +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected.pub b/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected.pub new file mode 100644 index 000000000000..15a35b3ad11f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz98jP4bLz1eNSFd5s2rauzUrREkRlcNt9yh9vXcRIMn19Jt35GUJQzqL5+gRVXbfFZ1qd2zYGSfva0aKclp0iA5ZT6SjGn6BGa0ksT842IAolCpErd44k0EfoC33o0yongbC/nobhbry4+APBRVDBUhzoRzpHKmLPsMT5L76BK8FAhVRC3teQ9xc7I3nO6PmoOFkziXpXs6D0taPj/YgXlpy8qN8gyl6qaen3PoFNhlC25BTpvVW4RiFfK8zouQzCd2xUaHjqQMoyZFCHIDwDqq8sCWIwyrzyTmBHgB4l5OeoNH9DXbQjo8ypg2XpMtOTz8qic448NH9dcZveIXrv asn@krikkit.cryptomilk.site diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256 b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256 new file mode 100644 index 000000000000..3ba5d0f783bf --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHdAXnAQz7Xy1DSC74tj4oPqcMFNld9f6sw/mnalVEjloAoGCCqGSM49 +AwEHoUQDQgAE5a5++ALfsz8CYb2pAzlWlj6ookcas3UmHaOsictgRgl7Nqdd9vTq +QPQSbF4oRMhSbfTlqO924OJwzc1WaYKnFw== +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub new file mode 100644 index 000000000000..aa1d415e93fa --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5a5++ALfsz8CYb2pAzlWlj6ookca +s3UmHaOsictgRgl7Nqdd9vTqQPQSbF4oRMhSbfTlqO924OJwzc1WaYKnFw== +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub new file mode 100644 index 000000000000..5ae82b88c0e4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub @@ -0,0 +1,2 @@ +#ecdsa public key in openssh format for authorized_keys +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOWufvgC37M/AmG9qQM5VpY+qKJHGrN1Jh2jrInLYEYJezanXfb06kD0EmxeKETIUm305ajvduDicM3NVmmCpxc= sprasad@linux.fritz.box diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384 b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384 new file mode 100644 index 000000000000..672e59c77953 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCM82jhy7V0k9pYkhGeqk8xYcH72RgLnLmhY/9nuq5+9+e6bZr6sdR+ +ZYknMIZB4rmgBwYFK4EEACKhZANiAAT/c5JrFJK9xmqVZpuDWcOULHYwnmdjBfa9 +7W17gxC5m6armE67TQGD/3KwI7k7+3ngqGsYuA9UWSiyZxuhIa0FMegEN+hGylp6 +H/LokPiBQX7FMImJEHRMTr7ti9OJxYU= +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub new file mode 100644 index 000000000000..d40891238077 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE/3OSaxSSvcZqlWabg1nDlCx2MJ5nYwX2 +ve1te4MQuZumq5hOu00Bg/9ysCO5O/t54KhrGLgPVFkosmcboSGtBTHoBDfoRspa +eh/y6JD4gUF+xTCJiRB0TE6+7YvTicWF +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub new file mode 100644 index 000000000000..225ff3fcd3e5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub @@ -0,0 +1,2 @@ +#ecdsa public key in openssh format for authorized_keys +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBP9zkmsUkr3GapVmm4NZw5QsdjCeZ2MF9r3tbXuDELmbpquYTrtNAYP/crAjuTv7eeCoaxi4D1RZKLJnG6EhrQUx6AQ36EbKWnof8uiQ+IFBfsUwiYkQdExOvu2L04nFhQ== sprasad@linux.fritz.box diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521 b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521 new file mode 100644 index 000000000000..c1a9a2d88cf4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIAIFhJ0TIwP+05/nqBcBIit6DoynzszbP5B7K8tk6d+R741dByNe7x +lsXvKpHkZ+oGn575LQmCJ1BQ+BENxj0G+b2gBwYFK4EEACOhgYkDgYYABADI9DFD +5j3ibs7pr0NRqf4AzMwq4J+OlvBl60fWGiNVvCsC4EQL99TAGcx8VryXybJZ9fmG +C0obHdEYXaRddOpOQgGLzYGTYFzQmF91PLNHIUb1K2IGUN8V8Ehr9qYMGcF/2HFN +VJ/7Iievz67wKcaVJiAoJ7zNpKpFE1fEUHqQSc9vdA== +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub new file mode 100644 index 000000000000..9f7be9986e15 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAyPQxQ+Y94m7O6a9DUan+AMzMKuCf +jpbwZetH1hojVbwrAuBEC/fUwBnMfFa8l8myWfX5hgtKGx3RGF2kXXTqTkIBi82B +k2Bc0JhfdTyzRyFG9StiBlDfFfBIa/amDBnBf9hxTVSf+yInr8+u8CnGlSYgKCe8 +zaSqRRNXxFB6kEnPb3Q= +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub new file mode 100644 index 000000000000..5ede53226f60 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub @@ -0,0 +1,2 @@ +#ecdsa public key in openssh format for authorized_keys +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADI9DFD5j3ibs7pr0NRqf4AzMwq4J+OlvBl60fWGiNVvCsC4EQL99TAGcx8VryXybJZ9fmGC0obHdEYXaRddOpOQgGLzYGTYFzQmF91PLNHIUb1K2IGUN8V8Ehr9qYMGcF/2HFNVJ/7Iievz67wKcaVJiAoJ7zNpKpFE1fEUHqQSc9vdA== sprasad@linux.fritz.box diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519 b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519 new file mode 100644 index 000000000000..e17372fdbfc2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519 @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEINATLZmMLR6HQ2076Uj6VQDYcxPIXrBV2TLU9UyJZSpK +-----END PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519.pub new file mode 100644 index 000000000000..d643f956ca7d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519.pub @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA3VoMJl9A48CsizGdLy4yKltC2Mz8UPvv6GmTKj2L3lY= +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519_openssh.pub new file mode 100644 index 000000000000..96e321912fc8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519_openssh.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN1aDCZfQOPArIsxnS8uMipbQtjM/FD77+hpkyo9i95W diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa new file mode 100644 index 000000000000..caedcdd8160b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqsV/cOD8KGdJfTEZ+hemwBONeWEVZQsY05EorJ7prWcrRyHs +wg3+AhwFGW17HVKBt2hgJSnmAbU6dYZ/2t4OYWvCCGIBGSbMQldfesuZ160OEu0i +FCVZinAeUVn10iTxwMOM6oUQY75UF5tKg72WGuY5x5zolPAyDXkK0aJ/ZHB9dmfG +v0zjutY2aURGYK+dzNT/xzFsKRQiBc7ROi5eMkXNpK+wgqFrs5Ydpj+xiGt6sowb +114hF6YyVdXpNw+EQrYqpFVRahUDxo2qFBFqXVrCJrmtdFf6Z03FnGwRnFumLVsc +1P9SLVIk4OFh9KndHIevCfj3vfFgQo/A1RzZqwIDAQABAoIBAF9/9xb/GsTVjilu +ziIoG96KkLFyc26AWC/om6B9dhy891zeQfL7tDMlzEw+B32MaHJS8oGV/sSxvrF+ +t/2D9PLSOj5J4GdaZRwslH6tYalVY+t9pXMyt/JNZQcFkg0lD/VG5oU4SMQe6hQR +ighGe60rVuCkhQTVlogmSsCVaTyXKa5oUeZCIaO5gxxvTYsgZiF+U/wsmzoRm1kY +WlqwqA3KM0p/AtFUNQD0iS3zoALwwsYg6MID657IbyyjEcnwpss+3hMV2SnQNpYY ++sBccnS8YCGHkA/KYc0e1gUmFhQkTzeSnhgFg/W7JdTJdyHKCiisNMpWOtqtXano +fiXFClECgYEA3dEMsF2AA5m14V68907J1Wi3Pf9cInmsrDlxpGcdyJiii4zeg1/w +6tLQ6H4+wFoRXTIcduNpZZS8al/zQXqQGH4A9Em38PLkOjPk5mdAV3KCv0LzMkPq +IwgfBFBPRPUVUtfiydLpzqeTQZ6RxokqPy4JY4NEy22gfWsMhKq9YVcCgYEAxRan +Ax0GnTpnZ9hYcJEi+fw7cmK6bX/fVzaNSqSirWNOgjlw1IHt/zp5jylW1HDbk7zJ +5fLnS78fm5WWq2Ov/U71C8KClKVWecKbdYDtuHs7VPM6b1jee/EUqkZevj+vPp4A +2vITwPCUk2EMwuPs8kcEwohfgbS3erNfBkDl8c0CgYAGzLa/3U3kTlz1+KqARkkH +orsjSmWvpN03NleWe5a9JHivIHVdv54qBKZkyiHwUZKAsd2Pg28irwmLlT9mvXQX +XB15X50k5L08T4TvzzB4vcjmRg4gd1aBFmmk/zU+3uh1bqrEFxQVwqtP8qVzWZcS +8r1U8Jw2RDkMPzDWDEnb5wKBgQCjxtsqXyhr52iehs2XXUQvcEcGGrzI2YYCtzZa +XM1CQE+xL4JvVTks6q6xJK0fBFmzPyKXj25dJ4lghMIb0k8gtEg1aYGRfIOL0wfU +cTsaHm2DfkuE09iuLfv6M1fvyIQcCLi1OIzpvAH7Jp6wJS91dUajUSfsZPxDXmMM +k0mB0QKBgB2cD2IMG2d3dOOoBbIw8NvVDR6bpdF4DdYJlCpGa7sfzw3y9yXnejzW +HQUZGvgvhMsMe9LolS0jE6D2MyOsZF3CPFLuDVemfMxDJRTqvsRAjuEPlz6co/uZ +ZVKlh/bW/iakKpaW9vSF1dNwiyWJhzVkutBho3FzZKZSbIkfFztB +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa.pub new file mode 100644 index 000000000000..b1f8f64235c6 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqsV/cOD8KGdJfTEZ+hem +wBONeWEVZQsY05EorJ7prWcrRyHswg3+AhwFGW17HVKBt2hgJSnmAbU6dYZ/2t4O +YWvCCGIBGSbMQldfesuZ160OEu0iFCVZinAeUVn10iTxwMOM6oUQY75UF5tKg72W +GuY5x5zolPAyDXkK0aJ/ZHB9dmfGv0zjutY2aURGYK+dzNT/xzFsKRQiBc7ROi5e +MkXNpK+wgqFrs5Ydpj+xiGt6sowb114hF6YyVdXpNw+EQrYqpFVRahUDxo2qFBFq +XVrCJrmtdFf6Z03FnGwRnFumLVsc1P9SLVIk4OFh9KndHIevCfj3vfFgQo/A1RzZ +qwIDAQAB +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa_openssh.pub new file mode 100644 index 000000000000..e3e2d6f464a2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa_openssh.pub @@ -0,0 +1,2 @@ +#rsa public key in openssh format for authorized_keys +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqxX9w4PwoZ0l9MRn6F6bAE415YRVlCxjTkSisnumtZytHIezCDf4CHAUZbXsdUoG3aGAlKeYBtTp1hn/a3g5ha8IIYgEZJsxCV196y5nXrQ4S7SIUJVmKcB5RWfXSJPHAw4zqhRBjvlQXm0qDvZYa5jnHnOiU8DINeQrRon9kcH12Z8a/TOO61jZpREZgr53M1P/HMWwpFCIFztE6Ll4yRc2kr7CCoWuzlh2mP7GIa3qyjBvXXiEXpjJV1ek3D4RCtiqkVVFqFQPGjaoUEWpdWsImua10V/pnTcWcbBGcW6YtWxzU/1ItUiTg4WH0qd0ch68J+Pe98WBCj8DVHNmr diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key b/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key new file mode 100644 index 000000000000..1fcd836f5359 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIB9v2n1oaXvBECf0gDPxTibeUPvvkI1anNWDAIkNjs5JoAoGCCqGSM49 +AwEHoUQDQgAEqkTqNu7gRegPJRy0WiseJz9NAdBimzyNSzNwI5eAkEqv9D6Y95KL +7DBEnDQ2p08iOLw+vN1PKHsCM7b/ONbYVg== +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key.pub b/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key.pub new file mode 100644 index 000000000000..460153a5c934 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKpE6jbu4EXoDyUctForHic/TQHQYps8jUszcCOXgJBKr/Q+mPeSi+wwRJw0NqdPIji8PrzdTyh7AjO2/zjW2FY= asn@magrathea diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_key b/src/libs/libssh-0.12.2/tests/keys/ssh_host_key new file mode 100644 index 0000000000000000000000000000000000000000..41d3bd654d8cb0360afb827944d15f361f542f45 GIT binary patch literal 978 zcmV;@11PJZIMaz$?DUjbIWST9YuP zbg5Fk(Sf1I5Gc3T(5=YXx+7BnwHv6J-$6*a<}0F z$iBMCMxo1JrUsR^ed|)bSjN*p&0)G>NJp1_M^r?y+3JlJ(j$3z#q8)3xY%!(R#} zm5Mlb1fdF4=eb&VKgC@b1NK*QPA<9=3@Ha*hE~U%cqEa&RpiNM~y95DmAwN5di=J z0000DVRLRkZDD6}VRUF^VZ3j=ZwLU4z47w@58QP~@h~Ha8!%i)2OCUfv$9hws=Qb? zs;m}bHsO@7#gT}6&^LgenkR7Dlv`IzR#51^T2(CfBSh=0m5sIx+4zJ~TuH5X!SQOL z0)bbz;E;IcKgfxKIYq|jM2>OcRpm8<55#SiB}qatS*B@~ILKy*1<$9NxyT(%K3MdwG0Ts9LBUQdeQK)mIf9y@Rs%6B zVoB<=OOw~v6NZM|SUZQlcKope|6nEk-WD|}jvvVXG;=q|Lg+|bbb5!l5JjBB?U5GK z4;-!{s0cYVgC*vwB3C5al^p1#5~p7AgG}^D6YMK_`R94GMBgQW7q}(vXF>lCIx0fpvV3+k|A0E)H-^h0-5TXsd5L<9iZ(qAB@ zgjKgz-?+N$$=J(=`FLcL>YT<>|I@Lsg%Lx=i^cwLv|v*7h$Lhw6+Jw-4hZX1ig_JO ztMl>Fz7wxi9IE>X<~ca3I#t5!g2{!dA~BHG;G<5PZ+R?vc7}KlctaXshHDi#Seu~3 zs*47MVkxDpn*Pe2-c`)aSgV@fCwSV#aO7U7DY&2Xv3M(|mA38vPCC4hl>h($0F*z_ Am;e9( literal 0 HcmV?d00001 diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_key.pub b/src/libs/libssh-0.12.2/tests/keys/ssh_host_key.pub new file mode 100644 index 000000000000..ae7cc2327386 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_key.pub @@ -0,0 +1 @@ +2048 65537 25221975523736997039149017470335977198642717886559395625730372192276493838727011206749822289920387480933533054627057418868711378045090730895752530916661328094497437687453813456961487210492465678475508526337829331199296553120728607984859224949182503917312492825658971738208505685553964707412720244524969161284321098487507924676797222812771309962906894332072854924265623785469343453142982185436565166155021228521252914913227554455102103918367844210755391318078654400527927267478149210805219779896806429660492177158822689909493046725157917529664436252598971135251689616517266344945600782273453037452082373553352939812279 asn@magrathea diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key b/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key new file mode 100644 index 000000000000..5032fb740184 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqzabeU0oKbHDwdlqindABvtzgWCvXdHJ+d2Ew6te2LXjkwju +y7u6B7y63NZRy57ccrE4YSeWItVoZn+DWN+guU354Ss/tzQ9/thUmLrvtKNvJwuF +F5Ch7Q4BsrXGsb2GSv+7W5tpx3yAqH1TvKQj/MmQVX+/9KtXEnh7/vpiCqoOXQAR +zvIIQxoo1aUQBHAkRW7Yw0Ds7AjC9uV1ns4xdBXPQmDk23pWvCq7E+7rOEbKRrjj +lApS4lJYy0oEXsFdqUPd+PtT1gG0nIElHFQtsTgUvwYQmJEzrBxv41odwRvxbMjg +THPr4SLRRRSuPwICWyvxqPpa7EsfMBnbPEIUzQIDAQABAoIBAEUO15MLvgFjRDQy +P7jt9JNcZPBwUQukjLUN1nkd7Dm407wAxGDErXplc3GTuJZK01wngzgcwX/3WA7P +q+jy+l8DxqA904tPtRnPo/+elwTjTvgOu3YPzmBRX/n3O9eBPGOP1sBSZU4jN7m+ +I0JZanKR0nfJ+WD0o0A9/LWRxG3MFIntBamtT6pgee8sAu44IvW0o7tHJabMq02J +Z/ndrJmox34wq6SMFANax+N1x9sZa60bL7gEoDWQJNKOaMrbtOaIoTGFIc4hFqoA +SzjNqcGsHPWs44cw0mNkUGq37jEvaCwzAp+U80ma1skBhXuJL9sQOxl1v5qW91c/ +Cnm5WYECgYEA4DPvqbLt+VdyTtmCQ370yiCk4OPPMPzbM65IVKgQL/rN6HdNShTO +uLF6P8XC8vNP2OSydJeFt+kMKd7E/4o5LfvEqUGXZJDkB7fLjrOjyZU3bxtIx95x +qYGWRcWbd3sHzlBJGuFVSE7GREE+lqhkSu4ry4l/GAKxSymAXgGd/9ECgYEAw37L +ppZIavcLE2rZgXHoqMiJzeGzsidJbkHss4k7ubLe8vyBMiv0HC2anxPa2+yNWuF2 ++pEr84bllh149VKeild24UEBAR2w/P41ggWqiUP7PKllh+huWzG4+KNFbfUP4dd0 +4LkVgfsCz32qD8qxXNCxJCZ8H2fmjKsYw/oCID0CgYAiuSh3GdUtdtOnTpyUI4d5 +/pBKnD2skpzIZkehhN3s8GUPidqYjJxvkl0in1hQFErbhp/02rrE/vz5Rx0vjpLI +gmO06wmtc5s9bsPB+CR3xfpt5MXi3pqv6/gAGli3qoBM/bY0yY1Rw5GFZK1y2+Wc +jUKPJV5fs5sNzwGojYuQ4QKBgQCNNgqOo2Fd+mLCvNyt1wTy3iBEWfL+DcjJ3s7G +hKtioKTQqbn87qjercZRf/sH/t/ANLpHlhNETj2KaHGV6v7f+PvDC7xY/QR6SnmG +GOetTTCuCcJwIGGOd+UfnHgrS+gT/xjKtoalpBXMoP31eDkTTR+XeEESQm/TTkeO +UAm3FQKBgD8Y7CLHpyZZ+eOnxRSPU4m4AWAEp7JOwHDRWWQeUornrXDYgD87d2M9 +iIAEuOzNggA56Nm3AzBOPRj4HkBh57ToVKPswHwB0oWvrtSjpLkkU6q8xRG3XuJD +2AskDaZONzIDoJGfZ3+W7YbKELK7DPtFXL15sOfBmpoEkI9RA5vM +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key.pub b/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key.pub new file mode 100644 index 000000000000..efa21919418b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrNpt5TSgpscPB2WqKd0AG+3OBYK9d0cn53YTDq17YteOTCO7Lu7oHvLrc1lHLntxysThhJ5Yi1Whmf4NY36C5TfnhKz+3ND3+2FSYuu+0o28nC4UXkKHtDgGytcaxvYZK/7tbm2nHfICofVO8pCP8yZBVf7/0q1cSeHv++mIKqg5dABHO8ghDGijVpRAEcCRFbtjDQOzsCML25XWezjF0Fc9CYOTbela8KrsT7us4RspGuOOUClLiUljLSgRewV2pQ934+1PWAbScgSUcVC2xOBS/BhCYkTOsHG/jWh3BG/FsyOBMc+vhItFFFK4/AgJbK/Go+lrsSx8wGds8QhTN asn@magrathea diff --git a/src/libs/libssh-0.12.2/tests/keys/user_ca b/src/libs/libssh-0.12.2/tests/keys/user_ca new file mode 100644 index 000000000000..dc9d80c2dea1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/user_ca @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApwNp+bx82W7Pxr0ZBpaCTVdQlxyEuV565tOR7rFCUlsj6tyN +YgjB3WAO6Hsx2vRghWGyHp2mD8vhulEkEY2EM/ErwGaNOkhTXiUrGtZACKsc3T0/ +ZAa0a2Ct/T2UD7GFX0T6mQXZnPo8K38/eyBRCclzCOOxaExpIBKu3rt89qcf7XSG +d+COR6+QwwcYnGYuI5bskVCR30j6AccoRezpD1+G2JK3OvdY3iEA8bm5eudFmCx4 +BX2ndMU8wSBROl1G+eQ6eQIEXXr0qLKNT3tCChccTFaT+uuCwm2CtOi7+pHKLXxZ +ab1LUdcsfI06BnEqKubEs08L10hwkEPWQNYbxQIDAQABAoIBACW2AaHgS5iVCtln +LVVterKX+pyEVfu9N6cTMqpg4AbUiYGol0wBijTAUd1wo8s6zuiPLLb5BdwfPzLg +y3IjMCzCUgy5mz4Dwr9JSThgFElgyb2y7LNbSDXOuLqrwtjgTqs6WhNfXMmzPw7b +Rqw4mdPJ5u2k7BQO3NXfIhks4ISYzpzNAwj1a2NMphvkZyvfRnWiQ0pvEXQCxwuR +74iGpPFeyFjjku/O4TiHZllPmDdD3ERalkf8RIudQ5gcbL4fRoONTzfZHtmARWoP +Jury4Zfr5b3VGSnkUDaGlzilXvBusAZOCaaU7chvOPVjXMbSAUEpFBmnRHk5dfrH +fCXECcECgYEA0KMtV3IzwMToVdvzcMQc1ovDvKZAQPneLTxFgNpOeycOhzulzY9p +3fRi5QUOA/Ff+LcCL86APqwoEYe4bgam6mwGFFhv1usf4ulbLNk8ZeR51CG6emPt +tLpg6PThxhMnNpu+StrBAOxeo9pZGd+Plt6d4vfoalOHVkPlSv7OC9kCgYEAzO1I +HuZAQkVdKLGuZlf8E4VEaiMBKdl5+H+8w9peOOax6nqAIrwp2d0aZ52LDjwg7d3C +eSmxu0U1jsbzexVVePr/NmdJOu3+gB0GvlzRjS1xT+MCZIye5a7Nxc7lBp5rFmgV +dJTA6XXRoykinZIxz068SHqtNhNOzO4hUmPDN80CgYAlxOR4aBwmUX8dy+uOBnKS +BEsy44XOPW2TEs4iPWLnuHJQ2ONzCvtHSu58NyYKYK/W/opOzTs6HUBDrCYfBOVC +mrufA0N7zKTBFy2COPFOIMZNOK3haiWmCfdxNKOKj/0RTbBtLJyz5hZb4zMuE+KS +lUpPxEE2vlhJrZDcurPiQQKBgQCIEqMKCX/vwVlLlTglsxSp7ZrxEw9Jt6O68y7n +qc9Y3y6ScQc2iVUM2jkXRlA4goqnB9KDW8EthZY7mTXBq/fWXmwqtsi0faW5cgyx +SLbIlL0h+63yEEHOZ5UxXOFM1NJszW45vDCglOBABCd9E79JVZHGWtc7CfUQNKsh +pybQnQKBgHbPnITR7esVQYLq3PHSsdOdkFiiVf3D7wHiNZcXWjJvUqMF4tH5XAzY +QafKqKk0FzO92ZOhQeB5xauFY5wzsa+Xl8cQkyvtWngFIKbWydEehZWVgXcedxEC +xjbZWKmsYDqBYi3bw9Dxb0AvT+kDtq0Azi8QTDAvRwylvtkYj/V8 +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/pkcs11/setup-softhsm-tokens.sh b/src/libs/libssh-0.12.2/tests/pkcs11/setup-softhsm-tokens.sh new file mode 100755 index 000000000000..ddc684a06543 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkcs11/setup-softhsm-tokens.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# The client keys are stored in a SoftHSM device. + +TESTDIR=$1 +PRIVKEY=$2 +OBJNAME=$3 +TOKENLABEL=$3 # yeah. The same as object label +LOADPUBLIC=$4 +LIBSOFTHSM_PATH=$5 +shift 5 + +PUBKEY="$PRIVKEY.pub" + +echo "TESTDIR: $TESTDIR" +echo "PRIVKEY: $PRIVKEY" +echo "PUBKEY: $PUBKEY" +echo "OBJNAME: $OBJNAME" +echo "TOKENLABEL: $TOKENLABEL" +echo "LOADPUBLIC: $LOADPUBLIC" + +if [ ! -d "$TESTDIR/db" ]; then + # Create temporary directory for tokens + install -d -m 0755 "$TESTDIR/db" + + # Create SoftHSM configuration file + cat >"$TESTDIR/softhsm.conf" < + */ + +#ifndef __PKD_CLIENT_H__ +#define __PKD_CLIENT_H__ + +#include "config.h" +#include "tests_config.h" + +/* OpenSSH */ + +#define OPENSSH_BINARY SSH_EXECUTABLE +#define OPENSSH_KEYGEN "ssh-keygen" + +#define OPENSSH_HOSTKEY_ALGOS \ + "-o HostKeyAlgorithms=" \ + OPENSSH_KEYS + +#define OPENSSH_PKACCEPTED_TYPES \ + "-o PubkeyAcceptedKeyTypes=" \ + OPENSSH_KEYS + +#ifdef HAVE_SK_DUMMY +#define SECURITY_KEY_PROVIDER \ + "-oSecurityKeyProvider=\"" SK_DUMMY_LIBRARY_PATH "\" " +#else +#define SECURITY_KEY_PROVIDER "" +#endif + +/* GlobalKnownHostsFile is just a place holder and won't actually set the hostkey */ +#define OPENSSH_CMD_START(hostkey_algos) \ + OPENSSH_BINARY " " \ + "-o UserKnownHostsFile=/dev/null " \ + "-o StrictHostKeyChecking=no " \ + SECURITY_KEY_PROVIDER \ + "-o GlobalKnownHostsFile=%s " \ + "-F /dev/null " \ + hostkey_algos " " \ + OPENSSH_PKACCEPTED_TYPES " " \ + "-i " CLIENT_ID_FILE " " \ + "1> %s.out " \ + "2> %s.err " \ + "-vvv " + +#define OPENSSH_CMD_END "-p 1234 localhost ls" + +#define OPENSSH_CMD \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) OPENSSH_CMD_END + +#define OPENSSH_KEX_CMD(kexalgo) \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-o KexAlgorithms=" kexalgo " " OPENSSH_CMD_END + +#define OPENSSH_CIPHER_CMD(ciphers) \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-c " ciphers " " OPENSSH_CMD_END + +#define OPENSSH_MAC_CMD(macs) \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-c aes128-ctr,aes192-ctr,aes256-ctr,aes256-cbc,aes192-cbc,aes128-cbc -o MACs=" macs " " OPENSSH_CMD_END + +#define OPENSSH_HOSTKEY_CMD(hostkeyalgo) \ + OPENSSH_CMD_START("-o HostKeyAlgorithms=" hostkeyalgo " ") OPENSSH_CMD_END + +#define OPENSSH_CERT_CMD \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-o CertificateFile=" CLIENT_ID_FILE "-cert.pub " OPENSSH_CMD_END + +#define OPENSSH_SHA256_CERT_CMD \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-o CertificateFile=" CLIENT_ID_FILE "-sha256-cert.pub " OPENSSH_CMD_END + +/* Dropbear */ + +#define DROPBEAR_BINARY DROPBEAR_EXECUTABLE +#define DROPBEAR_KEYGEN "dropbearkey" + +/* HostKeyAlias is just a place holder and won't actually set the hostkey */ +#define DROPBEAR_CMD_START \ + DROPBEAR_BINARY " " \ + "-y -y " \ + "-o HostKeyAlias=%s " \ + "-i " CLIENT_ID_FILE " " \ + "1> %s.out " \ + "2> %s.err " + +#define DROPBEAR_CMD_END "-p 1234 localhost ls" + +#define DROPBEAR_CMD \ + DROPBEAR_CMD_START DROPBEAR_CMD_END + +#if 0 /* dbclient does not expose control over kex algo */ +#define DROPBEAR_KEX_CMD(kexalgo) \ + DROPBEAR_CMD +#endif + +#define DROPBEAR_CIPHER_CMD(ciphers) \ + DROPBEAR_CMD_START "-c " ciphers " " DROPBEAR_CMD_END + +#define DROPBEAR_MAC_CMD(macs) \ + DROPBEAR_CMD_START "-m " macs " " DROPBEAR_CMD_END + +/* PuTTY */ + +#define PUTTY_BINARY PUTTY_EXECUTABLE +#define PUTTY_KEYGEN PUTTYGEN_EXECUTABLE + +#define PUTTY_CMD_START \ + PUTTY_BINARY " " \ + "-batch -ssh -P 1234 " \ + "-i " CLIENT_ID_FILE " " \ + "-hostkey $(" OPENSSH_KEYGEN \ + " -l -f %s.pub -E md5 | awk '{print $2}' | cut -d: -f2-) " \ + "1> %s.out 2> %s.err " + +#define PUTTY_CMD_END " localhost ls" + +#define PUTTY_CMD \ + PUTTY_CMD_START PUTTY_CMD_END + +#endif /* __PKD_CLIENT_H__ */ diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.c b/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.c new file mode 100644 index 000000000000..6c9ca03e2f92 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.c @@ -0,0 +1,596 @@ +/* + * pkd_daemon.c -- a sample public-key testing daemon using libssh + * + * Uses public key authentication to establish an exec channel and + * echo back payloads to the user. + * + * (c) 2014 Jon Simons + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "torture.h" // for ssh_fips_mode() +#include "pkd_daemon.h" + +#include // for cmocka +#include + +static int pkdout_enabled; +static int pkderr_enabled; + +static void pkdout(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2); +static void pkderr(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2); + +static void pkdout(const char *fmt, ...) { + va_list vargs; + if (pkdout_enabled) { + va_start(vargs, fmt); + vfprintf(stdout, fmt, vargs); + va_end(vargs); + } +} + +static void pkderr(const char *fmt, ...) { + va_list vargs; + if (pkderr_enabled) { + va_start(vargs, fmt); + vfprintf(stderr, fmt, vargs); + va_end(vargs); + } +} + +/* + * pkd state: only one thread can run pkd at a time --------------------- + */ + +static struct { + int rc; + pthread_t tid; + int keep_going; + volatile int pkd_ready; +} ctx; + +static struct { + int server_fd; + int req_exec_received; + int close_received; + int eof_received; +} pkd_state; + +static void pkd_sighandler(int signum) { + (void) signum; +} + +static int pkd_init_libssh(void) +{ + int rc = ssh_threads_set_callbacks(ssh_threads_get_pthread()); + return (rc == SSH_OK) ? 0 : 1; +} + +static int pkd_init_server_fd(short port) { + int rc = 0; + int yes = 1; + struct sockaddr_in addr; + + int server_fd = socket(PF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + rc = -1; + goto out; + } + + rc = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); + if (rc != 0) { + goto outclose; + } + + memset(&addr, 0x0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + rc = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rc != 0) { + goto outclose; + } + + rc = listen(server_fd, 128); + if (rc == 0) { + goto out; + } + +outclose: + close(server_fd); + server_fd = -1; +out: + pkd_state.server_fd = server_fd; + return rc; +} + +static int pkd_accept_fd(void) +{ + int fd = -1; + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + + do { + fd = accept(pkd_state.server_fd, (struct sockaddr *) &addr, &len); + } while ((ctx.keep_going != 0) && (fd < 0) && (errno == EINTR)); + + return fd; +} + +static void pkd_eof(ssh_session session, + ssh_channel channel, + void *userdata) { + (void) session; + (void) channel; + (void) userdata; + pkdout("pkd_eof\n"); + pkd_state.eof_received = 1; +} + +static void pkd_chan_close(ssh_session session, + ssh_channel channel, + void *userdata) { + (void) session; + (void) channel; + (void) userdata; + pkdout("pkd_chan_close\n"); + pkd_state.close_received = 1; +} + +static int pkd_req_exec(ssh_session s, + ssh_channel c, + const char *cmd, + void *userdata) { + (void) s; + (void) c; + (void) cmd; + (void) userdata; + /* assumes pubkey authentication has already succeeded */ + pkdout("pkd_req_exec\n"); + pkd_state.req_exec_received = 1; + return 0; +} + +/* assumes there is only ever a single channel */ +static struct ssh_channel_callbacks_struct pkd_channel_cb = { + .channel_eof_function = pkd_eof, + .channel_close_function = pkd_chan_close, + .channel_exec_request_function = pkd_req_exec, +}; + +static int pkd_auth_pubkey_cb(ssh_session s, + const char *user, + ssh_key key, + char state, + void *userdata) { + (void) s; + (void) user; + (void) key; + (void) state; + (void) userdata; + pkdout("pkd_auth_pubkey_cb keytype %s, state: %d\n", + ssh_key_type_to_char(ssh_key_type(key)), state); + if ((state == SSH_PUBLICKEY_STATE_NONE) || + (state == SSH_PUBLICKEY_STATE_VALID)) { + return SSH_AUTH_SUCCESS; + } + return SSH_AUTH_DENIED; +} + +static int pkd_service_request_cb(ssh_session session, + const char *service, + void *userdata) { + (void) session; + (void) userdata; + pkdout("pkd_service_request_cb: %s\n", service); + return (0 == (strcmp(service, "ssh-userauth"))) ? 0 : -1; +} + +static ssh_channel pkd_channel_openreq_cb(ssh_session s, + void *userdata) { + ssh_channel c = NULL; + ssh_channel *out = (ssh_channel *) userdata; + + /* assumes pubkey authentication has already succeeded */ + pkdout("pkd_channel_openreq_cb\n"); + + c = ssh_channel_new(s); + if (c == NULL) { + pkderr("ssh_channel_new: %s\n", ssh_get_error(s)); + return NULL; + } + + ssh_callbacks_init(&pkd_channel_cb); + pkd_channel_cb.userdata = userdata; + if (ssh_set_channel_callbacks(c, &pkd_channel_cb) != SSH_OK) { + pkderr("ssh_set_channel_callbacks: %s\n", ssh_get_error(s)); + ssh_channel_free(c); + c = NULL; + } + + *out = c; + + return c; +} + +static struct ssh_server_callbacks_struct pkd_server_cb = { + .auth_pubkey_function = pkd_auth_pubkey_cb, + .service_request_function = pkd_service_request_cb, + .channel_open_request_session_function = pkd_channel_openreq_cb, +}; + +static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) +{ + int rc = -1; + ssh_bind b = NULL; + ssh_session s = NULL; + ssh_event e = NULL; + ssh_channel c = NULL; + enum ssh_bind_options_e opts = -1; + + int level = args->opts.libssh_log_level; + enum pkd_hostkey_type_e type = args->type; + const char *hostkeypath = args->hostkeypath; + const char *all_kex = NULL; + const char *all_ciphers = NULL; + const char *all_macs = NULL; + const uint64_t rekey_data_limit = args->rekey_data_limit; + bool process_config = false; + + pkd_state.eof_received = 0; + pkd_state.close_received = 0; + pkd_state.req_exec_received = 0; + + b = ssh_bind_new(); + if (b == NULL) { + pkderr("ssh_bind_new\n"); + goto outclose; + } + + if (type == PKD_RSA || + type == PKD_ED25519 || + type == PKD_ECDSA) { + opts = SSH_BIND_OPTIONS_HOSTKEY; + } else { + pkderr("unknown hostkey type: %d\n", type); + rc = -1; + goto outclose; + } + + rc = ssh_bind_options_set(b, opts, hostkeypath); + if (rc != 0) { + pkderr("ssh_bind_options_set: %s\n", ssh_get_error(b)); + goto outclose; + } + + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_LOG_VERBOSITY, &level); + if (rc != 0) { + pkderr("ssh_bind_options_set log verbosity: %s\n", ssh_get_error(b)); + goto outclose; + } + + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_PROCESS_CONFIG, + &process_config); + if (rc != 0) { + pkderr("ssh_bind_options_set process config: %s\n", ssh_get_error(b)); + goto outclose; + } + + if (!ssh_fips_mode()) { + const char *all_hostkeys = NULL; + /* Add methods not enabled by default */ + + /* Enable all supported key exchange methods */ + all_kex = ssh_get_supported_methods(SSH_KEX); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_KEY_EXCHANGE, all_kex); + if (rc != 0) { + pkderr("ssh_bind_options_set kex methods: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* Enable all supported ciphers */ + all_ciphers = ssh_get_supported_methods(SSH_CRYPT_C_S); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_CIPHERS_C_S, all_ciphers); + if (rc != 0) { + pkderr("ssh_bind_options_set Ciphers C-S: %s\n", ssh_get_error(b)); + goto outclose; + } + + all_ciphers = ssh_get_supported_methods(SSH_CRYPT_S_C); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_CIPHERS_S_C, all_ciphers); + if (rc != 0) { + pkderr("ssh_bind_options_set Ciphers S-C: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* Enable all hostkey algorithms */ + all_hostkeys = ssh_get_supported_methods(SSH_HOSTKEYS); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, all_hostkeys); + if (rc != 0) { + pkderr("ssh_bind_options_set Hostkeys: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* Enable all message authentication codes */ + all_macs = ssh_get_supported_methods(SSH_MAC_C_S); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_HMAC_C_S, all_macs); + if (rc != 0) { + pkderr("ssh_bind_options_set MACs C-S: %s\n", ssh_get_error(b)); + goto outclose; + } + + all_macs = ssh_get_supported_methods(SSH_MAC_S_C); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_HMAC_S_C, all_macs); + if (rc != 0) { + pkderr("ssh_bind_options_set MACs S-C: %s\n", ssh_get_error(b)); + goto outclose; + } + + } + + s = ssh_new(); + if (s == NULL) { + pkderr("ssh_new\n"); + goto outclose; + } + + rc = ssh_options_set(s, SSH_OPTIONS_REKEY_DATA, &rekey_data_limit); + if (rc != 0) { + pkderr("ssh_options_set rekey data: %s\n", ssh_get_error(s)); + goto outclose; + } + + /* + * ssh_bind_accept loads host key as side-effect. If this + * succeeds, the given 'fd' will be closed upon 'ssh_free(s)'. + */ + rc = ssh_bind_accept_fd(b, s, fd); + if (rc != SSH_OK) { + pkderr("ssh_bind_accept_fd: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* accept only publickey-based auth */ + ssh_set_auth_methods(s, SSH_AUTH_METHOD_PUBLICKEY); + + /* initialize callbacks */ + ssh_callbacks_init(&pkd_server_cb); + pkd_server_cb.userdata = &c; + rc = ssh_set_server_callbacks(s, &pkd_server_cb); + if (rc != SSH_OK) { + pkderr("ssh_set_server_callbacks: %s\n", ssh_get_error(s)); + goto out; + } + + /* first do key exchange */ + rc = ssh_handle_key_exchange(s); + if (rc != SSH_OK) { + pkderr("ssh_handle_key_exchange: %s\n", ssh_get_error(s)); + goto out; + } + + /* setup and pump event to carry out exec channel */ + e = ssh_event_new(); + if (e == NULL) { + pkderr("ssh_event_new\n"); + goto out; + } + + rc = ssh_event_add_session(e, s); + if (rc != SSH_OK) { + pkderr("ssh_event_add_session\n"); + goto out; + } + + /* poll until exec channel established */ + while ((ctx.keep_going != 0) && + (rc != SSH_ERROR) && (pkd_state.req_exec_received == 0)) { + rc = ssh_event_dopoll(e, -1 /* infinite timeout */); + } + + if (rc == SSH_ERROR) { + pkderr("ssh_event_dopoll\n"); + goto out; + } else if (c == NULL) { + pkderr("poll loop exited but exec channel not ready\n"); + rc = -1; + goto out; + } + + rc = ssh_channel_write(c, args->payload.buf, args->payload.len); + if (rc != (int)args->payload.len) { + pkderr("ssh_channel_write partial (%d != %zd)\n", rc, args->payload.len); + } + + rc = ssh_channel_request_send_exit_status(c, 0); + if (rc != SSH_OK) { + pkderr("ssh_channel_request_send_exit_status: %s\n", + ssh_get_error(s)); + goto out; + } + + rc = ssh_channel_send_eof(c); + if (rc != SSH_OK) { + pkderr("ssh_channel_send_eof: %s\n", ssh_get_error(s)); + goto out; + } + + rc = ssh_channel_close(c); + if (rc != SSH_OK) { + pkderr("ssh_channel_close: %s\n", ssh_get_error(s)); + goto out; + } + + while ((ctx.keep_going != 0) && + (pkd_state.eof_received == 0) && + (pkd_state.close_received == 0)) { + rc = ssh_event_dopoll(e, 1000 /* milliseconds */); + if (rc == SSH_ERROR) { + /* log, but don't consider this fatal */ + pkdout("ssh_event_dopoll for eof + close: %s\n", ssh_get_error(s)); + rc = 0; + break; + } else { + rc = 0; + } + } + + while ((ctx.keep_going != 0) && + (ssh_is_connected(s))) { + rc = ssh_event_dopoll(e, 1000 /* milliseconds */); + if (rc == SSH_ERROR) { + /* log, but don't consider this fatal */ + pkdout("ssh_event_dopoll for session connection: %s\n", ssh_get_error(s)); + rc = 0; + break; + } else { + rc = 0; + } + } + goto out; + +outclose: + close(fd); +out: + if (c != NULL) { + ssh_channel_free(c); + } + if (e != NULL) { + ssh_event_remove_session(e, s); + ssh_event_free(e); + } + if (s != NULL) { + ssh_disconnect(s); + ssh_free(s); + } + if (b != NULL) { + ssh_bind_free(b); + } + return rc; +} + +/* + * main loop ------------------------------------------------------------ + */ + +static void *pkd_main(void *args) { + int rc = -1; + struct pkd_daemon_args *a = (struct pkd_daemon_args *) args; + + struct sigaction act = { .sa_handler = pkd_sighandler, }; + + pkd_state.server_fd = -1; + pkd_state.req_exec_received = 0; + pkd_state.close_received = 0; + pkd_state.eof_received = 0; + + /* SIGUSR1 is used to interrupt 'pkd_accept_fd'. */ + rc = sigaction(SIGUSR1, &act, NULL); + if (rc != 0) { + pkderr("sigaction: %d\n", rc); + goto out; + } + + /* Ignore SIGPIPE */ + signal(SIGPIPE, SIG_IGN); + + rc = pkd_init_libssh(); + if (rc != 0) { + pkderr("pkd_init_libssh: %d\n", rc); + goto out; + } + + rc = pkd_init_server_fd(1234); + if (rc != 0) { + pkderr("pkd_init_server_fd: %d\n", rc); + goto out; + } + + ctx.pkd_ready = 1; + + while (ctx.keep_going != 0) { + int fd = pkd_accept_fd(); + if (fd < 0) { + if (ctx.keep_going != 0) { + pkderr("pkd_accept_fd"); + rc = -1; + } else { + rc = 0; + } + break; + } + + rc = pkd_exec_hello(fd, a); + if (rc != 0) { + pkderr("pkd_exec_hello: %d\n", rc); + break; + } + } + + if (pkd_state.server_fd != -1) { + close(pkd_state.server_fd); + } + pkd_state.server_fd = -1; +out: + ctx.rc = rc; + + return NULL; +} + +/* + * pkd start and stop used by setup/teardown test scaffolding ----------- + */ + +int pkd_start(struct pkd_daemon_args *args) { + int rc = 0; + + pkdout_enabled = args->opts.log_stdout; + pkderr_enabled = args->opts.log_stderr; + + /* Initialize the pkd context. */ + ctx.rc = -1; + ctx.keep_going = 1; + ctx.pkd_ready = 0; + rc = pthread_create(&ctx.tid, NULL, &pkd_main, args); + assert_int_equal(rc, 0); + + /* Busy-spin until pkd thread is ready. */ + while (ctx.pkd_ready == 0); + + return rc; +} + +void pkd_stop(struct pkd_result *out) { + int rc = 0; + + ctx.keep_going = 0; + close(pkd_state.server_fd); + + rc = pthread_kill(ctx.tid, SIGUSR1); + assert_int_not_equal(rc, EINVAL); + assert_int_not_equal(rc, ENOTSUP); + + rc = pthread_join(ctx.tid, NULL); + assert_int_equal(rc, 0); + + assert_non_null(out); + out->ok = (ctx.rc == 0); + + return; +} diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.h b/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.h new file mode 100644 index 000000000000..2745f11a1db9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.h @@ -0,0 +1,62 @@ +/* + * pkd_daemon.h -- tests use this interface to start, stop pkd + * instances and get results + * + * (c) 2014 Jon Simons + */ + +#ifndef __PKD_DAEMON_H__ +#define __PKD_DAEMON_H__ + +#include "config.h" + +enum pkd_hostkey_type_e { + PKD_RSA, + PKD_ED25519, + PKD_ECDSA +}; + +struct pkd_daemon_args { + enum pkd_hostkey_type_e type; + const char *hostkeypath; + + struct { + const uint8_t *buf; + size_t len; + } payload; + + uint64_t rekey_data_limit; + + int original_dir_fd; + + struct { + int list; + + int log_stdout; + int log_stderr; + int libssh_log_level; + + const char *testname; + const char *testmatch; + unsigned int iterations; + + struct { + const char *argv_mkdtemp_str; + char *mkdtemp_str; + } socket_wrapper; + + struct { + const char *argv_mkdtemp_str; + char *mkdtemp_str; + } temp_dir; + } opts; +}; + +struct pkd_result { + int ok; +}; + +int pkd_start(struct pkd_daemon_args *args); +void pkd_stop(struct pkd_result *out); + +#endif /* __PKD_DAEMON_H__ */ diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_hello.c b/src/libs/libssh-0.12.2/tests/pkd/pkd_hello.c new file mode 100644 index 000000000000..feedaf34eaff --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_hello.c @@ -0,0 +1,1161 @@ +/* + * pkd_hello.c -- + * + * (c) 2014, 2017-2018, 2022 Jon Simons + */ +#include "config.h" + +#include +#include // for cmocka +#include // for cmocka +#include // for cmocka +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "torture.h" // for ssh_fips_mode() + +#include "pkd_client.h" +#include "pkd_daemon.h" +#include "pkd_keyutil.h" +#include "pkd_util.h" + +#if defined(HAVE_LIBCRYPTO) +/* for OPENSSL_cleanup() of OpenSSL context */ +#include +#endif + +#define DEFAULT_ITERATIONS 10 +static struct pkd_daemon_args pkd_dargs; + +static uint8_t default_payload_buf[] = { + 'h', 'e', 'l', 'l', 'o', '\n', +}; + +static size_t default_payload_len = sizeof(default_payload_buf); + +#ifdef HAVE_ARGP_H +#include +#define PROGNAME "pkd_hello" +#define ARGP_PROGNAME "libssh " PROGNAME +const char *argp_program_version = ARGP_PROGNAME " 2022-11-12"; +const char *argp_program_bug_address = "Jon Simons "; + +static char doc[] = \ + "\nExample usage:\n\n" + " " PROGNAME "\n" + " Run all tests with default number of iterations.\n" + " " PROGNAME " --list\n" + " List available individual test names.\n" + " " PROGNAME " -i 1000 -t torture_pkd_rsa_ecdh_sha2_nistp256\n" + " Run only the torture_pkd_rsa_ecdh_sha2_nistp256 testcase 1000 times.\n" + " " PROGNAME " -i 1000 -m curve25519\n" + " Run all tests with the string 'curve25519' 1000 times.\n" + " " PROGNAME " -v -v -v -v -e -o\n" + " Run all tests with maximum libssh and pkd logging.\n" +; + +static struct argp_option options[] = { + { "buffer", 'b', "string", 0, + "Use the given string for test buffer payload contents", 0 }, + { "stderr", 'e', NULL, 0, + "Emit pkd stderr messages", 0 }, + { "list", 'l', NULL, 0, + "List available individual test names", 0 }, + { "iterations", 'i', "number", 0, + "Run each test for the given number of iterations (default is 10)", 0 }, + { "match", 'm', "testmatch", 0, + "Run all tests with the given string", 0 }, + { "temp-dir", 'L', "", 0, + "Run in a temporary directory using the given mkdtemp template", 0 }, + { "socket-wrapper-dir", 'w', "", 0, + "Run in socket-wrapper mode using the given mkdtemp directory template", 0 }, + { "stdout", 'o', NULL, 0, + "Emit pkd stdout messages", 0 }, + { "rekey", 'r', "limit", 0, + "Set the given rekey data limit, in bytes, using SSH_OPTIONS_REKEY_DATA", 0 }, + { "test", 't', "testname", 0, + "Run tests matching the given testname", 0 }, + { "verbose", 'v', NULL, 0, + "Increase libssh verbosity (can be used multiple times)", 0 }, + { NULL, 0, NULL, 0, + NULL, 0 }, +}; + +static error_t parse_opt(int key, char *arg, struct argp_state *state) { + (void) arg; + (void) state; + + switch(key) { + case 'b': + pkd_dargs.payload.buf = (uint8_t *) arg; + pkd_dargs.payload.len = strlen(arg); + break; + case 'e': + pkd_dargs.opts.log_stderr = 1; + break; + case 'l': + pkd_dargs.opts.list = 1; + break; + case 'L': + pkd_dargs.opts.temp_dir.argv_mkdtemp_str = arg; + break; + case 'i': + pkd_dargs.opts.iterations = atoi(arg); + break; + case 'm': + pkd_dargs.opts.testmatch = arg; + break; + case 'o': + pkd_dargs.opts.log_stdout = 1; + break; + case 'r': + pkd_dargs.rekey_data_limit = atoi(arg); + break; + case 't': + pkd_dargs.opts.testname = arg; + break; + case 'v': + pkd_dargs.opts.libssh_log_level += 1; + break; + case 'w': + pkd_dargs.opts.socket_wrapper.argv_mkdtemp_str = arg; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +static struct argp parser = { + options, + parse_opt, + NULL, + doc, + NULL, + NULL, + NULL +}; +#endif /* HAVE_ARGP_H */ + +static struct pkd_state *torture_pkd_setup(enum pkd_hostkey_type_e type, + const char *hostkeypath) { + int rc = 0; + + pkd_dargs.type = type; + pkd_dargs.hostkeypath = hostkeypath; + + rc = pkd_start(&pkd_dargs); + assert_int_equal(rc, 0); + + return NULL; +} + +static int torture_pkd_teardown(void **state) { + struct pkd_result result = { .ok = 0 }; + + (void) state; + + pkd_stop(&result); + assert_int_equal(result.ok, 1); + + return 0; +} + +/* + * one setup for each server keytype ------------------------------------ + */ + +static int torture_pkd_setup_noop(void **state) { + *state = (void *) torture_pkd_setup(PKD_RSA, NULL /*path*/); + + return 0; +} + +static int torture_pkd_setup_rsa(void **state) { + setup_rsa_key(); + *state = (void *) torture_pkd_setup(PKD_RSA, LIBSSH_RSA_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ed25519(void **state) { + setup_ed25519_key(); + *state = (void *) torture_pkd_setup(PKD_ED25519, LIBSSH_ED25519_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ecdsa_256(void **state) { + setup_ecdsa_keys(); + *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_256_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ecdsa_384(void **state) { + setup_ecdsa_keys(); + *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_384_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ecdsa_521(void **state) { + setup_ecdsa_keys(); + *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_521_TESTKEY); + + return 0; +} + +/* + * Test matrices: f(clientname, testname, ssh-command, setup-function, teardown-function). + */ + +#define PKDTESTS_DEFAULT_FIPS(f, client, cmd) \ + f(client, rsa_default, cmd, setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_default, cmd, setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_default, cmd, setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_default, cmd, setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define PKDTESTS_DEFAULT(f, client, cmd) \ + /* Default passes by server key type. */ \ + PKDTESTS_DEFAULT_FIPS(f, client, cmd) \ + f(client, ed25519_default, cmd, setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) + +#define GEX_SHA256 "diffie-hellman-group-exchange-sha256" +#define GEX_SHA1 "diffie-hellman-group-exchange-sha1" + +#if defined(WITH_GEX) +#define PKDTESTS_KEX_FIPS(f, client, kexcmd) \ + f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) +#else /* !defined(WITH_GEX) */ +#define PKDTESTS_KEX_FIPS(f, client, kexcmd) \ + f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) +#endif + +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM +#define SNTRUP_OPENSSH_NAME "sntrup761x25519-sha512@openssh.com" +#define PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) \ + f(client, rsa_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) +#endif + +#ifdef OPENSSH_SNTRUP761X25519_SHA512 +#define SNTRUP_NAME "sntrup761x25519-sha512" +#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \ + f(client, rsa_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) +#endif + +#if defined(OPENSSH_MLKEM768X25519_SHA256) +#define PKDTESTS_KEX_MLKEM768X25519(f, client, kexcmd) \ + f(client, rsa_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_MLKEM768X25519(f, client, kexcmd) +#endif + +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) +#define PKDTESTS_KEX_MLKEM768NISTP256(f, client, kexcmd) \ + f(client, rsa_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_MLKEM768NISTP256(f, client, kexcmd) +#endif + +#if defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) +#define PKDTESTS_KEX_MLKEM1024NISTP384(f, client, kexcmd) \ + f(client, rsa_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_MLKEM1024NISTP384(f, client, kexcmd) +#endif + +#define PKDTESTS_KEX_COMMON(f, client, kexcmd) \ + PKDTESTS_KEX_FIPS(f, client, kexcmd) \ + PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \ + PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) \ + PKDTESTS_KEX_MLKEM768X25519(f, client, kexcmd) \ + PKDTESTS_KEX_MLKEM768NISTP256(f, client, kexcmd) \ + PKDTESTS_KEX_MLKEM1024NISTP384(f, client, kexcmd) \ + f(client, rsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#if defined(WITH_GEX) + /* GEX_SHA256 is included in PKDTESTS_KEX_FIPS if available */ +#define PKDTESTS_KEX(f, client, kexcmd) \ + /* Kex algorithms. */ \ + PKDTESTS_KEX_COMMON(f, client, kexcmd) \ + f(client, rsa_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) +#else +#define PKDTESTS_KEX(f, client, kexcmd) \ + /* Kex algorithms. */ \ + f(client, ed25519_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#endif + +#define PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ + f(client, rsa_aes128_ctr, ciphercmd("aes128-ctr"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_ctr, ciphercmd("aes256-ctr"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define PKDTESTS_CIPHER_FIPS(f, client, ciphercmd) \ + PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ + f(client, rsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_cbc, ciphercmd("aes256-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define CHACHA20 "chacha20-poly1305@openssh.com" +#define PKDTESTS_CIPHER_CHACHA(f, client, ciphercmd) \ + f(client, rsa_chacha20, ciphercmd(CHACHA20), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ed25519_chacha20, ciphercmd(CHACHA20), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ecdsa_256_chacha20, ciphercmd(CHACHA20), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_chacha20, ciphercmd(CHACHA20), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_chacha20, ciphercmd(CHACHA20), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define PKDTESTS_CIPHER(f, client, ciphercmd) \ + /* Ciphers. */ \ + PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ + PKDTESTS_CIPHER_CHACHA(f, client, ciphercmd) \ + f(client, ed25519_aes128_ctr, ciphercmd("aes128-ctr"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes256_ctr, ciphercmd("aes256-ctr"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) + +#define AES128_GCM "aes128-gcm@openssh.com" +#define AES256_GCM "aes256-gcm@openssh.com" + +#define PKDTESTS_CIPHER_OPENSSHONLY_FIPS(f, client, ciphercmd) \ + f(client, rsa_aes128_gcm, ciphercmd(AES128_GCM), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_gcm, ciphercmd(AES256_GCM), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define PKDTESTS_CIPHER_OPENSSHONLY(f, client, ciphercmd) \ + /* Ciphers. */ \ + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(f, client, ciphercmd) \ + f(client, rsa_3des_cbc, ciphercmd("3des-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes192_cbc, ciphercmd("aes192-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_cbc, ciphercmd("aes256-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes192_ctr, ciphercmd("aes192-ctr"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ed25519_3des_cbc, ciphercmd("3des-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes128_cbc, ciphercmd("aes128-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes256_cbc, ciphercmd("aes256-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes192_cbc, ciphercmd("aes192-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes192_ctr, ciphercmd("aes192-ctr"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes128_gcm, ciphercmd(AES128_GCM), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes256_gcm, ciphercmd(AES256_GCM), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ecdsa_256_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + + +#define PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#define PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_hmac_sha1, maccmd("hmac-sha1"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#ifdef DROPBEAR_SUPPORTS_HMAC_SHA1 +#define PKDTESTS_MAC_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) \ + f(client, ed25519_hmac_sha1, maccmd("hmac-sha1"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#define PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) +#else +#define PKDTESTS_MAC_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) +#define PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) +#endif + +#define PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#define PKDTESTS_MAC(f, client, maccmd) \ + /* MACs. */ \ + PKDTESTS_MAC_FIPS(f, client, maccmd) \ + f(client, ed25519_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) + +#define PKDTESTS_MAC_OPENSSHONLY(f, client, maccmd) \ + PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ + f(client, ed25519_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) + + +#define PKDTESTS_HOSTKEY_OPENSSHONLY_FIPS(f, client, hkcmd) \ + f(client, rsa_sha2_256, hkcmd("rsa-sha2-256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_sha2_512, hkcmd("rsa-sha2-512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_sha2_256_512, hkcmd("rsa-sha2-256,rsa-sha2-512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_sha2_512_256, hkcmd("rsa-sha2-512,rsa-sha2-256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#define PKDTESTS_HOSTKEY_OPENSSHONLY(f, client, hkcmd) \ + PKDTESTS_HOSTKEY_OPENSSHONLY_FIPS(f, client, hkcmd) + +static void torture_pkd_client_noop(void **state) { + struct pkd_state *pstate = (struct pkd_state *) (*state); + (void) pstate; + return; +} + +static void torture_pkd_runtest(const char *testname, + const char *testcmd) +{ + int i, rc; + char logfile[1024] = { 0 }; + int iterations = + (pkd_dargs.opts.iterations != 0) ? pkd_dargs.opts.iterations + : DEFAULT_ITERATIONS; + + for (i = 0; i < iterations; i++) { + rc = system_checked(testcmd); + assert_int_equal(rc, 0); + } + + /* Asserts did not trip: cleanup logs. */ + snprintf(&logfile[0], sizeof(logfile), "%s.out", testname); + unlink(logfile); + snprintf(&logfile[0], sizeof(logfile), "%s.err", testname); + unlink(logfile); +} + +/* + * Though each keytest function body is the same, separate functions are + * defined here to result in distinct output when running the tests. + */ + +#define emit_keytest(client, testname, sshcmd, setup, teardown, hostkey) \ + static void torture_pkd_## client ## _ ## testname(void **state) { \ + const char *tname = "torture_pkd_" #client "_" #testname; \ + char testcmd[2048] = { 0 }; \ + (void) state; \ + snprintf(&testcmd[0], sizeof(testcmd), sshcmd, hostkey, tname, tname); \ + torture_pkd_runtest(tname, testcmd); \ + } + +/* + * Actual test functions are emitted here. + */ +#define CLIENT_ID_FILE OPENSSH_RSA_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_rsa, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_rsa, OPENSSH_CERT_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_sha256_cert_rsa, OPENSSH_SHA256_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_rsa, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_rsa, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_MAC_CMD) +PKDTESTS_HOSTKEY_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_HOSTKEY_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE OPENSSH_ECDSA256_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_e256, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_e256, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_e256, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_e256, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE + +/* Could add these passes, too: */ +//#define CLIENT_ID_FILE OPENSSH_ECDSA384_TESTKEY +//#define CLIENT_ID_FILE OPENSSH_ECDSA521_TESTKEY + +#define CLIENT_ID_FILE OPENSSH_ED25519_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_ed, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ed, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_ed, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_ed, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE + +#ifdef HAVE_SK_DUMMY +#define CLIENT_ID_FILE OPENSSH_ECDSA_SK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_ec_sk, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ec_sk, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_ec_sk, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_ec_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ec_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_ec_sk, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ec_sk, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE OPENSSH_ED25519_SK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_ed_sk, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ed_sk, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_ed_sk, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_ed_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ed_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_ed_sk, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ed_sk, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE +#endif /* HAVE_SK_DUMMY */ + +#define CLIENT_ID_FILE DROPBEAR_RSA_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, dropbear_rsa, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear_rsa, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear_rsa, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE DROPBEAR_ECDSA256_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, dropbear_e256, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear_e256, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear_e256, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE DROPBEAR_ED25519_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, dropbear_ed, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear_ed, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear_ed, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE PUTTY_RSA_PPK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, putty_rsa, PUTTY_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE PUTTY_ED25519_PPK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, putty_ed, PUTTY_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE PUTTY_ECDSA256_PPK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, putty_e256, PUTTY_CMD) +#undef CLIENT_ID_FILE + +/* + * Define an array of testname strings mapped to their associated + * test function. Enables running tests individually by name from + * the command line. + */ + +#define emit_testmap(client, testname, sshcmd, setup, teardown, ...) \ + { "torture_pkd_" #client "_" #testname, \ + emit_unit_test(client, testname, sshcmd, setup, teardown, ##__VA_ARGS__) }, + +#define emit_unit_test(client, testname, sshcmd, setup, teardown, ...) \ + cmocka_unit_test_setup_teardown(torture_pkd_ ## client ## _ ## testname, \ + torture_pkd_ ## setup, \ + torture_pkd_ ## teardown) + +#define emit_unit_test_comma(client, testname, sshcmd, setup, teardown, ...) \ + emit_unit_test(client, testname, sshcmd, setup, teardown, ##__VA_ARGS__), + +struct { + const char *testname; + const struct CMUnitTest test; +} testmap[] = { + /* OpenSSH */ + + PKDTESTS_DEFAULT(emit_testmap, openssh_rsa, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_rsa, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_sha256_cert_rsa, OPENSSH_SHA256_CERT_CMD) + PKDTESTS_KEX(emit_testmap, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_HOSTKEY_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_HOSTKEY_CMD) + + PKDTESTS_DEFAULT(emit_testmap, openssh_e256, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_e256, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_testmap, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_e256, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_testmap, openssh_ed, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_ed, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_testmap, openssh_ed, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_ed, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_MAC_CMD) + + /* Dropbear */ + PKDTESTS_DEFAULT(emit_testmap, dropbear_rsa, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear_rsa, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear_rsa, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_testmap, dropbear_e256, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear_e256, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear_e256, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_testmap, dropbear_ed, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear_ed, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear_ed, DROPBEAR_MAC_CMD) + + /* PuTTY */ + PKDTESTS_DEFAULT(emit_testmap, putty_rsa, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_testmap, putty_e256, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_testmap, putty_ed, PUTTY_CMD) + + /* Noop */ + emit_testmap(client, noop, "", setup_noop, teardown, NULL) + + /* NULL tail entry */ + { .testname = NULL, + .test = { .name = NULL, + .test_func = NULL, + .setup_func = NULL, + .teardown_func = NULL } } +}; + +static int pkd_run_tests(void) { + int rc = -1; + int tindex = 0; + + const struct CMUnitTest openssh_tests[] = { + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_rsa, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_rsa, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_sha256_cert_rsa, + OPENSSH_SHA256_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_e256, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_e256, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ed, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ed, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_ed, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD) + +#ifdef HAVE_SK_DUMMY + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ec_sk, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ec_sk, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_ec_sk, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ec_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ec_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_ec_sk, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ec_sk, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ed_sk, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ed_sk, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_ed_sk, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ed_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ed_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_ed_sk, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ed_sk, OPENSSH_MAC_CMD) +#endif /* HAVE_SK_DUMMY */ + }; + + /* It is not possible to test hostkey and kex algorithms, because + * dbclient does not support setting hostkey and kex algorithms + * through cli (see 'man dbclient') + */ + const struct CMUnitTest dropbear_tests[] = { + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear_rsa, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear_rsa, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear_rsa, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear_e256, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear_e256, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear_e256, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear_ed, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear_ed, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear_ed, DROPBEAR_MAC_CMD) + }; + + const struct CMUnitTest putty_tests[] = { + PKDTESTS_DEFAULT(emit_unit_test_comma, putty_rsa, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, putty_e256, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, putty_ed, PUTTY_CMD) + }; + + const struct CMUnitTest openssh_fips_tests[] = { + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_CMD) + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_sha256_cert_rsa, + OPENSSH_SHA256_CERT_CMD) + PKDTESTS_KEX_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_CMD) + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_cert_e256, OPENSSH_CERT_CMD) + PKDTESTS_KEX_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + }; + + const struct CMUnitTest noop_tests[] = { + emit_unit_test(client, noop, "", setup_noop, teardown, NULL) + }; + + /* Test list is populated depending on which clients are enabled. */ + struct CMUnitTest all_tests[(sizeof(openssh_tests) / sizeof(openssh_tests[0])) + + (sizeof(dropbear_tests) / sizeof(dropbear_tests[0])) + + (sizeof(putty_tests) / sizeof(putty_tests[0])) + + (sizeof(noop_tests) / sizeof(noop_tests[0]))]; + memset(&all_tests[0], 0x0, sizeof(all_tests)); + + /* Generate client keys and populate test list for each enabled client. */ + if (is_openssh_client_enabled()) { + setup_openssh_client_keys(); + if (ssh_fips_mode()) { + memcpy(&all_tests[tindex], &openssh_fips_tests[0], sizeof(openssh_fips_tests)); + tindex += (sizeof(openssh_fips_tests) / sizeof(openssh_fips_tests[0])); + } else { + memcpy(&all_tests[tindex], &openssh_tests[0], sizeof(openssh_tests)); + tindex += (sizeof(openssh_tests) / sizeof(openssh_tests[0])); + } + } + + if (is_dropbear_client_enabled()) { + setup_dropbear_client_keys(); + if (!ssh_fips_mode()) { + memcpy(&all_tests[tindex], &dropbear_tests[0], sizeof(dropbear_tests)); + tindex += (sizeof(dropbear_tests) / sizeof(dropbear_tests[0])); + } + } + + if (is_putty_client_enabled()) { + setup_putty_client_keys(); + if (!ssh_fips_mode()) { + memcpy(&all_tests[tindex], &putty_tests[0], sizeof(putty_tests)); + tindex += (sizeof(putty_tests) / sizeof(putty_tests[0])); + } + } + + memcpy(&all_tests[tindex], &noop_tests[0], sizeof(noop_tests)); + tindex += (sizeof(noop_tests) / sizeof(noop_tests[0])); + + if ((pkd_dargs.opts.testname == NULL) && + (pkd_dargs.opts.testmatch == NULL)) { + rc = _cmocka_run_group_tests("all tests", all_tests, tindex, NULL, NULL); + } else { + size_t i = 0; + size_t num_found = 0; + const char *testname = pkd_dargs.opts.testname; + const char *testmatch = pkd_dargs.opts.testmatch; + + struct CMUnitTest matching_tests[sizeof(all_tests)]; + memset(&matching_tests[0], 0x0, sizeof(matching_tests)); + + while (testmap[i].testname != NULL) { + if ((testname != NULL) && + (strcmp(testmap[i].testname, testname) == 0)) { + memcpy(&matching_tests[0], + &testmap[i].test, + sizeof(struct CMUnitTest)); + num_found += 1; + break; + } + + if ((testmatch != NULL) && + (strstr(testmap[i].testname, testmatch) != NULL)) { + memcpy(&matching_tests[num_found], + &testmap[i].test, + sizeof(struct CMUnitTest)); + num_found += 1; + } + + i += 1; + } + + if (num_found > 0) { + rc = _cmocka_run_group_tests("found", matching_tests, num_found, NULL, NULL); + } else { + fprintf(stderr, "Did not find test '%s'\n", testname); + } + } + + /* Clean up client keys for each enabled client. */ + if (is_dropbear_client_enabled()) { + cleanup_dropbear_client_keys(); + } + + if (is_openssh_client_enabled()) { + cleanup_openssh_client_keys(); + } + + if (is_putty_client_enabled()) { + cleanup_putty_client_keys(); + } + + /* Clean up any server keys that were generated. */ + cleanup_rsa_key(); + cleanup_ecdsa_keys(); + if (!ssh_fips_mode()) { + cleanup_ed25519_key(); + } + + return rc; +} + +static int pkd_init_temp_dir(void) { + int rc = 0; + char *mkdtemp_str = NULL; + pkd_dargs.original_dir_fd = -1; + + if (pkd_dargs.opts.temp_dir.argv_mkdtemp_str == NULL) { + return 0; + } + + pkd_dargs.original_dir_fd = open(".", O_RDONLY); + if (pkd_dargs.original_dir_fd < 0) { + fprintf(stderr, "pkd_init_temp_dir open failed\n"); + return -1; + } + + mkdtemp_str = strdup(pkd_dargs.opts.temp_dir.argv_mkdtemp_str); + if (mkdtemp_str == NULL) { + fprintf(stderr, "pkd_init_temp_dir strdup failed\n"); + goto errstrdup; + } + pkd_dargs.opts.temp_dir.mkdtemp_str = mkdtemp_str; + + if (mkdtemp(mkdtemp_str) == NULL) { + fprintf(stderr, "pkd_init_temp_dir mkdtemp '%s' failed\n", mkdtemp_str); + goto errmkdtemp; + } + + rc = chdir(mkdtemp_str); + if (rc != 0) { + fprintf(stderr, "pkd_init_temp_dir chdir '%s' failed\n", mkdtemp_str); + goto errchdir; + } + + return 0; + +errchdir: + rmdir(mkdtemp_str); +errmkdtemp: + free(mkdtemp_str); +errstrdup: + close(pkd_dargs.original_dir_fd); + pkd_dargs.original_dir_fd = -1; + rc = -1; + return rc; +} + +static int pkd_init_socket_wrapper(void) { + int rc = 0; + char *mkdtemp_str = NULL; + + if (pkd_dargs.opts.socket_wrapper.argv_mkdtemp_str == NULL) { + goto out; + } + + mkdtemp_str = strdup(pkd_dargs.opts.socket_wrapper.argv_mkdtemp_str); + if (mkdtemp_str == NULL) { + fprintf(stderr, "pkd_init_socket_wrapper strdup failed\n"); + goto errstrdup; + } + pkd_dargs.opts.socket_wrapper.mkdtemp_str = mkdtemp_str; + + if (mkdtemp(mkdtemp_str) == NULL) { + fprintf(stderr, "pkd_init_socket_wrapper mkdtemp '%s' failed\n", mkdtemp_str); + goto errmkdtemp; + } + + if (setenv("SOCKET_WRAPPER_DIR", mkdtemp_str, 1) != 0) { + fprintf(stderr, "pkd_init_socket_wrapper setenv failed\n"); + goto errsetenv; + } + + goto out; +errsetenv: +errmkdtemp: + free(mkdtemp_str); +errstrdup: + rc = -1; +out: + return rc; +} + +static int pkd_rmfiles(const char *path) { + char bin[1024] = { 0 }; + snprintf(&bin[0], sizeof(bin), "rm -f %s/*", path); + return system_checked(bin); +} + +static int pkd_cleanup_temp_dir(void) { + int rc = 0; + + if (pkd_dargs.opts.temp_dir.mkdtemp_str == NULL) { + return 0; + } + + if (fchdir(pkd_dargs.original_dir_fd) != 0) { + fprintf(stderr, "pkd_cleanup_temp_dir failed fchdir\n"); + rc = -1; + goto out; + } + + if (rmdir(pkd_dargs.opts.temp_dir.mkdtemp_str) != 0) { + fprintf(stderr, "pkd_cleanup_temp_dir rmdir '%s' failed\n", + pkd_dargs.opts.temp_dir.mkdtemp_str); + rc = -1; + goto out; + } + +out: + close(pkd_dargs.original_dir_fd); + pkd_dargs.original_dir_fd = -1; + free(pkd_dargs.opts.temp_dir.mkdtemp_str); + return rc; +} + +static int pkd_cleanup_socket_wrapper(void) { + int rc = 0; + + if (pkd_dargs.opts.socket_wrapper.mkdtemp_str == NULL) { + goto out; + } + + /* clean up socket-wrapper unix domain sockets */ + if (pkd_rmfiles(pkd_dargs.opts.socket_wrapper.mkdtemp_str) != 0) { + fprintf(stderr, "pkd_cleanup_socket_wrapper pkd_rmfiles '%s' failed\n", + pkd_dargs.opts.socket_wrapper.mkdtemp_str); + goto errrmfiles; + } + + if (rmdir(pkd_dargs.opts.socket_wrapper.mkdtemp_str) != 0) { + fprintf(stderr, "pkd_cleanup_socket_wrapper rmdir '%s' failed\n", + pkd_dargs.opts.socket_wrapper.mkdtemp_str); + goto errrmdir; + } + + goto outfree; +errrmdir: +errrmfiles: + rc = -1; +outfree: + free(pkd_dargs.opts.socket_wrapper.mkdtemp_str); +out: + return rc; +} + +int main(int argc, char **argv) { + int i = 0; + int rc = 0; + int exit_code = -1; + + unsetenv("SSH_AUTH_SOCK"); + + pkd_dargs.payload.buf = default_payload_buf; + pkd_dargs.payload.len = default_payload_len; + + rc = ssh_init(); + if (rc != 0) { + goto out; + } + +#ifdef HAVE_ARGP_H + argp_parse(&parser, argc, argv, 0, 0, NULL); +#else /* HAVE_ARGP_H */ + (void) argc; (void) argv; +#endif /* HAVE_ARGP_H */ + + rc = pkd_init_temp_dir(); + if (rc != 0) { + fprintf(stderr, "pkd_init_temp_dir failed: %d\n", rc); + goto out_finalize; + } + + rc = pkd_init_socket_wrapper(); + if (rc != 0) { + fprintf(stderr, "pkd_init_socket_wrapper failed: %d\n", rc); + goto out_tempdir; + } + + if (pkd_dargs.opts.list != 0) { + while (testmap[i].testname != NULL) { + printf("%s\n", testmap[i++].testname); + } + } else { + exit_code = pkd_run_tests(); + if (exit_code != 0) { + fprintf(stderr, "pkd_run_tests failed: %d\n", exit_code); + } + } + + rc = pkd_cleanup_socket_wrapper(); + if (rc != 0) { + fprintf(stderr, "pkd_cleanup_socket_wrapper failed: %d\n", rc); + } + +out_tempdir: + rc = pkd_cleanup_temp_dir(); + if (rc != 0) { + fprintf(stderr, "pkd_cleanup_temp_dir failed: %d\n", rc); + } + +out_finalize: + rc = ssh_finalize(); + if (rc != 0) { + fprintf(stderr, "ssh_finalize: %d\n", rc); + } +#if defined(HAVE_LIBCRYPTO) + OPENSSL_cleanup(); +#endif +out: + return exit_code; +} \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.c b/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.c new file mode 100644 index 000000000000..34e071e24e12 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.c @@ -0,0 +1,269 @@ +/* + * pkd_keyutil.c -- pkd test key utilities + * + * (c) 2014 Jon Simons + */ + +#include "config.h" + +#include // for cmocka +#include // for cmocka +#include // for cmocka +#include // for cmocka +#include + +#include +#include +#include +#include + +#include "torture.h" // for ssh_fips_mode() + +#include "pkd_client.h" +#include "pkd_keyutil.h" +#include "pkd_util.h" + +void setup_rsa_key(void) { + int rc = 0; + if (access(LIBSSH_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + LIBSSH_RSA_TESTKEY); + } + assert_int_equal(rc, 0); +} + +void setup_ed25519_key(void) { + int rc = 0; + if (access(LIBSSH_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " + LIBSSH_ED25519_TESTKEY); + } + assert_int_equal(rc, 0); +} + +void setup_ecdsa_keys(void) { + int rc = 0; + + if (access(LIBSSH_ECDSA_256_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f " + LIBSSH_ECDSA_256_TESTKEY); + assert_int_equal(rc, 0); + } + if (access(LIBSSH_ECDSA_384_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 384 -q -N \"\" -f " + LIBSSH_ECDSA_384_TESTKEY); + assert_int_equal(rc, 0); + } + if (access(LIBSSH_ECDSA_521_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 521 -q -N \"\" -f " + LIBSSH_ECDSA_521_TESTKEY); + assert_int_equal(rc, 0); + } +} + +void cleanup_rsa_key(void) { + cleanup_key(LIBSSH_RSA_TESTKEY); +} + +void cleanup_ed25519_key(void) { + cleanup_key(LIBSSH_ED25519_TESTKEY); +} + +void cleanup_ecdsa_keys(void) { + cleanup_key(LIBSSH_ECDSA_256_TESTKEY); + cleanup_key(LIBSSH_ECDSA_384_TESTKEY); + cleanup_key(LIBSSH_ECDSA_521_TESTKEY); +} + +void setup_openssh_client_keys(void) { + int rc = 0; + + if (access(OPENSSH_CA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + OPENSSH_CA_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + OPENSSH_RSA_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_RSA_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_RSA_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_RSA_TESTKEY "-sha256-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -t rsa-sha2-256 " + "-s " OPENSSH_CA_TESTKEY " " + OPENSSH_RSA_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA256_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f " + OPENSSH_ECDSA256_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA256_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ECDSA256_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA384_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 384 -q -N \"\" -f " + OPENSSH_ECDSA384_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA384_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ECDSA384_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA521_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 521 -q -N \"\" -f " + OPENSSH_ECDSA521_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA521_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ECDSA521_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (!ssh_fips_mode()) { + + if (access(OPENSSH_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " + OPENSSH_ED25519_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ED25519_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ED25519_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + } + +#ifdef HAVE_SK_DUMMY + setenv("SSH_SK_PROVIDER", SK_DUMMY_LIBRARY_PATH, 1); + if (access(OPENSSH_ECDSA_SK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa-sk -q -N \"\" -f " + OPENSSH_ECDSA_SK_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ED25519_SK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519-sk -q -N \"\" -f " + OPENSSH_ED25519_SK_TESTKEY); + } + assert_int_equal(rc, 0); +#endif +} + +void cleanup_openssh_client_keys(void) { + cleanup_key(OPENSSH_CA_TESTKEY); + cleanup_key(OPENSSH_RSA_TESTKEY); + cleanup_file(OPENSSH_RSA_TESTKEY "-sha256-cert.pub"); + cleanup_key(OPENSSH_ECDSA256_TESTKEY); + cleanup_key(OPENSSH_ECDSA384_TESTKEY); + cleanup_key(OPENSSH_ECDSA521_TESTKEY); + if (!ssh_fips_mode()) { + cleanup_key(OPENSSH_ED25519_TESTKEY); + } +#ifdef HAVE_SK_DUMMY + cleanup_key(OPENSSH_ECDSA_SK_TESTKEY); + cleanup_key(OPENSSH_ED25519_SK_TESTKEY); +#endif +} + +void setup_dropbear_client_keys(void) +{ + int rc = 0; + if (access(DROPBEAR_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t rsa -f " + DROPBEAR_RSA_TESTKEY " 1>/dev/null 2>/dev/null"); + } + assert_int_equal(rc, 0); + if (access(DROPBEAR_ECDSA256_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t ecdsa -f " + DROPBEAR_ECDSA256_TESTKEY + " 1>/dev/null 2>/dev/null"); + } + assert_int_equal(rc, 0); + if (access(DROPBEAR_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t ed25519 -f " + DROPBEAR_ED25519_TESTKEY + " 1>/dev/null 2>/dev/null"); + } + assert_int_equal(rc, 0); +} + +void cleanup_dropbear_client_keys(void) +{ + cleanup_key(DROPBEAR_RSA_TESTKEY); + cleanup_key(DROPBEAR_ECDSA256_TESTKEY); + cleanup_key(DROPBEAR_ED25519_TESTKEY); +} + +void setup_putty_client_keys(void) +{ + int rc = 0; + + /* RSA Keys */ + if (access(PUTTY_RSA_TESTKEY, F_OK) != 0 || + access(PUTTY_RSA_PPK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + PUTTY_RSA_TESTKEY); + assert_int_equal(rc, 0); + + rc = system_checked(PUTTY_KEYGEN " " PUTTY_RSA_TESTKEY + " -O private -o " PUTTY_RSA_PPK_TESTKEY); + assert_int_equal(rc, 0); + } + + /* ECDSA 256 Keys */ + if (access(PUTTY_ECDSA256_TESTKEY, F_OK) != 0 || + access(PUTTY_ECDSA256_PPK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f " + PUTTY_ECDSA256_TESTKEY); + assert_int_equal(rc, 0); + + rc = system_checked(PUTTY_KEYGEN " " PUTTY_ECDSA256_TESTKEY + " -O private -o " PUTTY_ECDSA256_PPK_TESTKEY); + assert_int_equal(rc, 0); + } + + /* ED25519 Keys */ + if (access(PUTTY_ED25519_TESTKEY, F_OK) != 0 || + access(PUTTY_ED25519_PPK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " + PUTTY_ED25519_TESTKEY); + assert_int_equal(rc, 0); + + rc = system_checked(PUTTY_KEYGEN " " PUTTY_ED25519_TESTKEY + " -O private -o " PUTTY_ED25519_PPK_TESTKEY); + assert_int_equal(rc, 0); + } +} + +void cleanup_putty_client_keys(void) +{ + cleanup_key(PUTTY_RSA_TESTKEY); + cleanup_file(PUTTY_RSA_PPK_TESTKEY); + + cleanup_key(PUTTY_ECDSA256_TESTKEY); + cleanup_file(PUTTY_ECDSA256_PPK_TESTKEY); + + cleanup_key(PUTTY_ED25519_TESTKEY); + cleanup_file(PUTTY_ED25519_PPK_TESTKEY); +} \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.h b/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.h new file mode 100644 index 000000000000..86357c448b1f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.h @@ -0,0 +1,67 @@ +/* + * pkd_keyutil.h -- + * + * (c) 2014 Jon Simons + */ + +#ifndef __PKD_KEYUTIL_H__ +#define __PKD_KEYUTIL_H__ + +#include "config.h" + +/* Server keys. */ +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#define LIBSSH_ECDSA_256_TESTKEY "libssh_testkey.id_ecdsa256" +#define LIBSSH_ECDSA_384_TESTKEY "libssh_testkey.id_ecdsa384" +#define LIBSSH_ECDSA_521_TESTKEY "libssh_testkey.id_ecdsa521" + +void setup_rsa_key(void); +void setup_ed25519_key(void); +void setup_ecdsa_keys(void); +void cleanup_rsa_key(void); +void cleanup_ed25519_key(void); +void cleanup_ecdsa_keys(void); + +/* Client keys. */ +#define OPENSSH_RSA_TESTKEY "openssh_testkey.id_rsa" +#define OPENSSH_ECDSA256_TESTKEY "openssh_testkey.id_ecdsa256" +#define OPENSSH_ECDSA384_TESTKEY "openssh_testkey.id_ecdsa384" +#define OPENSSH_ECDSA521_TESTKEY "openssh_testkey.id_ecdsa521" +#define OPENSSH_ED25519_TESTKEY "openssh_testkey.id_ed25519" +#define OPENSSH_CA_TESTKEY "libssh_testkey.ca" +#define OPENSSH_ECDSA_SK_TESTKEY "openssh_testkey.id_ecdsa-sk" +#define OPENSSH_ED25519_SK_TESTKEY "openssh_testkey.id_ed25519-sk" + +#define DROPBEAR_RSA_TESTKEY "dropbear_testkey.id_rsa" +#define DROPBEAR_ECDSA256_TESTKEY "dropbear_testkey.id_ecdsa256" +#define DROPBEAR_ED25519_TESTKEY "dropbear_testkey.id_ed25519" + +#define PUTTY_RSA_TESTKEY "putty_testkey.id_rsa" +#define PUTTY_RSA_PPK_TESTKEY "putty_testkey.id_rsa.ppk" +#define PUTTY_ECDSA256_TESTKEY "putty_testkey.id_ecdsa256" +#define PUTTY_ECDSA256_PPK_TESTKEY "putty_testkey.id_ecdsa256.ppk" +#define PUTTY_ED25519_TESTKEY "putty_testkey.id_ed25519" +#define PUTTY_ED25519_PPK_TESTKEY "putty_testkey.id_ed25519.ppk" + +void setup_openssh_client_keys(void); +void cleanup_openssh_client_keys(void); + +void setup_dropbear_client_keys(void); +void cleanup_dropbear_client_keys(void); + +void setup_putty_client_keys(void); +void cleanup_putty_client_keys(void); + +#define cleanup_file(name) do {\ + if (access((name), F_OK) != -1) {\ + unlink((name));\ + }} while (0) + +#define cleanup_key(name) do {\ + cleanup_file((name));\ + cleanup_file((name ".pub"));\ + cleanup_file((name "-cert.pub"));\ + } while (0) + +#endif /* __PKD_KEYUTIL_H__ */ diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_util.c b/src/libs/libssh-0.12.2/tests/pkd/pkd_util.c new file mode 100644 index 000000000000..e4866bd5d7f3 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_util.c @@ -0,0 +1,121 @@ +/* + * pkd_util.c -- pkd utilities + * + * (c) 2014, 2018 Jon Simons + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "pkd_client.h" +#include "pkd_util.h" + +/** + * @brief runs system(3); exits if that is interrupted with SIGINT/QUIT + * @returns 0 upon success, non-zero otherwise + */ +int system_checked(const char *cmd) { + int rc = system(cmd); + + if (WIFSIGNALED(rc) && + ((WTERMSIG(rc) == SIGINT) || (WTERMSIG(rc) == SIGQUIT))) { + exit(1); + } + + if (rc == -1) { + return -1; + } + + return WEXITSTATUS(rc); +} + +static int bin_exists(const char *binary) { + char bin[1024] = { 0 }; + snprintf(&bin[0], sizeof(bin), "type %s 1>/dev/null 2>/dev/null", binary); + return (system_checked(bin) == 0); +} + +static int is_openssh_client_new_enough(void) { + int rc = -1; + FILE *fp = NULL; + char version_buff[1024] = { 0 }; + char *version; + + static int version_ok = 0; + unsigned long int major = 0; + char *tmp = NULL; + + if (version_ok) { + return version_ok; + } + + fp = popen("ssh -V 2>&1", "r"); + if (fp == NULL) { + fprintf(stderr, "failed to get OpenSSH client version\n"); + goto done; + } + + do { + if (fgets(&version_buff[0], sizeof(version_buff), fp) == NULL) { + fprintf(stderr, "failed to get OpenSSH client version string\n"); + goto errfgets; + } + version = strstr(version_buff, "OpenSSH"); + } while(version == NULL); + + /* "OpenSSH_...." */ + if (strlen(version) < 11) { + goto errversion; + } + + /* Extract major. */ + major = strtoul(version + 8, &tmp, 10); + if ((tmp == (version + 8)) || + ((errno == ERANGE) && (major == ULONG_MAX)) || + ((errno != 0) && (major == 0)) || + ((major < 1) || (major > 100))) { + fprintf(stderr, "failed to parse OpenSSH client version, " + "errno %d\n", errno); + errno = 0; + goto errversion; + } + + if (major < 7) { + fprintf(stderr, "error: minimum OpenSSH client version " + "required is 7, found: %ld\n", major); + goto errversion; + } + + version_ok = 1; + +errversion: +errfgets: + rc = pclose(fp); + if (rc != 0) { + fprintf(stderr, "failed to get OpenSSH client version: %d\n", rc); + } +done: + return version_ok; +} + +int is_openssh_client_enabled(void) { + return (bin_exists(OPENSSH_BINARY) && + bin_exists(OPENSSH_KEYGEN) && + is_openssh_client_new_enough()); +} + +int is_dropbear_client_enabled(void) { + return (bin_exists(DROPBEAR_BINARY) && bin_exists(DROPBEAR_KEYGEN)); +} + +int is_putty_client_enabled(void) +{ + return (bin_exists(PUTTY_BINARY) && + bin_exists(PUTTY_KEYGEN) && + bin_exists(OPENSSH_KEYGEN)); +} diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_util.h b/src/libs/libssh-0.12.2/tests/pkd/pkd_util.h new file mode 100644 index 000000000000..8c4a637d43f1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_util.h @@ -0,0 +1,17 @@ +/* + * pkd_keyutil.h -- + * + * (c) 2014 Jon Simons + */ + +#ifndef __PKD_UTIL_H__ +#define __PKD_UTIL_H__ + +int system_checked(const char *cmd); + +/* Is client 'X' enabled? */ +int is_openssh_client_enabled(void); +int is_dropbear_client_enabled(void); +int is_putty_client_enabled(void); + +#endif /* __PKD_UTIL_H__ */ \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/server/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/server/CMakeLists.txt new file mode 100644 index 000000000000..ab9ce5b4e0ee --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/CMakeLists.txt @@ -0,0 +1,62 @@ +project(servertests C) + +if (WITH_SERVER AND UNIX AND NOT WIN32) + +find_package(socket_wrapper) + +add_subdirectory(test_server) + +set(LIBSSH_SERVER_TESTS + torture_server_default + torture_server_auth_kbdint + torture_server_config + torture_server_algorithms + torture_sftpserver +) + +if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) + set(LIBSSH_SERVER_TESTS + ${LIBSSH_SERVER_TESTS} + torture_gssapi_server_auth + torture_gssapi_server_auth_cb + torture_gssapi_server_delegation + torture_gssapi_server_key_exchange + torture_gssapi_server_key_exchange_null + torture_gssapi_server_key_exchange_fallback) +endif() + +include_directories(${libssh_SOURCE_DIR}/include + ${libssh_BINARY_DIR}/include + ${libssh_BINARY_DIR} + test_server) + +set(TORTURE_SERVER_ENVIRONMENT ${TORTURE_ENVIRONMENT}) +list(APPEND TORTURE_SERVER_ENVIRONMENT NSS_WRAPPER_HOSTS=${CMAKE_BINARY_DIR}/tests/etc/hosts) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif () + +foreach(_SRV_TEST ${LIBSSH_SERVER_TESTS}) + add_cmocka_test(${_SRV_TEST} + SOURCES ${_SRV_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TORTURE_LIBRARY} testserver util + ) + + if (OSX) + set_property( + TEST + ${_SRV_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${SOCKET_WRAPPER_LIBRARY}) + else () + set_property( + TEST + ${_SRV_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_SERVER_ENVIRONMENT}) + endif() +endforeach() + +endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/server/test_server/CMakeLists.txt new file mode 100644 index 000000000000..f109a47d3c53 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/CMakeLists.txt @@ -0,0 +1,38 @@ +project(test_server C) + +if (WITH_SERVER AND UNIX AND NOT WIN32) + +find_package(socket_wrapper) + +set(server_SRCS + main.c +) + +add_library(testserver STATIC + test_server.c + default_cb.c + sftpserver_cb.c + testserver_common.c) +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(testserver) +endif (WITH_COVERAGE) + +include_directories(${libssh_SOURCE_DIR}/include + ${libssh_BINARY_DIR}/include + ${libssh_BINARY_DIR}) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif () + +if (UNIX AND NOT WIN32) + add_executable(test_server ${server_SRCS}) + target_compile_options(test_server PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(test_server + PRIVATE testserver ${TORTURE_LINK_LIBRARIES} ${ARGP_LIBRARIES} util) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(test_server) + endif (WITH_COVERAGE) +endif () + +endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.c b/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.c new file mode 100644 index 000000000000..dcbfd5059db3 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.c @@ -0,0 +1,1105 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + + +#include "config.h" +#include "test_server.h" +#include "default_cb.h" +#include "testserver_common.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBUTIL_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif + +#ifdef WITH_GSSAPI +#include +#endif + +int auth_none_cb(UNUSED_PARAM(ssh_session session), + const char *user, + void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_string banner = NULL; + + sdata = (struct session_data_st *)userdata; + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto denied; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + printf("None authentication of user %s\n", user); + + /* Send the banner */ + banner = ssh_string_from_char(SSHD_BANNER_MESSAGE); + if (banner == NULL) { + goto denied; + } + if (ssh_send_issue_banner(session, banner) == SSH_ERROR) { + fprintf(stderr, "Error: Failed to send the banner.\n"); + goto denied; + } +denied: + ssh_string_free(banner); + return SSH_AUTH_DENIED; +} + +int auth_pubkey_cb(UNUSED_PARAM(ssh_session session), + const char *user, + UNUSED_PARAM(struct ssh_key_struct *pubkey), + char signature_state, + void *userdata) +{ + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + printf("Public key authentication of user %s\n", user); + + switch(signature_state) { + case SSH_PUBLICKEY_STATE_NONE: + case SSH_PUBLICKEY_STATE_VALID: + break; + default: + goto denied; + } + + /* TODO */ + /* Check whether the user and public key are in authorized keys list */ + + /* Authenticated */ + printf("Authenticated\n"); + sdata->authenticated = 1; + sdata->auth_attempts = 0; + return SSH_AUTH_SUCCESS; + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} + +/* TODO implement proper pam authentication cb */ +int auth_password_cb(UNUSED_PARAM(ssh_session session), + const char *user, + const char *password, + void *userdata) +{ + bool known_user = false; + bool valid_password = false; + + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + if (sdata->password == NULL) { + fprintf(stderr, "Error: expected password not set\n"); + goto denied; + } + + printf("Password authentication of user %s\n", user); + + known_user = !(strcmp(user, sdata->username)); + valid_password = !(strcmp(password, sdata->password)); + + if (known_user && valid_password) { + sdata->authenticated = 1; + sdata->auth_attempts = 0; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} + +static int kbdint_check_response(ssh_session session, struct session_data_st *sdata) +{ + int count, cmp; + const char *answer = NULL; + + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + cmp = strcasecmp(sdata->username, answer); + if (cmp != 0) { + return 0; + } + answer = ssh_userauth_kbdint_getanswer(session, 1); + cmp = strcmp(sdata->password, answer); + if (cmp != 0) { + return 0; + } + + return 1; +} + +static int +auth_kbdint_cb(ssh_message message, ssh_session session, void *userdata) +{ + struct session_data_st *sdata = (struct session_data_st *)userdata; + + const char *name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + const char *instruction = "Get yourself authenticated"; + const char *prompts[2] = {"Username: ", "Password: "}; + char echo[] = {1, 0}; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + return SSH_AUTH_DENIED; + } + + if (!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, + name, + instruction, + 2, + prompts, + echo); + return SSH_AUTH_INFO; + } else { + if (kbdint_check_response(session, sdata)) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + } + + return SSH_AUTH_DENIED; +} + +#if WITH_GSSAPI +int auth_gssapi_mic_cb(UNUSED_PARAM(ssh_session session), + const char *user, + const char *principal, + void *userdata) +{ + struct session_data_st *sdata = NULL; + krb5_context krb5_ctx; + krb5_principal krb5_princ; + + if (user == NULL || principal == NULL || userdata == NULL) { + fprintf(stderr, "Error: invalid arguments to GSSAPI auth callback\n"); + return SSH_AUTH_ERROR; + } + + sdata = (struct session_data_st *)userdata; + + if (krb5_init_context(&krb5_ctx) != 0) { + fprintf(stderr, "Error: failed to initialize krb5 context\n"); + return SSH_AUTH_ERROR; + } + + if (krb5_parse_name(krb5_ctx, principal, &krb5_princ) != 0) { + fprintf(stderr, "Error: failed to parse krb5 principal name\n"); + krb5_free_context(krb5_ctx); + return SSH_AUTH_ERROR; + } + + if (!krb5_kuserok(krb5_ctx, krb5_princ, user)) { + krb5_free_principal(krb5_ctx, krb5_princ); + krb5_free_context(krb5_ctx); + sdata->auth_attempts++; + return SSH_AUTH_DENIED; + } + + krb5_free_principal(krb5_ctx, krb5_princ); + krb5_free_context(krb5_ctx); + + printf("Authenticated\n"); + sdata->authenticated = 1; + sdata->auth_attempts = 0; + return SSH_AUTH_SUCCESS; +} +#endif + +int channel_data_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if (len == 0 || cdata->pid < 1 || kill(cdata->pid, 0) < 0) { + rc = SSH_OK; + goto end; + } + + rc = write(cdata->child_stdin, (char *) data, len); + +end: + return rc; +} + +void channel_eof_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_close_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_signal_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *signal), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_exit_status_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(int exit_status), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_exit_signal_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *signal), + UNUSED_PARAM(int core), + UNUSED_PARAM(const char *errmsg), + UNUSED_PARAM(const char *lang), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +int channel_pty_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *term), + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + rc = openpty(&cdata->pty_master, + &cdata->pty_slave, + NULL, + NULL, + cdata->winsize); + if (rc != 0) { + fprintf(stderr, "Failed to open pty\n"); + rc = SSH_ERROR; + goto end; + } + + rc = SSH_OK; + +end: + return rc; +} + +int channel_pty_resize_cb(ssh_session session, + ssh_channel channel, + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + (void) session; + (void) channel; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + if (cdata->pty_master != -1) { + rc = ioctl(cdata->pty_master, TIOCSWINSZ, cdata->winsize); + goto end; + } + + rc = SSH_ERROR; + +end: + return rc; +} + +void channel_auth_agent_req_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ +} + +void channel_x11_req_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(int single_connection), + UNUSED_PARAM(const char *auth_protocol), + UNUSED_PARAM(const char *auth_cookie), + UNUSED_PARAM(uint32_t screen_number), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ +} + +static int exec_pty(const char *mode, + const char *command, + struct channel_data_st *cdata) +{ + int rc; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->pid = fork(); + switch(cdata->pid) { + case -1: + close(cdata->pty_master); + close(cdata->pty_slave); + fprintf(stderr, "Failed to fork\n"); + rc = SSH_ERROR; + goto end; + case 0: + close(cdata->pty_master); + if (login_tty(cdata->pty_slave) != 0) { + finalize_openssl(); + exit(1); + } + execl("/bin/sh", "sh", mode, command, NULL); + finalize_openssl(); + exit(0); + default: + close(cdata->pty_slave); + /* pty fd is bi-directional */ + cdata->child_stdout = cdata->child_stdin = cdata->pty_master; + } + + rc = SSH_OK; + +end: + return rc; +} + +static int exec_nopty(const char *command, struct channel_data_st *cdata) +{ + int in[2], out[2], err[2]; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto stdin_failed; + } + + /* Do the plumbing to be able to talk with the child process. */ + if (pipe(in) != 0) { + goto stdin_failed; + } + if (pipe(out) != 0) { + goto stdout_failed; + } + if (pipe(err) != 0) { + goto stderr_failed; + } + + switch(cdata->pid = fork()) { + case -1: + goto fork_failed; + case 0: + /* Finish the plumbing in the child process. */ + close(in[1]); + close(out[0]); + close(err[0]); + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + dup2(err[1], STDERR_FILENO); + close(in[0]); + close(out[1]); + close(err[1]); + /* exec the requested command. */ + execl("/bin/sh", "sh", "-c", command, NULL); + finalize_openssl(); + exit(0); + } + + close(in[0]); + close(out[1]); + close(err[1]); + + cdata->child_stdin = in[1]; + cdata->child_stdout = out[0]; + cdata->child_stderr = err[0]; + + return SSH_OK; + +fork_failed: + close(err[0]); + close(err[1]); +stderr_failed: + close(out[0]); + close(out[1]); +stdout_failed: + close(in[0]); + close(in[1]); +stdin_failed: + return SSH_ERROR; +} + +int channel_shell_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if(cdata->pid > 0) { + rc = SSH_ERROR; + goto end; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + rc = exec_pty("-l", NULL, cdata); + goto end; + } + + /* Client requested a shell without a pty, let's pretend we allow that */ + rc = SSH_OK; + +end: + return rc; +} + +int channel_exec_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + const char *command, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if(cdata->pid > 0) { + rc = SSH_ERROR; + goto end; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + rc = exec_pty("-c", command, cdata); + goto end; + } + + rc = exec_nopty(command, cdata); + +end: + return rc; +} + +int channel_env_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *env_name), + UNUSED_PARAM(const char *env_value), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + rc = SSH_OK; + +end: + return rc; +} + +int channel_subsystem_request_cb(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + rc = strcmp(subsystem, "sftp"); + if (rc == 0) { + rc = channel_exec_request_cb(session, + channel, + SFTP_SERVER_PATH, + userdata); + goto end; + } + + /* TODO add other subsystems */ + + rc = SSH_ERROR; + +end: + return rc; +} + +int channel_write_wontblock_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(size_t bytes), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ + + return 0; +} + +ssh_channel channel_new_session_cb(ssh_session session, void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_channel chan = NULL; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "NULL userdata"); + goto end; + } + + chan = ssh_channel_new(session); + if (chan == NULL) { + fprintf(stderr, "Error creating channel: %s\n", + ssh_get_error(session)); + goto end; + } + + sdata->channel = chan; + +end: + return chan; +} + +#ifdef WITH_PCAP +static void set_pcap(struct session_data_st *sdata, + ssh_session session, + char *pcap_file) +{ + int rc = 0; + + if (sdata == NULL) { + return; + } + + if (pcap_file == NULL) { + return; + } + + sdata->pcap = ssh_pcap_file_new(); + if (sdata->pcap == NULL) { + return; + } + + rc = ssh_pcap_file_open(sdata->pcap, pcap_file); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error opening pcap file\n"); + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; + return; + } + ssh_set_pcap_file(session, sdata->pcap); +} + +static void cleanup_pcap(struct session_data_st *sdata) +{ + if (sdata == NULL) { + return; + } + + if (sdata->pcap == NULL) { + return; + } + + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; +} +#endif + +static int process_stdout(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int process_stderr(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_server_callbacks_struct *get_default_server_cb(void) +{ + + struct ssh_server_callbacks_struct *cb; + + cb = (struct ssh_server_callbacks_struct *)calloc(1, + sizeof(struct ssh_server_callbacks_struct)); + + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->auth_none_function = auth_none_cb; + cb->auth_password_function = auth_password_cb; + cb->auth_pubkey_function = auth_pubkey_cb; + cb->channel_open_request_session_function = channel_new_session_cb; + cb->auth_kbdint_function = auth_kbdint_cb; +#if WITH_GSSAPI + cb->auth_gssapi_mic_function = auth_gssapi_mic_cb; +#endif + +end: + return cb; +} + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_channel_callbacks_struct *get_default_channel_cb(void) +{ + struct ssh_channel_callbacks_struct *cb; + + cb = (struct ssh_channel_callbacks_struct *)calloc(1, + sizeof(struct ssh_channel_callbacks_struct)); + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->channel_pty_request_function = channel_pty_request_cb; + cb->channel_pty_window_change_function = channel_pty_resize_cb; + cb->channel_shell_request_function = channel_shell_request_cb; + cb->channel_env_request_function = channel_env_request_cb; + cb->channel_subsystem_request_function = channel_subsystem_request_cb; + cb->channel_exec_request_function = channel_exec_request_cb; + cb->channel_data_function = channel_data_cb; + +end: + return cb; +}; + +void default_handle_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state) +{ + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_st cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_st sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD + }; + + struct ssh_channel_callbacks_struct *channel_cb = NULL; + struct ssh_server_callbacks_struct *server_cb = NULL; + + if (state == NULL) { + fprintf(stderr, "NULL server state provided\n"); + goto end; + } + + /* If callbacks were provided use them. Otherwise, use default callbacks */ + if (state->server_cb != NULL) { + /* This is a macro, it does not return a value */ + ssh_callbacks_init(state->server_cb); + + rc = ssh_set_server_callbacks(session, state->server_cb); + if (rc) { + goto end; + } + } else { + server_cb = get_default_server_cb(); + if (server_cb == NULL) { + goto end; + } + + server_cb->userdata = &sdata; + + /* This is a macro, it does not return a value */ + ssh_callbacks_init(server_cb); + + rc = ssh_set_server_callbacks(session, server_cb); + if (rc) { + goto end; + } + } + + sdata.server_state = (void *)state; + +#ifdef WITH_PCAP + set_pcap(&sdata, session, state->pcap_file); +#endif + + if (state->expected_username != NULL) { + sdata.username = state->expected_username; + } + + if (state->expected_password != NULL) { + sdata.password = state->expected_password; + } + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + goto end; + } + + /* Set the supported authentication methods */ + if (state->auth_methods) { + ssh_set_auth_methods(session, state->auth_methods); + } else { + ssh_set_auth_methods(session, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_PUBLICKEY | + SSH_AUTH_METHOD_INTERACTIVE | + SSH_AUTH_METHOD_GSSAPI_MIC); + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= state->max_tries || n >= 100) { + goto end; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + goto end; + } + n++; + } + + /* TODO check return values */ + if (state->channel_cb != NULL) { + ssh_callbacks_init(state->channel_cb); + + rc = ssh_set_channel_callbacks(sdata.channel, state->channel_cb); + if (rc) { + goto end; + } + } else { + channel_cb = get_default_channel_cb(); + if (channel_cb == NULL) { + goto end; + } + + channel_cb->userdata = &cdata; + + ssh_callbacks_init(channel_cb); + rc = ssh_set_channel_callbacks(sdata.channel, channel_cb); + if (rc) { + goto end; + } + } + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, -1) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while(ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } + +end: +#ifdef WITH_PCAP + cleanup_pcap(&sdata); +#endif + if (channel_cb != NULL) { + free(channel_cb); + } + if (server_cb != NULL) { + free(server_cb); + } + return; +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.h b/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.h new file mode 100644 index 000000000000..529c71e163f9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.h @@ -0,0 +1,180 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#define SSHD_DEFAULT_USER "libssh" +#define SSHD_DEFAULT_PASSWORD "libssh" +#define SSHD_DEFAULT_PORT 2222 +#define SSHD_DEFAULT_ADDRESS "127.0.0.1" +#define SSHD_DEFAULT_PCAP_FILE "debug.server.pcap" + +#define SSHD_BANNER_MESSAGE "Test Banner Message\nlibssh-send-banner\n" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) +#define SFTP_SERVER_PATH "/usr/lib/sftp-server" + +#ifdef HAVE_PTY_H +#include +#endif + +/* A userdata struct for channel. */ +struct channel_data_st { + /* pid of the child process the channel will spawn. */ + pid_t pid; + /* For PTY allocation */ + socket_t pty_master; + socket_t pty_slave; + /* For communication with the child process. */ + socket_t child_stdin; + socket_t child_stdout; + /* Only used for subsystem and exec requests. */ + socket_t child_stderr; + /* Event which is used to poll the above descriptors. */ + ssh_event event; + /* Terminal size struct. */ + struct winsize *winsize; + /* This pointer will hold the server state for default callbacks */ + void *server_state; + /* This pointer is useful to set data for custom callbacks */ + void *extra_data; + sftp_session sftp; +}; + +/* A userdata struct for session. */ +struct session_data_st { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + int auth_attempts; + int authenticated; + const char *username; + const char *password; +#ifdef WITH_PCAP + ssh_pcap_file pcap; +#endif + /* This pointer will hold the server state for default callbacks */ + void *server_state; + /* This pointer is useful to set data for custom callbacks */ + void *extra_data; +}; + +int auth_password_cb(ssh_session session, const char *user, + const char *password, void *userdata); + +#if WITH_GSSAPI +int auth_gssapi_mic_cb(ssh_session session, const char *user, + const char *principal, void *userdata); +#endif + +int channel_data_cb(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, void *userdata); + +void channel_eof_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_close_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_signal_cb (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +void channel_exit_status_cb (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +void channel_exit_signal_cb(ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +int channel_pty_request_cb(ssh_session session, ssh_channel channel, + const char *term, int cols, int rows, int py, int px, void *userdata); + +int channel_pty_resize_cb(ssh_session session, ssh_channel channel, + int cols, int rows, int py, int px, void *userdata); + +int channel_shell_request_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_auth_agent_req_callback(ssh_session session, + ssh_channel channel, void *userdata); + +void channel_x11_req_callback(ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata); + +int channel_exec_request_cb(ssh_session session, + ssh_channel channel, + const char *command, + void *userdata); + +int channel_env_request_cb(ssh_session session, + ssh_channel channel, const char *env_name, const char *env_value, + void *userdata); + +int channel_subsystem_request_cb(ssh_session session, + ssh_channel channel, const char *subsystem, + void *userdata); + +int channel_write_wontblock_cb(ssh_session session, + ssh_channel channel, + size_t bytes, + void *userdata); + +ssh_channel channel_new_session_cb(ssh_session session, void *userdata); + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_server_callbacks_struct *get_default_server_cb(void); + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_channel_callbacks_struct *get_default_channel_cb(void); + +void default_handle_session_cb(ssh_event event, ssh_session session, + struct server_state_st *state); diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/main.c b/src/libs/libssh-0.12.2/tests/server/test_server/main.c new file mode 100644 index 000000000000..bc4db3595469 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/main.c @@ -0,0 +1,667 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "test_server.h" +#include "default_cb.h" + +#include + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif + +#include +#include +#include +#include + +struct arguments_st { + char *address; + char *port; + + char *ecdsa_key; + char *ed25519_key; + char *rsa_key; + char *host_key; + + char *verbosity; + char *auth_methods; + bool with_pcap; + + char *pcap_file; + + char *username; + char *password; + + char *config_file; + char *log_file; + bool with_global_config; + char *pid_file; +}; + +static void free_arguments(struct arguments_st *arguments) +{ + if (arguments == NULL) { + return; + } + + SAFE_FREE(arguments->address); + SAFE_FREE(arguments->port); + + SAFE_FREE(arguments->ecdsa_key); + SAFE_FREE(arguments->ed25519_key); + SAFE_FREE(arguments->rsa_key); + SAFE_FREE(arguments->host_key); + + SAFE_FREE(arguments->verbosity); + SAFE_FREE(arguments->auth_methods); + SAFE_FREE(arguments->pcap_file); + + SAFE_FREE(arguments->username); + SAFE_FREE(arguments->password); + SAFE_FREE(arguments->config_file); + SAFE_FREE(arguments->log_file); + SAFE_FREE(arguments->pid_file); +} + +#ifdef HAVE_ARGP_H + +static void print_auth_methods(int auth_methods) +{ + printf("auth_methods = \n"); + if (auth_methods & SSH_AUTH_METHOD_NONE) { + printf("\tSSH_AUTH_METHOD_NONE\n"); + } + if (auth_methods & SSH_AUTH_METHOD_PASSWORD) { + printf("\tSSH_AUTH_METHOD_PASSWORD\n"); + } + if (auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + printf("\tSSH_AUTH_METHOD_PUBLICKEY\n"); + } + if (auth_methods & SSH_AUTH_METHOD_HOSTBASED) { + printf("\tSSH_AUTH_METHOD_HOSTBASED\n"); + } + if (auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { + printf("\tSSH_AUTH_METHOD_INTERACTIVE\n"); + } + if (auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { + printf("\tSSH_AUTH_METHOD_GSSAPI_MIC\n"); + } + if (auth_methods & SSH_AUTH_METHOD_GSSAPI_KEYEX) { + printf("\tSSH_AUTH_METHOD_GSSAPI_KEYEX\n"); + } +} + +static void print_verbosity(int verbosity) +{ + printf("verbosity = "); + switch(verbosity) { + case SSH_LOG_NOLOG: + printf("NO LOG\n"); + break; + case SSH_LOG_WARNING: + printf("WARNING\n"); + break; + case SSH_LOG_PROTOCOL: + printf("PROTOCOL\n"); + break; + case SSH_LOG_PACKET: + printf("PACKET\n"); + break; + case SSH_LOG_FUNCTIONS: + printf("FUNCTIONS\n"); + break; + default: + printf("UNKNOWN\n");; + break; + } +} + +static void print_server_state(struct server_state_st *state) +{ + if (state) { + printf("===================| STATE |=====================\n"); + printf("address = %s\n", + state->address? state->address: "NULL"); + printf("port = %d\n", + state->port? state->port: 0); + printf("=================================================\n"); + printf("ecdsa_key = %s\n", + state->ecdsa_key? state->ecdsa_key: "NULL"); + printf("ed25519_key = %s\n", + state->ed25519_key? state->ed25519_key: "NULL"); + printf("rsa_key = %s\n", + state->rsa_key? state->rsa_key: "NULL"); + printf("host_key = %s\n", + state->host_key? state->host_key: "NULL"); + printf("=================================================\n"); + print_auth_methods(state->auth_methods); + print_verbosity(state->verbosity); + printf("with_pcap = %s\n", + state->with_pcap? "TRUE": "FALSE"); + printf("pcap_file = %s\n", + state->pcap_file? state->pcap_file: "NULL"); + printf("=================================================\n"); + printf("username = %s\n", + state->expected_username? state->expected_username: "NULL"); + printf("password = %s\n", + state->expected_password? state->expected_password: "NULL"); + printf("=================================================\n"); + printf("with_global_config = %s\n", + state->parse_global_config? "TRUE": "FALSE"); + printf("config_file = %s\n", + state->config_file? state->config_file: "NULL"); + printf("log_file = %s\n", state->log_file ? state->log_file : "NULL"); + printf("=================================================\n"); + } +} + +static int init_server_state(struct server_state_st *state, + struct arguments_st *arguments) +{ + int rc = 0; + + if (state == NULL) { + rc = SSH_ERROR; + goto end; + } + + /* Initialize server state. The "arguments structure" */ + if (arguments->address) { + state->address = arguments->address; + arguments->address = NULL; + } else { + state->address = strdup(SSHD_DEFAULT_ADDRESS); + if (state->address == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->port) { + state->port = atoi(arguments->port); + } else { + state->port = SSHD_DEFAULT_PORT; + } + + if (arguments->ecdsa_key) { + state->ecdsa_key = arguments->ecdsa_key; + arguments->ecdsa_key = NULL; + } else { + state->ecdsa_key = NULL; + } + + if (arguments->ed25519_key) { + state->ed25519_key = arguments->ed25519_key; + arguments->ed25519_key = NULL; + } else { + state->ed25519_key = NULL; + } + + if (arguments->rsa_key) { + state->rsa_key = arguments->rsa_key; + arguments->rsa_key = NULL; + } else { + state->rsa_key = NULL; + } + + if (arguments->host_key) { + state->host_key = arguments->host_key; + arguments->host_key = NULL; + } else { + state->host_key = NULL; + } + + if (arguments->username) { + state->expected_username = arguments->username; + arguments->username = NULL; + } else { + state->expected_username = strdup(SSHD_DEFAULT_USER); + if (state->expected_username == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->password) { + state->expected_password = arguments->password; + arguments->password = NULL; + } else { + state->expected_password = strdup(SSHD_DEFAULT_PASSWORD); + if (state->expected_password == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->verbosity) { + state->verbosity = atoi(arguments->verbosity); + } else { + state->verbosity = 0; + } + + if (arguments->auth_methods) { + state->auth_methods = atoi(arguments->auth_methods); + } else { + state->auth_methods = SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_PUBLICKEY | + SSH_AUTH_METHOD_INTERACTIVE | + SSH_AUTH_METHOD_GSSAPI_MIC; + } + + state->with_pcap = arguments->with_pcap; + + if (arguments->pcap_file) { + state->pcap_file = arguments->pcap_file; + arguments->pcap_file = NULL; + } else { + if (arguments->with_pcap) { + state->pcap_file = strdup(SSHD_DEFAULT_PCAP_FILE); + if (state->pcap_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } else { + state->pcap_file = NULL; + } + } + + state->parse_global_config = arguments->with_global_config; + state->gssapi_key_exchange_algs = NULL; + + if (arguments->config_file) { + state->config_file = arguments->config_file; + arguments->config_file = NULL; + } + + if (arguments->log_file) { + state->log_file = arguments->log_file; + arguments->log_file = NULL; + } + + /* TODO make configurable */ + state->max_tries = 3; + state->error = 0; + + + if (state) { + print_server_state(state); + } + + /* TODO make callbacks configurable through command line ? */ + /* Set callbacks to be used */ + state->handle_session = default_handle_session_cb; + + /* Check required parameters */ + if (state->address == NULL) { + rc = SSH_ERROR; + goto end; + } + +end: + if (rc != 0) { + free_server_state(state); + } + + return rc; +} + +const char *argp_program_version = "libssh test server " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "ecdsakey", + .key = 'c', + .arg = "FILE", + .flags = 0, + .doc = "Set the ECDSA key.", + .group = 0 + }, + { + .name = "ed25519key", + .key = 'e', + .arg = "FILE", + .flags = 0, + .doc = "Set the ed25519 key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the RSA key.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "pcapfile", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "Set the pcap output file.", + .group = 0 + }, + { + .name = "pid_file", + .key = 'i', + .arg = "FILE", + .flags = 0, + .doc = "The server will write its pid in this file, if provided.", + .group = 0 + }, + { + .name = "auth-methods", + .key = 'a', + .arg = "METHODS", + .flags = 0, + .doc = "Set supported authentication methods.", + .group = 0 + }, + { + .name = "user", + .key = 'u', + .arg = "USERNAME", + .flags = 0, + .doc = "Set expected username.", + .group = 0 + }, + { + .name = "verbosity", + .key = 'v', + .arg = "VERBOSITY", + .flags = 0, + .doc = "Set output verbosity [0-4].", + .group = 0 + }, + { + .name = "with-pcap", + .key = 'w', + .arg = NULL, + .flags = 0, + .doc = "Use PCAP.", + .group = 0 + }, + { + .name = "without-global-config", + .key = 'g', + .arg = NULL, + .flags = 0, + .doc = "Do not use system-wide configuration file.", + .group = 0 + }, + { + .name = "config", + .key = 'C', + .arg = "CONFIG_FILE", + .flags = 0, + .doc = "Use this server configuration file.", + .group = 0 + }, + { + .name = "log_file", + .key = 'l', + .arg = "LOG_FILE", + .flags = 0, + .doc = "Output log to this file.", + .group = 0 + }, + { .name = NULL } +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct arguments_st *arguments = state->input; + error_t rc = 0; + + if (arguments == NULL) { + fprintf(stderr, "NULL pointer to arguments structure provided\n"); + rc = EINVAL; + goto end; + } + + switch (key) { + case 'c': + arguments->ecdsa_key = strdup(arg); + if (arguments->ecdsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'e': + arguments->ed25519_key = strdup(arg); + if (arguments->ed25519_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'f': + arguments->pcap_file = strdup(arg); + if (arguments->pcap_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'i': + arguments->pid_file = strdup(arg); + if (arguments->pid_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'k': + arguments->host_key = strdup(arg); + if (arguments->host_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'a': + arguments->auth_methods = strdup(arg); + if (arguments->auth_methods == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'p': + arguments->port = strdup(arg); + if (arguments->port == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'r': + arguments->rsa_key = strdup(arg); + if (arguments->rsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'u': + arguments->username = strdup(arg); + if (arguments->username == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'v': + arguments->verbosity = strdup(arg); + if (arguments->verbosity == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'w': + arguments->with_pcap = true; + break; + case 'g': + arguments->with_global_config = false; + break; + case 'C': + arguments->config_file = strdup(arg); + if (arguments->config_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'l': + arguments->log_file = strdup(arg); + if (arguments->log_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + printf("Too many arguments\n"); + argp_usage(state); + } + arguments->address = strdup(arg); + if (arguments->address == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + printf("Too few arguments\n"); + /* Not enough arguments. */ + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + +end: + return rc; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; + +#endif /* HAVE_ARGP_H */ + +int main(UNUSED_PARAM(int argc), UNUSED_PARAM(char **argv)) +{ + int rc; + FILE *pid_file; + pid_t pid; + + struct arguments_st arguments = { + .address = NULL, + .with_global_config = true, + }; + struct server_state_st *state = calloc(1, sizeof(struct server_state_st)); + + if (state == NULL) { + printf("Failed to allocate memory\n"); + return -1; + } + +#ifdef HAVE_ARGP_H + argp_parse (&argp, argc, argv, 0, 0, &arguments); +#endif + + if (arguments.pid_file) { + pid_file = fopen(arguments.pid_file, "w"); + if (pid_file == NULL) { + rc = -1; + free_server_state(state); + SAFE_FREE(state); + goto free_arguments; + } + pid = getpid(); + fprintf(pid_file, "%d\n", pid); + fclose(pid_file); + } + + /* Initialize the state using default or given parameters */ + rc = init_server_state(state, &arguments); + if (rc != 0) { + free_server_state(state); + SAFE_FREE(state); + goto free_arguments; + } + + /* Free the arguments used to initialize the state before fork */ + free_arguments(&arguments); + + /* Run the server: Frees the state in all processes */ + rc = run_server(state); + +free_arguments: + free_arguments(&arguments); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/sftpserver_cb.c b/src/libs/libssh-0.12.2/tests/server/test_server/sftpserver_cb.c new file mode 100644 index 000000000000..3dd78426ffde --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/sftpserver_cb.c @@ -0,0 +1,425 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "test_server.h" +#include "default_cb.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for cmocka +#include // for cmocka +#include // for cmocka + +#include + +#ifdef HAVE_LIBUTIL_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif + +/* below are for sftp */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) + + +/* TODO implement proper pam authentication cb */ +static int sftp_auth_password_cb(UNUSED_PARAM(ssh_session session), + const char *user, + const char *password, + void *userdata) +{ + bool known_user = false; + bool valid_password = false; + + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + if (sdata->password == NULL) { + fprintf(stderr, "Error: expected password not set\n"); + goto denied; + } + + printf("Password authentication of user %s\n", user); + + known_user = !(strcmp(user, sdata->username)); + valid_password = !(strcmp(password, sdata->password)); + + if (known_user && valid_password) { + sdata->authenticated = 1; + sdata->auth_attempts = 0; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} + +static ssh_channel sftp_channel_new_session_cb(ssh_session session, void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_channel chan = NULL; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "NULL userdata"); + goto end; + } + + if (sdata->channel != NULL) { + fprintf(stderr, "Only one channel is supported\n"); + goto end; + } + + chan = ssh_channel_new(session); + if (chan == NULL) { + fprintf(stderr, "Error creating channel: %s\n", + ssh_get_error(session)); + goto end; + } + + sdata->channel = chan; + +end: + return chan; +} + +#ifdef WITH_PCAP +static void set_pcap(struct session_data_st *sdata, + ssh_session session, + char *pcap_file) +{ + int rc = 0; + + if (sdata == NULL) { + return; + } + + if (pcap_file == NULL) { + return; + } + + sdata->pcap = ssh_pcap_file_new(); + if (sdata->pcap == NULL) { + return; + } + + rc = ssh_pcap_file_open(sdata->pcap, pcap_file); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error opening pcap file\n"); + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; + return; + } + ssh_set_pcap_file(session, sdata->pcap); +} + +static void cleanup_pcap(struct session_data_st *sdata) +{ + if (sdata == NULL) { + return; + } + + if (sdata->pcap == NULL) { + return; + } + + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; +} +#endif + + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + */ +struct ssh_server_callbacks_struct *get_sftp_server_cb(void) +{ + + struct ssh_server_callbacks_struct *cb; + + cb = (struct ssh_server_callbacks_struct *)calloc(1, + sizeof(struct ssh_server_callbacks_struct)); + + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->auth_password_function = sftp_auth_password_cb; + cb->channel_open_request_session_function = sftp_channel_new_session_cb; + +end: + return cb; +} + +/* Default SFTP channel data callback with some additional checks */ +int sftp_channel_data_callback(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + sftp_session *sftpp = (sftp_session *)userdata; + int rv; + + rv = sftp_channel_default_data_callback(session, + channel, + data, + len, + is_stderr, + userdata); + + if (sftpp != NULL && *sftpp != NULL) { + sftp_session sftp = *sftpp; + /* NOTE that this expects both server and clieng being libssh with this + * same version number */ + assert_true(sftp->client_version <= LIBSFTP_VERSION); + } + return rv; +} + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_channel_callbacks_struct *get_sftp_channel_cb(void) +{ + struct ssh_channel_callbacks_struct *cb; + + cb = (struct ssh_channel_callbacks_struct *)calloc(1, + sizeof(struct ssh_channel_callbacks_struct)); + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->channel_data_function = sftp_channel_data_callback; + cb->channel_subsystem_request_function = sftp_channel_default_subsystem_request; + +end: + return cb; +}; + +void sftp_handle_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state) +{ + int n; + int rc = 0; + + /* Our struct holding information about the channel. */ + struct channel_data_st cdata = { + .sftp = NULL, + }; + + /* Our struct holding information about the session. */ + struct session_data_st sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD, + }; + + struct ssh_channel_callbacks_struct *channel_cb = NULL; + struct ssh_server_callbacks_struct *server_cb = NULL; + + if (state == NULL) { + fprintf(stderr, "NULL server state provided\n"); + goto end; + } + + /* If callbacks were provided use them. Otherwise, use default callbacks */ + if (state->server_cb != NULL) { + /* This is a macro, it does not return a value */ + ssh_callbacks_init(state->server_cb); + + rc = ssh_set_server_callbacks(session, state->server_cb); + if (rc) { + goto end; + } + } else { + server_cb = get_sftp_server_cb(); + if (server_cb == NULL) { + goto end; + } + + server_cb->userdata = &sdata; + + /* This is a macro, it does not return a value */ + ssh_callbacks_init(server_cb); + + rc = ssh_set_server_callbacks(session, server_cb); + if (rc) { + goto end; + } + } + + sdata.server_state = (void *)state; + +#ifdef WITH_PCAP + set_pcap(&sdata, session, state->pcap_file); +#endif + + if (state->expected_username != NULL) { + sdata.username = state->expected_username; + } + + if (state->expected_password != NULL) { + sdata.password = state->expected_password; + } + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + goto end; + } + + /* Set the supported authentication methods */ + if (state->auth_methods) { + ssh_set_auth_methods(session, state->auth_methods); + } else { + ssh_set_auth_methods(session, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_PUBLICKEY); + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= state->max_tries || n >= 100) { + goto end; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + goto end; + } + n++; + } + + /* TODO check return values */ + if (state->channel_cb != NULL) { + ssh_callbacks_init(state->channel_cb); + + rc = ssh_set_channel_callbacks(sdata.channel, state->channel_cb); + if (rc) { + goto end; + } + } else { + channel_cb = get_sftp_channel_cb(); + if (channel_cb == NULL) { + goto end; + } + + channel_cb->userdata = &(cdata.sftp); + + ssh_callbacks_init(channel_cb); + rc = ssh_set_channel_callbacks(sdata.channel, channel_cb); + if (rc) { + goto end; + } + } + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + } while (ssh_channel_is_open(sdata.channel) && + !ssh_channel_is_eof(sdata.channel)); + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + sftp_server_free(cdata.sftp); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } + +end: +#ifdef WITH_PCAP + cleanup_pcap(&sdata); +#endif + if (channel_cb != NULL) { + free(channel_cb); + } + if (server_cb != NULL) { + free(server_cb); + } + return; +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/test_server.c b/src/libs/libssh-0.12.2/tests/server/test_server/test_server.c new file mode 100644 index 000000000000..69b6dac23296 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/test_server.c @@ -0,0 +1,395 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "test_server.h" +#include "testserver_common.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +void free_server_state(struct server_state_st *state) +{ + if (state == NULL) { + return; + } + + SAFE_FREE(state->address); + + SAFE_FREE(state->ecdsa_key); + SAFE_FREE(state->ed25519_key); + SAFE_FREE(state->rsa_key); + SAFE_FREE(state->host_key); + + SAFE_FREE(state->pcap_file); + + SAFE_FREE(state->expected_username); + SAFE_FREE(state->expected_password); + SAFE_FREE(state->config_file); + SAFE_FREE(state->log_file); + SAFE_FREE(state->server_cb); + SAFE_FREE(state->channel_cb); +} + +/* SIGCHLD handler for cleaning up dead children. */ +static void sigchld_handler(int signo) { + (void) signo; + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +bool done = false; + +static void sigterm_handler(int signo) +{ + (void) signo; + fprintf(stderr, "Received SIGTERM. Gracefully exiting ...\n"); + done = true; +} + +int run_server(struct server_state_st *state) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event event = NULL; + + struct sigaction sa = { + .sa_flags = 0 + }; + + int rc = SSH_ERROR; + + /* Check provided state */ + if (state == NULL) { + fprintf(stderr, "Invalid state\n"); + goto out; + } + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGCHLD handler\n"); + goto out; + } + + /* Set up SIGTERM handler. */ + sa.sa_handler = sigterm_handler; + sa.sa_flags = 0; + + if (sigaction(SIGTERM, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGTERM handler\n"); + goto out; + } + + /* Redirect all the output and errors to the file to avoid mixing up with + * the output from the client */ + if (state->log_file != NULL) { + int fd; + FILE *f = fopen(state->log_file, "a"); + if (f == NULL) { + fprintf(stderr, "Failed to open the log file: %s\n", strerror(errno)); + goto out; + } + fd = dup2(fileno(f), STDERR_FILENO); + if (fd == -1) { + fprintf(stderr, "dup2 of log file to stderr failed: %s\n", + strerror(errno)); + fclose(f); + goto out; + } + fd = dup2(fileno(f), STDOUT_FILENO); + if (fd == -1) { + fprintf(stderr, "dup2 of log file to stdout failed: %s\n", + strerror(errno)); + fclose(f); + goto out; + } + fclose(f); + } + + if (state->address == NULL) { + fprintf(stderr, "Missing bind address\n"); + goto out; + } + + if (state->host_key == NULL && state->rsa_key == NULL && + state->ecdsa_key == NULL && state->ed25519_key == NULL) { + fprintf(stderr, "Missing host key\n"); + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + fprintf(stderr, "Out of memory\n"); + goto out; + } + + if (state->verbosity) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &state->verbosity); + if (rc != 0) { + fprintf(stderr, + "Error setting verbosity level: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } + + if (!state->parse_global_config) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_PROCESS_CONFIG, + &(state->parse_global_config)); + if (rc != 0) { + goto out; + } + } + + if (state->config_file) { + rc = ssh_bind_options_parse_config(sshbind, state->config_file); + if (rc != 0) { + goto out; + } + } + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_BINDADDR, + state->address); + if (rc != 0) { + fprintf(stderr, + "Error setting bind address: %s\n", + ssh_get_error(sshbind)); + goto out; + } + +#ifdef WITH_GSSAPI + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE, + &(state->gssapi_key_exchange)); + if (rc != 0) { + fprintf(stderr, + "Error setting GSSAPI key exchange: %s\n", + ssh_get_error(sshbind)); + goto out; + } + + if (state->gssapi_key_exchange_algs != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + state->gssapi_key_exchange_algs); + if (rc != 0) { + fprintf(stderr, + "Error setting GSSAPI key exchange algorithms: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } +#endif /* WITH_GSSAPI */ + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_BINDPORT, + &(state->port)); + if (rc != 0) { + fprintf(stderr, + "Error setting bind port: %s\n", + ssh_get_error(sshbind)); + goto out; + } + + if (state->rsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + state->rsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting RSA key: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } + + if (state->ecdsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + state->ecdsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting ECDSA key: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } + + if (state->host_key) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + state->host_key); + if (rc) { + fprintf(stderr, + "Error setting hostkey: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } + + rc = ssh_bind_listen(sshbind); + if (rc != 0) { + fprintf(stderr, + "Error listening to socket: %s\n", + ssh_get_error(sshbind)); + goto out; + } + + printf("%d: Started libssh test server on port %d\n", getpid(), state->port); + + while (done == false) { + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto out; + } + + /* Blocks until there is a new incoming connection. */ + rc = ssh_bind_accept(sshbind, session); + if (rc != SSH_ERROR) { + pid_t pid = fork(); + + switch(pid) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove the SIGTERM handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGTERM, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + state->handle_session(event, session, state); + ssh_event_free(event); + } else { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + + free_server_state(state); + SAFE_FREE(state); + finalize_openssl(); + exit(0); + case -1: + fprintf(stderr, "Failed to fork\n"); + } + fprintf(stderr, "Forked process PID %d\n", pid); + } else { + fprintf(stderr, + "Error accepting a connection: %s\n", + ssh_get_error(sshbind)); + } + + /* Since the session has been passed to a child fork, do some cleaning + * up at the parent process. */ + ssh_disconnect(session); + ssh_free(session); + } + + rc = 0; + +out: + free_server_state(state); + SAFE_FREE(state); + ssh_bind_free(sshbind); + return rc; +} + +pid_t +fork_run_server(struct server_state_st *state, + void (*free_test_state) (void **userdata), + void *userdata) +{ + pid_t pid; + int rc; + + char err_str[1024] = {0}; + + struct sigaction sa; + + /* Check provided state */ + if (state == NULL) { + fprintf(stderr, "Invalid state\n"); + return -1; + } + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + strerror_r(errno, err_str, 1024); + fprintf(stderr, "Failed to register SIGCHLD handler: %s\n", + err_str); + return -1; + } + + pid = fork(); + switch(pid) { + case 0: + /* no longer needed */ + free_test_state(userdata); + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + + /* The child process starts a server which will listen for connections */ + rc = run_server(state); + finalize_openssl(); + exit(rc); + case -1: + strerror_r(errno, err_str, 1024); + fprintf(stderr, "Failed to fork: %s\n", + err_str); + return -1; + default: + /* Return the child pid */ + fprintf(stderr, "Forked process PID %d\n", pid); + return pid; + } +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/test_server.h b/src/libs/libssh-0.12.2/tests/server/test_server/test_server.h new file mode 100644 index 000000000000..b4e17f69fdab --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/test_server.h @@ -0,0 +1,82 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +struct server_state_st { + /* Arguments */ + char *address; + int port; + + char *ecdsa_key; + char *ed25519_key; + char *rsa_key; + char *host_key; + + int verbosity; + int auth_methods; + bool with_pcap; + + char *pcap_file; + + char *expected_username; + char *expected_password; + + char *config_file; + bool parse_global_config; + + char *log_file; + bool gssapi_key_exchange; + const char *gssapi_key_exchange_algs; + + /* State */ + int max_tries; + int error; + + struct ssh_server_callbacks_struct *server_cb; + struct ssh_channel_callbacks_struct *channel_cb; + + /* Callback to handle the session, should block until disconnected */ + void (*handle_session)(ssh_event event, + ssh_session session, + struct server_state_st *state); +}; + +/*TODO: Add documentation */ +void free_server_state(struct server_state_st *state); + +/*TODO: Add documentation */ +int run_server(struct server_state_st *state); + +/*TODO: Add documentation */ +pid_t +fork_run_server(struct server_state_st *state, + void (*free_state) (void **userdata), + void *userdata); diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.c b/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.c new file mode 100644 index 000000000000..c097073871e7 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.c @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "testserver_common.h" + +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + +void finalize_openssl(void) +{ +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.h b/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.h new file mode 100644 index 000000000000..01a9c57f9bad --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.h @@ -0,0 +1,26 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +void finalize_openssl(void); diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth.c new file mode 100644 index 000000000000..8058d4d05b19 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth.c @@ -0,0 +1,456 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int +setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int +teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int +session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void +torture_gssapi_server_auth_no_client_cred(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* No client credential */ + torture_setup_kdc_server( + (void**)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_invalid_host(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Invalid host principal */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site\n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site\n" + "kadmin.local addprinc -pw bar alice\n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_OK); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_auth_server_identity(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid server identity option */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "invalid.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server((void **)&s); + + /* Valid server identity option*/ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "server.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_no_client_cred, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_invalid_host, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_server_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth_cb.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth_cb.c new file mode 100644 index 000000000000..6a30e6c04269 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth_cb.c @@ -0,0 +1,518 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/gssapi.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static ssh_string +select_oid(ssh_session session, + const char *user, + int n_oid, + ssh_string *oids, + void *userdata) +{ + /* Choose the first oid */ + return oids[0]; +} + +static int +accept_sec_ctx(ssh_session session, + ssh_string input_token, + ssh_string *output_token, + void *userdata) +{ + ssh_string token; + OM_uint32 min_stat; + gss_buffer_desc itoken, otoken = GSS_C_EMPTY_BUFFER; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags = 0; + gss_channel_bindings_t input_bindings = GSS_C_NO_CHANNEL_BINDINGS; + + itoken.length = ssh_string_len(input_token); + itoken.value = ssh_string_data(input_token); + + gss_accept_sec_context(&min_stat, + &session->gssapi->ctx, + session->gssapi->server_creds, + &itoken, + input_bindings, + &client_name, + NULL /*mech_oid*/, + &otoken, + &ret_flags, + NULL /*time*/, + &session->gssapi->client_creds); + + if (client_name != GSS_C_NO_NAME) { + session->gssapi->client_name = client_name; + session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); + } + token = ssh_string_new(otoken.length); + ssh_string_fill(token, otoken.value, otoken.length); + *output_token = token; + + gss_release_buffer(&min_stat, &otoken); + gss_release_name(&min_stat, &client_name); + SSH_STRING_FREE(input_token); + + return 0; +} + +static int +verify_mic(ssh_session session, + ssh_string mic, + void *mic_buffer, + size_t mic_buffer_size, + void *userdata) +{ + /* Verify without checking */ + return 0; +} + +static int +setup_callback_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + struct session_data_st sdata = {.channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD}; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + ss->server_cb = get_default_server_cb(); + ss->server_cb->gssapi_select_oid_function = select_oid; + ss->server_cb->gssapi_accept_sec_ctx_function = accept_sec_ctx; + ss->server_cb->gssapi_verify_mic_function = verify_mic; + ss->server_cb->userdata = &sdata; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int +teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + SAFE_FREE(tss->ss->server_cb); + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int +session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void +torture_gssapi_server_auth_cb_no_client_cred(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* No client credential */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_cb_invalid_host(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Invalid host principal */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_cb(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site\n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site\n" + "kadmin.local addprinc -pw bar alice\n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_OK); + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_auth_cb_bad_user(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site\n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site\n" + "kadmin.local addprinc -pw bar alice\n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb_no_client_cred, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb_invalid_host, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb_bad_user, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_callback_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_delegation.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_delegation.c new file mode 100644 index 000000000000..dd3ad335b22d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_delegation.c @@ -0,0 +1,376 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int +auth_gssapi_mic(ssh_session session, + UNUSED_PARAM(const char *user), + UNUSED_PARAM(const char *principal), + void *userdata) +{ + OM_uint32 min_stat; + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + assert_non_null(creds); + + gss_release_cred(&min_stat, creds); + + return SSH_AUTH_SUCCESS; +} + +static int +setup_callback_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + struct session_data_st sdata = {.channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD}; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + ss->server_cb = get_default_server_cb(); + ss->server_cb->auth_gssapi_mic_function = auth_gssapi_mic; + ss->server_cb->userdata = &sdata; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int +teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + SAFE_FREE(tss->ss->server_cb); + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int +session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void +torture_gssapi_server_delegate_creds(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + OM_uint32 maj_stat, min_stat; + gss_cred_id_t client_creds = GSS_C_NO_CREDENTIAL; + gss_OID_set no_mechs = GSS_C_NO_OID_SET; + int t = 1; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &t); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + maj_stat = gss_acquire_cred(&min_stat, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + GSS_C_INITIATE, + &client_creds, + &no_mechs, + NULL); + assert_int_equal(GSS_ERROR(maj_stat), 0); + + ssh_gssapi_set_creds(session, client_creds); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + gss_release_cred(&min_stat, &client_creds); + gss_release_oid_set(&min_stat, &no_mechs); + + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_delegate_creds, + session_setup, + session_teardown), + + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_callback_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange.c new file mode 100644 index 000000000000..67704f6d088e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange.c @@ -0,0 +1,604 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/crypto.h" +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_KEYEX; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-," + "gss-group16-sha512-," + "gss-nistp256-sha256-," + "gss-curve25519-sha256-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024] = {0}; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_gssapi_server_key_exchange(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + assert_true(ssh_session_kex_is_gss(session)); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_no_tgt(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Don't run kinit */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_false(ssh_session_kex_is_gss(session)); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_alg(void **state, + const char *kex_string, + enum ssh_key_exchange_e kex_type) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + kex_string); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_int_equal(session->current_crypto->kex_type, kex_type); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_gss_group14_sha256(void **state) +{ + torture_gssapi_server_key_exchange_alg(state, + "gss-group14-sha256-", + SSH_GSS_KEX_DH_GROUP14_SHA256); +} + +static void torture_gssapi_server_key_exchange_gss_group16_sha512(void **state) +{ + torture_gssapi_server_key_exchange_alg(state, + "gss-group16-sha512-", + SSH_GSS_KEX_DH_GROUP16_SHA512); +} + +static void torture_gssapi_server_key_exchange_gss_nistp256_sha256(void **state) +{ + torture_gssapi_server_key_exchange_alg(state, + "gss-nistp256-sha256-", + SSH_GSS_KEX_ECDH_NISTP256_SHA256); +} + +static void torture_gssapi_server_key_exchange_gss_curve25519_sha256(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + torture_gssapi_server_key_exchange_alg(state, + "gss-curve25519-sha256-", + SSH_GSS_KEX_CURVE25519_SHA256); +} + +static void torture_gssapi_server_key_exchange_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_auth_bad_user(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_no_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + bool f = false; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + /* Don't do GSSAPI Key Exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &f); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + assert_false(ssh_session_kex_is_gss(session)); + + /* Still try to do "gssapi-keyex" auth */ + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + + torture_teardown_kdc_server((void **)&s); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_no_tgt, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_group16_sha512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_nistp256_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_curve25519_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_auth_bad_user, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_no_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_fallback.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_fallback.c new file mode 100644 index 000000000000..a5988ed9d6fb --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_fallback.c @@ -0,0 +1,336 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/crypto.h" +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_KEYEX; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024] = {0}; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_gssapi_server_key_exchange_fallback(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + "gss-group16-sha512-"); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + assert_false(ssh_session_kex_is_gss(session)); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + + torture_teardown_kdc_server((void **)&s); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_fallback, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_null.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_null.c new file mode 100644 index 000000000000..1fc9fa866868 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_null.c @@ -0,0 +1,346 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_KEYEX; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-," + "gss-group16-sha512-," + "gss-nistp256-sha256-," + "gss-curve25519-sha256-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024] = {0}; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + torture_teardown_kdc_server((void **)&s); + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_gssapi_server_key_exchange_null(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(s->ssh.session, rc); + + assert_true(ssh_session_kex_is_gss(session)); + + assert_string_equal(session->current_crypto->kex_methods[SSH_HOSTKEYS], + "null"); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_no_tgt(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Don't run kinit */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + torture_teardown_kdc_server((void **)&s); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_null, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_no_tgt, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_server_algorithms.c b/src/libs/libssh-0.12.2/tests/server/torture_server_algorithms.c new file mode 100644 index 000000000000..02d8aaae2746 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_server_algorithms.c @@ -0,0 +1,454 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/token.h" + +#include "test_server.h" +#include "default_cb.h" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + char *cwd; + char *temp_dir; + char rsa_hostkey[1024]; +}; + +static int setup_files(void **state) +{ + struct test_server_st *tss; + struct torture_state *s; + char sshd_path[1024]; + char log_file[1024]; + + int rc; + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(tss->rsa_hostkey, + sizeof(tss->rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(tss->rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + /* not to mix up the client and server messages */ + s->log_file = strdup(log_file); + + tss->state = s; + *state = tss; + + return 0; +} + +static int teardown_files(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + torture_teardown_socket_dir((void **)&s); + SAFE_FREE(tss); + + return 0; +} + +static int setup_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + char *cwd = NULL; + char *tmp_dir = NULL; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + return 0; +} + +static int teardown_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + int rc; + + assert_non_null(tss); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static int start_server(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + /* Start the server using the default values */ + torture_setup_libssh_server((void **)&s, "./test_server/test_server"); + assert_non_null(s); + + return 0; +} + +static int stop_server(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + int rc; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + rc = torture_terminate_process(s->srv_pidfile); + assert_return_code(rc, errno); + + unlink(s->srv_pidfile); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* + * Check that the handshake works with an AEAD cipher configured + * but with no overlap for HMACs. AEAD ciphers have an implied HMAC + * so no HMAC overlap in the handshake should not fail the connection. + */ +static void test_algorithm_no_hmac_overlap(void **state, const char *algorithm) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + char config_content[4096]; + + ssh_session session = NULL; + + int rc; + + assert_non_null(tss); + s = tss->state; + assert_non_null(s); + + /* Prepare config file */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\nMACs %s\n", + tss->rsa_hostkey, + algorithm, + "hmac-sha2-512-etm@openssh.com"); + + assert_non_null(s->srv_config); + torture_write_file(s->srv_config, config_content); + + SSH_LOG(SSH_LOG_TRACE, + "Config file %s content: \n\n%s\n", + s->srv_config, + config_content); + + /* Start server */ + rc = start_server(state); + assert_int_equal(rc, 0); + + /* Setup session */ + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, algorithm); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, algorithm); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, "hmac-sha2-512"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha2-512"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_ssh_return_code(session, rc); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); + + SAFE_FREE(s->srv_additional_config); +} + +static void torture_algorithm_chacha20_with_no_hmac_overlap(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + test_algorithm_no_hmac_overlap(state, "chacha20-poly1305@openssh.com"); +} + +static void torture_algorithm_aes256gcm_with_no_hmac_overlap(void **state) +{ + test_algorithm_no_hmac_overlap(state, "aes256-gcm@openssh.com"); +} + +static void torture_algorithm_aes128gcm_with_no_hmac_overlap(void **state) +{ + test_algorithm_no_hmac_overlap(state, "aes128-gcm@openssh.com"); +} + +/* + * Check the self-compatibility of a given key exchange method. + */ +static void test_kex_self_compat(void **state, const char *kex) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + char config_content[4096]; + int rc; + + assert_non_null(tss); + s = tss->state; + assert_non_null(s); + + /* Prepare config file */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nKexAlgorithms %s\n", + tss->rsa_hostkey, + kex); + + assert_non_null(s->srv_config); + torture_write_file(s->srv_config, config_content); + + SSH_LOG(SSH_LOG_TRACE, + "Config file %s content: \n\n%s\n", + s->srv_config, + config_content); + + rc = start_server(state); + assert_int_equal(rc, 0); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_ssh_return_code(session, rc); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); +} + +static void torture_algorithm_mlkem768x25119_self_compat(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + test_kex_self_compat(state, "mlkem768x25519-sha256"); +} + +static void torture_algorithm_mlkem768nistp256_self_compat(void **state) +{ + test_kex_self_compat(state, "mlkem768nistp256-sha256"); +} + +#ifdef HAVE_MLKEM1024 +static void torture_algorithm_mlkem1024nistp384_self_compat(void **state) +{ + test_kex_self_compat(state, "mlkem1024nistp384-sha384"); +} +#endif /* HAVE_MLKEM1024 */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_algorithm_chacha20_with_no_hmac_overlap, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_aes256gcm_with_no_hmac_overlap, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_aes128gcm_with_no_hmac_overlap, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_mlkem768x25119_self_compat, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_mlkem768nistp256_self_compat, + setup_temp_dir, teardown_temp_dir), +#ifdef HAVE_MLKEM1024 + cmocka_unit_test_setup_teardown(torture_algorithm_mlkem1024nistp384_self_compat, + setup_temp_dir, teardown_temp_dir), +#endif /* HAVE_MLKEM1024 */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_files, + teardown_files); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_server_auth_kbdint.c b/src/libs/libssh-0.12.2/tests/server/torture_server_auth_kbdint.c new file mode 100644 index 000000000000..c12626bb2585 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_server_auth_kbdint.c @@ -0,0 +1,818 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +enum { + SUCCESS, + MORE, + FAILED +}; + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; +}; + +#ifdef WITH_PCAP +static void set_pcap(struct session_data_st *sdata, + ssh_session session, + char *pcap_file) +{ + int rc = 0; + + if (sdata == NULL) { + return; + } + + if (pcap_file == NULL) { + return; + } + + sdata->pcap = ssh_pcap_file_new(); + if (sdata->pcap == NULL) { + return; + } + + rc = ssh_pcap_file_open(sdata->pcap, pcap_file); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error opening pcap file\n"); + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; + return; + } + ssh_set_pcap_file(session, sdata->pcap); +} + +static void cleanup_pcap(struct session_data_st *sdata) +{ + if (sdata == NULL) { + return; + } + + if (sdata->pcap == NULL) { + return; + } + + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; +} +#endif + +static int process_stdout(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int process_stderr(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +static int authenticate_kbdint(ssh_session session, + ssh_message message, + void *userdata) +{ + int rc = 0; + int count; + int *step = NULL; + size_t expected_len; + + const char instruction[] = "Type the requested data"; + const char name[] = "Keyboard-Interactive Authentication\n"; + char initial_echo[] = {1, 0}; + char retype_echo[] = {0}; + const char *initial_prompt[2]; + const char *retype_prompt[1]; + int cmp; + + const char *answer; + + struct session_data_st *sdata = (struct session_data_st *)userdata; + + initial_prompt[0] = "username: "; + initial_prompt[1] = "password: "; + + /* Prompt for additional prompts */ + retype_prompt[0] = "retype password: "; + + if ((session == NULL) || (message == NULL) || (sdata == NULL)) { + fprintf(stderr, "Null argument provided\n"); + goto failed; + } + + if (sdata->extra_data == NULL) { + goto failed; + } + + step = (int *)sdata->extra_data; + + switch (*step) { + case 0: + ssh_message_auth_interactive_request(message, name, instruction, 2, + initial_prompt, initial_echo); + rc = MORE; + goto end; + case 1: + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + goto failed; + } + + if ((sdata->username == NULL) || (sdata->password == NULL)) { + goto failed; + } + + /* Get and compare username */ + expected_len = strlen(sdata->username); + if (expected_len <= 0) { + goto failed; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + if (answer == NULL) { + goto failed; + } + + cmp = strncmp(answer, sdata->username, expected_len); + if (cmp != 0) { + goto failed; + } + + /* Get and compare password */ + expected_len = strlen(sdata->password); + if (expected_len <= 0) { + goto failed; + } + + answer = ssh_userauth_kbdint_getanswer(session, 1); + if (answer == NULL) { + goto failed; + } + + cmp = strncmp(answer, sdata->password, expected_len); + if (cmp != 0) { + goto failed; + } + + /* Username and password matched. Ask for a retype. */ + ssh_message_auth_interactive_request(message, + name, + instruction, + 1, + retype_prompt, + retype_echo); + + rc = MORE; + goto end; + case 2: + /* Get and compare password */ + expected_len = strlen(sdata->password); + if (expected_len <= 0) { + goto failed; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + if (answer == NULL) { + goto failed; + } + + cmp = strncmp(answer, sdata->password, expected_len); + if (cmp != 0) { + goto failed; + } + + /* Password was correct, authenticated */ + rc = SUCCESS; + goto end; + default: + goto failed; + } + +failed: + if (step != NULL) { + *step = 0; + } + return FAILED; + +end: + if (step != NULL) { + (*step)++; + } + return rc; +} + +static int authenticate_callback(ssh_session session, + ssh_message message, + void *userdata) +{ + struct session_data_st *sdata = (struct session_data_st *)userdata; + int rc; + + if (sdata == NULL) { + fprintf(stderr, "Null userdata\n"); + goto denied; + } + + if (sdata->extra_data == NULL) { + sdata->extra_data = (void *)calloc(1, sizeof(int)); + } + + switch (ssh_message_type(message)) { + case SSH_REQUEST_AUTH: + switch (ssh_message_subtype(message)) { + case SSH_AUTH_METHOD_INTERACTIVE: + rc = authenticate_kbdint(session, message, (void *)sdata); + if (rc == SUCCESS) { + goto accept; + } + else if (rc == MORE) { + goto more; + } + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_INTERACTIVE); + goto denied; + default: + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_INTERACTIVE); + goto denied; + } + default: + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_INTERACTIVE); + goto denied; + } + + ssh_message_free(message); + +accept: + if (sdata) { + if (sdata->extra_data) { + free(sdata->extra_data); + sdata->extra_data = NULL; + } + } + ssh_message_auth_reply_success (message, 0); +more: + return 0; +denied: + if (sdata) { + if (sdata->extra_data) { + free(sdata->extra_data); + sdata->extra_data = NULL; + } + } + return 1; +} + +static void handle_kbdint_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state) +{ + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_st cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_st sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = TORTURE_SSH_USER_BOB, + .password = TORTURE_SSH_USER_BOB_PASSWORD + }; + + struct ssh_channel_callbacks_struct *channel_cb = NULL; + struct ssh_server_callbacks_struct *server_cb = NULL; + + if (state == NULL) { + fprintf(stderr, "NULL server state provided\n"); + goto end; + } + + server_cb = get_default_server_cb(); + if (server_cb == NULL) { + goto end; + } + + /* + * This test was written prior to adding the kbdint callback + * for the server. Hence, here the server uses the + * ssh_message_callback for kbdint authentication, + * instead of the kbdint callback. + * + * Setting the kbdint callback as NULL ensures that the + * default kbdint callback for test_server doesn't get used + * for kbdint authentication. + * + * The test for kbdint callback based authentication has + * been added in torture_server.c, libssh keeps this test to + * test the old way of doing kbdint authentication using + * ssh_message_callback. + */ + server_cb->auth_kbdint_function = NULL; + server_cb->userdata = &sdata; + + /* This is a macro, it does not return a value */ + ssh_callbacks_init(server_cb); + + rc = ssh_set_server_callbacks(session, server_cb); + if (rc) { + goto end; + } + +#ifdef WITH_PCAP + set_pcap(&sdata, session, state->pcap_file); +#endif + + rc = ssh_handle_key_exchange(session); + if (rc != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + goto end; + } + + /* Set the supported authentication methods */ + ssh_set_auth_methods(session, SSH_AUTH_METHOD_INTERACTIVE); + + ssh_set_message_callback(session, authenticate_callback, &sdata); + + rc = ssh_event_add_session(event, session); + if (rc != 0) { + fprintf(stderr, "Error adding session to event\n"); + goto end; + } + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= state->max_tries || n >= 100) { + goto end; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + goto end; + } + n++; + } + + channel_cb = get_default_channel_cb(); + if (channel_cb == NULL) { + goto end; + } + + channel_cb->userdata = &cdata; + + ssh_callbacks_init(channel_cb); + rc = ssh_set_channel_callbacks(sdata.channel, channel_cb); + if (rc != 0) { + goto end; + } + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + rc = ssh_event_dopoll(event, -1); + if (rc == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while(ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } + +end: +#ifdef WITH_PCAP + cleanup_pcap(&sdata); +#endif + if (channel_cb != NULL) { + free(channel_cb); + } + if (server_cb != NULL) { + free(server_cb); + } + return; +} + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static int setup_kbdint_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + char rsa_hostkey[1024] = {0}; + + char sshd_path[1024]; + char log_file[1024]; + + int rc; + + char pid_str[1024]; + + pid_t pid; + + assert_non_null(state); + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + + /* Create the server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->host_key = strdup(rsa_hostkey); + assert_non_null(rsa_hostkey); + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + ss->max_tries = 3; + ss->error = 0; + + tss->state = s; + tss->ss = ss; + + /* Set the session handling function */ + ss->handle_session = handle_kbdint_session_cb; + assert_non_null(ss->handle_session); + + /* Start the server */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + rc = torture_wait_for_daemon(15); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int teardown_kbdint_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_server_auth_kbdint(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int nprompts = 0; + int rc; + const char *prompt = NULL; + char echo; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + nprompts = ssh_userauth_kbdint_getnprompts(session); + assert_int_equal(nprompts, 2); + + prompt = ssh_userauth_kbdint_getprompt(NULL, 0, &echo); + assert_null(prompt); + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + assert_string_equal(prompt, "username: "); + assert_int_equal(echo, 1); + prompt = ssh_userauth_kbdint_getprompt(session, 1, &echo); + assert_string_equal(prompt, "password: "); + assert_int_equal(echo, 0); + prompt = ssh_userauth_kbdint_getprompt(session, 2, &echo); + assert_null(prompt); + + /* Reply the first 2 prompts using the username and password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, + TORTURE_SSH_USER_BOB); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, + TORTURE_SSH_USER_BOB_PASSWORD); + assert_false(rc < 0); + + /* Resend the password */ + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + nprompts = ssh_userauth_kbdint_getnprompts(session); + assert_int_equal(nprompts, 1); + + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + assert_string_equal(prompt, "retype password: "); + assert_int_equal(echo, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 0, + TORTURE_SSH_USER_BOB_PASSWORD); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + + /* Sometimes, SSH server send an empty query at the end of exchange */ + if (rc == SSH_AUTH_INFO) { + nprompts = ssh_userauth_kbdint_getnprompts(session); + assert_int_equal(nprompts, 0); + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + assert_null(prompt); + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_auth_kbdint, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_kbdint_server, + teardown_kbdint_server); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_server_config.c b/src/libs/libssh-0.12.2/tests/server/torture_server_config.c new file mode 100644 index 000000000000..c3c5f3a5b42c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_server_config.c @@ -0,0 +1,805 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/token.h" + +#include "test_server.h" +#include "default_cb.h" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + char *cwd; + char *temp_dir; + char ed25519_hostkey[1024]; + char rsa_hostkey[1024]; + char ecdsa_521_hostkey[1024]; + char ecdsa_384_hostkey[1024]; + char ecdsa_256_hostkey[1024]; +}; + +static int setup_files(void **state) +{ + struct test_server_st *tss; + struct torture_state *s; + char sshd_path[1024]; + + int rc; + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(tss->rsa_hostkey, + sizeof(tss->rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(tss->rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(tss->ecdsa_521_hostkey, + sizeof(tss->ecdsa_521_hostkey), + "%s/sshd/ssh_host_ecdsa_521_key", + s->socket_dir); + torture_write_file(tss->ecdsa_521_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + snprintf(tss->ecdsa_384_hostkey, + sizeof(tss->ecdsa_384_hostkey), + "%s/sshd/ssh_host_ecdsa_384_key", + s->socket_dir); + torture_write_file(tss->ecdsa_384_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P384, 0)); + + snprintf(tss->ecdsa_256_hostkey, + sizeof(tss->ecdsa_256_hostkey), + "%s/sshd/ssh_host_ecdsa_256_key", + s->socket_dir); + torture_write_file(tss->ecdsa_256_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P256, 0)); + + if (!ssh_fips_mode()) { + snprintf(tss->ed25519_hostkey, + sizeof(tss->ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(tss->ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + } + + tss->state = s; + *state = tss; + + return 0; +} + +static int teardown_files(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + torture_teardown_socket_dir((void **)&s); + SAFE_FREE(tss); + + return 0; +} + +static int setup_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + char *cwd = NULL; + char *tmp_dir = NULL; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + return 0; +} + +static int teardown_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + int rc; + + assert_non_null(tss); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static int start_server(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + /* Start the server using the default values */ + torture_setup_libssh_server((void **)&s, "./test_server/test_server"); + assert_non_null(s); + + return 0; +} + +static int stop_server(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + int rc; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + rc = torture_terminate_process(s->srv_pidfile); + assert_return_code(rc, errno); + + unlink(s->srv_pidfile); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + const char *compat_hostkeys = ssh_get_supported_methods(SSH_HOSTKEYS); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOSTKEYS, compat_hostkeys); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int try_config_content(void **state, const char *config_content, + bool parse_global) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc; + + ssh_session session; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + assert_non_null(s->srv_config); + + if (parse_global) { + fprintf(stderr, "Using system-wide configuration\n"); + } else { + /* The string is duplicated to not break the cleanup on error */ + s->srv_additional_config = strdup("-g"); + } + + torture_write_file(s->srv_config, config_content); + + fprintf(stderr, "Config file %s content: \n\n%s\n", s->srv_config, + config_content); + + rc = start_server(state); + assert_int_equal(rc, 0); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + /* Authenticate as alice with bob */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); + + SAFE_FREE(s->srv_additional_config); + + return 0; +} + +static char *hostkey_files[6] = {0}; + +static size_t setup_hostkey_files(struct test_server_st *tss) +{ + size_t num_hostkey_files = 1; + + hostkey_files[0] = tss->rsa_hostkey; + +#ifdef TEST_ALL_CRYPTO_COMBINATIONS + hostkey_files[1] = tss->ecdsa_256_hostkey; + hostkey_files[2] = tss->ecdsa_384_hostkey; + hostkey_files[3] = tss->ecdsa_521_hostkey; + + num_hostkey_files = 4; + + if (!ssh_fips_mode()) { + hostkey_files[4] = tss->ed25519_hostkey; + num_hostkey_files++; + } +#endif /* TEST_ALL_CRYPTO_COMBINATIONS */ + + return num_hostkey_files; +} + +static void torture_server_config_hostkey(void **state) +{ + struct test_server_st *tss = *state; + size_t i, num_hostkey_files; + char config_content[4096]; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + for (i = 0; i < num_hostkey_files; i++) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\n", + hostkey_files[i]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } +} + +static void torture_server_config_ciphers(void **state) +{ + struct test_server_st *tss = *state; + size_t i, j, num_hostkey_files = 1; + char config_content[4096]; + + const char *ciphers; + + struct ssh_tokens_st *tokens; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_S_C); + assert_non_null(ciphers); + } else { + ciphers = ssh_kex_get_default_methods(SSH_CRYPT_S_C); + assert_non_null(ciphers); + } + + tokens = ssh_tokenize(ciphers, ','); + assert_non_null(tokens); + + for (i = 0; i < num_hostkey_files; i++) { + /* Try setting all default algorithms */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\n", + hostkey_files[i], ciphers); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\n", + hostkey_files[i], tokens->tokens[j]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + j++; + } + } + + ssh_tokens_free(tokens); +} + +static void torture_server_config_macs(void **state) +{ + struct test_server_st *tss = *state; + size_t i, j, num_hostkey_files = 1; + char config_content[4096]; + + const char *macs; + + struct ssh_tokens_st *tokens; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + macs = ssh_kex_get_fips_methods(SSH_MAC_S_C); + assert_non_null(macs); + } else { + macs = ssh_kex_get_default_methods(SSH_MAC_S_C); + assert_non_null(macs); + } + + tokens = ssh_tokenize(macs, ','); + assert_non_null(tokens); + + for (i = 0; i < num_hostkey_files; i++) { + /* Try setting all default algorithms */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nMACs %s\n", + hostkey_files[i], macs); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nMACs %s\n", + hostkey_files[i], tokens->tokens[j]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + j++; + } + } + + ssh_tokens_free(tokens); +} + +static void torture_server_config_kex(void **state) +{ + struct test_server_st *tss = *state; + size_t i, j, num_hostkey_files = 1; + char config_content[4096]; + + const char *kex; + + struct ssh_tokens_st *tokens; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + kex = ssh_kex_get_fips_methods(SSH_KEX); + assert_non_null(kex); + } else { + kex = ssh_kex_get_default_methods(SSH_KEX); + assert_non_null(kex); + } + + tokens = ssh_tokenize(kex, ','); + assert_non_null(tokens); + + for (i = 0; i < num_hostkey_files; i++) { + /* Try setting all default algorithms */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nKexAlgorithms %s\n", + hostkey_files[i], kex); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nKexAlgorithms %s\n", + hostkey_files[i], tokens->tokens[j]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + j++; + } + } + + ssh_tokens_free(tokens); +} + +static void torture_server_config_hostkey_algorithms(void **state) +{ + struct test_server_st *tss = *state; + size_t i, num_hostkey_files = 5; + char config_content[4096]; + + const char *allowed; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + allowed = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + assert_non_null(allowed); + } else { + allowed = ssh_kex_get_default_methods(SSH_HOSTKEYS); + assert_non_null(allowed); + } + + for (i = 0; i < num_hostkey_files; i++) { + /* Should work with all allowed */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostKeyAlgorithms %s\n", + hostkey_files[i], allowed); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } + + /* Should work with matching hostkey and allowed algorithm */ + + if (!ssh_fips_mode()) { + /* ed25519 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ed25519_hostkey, "ssh-ed25519"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ssh-rsa */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, "ssh-rsa"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } + + /* rsa-sha2-256 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, "rsa-sha2-256"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ssh-sha2-512 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, "rsa-sha2-512"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ecdsa-sha2-nistp256 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ecdsa_256_hostkey, "ecdsa-sha2-nistp256"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ecdsa-sha2-nistp384 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ecdsa_384_hostkey, "ecdsa-sha2-nistp384"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ecdsa-sha2-nistp521 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ecdsa_521_hostkey, "ecdsa-sha2-nistp521"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + +} + +static void torture_server_config_unknown(void **state) +{ + struct test_server_st *tss = *state; + char config_content[4096]; + + int rc; + + assert_non_null(tss); + assert_non_null(tss->rsa_hostkey); + + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nUnknownOption unknown-value1,unknown-value2\n", + tss->rsa_hostkey); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); +} + +/* + * Check that the server returns the correct signature when the negotiated host + * key is RSA but the signature algorithm is not the server's preferred + * algorithm (e.g. when the client prefers ssh-rsa over rsa-sha2-256 or + * rsa-sha2-512). + * + * Related: T191, T240 + */ +static void torture_server_config_rsa_hostkey_order(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + char config_content[4096]; + size_t num_hostkey_files; + const char *allowed = NULL; + + ssh_session session = NULL; + + int rc; + + assert_non_null(tss); + s = tss->state; + assert_non_null(s); + + /* Prepare key files */ + num_hostkey_files = setup_hostkey_files(tss); + assert_true(num_hostkey_files > 0); + + /* Create the server configuration file */ + if (ssh_fips_mode()) { + allowed = "rsa-sha2-512,rsa-sha2-256"; + } else { + allowed = "rsa-sha2-256,ssh-rsa"; + } + + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, allowed); + + assert_non_null(s->srv_config); + torture_write_file(s->srv_config, config_content); + + fprintf(stderr, "Config file %s content: \n\n%s\n", s->srv_config, + config_content); + fflush(stderr); + + /* Start server */ + rc = start_server(state); + assert_int_equal(rc, 0); + + /* Setup session */ + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Set client order of preference different from the server */ + if (ssh_fips_mode()) { + /* Set the host keys with rsa-sha2-256 before rsa-sha2-512 */ + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "rsa-sha2-256,rsa-sha2-512"); + assert_int_equal(rc, SSH_OK); + } else { + /* Set the host keys with ssh-rsa before rsa-sha2-256 */ + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "ssh-rsa,rsa-sha2-256"); + assert_int_equal(rc, SSH_OK); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_ssh_return_code(session, rc); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); + + SAFE_FREE(s->srv_additional_config); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_config_hostkey, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_ciphers, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_macs, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_kex, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_hostkey_algorithms, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_unknown, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_rsa_hostkey_order, + setup_temp_dir, teardown_temp_dir), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_files, + teardown_files); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_server_default.c b/src/libs/libssh-0.12.2/tests/server/torture_server_default.c new file mode 100644 index 000000000000..a3c3d9da56ad --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_server_default.c @@ -0,0 +1,657 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include "test_server.h" +#include "default_cb.h" + +#include "channels.c" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + char *cwd; + char *temp_dir; +}; + +static int libssh_server_setup(void **state) +{ + struct test_server_st *tss = NULL; + struct torture_state *s = NULL; + + char log_file[1024]; + + assert_non_null(state); + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + torture_setup_create_libssh_config((void **)&s); + + snprintf(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + + s->log_file = strdup(log_file); + + /* The second argument is the relative path to the "server" directory binary + */ + torture_setup_libssh_server((void **)&s, "./test_server/test_server"); + assert_non_null(s); + + tss->state = s; + + *state = tss; + + return 0; +} + +static int sshd_teardown(void **state) { + + struct test_server_st *tss = NULL; + struct torture_state *s = NULL; + + assert_non_null(state); + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + /* This function can be reused to teardown the server */ + torture_teardown_sshd_server((void **)&s); + + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + char *cwd = NULL; + char *tmp_dir = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_server_auth_none(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + char *banner = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + banner = ssh_get_issue_banner(session); + assert_string_equal(banner, SSHD_BANNER_MESSAGE); + free(banner); + banner = NULL; + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } +} + +static void torture_server_auth_password(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default user for the server */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_server_auth_pubkey(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Authenticate as alice with bob's pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_server_auth_kbdint(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing a wrong password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, SSHD_DEFAULT_USER); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, "wrongpassword"); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing a wrong username */ + rc = ssh_userauth_kbdint_setanswer(session, 0, "wrongusername"); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing the right password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, SSHD_DEFAULT_USER); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_server_hostkey_mismatch(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + char known_hosts_file[1024] = {0}; + FILE *file = NULL; + enum ssh_known_hosts_e found; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Store the testkey in the knownhosts file */ + snprintf(known_hosts_file, + sizeof(known_hosts_file), + "%s/%s", + s->socket_dir, + TORTURE_KNOWN_HOSTS_FILE); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + /* Using the default user for the server */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_ssh_return_code(session, rc); + + /* Configure the client to offer only rsa-sha2-256 hostkey algorithm */ + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Make sure we can verify the signature */ + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + +static void torture_server_unknown_global_request(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + ssh_channel channel; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Request asking for reply */ + rc = ssh_global_request(session, "unknown-request-00@test.com", NULL, 1); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* Request and don't ask for reply */ + rc = ssh_global_request(session, "another-bad-req-00@test.com", NULL, 0); + assert_ssh_return_code(session, rc); + + /* Open channel to make sure the session is still working */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + ssh_channel_close(channel); +} + +static void torture_server_unknown_channel_request(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + ssh_channel channel; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Open a channel session */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Request asking for reply */ + rc = channel_request(channel, "unknown-request-00@test.com", NULL, 1); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* Request and don't ask for reply */ + rc = channel_request(channel, "another-bad-req-00@test.com", NULL, 0); + assert_ssh_return_code(session, rc); + + ssh_channel_close(channel); + ssh_channel_free(channel); +} + +static void torture_server_no_more_sessions(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + ssh_channel channels[2]; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Open a channel session */ + channels[0] = ssh_channel_new(session); + assert_non_null(channels[0]); + + rc = ssh_channel_open_session(channels[0]); + assert_ssh_return_code(session, rc); + + /* Send no-more-sessions@openssh.com global request */ + rc = ssh_request_no_more_sessions(session); + assert_ssh_return_code(session, rc); + + /* Try to open an extra session and expect failure */ + channels[1] = ssh_channel_new(session); + assert_non_null(channels[1]); + + rc = ssh_channel_open_session(channels[1]); + assert_int_equal(rc, SSH_ERROR); + + /* Free the unused channel */ + ssh_channel_close(channels[1]); + ssh_channel_free(channels[1]); + + /* Close and free open channel */ + ssh_channel_close(channels[0]); + ssh_channel_free(channels[0]); +} + +static void torture_server_set_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + const char *message = "Goodbye"; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(session,message); + assert_ssh_return_code(session, rc); + assert_string_equal(session->disconnect_message,message); +} + +static void torture_null_server_set_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(NULL,"Goodbye"); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_server_set_null_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(session,NULL); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->disconnect_message,"Bye Bye"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_auth_none, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_password, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_pubkey, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_kbdint, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_hostkey_mismatch, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_unknown_global_request, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_unknown_channel_request, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_no_more_sessions, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_set_disconnect_message, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_null_server_set_disconnect_message, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_set_null_disconnect_message, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, libssh_server_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_sftpserver.c b/src/libs/libssh-0.12.2/tests/server/torture_sftpserver.c new file mode 100644 index 000000000000..d19ebb4f760a --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_sftpserver.c @@ -0,0 +1,1568 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/sftp.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include "libssh/buffer.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/sftp_priv.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; + char *temp_dir; +}; + +void sftp_handle_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state); + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static int setup_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + //char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + int rc; + + char pid_str[1024]; + + pid_t pid; + + assert_non_null(state); + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + tss->state = s; + tss->ss = ss; + + /* Use the default session handling function */ + ss->handle_session = sftp_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + char template2[] = "/tmp/ssh_torture_XXXXXX"; + char *cwd = NULL; + char *tmp_dir = NULL; + char *p = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + p = mkdtemp(template2); + assert_non_null(p); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = (struct torture_sftp*)calloc(1, sizeof(struct torture_sftp)); + assert_non_null(s->ssh.tsftp); + s->ssh.tsftp->testdir = strdup(p); + assert_non_null(s->ssh.tsftp->testdir); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_setup_sftp(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + ssh_session session = NULL; + sftp_session sftp = NULL; + int rc; + + assert_non_null(tss); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + ssh_get_issue_banner(session); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + SAFE_FREE(s->ssh.tsftp->testdir); + sftp_free(s->ssh.tsftp->sftp); + SAFE_FREE(s->ssh.tsftp); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_server_establish_sftp(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + ssh_session session; + sftp_session sftp; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default user for the server */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + ssh_get_issue_banner(session); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + + printf("in establish before sftp_new\n"); + sftp = sftp_new(session); + assert_non_null(sftp); + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + tsftp->sftp = sftp; +} + +static void torture_server_test_sftp_function(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + ssh_session session; + sftp_session sftp; + int rc; + char *rv_str; + sftp_dir dir; + + char data[65535] = {0}; + sftp_file source; + sftp_file to; + int read_len; + int write_len; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + /* Assert some information about the connected session */ + assert_int_equal(sftp->server_version, LIBSFTP_VERSION); + + /* symbol link */ + rc = sftp_symlink(sftp, "/tmp/this_is_the_link", "/tmp/sftp_symlink_test"); + assert_int_equal(rc, SSH_OK); + + rv_str = sftp_readlink(sftp, "/tmp/sftp_symlink_test"); + assert_non_null(rv_str); + ssh_string_free_char(rv_str); + + rc = sftp_unlink(sftp, "/tmp/sftp_symlink_test"); + assert_int_equal(rc, SSH_OK); + + /* open and close dir */ + dir = sftp_opendir(sftp, "./"); + assert_non_null(dir); + + rc = sftp_closedir(dir); + assert_int_equal(rc, SSH_OK); + + /* file read and write */ + source = sftp_open(sftp, "/usr/bin/ssh", O_RDONLY, 0); + assert_non_null(source); + + to = sftp_open(sftp, "ssh-copy", O_WRONLY | O_CREAT, 0700); + assert_non_null(to); + + read_len = sftp_read(source, data, 4096); + write_len = sftp_write(to, data, read_len); + assert_int_equal(write_len, read_len); + + rc = sftp_close(source); + assert_int_equal(rc, SSH_OK); + + rc = sftp_close(to); + assert_int_equal(rc, SSH_OK); + + rc = sftp_unlink(sftp, "ssh-copy"); + assert_int_equal(rc, SSH_OK); +} + +static void torture_server_sftp_init_repeat(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + ssh_session session = NULL; + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_packet packet = NULL; + uint32_t version; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + /* send one version N-1 */ + rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION - 1); + assert_int_equal(rc, SSH_OK); + rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); + SSH_BUFFER_FREE(buffer); + assert_int_equal(rc, 9); + + packet = sftp_packet_read(sftp); + assert_non_null(packet); + assert_int_equal(packet->type, SSH_FXP_VERSION); + + /* Make sure we get the expected version N-1 */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + assert_int_equal(rc, SSH_OK); + assert_int_equal(version, LIBSFTP_VERSION - 1); + + /* Repeated INIT will fail on server */ + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_server_sftp_open_read_write(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + sftp_session sftp = NULL; + ssh_session session = NULL; + sftp_attributes a = NULL; + sftp_file new_file = NULL; + char tmp_file[PATH_MAX] = {0}; + char data[10] = "0123456789"; + char read_data[10] = {0}; + struct stat sb; + int rc, write_len, read_len; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", tss->temp_dir); + + /* + * Create a new file + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0751); + assert_non_null(new_file); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* Verify locally the mode is correct */ + rc = stat(tmp_file, &sb); + assert_int_equal(rc, 0); + assert_int_equal(sb.st_mode, S_IFREG | 0751); + assert_int_equal(sb.st_size, sizeof(data)); /* 10b written */ + + /* Remote stat */ + a = sftp_stat(sftp, tmp_file); + assert_non_null(a); + assert_int_equal(a->permissions, S_IFREG | 0751); + assert_int_equal(a->size, sizeof(data)); /* 10b written */ + assert_int_equal(a->type, SSH_FILEXFER_TYPE_REGULAR); + sftp_attributes_free(a); + + /* + * Now that file exists and contains some data, lets try O_TRUNC, + * mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_TRUNC, 0); + assert_non_null(new_file); + + /* Verify that the existing data in the file has been truncated */ + a = sftp_stat(sftp, tmp_file); + assert_non_null(a); + assert_int_equal(a->size, 0); /* No content due to truncation */ + sftp_attributes_free(a); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* + * Now, lets try O_APPEND, mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_APPEND, 0); + assert_non_null(new_file); + + /* fstat is not implemented */ + a = sftp_fstat(new_file); + assert_null(a); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* + * Now, lets try read+write, mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_RDWR, 0); + assert_non_null(new_file); + + /* Reading should work */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, sizeof(read_data)); + assert_int_equal(sizeof(read_data), sizeof(data)); /* sanity */ + assert_memory_equal(read_data, data, sizeof(data)); + + rc = sftp_seek(new_file, 20); + assert_ssh_return_code(session, rc); + + /* Write should work also ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* Remove the file */ + rc = sftp_unlink(sftp, tmp_file); + assert_ssh_return_code(session, rc); + + /* again: the file does not exist anymore so we should fail now */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_ERROR); + + /* + * Now, lets try read+write+create + */ + new_file = sftp_open(sftp, tmp_file, O_RDWR | O_CREAT, 0700); + assert_non_null(new_file); + + /* Reading should not fail but return no data */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, 0); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* be nice */ + rc = sftp_unlink(sftp, tmp_file); + assert_ssh_return_code(session, rc); + + /* null flags should be invalid */ + /* but there is no way in libssh client to force null flags so skip this + new_file = sftp_open(sftp, tmp_file, 0, 0700); + assert_null(new_file); + */ + + /* Only O_CREAT is invalid on file which does not exist. Read is implicit */ + new_file = sftp_open(sftp, tmp_file, O_CREAT, 0700); + assert_null(new_file); +} + +static void torture_server_sftp_mkdir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + sftp_attributes a = NULL; + struct stat sb; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + + /* create a test dir */ + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* try the same path again -- we should get an error */ + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_int_equal(rc, SSH_ERROR); + + /* Verify locally the mode is correct */ + rc = stat(tmp_dir, &sb); + assert_int_equal(rc, 0); + assert_int_equal(sb.st_mode, S_IFDIR | 0751); + + /* Remote stat */ + a = sftp_stat(sftp, tmp_dir); + assert_non_null(a); + assert_int_equal(a->permissions, S_IFDIR | 0751); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_DIRECTORY); + sftp_attributes_free(a); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newdir/newfile", tss->temp_dir); + + /* create a file in there */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* remove of non-empty directory fails */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_ERROR); + + /* Unlink can not remove directory either */ + rc = sftp_unlink(sftp, tmp_dir); + assert_int_equal(rc, SSH_ERROR); + + /* Remove the file */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + + /* Now it should work */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_ssh_return_code(session, rc); +} + +static void torture_server_sftp_realpath(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + char path[PATH_MAX] = {0}; + char exp_path[PATH_MAX] = {0}; + char *new_path = NULL; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* first try with the empty string, which should be equivalent to CWD */ + new_path = sftp_canonicalize_path(sftp, path); + assert_non_null(new_path); + assert_string_equal(new_path, tss->cwd); + ssh_string_free_char(new_path); + + /* now, lets try some more complicated paths relative to the CWD */ + snprintf(path, sizeof(path), "%s/.././%s", + tss->temp_dir, tss->temp_dir); + new_path = sftp_canonicalize_path(sftp, path); + assert_non_null(new_path); + snprintf(exp_path, sizeof(exp_path), "%s/%s", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, exp_path); + ssh_string_free_char(new_path); + + /* and this one does not exists, which is an error */ + snprintf(path, sizeof(path), "%s/.././%s/nodir", + tss->temp_dir, tss->temp_dir); + new_path = sftp_canonicalize_path(sftp, path); + assert_null(new_path); + ssh_string_free_char(new_path); +} + +static void torture_server_sftp_symlink(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + char path[PATH_MAX] = {0}; + char abs_path[PATH_MAX] = {0}; + char data[42] = "012345678901234567890123456789012345678901"; + char *new_path = NULL; + sftp_attributes a = NULL; + sftp_dir dir; + int write_len, num_files = 0; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* create a test dir */ + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* create a file in there */ + snprintf(tmp_file, sizeof(tmp_file), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* now, lets create a (relative) symlink to the new file */ + snprintf(path, sizeof(path), "%s/newdir/linkname", tss->temp_dir); + rc = sftp_symlink(sftp, tmp_file, path); + assert_ssh_return_code(session, rc); + + /* when the destination exists, it should fail */ + rc = sftp_symlink(sftp, tmp_dir, tmp_file); + assert_int_equal(rc, SSH_ERROR); + + /* now, there are different versions of stat that follow symlinks or not */ + /* lstat should not follow the symlink and show information about the link + * itself */ + a = sftp_lstat(sftp, path); + assert_non_null(a); + assert_int_not_equal(a->size, sizeof(data)); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_SYMLINK); + sftp_attributes_free(a); + + /* readlink should give us more information about the target of the symlink + */ + new_path = sftp_readlink(sftp, path); + assert_non_null(new_path); + snprintf(abs_path, sizeof(abs_path), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, abs_path); + ssh_string_free_char(new_path); + + /* stat should follow the symlink and show information about the link + * target */ + a = sftp_stat(sftp, path); + assert_non_null(a); + assert_int_equal(a->size, sizeof(data)); + assert_int_equal(a->permissions, S_IFREG | 0700); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_REGULAR); + sftp_attributes_free(a); + + /* on non-existing path, they fail */ + a = sftp_lstat(sftp, "non-existing"); + assert_null(a); + a = sftp_stat(sftp, "non-existing"); + assert_null(a); + + /**** readdir ****/ + dir = sftp_opendir(sftp, tmp_dir); + assert_non_null(dir); + while ((a = sftp_readdir(sftp, dir))) { + if (strcmp(a->name, ".") != 0 && + strcmp(a->name, "..") != 0 && + strcmp(a->name, "newfile") != 0 && + strcmp(a->name, "linkname") != 0) { + /* There is a file we did not create */ + assert_true(false); + } + + num_files++; + sftp_attributes_free(a); + } + assert_int_equal(num_files, 4); + rc = sftp_dir_eof(dir); + assert_int_equal(rc, 1); + rc = sftp_closedir(dir); + assert_ssh_return_code(session, rc); + + /* now, remove the target of the link, the stat should not handle that, + * while lstat should keep working */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + + a = sftp_lstat(sftp, path); + assert_non_null(a); + assert_int_not_equal(a->size, sizeof(data)); + sftp_attributes_free(a); + + a = sftp_stat(sftp, path); + assert_null(a); + + /* readlink works ok on broken symlinks */ + new_path = sftp_readlink(sftp, path); + assert_non_null(new_path); + snprintf(abs_path, sizeof(abs_path), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, abs_path); + ssh_string_free_char(new_path); + + /* readlink should fail on directories */ + new_path = sftp_readlink(sftp, tmp_dir); + assert_null(new_path); + /* readlink should fail on or on non-existing files */ + new_path = sftp_readlink(sftp, tmp_file); + assert_null(new_path); + + /* Clean up symlink */ + rc = sftp_unlink(sftp, path); + assert_int_equal(rc, SSH_OK); + /* Clean up temporary directory */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_OK); +} + +static void torture_server_sftp_extended(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + sftp_statvfs_t st = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* create a test dir */ + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* create a file in there */ + snprintf(tmp_file, sizeof(tmp_file), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + + /* extended fstatvsf is not advertised nor supported now but calling this + * message will keep hanging the server. The extension protocol says that + * the clients can not request extension that are not supported by the + * server so before doing this, we should use sftp_extension_supported() + * anyway */ + /* st = sftp_fstatvfs(new_file); + assert_null(st); */ + + /* close */ + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* extended statvsf */ + st = sftp_statvfs(sftp, tmp_file); + assert_non_null(st); + /* probably hard to check more */ + sftp_statvfs_free(st); + + /* Clean up temporary directory */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_OK); +} + +static void +torture_server_sftp_setstat(void **state) +{ + + char name[128] = {0}; + char data[10] = "0123456789"; + int rc; + size_t len; + int atime = 10676, mtime = 13467; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + + struct passwd *pwd = NULL; + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + struct sftp_attributes_struct attr; + sftp_attributes tmp_attr = NULL; + + sftp_session sftp = NULL; + ssh_session session = NULL; + sftp_file new_file = NULL; + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + assert_non_null(tsftp->testdir); + snprintf(name, sizeof(name), "%s/server_setstat_test", tsftp->testdir); + new_file = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(len, sizeof(data)); + rc = sftp_close(new_file); + assert_int_equal(rc, SSH_OK); + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS | + SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_ACMODTIME; + + attr.size = len; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.permissions = mode; + attr.atime = atime; + attr.mtime = mtime; + + rc = sftp_setstat(sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(rc, SSH_OK); + + tmp_attr = sftp_stat(sftp, name); + assert_non_null(tmp_attr); + + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + + assert_int_equal(len, tmp_attr->size); + assert_int_equal(tmp_attr->permissions & ACCESSPERMS, mode); + assert_int_equal(tmp_attr->mtime, mtime); + assert_int_equal(tmp_attr->atime, atime); + + /*negative tests*/ + rc = sftp_setstat(sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + sftp_unlink(sftp, name); + sftp_attributes_free(tmp_attr); +} + +static void +torture_server_sftp_readdir(void **state) +{ + + char name[128] = {0}; + char data[10] = "0123456789"; + int rc; + size_t len; + int atime = 10676, mtime = 13467; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + int num_files = 0; + sftp_dir dir; + sftp_attributes a = NULL; + + struct passwd *pwd = NULL; + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + struct sftp_attributes_struct attr; + + sftp_session sftp = NULL; + ssh_session session = NULL; + sftp_file new_file = NULL; + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + assert_non_null(tsftp->testdir); + snprintf(name, sizeof(name), "%s/server_setstat_test", tsftp->testdir); + new_file = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(len, sizeof(data)); + rc = sftp_close(new_file); + assert_int_equal(rc, SSH_OK); + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS | + SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_ACMODTIME; + + attr.size = len; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.permissions = mode; + attr.atime = atime; + attr.mtime = mtime; + + rc = sftp_setstat(sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + dir = sftp_opendir(sftp, tsftp->testdir); + assert_non_null(dir); + while ((a = sftp_readdir(sftp, dir))) { + if (strcmp(a->name, "server_setstat_test") == 0) { + /* verify long name is in the expected format */ + assert_string_equal(a->longname, + "-rw-r----- 1 5001 9000 10 Jan 1 03:44:27 1970 server_setstat_test"); + } else if (strcmp(a->name, ".") != 0 && + strcmp(a->name, "..") != 0) { + /* There is a file we did not create */ + assert_true(false); + } + + num_files++; + sftp_attributes_free(a); + } + assert_int_equal(num_files, 3); + rc = sftp_dir_eof(dir); + assert_int_equal(rc, 1); + rc = sftp_closedir(dir); + assert_ssh_return_code(session, rc); +} + + +/* The max number of handles is 256 in sftpserver.h -- keep in sync! */ +#define SFTP_HANDLES 256 +static void torture_server_sftp_handles_exhaustion(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_file handle, handles[SFTP_HANDLES] = {0}; + sftp_session sftp = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Occupy all handles */ + for (int i = 0; i < SFTP_HANDLES; i++) { + snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i); + handles[i] = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handles[i]); + } + + /* Next handle should fail, but not crash or OOB */ + snprintf(name, sizeof(name), "%s/failfn", tsftp->testdir); + handle = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_null(handle); + + /* cleanup */ + for (int i = 0; i < SFTP_HANDLES; i++) { + snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i); + rc = sftp_close(handles[i]); + assert_int_equal(rc, SSH_OK); + } +} + +static void torture_server_sftp_opendir_handles_exhaustion(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_file handles[SFTP_HANDLES] = {0}; + sftp_dir dir = NULL; + sftp_session sftp = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Occupy all handles with files */ + for (int i = 0; i < SFTP_HANDLES; i++) { + snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i); + handles[i] = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handles[i]); + } + + /* Opening a directory should fail gracefully without leaking h->name */ + dir = sftp_opendir(sftp, tsftp->testdir); + assert_null(dir); + + /* cleanup */ + for (int i = 0; i < SFTP_HANDLES; i++) { + rc = sftp_close(handles[i]); + assert_int_equal(rc, SSH_OK); + } +} + +static void torture_server_sftp_handle_overrun(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_session sftp = NULL; + sftp_file handle = NULL; + ssh_buffer buffer = NULL; + uint32_t id, bad_handle = SFTP_HANDLES, bad_handle_len = 4; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Initialize the sftp handles by opening first file */ + snprintf(name, sizeof(name), "%s/file", tsftp->testdir); + handle = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handle); + + rc = sftp_close(handle); + assert_int_equal(rc, SSH_OK); + + /* Craft an malicious SFTP packet trying to access handle 256 + * (SFTP_HANDLES) */ + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + rc = sftp_get_new_id(sftp, &id); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_pack(buffer, + "ddPqd", + id, + (uint32_t)bad_handle_len, + (size_t)bad_handle_len, + &bad_handle, /* a 32b int as ssh_string */ + (uint64_t)0, + (uint32_t)1024); + assert_int_equal(rc, SSH_OK); + rc = sftp_packet_write(sftp, SSH_FXP_READ, buffer); + SSH_BUFFER_FREE(buffer); + assert_int_equal(rc, 29); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + assert_int_equal(rc, SSH_OK); + assert_int_equal(msg->packet_type, SSH_FXP_STATUS); + status = parse_status_msg(msg); + sftp_message_free(msg); + assert_int_equal(status->status, SSH_FX_INVALID_HANDLE); + status_msg_free(status); +} + +static void torture_server_sftp_payload_overrun(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_session sftp = NULL; + sftp_file handle = NULL; + ssh_buffer buffer = NULL; + uint32_t id, bad_payload_len = 0x7ffffffc; + int rc; + +#ifdef HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + /* This malformed message does not crash the server, but keeps waiting + * for more data as announced in the payloiad length so the opened FD on + * the server side is leaking when the server terminates. + * Given that the custom sftp server could store anything into the + * handles, it should take care of cleaning up the outstanding handles, + * but this is something to solve in the future. Now just skipping the + * test. + */ + skip(); + } +#endif /* HAVE_VALGRIND_VALGRIND_H */ + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Open a file for writing */ + snprintf(name, sizeof(name), "%s/file", tsftp->testdir); + handle = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handle); + + /* Craft an malicious SFTP packet trying to write to the file with + * payload_length overrun */ + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + rc = sftp_get_new_id(sftp, &id); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_pack(buffer, + "dbdSqd", + bad_payload_len, + SSH_FXP_WRITE, + id, + handle->handle, + (uint64_t)0, + (uint32_t)0); + assert_int_equal(rc, SSH_OK); + rc = ssh_channel_write(sftp->channel, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + assert_int_equal(rc, 29); + SSH_BUFFER_FREE(buffer); + + /* We do not get answer for this malformed packet -- just kill the + * connection */ + ssh_string_free(handle->handle); + free(handle); +} + +static void torture_server_sftp_after_channel_close(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char tmp_file[PATH_MAX] = {0}; + sftp_session sftp = NULL; + sftp_file handle = NULL; + struct stat sb; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", tss->temp_dir); + + /* Close the channel */ + ssh_channel_close(sftp->channel); + /* Reset the flags so the channel looks open for the caller so we do not + * have to reimplement sending the message in the test */ + sftp->channel->local_eof = 0; + sftp->channel->state = SSH_CHANNEL_STATE_OPEN; + sftp->channel->flags &= ~SSH_CHANNEL_FLAG_CLOSED_LOCAL; + + /* Create a new file */ + handle = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0751); + assert_null(handle); + + /* Should not be created */ + rc = stat(tmp_file, &sb); + assert_int_equal(rc, -1); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_establish_sftp, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_test_sftp_function, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_init_repeat, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_open_read_write, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_mkdir, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_realpath, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_symlink, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_extended, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_setstat, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_readdir, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_handles_exhaustion, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_opendir_handles_exhaustion, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_handle_overrun, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_payload_overrun, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_after_channel_close, + session_setup_sftp, + session_teardown), + }; + + setenv("TZ", "UTC", 1); + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/ssh_ping.c b/src/libs/libssh-0.12.2/tests/ssh_ping.c new file mode 100644 index 000000000000..31bb64b45417 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/ssh_ping.c @@ -0,0 +1,108 @@ +/* ssh_ping.c */ +/* +Copyright 2018 Red Hat, Inc + +Author: Jakub Jelen + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *banner = NULL; + ssh_session session = NULL; + const char *hostkeys = NULL; + const char *kex = NULL; + int rc = 1; +#ifdef WITH_GSSAPI + bool t = true; +#endif /* WITH_GSSAPI */ + + bool process_config = false; + + if (argc < 1 || argv[1] == NULL) { + fprintf(stderr, "Error: Need an argument (hostname)\n"); + goto out; + } + + ssh_init(); + + session = ssh_new(); + if (session == NULL) { + goto out; + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, argv[1]); + if (rc < 0) { + goto out; + } + + /* The automatic username is not available under uid wrapper */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, "ping"); + if (rc < 0) { + goto out; + } + + /* Ignore system-wide configurations when simply trying to reach host */ + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + if (rc < 0) { + goto out; + } + + /* Enable all supported algorithms */ + hostkeys = ssh_get_supported_methods(SSH_HOSTKEYS); + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkeys); + if (rc < 0) { + goto out; + } + + /* Enable all supported kex algorithms */ + kex = ssh_get_supported_methods(SSH_KEX); + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + if (rc < 0) { + goto out; + } + +#ifdef WITH_GSSAPI + rc = ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + if (rc < 0) { + goto out; + } +#endif /* WITH_GSSAPI */ + + rc = ssh_connect(session); + if (rc != SSH_OK) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + goto out; + } + + banner = ssh_get_serverbanner(session); + if (banner == NULL) { + fprintf(stderr, "Did not receive SSH banner\n"); + goto out; + } + + printf("OK: %s\n", banner); + rc = 0; + +out: + ssh_free(session); + ssh_finalize(); + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/suppressions/lsan.supp b/src/libs/libssh-0.12.2/tests/suppressions/lsan.supp new file mode 100644 index 000000000000..dc95dfa5b1d8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/suppressions/lsan.supp @@ -0,0 +1,6 @@ +leak:libcrypto.so + +## sk-dummy.so +# The sk-dummy.so enroll function allocates 1-byte memory for the signature, but marks the signature length as 0. +# Since, we use burn_free to free the signature, it skips the freeing because the size is 0, which results in a memory leak. +leak:sk-dummy.so diff --git a/src/libs/libssh-0.12.2/tests/test_socket.c b/src/libs/libssh-0.12.2/tests/test_socket.c new file mode 100644 index 000000000000..84f7b35e8012 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/test_socket.c @@ -0,0 +1,93 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* Simple test for the socket callbacks */ + +#include +#include +#include +#include + +#include +#include +#include + +int stop=0; +ssh_socket s; + +static int data_rcv(const void *data, size_t len, void *user){ + printf("Received data: '"); + fwrite(data,1,len,stdout); + printf("'\n"); + ssh_socket_write(s,"Hello you !\n",12); + ssh_socket_nonblocking_flush(s); + return len; +} + +static void controlflow(int code,void *user){ + printf("Control flow: %x\n",code); +} + +static void exception(int code, int errno_code,void *user){ + printf("Exception: %d (%d)\n",code,errno_code); + stop=1; +} + +static void connected(int code, int errno_code,void *user){ + if(code == SSH_SOCKET_CONNECTED_OK) + printf("Connected: %d (%d)\n",code, errno_code); + else { + printf("Error while connecting:(%d, %d:%s)\n",code,errno_code,strerror(errno_code)); + stop=1; + } +} + +struct ssh_socket_callbacks_struct callbacks={ + data_rcv, + controlflow, + exception, + connected, + NULL +}; +int main(int argc, char **argv){ + ssh_session session; + ssh_poll_ctx ctx; + int verbosity=SSH_LOG_FUNCTIONS; + if(argc < 3){ + printf("Usage : %s host port\n", argv[0]); + return EXIT_FAILURE; + } + session=ssh_new(); + ssh_options_set(session,SSH_OPTIONS_LOG_VERBOSITY,&verbosity); + ssh_init(); + s=ssh_socket_new(session); + ctx=ssh_poll_ctx_new(2); + ssh_socket_set_callbacks(s, &callbacks); + ssh_poll_ctx_add_socket(ctx,s); + if(ssh_socket_connect(s,argv[1],atoi(argv[2]),NULL) != SSH_OK){ + printf("ssh_socket_connect: %s\n",ssh_get_error(session)); + return EXIT_FAILURE; + } + while(!stop) + ssh_poll_ctx_dopoll(ctx,-1); + printf("finished\n"); + return EXIT_SUCCESS; +} diff --git a/src/libs/libssh-0.12.2/tests/tests_config.h.cmake b/src/libs/libssh-0.12.2/tests/tests_config.h.cmake new file mode 100644 index 000000000000..f448df634e2e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/tests_config.h.cmake @@ -0,0 +1,88 @@ +/* OpenSSH capabilities */ + +#cmakedefine OPENSSH_VERSION_MAJOR ${OPENSSH_VERSION_MAJOR} +#cmakedefine OPENSSH_VERSION_MINOR ${OPENSSH_VERSION_MINOR} +#cmakedefine OPENSSH_SUPPORTS_SSHSIG ${OPENSSH_SUPPORTS_SSHSIG} + +#cmakedefine OPENSSH_CIPHERS "${OPENSSH_CIPHERS}" +#cmakedefine OPENSSH_MACS "${OPENSSH_MACS}" +#cmakedefine OPENSSH_KEX "${OPENSSH_KEX}" +#cmakedefine OPENSSH_KEYS "${OPENSSH_KEYS}" + + +#cmakedefine OPENSSH_3DES_CBC 1 +#cmakedefine OPENSSH_AES128_CBC 1 +#cmakedefine OPENSSH_AES192_CBC 1 +#cmakedefine OPENSSH_AES256_CBC 1 +#cmakedefine OPENSSH_RIJNDAEL_CBC_LYSATOR_LIU_SE 1 +#cmakedefine OPENSSH_AES128_CTR 1 +#cmakedefine OPENSSH_AES192_CTR 1 +#cmakedefine OPENSSH_AES256_CTR 1 +#cmakedefine OPENSSH_AES128_GCM_OPENSSH_COM 1 +#cmakedefine OPENSSH_AES256_GCM_OPENSSH_COM 1 +#cmakedefine OPENSSH_CHACHA20_POLY1305_OPENSSH_COM 1 +#cmakedefine OPENSSH_BLOWFISH_CBC 1 +#cmakedefine OPENSSH_HMAC_SHA1 1 +#cmakedefine OPENSSH_HMAC_SHA1_96 1 +#cmakedefine OPENSSH_HMAC_SHA2_256 1 +#cmakedefine OPENSSH_HMAC_SHA2_512 1 +#cmakedefine OPENSSH_HMAC_MD5 1 +#cmakedefine OPENSSH_HMAC_MD5_96 1 +#cmakedefine OPENSSH_UMAC_64_OPENSSH_COM 1 +#cmakedefine OPENSSH_UMAC_128_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_SHA1_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_SHA1_96_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_SHA2_256_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_SHA2_512_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_MD5_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_MD5_96_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_UMAC_64_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_UMAC_128_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP1_SHA1 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP14_SHA1 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP14_SHA256 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP16_SHA512 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP18_SHA512 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP_EXCHANGE_SHA1 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP_EXCHANGE_SHA256 1 +#cmakedefine OPENSSH_ECDH_SHA2_NISTP256 1 +#cmakedefine OPENSSH_ECDH_SHA2_NISTP384 1 +#cmakedefine OPENSSH_ECDH_SHA2_NISTP521 1 +#cmakedefine OPENSSH_CURVE25519_SHA256 1 +#cmakedefine OPENSSH_CURVE25519_SHA256_LIBSSH_ORG 1 +#cmakedefine OPENSSH_SNTRUP761X25519_SHA512 1 +#cmakedefine OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM 1 +#cmakedefine OPENSSH_MLKEM768X25519_SHA256 1 +#cmakedefine OPENSSH_MLKEM768NISTP256_SHA256 1 +#cmakedefine OPENSSH_MLKEM1024NISTP384_SHA384 1 +#cmakedefine OPENSSH_SSH_ED25519 1 +#cmakedefine OPENSSH_SSH_ED25519_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_SSH_RSA 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP256 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP384 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP521 1 +#cmakedefine OPENSSH_SSH_RSA_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP256_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP384_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP521_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_SSH_ED25519_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_SSH_ED25519_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_ECDSA_SHA2_NISTP256_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_ECDSA_SHA2_NISTP256_CERT_V01_OPENSSH_COM 1 + +/* Available programs */ + +#cmakedefine NCAT_EXECUTABLE "${NCAT_EXECUTABLE}" +#cmakedefine SSHD_EXECUTABLE "${SSHD_EXECUTABLE}" +#cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" +#cmakedefine SSH_EXECUTABLE_SIZE "${SSH_EXECUTABLE_SIZE}" +#cmakedefine SSH_KEYGEN_EXECUTABLE "${SSH_KEYGEN_EXECUTABLE}" +#cmakedefine DROPBEAR_EXECUTABLE "${DROPBEAR_EXECUTABLE}" +#cmakedefine PUTTY_EXECUTABLE "${PUTTY_EXECUTABLE}" +#cmakedefine PUTTYGEN_EXECUTABLE "${PUTTYGEN_EXECUTABLE}" +#cmakedefine WITH_TIMEOUT ${WITH_TIMEOUT} +#cmakedefine TIMEOUT_EXECUTABLE "${TIMEOUT_EXECUTABLE}" +#cmakedefine SOFTHSM2_LIBRARY "${SOFTHSM2_LIBRARY}" +#cmakedefine PKCS11SPY "${PKCS11SPY}" +#cmakedefine HAVE_SK_DUMMY 1 +#cmakedefine SK_DUMMY_LIBRARY_PATH "${SK_DUMMY_LIBRARY_PATH}" diff --git a/src/libs/libssh-0.12.2/tests/torture.c b/src/libs/libssh-0.12.2/tests/torture.c new file mode 100644 index 000000000000..890c61d04988 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture.c @@ -0,0 +1,2288 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "tests_config.h" +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#elif (defined _WIN32) || (defined _WIN64) +#include +#define chdir _chdir +#endif + +#include "libssh/libssh.h" +#include "libssh/misc.h" +#include "libssh/token.h" +#include "torture.h" +#include "torture_key.h" + +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#ifdef WITH_GSSAPI +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + +#define TORTURE_SSHD_SRV_IPV4 "127.0.0.10" +#define TORTURE_SSHD_SRV1_IPV4 "127.0.0.11" +/* socket wrapper IPv6 prefix fd00::5357:5fxx */ +#define TORTURE_SSHD_SRV_IPV6 "fd00::5357:5f0a" +#define TORTURE_SSHD_SRV1_IPV6 "fd00::5357:5f0b" +#define TORTURE_SSHD_SRV_PORT 22 +#define TORTURE_SSHD_SRV_IFACE "10" +#define TORTURE_SSHD_SRV1_IFACE "11" + +#define TORTURE_SOCKET_DIR "/tmp/test_socket_wrapper_XXXXXX" +#define TORTURE_SSHD_PIDFILE "sshd/sshd.pid" +#define TORTURE_SSHD_CONFIG "sshd/sshd_config" +#define TORTURE_SSHD1_PIDFILE "sshd1/sshd.pid" +#define TORTURE_SSHD1_CONFIG "sshd1/sshd_config" +#define TORTURE_PCAP_FILE "socket_trace.pcap" + +static const char torture_rsa_certauth_pub[] = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCnA2n5vHzZbs/GvRkGloJNV1CXHI" + "S5Xnrm05HusUJSWyPq3I1iCMHdYA7oezHa9GCFYbIenaYPy+G6USQRjYQz8SvAZo06" + "SFNeJSsa1kAIqxzdPT9kBrRrYK39PZQPsYVfRPqZBdmc+jwrfz97IFEJyXMI47FoTG" + "kgEq7eu3z2px/tdIZ34I5Hr5DDBxicZi4jluyRUJHfSPoBxyhF7OkPX4bYkrc691je" + "IQDxubl650WYLHgFfad0xTzBIFE6XUb55Dp5AgRdevSoso1Pe0IKFxxMVpP664LCbY" + "K06Lv6kcotfFlpvUtR1yx8jToGcSoq5sSzTwvXSHCQQ9ZA1hvF " + "torture_certauth_key"; + +static int verbosity = 0; +static const char *pattern = NULL; + +#ifndef _WIN32 + +/* TODO missing code coverage */ +static int _torture_auth_kbdint(ssh_session session, const char *password) +{ + const char *prompt; + char echo; + int err; + + if (session == NULL || password == NULL) { + return SSH_AUTH_ERROR; + } + + err = ssh_userauth_kbdint(session, NULL, NULL); + if (err == SSH_AUTH_ERROR) { + return err; + } + + if (ssh_userauth_kbdint_getnprompts(session) != 1) { + return SSH_AUTH_ERROR; + } + + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + if (prompt == NULL) { + return SSH_AUTH_ERROR; + } + + if (ssh_userauth_kbdint_setanswer(session, 0, password) < 0) { + return SSH_AUTH_ERROR; + } + err = ssh_userauth_kbdint(session, NULL, NULL); + if (err == SSH_AUTH_INFO) { + if (ssh_userauth_kbdint_getnprompts(session) != 0) { + return SSH_AUTH_ERROR; + } + err = ssh_userauth_kbdint(session, NULL, NULL); + } + + return err; +} + +int torture_rmdirs(const char *path) +{ + DIR *d; + struct dirent *dp; + struct stat sb; + char *fname; + + if ((d = opendir(path)) != NULL) { + while (stat(path, &sb) == 0) { + /* if we can remove the directory we're done */ + if (rmdir(path) == 0) { + break; + } + switch (errno) { + case ENOTEMPTY: + case EEXIST: + case EBADF: + break; /* continue */ + default: + closedir(d); + return 0; + } + + while ((dp = readdir(d)) != NULL) { + size_t len; + /* skip '.' and '..' */ + if (dp->d_name[0] == '.' && + (dp->d_name[1] == '\0' || + (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) { + continue; + } + + len = strlen(path) + strlen(dp->d_name) + 2; + fname = malloc(len); + if (fname == NULL) { + closedir(d); + return -1; + } + snprintf(fname, len, "%s/%s", path, dp->d_name); + + /* stat the file */ + if (lstat(fname, &sb) != -1) { + if (S_ISDIR(sb.st_mode) && !S_ISLNK(sb.st_mode)) { + if (rmdir(fname) < 0) { /* can't be deleted */ + if (errno == EACCES) { + closedir(d); + SAFE_FREE(fname); + return -1; + } + torture_rmdirs(fname); + } + } else { + unlink(fname); + } + } /* lstat */ + SAFE_FREE(fname); + } /* readdir */ + + rewinddir(d); + } + } else { + return -1; + } + + closedir(d); + return 0; +} + +int torture_isdir(const char *path) +{ + struct stat sb; + + if (lstat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + return 1; + } + + return 0; +} + +static pid_t torture_read_pidfile(const char *pidfile) +{ + char buf[8] = {0}; + long int tmp; + pid_t ret; + ssize_t rc; + int fd; + + fd = open(pidfile, O_RDONLY); + if (fd < 0) { + return -1; + } + + rc = read(fd, buf, sizeof(buf)); + close(fd); + if (rc <= 0) { + return -1; + } + + buf[sizeof(buf) - 1] = '\0'; + + tmp = strtol(buf, NULL, 10); + if (tmp == 0 || errno == ERANGE) { + return -1; + } + ret = (pid_t)tmp; + /* Check if we are out of pid_t range on this system */ + if ((long)ret != tmp) { + return -1; + } + + return ret; +} + +int torture_terminate_process(const char *pidfile) +{ +#ifndef WIN32 + ssize_t rc; + pid_t pid; + int is_running = 1; + int count; + + /* read the pidfile */ + pid = torture_read_pidfile(pidfile); + if (pid == -1) { + fprintf(stderr, "Failed to read PID file %s\n", pidfile); + return -1; + } + assert_int_not_equal(pid, -1); + + for (count = 0; count < 500; count++) { + /* Make sure the daemon goes away! */ + kill(pid, SIGTERM); + + /* 25 ms */ + usleep(25 * 1000); +#ifdef HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + SSH_LOG(SSH_LOG_INFO, + "Running within Valgrind, wait one more " + "second for the server to clean up."); + usleep(1000 * 1000); + } +#endif /* HAVE_VALGRIND_VALGRIND_H */ + + rc = kill(pid, 0); + if (rc != 0) { + /* Process not found */ + if (errno == ESRCH) { + is_running = 0; + rc = 0; + break; + } + } + } + + if (is_running) { + fprintf(stderr, + "WARNING: The process with pid %u is still running!\n", + pid); + } + + return rc; +#else + (void)pidfile; + return -1; /* Stub implementation for Windows */ +#endif +} + +ssh_session torture_ssh_session(struct torture_state *s, + const char *host, + const unsigned int *port, + const char *user, + const char *password) +{ + ssh_session session; + int method; + int rc; + + bool process_config = false; + + if (host == NULL) { + return NULL; + } + + session = ssh_new(); + if (session == NULL) { + return NULL; + } + +#ifdef WITH_PCAP + if (s != NULL && s->plain_pcap != NULL) { + ssh_set_pcap_file(session, s->plain_pcap); + } +#endif /* WITH_PCAP */ + + if (ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity) < 0) { + goto failed; + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + goto failed; + } + + if (port != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_PORT, port) < 0) { + goto failed; + } + } + + if (user != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + goto failed; + } + } + + if (ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config) < + 0) { + goto failed; + } + + if (ssh_connect(session)) { + goto failed; + } + + /* We are in testing mode, so consinder the hostkey as verified ;) */ + + /* This request should return a SSH_REQUEST_DENIED error */ + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_ERROR) { + goto failed; + } + method = ssh_userauth_list(session, NULL); + if (method == 0) { + goto failed; + } + + if (password != NULL) { + if (method & SSH_AUTH_METHOD_PASSWORD) { + rc = ssh_userauth_password(session, NULL, password); + } else if (method & SSH_AUTH_METHOD_INTERACTIVE) { + rc = _torture_auth_kbdint(session, password); + } + } else { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc == SSH_AUTH_ERROR) { + goto failed; + } + } + if (rc != SSH_AUTH_SUCCESS) { + goto failed; + } + + return session; +failed: + if (ssh_is_connected(session)) { + ssh_disconnect(session); + } + ssh_free(session); + + return NULL; +} + +/* always return verification successful */ +static int verify_knownhost_trust_all(UNUSED_PARAM(ssh_session jump_session), + UNUSED_PARAM(void *user)) +{ + return SSH_OK; +} + +/** + * @brief Create a session connected to server via proxyjump + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + * + * @warning It is expected that both sshd servers are setup before calling + * this, see torture_setup_sshd_server() and + * torture_setup_sshd_servers() + * + * TODO: If needed, in future, we can extend this function to: + * - allow caller to pass server host port, user, password similar to + * torture_ssh_session() or club this with that function + * + * - allow caller to customize jump hosts and callbacks for each of them + */ +ssh_session torture_ssh_session_proxyjump(void) +{ + /* + * We'll setup the connection chain: + * - client + * - jump host 1: doe (sshd server, IPV4) + * - jump host 2: alice (sshd server1, IPV6) + * - server: alice (sshd server, IPV4) + */ + char jump_host_list[1024] = {0}; + int jump_host_count = 2; + const char *jump_host_1_address = torture_server_address(AF_INET); + const char *jump_host_2_address = torture_server1_address(AF_INET6); + struct ssh_jump_callbacks_struct jump_host_callbacks = { + .before_connection = NULL, + .verify_knownhost = verify_knownhost_trust_all, + .authenticate = NULL, + }; + + ssh_session session = NULL; + bool process_config = false; + int rc, i; + + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Failed to create new ssh session\n"); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + if (rc < 0) { + fprintf(stderr, + "Failed to set session host: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + if (rc < 0) { + fprintf(stderr, + "Failed to set session user: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + if (rc < 0) { + fprintf(stderr, + "Failed to set process config option: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = snprintf(jump_host_list, + sizeof(jump_host_list), + "doe@%s:22,alice@%s:22", + jump_host_1_address, + jump_host_2_address); + if (rc < 0) { + fprintf(stderr, "snprintf failed: %s\n", strerror(errno)); + goto failed; + } + + if (rc >= (int)sizeof(jump_host_list)) { + fprintf(stderr, "Insufficient jump host list buffer size\n"); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, jump_host_list); + if (rc < 0) { + fprintf(stderr, + "Failed to set jump hosts for the session: %s\n", + ssh_get_error(session)); + goto failed; + } + + for (i = 0; i < jump_host_count; ++i) { + rc = ssh_options_set(session, + SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, + &jump_host_callbacks); + if (rc < 0) { + fprintf(stderr, + "Failed to set jump callbacks for jump host %d: %s\n", + i + 1, + ssh_get_error(session)); + goto failed; + } + } + + rc = ssh_connect(session); + if (rc != SSH_OK) { + fprintf(stderr, + "Failed to connect to ssh server: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc != SSH_AUTH_SUCCESS) { + fprintf(stderr, "Public key authentication did not succeed\n"); + goto failed; + } + + return session; + +failed: + if (ssh_is_connected(session)) { + ssh_disconnect(session); + } + ssh_free(session); + + return NULL; +} + +#ifdef WITH_SERVER + +ssh_bind torture_ssh_bind(const char *addr, + const unsigned int port, + enum ssh_keytypes_e key_type, + const char *private_key_file) +{ + int rc; + ssh_bind sshbind = NULL; + enum ssh_bind_options_e opts = -1; + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + goto out; + } + + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, addr); + if (rc != 0) { + goto out_free; + } + + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT, &port); + if (rc != 0) { + goto out_free; + } + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &verbosity); + if (rc < 0) { + goto out_free; + } + + switch (key_type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ED25519: + opts = SSH_BIND_OPTIONS_HOSTKEY; + break; + default: + goto out_free; + } + + rc = ssh_bind_options_set(sshbind, opts, private_key_file); + if (rc != 0) { + goto out_free; + } + + rc = ssh_bind_listen(sshbind); + if (rc != SSH_OK) { + goto out_free; + } + + goto out; +out_free: + ssh_bind_free(sshbind); + sshbind = NULL; +out: + return sshbind; +} + +#endif /* WITH_SERVER */ + +#ifdef WITH_SFTP + +struct torture_sftp *torture_sftp_session_channel(ssh_session session, + ssh_channel channel) +{ + struct torture_sftp *t; + char template[] = "/tmp/ssh_torture_XXXXXX"; + char *p; + int rc; + + if (session == NULL) { + return NULL; + } + + t = malloc(sizeof(struct torture_sftp)); + if (t == NULL) { + return NULL; + } + + t->ssh = session; + if (channel == NULL) { + t->sftp = sftp_new(session); + if (t->sftp == NULL) { + goto failed; + } + } else { + t->sftp = sftp_new_channel(session, channel); + if (t->sftp == NULL) { + goto failed; + } + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) { + goto failed; + } + + rc = ssh_channel_request_sftp(channel); + if (rc != SSH_OK) { + goto failed; + } + } + + rc = sftp_init(t->sftp); + if (rc < 0) { + goto failed; + } + + p = mkdtemp(template); + if (p == NULL) { + goto failed; + } + /* useful if TESTUSER is not the local user */ + chmod(template, 0777); + t->testdir = strdup(p); + if (t->testdir == NULL) { + goto failed; + } + + return t; +failed: + if (t->sftp != NULL) { + sftp_free(t->sftp); + } + ssh_disconnect(t->ssh); + ssh_free(t->ssh); + free(t); + + return NULL; +} + +struct torture_sftp *torture_sftp_session(ssh_session session) +{ + return torture_sftp_session_channel(session, NULL); +} + +void torture_sftp_close(struct torture_sftp *t) +{ + if (t == NULL) { + return; + } + + if (t->sftp != NULL) { + sftp_free(t->sftp); + } + + if (t->testdir) { + torture_rmdirs(t->testdir); + } + + free(t->testdir); + free(t); +} +#endif /* WITH_SFTP */ + +int torture_server_port(void) +{ + char *env = getenv("TORTURE_SERVER_PORT"); + + if (env != NULL && env[0] != '\0' && strlen(env) < 6) { + int port = atoi(env); + + if (port > 0 && port < 65536) { + return port; + } + } + + return TORTURE_SSHD_SRV_PORT; +} + +const char *torture_server_address(int family) +{ + switch (family) { + case AF_INET: { + const char *ip4 = getenv("TORTURE_SERVER_ADDRESS_IPV4"); + + if (ip4 != NULL && ip4[0] != '\0') { + return ip4; + } + + return TORTURE_SSHD_SRV_IPV4; + } + case AF_INET6: { + const char *ip6 = getenv("TORTURE_SERVER_ADDRESS_IPV6"); + + if (ip6 != NULL && ip6[0] != '\0') { + return ip6; + } + + return TORTURE_SSHD_SRV_IPV6; + } + default: + return NULL; + } + + return NULL; +} + +const char *torture_server1_address(int family) +{ + switch (family) { + case AF_INET: + return TORTURE_SSHD_SRV1_IPV4; + case AF_INET6: + return TORTURE_SSHD_SRV1_IPV6; + default: + return NULL; + } + + return NULL; +} + +void torture_setup_socket_dir(void **state) +{ + struct torture_state *s; + const char *p; + size_t len; + char *env = NULL; + char gss_dir[1024] = {0}; + int rc; + + s = calloc(1, sizeof(struct torture_state)); + assert_non_null(s); + +#ifdef WITH_PCAP + env = getenv("TORTURE_PLAIN_PCAP_FILE"); + if (env != NULL && env[0] != '\0') { + s->plain_pcap = ssh_pcap_file_new(); + assert_non_null(s->plain_pcap); + + rc = ssh_pcap_file_open(s->plain_pcap, env); + assert_int_equal(rc, SSH_OK); + } +#endif /* WITH_PCAP */ + + s->socket_dir = torture_make_temp_dir(TORTURE_SOCKET_DIR); + assert_non_null(s->socket_dir); + +#ifdef WITH_GSSAPI + snprintf(gss_dir, sizeof(gss_dir), "%s/gss", s->socket_dir); + rc = mkdir(gss_dir, 0755); + assert_return_code(rc, errno); + s->gss_dir = strdup(gss_dir); +#endif + + p = s->socket_dir; + + /* pcap file */ + len = strlen(p) + 1 + strlen(TORTURE_PCAP_FILE) + 1; + + s->pcap_file = malloc(len); + assert_non_null(s->pcap_file); + + snprintf(s->pcap_file, len, "%s/%s", p, TORTURE_PCAP_FILE); + + /* pid file */ + len = strlen(p) + 1 + strlen(TORTURE_SSHD_PIDFILE) + 1; + + s->srv_pidfile = malloc(len); + assert_non_null(s->srv_pidfile); + + snprintf(s->srv_pidfile, len, "%s/%s", p, TORTURE_SSHD_PIDFILE); + + /* config file */ + len = strlen(p) + 1 + strlen(TORTURE_SSHD_CONFIG) + 1; + + s->srv_config = malloc(len); + assert_non_null(s->srv_config); + + snprintf(s->srv_config, len, "%s/%s", p, TORTURE_SSHD_CONFIG); + + s->disable_hostkeys = false; + + setenv("SOCKET_WRAPPER_DIR", p, 1); + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "170", 1); + env = getenv("TORTURE_GENERATE_PCAP"); + if (env != NULL && env[0] == '1') { + setenv("SOCKET_WRAPPER_PCAP_FILE", s->pcap_file, 1); + } + + *state = s; +} + +static void torture_setup_second_sshd_dir(void **state) +{ + struct torture_state *s = *state; + size_t len; + + /* pid file */ + len = strlen(s->socket_dir) + 1 + strlen(TORTURE_SSHD1_PIDFILE) + 1; + + s->srv1_pidfile = malloc(len); + assert_non_null(s->srv1_pidfile); + + snprintf(s->srv1_pidfile, + len, + "%s/%s", + s->socket_dir, + TORTURE_SSHD1_PIDFILE); + + /* config file */ + len = strlen(s->socket_dir) + 1 + strlen(TORTURE_SSHD1_CONFIG) + 1; + + s->srv1_config = malloc(len); + assert_non_null(s->srv1_config); + + snprintf(s->srv1_config, len, "%s/%s", s->socket_dir, TORTURE_SSHD1_CONFIG); +} + +/** + * @brief Create a libssh server configuration file + * + * It is expected the socket directory to be already created before by calling + * torture_setup_socket_dir(). The created configuration file will be stored in + * the socket directory and the srv_config pointer in the state will be + * initialized. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + */ +void torture_setup_create_libssh_config(void **state) +{ + struct torture_state *s = *state; + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + char sshd_config[2048]; + char sshd_path[1024]; + const char *additional_config = NULL; + struct stat sb; + const char config_string[] = + "LogLevel DEBUG3\n" + "Port 22\n" + "ListenAddress 127.0.0.10\n" + "HostKey %s\n" + "HostKey %s\n" + "HostKey %s\n" + "%s\n"; /* The space for test-specific options */ + const char fips_config_string[] = + "LogLevel DEBUG3\n" + "Port 22\n" + "ListenAddress 127.0.0.10\n" + "HostKey %s\n" + "HostKey %s\n" + "%s\n"; /* The space for test-specific options */ + bool written = false; + int rc; + + assert_non_null(s->socket_dir); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = lstat(sshd_path, &sb); + if (rc == 0) { /* The directory is already in place */ + written = true; + } + + if (!written) { + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + } + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + + if (!written) { + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file(rsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + } + + additional_config = + (s->srv_additional_config != NULL ? s->srv_additional_config : ""); + + if (ssh_fips_mode()) { + snprintf(sshd_config, + sizeof(sshd_config), + fips_config_string, + rsa_hostkey, + ecdsa_hostkey, + additional_config); + } else { + snprintf(sshd_config, + sizeof(sshd_config), + config_string, + ed25519_hostkey, + rsa_hostkey, + ecdsa_hostkey, + additional_config); + } + + torture_write_file(s->srv_config, sshd_config); +} + +#ifdef SSHD_EXECUTABLE +static void +torture_setup_create_sshd_config(void **state, bool pam, bool second_sshd) +{ + struct torture_state *s = *state; + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + char trusted_ca_pubkey[1024]; + char sshd_config[4096]; + char sshd_path[1024]; + const char *additional_config = NULL; + struct stat sb; + const char *sftp_server_locations[] = { + "/usr/lib/ssh/sftp-server", + "/usr/libexec/ssh/sftp-server", /* Tumbleweed 20200829 */ + "/usr/libexec/sftp-server", + "/usr/libexec/openssh/sftp-server", + "/usr/lib/openssh/sftp-server", /* Debian */ + }; + const char config_string[] = + "Port 22\n" + "ListenAddress %s\n" + "ListenAddress %s\n" + "%s %s\n" /* ed25519 HostKey */ + "%s %s\n" /* RSA HostKey */ + "%s %s\n" /* ECDSA HostKey */ + "\n" + "TrustedUserCAKeys %s\n" + "\n" + "LogLevel DEBUG3\n" + "Subsystem sftp %s -l DEBUG3 -e\n" + "\n" + "PasswordAuthentication yes\n" + "PubkeyAuthentication yes\n" + "\n" + "StrictModes no\n" + "\n" + "%s\n" /* Here comes UsePam */ + "%s" /* The space for test-specific options */ + "\n" + /* add all supported algorithms */ + "HostKeyAlgorithms " OPENSSH_KEYS "\n" +#if OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR >= 2 + "CASignatureAlgorithms " OPENSSH_KEYS "\n" +#endif +#if (OPENSSH_VERSION_MAJOR == 9 && OPENSSH_VERSION_MINOR >= 8) || \ + OPENSSH_VERSION_MAJOR > 9 + "PerSourcePenaltyExemptList 127.0.0.21\n" +#endif + "Ciphers " OPENSSH_CIPHERS "\n" + "KexAlgorithms " OPENSSH_KEX "\n" + "MACs " OPENSSH_MACS "\n" + "\n" + "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY " + "LC_MESSAGES\n" + "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" + "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" + "\n" + "PidFile %s\n"; + /* FIPS config */ + const char fips_config_string[] = + "Port 22\n" + "ListenAddress %s\n" + "ListenAddress %s\n" + "%s %s\n" /* RSA HostKey */ + "%s %s\n" /* ECDSA HostKey */ + "\n" + "TrustedUserCAKeys %s\n" /* Trusted CA */ + "\n" + "LogLevel DEBUG3\n" + "Subsystem sftp %s -l DEBUG3 -e\n" /* SFTP server */ + "\n" + "PasswordAuthentication yes\n" + "PubkeyAuthentication yes\n" + "\n" + "StrictModes no\n" + "\n" + "%s\n" /* Here comes UsePam */ + "%s" /* The space for test-specific options */ + "\n" +#if (OPENSSH_VERSION_MAJOR == 9 && OPENSSH_VERSION_MINOR >= 8) || \ + OPENSSH_VERSION_MAJOR > 9 + "PerSourcePenaltyExemptList 127.0.0.21\n" +#endif + "Ciphers " + "aes256-gcm@openssh.com,aes256-ctr,aes256-cbc," + "aes128-gcm@openssh.com,aes128-ctr,aes128-cbc" + "\n" + "MACs " + "hmac-sha2-256-etm@openssh.com,hmac-sha1-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com,hmac-sha2-256," + "hmac-sha1,hmac-sha2-512" + "\n" + "GSSAPIKeyExchange no\n" + "KexAlgorithms " +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) + "mlkem768nistp256-sha256," +#ifdef HAVE_MLKEM1024 + "mlkem1024nistp384-sha384," +#endif +#endif + "ecdh-sha2-nistp256,ecdh-sha2-nistp384," + "ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512" + "\n" + "PubkeyAcceptedKeyTypes " + "rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com," + "ecdsa-sha2-nistp256,ecdsa-sha2-nistp256-cert-v01@openssh.com," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp384-cert-v01@openssh.com," + "rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp521-cert-v01@openssh.com" + "\n" + "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY " + "LC_MESSAGES\n" + "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" + "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" + "\n" + "PidFile %s\n"; + const char usepam_yes[] = "UsePAM yes\n" + "KbdInteractiveAuthentication yes\n"; + const char usepam_no[] = "UsePAM no\n" + "KbdInteractiveAuthentication no\n"; + size_t sftp_sl_size = ARRAY_SIZE(sftp_server_locations); + const char *sftp_server, *usepam; + size_t i; + bool written = false; + int rc; + + s->srv_pam = pam; + if (pam) { + usepam = usepam_yes; + } else { + usepam = usepam_no; + } + + assert_non_null(s->socket_dir); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd%s", + s->socket_dir, + second_sshd ? "1" : ""); + + rc = lstat(sshd_path, &sb); + if (rc == 0) { /* The directory is already in place */ + written = true; + } + + if (!written) { + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + } + + rc = snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/ssh_host_ed25519_key", + sshd_path); + assert_true(rc >= 0); + + rc = snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/ssh_host_rsa_key", + sshd_path); + assert_true(rc >= 0); + + rc = snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/ssh_host_ecdsa_key", + sshd_path); + assert_true(rc >= 0); + + rc = snprintf(trusted_ca_pubkey, + sizeof(trusted_ca_pubkey), + "%s/user_ca.pub", + sshd_path); + assert_true(rc >= 0); + + if (!written) { + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file(rsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + torture_write_file(trusted_ca_pubkey, torture_rsa_certauth_pub); + } + + sftp_server = getenv("TORTURE_SFTP_SERVER"); + if (sftp_server == NULL) { + for (i = 0; i < sftp_sl_size; i++) { + sftp_server = sftp_server_locations[i]; + rc = lstat(sftp_server, &sb); + if (rc == 0) { + break; + } + } + } + assert_non_null(sftp_server); + + additional_config = + (s->srv_additional_config != NULL ? s->srv_additional_config : ""); + + if (ssh_fips_mode()) { + snprintf(sshd_config, + sizeof(sshd_config), + fips_config_string, + second_sshd ? TORTURE_SSHD_SRV1_IPV4 : TORTURE_SSHD_SRV_IPV4, + second_sshd ? TORTURE_SSHD_SRV1_IPV6 : TORTURE_SSHD_SRV_IPV6, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : rsa_hostkey, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : ecdsa_hostkey, + trusted_ca_pubkey, + sftp_server, + usepam, + additional_config, + second_sshd ? s->srv1_pidfile : s->srv_pidfile); + } else { + snprintf(sshd_config, + sizeof(sshd_config), + config_string, + second_sshd ? TORTURE_SSHD_SRV1_IPV4 : TORTURE_SSHD_SRV_IPV4, + second_sshd ? TORTURE_SSHD_SRV1_IPV6 : TORTURE_SSHD_SRV_IPV6, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : ed25519_hostkey, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : rsa_hostkey, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : ecdsa_hostkey, + trusted_ca_pubkey, + sftp_server, + usepam, + additional_config, + second_sshd ? s->srv1_pidfile : s->srv_pidfile); + } + + if (second_sshd) { + torture_write_file(s->srv1_config, sshd_config); + } else { + torture_write_file(s->srv_config, sshd_config); + } +} + +int torture_wait_for_daemon(unsigned int seconds) +{ + struct ssh_timestamp start; + int rc; + + ssh_timestamp_init(&start); + + while (!ssh_timeout_elapsed(&start, seconds * 1000)) { + rc = system(SSH_PING_EXECUTABLE " " TORTURE_SSH_SERVER); + if (rc == 0) { + return 0; + } + /* Wait 200 ms before retrying */ + usleep(200 * 1000); + } + return 1; +} + +void torture_set_kdc_env_str(const char *gss_dir, char *env, size_t size) +{ + int rc; + rc = snprintf(env, + size, + "KRB5CCNAME=%s/cc " + "KRB5_CONFIG=%s/k/krb5.conf " + "KRB5_KDC_PROFILE=%s/k " + "KRB5_KTNAME=%s/d/ssh.keytab " + "KRB5RCACHETYPE=none ", + gss_dir, + gss_dir, + gss_dir, + gss_dir); + if (rc < 0 || rc >= (int)size) { + fail_msg("snprintf failed"); + } +} + +void torture_set_env_from_str(const char *env) +{ + struct ssh_tokens_st *vars = NULL, *var = NULL; + + vars = ssh_tokenize(env, ' '); + if (vars == NULL) { + fail_msg("failed to tokenize environment string"); + } + + for (int i = 0; vars->tokens[i]; i++) { + var = ssh_tokenize(vars->tokens[i], '='); + if (var == NULL) { + ssh_tokens_free(vars); + fail_msg("invalid environment string format"); + } + if (var->tokens[0] != NULL && var->tokens[1] != NULL) { + setenv(var->tokens[0], var->tokens[1], 1); + } else { + ssh_tokens_free(var); + ssh_tokens_free(vars); + fail_msg("invalid environment string format"); + } + ssh_tokens_free(var); + } + ssh_tokens_free(vars); +} + +/** + * @brief Run a libssh based server under timeout. + * + * It is expected that the socket directory and libssh configuration file were + * already created before by calling torture_setup_socket_dir() and + * torture_setup_create_libssh_config() (or alternatively setup the state with + * the correct values). + * + * @param[in] state The content of the address pointed by this variable must be + * a pointer to an initialized instance of torture_state + * structure; it can be obtained by calling + * torture_setup_socket_dir() and + * torture_setup_create_libssh_config(). + * @param[in] server_path The path to the server executable. + * + * @note This function will use the state->srv_additional_config field as + * additional command line option used when starting the server instead of extra + * configuration file options. + * */ +void torture_setup_libssh_server(void **state, const char *server_path) +{ + struct torture_state *s; + char start_cmd[1024]; + char timeout_cmd[512]; + char env[1024]; + char kdc_env[255]; + char extra_options[1024]; + int rc; + char *ld_preload = NULL; + const char *force_fips = NULL; + + struct ssh_tokens_st *env_tokens; + struct ssh_tokens_st *arg_tokens; + + pid_t pid; + ssize_t printed; + + s = *state; + + /* Get all the wrapper libraries to be pre-loaded */ + ld_preload = getenv("LD_PRELOAD"); + + if (s->srv_additional_config != NULL) { + printed = snprintf(extra_options, + sizeof(extra_options), + " %s ", + s->srv_additional_config); + if (printed < 0 || printed >= (ssize_t)sizeof(extra_options)) { + fail_msg("Failed to print additional config!"); + /* Unreachable */ + __builtin_unreachable(); + } + } else { + printed = snprintf(extra_options, sizeof(extra_options), " "); + if (printed < 0 || printed >= (ssize_t)sizeof(extra_options)) { + fail_msg("Failed to print empty additional config!"); + /* Unreachable */ + __builtin_unreachable(); + } + } + + if (ssh_fips_mode()) { + force_fips = "OPENSSL_FORCE_FIPS_MODE=1 "; + } else { + force_fips = ""; + } + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + + /* Write the environment setting */ + /* OPENSSL variable is needed to enable SHA1 */ + printed = snprintf(env, + sizeof(env), + "SOCKET_WRAPPER_DIR=%s " + "SOCKET_WRAPPER_DEFAULT_IFACE=10 " + "LD_PRELOAD=%s " + "%s " + "OPENSSL_ENABLE_SHA1_SIGNATURES=1 " + "NSS_WRAPPER_HOSTNAME=server.libssh.site " + "%s ", + s->socket_dir, + ld_preload, + force_fips, + kdc_env); + if (printed < 0 || printed >= (ssize_t)sizeof(env)) { + fail_msg("Failed to print env!"); + /* Unreachable */ + __builtin_unreachable(); + } + +#ifdef WITH_TIMEOUT + snprintf(timeout_cmd, + sizeof(timeout_cmd), + "%s %s ", + TIMEOUT_EXECUTABLE, + "5m"); +#else + timeout_cmd[0] = '\0'; +#endif + + /* Write the start command */ + printed = snprintf(start_cmd, + sizeof(start_cmd), + "%s" + "%s -f%s -v4 -p22 -i%s -C%s%s%s%s%s", + timeout_cmd, + server_path, + s->pcap_file, + s->srv_pidfile, + s->srv_config, + s->log_file ? " -l " : "", + s->log_file ? s->log_file : "", + extra_options, + TORTURE_SSH_SERVER); + if (printed < 0 || printed >= (ssize_t)sizeof(start_cmd)) { + fail_msg("Failed to print start command!"); + /* Unreachable */ + __builtin_unreachable(); + } + + pid = fork(); + switch (pid) { + case 0: + env_tokens = ssh_tokenize(env, ' '); + if (env_tokens == NULL || env_tokens->tokens == NULL) { + fail_msg("Failed to tokenize env!"); + /* Unreachable */ + __builtin_unreachable(); + } + + arg_tokens = ssh_tokenize(start_cmd, ' '); + if (arg_tokens == NULL || arg_tokens->tokens == NULL) { + ssh_tokens_free(env_tokens); + fail_msg("Failed to tokenize args!"); + /* Unreachable */ + __builtin_unreachable(); + } + + execve(arg_tokens->tokens[0], + (char **)arg_tokens->tokens, + (char **)env_tokens->tokens); + + /* execve returns only in case of error */ + ssh_tokens_free(env_tokens); + ssh_tokens_free(arg_tokens); + fail_msg("Error in execve: %s", strerror(errno)); + /* Unreachable */ + __builtin_unreachable(); + case -1: + fail_msg("Failed to fork!"); + /* Unreachable */ + __builtin_unreachable(); + default: + /* The parent continues the execution of the tests */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the server is ready to accept connections */ + rc = torture_wait_for_daemon(15); + assert_int_equal(rc, 0); + break; + } +} + +static int torture_start_sshd_server(void **state, bool second_sshd) +{ + struct torture_state *s = *state; + char sshd_start_cmd[1024]; + int rc; + char kdc_env[255] = {0}; + + /* Set the default interface for the server + * default is 10 */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", + second_sshd ? TORTURE_SSHD_SRV_IFACE : TORTURE_SSHD_SRV_IFACE, + 1); + setenv("PAM_WRAPPER", "1", 1); + +#ifdef WITH_GSSAPI + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); +#endif + rc = snprintf(sshd_start_cmd, + sizeof(sshd_start_cmd), + "%s " SSHD_EXECUTABLE + " -r -f %s -E %s/sshd%s/daemon.log 2> %s/sshd%s/cwrap.log", + kdc_env, + second_sshd ? s->srv1_config : s->srv_config, + s->socket_dir, + second_sshd ? "1" : "", + s->socket_dir, + second_sshd ? "1" : ""); + if (rc < 0 || rc >= (int)sizeof(sshd_start_cmd)) { + fail_msg("snprintf failed"); + } + + rc = system(sshd_start_cmd); + assert_return_code(rc, errno); + + unsetenv("NSS_WRAPPER_HOSTNAME"); + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(15); + assert_int_equal(rc, 0); + + return SSH_OK; +} + +void torture_setup_sshd_server(void **state, bool pam) +{ + int rc; + + torture_setup_socket_dir(state); + torture_setup_create_sshd_config(state, pam, false); + + rc = torture_start_sshd_server(state, false); + assert_int_equal(rc, 0); +} + +/* Create an another sshd instance in the same SOCKET_WRAPPER_DIR + * Param state has to be initialized with torture_setup_sshd_server */ +void torture_setup_sshd_servers(void **state, bool pam) +{ + int rc; + + torture_setup_second_sshd_dir(state); + torture_setup_create_sshd_config(state, pam, true); + + rc = torture_start_sshd_server(state, true); + assert_int_equal(rc, 0); +} + +#ifdef WITH_GSSAPI +/** + * @brief Setup KDC for GSSAPI testing + * + * This should be called after sshd or libssh server's setup functions. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + * @param[in] kadmin_script kadmin commands to be executed on the KDC + * @param[in] kinit_script kinit commands to get the TGT + * + */ +void torture_setup_kdc_server(void **state, + const char *kadmin_script, + const char *kinit_script) +{ + struct torture_state *s = *state; + int rc; + char command[1024] = {0}; + char kdc_env[255] = {0}; + char kadmin_file[255] = {0}; + char kinit_file[255] = {0}; + + /* Remove the previous files and folders, but keep the same directory + * because we pass only one temporary directory to the server */ + rc = snprintf(command, sizeof(command), "rm -rf %s/*", s->gss_dir); + if (rc < 0 || rc >= (int)sizeof(command)) { + fail_msg("snprintf failed"); + } + rc = system(command); + assert_return_code(rc, errno); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "11", 1); + setenv("NSS_WRAPPER_HOSTNAME", "kdc.libssh.site", 1); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + snprintf(kadmin_file, sizeof(kadmin_file), "%s/kadmin.sh", s->gss_dir); + snprintf(kinit_file, sizeof(kinit_file), "%s/kinit.sh", s->gss_dir); + + torture_write_file(kadmin_file, kadmin_script); + torture_write_file(kinit_file, kinit_script); + + rc = snprintf(command, + sizeof(command), + "%s/tests/gss/kdcsetup.sh %s", + BINARYDIR, + s->socket_dir); + if (rc < 0 || rc >= (int)sizeof(command)) { + fail_msg("snprintf failed"); + } + rc = system(command); + assert_return_code(rc, errno); + assert_int_equal(rc, 0); + + unsetenv("NSS_WRAPPER_HOSTNAME"); + /* Back to client */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); +} + +/** + * @brief Teardown KDC + * + * This should be called before sshd or libssh server's teardown functions. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + */ +void torture_teardown_kdc_server(void **state) +{ + struct torture_state *s = *state; + int rc; + char pid_path[1024] = {0}; + + rc = snprintf(pid_path, sizeof(pid_path), "%s/pid", s->gss_dir); + if (rc < 0 || rc >= (int)sizeof(pid_path)) { + fail_msg("snprintf failed"); + } + rc = torture_terminate_process(pid_path); + assert_return_code(rc, errno); +} + +#endif /* WITH_GSSAPI */ + +void torture_free_state(struct torture_state *s) +{ + free(s->srv_config); + free(s->srv1_config); + free(s->socket_dir); +#ifdef WITH_GSSAPI + free(s->gss_dir); +#endif + free(s->pcap_file); + free(s->log_file); + free(s->srv_pidfile); + free(s->srv1_pidfile); + free(s->srv_additional_config); + free(s); +} + +void torture_teardown_socket_dir(void **state) +{ + struct torture_state *s = *state; + char *env = getenv("TORTURE_SKIP_CLEANUP"); + int rc; + + if (env != NULL && env[0] == '1') { + fprintf(stderr, + "[ TORTURE ] >>> Skipping cleanup of %s\n", + s->socket_dir); + } else { + rc = torture_rmdirs(s->socket_dir); + if (rc < 0) { + fprintf(stderr, + "torture_rmdirs(%s) failed: %s", + s->socket_dir, + strerror(errno)); + } + } +#ifdef WITH_PCAP + if (s->plain_pcap != NULL) { + ssh_pcap_file_free(s->plain_pcap); + } + s->plain_pcap = NULL; +#endif /* WITH_PCAP */ + torture_free_state(s); +} + +static int torture_reload_sshd_server(void **state) +{ + struct torture_state *s = *state; + int rc; + + rc = torture_terminate_process(s->srv_pidfile); + assert_return_code(rc, errno); + + return torture_start_sshd_server(state, false); +} + +/* @brief: Updates SSHD server configuration with more options and + * reloads the server to apply them. + * Note, that this still uses the default configuration options specified + * in this file and overwrites options previously specified by this function. + */ +int torture_update_sshd_config(void **state, const char *config) +{ + struct torture_state *s = *state; + int rc; + + /* Store the configuration in internal structure */ + SAFE_FREE(s->srv_additional_config); + s->srv_additional_config = strdup(config); + assert_non_null(s->srv_additional_config); + + /* Rewrite the configuration file */ + torture_setup_create_sshd_config(state, s->srv_pam, false); + + /* Reload the server */ + rc = torture_reload_sshd_server(state); + assert_int_equal(rc, SSH_OK); + + return SSH_OK; +} + +void torture_teardown_sshd_server(void **state) +{ + struct torture_state *s = *state; + + torture_terminate_process(s->srv_pidfile); + if (s->srv1_pidfile != NULL) { + torture_terminate_process(s->srv1_pidfile); + } + torture_teardown_socket_dir(state); +} + +void torture_teardown_sshd_server1(void **state) +{ + struct torture_state *s = *state; + + torture_terminate_process(s->srv1_pidfile); + SAFE_FREE(s->srv1_pidfile); + SAFE_FREE(s->srv1_config); +} +#endif /* SSHD_EXECUTABLE */ + +#ifdef WITH_PKCS11_URI +void torture_setup_tokens(const char *temp_dir, + const char *filename, + const char object_name[], + const char *load_public) +{ + char token_setup_start_cmd[1024] = {0}; + char socket_path[1204] = {0}; + char conf_path[1024] = {0}; +#ifdef WITH_PKCS11_PROVIDER + char *env = NULL; +#endif /* WITH_PKCS11_PROVIDER */ + int rc; + + rc = snprintf(token_setup_start_cmd, + sizeof(token_setup_start_cmd), + "%s/tests/pkcs11/setup-softhsm-tokens.sh %s %s %s %s %s", + BINARYDIR, + temp_dir, + filename, + object_name, + load_public, + SOFTHSM2_LIBRARY); + assert_int_not_equal(rc, sizeof(token_setup_start_cmd)); + + rc = system(token_setup_start_cmd); + assert_return_code(rc, errno); + +#ifdef WITH_PKCS11_PROVIDER + setenv("PKCS11_PROVIDER_MODULE", SOFTHSM2_LIBRARY, 1); + + /* This is useful for debugging PKCS#11 calls */ + env = getenv("TORTURE_PKCS11"); + if (env != NULL && env[0] != '\0') { +#ifdef PKCS11SPY + setenv("PKCS11SPY", SOFTHSM2_LIBRARY, 1); + setenv("PKCS11_PROVIDER_MODULE", PKCS11SPY, 1); +#else + fprintf(stderr, "[ TORTURE ] >>> pkcs11-spy not found\n"); +#endif /* PKCS11SPY */ + } +#endif /* WITH_PKCS11_PROVIDER */ + + snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", temp_dir); + setenv("SOFTHSM2_CONF", conf_path, 1); +} + +void torture_cleanup_tokens(const char *temp_dir) +{ + unsetenv("SOFTHSM2_CONF"); +} +#endif /* WITH_PKCS11_URI */ + +char *torture_make_temp_dir(const char *template) +{ + char *new_dir = NULL; + char *template_copy = NULL; + + if (template == NULL) { + goto end; + } + + template_copy = strdup(template); + if (template_copy == NULL) { + goto end; + } + + new_dir = mkdtemp(template_copy); + if (new_dir == NULL) { + SAFE_FREE(template_copy); + } + +end: + return template_copy; +} + +char *torture_create_temp_file(const char *template) +{ + char *new_file = NULL; + FILE *fp = NULL; + mode_t mask; + int fd; + + new_file = strdup(template); + if (new_file == NULL) { + goto end; + } + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(new_file); + umask(mask); + if (fd == -1) { + goto end; + } + + fp = fdopen(fd, "w"); + if (fp == NULL) { + SAFE_FREE(new_file); + close(fd); + goto end; + } + + fclose(fp); + +end: + return new_file; +} + +char *torture_get_current_working_dir(void) +{ + + char *cwd = NULL; + char *result = NULL; + + cwd = (char *)malloc(PATH_MAX + 1); + if (cwd == NULL) { + goto end; + } + + result = getcwd(cwd, PATH_MAX); + + if (result == NULL) { + SAFE_FREE(cwd); + goto end; + } + +end: + return cwd; +} + +#else /* _WIN32 */ + +char *torture_make_temp_dir(const char *template) +{ + DWORD rc = 0; + char tmp_dir_path[PATH_MAX]; + char tmp_file_name[PATH_MAX]; + char *prefix = NULL; + char *path = NULL; + char *prefix_end = NULL; + char *slash = NULL; + + BOOL created; + + if (template == NULL) { + goto end; + } + + prefix = strdup(template); + if (prefix == NULL) { + goto end; + } + + /* Replace slashes with backslashes */ + slash = strchr(prefix, '/'); + for (; slash != NULL; slash = strchr(prefix, '/')) { + *slash = '\\'; + } + + prefix_end = strstr(prefix, "XXXXXX"); + if (prefix_end != NULL) { + *prefix_end = '\0'; + } + + rc = GetTempPathA(PATH_MAX, tmp_dir_path); + if ((rc > PATH_MAX) || (rc == 0)) { + goto free_prefix; + } + + rc = GetTempFileNameA(tmp_dir_path, TEXT(prefix), 0, tmp_file_name); + if (rc == 0) { + goto free_prefix; + } + + path = strdup(tmp_file_name); + if (path == NULL) { + goto free_prefix; + } + + /* GetTempFileNameA() creates a temporary file; we need to remove it */ + rc = DeleteFileA(path); + if (rc == 0) { + rc = -1; + SAFE_FREE(path); + goto free_prefix; + } + + created = CreateDirectoryA(path, NULL); + if (!created) { + SAFE_FREE(path); + } + +free_prefix: + SAFE_FREE(prefix); +end: + return path; +} + +static int recursive_rm_dir_content(const char *path) +{ + WIN32_FIND_DATA file_data; + HANDLE file_handle; + DWORD attributes; + + DWORD last_error = 0; + + char file_path[PATH_MAX]; + + int rc = 0; + BOOL removed; + + strcpy(file_path, path); + strcat(file_path, "\\*"); + + file_handle = FindFirstFile(file_path, &file_data); + + if (file_handle == INVALID_HANDLE_VALUE) { + last_error = GetLastError(); + + /* Empty directory */ + if (last_error == ERROR_FILE_NOT_FOUND) { + rc = 0; + } else { + /*TODO print error message?*/ + rc = last_error; + } + goto end; + } else { + do { + rc = strcmp(file_data.cFileName, "."); + if (rc == 0) { + continue; + } + + rc = strcmp(file_data.cFileName, ".."); + if (rc == 0) { + continue; + } + + /* Create full file path */ + strcpy(file_path, path); + strcat(file_path, "\\"); + strcat(file_path, file_data.cFileName); + + attributes = GetFileAttributes(file_path); + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { + rc = recursive_rm_dir_content((const char *)file_path); + if (rc != 0) { + goto end; + } + + removed = RemoveDirectoryA(file_path); + + if (!removed) { + last_error = GetLastError(); + + /*TODO print error message?*/ + + rc = last_error; + goto end; + } + } else { + rc = remove(file_path); + if (rc) { + goto end; + } + } + + } while (FindNextFile(file_handle, &file_data)); + + FindClose(file_handle); + } + +end: + return rc; +} + +int torture_rmdirs(const char *path) +{ + int rc = 0; + BOOL removed; + + rc = recursive_rm_dir_content(path); + if (rc) { + return rc; + } + + removed = RemoveDirectoryA(path); + if (!removed) { + rc = -1; + } + + return rc; +} + +int torture_isdir(const char *path) +{ + + DWORD attributes = 0; + + attributes = GetFileAttributes(path); + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { + return 1; + } + + return 0; +} + +char *torture_create_temp_file(const char *template) +{ + DWORD rc = 0; + char tmp_dir_path[PATH_MAX]; + char tmp_file_name[PATH_MAX]; + char *prefix = NULL; + char *path = NULL; + char *prefix_end = NULL; + char *slash = NULL; + + if (template == NULL) { + goto end; + } + + prefix = strdup(template); + if (prefix == NULL) { + goto end; + } + + /* Replace slashes with backslashes */ + slash = strchr(prefix, '/'); + for (; slash != NULL; slash = strchr(prefix, '/')) { + *slash = '\\'; + } + + prefix_end = strstr(prefix, "XXXXXX"); + if (prefix_end != NULL) { + *prefix_end = '\0'; + } + + rc = GetTempPathA(PATH_MAX, tmp_dir_path); + if ((rc > PATH_MAX) || (rc == 0)) { + goto free_prefix; + } + + /* Remark: this function creates the file */ + rc = GetTempFileNameA(tmp_dir_path, TEXT(prefix), 0, tmp_file_name); + if (rc == 0) { + goto free_prefix; + } + + path = strdup(tmp_file_name); + +free_prefix: + SAFE_FREE(prefix); +end: + return path; +} + +char *torture_get_current_working_dir(void) +{ + char *cwd = NULL; + char *result = NULL; + + cwd = (char *)malloc(_MAX_PATH + 1); + if (cwd == NULL) { + goto end; + } + + result = _getcwd(cwd, _MAX_PATH); + + if (result == NULL) { + SAFE_FREE(cwd); + goto end; + } + +end: + return cwd; +} + +#endif /* _WIN32 */ + +int torture_change_dir(char *path) +{ + int rc = 0; + + if (path == NULL) { + rc = -1; + goto end; + } + + rc = chdir(path); + +end: + return rc; +} + +int torture_libssh_verbosity(void) +{ + return verbosity; +} + +void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests) +{ + (void)tests; + (void)ntests; + + return; +} + +void torture_write_file(const char *filename, const char *data) +{ + int fd; + int rc; + + assert_non_null(filename); + assert_true(filename[0] != '\0'); + assert_non_null(data); + + fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, 0600); + assert_true(fd >= 0); + + rc = write(fd, data, strlen(data)); + assert_int_equal(rc, strlen(data)); + + close(fd); +} + +void torture_reset_config(ssh_session session) +{ + memset(session->opts.options_seen, 0, sizeof(session->opts.options_seen)); + if (ssh_libssh_proxy_jumps()) { + ssh_proxyjumps_free(session->opts.proxy_jumps); + } +} + +void torture_unsetenv(const char *variable) +{ + int rc; +#ifdef WIN32 + rc = _putenv_s(variable, ""); +#else + rc = unsetenv(variable); +#endif // WIN32 + assert_return_code(rc, errno); +} + +void torture_setenv(const char *variable, const char *value) +{ + int rc; +#ifdef WIN32 + if (value != NULL) { + rc = _putenv_s(variable, value); + assert_return_code(rc, errno); + } else { + torture_unsetenv(variable); + } +#else + rc = setenv(variable, value, 1); + assert_return_code(rc, errno); +#endif // WIN32 +} + +#if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) +__attribute__((weak)) int torture_run_tests(void) +{ + fail_msg("torture_run_tests from shared library called"); + + return -1; +} +#endif /* defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) */ + +/** + * Finalize the torture context. No-op except for OpenSSL or GSSAPI + * + * When OpenSSL is built without the at-exit handlers, it won't call the + * OPENSSL_cleanup() from destructor or at-exit handler, which means we need to + * do it manually in the tests. + * + * It is never a good idea to call this function from the library context as we + * can not be sure the libssh is really the last one using the OpenSSL. + * + * This needs to be called at the end of the main function or any time before + * any forked process (servers) exits. + */ +void torture_finalize(void) +{ +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif +} + +int main(int argc, char **argv) +{ + struct argument_s arguments; + char *env = getenv("LIBSSH_VERBOSITY"); + int rv; + + arguments.verbose = 0; + arguments.pattern = NULL; + torture_cmdline_parse(argc, argv, &arguments); + verbosity = arguments.verbose; + pattern = arguments.pattern; + + if (verbosity == 0 && env != NULL && env[0] != '\0') { + if (env[0] > '0' && env[0] < '9') { + verbosity = atoi(env); + } + } + +#if defined HAVE_CMOCKA_SET_TEST_FILTER + cmocka_set_test_filter(pattern); +#endif + + rv = torture_run_tests(); + + torture_finalize(); + + return rv; +} + +/** + * @brief Setup an SSH agent for testing + * + * This function starts an SSH agent, exports the environment variables, + * and optionally adds an SSH key to the agent. + * + * @param s The torture state + * @param add_key Path to the key to add to the agent, or NULL to skip + * + * @return 0 on success, -1 on error + */ +int torture_setup_ssh_agent(struct torture_state *s, const char *add_key) +{ +#ifndef WIN32 + int rc; + char ssh_agent_cmd[4096]; + char ssh_agent_sock[1024]; + char ssh_agent_pidfile[1024]; + char long_cmd[2048]; + + /* Setup SSH agent */ + snprintf(ssh_agent_sock, + sizeof(ssh_agent_sock), + "%s/agent.sock", + s->socket_dir); + + snprintf(ssh_agent_pidfile, + sizeof(ssh_agent_pidfile), + "%s/agent.pid", + s->socket_dir); + + /* Create command to start SSH agent with our custom socket */ + snprintf(ssh_agent_cmd, + sizeof(ssh_agent_cmd), + "eval `ssh-agent -a %s`; echo $SSH_AGENT_PID > %s", + ssh_agent_sock, + ssh_agent_pidfile); + + /* Run ssh-agent as the normal user */ + torture_unsetenv("UID_WRAPPER_ROOT"); + + rc = system(ssh_agent_cmd); + if (rc != 0) { + return -1; + } + + /* Set environment variables for SSH agent */ + torture_setenv("SSH_AUTH_SOCK", ssh_agent_sock); + torture_setenv("TORTURE_SSH_AGENT_PIDFILE", ssh_agent_pidfile); + + /* Add key to the agent if specified */ + if (add_key != NULL) { + snprintf(long_cmd, sizeof(long_cmd), "ssh-add %s", add_key); + rc = system(long_cmd); + if (rc != 0) { + return -1; + } + } + + return 0; +#else + /* On Windows, we don't set up an SSH agent */ + (void)s; + (void)add_key; + + /* Return failure to make it explicit that agent forwarding isn't supported + * on Windows */ + return -1; +#endif +} + +/** + * @brief Teardown an SSH agent + * + * This function kills the SSH agent process and cleans up environment + * variables. + * + * @return 0 on success, -1 on error + */ +int torture_cleanup_ssh_agent(void) +{ +#ifndef WIN32 + const char *ssh_agent_pidfile; + int rc; + + ssh_agent_pidfile = getenv("TORTURE_SSH_AGENT_PIDFILE"); + if (ssh_agent_pidfile == NULL) { + return 0; + } + + rc = torture_terminate_process(ssh_agent_pidfile); + if (rc != 0) { + return -1; + } + + torture_unsetenv("TORTURE_SSH_AGENT_PIDFILE"); + torture_unsetenv("SSH_AUTH_SOCK"); + + return 0; +#else + /* On Windows, we don't start an SSH agent, so nothing to clean up */ + return -1; +#endif +} diff --git a/src/libs/libssh-0.12.2/tests/torture.h b/src/libs/libssh-0.12.2/tests/torture.h new file mode 100644 index 000000000000..0120bd6fe827 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture.h @@ -0,0 +1,202 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _TORTURE_H +#define _TORTURE_H + +#include +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/sftp.h" + +#include + +#include "tests_config.h" +#include "torture_cmocka.h" + +#ifndef assert_return_code +/* hack for older versions of cmocka */ +#define assert_return_code(code, errno) assert_true(code >= 0) +#endif /* assert_return_code */ + +#define TORTURE_SSH_SERVER "127.0.0.10" +#define TORTURE_SSH_SERVER_IP6 "fd00::5357:5f0a" +#define TORTURE_SSH_USER_BOB "bob" +#define TORTURE_SSH_USER_BOB_PASSWORD "secret" + +#define TORTURE_SSH_USER_ALICE "alice" +#define TORTURE_SSH_USER_CHARLIE "charlie" + +/* Used by main to communicate with parse_opt. */ +struct argument_s { + const char *pattern; + int verbose; +}; + +struct torture_sftp { + ssh_session ssh; + sftp_session sftp; + char *testdir; +}; + +struct torture_ssh { + ssh_session session; + void *cb_state; /* For storing callback state */ + void *callbacks; /* For storing callbacks */ +}; + +struct torture_state { + char *socket_dir; + char *gss_dir; + char *pcap_file; + char *log_file; + char *srv_pidfile; + char *srv_config; + char *srv1_pidfile; + char *srv1_config; + bool srv_pam; + bool disable_hostkeys; + char *srv_additional_config; + struct { + ssh_session session; + struct torture_sftp *tsftp; + struct torture_ssh ssh; + } ssh; +#ifdef WITH_PCAP + ssh_pcap_file plain_pcap; +#endif + void *private_data; +}; + +#ifndef ZERO_STRUCT +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) +#endif + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments); + +int torture_rmdirs(const char *path); +int torture_isdir(const char *path); + +int torture_terminate_process(const char *pidfile); + +/* + * Returns the verbosity level asked by user + */ +int torture_libssh_verbosity(void); + +ssh_session torture_ssh_session(struct torture_state *s, + const char *host, + const unsigned int *port, + const char *user, + const char *password); + +ssh_session torture_ssh_session_proxyjump(void); + +ssh_bind torture_ssh_bind(const char *addr, + const unsigned int port, + enum ssh_keytypes_e key_type, + const char *private_key_file); + +struct torture_sftp *torture_sftp_session(ssh_session session); +struct torture_sftp *torture_sftp_session_channel(ssh_session session, + ssh_channel channel); +void torture_sftp_close(struct torture_sftp *t); + +void torture_write_file(const char *filename, const char *data); + +#define torture_filter_tests(tests) \ + _torture_filter_tests(tests, sizeof(tests) / sizeof(tests)[0]) +void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests); + +const char *torture_server_address(int domain); +const char *torture_server1_address(int domain); +int torture_server_port(void); + +int torture_wait_for_daemon(unsigned int seconds); + +#ifdef SSHD_EXECUTABLE +void torture_setup_socket_dir(void **state); +void torture_setup_sshd_server(void **state, bool pam); +void torture_setup_sshd_servers(void **state, bool pam); + +void torture_teardown_socket_dir(void **state); +void torture_teardown_sshd_server(void **state); +void torture_teardown_sshd_server1(void **state); + +int torture_update_sshd_config(void **state, const char *config); +#endif /* SSHD_EXECUTABLE */ + +#ifdef WITH_PKCS11_URI +void torture_setup_tokens(const char *temp_dir, + const char *filename, + const char object_name[], + const char *load_public); +void torture_cleanup_tokens(const char *temp_dir); +#endif /* WITH_PKCS11_URI */ + +void torture_reset_config(ssh_session session); + +void torture_setup_create_libssh_config(void **state); + +void torture_setup_libssh_server(void **state, const char *server_path); + +#ifdef WITH_GSSAPI +void torture_setup_kdc_server(void **state, + const char *kadmin_script, + const char *kinit_script); +void torture_teardown_kdc_server(void **state); +void torture_set_kdc_env_str(const char *gss_dir, char *env, size_t size); +void torture_set_env_from_str(const char *env); +#endif /* WITH_GSSAPI */ + +#if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) +__attribute__((weak)) int torture_run_tests(void); +#else +/* + * This function must be defined in every unit test file. + */ +int torture_run_tests(void); +#endif + +void torture_free_state(struct torture_state *s); + +char *torture_make_temp_dir(const char *template); +char *torture_create_temp_file(const char *template); + +char *torture_get_current_working_dir(void); +int torture_change_dir(char *path); + +void torture_setenv(char const *variable, char const *value); +void torture_unsetenv(char const *variable); + +int torture_setup_ssh_agent(struct torture_state *s, const char *add_key); +int torture_cleanup_ssh_agent(void); + +void torture_finalize(void); + +#endif /* _TORTURE_H */ diff --git a/src/libs/libssh-0.12.2/tests/torture_cmocka.c b/src/libs/libssh-0.12.2/tests/torture_cmocka.c new file mode 100644 index 000000000000..9c259f7391ad --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_cmocka.c @@ -0,0 +1,102 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" + +void _assert_ssh_return_code(ssh_session session, + int rc, + const char * const file, + const int line) +{ + char ssh_error[1024] = {0}; + + if (session != NULL) { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code - %s", + ssh_get_error(session)); + } else { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code"); + } + + _assert_true(rc == SSH_OK, + ssh_error, + file, + line); +} + +void _assert_ssh_return_code_equal(ssh_session session, + int rc, + int expected_rc, + const char * const file, + const int line) +{ + char ssh_error[1024] = {0}; + + if (session != NULL) { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code - %s", + ssh_get_error(session)); + } else { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code"); + } + + _assert_true((rc == expected_rc), + ssh_error, + file, + line); +} + +void _assert_ssh_return_code_not_equal(ssh_session session, + int rc, + int unexpected_rc, + const char * const file, + const int line) +{ + char ssh_error[1024] = {0}; + + if (session != NULL) { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code - %s", + ssh_get_error(session)); + } else { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code"); + } + + _assert_true((rc != unexpected_rc), + ssh_error, + file, + line); +} diff --git a/src/libs/libssh-0.12.2/tests/torture_cmocka.h b/src/libs/libssh-0.12.2/tests/torture_cmocka.h new file mode 100644 index 000000000000..c831743dc49d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_cmocka.h @@ -0,0 +1,55 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _TORTURE_CMOCKA_H +#define _TORTURE_CMOCKA_H + +#include "libssh/session.h" + +void _assert_ssh_return_code(ssh_session session, + int rc, + const char * const file, + const int line); + +#define assert_ssh_return_code(session, rc) \ + _assert_ssh_return_code((session), (rc), __FILE__, __LINE__) + +void _assert_ssh_return_code_equal(ssh_session session, + int rc, + int expected_rc, + const char * const file, + const int line); + +#define assert_ssh_return_code_equal(session, rc, expected_rc) \ + _assert_ssh_return_code_equal((session), (rc), (expected_rc), __FILE__, __LINE__) + +void _assert_ssh_return_code_not_equal(ssh_session session, + int rc, + int expected_rc, + const char * const file, + const int line); + +#define assert_ssh_return_code_not_equal(session, rc, unexpected_rc) \ + _assert_ssh_return_code_not_equal((session), (rc), (unexpected_rc), __FILE__, __LINE__) + +#endif /* _TORTURE_CMOCKA_H */ diff --git a/src/libs/libssh-0.12.2/tests/torture_key.c b/src/libs/libssh-0.12.2/tests/torture_key.c new file mode 100644 index 000000000000..a230484aea9b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_key.c @@ -0,0 +1,1137 @@ +/* + * torture_key.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "torture.h" +#include "torture_key.h" + +enum torture_format_e { + FORMAT_PEM = 0, + FORMAT_OPENSSH, + FORMAT_PKCS8, +}; + +/**************************************************************************** + * RSA KEYS + ****************************************************************************/ +static const char torture_rsa_private_testkey[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" + "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" + "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" + "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" + "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" + "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" + "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" + "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" + "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" + "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" + "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" + "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" + "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" + "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" + "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" + "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" + "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" + "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" + "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" + "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" + "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" + "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" + "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" + "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" + "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" + "-----END RSA PRIVATE KEY-----\n"; + +static const char torture_rsa_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsA5ERRaUFckAp\n" + "nmEAFjLGdFrINk/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34lHJaO+WXbDYYj7duW3\n" + "SP7H9lbCMwq79BhzJxinkcvTWCjE7G66xluL4qIdEYHrPQQx1cztTzZTuUD+P/8f\n" + "JmmnIONQOeJZptdAmB7ySwZcZOIV4An/rzu5X4klyMY/EAYVDHPKOK1/8Wsv1LRY\n" + "YplvKp4YPPJ4FnU0si5qI45HIsZJbh24csM3vwSawmfCqDaAlCZFJoPgE1kyO1t+\n" + "IVxIv1TDhdAVOxa6BQMRjUBThzmDXWeHMfMGL2ow63kPOtlCkPiPSADYs4ekeGg5\n" + "2DVm4esZAgMBAAECggEAUlNqrL+OWWry9nhMqIP3TTAG+uezke1KUclN7zSGpChP\n" + "5JrVxZLE9HR56QvfYePL2KBszNnze5bOHsia2A04PL29F3Od+A7wJ9MJoF8DNCyM\n" + "WmvxCKQxPoRQj8umJEvVRoaw06VIS+dJLRlvW4PMB2uMv6ewQcQMm534r2PBKQJG\n" + "J9UzLt8t9q6b5gql14TPsrYyCbmkn9WD72y8jZXWrscA4jh5RVOAk0dfLl52JEmV\n" + "qNAd/+U1TMcqqfuIf4lGXTWC3rZfyugbrZtNsXKVuF6oyb1rC5IXa5w5zH6C67CG\n" + "Q+KvwEQxvTyPeGIr5N/A91jQXpAfg6GIorsxOpNWAQKBgQDS+aa7kY4G3BGarqBU\n" + "0IeF0sNBZrEBmpBGZFvPFWEWlu/QaNtAQXu222rd+MOvMN/oIT1G1lbDYFRJdWr4\n" + "hcH9tdvWSBZTbFbLCb44UOT05dCzy6L4xjsWI/fK6rdgmhvaDkIKxVBaXYIe9YaZ\n" + "MuETU4OWXf23tjW7B1wOhsk34QKBgQDQuVGF8yPJ1abCqihT1MONc0GbC5/JiU0V\n" + "MffnVqZiAVQOk1/flIwI/iEOixTD/MJMOlz7XDTgEkl+QfVI29dI6KWl1phFNi6v\n" + "IO8aZYLrorYpfWcsSuGWGzXsLDNX33udiAMfc45Tgd+ogqZ0ZuO9aWSZj6YsfIbX\n" + "Rtuk5Nu6OQKBgBzrzEd6NJYMJP2nYqM90tYthka45RrrLUv5BfzlWWHXXxiaxzOP\n" + "xn3Splu3dlvVv1v68oN7bnlqAyPuzLM04tr8yx4c07Kr71n2A/WEYV6RAoMjLzwb\n" + "MI5gb9+JPYoolhjWS4dedR1uJCBKWCE4MFKN6EP8roM4nzT8VUpIf90BAoGBAMn5\n" + "qlPOlmEnZ/mTovaMOrdUQrYAJ51kSb0q31NzXAoWcvqvzeSS4HeLQv3JxExq+LG/\n" + "AF8UOO5jg86zkOHyKn+Y8UJrSGohJke9vOJ0PJ48k73OZdpMk8MnjaR+mSh9UCd+\n" + "iFcVATL5yC5OTzjwn4yYwgp/ITSHRAxpuv3zoFSxAoGARWCiLhFfCa0ai+0x7DTU\n" + "5E9X0w5fe/5WewKAPj/npREo4CeCJLvTEQ843A1tMJa66/jccbMmhUWIAwadA/UE\n" + "AnuCQeSNJ6A4YqSLDXYQ9RaEVwyD4aSA+Fyk6o239UwE+Mn/KwcmqYt9Ar0DfklX\n" + "oKZ2mF744iXbxtwqEA7PwPA=\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_rsa_private_testkey_passphrase[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5375534F40903DD66B3851A0DA03F6FA\n" + "\n" + "m5YYTNOMd1xCKfifwCX4R1iLJoAc4cn1aFiL7f2kBbfE2jF1LTQBJV1h1CqYZfAB\n" + "WtM/7FkQPnKXqsMndP+v+1Xc+PYigE3AezJj/0g7xn/zIBwGjkLAp435AdL5i6Fg\n" + "OhOL8LyolRrcGn17jE4S4iGbzw8PVyfzNzdj0Emwql5F6M7pgLbInRNKM/TF4z2h\n" + "b6Pi9Bw43dwaJ7wiiy/vo/v4MyXsJBoeKbc4VCmxiYFvAYCvVFlDkyIw/QnR3MKQ\n" + "g/Zsk7Pw3aOioxk6LJpZ5x0tO23nXDG1aOZHWykI0BpJV+LIpD2oSYOHJyVO83XT\n" + "RQUMSTXc2K2+ejs0XQoLt/GxDDHe+8W8fWQK3C7Lyvl9oKjmb5sTWi3mdSv0C+zR\n" + "n5KSVbUKNXrjix7qPKkv5rWqb84CKVnCMb7tWaPLR19nQqKVYBIs6v0OTTvS6Le7\n" + "lz4lxBkcUy6vi0tWH9MvLuT+ugdHLJZ4UXBthCgV58pM1o+L+WMIl+SZXckiCAO3\n" + "7ercA57695IA6iHskmr3eazJsYFEVFdR/cm+IDy2FPkKmJMjXeIWuh3yASBk7LBR\n" + "EQq3CC7AioO+Vj8m/fEIiNZJSQ6p0NmgnPoO3rTYT/IobmE99/Ht6oNLmFX4Pr7e\n" + "F4CGWKzwxWpCnw2vVolCFByASmZycbJvrIonZBKY1toU28lRm4tCM6eCNISVLMeE\n" + "VtQ+1PH9/2KZspZl+SX/kjV3egggy0TFKRU8EcYPJFC3Vpy+shEai35KBVo44Z18\n" + "apza7exm3igNEqOqe07hLs3Bjhvk1oS+WhMbAG9ARTOKuyBOJh/ZV9tFMNZ6v+q5\n" + "TofgNcIhNYNascymU1io18xTW9c3RRcmRKqIWnj4EH8o7Aojv/l+zvdV7/GVlR4W\n" + "pR9cuJEiyiEjS46axoc6dSOtdnvag+BpFQb+lGY97F9nNGyBdtLD5ASVh5OVG4fu\n" + "Pf0O7Bdj1kIuBhV8axE/slf6UHANiodeqkR9B24+0Cy+miPiHazzUkbdSJ4r03g5\n" + "J1Y5S8qbl9++sqhQMLMUkeK4pDWh1aocA9bDA2RcBNuXGiZeRFUiqxcBS+iO418n\n" + "DFyWz4UfI/m1IRSjoo/PEpgu5GmosUzs3Dl4nAcf/REBEX6M/kKKxHTLjE8DxDsz\n" + "fn/vfsXV3s0tbN7YyJdP8aU+ApZntw1OF2TS2qS8CPWHTcCGGTab5WEGC3xFXKp0\n" + "uyonCxV7vNLOiIiHdQX+1bLu7ps7GBH92xGkPg7FrNNcMc07soP7jjjB578n9Gpl\n" + "cIDBdgovTRFHiWu3yRspVt0zPfMJB/hqn+IAp98wfvjl8OZM1ZZkejnwXnQil5ZU\n" + "wjEBEtx+nX56vdxipzKoHh5yDXmPbNajBYkg3rXJrLFh3Tsf0CzHcLdHNz/qJ9LO\n" + "wH16grjR1Q0CzCW3FAv0Q0euqkXac+TfuIg3HiTPrBPnJQW1uivrx1F5tpO/uboG\n" + "h28LwqJLYh+1T0V//uiy3SMATpYKvzg2byGct9VUib8QVop8LvVF/n42RaxtTCfw\n" + "JSvUyxoaZUjQkT7iF94HsF+FVVJdI55UjgnMiZ0d5vKffWyTHYcYHkFYaSloAMWN\n" + "-----END RSA PRIVATE KEY-----\n"; + +static const char torture_rsa_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI0RSm1ZXOBD8CAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBBS+59quuIVuxN/H9Wltk8TBIIE\n" + "0J7OhRw35ANRyTU2qhlhS8NATcguoD1J4IMXpXpv38iCBWd2bjxvuWnEu4aBX7iU\n" + "desfz9n6AoTVqURaOMLsv6EFV0tycf+mZsmdUmrD2270Wyj6TtQD8LO/7ibifCeL\n" + "XCCKjxciueSggHp5lnfogZwn8wjSEDP7OqNVRTwm8QKNrE7J5m5giFrjXoyqKM7r\n" + "DBa35UIZAXXY8z9CkI+GsyRtaZik3VD+xHShwUriOYg4x4VGZQLj24tjoUnqU4ml\n" + "iRMhGyYpxN7CnfaIwHJr3T0dmbT/BIXOQ2B6sWakioZeUuA6OTBHbFTUN9TUHaF0\n" + "rDMVmjL6BQcEiWwjvtw/3NLdkcKFjMiLTWA2GL71KPGCecpMmAMjo+ijnxeVhqpQ\n" + "dnhowG92DhCSf/XZI0vaaYflrV54U9PgcSPDFWmTOVe5151Mi8eR9qrCanfyHmX1\n" + "MLXs8Mw6xWedNj8AWLV3JGiWEeAEATuTAQfTqmBZbzaFKfSKp5PZjWxa5bZIomzS\n" + "Q0AsONTeYmKK+Pv95RYlgR2kKqhwy3OmcOuepwnzSeAGh1BdBzd2raoipkq1fpY5\n" + "8e75dJnTGvWfqfh0VXz/Wud+hMz/98Mh6Bnp9l+Ddxpp4RioWB2aH0HM8ZGTlbhf\n" + "r5qFmDY7k+RfDDp7K7UYMA+2hHCxY1aFSHVYGRQKdYdKIugLtKx6YKLeGVCR7Gbm\n" + "l/88qiGshF/qhdFbPb4K0Tz2Ug5uklveOQSkKX6RSZ30IW+N3E4nH/wvyOwbCPk7\n" + "u+iHB2zzk2Hws4O52a0Gqj+RbeGzzhl1D9jH35GMHUsfhDSA3/mmrVC7hiN/Aplt\n" + "2OmKFAkobZh/1UJAHBY9feIhLmQUy9dwy0E8G/0LEyyZYEizDC76jsvbh2cPg3jM\n" + "JsI31qUaGggwh3wB034BvsYIf/ZqLCt8hAXF9U5U7T5y3r6FNNBla8zlj25ILog6\n" + "t/bhOwFKYXamAVYMhhvUiA3YIYuBxT7MrgL7gDtKh3N/DleS/pLjmOFfMI3dfCd0\n" + "KSQX46uw7aFbV0Has9uUuGle9Foq52QFvYnDHWJuIyOvJ5st1Hd3Mjjsl9t3JFVM\n" + "I1aDZ17Z4LoThdezNQKGaAe5z7gGFMKKsm55CMT/7FxvConALeQKGAV6jA5xZzl4\n" + "+QB14YlxlZTxYnXd/69KGV56wP8sb6uMVDC/f5Vd3oHsamJKpPgts8WCn11f9wFn\n" + "Mx8YY/vBVVLQMw1aB+82Vk+Ix8YDYIPj5bJk2BkyCCUnMYkKswUOVzsdUq0xssEp\n" + "PASw0YvQ9mY2aQ9exme99JuAj5t4qIXoYTSrX5iv6NXtzDHgTR1pl9gQQVQ0zAUO\n" + "ZHKZXYAv5rLZKRcyeCLw0LkuthY2QtN3PsBlaRtfwZTaqUbBGbvEkcx5fxdEsasS\n" + "yQkZKBBvIi42LUN9ZzywYNGbOanCZ04p/+QscmmnVGuDMZJyaDRaapW6f0nJQ+lQ\n" + "CaVPRzLKGnHV5hWQDjTaPIh2s9rJSZJ3HyE8qshETHW/vQoYIcVB9TX5TnOY02Ak\n" + "IINKfSZGgz/NBeJItjk30UuTcISk65ekoXZIHHgdxD9iHy9D0w6FXcPNLLsWQn7n\n" + "jS4Bvt0VZ9zVAiyyVO4yAaMgP+saitYpjMgI8g67geD3\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_rsa_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDX\n" + "ClCBeHgYyOEqmWpAanz9AAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAAB\n" + "AQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP2PhOh5pyxa1VfGKe\n" + "453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjOFvRRTLot5fPLNDOv\n" + "9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpbJvTXSDipukpUy41d\n" + "7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdcARyhix4RMWZZqVY2\n" + "0H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1TjzkrBZ6B6thmZty\n" + "eb9IsiB0tg2g0JN2VTAGkxqpAAADwG8gm8jZpx+GIKdhV+igcvYvIhzA+fz6UdXf\n" + "d/8wnYzMXtg+Ys7XsKUsxtMD8HGPiuwYsTrd/YGiol7SpkJV0STqtW+UZrcKamJ5\n" + "reFaDoIU8hhWTXCe/ogplTxH/zNNK7Xx5OAGnNWE3zsR1vbZaCv+Vwwa27eUCbpv\n" + "V1+92nBwkah3FCKCbwYDvTVRn1TZHQwnuNxDCRrlwaMjf8eX2ssqLLX7jqrb3j1u\n" + "c28GR3fNJ8ENaWshZ77tqexUQCnCx14/qtT434CMvENXnCP5BP/cRmbOlCFQ6Id7\n" + "nLMW0uDIy/q3xBsAcdMyV0LJW7sJNXIjTnS4lyXd0XescXrqTAKxTkqd1E0VIBpc\n" + "37+7vqv9A9Xxq74jy//L9L4Yrbijc9Vt+oNWFgOuakZGBLIQvm36Oqb0z0oWJcUt\n" + "VdZcvkCNMeixBqCnrQ8egO3x0pnZwo6cwH586Me8FgFacOnzWjzuQT6vYJ4EK5ch\n" + "YNRQpjtz5+T3rZK7eIF1ZUobM4S6di7A6lW9tycQVhjo5XlhalMfCfajhazgcIrY\n" + "Qdaq8+AguP8H+3bvXPZmitL8/mv5uVjqxy1lYh2xLzViTmFnvfdbZ92BWI9C6JBI\n" + "+mRWzXeEY71MjfeEaPStwBm5OYBMFwYrXPL7E3JjAXRxbB+LKUksj/lRk3K7aQp4\n" + "IDKCzAACgkOixfP39BgKQkrLjAoi6mEDqu5Ajc3GoljXsJEkcbu0j+0tVth+41nV\n" + "8yCkP5SVUQTCSKzoduE+0pk6oYO6vrwKLM62cQRPXLl/XNoUqETIe8dklIKojYo6\n" + "3ho1RaHgYr9/NAS0029CFt/rGmONWF9ihKON6wMavJRcofZ25FeylKiP2rrqdDIb\n" + "EiWULZi3MUJfKBwSeZMwaYYmSpaOZF1U/MgvEfeRkE1UmDp3FmBLSNHBYhAxNazH\n" + "R393BTr1zk7h+8s7QK986ZtcKkyUNXEK1NkLLuKlqMwFnjiOdeAIGwz9NEn+Tj60\n" + "jE5IcCE06B6ze/MOZcsPp1SoZv4kKmgWY5Gdqv/9O9SyFQ0Yh4MvBSD8l4x0epId\n" + "8Xm54ISVWP1SZ1x3Oe8yvtwOGqDkZeOVjnP7EQ7R0+1PZzW5P/x47skACqadGChN\n" + "ahbngIl+EhPOqhx+wIfDbtzTmGABgNhcI/d02b8py5MXFnA+uzeSucDREYRdm2TO\n" + "TQQ2CtxB6lcatIYG4AhyouQbujLd/AwpZJ05S1i/Qt6NenTgK3YyTWdXLQnjZSMx\n" + "FBRkf+Jj9eVXieT4PJKtWuvxNNrJVA==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_rsa_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz\n" + "c2gtcnNhAAAAAwEAAQAAAQEA1717msM2gKxMJCyF08/xFssCzgrakC9z/cipK4c+\n" + "gKZVz9j4ToeacsWtVXxinuOd4zXmASdETraNwbIV4GV26POqC105G3tIDucYt6+V\n" + "o18Yzhb0UUy6LeXzyzQzr/fk57D5pDZ9IiHsj5363DyMo1rec0llH5DQvKDR8bIk\n" + "rIY6Wyb010g4qbpKVMuNXe44OLlhrqJzExe8rux37h6qu5YW9FpcEqX26gG32AEQ\n" + "XXXHXAEcoYseETFmWalWNtB91bI6vG4ygvjSVxXHtT0YPqnDlkHZVxHouHZMwLx7\n" + "30EcdU485KwWegerYZmbcnm/SLIgdLYNoNCTdlUwBpMaqQAAA7iQHqVWkB6lVgAA\n" + "AAdzc2gtcnNhAAABAQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP\n" + "2PhOh5pyxa1VfGKe453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjO\n" + "FvRRTLot5fPLNDOv9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpb\n" + "JvTXSDipukpUy41d7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdc\n" + "ARyhix4RMWZZqVY20H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1\n" + "TjzkrBZ6B6thmZtyeb9IsiB0tg2g0JN2VTAGkxqpAAAAAwEAAQAAAQAdjR3uQAkq\n" + "LO+tENAwCE680YgL0x7HG0jnHWJWzQq5so8UjmLM1vRH/l3U1Nnpa8JHyi08QTWx\n" + "Fn5qZstqVluoYyAKuHVHF2bya6NOHeYAX9lU+X3z2O+zs8jmL7tYwjr/pZU8ch5H\n" + "25+8uGYRXtXg1mScJBSO81Y0UE8RrVYqr2Os583yB657kYiVYYYSZlRGd9wmfXnJ\n" + "w0t8LaYcTn+i/lOvrJGa0Q0iV6+4rYmjwYd/D/vyNzF31hUEFrn3vDSgTnJdShgH\n" + "VqW0OwNuEDe/4p8KkKR1EVVj6xv4zicwouY7aQI+zT3MwAzvNdvYwytsIj6bhT9x\n" + "oyeAAIW0vaKVAAAAgQD6pPfu6tb7DiTlaH3/IPdGh3PTIf0zXHZ/ygxORXBZdoLY\n" + "Fq2h/YnBd2Hs8vARAjGJYs78gTPP0FVXPV8ut38xct4DQ2hbPMrjWv5gdhDazq8Q\n" + "qaFEa0+DeYONej8ItKwpsV2Rskkv5Pfm7M6EffVty1uzOpIcT8RYDAYUlc5D/wAA\n" + "AIEA+44ykLho3BDWnUzshVEm6iNoqlZqcDVcNSpCuYDnCy5UrTDk0zj+OUG9M0Zx\n" + "4c7kAmu/poXSimgAgMh9GNCzy3+a70WvH+fBqvG5tXLaSOQCswSdQjltANAnlt5L\n" + "YDHzGGJBsS4pYxoz22MKhFbpYUCQJvotXnZJpTQU6hdFRX8AAACBANuNSlFq/vG8\n" + "Vf9c2YsPiITmOrYxpUDMiMLvUGQOdyIIc45EAggOFHNF3AdPZEhinpD92EK+LiJc\n" + "WYJ26muVcicZoddgmpcHRt2gByC+ckWOM4sLpih6EyQLFZfqTx2X+KOI0ZTt7zEi\n" + "zfm1MJUNDFOr3DM0VBIf34Bn1hU/isPXAAAAAAEC\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_rsa_public_testkey[] = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsA5ERRaUFckApnmEAFjLGdFrIN" + "k/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34lHJaO+WXbDYYj7duW3SP7H9lbCMwq79B" + "hzJxinkcvTWCjE7G66xluL4qIdEYHrPQQx1cztTzZTuUD+P/8fJmmnIONQOeJZptd" + "AmB7ySwZcZOIV4An/rzu5X4klyMY/EAYVDHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0" + "si5qI45HIsZJbh24csM3vwSawmfCqDaAlCZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6B" + "QMRjUBThzmDXWeHMfMGL2ow63kPOtlCkPiPSADYs4ekeGg52DVm4esZ " + "aris@aris-air\n"; + +static const char torture_rsa_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArAOREUWlBXJAKZ5hABYy\n" + "xnRayDZP1bJeLbPVK+npxemrhHyZgjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZW\n" + "wjMKu/QYcycYp5HL01goxOxuusZbi+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDj\n" + "UDniWabXQJge8ksGXGTiFeAJ/687uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqe\n" + "GDzyeBZ1NLIuaiOORyLGSW4duHLDN78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9U\n" + "w4XQFTsWugUDEY1AU4c5g11nhzHzBi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHr\n" + "GQIDAQAB\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_rsa_testkey_cert[] = + "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNz" + "aC5jb20AAAAgL77S/SgY969FbEtNBsbLvvtGFgnEHaPb+V7ajwuf+R0AAAADAQABA" + "AABAQCsA5ERRaUFckApnmEAFjLGdFrINk/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34" + "lHJaO+WXbDYYj7duW3SP7H9lbCMwq79BhzJxinkcvTWCjE7G66xluL4qIdEYHrPQQ" + "x1cztTzZTuUD+P/8fJmmnIONQOeJZptdAmB7ySwZcZOIV4An/rzu5X4klyMY/EAYV" + "DHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0si5qI45HIsZJbh24csM3vwSawmfCqDaAl" + "CZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6BQMRjUBThzmDXWeHMfMGL2ow63kPOtlCkP" + "iPSADYs4ekeGg52DVm4esZAAAAAAAAAAAAAAABAAAADmxpYnNzaF90b3J0dXJlAAA" + "AAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRp" + "bmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" + "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdX" + "Nlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAoowcv2Gn8tO" + "eDyw/lgdMpoBsLtHTTdVVOOo5HwMFvj/lFkbZlb6J2n9GIE64HNPE45vSnIdJZwz4" + "UYfTvtnNKNHp1MgMrjK1Z6EjyZsGqDZ+BhmvcKA6IckkhBJnDV7U9dMrovAWha61Z" + "9GpDqB1naRfbwqJQwSRHF1p71Cnf0fZKxOhAVx0ophmYGz3x3qq4PeOZv3Yl0AHTV" + "dRmqmeELDUxeuXN2bgSyb881zEgdaKHH5oWySykP4uwjn6T7ETuL2MsDdG3HZHDhn" + "LzLmfzOZ/cNadMCrgauMluQKc5dYF2TSeDaUxwun/NPMQBVZdETHLAMBgkGmhRUku" + "flVDIQAAAQ8AAAAHc3NoLXJzYQAAAQADSp4b/Zta8zs6v47iwmxV2Gbucvt1kDrvT" + "vKAKSbGN0+zoMyXiNfMHM/OvZObDS/WWGs4GMRqbJavwO3ja/dQY17oJss23lZ+Rc" + "Lw4Rqsi3/ZEPCnX6ficiRS/yRN/LAkoXvx9vBx9QHfxlzF6JXq07wTt21zxW0tntd" + "8dL+JI9ZZ9YylnxF3gHqfRFe2ahJpiywmxm0yOZgDmimOhep59i6BH5zHiPALvpge" + "Mbk075oA5K9XKsHTflCcsQRQH+pXqaNQGL37z2CFz9oezxQYvIqqKF0w/eeRIARoA" + "neB6OdgTpKFsmgPZVtqrvhjw+b5T8a4W4iWSl+6wg6gowAm " + "rsa_privkey.pub\n"; + +/**************************************************************************** + * DSA KEYS + ****************************************************************************/ + +static const char torture_dsa_private_testkey[] = + "-----BEGIN DSA PRIVATE KEY-----\n" + "MIIBuwIBAAKBgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" + "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" + "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwIVAK84\n" + "j20GmU+zewjQwsIXuVb6C/PHAoGAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHOzMB1\n" + "xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzPiXDu\n" + "l0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GSBZPX\n" + "MpUdng8CgYB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7\n" + "n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2u\n" + "ADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgIVAI1Hd8/i\n" + "Pzsg7bTzoNvjQL+Noyiy\n" + "-----END DSA PRIVATE KEY-----\n"; + +static const char torture_dsa_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIIBSwIBADCCASsGByqGSM44BAEwggEeAoGBAJTK9U8SSfdSdkOPMLNLNIelOW3O\n" + "vQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fAC7ltzHDxOQhKrthE5Dj\n" + "T0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5qX3HUnFOZzpPQYGrW\n" + "XK74JNRTKHblo0MXAhUArziPbQaZT7N7CNDCwhe5VvoL88cCgYBeG4hWwnFRAnmd\n" + "ZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWTMN4BlTJzPfVaoYvzJ\n" + "ev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+UGeAruNBEhVSDcX\n" + "fXTh9tVravBqeIuXgZIFk9cylR2eDwQXAhUAjUd3z+I/OyDttPOg2+NAv42jKLI=\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_dsa_private_testkey_passphrase[] = + "-----BEGIN DSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,266023B64B1B814BCD0D0E477257F06D\n" + "\n" + "QJQErZrvYsfeMNMnU+6yVHH5Zze/zUFdPip7Bon4T1wCGlVasn4x/GQcMm1+mgmb\n" + "PCK/qJ5qw9nCepLYJq2xh8gohbwF/XKxeaNGcRA2+ancTooDUjeRTlk1WRtS1+bq\n" + "LBkwhxLXW26lIuQUHzfi93rRqQI2LC4McngY7L7WVJer7sH7hk5//4Gf6zHtPEl+\n" + "Tr2ub1zNrVbh6e1Bitw7DaGZNX6XEWpyTTsAd42sQWh6o23MC6GyfS1YFsPGHzGe\n" + "WYQbWn2AZ1mK32z2mLZfVg41qu9RKG20iCyaczZ2YmuYyOkoLHijOAHC8vZbHwYC\n" + "+lN9Yc8/BoMuMMwDTMDaJD0TsBX02hi9YI7Gu88PMCJO+SRe5400MonUMXTwCa91\n" + "Tt3RhYpBzx2XGOq5199+oLdTJAaXHJcuB6viKNdSLBuhx6RAEJXZnVexchaHs4Q6\n" + "HweIv6Et8MjVoqwkaQDmcIGA73qZ0lbUJFZAu2YDJ6TpHc1lHZes763HoMYfuvkX\n" + "HTSuHZ7edjoWqwnl/vkc3+nG//IEj8LqAacx0i4krDcQpGuQ6BnPfwPFco2NQQpw\n" + "wHBOL6HrOnD+gGs6DUFwzA==\n" + "-----END DSA PRIVATE KEY-----\n"; + +static const char torture_dsa_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI8001emUNAOECAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBDgXXvQsVxY6zaAQVwzUwvDBIIB\n" + "UOBQqqJs4rYK6R0rXFitkdUodOK3CdFAKodyCkSC5cgoW2+ht2ndRCepxuKB2X14\n" + "Lvt1CIxPvu1k7bGnd25kePmNF85cJxG9wf0/+6vpptO3fTUdsUKyLcRKDqvxxOMB\n" + "OSqQK1MLgvUxB5uBSGCsKqFkVUPYs46uihfozjqHH2IghHSQr+VczhFDoWtzgcgp\n" + "nRNZiyXN5Thob5WOrL849TSlcaMyI3ssErEVP1G2t3ax5bLQ4AqDddumoRBed/XY\n" + "lad5QGAS2XlwMFj8tR/Spi1fEWfamIsvh23ba5ksb35TT3SUJd2gf2NC7QEz3dUK\n" + "YDSSeRSF24c4nXBsJ94TkVuUujo4X3QSaWQ2anYYBBwfQtrddVNVu95QS2sQGLov\n" + "UWIhq1xXbnL/SGC6E5T1VGnAx3qwfDEZX5tTNzkwqeTZfkrb6vRk+O+Lxt67iP+n\n" + "nw==\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_dsa_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBC\n" + "UZK61oXs3uKMs4l7G0cpAAAAEAAAAAEAAAGxAAAAB3NzaC1kc3MAAACBAJTK9U8S\n" + "SfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+f\n" + "AC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hod\n" + "Qn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW\n" + "+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8c\n" + "NlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0\n" + "kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQ\n" + "Bfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv\n" + "1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/\n" + "iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgAAAeAtGFEW6JZTeSumizZJI4T2\n" + "Kha05Ze3juTeW+BMjqTcf77yAL2jvsljogCtu4+5CWWO4g+cr80vyVytji6IYTNM\n" + "MPn1qe6dHXnfmgtiegHXxrjr5v5/i1cvD32Bxffy+yjR9kbV9GJYF+K5pfYVpQBa\n" + "XVmq6AJUPd/yxKw6jRGZJi8GTcrKbCZAL+VYSPwc0veCrmGPjeeMCgYcEXPvhSui\n" + "P0JnG1Ap12FeK+61rIbZBAr7qbTGJi5Z5HlDlgon2tmMZOkIuL1Oytgut4MpmYjP\n" + "ph+qrzgwfSwOsjVIuHlb1L0phWRlgbT8lmysEE7McGKWiCOabxgl3NF9lClhDBb9\n" + "nzupkK1cg/4p17USYMOdeNhTmJ0DkQT+8UenfBOmzV7kamLlEYXJdDZBN//dZ8UR\n" + "KEzAzpaAVIyJQ+wvCUIh/VO8sJP+3q4XQUkv0QcIRlc0+r9qbW2Tqv3vajFcFtK6\n" + "nrTmIJVL0pG+z/93Ncpy5susD+JvhJ4yfl7Jet3jy4fWwm3qkLl0WsobJ7Om+GyH\n" + "DzHH9RgDk3XuUHS/fz+kTwmtyIH/Rq1jIt+s+T8iA9CzKSX6sBu2yfMo1w2/LbCx\n" + "Xy1rHS42TePw28m1cQuUfjqdOC3IBgQ1m3x2f1on7hk=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_dsa_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdz\n" + "c2gtZHNzAAAAgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" + "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" + "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwAAABUA\n" + "rziPbQaZT7N7CNDCwhe5VvoL88cAAACAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHO\n" + "zMB1xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzP\n" + "iXDul0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GS\n" + "BZPXMpUdng8AAACAeK6A0AX4H/AKEgCQG+8vDlb8beXE+4qmkTEF9l4LVtPTbvUW\n" + "1CFUu59psN4qmRtMZxt4r9aC/YMDVzC5X/oC5rKlN+/PI2hGfSIWfFXYBwKB5ulF\n" + "jO29rgA5oYqyOnUWVFTvv4jBlLw8WuujiOIQ1dOeaW8EIbcbqVGrU4cg8yIAAAHY\n" + "tbI937WyPd8AAAAHc3NoLWRzcwAAAIEAlMr1TxJJ91J2Q48ws0s0h6U5bc69BHPt\n" + "Zs/yQAoxzASfQv1vz8Lx98o5H6BYYURL6GOyD58ALuW3McPE5CEqu2ETkONPT6sM\n" + "D78tDdxlKf/pCktuvASCflSjedd10X+3i9XuGh1CfmpfcdScU5nOk9BgatZcrvgk\n" + "1FModuWjQxcAAAAVAK84j20GmU+zewjQwsIXuVb6C/PHAAAAgF4biFbCcVECeZ1k\n" + "ES3+6N1xBkPhHFZhzszAdcQrB42J6RIYXKPq/xw2Ug99ZMw3gGVMnM99Vqhi/Ml6\n" + "/jmUS6hLBgu3cBAMz4lw7pdMx3JdMoij60ay4DSR3inE+iX5QZ4Cu40ESFVINxd9\n" + "dOH21Wtq8Gp4i5eBkgWT1zKVHZ4PAAAAgHiugNAF+B/wChIAkBvvLw5W/G3lxPuK\n" + "ppExBfZeC1bT0271FtQhVLufabDeKpkbTGcbeK/Wgv2DA1cwuV/6AuaypTfvzyNo\n" + "Rn0iFnxV2AcCgebpRYztva4AOaGKsjp1FlRU77+IwZS8PFrro4jiENXTnmlvBCG3\n" + "G6lRq1OHIPMiAAAAFQCNR3fP4j87IO2086Db40C/jaMosgAAAAABAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_dsa_public_testkey[] = + "ssh-dss AAAAB3NzaC1kc3MAAACBAJTK9U8SSfdSdkOPMLNLNIelOW3OvQRz7WbP8k" + "AKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fAC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3c" + "ZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0" + "MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD" + "4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3" + "AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuX" + "gZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9R" + "bUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM" + "7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIg==\n"; + +static const char torture_dsa_testkey_cert[] = + "ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNza" + "C5jb20AAAAgKAd9MpIBrzctQyJvCYYJ2WUD5fyWlXMSv1G/3VihbCAAAACBAJTK9U8" + "SSfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fA" + "C7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5" + "qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzx" + "wAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWT" + "MN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+" + "UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJA" + "b7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+" + "gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hD" + "V055pbwQhtxupUatThyDzIgAAAAAAAAAAAAAAAQAAAA5saWJzc2hfdG9ydHVyZQAAA" + "AAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5" + "nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvc" + "nQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXI" + "tcmMAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAKKMHL9hp/LTng8sP" + "5YHTKaAbC7R003VVTjqOR8DBb4/5RZG2ZW+idp/RiBOuBzTxOOb0pyHSWcM+FGH077" + "ZzSjR6dTIDK4ytWehI8mbBqg2fgYZr3CgOiHJJIQSZw1e1PXTK6LwFoWutWfRqQ6gd" + "Z2kX28KiUMEkRxdae9Qp39H2SsToQFcdKKYZmBs98d6quD3jmb92JdAB01XUZqpnhC" + "w1MXrlzdm4Esm/PNcxIHWihx+aFskspD+LsI5+k+xE7i9jLA3Rtx2Rw4Zy8y5n8zmf" + "3DWnTAq4GrjJbkCnOXWBdk0ng2lMcLp/zTzEAVWXRExywDAYJBpoUVJLn5VQyEAAAE" + "PAAAAB3NzaC1yc2EAAAEAAt4V9aGqeahOfUvhG7M8/Mn26aLB/HXbICYFJF7dY6urm" + "SIoS2KBqISCFGXTituiwGlZeAJ+pVgCMYo07Nxtd6oqIjsgKfJqDNx7e4pGw/YJnkm" + "BqMO/k/ygu2mLmQF0lnpmG2KyjKEljMibHaKlFkcVNbwfOb4p8N3OHm66g5mbCUTRZ" + "DHqMSJb3YtnObLexD13RydwxkG5AfCnOWxy5O4agXGEYwr/48AQBHYg9obGtpD1qyF" + "4mMXgzaLViFtcwah6wHGlW0UPQMvrq/RqigAkyUszSccfibkIXJ+wGAgsRYhVAMwME" + "JqPZ6GHOEIjLBKUegsclHb7Pk0YO8Auaw== " + "aris@aris-air\n"; + +/**************************************************************************** + * ECDSA KEYS + ****************************************************************************/ + +static const char torture_ecdsa256_private_testkey[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "MHcCAQEEIBCDeeYYAtX3EnsP0ratwVpNTaA/4K1N6VvHMiUZlVdhoAoGCCqGSM49\n" + "AwEHoUQDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7VtFHA0EbNHqBUQxoh+Ik\n" + "89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEIN55hgC1fcSew/S\n" + "tq3BWk1NoD/grU3pW8cyJRmVV2GhRANCAATH7253zxDkZa0t37Iy1hpqkLzmD7Zk\n" + "untW0UcDQRs0eoFRDGiH4iTz0yWvsBTFwU936QKj40TrKr+Y1BwvsTqJ\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_testkey_passphrase[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" + "\n" + "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" + "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" + "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAhvndbkbElTnAICCAAw\n" + "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEOu4ierPcQpcA9RJNHUbTCoEgZBe\n" + "iusOkUYp4JZJEIpi98VlqnROzDXHpTTpEGiUDC/k+cuKvoPop5+Jx0qXp+A1NJxu\n" + "kx3j+U0ISGY7J6b2Pqt1msC/FzqpeFM7ybuHDRz+c5ZBONTp8wrs52d5NdjrYguz\n" + "UO6n9+yydSsO0FqbwPaqNZ6goBN0TfhYnToG4ZPJxlHa7gf7Su4KSMYKZdOtfx4=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTH7253zxDkZa0t37Iy\n" + "1hpqkLzmD7ZkuntW0UcDQRs0eoFRDGiH4iTz0yWvsBTFwU936QKj40TrKr+Y1Bwv\n" + "sTqJAAAAmOuDchHrg3IRAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy\n" + "NTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPT\n" + "Ja+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAgEIN55hgC1fcSew/Stq3BWk1NoD/g\n" + "rU3pW8cyJRmVV2EAAAAA\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_openssh_testkey_pasphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABA+\n" + "O0w3yPZF2q0FjVBhQjn2AAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAy\n" + "NTYAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNB\n" + "GzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAACghvb4EX8M06UB\n" + "zigxOn9bg5cZkZ2yWY8jzxtOWH4YJXsuhON/jePDJuI2ro5u4iKFD1u2JLfcshdh\n" + "vKZyjixU9KdewykQQt/wFkrCfNUyCH8jFiQsAqhBfopRFyDJV9pmcUBL/3fJqwut\n" + "ZeBSfA7tXORp3xrwFI1tXiiUCM+/nhxiCsFaCJXeiM3tN+kFtwQ8kamINqwaC8Vj\n" + "lFLKHDfwJQ==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_public_testkey[] = + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNT" + "YAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPTJa+w" + "FMXBT3fpAqPjROsqv5jUHC+xOok= aris@kalix86\n"; + +static const char torture_ecdsa256_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2\n" + "ZLp7VtFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_ecdsa256_testkey_cert[] = + "ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHvXWcdSrQeZL2/Z68V8ntbL7rDo" + "Qwrsc+ps6HbMGZrkAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPt" + "mS6e1bRRwNBGzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAAAAA" + "AAAAAAAEAAAAHbXlpZGVudAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVc" + "GVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGl" + "uZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0e" + "QAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1" + "uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7V" + "tFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQAAAGQAAAATZWN" + "kc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhALDSBnmFF59tgTKDQ4meTJEI7/BP2Zgf1" + "AKg1H3kIijQAAAAIFYrqSg6GI03ohXqUVsZ3lCB/XIism2aV5Vz2bg1d9zo " + "./ec256.pub"; + +static const char torture_ecdsa384_private_testkey[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "MIGkAgEBBDBY8jEa5DtRy4AVeTWhPJ/TK257behiC3uafEi6YA2oHORibqX55EDN\n" + "wz29MT40mQSgBwYFK4EEACKhZANiAARXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1P\n" + "bJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYC\n" + "jkXlc5YbYHjueBbp0oeNXqsXHNAWQZo=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBY8jEa5DtRy4AVeTWh\n" + "PJ/TK257behiC3uafEi6YA2oHORibqX55EDNwz29MT40mQShZANiAARXc4BN6BrV\n" + "o1QMi3+i/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdzt\n" + "J0bYoyT4V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXHNAWQZo=\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_testkey_passphrase[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" + "\n" + "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" + "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" + "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBHDBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIEuMnFkuHkDkCAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBA/fjhqXxV/Dk7cg8XgPxzuBIHA\n" + "TbiloDCPfKKlkm9ZguahtfJOxcVBbMtrFAK2vA/jMXGnbB9Qe13uLl8fTd6QB4tE\n" + "Zbyucq4OA0L2HyhuEsJiLvf0ICX8APrBajNv3B8F7ZStrXx7hcJUg8qTlsbdovYq\n" + "nCjOKoq/F6ax/r1F9Rr5PlXQDoSKDJ3mQkZc4n8VNKFfXOPQ7C4rEYzglSyzGwyQ\n" + "2EwRwnkkJqcYotRyH4JWtXCRak7znLVDeGbavhpP6paSVsK8OpycAoJstfQb0L4q\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRXc4BN6BrVo1QMi3+i\n" + "/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4\n" + "V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXHNAWQZoAAADIITfDfiE3w34A\n" + "AAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAGEEV3OATega1aNU\n" + "DIt/ovwfOS7u0jLswYvtT2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG\n" + "2KMk+Fdwd2akfUchqupmAo5F5XOWG2B47ngW6dKHjV6rFxzQFkGaAAAAMFjyMRrk\n" + "O1HLgBV5NaE8n9Mrbntt6GILe5p8SLpgDagc5GJupfnkQM3DPb0xPjSZBAAAAAA=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABB4N\n" + "dKGEoxFeg6dqiR2vTl6AAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzOD\n" + "QAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5\n" + "LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtg\n" + "eO54FunSh41eqxcc0BZBmgAAANDOL7sWcylFf8SsjGVFvr36mpyUBpAJ/e7o4RbQg\n" + "H8FDu1IxscOfbLDoB3CV7UEIgG58nVsDamfL6rXV/tzWnPxYxi6jUHcKT1BugO/Jt\n" + "/ncelMeoAS6MAZhElaGKzU1cJMlMTV9ofmuKuAwllQULG7L8lwHs9whBK4JmWPaGL\n" + "pU3i9ZoT33/g6pcvA83vicCNqj7ggl6Vb9MeO/zGW1+oV2HC3WiLTqBsYxEJu4YCM\n" + "ewfx9pWeWaCllNy/F1rCBu3cxqzcge9hqIlNtpT7Dq3k\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_public_testkey[] = + "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzOD" + "QAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y5" + "0sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtgeO54FunSh41eqxcc0B" + "ZBmg== aris@kalix86"; + +static const char torture_ecdsa384_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEV3OATega1aNUDIt/ovwfOS7u0jLswYvt\n" + "T2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG2KMk+Fdwd2akfUchqupm\n" + "Ao5F5XOWG2B47ngW6dKHjV6rFxzQFkGa\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_ecdsa384_testkey_cert[] = + "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgvggfi3v98HjOiqVi1O5aPy7JvMd" + "rTZe68GZ0qCaAN5MAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7" + "MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIar" + "qZgKOReVzlhtgeO54FunSh41eqxcc0BZBmgAAAAAAAAAAAAAAAQAAAAdteWlkZW50A" + "AAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmR" + "pbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" + "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXN" + "lci1yYwAAAAAAAAAAAAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwM" + "zg0AAAAYQRXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+" + "WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXH" + "NAWQZoAAACEAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAABpAAAAMQD5f0pF6U6eeBO" + "PrOV7Y3w5NuTzvuyDAq0kTv6VYNMp83TYpIJw16+tMAplOSzPTvwAAAAwWD9StvMEP" + "b+SDH2G5qqkMk+F5IaHI9fev8zcFzzdOlilLc/+CFM0NKMAFtOrrhv0 " + "./ec384.pub"; + +static const char torture_ecdsa521_private_testkey[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "MIHbAgEBBEG83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrIaTUmNzk+60f0sCCm\n" + "Gll0dgrZLmeIw+TtnG1E20VZflCKq+IdkaAHBgUrgQQAI6GBiQOBhgAEAc6D728d\n" + "baQkHnSPtztaRwJw63CBl15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9d\n" + "Qnuwn8KQUh2T/z+BARjWPKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJ\n" + "KATU3h8gzTMkbrx8DJTFHEIjXBShs44HsSYVl3Xy\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAvN50idki6IgbxJLt\n" + "SbXkClsf17ekx5LIAu66yGk1Jjc5PutH9LAgphpZdHYK2S5niMPk7ZxtRNtFWX5Q\n" + "iqviHZGhgYkDgYYABAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT\n" + "8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsM\n" + "zGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd1\n" + "8g==\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_testkey_passphrase[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,24C4F383915BC07D9C63209BF6AD3DEE\n" + "\n" + "M+JGfpGfoH3Wn6XWSoHrGGevaS6p2vJGQdkFEIgUfh16s+U/LcRhAhRnhX/MV6Ds\n" + "OZTpusrjInlZXNUR97fJbmjr/600qUlh4y3U9ikiX3IXE+RI80TPNdishOOjKRF7\n" + "aWDW8UxTlFfU2Zc1Ew0pTvMXXcuTpozW1NNVY+6S9uWfHwq1/EcR35dbnEmG0gId\n" + "qsiEdVKh7p+9Qto8jcVWzMh7ANMcIwmxQ4zbvnqypwgAgpMbamWqBZ9q4egsVZKd\n" + "uRzL95L05ctOBGYNYqpPNIX3UdQU07kzwNC+yaHOb2s=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBXTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIY6X14D05Q7gCAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBCmngDUX2/kg+45m4qoCBLiBIIB\n" + "ANHV+GC6Hnend9cVScT5oNtOS2a/TD82N1h+9cYmxn953IRNk2rF7LFYFFeZzcZi\n" + "e840YFYFRiTScm1GbKgwyFLYzYguvpUpS3qz3yZMygoX3xlvFw0l8FWsfeUmOzG1\n" + "uQQPGeoFCus43D3k1iQCOafEe0DPbyfcF/IxajZ+P0N8A5ikgPsOfpTLAdWiYgFt\n" + "wkafVfXx5ZH1u8S34+kmoKRhf5zBFQI1BHD6bCQDANPBkbP4KEjH5mHRO99nHK9r\n" + "EhdLDBEXRo9xb1BhgPLdQA0AdPPqZ6Wugy3KyxkEiH/GB/oBoIpg0oALnowL129g\n" + "BV6jZHwXHuO4/CLJ9rN2tdE=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1tpCQedI+3\n" + "O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBS\n" + "HZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDN\n" + "MyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAAEAt6sYz7erGM8AAAATZWNkc2Etc2hh\n" + "Mi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAIUEAc6D728dbaQkHnSPtztaRwJw63CB\n" + "l15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9dQnuwn8KQUh2T/z+BARjW\n" + "PKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJKATU3h8gzTMkbrx8DJTF\n" + "HEIjXBShs44HsSYVl3XyAAAAQgC83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrI\n" + "aTUmNzk+60f0sCCmGll0dgrZLmeIw+TtnG1E20VZflCKq+IdkQAAAAABAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAj\n" + "9WBFa/piJcPFEE4CGZTKAAAAEAAAAAEAAACsAAAAE2VjZHNhLXNoYTItbmlzdHA1\n" + "MjEAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl1\n" + "7sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee1\n" + "6VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOO\n" + "B7EmFZd18gAAAQDLjaKp+DLEHFb98f5WnVFg6LgDN847sfeuPZVfVjeSAiIv016O\n" + "ld7DXb137B2xYVsuce6sHbypr10dJOvgMTLdzTl+crYNJL+8UufJP0rOIFaDenzQ\n" + "RW8wydwiQxwt1ZqtD8ASqFmadxngufJKZzPLGfjCbCz3uATKa2sXN66nRXRZJbVA\n" + "IlNYDY8ivAStNhfItUMqyM6PkYlKJECtJw7w7TYKpvts7t72JmtgqVjS45JI/YZ+\n" + "kitIG0YmG8rzL9d1vBB5m+MH/fnFz2uJqbQYCH9Ctc8HZodAVoTNDzXHU2mYF9PE\n" + "Z6+gi3jd+kOyUk3NifHcre9K6ie7LL33JayM\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_public_testkey[] = + "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1Mj" + "EAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT8wYo1FCzE07w" + "V262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsMzGGh1FJ0XXhRJj" + "UbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd18g== aris@kalix86"; + +static const char torture_ecdsa521_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBzoPvbx1tpCQedI+3O1pHAnDrcIGX\n" + "XlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBSHZP/P4EBGNY8\n" + "qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDNMyRuvHwMlMUc\n" + "QiNcFKGzjgexJhWXdfI=\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_ecdsa521_testkey_cert[] = + "ecdsa-sha2-nistp521-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HA1MjEtY2VydC12MDFAb3BlbnNzaC5jb20AAAAggFIwlsx63C++kmCBDF4O14fvu5j" + "Icsm8uMbMp0smOVwAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwg" + "ZdeXMpAeEl17sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jy" + "oXB3Bsee16VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI" + "1wUobOOB7EmFZd18gAAAAAAAAAAAAAAAQAAAAdteWlkZW50AAAAAAAAAAAAAAAA///" + "///////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blc" + "m1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5" + "nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAA" + "AAArAAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1" + "tpCQedI+3O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce" + "7CfwpBSHZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNT" + "eHyDNMyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAACnAAAAE2VjZHNhLXNoYTItbmlzd" + "HA1MjEAAACMAAAAQgCJzTxw/hz2qE8Qkd4XW9Qn7fPxML6Ebtttg9C18AguyGyE6Nk" + "YH1NcToYxwQxrgzDXowXYm9eCbq9JEvaXDEtIfAAAAEIBk06LmKAYR2HDwwt4f5wVI" + "PKJ0pHVLZEx3FMZI3SfwS9mVm+oojLkZ2hr8X0xn28zbN045d8daB7BB1mHMGNT+YA" + "= ./ec521.pub"; + +static const char torture_ecdsa_sk_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2\n" + "RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQRUfa1IVvak\n" + "xFQZudDtXVlTtw6SiuAgfTpqZBuMdcK55kgy3o7V2z02/XClN1zpvSydzdjGWVgLj6WE9Q\n" + "6xEOhQAAAABHNzaDoAAADoWSfkhlkn5IYAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv\n" + "cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEEVH2tSFb2pMRUGbnQ7V1ZU7cOkorgIH06am\n" + "QbjHXCueZIMt6O1ds9Nv1wpTdc6b0snc3YxllYC4+lhPUOsRDoUAAAAARzc2g6AQAAAEBS\n" + "Smuf/sZP2WxVdlqgSMN7E8VLFdZI717mTi/svHahGy3wcFp2tPPylCaIG9aKAQrfVt+pOJ\n" + "U+OPsm8rphRRM1AAAAAAAAABJwaG9lbml4QHBob2VuaXgtcGMBAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa_sk_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCzS672qr\n" + "+0DRopx7VjkjCnAAAAGAAAAAEAAAB/AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3Bl\n" + "bnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBFR9rUhW9qTEVBm50O1dWVO3DpKK4CB9OmpkG4\n" + "x1wrnmSDLejtXbPTb9cKU3XOm9LJ3N2MZZWAuPpYT1DrEQ6FAAAAAEc3NoOgAAAPDoWSmM\n" + "ki/XGLXidNxyLy2uRGejaZTOI3Ran10b7UF2ddRCrmBc6eVEXzgJ+BzB0sO0/uc1Q7QJhy\n" + "fGR9bz1rvwJd5RpLLw9cSoTHbDiap4tkQu2snQt7AF/E6MOgQ3mvdhDDYoTYvxNIiwZTH1\n" + "/Cxl2ZcRBKwSl6yp3JOxIVgttmJmNTqpt2U/uYwag9N1o6wxhWy1aamKZd1qHtPVC7MPL8\n" + "/Q96mBlCEIe3vd4Hge4wgDa24F4Lwat7IA0/NGNFISIQH7x4VaGHAiTeMFL1NOVyw52xWr\n" + "aAgXfkyplffxlB7ZfCf7RLsiCZDinMCE9y8=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa_sk_public_testkey[] = + "sk-ecdsa-sha2-nistp256@openssh.com " + "AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBF" + "R9rUhW9qTEVBm50O1dWVO3DpKK4CB9OmpkG4x1wrnmSDLejtXbPTb9cKU3XOm9LJ3N2MZZWAuP" + "pYT1DrEQ6FAAAAAEc3NoOg== phoenix@phoenix-pc"; + +/**************************************************************************** + * ED25519 KEYS + ****************************************************************************/ + +static const char torture_ed25519_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MC4CAQAwBQYDK2VwBCIEIGBhcqLe61tkqVjIHKEzwB3oINasSHWGbIWXQWcLPmGN\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + "QyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlwAAAJBzsDN1c7Az\n" + "dQAAAAtzc2gtZWQyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlw\n" + "AAAEBgYXKi3utbZKlYyByhM8Ad6CDWrEh1hmyFl0FnCz5hjRWWnxuCYiOyvMYLtkgoEyEK\n" + "lLV+klM+BU6Nh3PmAiqXAAAADGFyaXNAa2FsaXg4NgE=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDYuz+a8i\n" + "nb/BgGjQjQtvkUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYL\n" + "tkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAkOBxqvzvPSns3TbhjkCayvANI66100OELnpDOm\n" + "JBGgXr5q846NkAovH3pmJ4O7qzPLTQ/cm0+959VUODRhM1i96qBg5MTNtV33lf5Y57Klzu\n" + "JegbiexcqkHIzriH42K0XSOEpfW8f/rTH7ffjbE/7l8HRNwf7AmcnxLx/d8J8FTBr+8aU7\n" + "qMU3xAJ4ixnwhYFg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAie1RBk/ub+EwICCAAw\n" + "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEECRLkPChQx/sZPYLdNJhxMUEQFLj\n" + "7nelAdOx3WXIBbCOfOqg3aAn8C5cXPtIQ+fiui1V8wlXXV8RBiuDCC97ScLs91D5\n" + "qQhQtw0vgfnq1um/izg=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ed25519_public_testkey[] = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+" + "BU6Nh3PmAiqX aris@kalix86"; + +static const char torture_ed25519_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MCowBQYDK2VwAyEAFZafG4JiI7K8xgu2SCgTIQqUtX6SUz4FTo2Hc+YCKpc=\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_ed25519_testkey_cert[] = + "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQ" + "G9wZW5zc2guY29tAAAAILrR4sPB+b6BRId/OkQha9nWwoACXqUTILz1TrmG4R9CAAA" + "AIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAAAAAAAAAAAABAAAAB" + "215aWRlbnQAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTE" + "tZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAF" + "nBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnB" + "lcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCY" + "iOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB" + "d8AogGWM6njfejbazFVyfnjNiWqatx6IV3Nnqc3LjCiPY19fqIPe2YJSzytHwLTD5X" + "IjD2bJpq2ZfjQwXpO0J ./ed.pub"; + +static const char torture_ed25519_sk_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2\n" + "gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACCihqLlueARJOQCZMYRHefNkQ3WBHlhlUOuG7a/\n" + "ivCkaQAAAARzc2g6AAAA+OivRKLor0SiAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2\n" + "9tAAAAIKKGouW54BEk5AJkxhEd582RDdYEeWGVQ64btr+K8KRpAAAABHNzaDoBAAAAgNxc\n" + "Q6pfw2S2fpCEB1UGO4Fy8O5gXZDbw3Vj8EHTcUDucNmk/iaI/GTPcUQK5cgPJH8AaB+lIZ\n" + "GasyHd28mghgpaztG2cYmxrF3ZuvNdEZJecflgMOJDXZwoYvKpb7rZWjQgf8AeDy2u2dpl\n" + "XCKHH8/LkJHdo4MABojarKofgaGzAAAAAAAAABJwaG9lbml4QHBob2VuaXgtcGMBAgMEBQ\n" + "YH\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_sk_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAkfYBhph\n" + "EvYRpuOO6V4wihAAAAGAAAAAEAAABKAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t\n" + "AAAAIKKGouW54BEk5AJkxhEd582RDdYEeWGVQ64btr+K8KRpAAAABHNzaDoAAAEA2WmpuB\n" + "2ip0Bq4XJ9c2C33fE5twVYvK3WrJfAJKzih7bFXxbt5NmUFs121SD/x+3xZLwBJWGOIhdf\n" + "idOD4gy9VWWAGCdJ0v87T/WaBYzEACr32hd99cD+Ki7VmmAxOKxx2/+/gg+WkbgygNns3c\n" + "7YoYW5SSJm7WlhtmHFCKHtSh0fd8X1Q7gLHWTdd4B+3U9PyGpVgCKe2s2IOoTIcWOHlDW3\n" + "KbEdlKELKCUEb0kof5m3hu8cktn0J/YIe1Y98YVjv472P6CO0Jw92jHSEPiTGn8JdSPkBY\n" + "Qcoq18tszucoR2gp+sf5UvQhW8iOALDxO72Yq6HINAXNbpCB22U++GJw==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_sk_public_testkey[] = + "sk-ssh-ed25519@openssh.com " + "AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIKKGouW54BEk5AJkxhEd582RDdYEeW" + "GVQ64btr+K8KRpAAAABHNzaDo= phoenix@phoenix-pc"; + +static const char * +torture_get_testkey_public_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + return torture_dsa_public_testkey; + case SSH_KEYTYPE_RSA: + if (format == FORMAT_OPENSSH) { + return torture_rsa_public_testkey; + } + return torture_rsa_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P521: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa521_public_testkey; + } + return torture_ecdsa521_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P384: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa384_public_testkey; + } + return torture_ecdsa384_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P256: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa256_public_testkey; + } + return torture_ecdsa256_public_testkey_pem; + case SSH_KEYTYPE_ED25519: + if (format == FORMAT_OPENSSH) { + return torture_ed25519_public_testkey; + } + return torture_ed25519_public_testkey_pem; + case SSH_KEYTYPE_DSS_CERT01: + return torture_dsa_testkey_cert; + case SSH_KEYTYPE_RSA_CERT01: + return torture_rsa_testkey_cert; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + return torture_ecdsa256_testkey_cert; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return torture_ecdsa384_testkey_cert; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return torture_ecdsa521_testkey_cert; + case SSH_KEYTYPE_ED25519_CERT01: + return torture_ed25519_testkey_cert; + case SSH_KEYTYPE_SK_ECDSA: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa_sk_public_testkey; + } + return NULL; + case SSH_KEYTYPE_SK_ED25519: + if (format == FORMAT_OPENSSH) { + return torture_ed25519_sk_public_testkey; + } + return NULL; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +static const char * +torture_get_testkey_encrypted_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + switch (format) { + case FORMAT_OPENSSH: + return torture_dsa_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_dsa_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_dsa_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_RSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_rsa_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_rsa_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_rsa_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P521: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa521_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ecdsa521_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa521_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P384: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa384_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ecdsa384_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa384_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P256: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa256_private_openssh_testkey_pasphrase; + case FORMAT_PKCS8: + return torture_ecdsa256_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa256_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ed25519_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + /* ed25519 keys are not available in legacy PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ECDSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa_sk_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + case FORMAT_PEM: + /* SK keys are not available in PKCS8 or PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_sk_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + case FORMAT_PEM: + /* SK keys are not available in PKCS8 or PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +static const char * +torture_get_testkey_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + switch (format) { + case FORMAT_OPENSSH: + return torture_dsa_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_dsa_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_dsa_private_testkey; + } + return NULL; + case SSH_KEYTYPE_RSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_rsa_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_rsa_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_rsa_private_testkey; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P521: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa521_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa521_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_ecdsa521_private_testkey; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P384: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa384_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa384_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_ecdsa384_private_testkey; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P256: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa256_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa256_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_ecdsa256_private_testkey; + } + return NULL; + case SSH_KEYTYPE_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ed25519_private_pkcs8_testkey; + case FORMAT_PEM: + /* ed25519 keys are not available in legacy PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ECDSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa_sk_private_openssh_testkey; + case FORMAT_PKCS8: + case FORMAT_PEM: + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_sk_private_openssh_testkey; + case FORMAT_PKCS8: + case FORMAT_PEM: + return NULL; + } + return NULL; + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +/* Return the encrypted private key in a new OpenSSH format */ +const char * +torture_get_openssh_testkey(enum ssh_keytypes_e type, bool with_passphrase) +{ + if (with_passphrase) { + return torture_get_testkey_encrypted_internal(type, FORMAT_OPENSSH); + } else { + return torture_get_testkey_internal(type, FORMAT_OPENSSH); + } +} + +/* Return the private key in PEM format */ +const char * +torture_get_testkey(enum ssh_keytypes_e type, bool with_passphrase) +{ + enum torture_format_e format = FORMAT_PEM; + + if (with_passphrase) { +/* This is the new PKCS8 PEM format, which works only in OpenSSL */ +#if defined(HAVE_LIBCRYPTO) + format = FORMAT_PKCS8; +#endif + return torture_get_testkey_encrypted_internal(type, format); + } else { +/* The unencrypted format works also in mbedTLS */ +#if defined(HAVE_LIBCRYPTO) || defined(HAVE_LIBMBEDCRYPTO) + format = FORMAT_PKCS8; +#endif + return torture_get_testkey_internal(type, format); + } +} + +const char * +torture_get_testkey_pub(enum ssh_keytypes_e type) +{ + return torture_get_testkey_public_internal(type, FORMAT_OPENSSH); +} + +const char * +torture_get_testkey_pub_pem(enum ssh_keytypes_e type) +{ + return torture_get_testkey_public_internal(type, FORMAT_PEM); +} + +const char * +torture_get_testkey_passphrase(void) +{ + return TORTURE_TESTKEY_PASSWORD; +} diff --git a/src/libs/libssh-0.12.2/tests/torture_key.h b/src/libs/libssh-0.12.2/tests/torture_key.h new file mode 100644 index 000000000000..5eacdab95f87 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_key.h @@ -0,0 +1,44 @@ +/* + * torture_key.h - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _TORTURE_KEY_H +#define _TORTURE_KEY_H + +#include + +#define TORTURE_TESTKEY_PASSWORD "libssh-rocks" + +/* Return the encrypted private key in a new OpenSSH format */ +const char *torture_get_openssh_testkey(enum ssh_keytypes_e type, + bool with_passphrase); + +/* Return the private key in the legacy PEM format */ +const char *torture_get_testkey(enum ssh_keytypes_e type, + bool with_passphrase); +const char *torture_get_testkey_passphrase(void); + +const char *torture_get_testkey_pub(enum ssh_keytypes_e type); + +const char *torture_get_testkey_pub_pem(enum ssh_keytypes_e type); + +#endif /* _TORTURE_KEY_H */ diff --git a/src/libs/libssh-0.12.2/tests/torture_pki.c b/src/libs/libssh-0.12.2/tests/torture_pki.c new file mode 100644 index 000000000000..f54209469a85 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_pki.c @@ -0,0 +1,97 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "torture_pki.h" +#include + +char *torture_pki_read_file(const char *filename) +{ + char *key; + int fd; + int size; + int rc; + struct stat sb; + + if (filename == NULL || filename[0] == '\0') { + return NULL; + } + + fd = open(filename, O_RDONLY); + if (fd < 0) { + return NULL; + } + + rc = fstat(fd, &sb); + if (rc != 0) { + close(fd); + return NULL; + } + + key = malloc(sb.st_size + 1); + if (key == NULL) { + close(fd); + return NULL; + } + + size = read(fd, key, sb.st_size); + close(fd); + if (size != sb.st_size) { + free(key); + return NULL; + } + + key[size] = '\0'; + return key; +} + +int torture_read_one_line(const char *filename, char *buffer, size_t len) +{ + FILE *fp; + size_t nmemb; + + fp = fopen(filename, "r"); + if (fp == NULL) { + return -1; + } + + nmemb = fread(buffer, len - 2, 1, fp); + if (nmemb != 0 || ferror(fp)) { + fclose(fp); + return -1; + } + buffer[len - 1] = '\0'; + + fclose(fp); + + return 0; +} + +/** + * @internal + * + * Returns the character len of a public key string, omitting the comment part + */ +size_t torture_pubkey_len(const char *pubkey) +{ + const char *ptr; + + ptr = strchr(pubkey, ' '); + if (ptr != NULL) { + ptr = strchr(ptr + 1, ' '); + if (ptr != NULL) { + return ptr - pubkey; + } + } + + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/torture_pki.h b/src/libs/libssh-0.12.2/tests/torture_pki.h new file mode 100644 index 000000000000..460cc91f2405 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_pki.h @@ -0,0 +1,3 @@ +char *torture_pki_read_file(const char *filename); +int torture_read_one_line(const char *filename, char *buffer, size_t len); +size_t torture_pubkey_len(const char *pubkey); diff --git a/src/libs/libssh-0.12.2/tests/torture_sk.c b/src/libs/libssh-0.12.2/tests/torture_sk.c new file mode 100644 index 000000000000..fdb2a815b7ed --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_sk.c @@ -0,0 +1,395 @@ +/* + * torture_sk.c - torture library for testing security keys + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "torture_sk.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/sk_api.h" /* For SSH_SK_* flag definitions */ + +void assert_sk_key_valid(ssh_key key, + enum ssh_keytypes_e expected_type, + bool private) +{ + char *app_str = NULL; + const char *expected_type_str = NULL; + + assert_non_null(key); + assert_true(is_sk_key_type(expected_type)); + assert_int_equal(key->type, expected_type); + + if (private) { + assert_int_equal(key->flags, + SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC); + } else { + assert_int_equal(key->flags, SSH_KEY_FLAG_PUBLIC); + } + + expected_type_str = ssh_key_type_to_char(expected_type); + assert_non_null(expected_type_str); + + assert_non_null(key->type_c); + assert_string_equal(key->type_c, expected_type_str); + + /* Validate security key specific fields */ + assert_non_null(key->sk_application); + + /* Validate application string format and content */ + app_str = ssh_string_to_char(key->sk_application); + assert_non_null(app_str); + + assert_true(ssh_string_len(key->sk_application) >= 4); + assert_true(strncmp(app_str, "ssh:", 4) == 0); + ssh_string_free_char(app_str); + + if (private) { + assert_non_null(key->sk_key_handle); + assert_true(ssh_string_len(key->sk_key_handle) > 0); + } + + const uint8_t allowed_flags = SSH_SK_USER_PRESENCE_REQD | + SSH_SK_USER_VERIFICATION_REQD | + SSH_SK_RESIDENT_KEY | SSH_SK_FORCE_OPERATION; + + /* Validate sk_flags contain only allowed bits */ + uint8_t flags = key->sk_flags; + assert_int_equal(flags & ~allowed_flags, 0); + + /* Validate underlying cryptographic key exists based on type */ + switch (expected_type) { + case SSH_KEYTYPE_SK_ECDSA: +#if defined(HAVE_LIBGCRYPT) + assert_non_null(key->ecdsa); +#elif defined(HAVE_LIBMBEDCRYPTO) + assert_non_null(key->ecdsa); +#elif defined(HAVE_LIBCRYPTO) + assert_non_null(key->key); +#endif + break; + + case SSH_KEYTYPE_SK_ED25519: +#if defined(HAVE_LIBCRYPTO) + assert_non_null(key->key); +#elif !defined(HAVE_LIBCRYPTO) + assert_non_null(key->ed25519_pubkey); +#endif + break; + + default: + /* Should not reach here */ + assert_true(0); + break; + } +} + +void assert_sk_signature_valid(ssh_signature signature, + enum ssh_keytypes_e expected_type, + ssh_key signing_key, + const uint8_t *data, + size_t data_len) +{ + uint8_t valid_flags; + const char *expected_type_str = NULL; + ssh_string sig_blob = NULL; + ssh_signature reconstructed = NULL; + ssh_buffer sk_sig_buffer = NULL; + int rc; + + /* Basic null and type validation */ + assert_non_null(signature); + assert_int_equal(signature->type, expected_type); + + /* Validate hash type is appropriate for security keys */ + switch (expected_type) { + case SSH_KEYTYPE_SK_ECDSA: + assert_int_equal(signature->hash_type, SSH_DIGEST_SHA256); + break; + case SSH_KEYTYPE_SK_ED25519: + assert_int_equal(signature->hash_type, SSH_DIGEST_AUTO); + break; + default: + /* Should not reach here */ + assert_true(0); + break; + } + + expected_type_str = ssh_key_type_to_char(expected_type); + assert_non_null(signature->type_c); + assert_string_equal(signature->type_c, expected_type_str); + + /* Check that only valid SK flags are set */ + valid_flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD; + assert_int_equal(signature->sk_flags & ~valid_flags, 0); + + assert_true(signature->sk_flags & SSH_SK_USER_PRESENCE_REQD); + assert_true(signature->sk_counter > 0); + + assert_non_null(signature->raw_sig); + assert_true(ssh_string_len(signature->raw_sig) > 0); + + rc = ssh_pki_export_signature_blob(signature, &sig_blob); + assert_int_equal(rc, SSH_OK); + assert_non_null(sig_blob); + + assert_non_null(signing_key); + rc = ssh_pki_import_signature_blob(sig_blob, signing_key, &reconstructed); + assert_int_equal(rc, SSH_OK); + assert_non_null(reconstructed); + + rc = pki_sk_signature_buffer_prepare(signing_key, + reconstructed, + data, + data_len, + &sk_sig_buffer); + assert_int_equal(rc, SSH_OK); + assert_non_null(sk_sig_buffer); + + rc = pki_verify_data_signature(reconstructed, + signing_key, + ssh_buffer_get(sk_sig_buffer), + ssh_buffer_get_len(sk_sig_buffer)); + assert_int_equal(rc, SSH_OK); + + SSH_BUFFER_FREE(sk_sig_buffer); + + ssh_signature_free(reconstructed); + ssh_string_free(sig_blob); +} + +ssh_pki_ctx +torture_create_sk_pki_ctx(const char *application, + uint8_t flags, + const void *challenge_data, + size_t challenge_len, + ssh_auth_callback pin_callback, + const char *device_path, + const char *user_id, + const struct ssh_sk_callbacks_struct *sk_callbacks) +{ + ssh_pki_ctx ctx = NULL; + ssh_buffer challenge_buffer = NULL; + int rc; + + ctx = ssh_pki_ctx_new(); + assert_non_null(ctx); + + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_SK_APPLICATION, + application); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_SK_FLAGS, &flags); + assert_int_equal(rc, SSH_OK); + + if (challenge_data != NULL && challenge_len > 0) { + challenge_buffer = ssh_buffer_new(); + assert_non_null(challenge_buffer); + + rc = ssh_buffer_add_data(challenge_buffer, + challenge_data, + challenge_len); + assert_int_equal(rc, SSH_OK); + } + + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_SK_CHALLENGE, + challenge_buffer); + assert_int_equal(rc, SSH_OK); + + SSH_BUFFER_FREE(challenge_buffer); + + rc = ssh_pki_ctx_set_sk_pin_callback(ctx, pin_callback, NULL); + assert_int_equal(rc, SSH_OK); + + if (device_path != NULL) { + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + device_path, + false); + assert_int_equal(rc, SSH_OK); + } + if (user_id != NULL) { + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_USER_ID, + user_id, + false); + assert_int_equal(rc, SSH_OK); + } + + if (sk_callbacks != NULL) { + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + sk_callbacks); + assert_int_equal(rc, SSH_OK); + } + + return ctx; +} + +void assert_sk_enroll_response(struct sk_enroll_response *response, int flags) +{ + assert_non_null(response); + + assert_non_null(response->public_key); + assert_true(response->public_key_len > 0); + + assert_non_null(response->key_handle); + assert_true(response->key_handle_len > 0); + + assert_non_null(response->signature); + assert_true(response->signature_len > 0); + + /* + * This check might fail for some authenticators, as returning an + * attestation certificate as part of the attestation statement is not + * mandated by the FIDO2 standard. + */ + assert_non_null(response->attestation_cert); + assert_true(response->attestation_cert_len > 0); + + assert_non_null(response->authdata); + assert_true(response->authdata_len > 0); + + assert_int_equal(response->flags, flags); +} + +void assert_sk_sign_response(struct sk_sign_response *response, + enum ssh_keytypes_e key_type) +{ + assert_non_null(response); + + assert_non_null(response->sig_r); + assert_true(response->sig_r_len > 0); + + /* sig_s is NULL for Ed25519, present for ECDSA */ + switch (key_type) { + case SSH_SK_ECDSA: + assert_non_null(response->sig_s); + assert_true(response->sig_s_len > 0); + break; + case SSH_SK_ED25519: + assert_null(response->sig_s); + assert_int_equal(response->sig_s_len, 0); + break; + default: + /* Should not reach here */ + assert_true(0); + break; + } +} + +void assert_sk_resident_key(struct sk_resident_key *resident_key) +{ + assert_non_null(resident_key); + + assert_non_null(resident_key->application); + assert_true(strlen(resident_key->application) > 0); + + assert_non_null(resident_key->user_id); + assert_true(resident_key->user_id_len > 0); + + assert_non_null(resident_key->key.public_key); + assert_true(resident_key->key.public_key_len > 0); + + assert_non_null(resident_key->key.key_handle); + assert_true(resident_key->key.key_handle_len > 0); +} + +const char *torture_get_sk_pin(void) +{ + const char *pin = getenv("TORTURE_SK_PIN"); + return (pin != NULL && pin[0] != '\0') ? pin : NULL; +} + +#ifdef HAVE_SK_DUMMY + +/* External declarations for sk-dummy library functions + * These match the signatures in openssh sk-api.h */ +extern uint32_t sk_api_version(void); + +extern int sk_enroll(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response); + +extern int sk_sign(uint32_t alg, + const uint8_t *data, + size_t data_len, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_sign_response **sign_response); + +extern int sk_load_resident_keys(const char *pin, + struct sk_option **options, + struct sk_resident_key ***resident_keys, + size_t *num_keys_found); + +static struct ssh_sk_callbacks_struct sk_dummy_callbacks = { + .api_version = sk_api_version, + .enroll = sk_enroll, + .sign = sk_sign, + .load_resident_keys = sk_load_resident_keys, +}; + +#endif /* HAVE_SK_DUMMY */ + +#ifdef WITH_FIDO2 + +const struct ssh_sk_callbacks_struct *torture_get_sk_dummy_callbacks(void) +{ +#ifdef HAVE_SK_DUMMY + ssh_callbacks_init(&sk_dummy_callbacks); + return &sk_dummy_callbacks; +#else + return NULL; +#endif /* HAVE_SK_DUMMY */ +} + +const struct ssh_sk_callbacks_struct *torture_get_sk_callbacks(void) +{ + const char *env = getenv("TORTURE_SK_USBHID"); + bool torture_sk_usbhid = (env != NULL && env[0] != '\0'); + + if (torture_sk_usbhid) { + return ssh_sk_get_default_callbacks(); + } else { + return torture_get_sk_dummy_callbacks(); + } +} + +#endif /* WITH_FIDO2 */ + +bool torture_sk_is_using_sk_dummy(void) +{ + const char *env = getenv("TORTURE_SK_USBHID"); + /* Return true if using sk-dummy callbacks (when TORTURE_SK_USBHID is NOT + * set) */ + return (env == NULL || env[0] == '\0'); +} diff --git a/src/libs/libssh-0.12.2/tests/torture_sk.h b/src/libs/libssh-0.12.2/tests/torture_sk.h new file mode 100644 index 000000000000..d63b02a06013 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_sk.h @@ -0,0 +1,167 @@ +/* + * torture_sk.h - torture library for testing security keys + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _TORTURE_SK_H +#define _TORTURE_SK_H + +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/callbacks.h" +#include "libssh/pki.h" +#include "torture.h" +#include "torture_pki.h" + +/** + * @brief Validate a security key (ssh_key) structure + * + * Checks that the provided key is not NULL, matches the expected key type, + * and other internal fields. + * + * @param[in] key The key to validate + * @param[in] expected_type The expected key type (e.g., SSH_KEYTYPE_SK_ECDSA) + * @param[in] private true if key should be private, false for public + */ +void assert_sk_key_valid(ssh_key key, + enum ssh_keytypes_e expected_type, + bool private); + +/** + * @brief Validate a security key signature structure + * + * Checks that the signature is not NULL, matches the expected key type, and + * other internal fields. Also verifies that the signature was produced by the + * given signing key. + * + * @param[in] signature The signature to validate + * @param[in] expected_type The expected key type (e.g., SSH_KEYTYPE_SK_ECDSA) + * @param[in] signing_key The key that should have produced the signature + * @param[in] data The signed data buffer + * @param[in] data_len Length of the signed data + */ +void assert_sk_signature_valid(ssh_signature signature, + enum ssh_keytypes_e expected_type, + ssh_key signing_key, + const uint8_t *data, + size_t data_len); + +/** + * @brief Create and initialize a PKI context configured for security key + * operations. + * + * Parameters: + * @param[in] application Application string + * @param[in] flags SK flags + * @param[in] challenge_data Optional challenge bytes (may be NULL) + * @param[in] challenge_len Length of challenge_data + * @param[in] pin_callback Callback used to obtain the PIN (may be NULL) + * @param[in] device_path Optional device path (may be NULL) + * @param[in] user_id Optional user_id string (may be NULL) + * @param[in] sk_callbacks Pointer to SK callbacks (may be NULL) + * + * @return A configured ssh_pki_ctx on success, or NULL on allocation failure. + */ +ssh_pki_ctx +torture_create_sk_pki_ctx(const char *application, + uint8_t flags, + const void *challenge_data, + size_t challenge_len, + ssh_auth_callback pin_callback, + const char *device_path, + const char *user_id, + const struct ssh_sk_callbacks_struct *sk_callbacks); + +/** + * @brief Validate a security key enrollment response structure + * + * Validates that an sk_enroll_response contains valid data from a FIDO2 + * enrollment operation, including public key, key handle, signature, + * attestation certificate, and authenticator data. + * + * @param[in] response The enrollment response to validate + * @param[in] flags The expected flags that should match the response flags + */ +void assert_sk_enroll_response(struct sk_enroll_response *response, int flags); + +/** + * @brief Validate a security key sign response structure + * + * Validates that an sk_sign_response contains valid signature data from + * a FIDO2 sign operation. + * + * @param[in] response The sign response to validate + * @param[in] key_type The key type (e.g., SSH_SK_ECDSA, SSH_SK_ED25519) + */ +void assert_sk_sign_response(struct sk_sign_response *response, + enum ssh_keytypes_e key_type); + +/** + * @brief Validate a security key resident key structure + * + * Validates that an sk_resident_key contains valid data including application + * identifier, user ID, public key, and key handle. + * + * @param[in] resident_key The resident key to validate + */ +void assert_sk_resident_key(struct sk_resident_key *resident_key); + +/** + * @brief Get security key PIN from environment variable + * + * Reads the TORTURE_SK_PIN environment variable and returns its value. + * + * @return Pointer to PIN string if set and non-empty, NULL otherwise + */ +const char *torture_get_sk_pin(void); + +/** + * @brief Get dummy security key callbacks for testing + * + * Returns dummy security key callbacks from openssh's sk-dummy + * if available, or NULL if not. + * + * @return Pointer to ssh_sk_callbacks_struct or NULL if unavailable. + * + */ +const struct ssh_sk_callbacks_struct *torture_get_sk_dummy_callbacks(void); + +/** + * @brief Get security key callbacks for testing + * + * Returns the default sk callbacks if TORTURE_SK_USBHID is set, + * otherwise returns dummy callbacks from openssh sk-dummy, or NULL if + * unavailable. + * + * @return Pointer to ssh_sk_callbacks_struct or NULL if unavailable + */ +const struct ssh_sk_callbacks_struct *torture_get_sk_callbacks(void); + +/** + * @brief Check if using sk-dummy callbacks for testing + * + * @return true if using sk-dummy callbacks, false otherwise + */ +bool torture_sk_is_using_sk_dummy(void); + +#endif /* _TORTURE_SK_H */ diff --git a/src/libs/libssh-0.12.2/tests/unittests/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/unittests/CMakeLists.txt new file mode 100644 index 000000000000..79f6b218a518 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/CMakeLists.txt @@ -0,0 +1,161 @@ +project(unittests C) + +set(LIBSSH_UNIT_TESTS + torture_bignum + torture_buffer + torture_bytearray + torture_callbacks + torture_crypto + torture_init + torture_list + torture_misc + torture_config + torture_options + torture_isipaddr + torture_knownhosts_parsing + torture_hashes + torture_packet_filter + torture_temp_dir + torture_temp_file + torture_push_pop_dir + torture_session_keys + torture_string + torture_tokens +) + +set(LIBSSH_THREAD_UNIT_TESTS + torture_rand + torture_threads_init + torture_threads_buffer + torture_threads_crypto +) + +set(TORTURE_UNIT_ENVIRONMENT + "LSAN_OPTIONS=suppressions=${libssh-tests_SOURCE_DIR}/suppressions/lsan.supp;") +if (OPENSSL_FOUND) + list(APPEND TORTURE_UNIT_ENVIRONMENT OPENSSL_ENABLE_SHA1_SIGNATURES=1) +endif (OPENSSL_FOUND) + +if (UNIX AND NOT WIN32) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + # this uses a socketpair + torture_packet + # requires ssh-keygen + torture_keyfiles + torture_pki + torture_pki_rsa + torture_pki_dsa + torture_pki_ed25519 + torture_pki_sk_ed25519 + torture_pki_sshsig + # requires /dev/null + torture_channel + ) + if (HAVE_IFADDRS_H) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + # requires some non-standard API from netdb.h, in.h + # and arpa/inet.h for handling IP addresses + torture_config_match_localnetwork + ) + endif (HAVE_IFADDRS_H) + if (WITH_SERVER) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_bind_config) + + if (WITH_GEX) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_moduli) + endif() + endif() + + if (WITH_PKCS11_URI) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_rsa_uri + torture_pki_ecdsa_uri + ) + if (WITH_PKCS11_PROVIDER) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_ed25519_uri + ) + list(APPEND TORTURE_UNIT_ENVIRONMENT + PKCS11_PROVIDER_DEBUG=file:/tmp/p11prov-debug.log) + endif() + endif() + + if (WITH_FIDO2) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_sk + ) + endif (WITH_FIDO2) + + if (HAVE_ECC) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_ecdsa + torture_pki_sk_ecdsa + ) + endif() + + set(LIBSSH_THREAD_UNIT_TESTS + ${LIBSSH_THREAD_UNIT_TESTS} + # requires pthread + torture_threads_pki_rsa + ) + if (WITH_SERVER) + set(LIBSSH_THREAD_UNIT_TESTS + ${LIBSSH_THREAD_UNIT_TESTS} + torture_unit_server + torture_server_x11 + torture_forwarded_tcpip_callback + torture_server_direct_tcpip + ) + endif (WITH_SERVER) +endif (UNIX AND NOT WIN32) + +if (HAVE_LIBFIDO2) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_sk_usbhid + ) +endif (HAVE_LIBFIDO2) + +if (WITH_SFTP) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_unit_sftp + ) +endif (WITH_SFTP) + +foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS}) + add_cmocka_test(${_UNIT_TEST} + SOURCES ${_UNIT_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} + ) + + set_property(TEST ${_UNIT_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_UNIT_ENVIRONMENT}) +endforeach() + +if (CMAKE_USE_PTHREADS_INIT) + foreach(_UNIT_TEST ${LIBSSH_THREAD_UNIT_TESTS}) + add_cmocka_test(${_UNIT_TEST} + SOURCES ${_UNIT_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} Threads::Threads + ) + + set_property(TEST ${_UNIT_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_UNIT_ENVIRONMENT}) + endforeach() +endif () + diff --git a/src/libs/libssh-0.12.2/tests/unittests/hello world.sh b/src/libs/libssh-0.12.2/tests/unittests/hello world.sh new file mode 100755 index 000000000000..8f6870284f2c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/hello world.sh @@ -0,0 +1,2 @@ +#!/bin/sh +printf '%s' "$1" 2>&1 diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_bignum.c b/src/libs/libssh-0.12.2/tests/unittests/torture_bignum.c new file mode 100644 index 000000000000..6f679946a263 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_bignum.c @@ -0,0 +1,176 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/bignum.h" +#include "libssh/string.h" + +static void check_str(int n, ssh_string str) +{ + if (n > 0 && n <= 127) { + assert_int_equal(1, ntohl(str->size)); + assert_int_equal(n, str->data[0]); + } else if (n > 127 && n <= 255) { + assert_int_equal(2, ntohl(str->size)); + assert_int_equal(0, str->data[0]); + assert_int_equal(n, str->data[1]); + } else if (n > 255 && n <= 32767) { + assert_int_equal(2, ntohl(str->size)); + assert_int_equal(n >> 8, str->data[0]); + assert_int_equal(n & 0xFF, str->data[1]); + } else { + assert_int_equal(3, ntohl(str->size)); + assert_int_equal(n >> 16, str->data[0]); + assert_int_equal((n >> 8) & 0xFF, str->data[1]); + assert_int_equal(n & 0xFF, str->data[2]); + } +} + +static void check_padded_str(int n, ssh_string str) +{ + assert_int_equal(4, ntohl(str->size)); + if (n > 0 && n <= 255) { + assert_int_equal(0, str->data[0]); + assert_int_equal(0, str->data[1]); + assert_int_equal(0, str->data[2]); + assert_int_equal(n, str->data[3]); + } else if (n > 255 && n <= 65535) { + assert_int_equal(0, str->data[0]); + assert_int_equal(0, str->data[1]); + assert_int_equal(n >> 8, str->data[2]); + assert_int_equal(n & 0xFF, str->data[3]); + } else { + assert_int_equal(0, str->data[0]); + assert_int_equal(n >> 16, str->data[1]); + assert_int_equal((n >> 8) & 0xFF, str->data[2]); + assert_int_equal(n & 0xFF, str->data[3]); + } +} + +static void check_bignum(int n, const char *nstr) +{ + bignum num = NULL, num2 = NULL; + bignum num3 = NULL; + ssh_string str = NULL; + char *dec = NULL; + int rc; + + num = bignum_new(); + assert_non_null(num); + + rc = bignum_set_word(num, n); + assert_int_equal(rc, 1); + + ssh_print_bignum("num", num); + + dec = bignum_bn2dec (num); + assert_non_null (dec); + assert_string_equal (nstr, dec); + ssh_crypto_free(dec); + + /* ssh_make_bignum_string */ + + str = ssh_make_bignum_string(num); + assert_non_null(str); + + check_str (n, str); + + /* ssh_make_string_bn */ + + num2 = ssh_make_string_bn(str); + ssh_string_free (str); + assert_non_null(num2); + + ssh_print_bignum("num2", num2); + + assert_int_equal (0, bignum_cmp (num, num2)); + + dec = bignum_bn2dec (num2); + assert_non_null (dec); + assert_string_equal (nstr, dec); + ssh_crypto_free(dec); + + bignum_dup(num, &num3); + assert_non_null(num3); + assert_int_equal(0, bignum_cmp(num, num3)); + + bignum_safe_free(num2); + + /* ssh_make_padded_bignum_string */ + + str = ssh_make_padded_bignum_string(num, 4); + assert_non_null(str); + + check_padded_str(n, str); + + num2 = ssh_make_string_bn(str); + ssh_string_free(str); + assert_non_null(num2); + + ssh_print_bignum("num2", num2); + + assert_true(bignum_cmp(num, num2) == 0); + + dec = bignum_bn2dec(num2); + assert_non_null(dec); + assert_string_equal(nstr, dec); + ssh_crypto_free(dec); + + /* negative test */ + str = ssh_make_padded_bignum_string(num, 2); + if (n > 65535) { + /* larger values need larger padding! */ + assert_null(str); + } else { + assert_non_null(str); + assert_int_equal(2, ntohl(str->size)); + if (n > 0 && n <= 255) { + assert_int_equal(0, str->data[0]); + assert_int_equal(n, str->data[1]); + } else { + assert_int_equal(n >> 8, str->data[0]); + assert_int_equal(n & 0xFF, str->data[1]); + } + ssh_string_free(str); + } + + bignum_safe_free(num); + bignum_safe_free(num2); + bignum_safe_free(num3); +} + + +static void torture_bignum(void **state) { + (void) state; /* unused */ + + ssh_set_log_level(SSH_LOG_TRACE); + + check_bignum (1, "1"); + check_bignum (17, "17"); + check_bignum (42, "42"); + check_bignum (127, "127"); + check_bignum (128, "128"); + check_bignum (254, "254"); + check_bignum (255, "255"); + check_bignum (256, "256"); + check_bignum (257, "257"); + check_bignum (300, "300"); + check_bignum (32767, "32767"); + check_bignum (32768, "32768"); + check_bignum (65535, "65535"); + check_bignum (65536, "65536"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_bignum), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_bind_config.c b/src/libs/libssh-0.12.2/tests/unittests/torture_bind_config.c new file mode 100644 index 000000000000..514727e52f76 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_bind_config.c @@ -0,0 +1,1888 @@ +/* + * torture_bind_config.c - Tests for server side configuration + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" + +#include +#include + +extern LIBSSH_THREAD int ssh_log_level; + +#define LOGLEVEL "verbose" +#define LOGLEVEL2 "fatal" +#define LOGLEVEL3 "DEBUG1" +#define LOGLEVEL4 "DEBUG2" +#define LISTEN_ADDRESS "::1" +#define LISTEN_ADDRESS2 "::2" +#define KEXALGORITHMS "ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha1" +#define KEXALGORITHMS2 "ecdh-sha2-nistp521" +#define CIPHERS "aes128-ctr,aes192-ctr,aes256-ctr" +#define CIPHERS2 "aes256-ctr" +#define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa" +#define HOSTKEYALGORITHMS_UNKNOWN "ssh-ed25519,ecdsa-sha2-nistp521,unknown,ssh-rsa" +#define HOSTKEYALGORITHMS2 "rsa-sha2-256" +#define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521" +#define PUBKEYACCEPTEDTYPES_UNKNOWN "rsa-sha2-512,ssh-rsa,unknown,ecdsa-sha2-nistp521" +#define PUBKEYACCEPTEDTYPES2 "rsa-sha2-256,ssh-rsa" +#define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com" +#define MACS2 "hmac-sha1" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#ifdef HAVE_ECC +#define LIBSSH_ECDSA_521_TESTKEY "libssh_testkey.id_ecdsa521" +#endif + +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS "libssh_test_bind_config_listenaddress" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING "ListenAddress "LISTEN_ADDRESS"\n" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2 "libssh_test_bind_config_listenaddress2" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING "ListenAddress "LISTEN_ADDRESS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE "libssh_test_bind_config_listenaddress_twice" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING \ + "ListenAddress "LISTEN_ADDRESS"\n" \ + "ListenAddress "LISTEN_ADDRESS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC "libssh_test_bind_config_listenaddress_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING \ + "ListenAddress "LISTEN_ADDRESS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_PORT "libssh_test_bind_config_port" +#define LIBSSH_TEST_BIND_CONFIG_PORT_STRING "Port 123\n" +#define LIBSSH_TEST_BIND_CONFIG_PORT2 "libssh_test_bind_config_port2" +#define LIBSSH_TEST_BIND_CONFIG_PORT2_STRING "Port 456\n" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE "libssh_test_bind_config_port_twice" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING \ + "Port 123\n" \ + "Port 456\n" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC "libssh_test_bind_config_port_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING \ + "Port 123\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_PORT2"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY "libssh_test_bind_config_hostkey" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY2 "libssh_test_bind_config_hostkey2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY2_STRING "HostKey "LIBSSH_RSA_TESTKEY"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE "libssh_test_bind_config_hostkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING \ + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" \ + "HostKey "LIBSSH_RSA_TESTKEY"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC "libssh_test_bind_config_hostkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING \ + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL "libssh_test_bind_config_loglevel" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING "LogLevel "LOGLEVEL"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1 "libssh_test_bind_config_loglevel2" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING "LogLevel "LOGLEVEL2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE "libssh_test_bind_config_loglevel_twice" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING \ + "LogLevel "LOGLEVEL"\n" \ + "LogLevel "LOGLEVEL2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC "libssh_test_bind_config_loglevel_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING \ + "LogLevel "LOGLEVEL"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1"\n" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS "libssh_test_bind_config_ciphers" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING "Ciphers "CIPHERS"\n" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS2 "libssh_test_bind_config_ciphers2" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING "Ciphers "CIPHERS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE "libssh_test_bind_config_ciphers_twice" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING \ + "Ciphers "CIPHERS"\n" \ + "Ciphers "CIPHERS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC "libssh_test_bind_config_ciphers_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING \ + "Ciphers "CIPHERS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_MACS "libssh_test_bind_config_macs" +#define LIBSSH_TEST_BIND_CONFIG_MACS_STRING "MACs "MACS"\n" +#define LIBSSH_TEST_BIND_CONFIG_MACS2 "libssh_test_bind_config_macs2" +#define LIBSSH_TEST_BIND_CONFIG_MACS2_STRING "MACs "MACS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE "libssh_test_bind_config_macs_twice" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING \ + "MACs "MACS"\n" \ + "MACs "MACS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC "libssh_test_bind_config_macs_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING \ + "MACs "MACS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_MACS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS "libssh_test_bind_config_kexalgorithms" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING "KexAlgorithms "KEXALGORITHMS"\n" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2 "libssh_test_bind_config_kexalgorithms2" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING "KexAlgorithms "KEXALGORITHMS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE "libssh_test_bind_config_kexalgorithms_twice" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING \ + "KexAlgorithms "KEXALGORITHMS"\n" \ + "KexAlgorithms "KEXALGORITHMS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC "libssh_test_bind_config_kexalgorithms_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING \ + "KexAlgorithms "KEXALGORITHMS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2"\n" + +#define LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE "libssh_test_bind_config_required_rsa_size" +#define LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE_STRING "RequiredRsaSize 2233\n" + +#define LIBSSH_TEST_BIND_CONFIG_FULL "libssh_test_bind_config_full" +#define LIBSSH_TEST_BIND_CONFIG_INCLUDE "libssh_test_bind_config_include" +#define LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE "libssh_test_bind_config_include_recursive" +#define LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP "libssh_test_bind_config_include_recursive_loop" +#define LIBSSH_TEST_BIND_CONFIG_CORNER_CASES "libssh_test_bind_config_corner_cases" + +#define LIBSSH_TEST_BIND_CONFIG_MATCH_ALL "libssh_test_bind_config_match_all" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_TWICE "libssh_test_bind_config_match_twice" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_UNSUPPORTED "libssh_test_bind_config_match_unsupported" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_NOT_ALLOWED "libssh_test_bind_config_match_not_allowed" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_CORNER_CASES "libssh_test_bind_config_match_corner_cases" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID "libssh_test_bind_config_match_invalid" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2 "libssh_test_bind_config_match_invalid2" + +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED "libssh_test_bind_config_pubkey" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2 "libssh_test_bind_config_pubkey2" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE "libssh_test_bind_config_pubkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC "libssh_test_bind_config_pubkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN "libssh_test_bind_config_pubkey_unknown" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES_UNKNOWN"\n" + +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS "libssh_test_bind_config_hostkey_alg" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2 "libssh_test_bind_config_hostkey_alg2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE "libssh_test_bind_config_hostkey_alg_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC "libssh_test_bind_config_hostkey_alg_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN "libssh_test_bind_config_hostkey_alg_unknown" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS_UNKNOWN"\n" + +const char template[] = "temp_dir_XXXXXX"; + +struct bind_st { + char *cwd; + char *temp_dir; + ssh_bind bind; +}; + +static int setup_config_files(void **state) +{ + struct bind_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct bind_st *)malloc(sizeof(struct bind_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + /* For ed25519 the test keys are not available in legacy PEM format. Using + * the new OpenSSH format for all algorithms */ + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + + torture_write_file(LIBSSH_ED25519_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); +#ifdef HAVE_ECC + torture_write_file(LIBSSH_ECDSA_521_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); +#endif + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT, + LIBSSH_TEST_BIND_CONFIG_PORT_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT2, + LIBSSH_TEST_BIND_CONFIG_PORT2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY2, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS2, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS, + LIBSSH_TEST_BIND_CONFIG_MACS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS2, + LIBSSH_TEST_BIND_CONFIG_MACS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE, + LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_FULL, + "ListenAddress "LISTEN_ADDRESS"\n" + "Port 123\n" + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" + "LogLevel "LOGLEVEL"\n" + "Ciphers "CIPHERS"\n" + "MACs "MACS"\n" + "KexAlgorithms "KEXALGORITHMS"\n" + "RequiredRsaSize 2233\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE, + "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_PORT"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_MACS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE, + "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP, + "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP"\n"); + + /* Unsupported options and corner cases */ + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CORNER_CASES, + "\n" /* empty line */ + "# comment line\n" + " # comment line not starting with hash\n" + "UnknownConfigurationOption yes\n" + "Ciphers "CIPHERS2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_ALL, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_TWICE, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_UNSUPPORTED, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match User alice\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Group sftp_users\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Host 192.168.0.*\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match LocalAddress 172.30.1.5\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match LocalPort 42\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Rdomain 4\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Address 10.0.0.10\n" + "\tLogLevel "LOGLEVEL2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_NOT_ALLOWED, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All\n" + "\tListenAddress "LISTEN_ADDRESS2"\n" + "\tPort 456\n" + "\tHostKey "LIBSSH_RSA_TESTKEY"\n" + "\tCiphers "CIPHERS2"\n" + "\tMACs "MACS2"\n" + "\tKexAlgorithms "KEXALGORITHMS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_CORNER_CASES, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match User alice\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match User alice All\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL4"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All User alice\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL4"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING); + return 0; +} + +static int sshbind_setup(void **state) +{ + int rc; + struct bind_st *test_state = NULL; + + rc = setup_config_files((void **)&test_state); + assert_int_equal(rc, 0); + assert_non_null(test_state); + + test_state->bind = ssh_bind_new(); + assert_non_null(test_state->bind); + + *state = test_state; + + return 0; +} + +static int sshbind_teardown(void **state) +{ + struct bind_st *test_state = NULL; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + assert_non_null(test_state->bind); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + ssh_bind_free(test_state->bind); + SAFE_FREE(test_state); + + return 0; +} + +/** + * @brief helper function loading configuration from either file or string + */ +static void +_parse_config(ssh_bind bind, + const char *file, + const char *string, + int expected) +{ + int ret = -1; + + /* make sure either config file or config string is given, + * not both */ + assert_int_not_equal(file == NULL, string == NULL); + + if (file != NULL) { + ret = ssh_bind_config_parse_file(bind, file); + } else if (string != NULL) { + ret = ssh_bind_config_parse_string(bind, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_return_code(ret, expected); +} + + +static void +torture_bind_config_listen_address(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, expect); +} + +static void torture_bind_config_listen_address_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS, + NULL, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address2_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2, + NULL, + LISTEN_ADDRESS2); +} + +static void torture_bind_config_listen_address2_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING, + LISTEN_ADDRESS2); +} + +static void torture_bind_config_listen_address_twice_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE, + NULL, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_twice_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_twice_rec_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC, + NULL, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_twice_rec_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING, + LISTEN_ADDRESS); +} + +static void +torture_bind_config_port(void **state, + const char *file, + const char *string, + int expect) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_int_equal(bind->bindport, expect); +} + +static void torture_bind_config_port_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT, + NULL, + 123); +} + +static void torture_bind_config_port_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_STRING, + 123); +} + +static void torture_bind_config_port2_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT2, + NULL, + 456); +} + +static void torture_bind_config_port2_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT2_STRING, + 456); +} + +static void torture_bind_config_port_twice_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE, + NULL, + 123); +} + +static void torture_bind_config_port_twice_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING, + 123); +} + +static void torture_bind_config_port_twice_rec_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC, + NULL, + 123); +} + +static void torture_bind_config_port_twice_rec_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING, + 123); +} + +static void +torture_bind_config_hostkey(void **state, + const char *file, + const char *string) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +} + +static void +torture_bind_config_hostkey2(void **state, + const char *file, + const char *string) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); +} + +static void torture_bind_config_hostkey_file(void **state) +{ + torture_bind_config_hostkey(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY, NULL); +} + +static void torture_bind_config_hostkey_string(void **state) +{ + torture_bind_config_hostkey(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING); +} + +static void torture_bind_config_hostkey_twice_file(void **state) +{ + torture_bind_config_hostkey2(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE, NULL); +} + +static void torture_bind_config_hostkey_twice_string(void **state) +{ + torture_bind_config_hostkey2(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING); +} + +static void torture_bind_config_hostkey_twice_rec_file(void **state) +{ + torture_bind_config_hostkey2(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC, NULL); +} + +static void torture_bind_config_hostkey_twice_rec_string(void **state) +{ + torture_bind_config_hostkey2(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING); +} + +static void torture_bind_config_hostkey_separately(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_HOSTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_HOSTKEY2); + assert_int_equal(rc, 0); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +} + +static void +torture_bind_config_loglevel(void **state, + const char *file, + const char *string, + int expect) +{ + struct bind_st *test_state; + ssh_bind bind; + int previous_level, new_level, rc; + + previous_level = ssh_get_log_level(); + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, expect); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_loglevel_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL, + NULL, + 2); +} + +static void torture_bind_config_loglevel_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING, + 2); +} + +static void torture_bind_config_loglevel1_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1, + NULL, + 1); +} + +static void torture_bind_config_loglevel1_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING, + 1); +} + +static void torture_bind_config_loglevel_twice_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE, + NULL, + 2); +} + +static void torture_bind_config_loglevel_twice_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING, + 2); +} + +static void torture_bind_config_loglevel_twice_rec_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC, + NULL, + 2); +} + +static void torture_bind_config_loglevel_twice_rec_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING, + 2); +} + +static void +torture_bind_config_ciphers(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_ciphers = NULL; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, expect); + assert_non_null(fips_ciphers); + } + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); + SAFE_FREE(fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], expect); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], expect); + } +} + +static void torture_bind_config_ciphers_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS, + NULL, + CIPHERS); +} + +static void torture_bind_config_ciphers_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING, + CIPHERS); +} + +static void torture_bind_config_ciphers2_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2, + NULL, + CIPHERS2); +} + +static void torture_bind_config_ciphers2_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING, + CIPHERS2); +} + +static void torture_bind_config_ciphers_twice_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE, + NULL, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_rec_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC, + NULL, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_rec_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING, + CIPHERS); +} + +static void +torture_bind_config_macs(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], expect); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], expect); +} + +static void torture_bind_config_macs_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS, + NULL, + MACS); +} + +static void torture_bind_config_macs_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_STRING, + MACS); +} + +static void torture_bind_config_macs2_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS2, + NULL, + MACS2); +} + +static void torture_bind_config_macs2_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS2_STRING, + MACS2); +} + +static void torture_bind_config_macs_twice_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE, + NULL, + MACS); +} + +static void torture_bind_config_macs_twice_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING, + MACS); +} + +static void torture_bind_config_macs_twice_rec_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC, + NULL, + MACS); +} + +static void torture_bind_config_macs_twice_rec_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING, + MACS); +} + +static void +torture_bind_config_kexalgorithms(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_kex = NULL; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + if (ssh_fips_mode()) { + fips_kex = ssh_keep_fips_algos(SSH_KEX, expect); + assert_non_null(fips_kex); + } + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); + SAFE_FREE(fips_kex); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], expect); + } +} + +static void torture_bind_config_kexalgorithms_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS, + NULL, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms2_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2, + NULL, + KEXALGORITHMS2); +} + +static void torture_bind_config_kexalgorithms2_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING, + KEXALGORITHMS2); +} + +static void torture_bind_config_kexalgorithms_twice_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE, + NULL, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_rec_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, + NULL, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_rec_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING, + KEXALGORITHMS); +} + +static void +torture_bind_config_pubkey_accepted(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_pubkeys = NULL; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + if (ssh_fips_mode()) { + fips_pubkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, expect); + assert_non_null(fips_pubkeys); + } + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys); + SAFE_FREE(fips_pubkeys); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, expect); + } +} + +static void torture_bind_config_pubkey_accepted_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED, + NULL, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_twice_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE, + NULL, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_twice_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_twice_rec_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC, + NULL, + PUBKEYACCEPTEDTYPES2); +} + +static void torture_bind_config_pubkey_accepted_twice_rec_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING, + PUBKEYACCEPTEDTYPES2); +} + +static void torture_bind_config_pubkey_accepted_unknown_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN, + NULL, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_unknown_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted2_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2, + NULL, + PUBKEYACCEPTEDTYPES2); +} + +static void torture_bind_config_pubkey_accepted2_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING, + PUBKEYACCEPTEDTYPES2); +} + +static void +torture_bind_config_hostkey_algorithms(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_hostkey = NULL; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + if (ssh_fips_mode()) { + fips_hostkey = ssh_keep_fips_algos(SSH_HOSTKEYS, expect); + assert_non_null(fips_hostkey); + } + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkey); + SAFE_FREE(fips_hostkey); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], expect); + } +} + +static void torture_bind_config_hostkey_algorithms_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_rec_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC, + NULL, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms_twice_rec_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms2_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2, + NULL, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms2_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms_unknown_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_unknown_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING, + HOSTKEYALGORITHMS); +} + +static int assert_full_bind_config(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int new_level; + + char *fips_ciphers = NULL; + char *fips_kex = NULL; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + fips_kex = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS); + assert_non_null(fips_kex); + } + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 2); + + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, LISTEN_ADDRESS); + + assert_int_equal(bind->bindport, 123); + + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS); + } + + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS); + } + + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); + + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); + + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); + } + + assert_int_equal(bind->rsa_min_size, 2233); + + SAFE_FREE(fips_ciphers); + SAFE_FREE(fips_kex); + + return 0; +} + +static void torture_bind_config_full(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_FULL); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_include(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_INCLUDE); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_include_recursive(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_include_recursive_loop(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP); + assert_int_equal(rc, 0); +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options + */ +static void torture_bind_config_corner_cases(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_CORNER_CASES); + assert_int_equal(rc, 0); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS2); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS2); +} + +static void torture_bind_config_match_all(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_ALL); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_twice(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_TWICE); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_unsupported(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_UNSUPPORTED); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_not_allowed(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_NOT_ALLOWED); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_corner_cases(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_CORNER_CASES); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 3); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_invalid(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID); + assert_int_equal(rc, -1); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2); + assert_int_equal(rc, -1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_separately, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel1_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel1_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_full, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_include, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_include_recursive, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_include_recursive_loop, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_corner_cases, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_all, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_twice, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_unsupported, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_not_allowed, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_corner_cases, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_invalid, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_unknown_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_unknown_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_unknown_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_unknown_string, + sshbind_setup, sshbind_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_buffer.c b/src/libs/libssh-0.12.2/tests/unittests/torture_buffer.c new file mode 100644 index 000000000000..028645fa3edc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_buffer.c @@ -0,0 +1,404 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#define DEBUG_BUFFER +#include "buffer.c" + +#include + +#define LIMIT (8*1024*1024) + +static int setup(void **state) { + ssh_buffer buffer; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return -1; + } + ssh_buffer_set_secure(buffer); + *state = (void *) buffer; + + return 0; +} + +static int teardown(void **state) { + SSH_BUFFER_FREE(*state); + + return 0; +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity + */ +static void torture_growing_buffer(void **state) { + ssh_buffer buffer = *state; + int i; + + for(i=0;iused >= 128){ + if(ssh_buffer_get_len(buffer) * 2 < buffer->allocated){ + assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated); + } + } + } +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity, when we remove 1 byte after each call (sliding window) + */ +static void torture_growing_buffer_shifting(void **state) { + ssh_buffer buffer = *state; + int i; + unsigned char c; + for(i=0; i<1024;++i){ + ssh_buffer_add_data(buffer,"S",1); + } + for(i=0;iused >= 128){ + if(ssh_buffer_get_len(buffer) * 4 < buffer->allocated){ + assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated); + return; + } + } + } +} + +/* + * Test the behavior of ssh_buffer_prepend_data + */ +static void torture_buffer_prepend(void **state) { + ssh_buffer buffer = *state; + uint32_t v; + ssh_buffer_add_data(buffer,"abcdef",6); + ssh_buffer_prepend_data(buffer,"xyz",3); + assert_int_equal(ssh_buffer_get_len(buffer),9); + assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9); + + /* Now remove 4 bytes and see if we can replace them */ + ssh_buffer_get_u32(buffer,&v); + assert_int_equal(ssh_buffer_get_len(buffer),5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer,"aris",4); + assert_int_equal(ssh_buffer_get_len(buffer),9); + assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9); + + /* same thing but we add 5 bytes now */ + ssh_buffer_get_u32(buffer,&v); + assert_int_equal(ssh_buffer_get_len(buffer),5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer,"12345",5); + assert_int_equal(ssh_buffer_get_len(buffer),10); + assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10); +} + +/* + * Test the behavior of ssh_buffer_get_ssh_string with invalid data + */ +static void torture_ssh_buffer_get_ssh_string(void **state) { + ssh_buffer buffer; + int i,j,k,l, rc; + /* some values that can go wrong */ + uint32_t values[] = {0xffffffff, 0xfffffffe, 0xfffffffc, 0xffffff00, + 0x80000000, 0x80000004, 0x7fffffff}; + char data[128]; + (void)state; + memset(data,'X',sizeof(data)); + for(i=0; i < (int)(sizeof(values)/sizeof(values[0]));++i){ + for(j=0; j< (int)sizeof(data);++j){ + for(k=1;k<5;++k){ + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + for(l=0;lsecure); + SSH_BUFFER_FREE(dup_buffer); + + /* test buffer with data */ + rc = ssh_buffer_add_data(buffer, test_data, test_data_len); + assert_int_equal(rc, SSH_OK); + dup_buffer = ssh_buffer_dup(buffer); + assert_non_null(dup_buffer); + assert_int_equal(ssh_buffer_get_len(dup_buffer), test_data_len); + assert_memory_equal(ssh_buffer_get(dup_buffer), test_data, test_data_len); + assert_true(dup_buffer->secure); + + /* test independence of buffers - modify original buffer */ + rc = ssh_buffer_add_data(buffer, " more data", 10); + assert_int_equal(rc, SSH_OK); + assert_int_equal(ssh_buffer_get_len(buffer), test_data_len + 10); + assert_int_equal(ssh_buffer_get_len(dup_buffer), test_data_len); + + /* test independence of buffers - modify duplicated buffer */ + rc = ssh_buffer_add_data(dup_buffer, " different", 10); + assert_int_equal(rc, SSH_OK); + assert_int_equal(ssh_buffer_get_len(dup_buffer), test_data_len + 10); + assert_int_equal(ssh_buffer_get_len(buffer), test_data_len + 10); + + assert_memory_not_equal(ssh_buffer_get(buffer), + ssh_buffer_get(dup_buffer), + test_data_len + 10); + + SSH_BUFFER_FREE(dup_buffer); + + /* test duplicating non-secure buffer */ + null_buffer = ssh_buffer_new(); + assert_non_null(null_buffer); + rc = ssh_buffer_add_data(null_buffer, "non-secure data", 15); + assert_int_equal(rc, SSH_OK); + + dup_buffer = ssh_buffer_dup(null_buffer); + assert_non_null(dup_buffer); + assert_int_equal(ssh_buffer_get_len(dup_buffer), 15); + assert_memory_equal(ssh_buffer_get(dup_buffer), "non-secure data", 15); + assert_false(dup_buffer->secure); + + SSH_BUFFER_FREE(dup_buffer); + SSH_BUFFER_FREE(null_buffer); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_growing_buffer, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_growing_buffer_shifting, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_buffer_prepend, + setup, + teardown), + cmocka_unit_test(torture_ssh_buffer_get_ssh_string), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_add_format, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_get_format, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_get_format_error, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_buffer_pack_badformat, + setup, + teardown), + cmocka_unit_test(torture_ssh_buffer_bignum), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_dup, + setup, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_bytearray.c b/src/libs/libssh-0.12.2/tests/unittests/torture_bytearray.c new file mode 100644 index 000000000000..5bb862476717 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_bytearray.c @@ -0,0 +1,410 @@ +#include "config.h" + +#include + +#include "torture.h" +#include "libssh/bytearray.h" + +static void torture_pull_le_u8(void **state) +{ + uint8_t data[2] = {0}; + uint8_t result; + + (void)state; + + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 42); + + + data[0] = 0xf; + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 0xf); + + data[0] = 0xff; + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 0xff); + + data[1] = 0x2a; + result = PULL_LE_U8(data, 1); + assert_int_equal(result, 42); +} + +static void torture_pull_le_u16(void **state) +{ + uint8_t data[2] = {0, 0}; + uint16_t result; + + (void)state; + + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + data[1] = 0x00; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 42); + + data[0] = 0xff; + data[1] = 0x00; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0x00; + data[1] = 0xff; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0xff; + data[1] = 0xff; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0xffff); +} + +static void torture_pull_le_u32(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint32_t result; + + (void)state; + + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 42); + + data[0] = 0xff; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0x00; + data[1] = 0xff; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0xff; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xff0000); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0xff; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xff000000); + + data[0] = 0xff; + data[1] = 0xff; + data[2] = 0xff; + data[3] = 0xff; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xffffffff); +} + +static void torture_push_le_u8(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {42, 42, 42, 42}; + + (void)state; + + PUSH_LE_U8(data, 0, 42); + PUSH_LE_U8(data, 1, 42); + PUSH_LE_U8(data, 2, 42); + PUSH_LE_U8(data, 3, 42); + assert_memory_equal(data, data2, sizeof(data)); +} + +static void torture_push_le_u16(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {0xa6, 0x7f, 0x2a, 0x00}; + uint16_t result; + + (void)state; + + PUSH_LE_U16(data, 0, 32678); + PUSH_LE_U16(data, 2, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_LE_U16(data, 2); + assert_int_equal(result, 42); + + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 32678); +} + +static void torture_push_le_u32(void **state) +{ + uint8_t data[8] = {0}; + uint8_t data2[8] = {0xa6, 0x7f, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00}; + uint32_t result; + + (void)state; + + PUSH_LE_U32(data, 0, 32678); + PUSH_LE_U32(data, 4, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_LE_U32(data, 4); + assert_int_equal(result, 42); + + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 32678); + + PUSH_LE_U32(data, 0, 0xfffefffe); + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xfffefffe); +} + +static void torture_push_le_u64(void **state) +{ + uint8_t data[16] = {0}; + uint64_t result; + + (void)state; + + PUSH_LE_U64(data, 0, 32678); + + result = PULL_LE_U64(data, 0); + assert_int_equal(result, 32678); + + PUSH_LE_U64(data, 0, 0xfffefffefffefffeUL); + + result = PULL_LE_U64(data, 0); + assert_int_equal(result, 0xfffefffefffefffeUL); +} + +/****************** BIG ENDIAN ********************/ + +static void torture_pull_be_u8(void **state) +{ + uint8_t data[2] = {0}; + uint8_t result; + + (void)state; + + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 42); + + + data[0] = 0xf; + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 0xf); + + data[0] = 0xff; + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 0xff); + + data[1] = 0x2a; + result = PULL_BE_U8(data, 1); + assert_int_equal(result, 42); +} + +static void torture_pull_be_u16(void **state) +{ + uint8_t data[2] = {0, 0}; + uint16_t result; + + (void)state; + + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x00; + data[1] = 0x2a; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 42); + + data[0] = 0x00; + data[1] = 0xff; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0xff; + data[1] = 0x00; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0xff; + data[1] = 0xff; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0xffff); +} + +static void torture_pull_be_u32(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint32_t result; + + (void)state; + + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x2a; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 42); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0xff; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0xff; + data[3] = 0x00; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0x00; + data[1] = 0xff; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xff0000); + + data[0] = 0xff; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xff000000); + + data[0] = 0xff; + data[1] = 0xff; + data[2] = 0xff; + data[3] = 0xff; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xffffffff); +} + +static void torture_push_be_u8(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {42, 42, 42, 42}; + + (void)state; + + PUSH_BE_U8(data, 0, 42); + PUSH_BE_U8(data, 1, 42); + PUSH_BE_U8(data, 2, 42); + PUSH_BE_U8(data, 3, 42); + assert_memory_equal(data, data2, sizeof(data)); +} + +static void torture_push_be_u16(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {0x7f, 0xa6, 0x00, 0x2a}; + uint16_t result; + + (void)state; + + PUSH_BE_U16(data, 0, 32678); + PUSH_BE_U16(data, 2, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_BE_U16(data, 2); + assert_int_equal(result, 42); + + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 32678); +} + +static void torture_push_be_u32(void **state) +{ + uint8_t data[8] = {0}; + uint8_t data2[8] = {0x00, 0x00, 0x7f, 0xa6, 0x00, 0x00, 0x00, 0x2a}; + uint32_t result; + + (void)state; + + PUSH_BE_U32(data, 0, 32678); + PUSH_BE_U32(data, 4, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_BE_U32(data, 4); + assert_int_equal(result, 42); + + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 32678); + + PUSH_BE_U32(data, 0, 0xfffefffe); + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xfffefffe); +} + +static void torture_push_be_u64(void **state) +{ + uint8_t data[16] = {0}; + uint64_t result; + + (void)state; + + PUSH_BE_U64(data, 0, 32678); + + result = PULL_BE_U64(data, 0); + assert_int_equal(result, 32678); + + PUSH_LE_U64(data, 8, 0xfffefffe); + + result = PULL_LE_U64(data, 8); + assert_int_equal(result, 0xfffefffe); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pull_le_u8), + cmocka_unit_test(torture_pull_le_u16), + cmocka_unit_test(torture_pull_le_u32), + + cmocka_unit_test(torture_push_le_u8), + cmocka_unit_test(torture_push_le_u16), + cmocka_unit_test(torture_push_le_u32), + cmocka_unit_test(torture_push_le_u64), + + /* BIG ENDIAN */ + cmocka_unit_test(torture_pull_be_u8), + cmocka_unit_test(torture_pull_be_u16), + cmocka_unit_test(torture_pull_be_u32), + + cmocka_unit_test(torture_push_be_u8), + cmocka_unit_test(torture_push_be_u16), + cmocka_unit_test(torture_push_be_u32), + cmocka_unit_test(torture_push_be_u64), + }; + + torture_filter_tests(tests); + + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_callbacks.c b/src/libs/libssh-0.12.2/tests/unittests/torture_callbacks.c new file mode 100644 index 000000000000..ee8b03835350 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_callbacks.c @@ -0,0 +1,268 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include +#include +#include + +static int myauthcallback (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata) { + (void) prompt; + (void) buf; + (void) len; + (void) echo; + (void) verify; + (void) userdata; + return 0; +} + +static int setup(void **state) +{ + struct ssh_callbacks_struct *cb = NULL; + + cb = calloc(1, sizeof(struct ssh_callbacks_struct)); + assert_non_null(cb); + + cb->userdata = (void *) 0x0badc0de; + cb->auth_function = myauthcallback; + + ssh_callbacks_init(cb); + *state = cb; + + return 0; +} + +static int teardown(void **state) +{ + free(*state); + + return 0; +} + +static void torture_callbacks_size(void **state) { + struct ssh_callbacks_struct *cb = *state; + + assert_int_not_equal(cb->size, 0); +} + +static void torture_callbacks_exists(void **state) { + struct ssh_callbacks_struct *cb = *state; + + assert_int_not_equal(ssh_callbacks_exists(cb, auth_function), 0); + assert_int_equal(ssh_callbacks_exists(cb, log_function), 0); + + /* + * We redefine size so auth_function is outside the range of + * callbacks->size. + */ + cb->size = (unsigned char *) &cb->auth_function - (unsigned char *) cb; + assert_int_equal(ssh_callbacks_exists(cb, auth_function), 0); + + /* Now make it one pointer bigger so we spill over the auth_function slot */ + cb->size += sizeof(void *); + assert_int_not_equal(ssh_callbacks_exists(cb, auth_function), 0); +} + +struct test_mock_state { + int executed; +}; + +static void test_mock_ssh_logging_callback(int priority, + const char *function, + const char *buffer, + void *userdata) +{ + struct test_mock_state *t = (struct test_mock_state *)userdata; + + check_expected(priority); + check_expected(function); + check_expected(buffer); + + t->executed++; +} + +static void torture_log_callback(void **state) +{ + struct test_mock_state t = { + .executed = 0, + }; + + (void)state; /* unused */ + + ssh_set_log_callback(test_mock_ssh_logging_callback); + ssh_set_log_userdata(&t); + ssh_set_log_level(1); + + expect_value(test_mock_ssh_logging_callback, priority, 1); + expect_string(test_mock_ssh_logging_callback, function, "torture_log_callback"); + expect_string(test_mock_ssh_logging_callback, buffer, "torture_log_callback: test"); + + SSH_LOG(SSH_LOG_WARN, "test"); + + assert_int_equal(t.executed, 1); +} + +static void cb1(ssh_session session, ssh_channel channel, void *userdata){ + int *v = userdata; + (void) session; + (void) channel; + *v += 1; +} + +static void cb2(ssh_session session, ssh_channel channel, int status, void *userdata){ + int *v = userdata; + (void) session; + (void) channel; + (void) status; + *v += 10; +} + +static void torture_callbacks_execute_list(void **state){ + struct ssh_list *list = ssh_list_new(); + int v = 0, w = 0; + struct ssh_channel_callbacks_struct c1 = { + .channel_eof_function = cb1, + .userdata = &v + }; + struct ssh_channel_callbacks_struct c2 = { + .channel_exit_status_function = cb2, + .userdata = &v + }; + struct ssh_channel_callbacks_struct c3 = { + .channel_eof_function = cb1, + .channel_exit_status_function = cb2, + .userdata = &w + }; + + (void)state; + + assert_non_null(list); + + ssh_callbacks_init(&c1); + ssh_callbacks_init(&c2); + ssh_callbacks_init(&c3); + + ssh_list_append(list, &c1); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_eof_function, + NULL, + NULL); + assert_int_equal(v, 1); + + v = 0; + ssh_list_append(list, &c2); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_eof_function, + NULL, + NULL); + assert_int_equal(v, 1); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_exit_status_function, + NULL, + NULL, + 0); + assert_int_equal(v, 11); + + v = 0; + w = 0; + ssh_list_append(list, &c3); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_eof_function, + NULL, + NULL); + assert_int_equal(v, 1); + assert_int_equal(w, 1); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_exit_status_function, + NULL, + NULL, + 0); + assert_int_equal(v, 11); + assert_int_equal(w, 11); + + ssh_list_free(list); + +} + +static int cb3(ssh_session session, ssh_channel channel, void *userdata){ + int *v = userdata; + (void)session; + (void)channel; + *v = 1; + return 10; +} + +static void torture_callbacks_iterate(void **state){ + struct ssh_list *list = ssh_list_new(); + int v = 0, w = 0; + struct ssh_channel_callbacks_struct c1 = { + .channel_eof_function = cb1, + .channel_shell_request_function = cb3, + .userdata = &v + }; + struct ssh_channel_callbacks_struct c2 = { + .channel_eof_function = cb1, + .channel_shell_request_function = cb3, + .userdata = &v + }; + + (void)state; /* unused */ + + assert_non_null(list); + + ssh_callbacks_init(&c1); + ssh_callbacks_init(&c2); + + ssh_list_append(list, &c1); + ssh_list_append(list, &c2); + + ssh_callbacks_iterate(list, ssh_channel_callbacks, channel_eof_function){ + ssh_callbacks_iterate_exec(channel_eof_function, NULL, NULL); + } + ssh_callbacks_iterate_end(); + + assert_int_equal(v, 2); + + v = 0; + ssh_callbacks_iterate(list, ssh_channel_callbacks, channel_shell_request_function){ + w = ssh_callbacks_iterate_exec(channel_shell_request_function, NULL, NULL); + if (w) { + break; + } + } + ssh_callbacks_iterate_end(); + + assert_int_equal(w, 10); + assert_int_equal(v, 1); + + ssh_list_free(list); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_callbacks_size, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_callbacks_exists, + setup, + teardown), + cmocka_unit_test(torture_log_callback), + cmocka_unit_test(torture_callbacks_execute_list), + cmocka_unit_test(torture_callbacks_iterate), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_channel.c b/src/libs/libssh-0.12.2/tests/unittests/torture_channel.c new file mode 100644 index 000000000000..23defc0ffa6c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_channel.c @@ -0,0 +1,198 @@ +#include "config.h" + +#define LIBSSH_STATIC +#include + +#include +#include +#include + +#include "torture.h" +#include "channels.c" + +#include + +static void torture_channel_select(void **state) +{ + fd_set readfds; + int fd; + int rc; + int i; + + (void)state; /* unused */ + + ZERO_STRUCT(readfds); + + fd = open("/dev/null", 0); + assert_true(fd > 2); + + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + + for (i = 0; i < 10; i++) { + ssh_channel cin[1] = { NULL, }; + ssh_channel cout[1] = { NULL, }; + struct timeval tv = { .tv_sec = 0, .tv_usec = 1000 }; + + rc = ssh_select(cin, cout, fd + 1, &readfds, &tv); + assert_int_equal(rc, SSH_OK); + } + + close(fd); +} + +static void torture_channel_null_session(void **state) +{ + ssh_channel channel = NULL; + + (void)state; + + channel = calloc(1, sizeof(struct ssh_channel_struct)); + + assert_non_null(channel); + + channel->state = SSH_CHANNEL_STATE_OPEN; + channel->session = NULL; + + assert_int_equal(ssh_channel_is_open(channel), 0); + + free(channel); +} + +/* Feed a fabricated SSH2_MSG_CHANNEL_OPEN_CONFIRMATION with the given + * maximum packet size to a channel in OPENING state and return its + * resulting state. */ +static enum ssh_channel_state_e +channel_open_conf_maxpacket(uint32_t maxpacket) +{ + ssh_session session = NULL; + ssh_channel channel = NULL; + ssh_buffer packet = NULL; + enum ssh_channel_state_e result; + int rc; + + session = ssh_new(); + assert_non_null(session); + session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; + + channel = ssh_channel_new(session); + assert_non_null(channel); + channel->local_channel = ssh_channel_new_id(session); + channel->state = SSH_CHANNEL_STATE_OPENING; + + packet = ssh_buffer_new(); + assert_non_null(packet); + rc = ssh_buffer_pack(packet, + "dddd", + channel->local_channel, + (uint32_t)42, /* sender channel */ + (uint32_t)64000, /* initial window size */ + maxpacket); + assert_int_equal(rc, SSH_OK); + + rc = ssh_packet_channel_open_conf(session, + SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, + packet, + NULL); + assert_int_equal(rc, SSH_PACKET_USED); + + result = channel->state; + + SSH_BUFFER_FREE(packet); + ssh_free(session); + + return result; +} + +static void torture_channel_open_conf(void **state) +{ + (void)state; /* unused */ + + assert_int_equal(channel_open_conf_maxpacket(32768), + SSH_CHANNEL_STATE_OPEN); +} + +/* CVE-2026-59843: a maximum packet size of 0 in CHANNEL_OPEN_CONFIRMATION + * must not open the channel, as it would cause an infinite loop in + * channel_write_common(). */ +static void torture_channel_open_conf_zero_maxpacket(void **state) +{ + (void)state; /* unused */ + + assert_int_not_equal(channel_open_conf_maxpacket(0), + SSH_CHANNEL_STATE_OPEN); +} + +/* Feed a fabricated SSH2_MSG_CHANNEL_OPEN with the given maximum packet + * size to an unauthenticated session and return the resulting session + * error string via a static buffer. */ +static const char * +channel_open_maxpacket_error(uint32_t maxpacket) +{ + static char error[256]; + ssh_session session = NULL; + ssh_buffer packet = NULL; + int rc; + + session = ssh_new(); + assert_non_null(session); + + packet = ssh_buffer_new(); + assert_non_null(packet); + rc = ssh_buffer_pack(packet, + "sddd", + "session", + (uint32_t)42, /* sender channel */ + (uint32_t)64000, /* initial window size */ + maxpacket); + assert_int_equal(rc, SSH_OK); + + rc = ssh_packet_channel_open(session, + SSH2_MSG_CHANNEL_OPEN, + packet, + NULL); + assert_int_equal(rc, SSH_PACKET_USED); + + snprintf(error, sizeof(error), "%s", ssh_get_error(session)); + + SSH_BUFFER_FREE(packet); + ssh_free(session); + + return error; +} + +/* CVE-2026-59843: a maximum packet size of 0 in CHANNEL_OPEN must be + * rejected before any further processing. The control case with a valid + * size proceeds to the session state check, proving the zero case failed + * on the packet size specifically. */ +static void torture_channel_open_zero_maxpacket(void **state) +{ + (void)state; /* unused */ + + assert_string_equal( + channel_open_maxpacket_error(0), + "Invalid maximum packet size 0 in SSH2_MSG_CHANNEL_OPEN"); + + assert_string_equal( + channel_open_maxpacket_error(32768), + "Invalid state when receiving channel open request " + "(must be authenticated)"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_channel_select), + cmocka_unit_test(torture_channel_null_session), + cmocka_unit_test(torture_channel_open_conf), + cmocka_unit_test(torture_channel_open_conf_zero_maxpacket), + cmocka_unit_test(torture_channel_open_zero_maxpacket), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_config.c b/src/libs/libssh-0.12.2/tests/unittests/torture_config.c new file mode 100644 index 000000000000..e82aa5f5d496 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_config.c @@ -0,0 +1,3260 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#ifndef _WIN32 +#define _POSIX_PTHREAD_SEMANTICS +#include +#endif + +#include "torture.h" +#include "libssh/options.h" +#include "libssh/session.h" +#include "libssh/config_parser.h" +#include "match.c" +#include "config.c" +#include "libssh/socket.h" +#include "libssh/misc.h" + +extern LIBSSH_THREAD int ssh_log_level; + +#define USERNAME "testuser" +#define PROXYCMD "ssh -q -W %h:%p gateway.example.com" +#define ID_FILE "/etc/xxx" +#define KEXALGORITHMS "ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha1" +#define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa" +#define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521" +#define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com" +#define USER_KNOWN_HOSTS "%d/.ssh/my_known_hosts" +#define GLOBAL_KNOWN_HOSTS "/etc/ssh/my_ssh_known_hosts" +#define BIND_ADDRESS "::1" + + +#define LIBSSH_TESTCONFIG1 "libssh_testconfig1.tmp" +#define LIBSSH_TESTCONFIG2 "libssh_testconfig2.tmp" +#define LIBSSH_TESTCONFIG3 "libssh_testconfig3.tmp" +#define LIBSSH_TESTCONFIG4 "libssh_testconfig4.tmp" +#define LIBSSH_TESTCONFIG5 "libssh_testconfig5.tmp" +#define LIBSSH_TESTCONFIG6 "libssh_testconfig6.tmp" +#define LIBSSH_TESTCONFIG7 "libssh_testconfig7.tmp" +#define LIBSSH_TESTCONFIG8 "libssh_testconfig8.tmp" +#define LIBSSH_TESTCONFIG9 "libssh_testconfig9.tmp" +#define LIBSSH_TESTCONFIG10 "libssh_testconfig10.tmp" +#define LIBSSH_TESTCONFIG11 "libssh_testconfig11.tmp" +#define LIBSSH_TESTCONFIG12 "libssh_testconfig12.tmp" +#define LIBSSH_TESTCONFIG14 "libssh_testconfig14.tmp" +#define LIBSSH_TESTCONFIG15 "libssh_testconfig15.tmp" +#define LIBSSH_TESTCONFIG16 "libssh_testconfig16.tmp" +#define LIBSSH_TESTCONFIG17 "libssh_testconfig17.tmp" +#define LIBSSH_TESTCONFIG18 "libssh_testconfig18.tmp" +#define LIBSSH_TESTCONFIGGLOB "libssh_testc*[36].tmp" +#define LIBSSH_TEST_PUBKEYTYPES "libssh_test_PubkeyAcceptedKeyTypes.tmp" +#define LIBSSH_TEST_PUBKEYALGORITHMS "libssh_test_PubkeyAcceptedAlgorithms.tmp" +#define LIBSSH_TEST_NONEWLINEEND "libssh_test_NoNewLineEnd.tmp" +#define LIBSSH_TEST_NONEWLINEONELINE "libssh_test_NoNewLineOneline.tmp" +#define LIBSSH_TEST_RECURSIVE_INCLUDE "libssh_test_recursive_include.tmp" +#define LIBSSH_TESTCONFIG_MATCH_COMPLEX "libssh_test_match_complex.tmp" +#define LIBSSH_TESTCONFIG_LOGLEVEL_MISSING "libssh_test_loglevel_missing.tmp" +#define LIBSSH_TESTCONFIG_JUMP "libssh_test_jump.tmp" + +#define LIBSSH_TESTCONFIG_STRING1 \ + "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n" + +#define LIBSSH_TESTCONFIG_STRING2 \ + "Include "LIBSSH_TESTCONFIG3"\n" \ + "ProxyCommand "PROXYCMD"\n\n" + +#define LIBSSH_TESTCONFIG_STRING3 \ + "\n\nIdentityFile "ID_FILE"\n" \ + "\n\nKexAlgorithms "KEXALGORITHMS"\n" \ + "\n\nHostKeyAlgorithms "HOSTKEYALGORITHMS"\n" \ + "\n\nPubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" \ + "\n\nMACs "MACS"\n" + +/* Multiple Port settings -> parsing returns early. */ +#define LIBSSH_TESTCONFIG_STRING4 \ + "Port 123\nPort 456\n" + +/* Testing glob include */ +#define LIBSSH_TESTCONFIG_STRING5 \ + "User "USERNAME"\nInclude "LIBSSH_TESTCONFIGGLOB"\n\n" \ + +#define LIBSSH_TESTCONFIG_STRING6 \ + "ProxyCommand "PROXYCMD"\n\n" + +/* new options */ +#define LIBSSH_TESTCONFIG_STRING7 \ + "\tBindAddress "BIND_ADDRESS"\n" \ + "\tConnectTimeout 30\n" \ + "\tLogLevel DEBUG3\n" \ + "\tGlobalKnownHostsFile "GLOBAL_KNOWN_HOSTS"\n" \ + "\tCompression yes\n" \ + "\tStrictHostkeyChecking no\n" \ + "\tGSSAPIDelegateCredentials yes\n" \ + "\tGSSAPIServerIdentity example.com\n" \ + "\tGSSAPIClientIdentity home.sweet\n" \ + "\tUserKnownHostsFile "USER_KNOWN_HOSTS"\n" \ + "\tRequiredRSASize 2233\n" \ + "\tGSSAPIKeyExchange yes\n" \ + "\tGSSAPIKexAlgorithms gss-group14-sha256-\n" + +/* authentication methods */ +#define LIBSSH_TESTCONFIG_STRING8 \ + "Host gss\n" \ + "\tGSSAPIAuthentication yes\n" \ + "Host kbd\n" \ + "\tKbdInteractiveAuthentication yes\n" \ + "Host pass\n" \ + "\tPasswordAuthentication yes\n" \ + "Host pubkey\n" \ + "\tPubkeyAuthentication yes\n" \ + "Host nogss\n" \ + "\tGSSAPIAuthentication no\n" \ + "Host nokbd\n" \ + "\tKbdInteractiveAuthentication no\n" \ + "Host nopass\n" \ + "\tPasswordAuthentication no\n" \ + "Host nopubkey\n" \ + "\tPubkeyAuthentication no\n" + +/* unsupported options and corner cases */ +#define LIBSSH_TESTCONFIG_STRING9 \ + "\n" /* empty line */ \ + "# comment line\n" \ + " # comment line not starting with hash\n" \ + "UnknownConfigurationOption yes\n" \ + "GSSAPIKexAlgorithms yes\n" \ + "ControlMaster auto\n" /* SOC_NA */ \ + "VisualHostkey yes\n" /* SOC_UNSUPPORTED */ \ + "HostName =equal.sign\n" /* valid */ \ + "ProxyJump = many-spaces.com\n" /* valid */ + +/* Match keyword */ +#define LIBSSH_TESTCONFIG_STRING10 \ + "Match host example\n" \ + "\tHostName example.com\n" \ + "Match host example1,example2\n" \ + "\tHostName exampleN\n" \ + "Match user guest\n" \ + "\tHostName guest.com\n" \ + "Match user tester host testhost\n" \ + "\tHostName testhost.com\n" \ + "Match !user tester host testhost\n" \ + "\tHostName nonuser-testhost.com\n" \ + "Match all\n" \ + "\tHostName all-matched.com\n" \ + /* Unsupported options */ \ + "Match originalhost example\n" \ + "\tHostName original-example.com\n" \ + "Match localuser guest\n" \ + "\tHostName local-guest.com\n" + +/* ProxyJump */ +#define LIBSSH_TESTCONFIG_STRING11 \ + "Host simple\n" \ + "\tProxyJump jumpbox\n" \ + "Host user\n" \ + "\tProxyJump user@jumpbox\n" \ + "Host port\n" \ + "\tProxyJump jumpbox:2222\n" \ + "Host two-step\n" \ + "\tProxyJump u1@first:222,u2@second:33\n" \ + "Host three-step\n" \ + "\tProxyJump u1@first:222,u2@second:33,u3@third:444\n" \ + "Host none\n" \ + "\tProxyJump none\n" \ + "Host only-command\n" \ + "\tProxyCommand "PROXYCMD"\n" \ + "\tProxyJump jumpbox\n" \ + "Host only-jump\n" \ + "\tProxyJump jumpbox\n" \ + "\tProxyCommand "PROXYCMD"\n" \ + "Host ipv6\n" \ + "\tProxyJump [2620:52:0::fed]\n" + +/* RekeyLimit combinations */ +#define LIBSSH_TESTCONFIG_STRING12 \ + "Host default\n" \ + "\tRekeyLimit default none\n" \ + "Host data1\n" \ + "\tRekeyLimit 42G\n" \ + "Host data2\n" \ + "\tRekeyLimit 31M\n" \ + "Host data3\n" \ + "\tRekeyLimit 521K\n" \ + "Host time1\n" \ + "\tRekeyLimit default 3D\n" \ + "Host time2\n" \ + "\tRekeyLimit default 2h\n" \ + "Host time3\n" \ + "\tRekeyLimit default 160m\n" \ + "Host time4\n" \ + "\tRekeyLimit default 9600\n" + +/* Multiple IdentityFile settings all are applied */ +#define LIBSSH_TESTCONFIG_STRING13 \ + "IdentityFile id_rsa_one\n" \ + "CertificateFile id_rsa_one-cert.pub\n" \ + "IdentityFile id_ecdsa_two\n" \ + "CertificateFile id_ecdsa_two-cert.pub\n" \ + +/* +,-,^ features for all supported list */ +/* kex won't work in fips */ +#define LIBSSH_TESTCONFIG_STRING14 \ + "HostKeyAlgorithms +ssh-rsa\n" \ + "Ciphers +aes128-cbc,aes256-cbc\n" \ + "KexAlgorithms +diffie-hellman-group14-sha1,diffie-hellman-group1-sha1\n" \ + "MACs +hmac-sha1,hmac-sha1-etm@openssh.com\n" + +/* have to be algorithms which are in the default list */ +#define LIBSSH_TESTCONFIG_STRING15 \ + "HostKeyAlgorithms -rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers -aes256-ctr\n" \ + "KexAlgorithms -diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs -hmac-sha2-256-etm@openssh.com\n" + +#define LIBSSH_TESTCONFIG_STRING16 \ + "HostKeyAlgorithms ^rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers ^aes256-cbc\n" \ + "KexAlgorithms ^diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs ^hmac-sha1\n" + +/* Connection Multiplexing */ +#define LIBSSH_TESTCONFIG_STRING17 \ + "Host simple\n" \ + "\tControlMaster auto\n" \ + "\tControlPath /tmp/ssh-%r@%h:%p\n" \ + "Host none\n" \ + "\tControlMaster yes\n" \ + "\tControlPath none\n" + +#define LIBSSH_TESTCONFIG_STRING18 \ + "Host simple\n" \ + "Host af\n" \ + "\tAddressFamily any\n" \ + "Host af4\n" \ + "\tAddressFamily inet\n" \ + "Host af6\n" \ + "\tAddressFamily inet6\n" + +#define LIBSSH_TEST_PUBKEYTYPES_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" + +#define LIBSSH_TEST_PUBKEYALGORITHMS_STRING \ + "PubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" + +#define LIBSSH_TEST_NONEWLINEEND_STRING \ + "ConnectTimeout 30\n" \ + "LogLevel DEBUG3" + +#define LIBSSH_TEST_NONEWLINEONELINE_STRING \ + "ConnectTimeout 30" + +#define LIBSSH_TEST_RECURSIVE_INCLUDE_STRING \ + "Include " LIBSSH_TEST_RECURSIVE_INCLUDE + +/* Complex match cases */ +#define LIBSSH_TESTCONFIG_MATCH_COMPLEX_STRING \ + "Match originalhost \"Foo,Bar\" exec \"[ \\\"$(ps h o comm p $(ps h o ppid p $PPID))\\\" != \\\"rsync\\\" ]\"\n" \ + "Match exec \"[ \\\"$(ps h o comm p $(ps h o ppid p $PPID))\\\" != \\\"rsync\\\" ]\"\n" \ + "\tForwardAgent yes\n" \ + "\tHostName complex-match\n" + +#define LIBSSH_TESTCONFIG_LOGLEVEL_MISSING_STRING "LogLevel\n" +#define LIBSSH_TESTCONFIG_JUMP_STRING \ + "# The jump host\n" \ + "Host ub-jumphost\n" \ + " HostName 1xxxxxx\n" \ + " User ubuntu\n" \ + " IdentityFile ~/of/temp-libssh.pem\n" \ + " Port 23\n" \ + " LogLevel DEBUG3\n" \ + "\n" \ + "# Cisco Router through Jump Host\n" \ + "Host cisco-router\n" \ + " HostName xx.xxxxxxxxx\n" \ + " User username\n" \ + " ProxyJump ub-jumphost\n" \ + " Port 5555\n" \ + " #RequiredRSASize 512\n" \ + " PasswordAuthentication yes\n" \ + " LogLevel DEBUG3\n" + +/** + * @brief helper function loading configuration from either file or string + */ +static void _parse_config(ssh_session session, + const char *file, const char *string, int expected) +{ + int ret = -1; + + /* make sure either config file or config string is given, + * not both */ + assert_int_not_equal(file == NULL, string == NULL); + + if (file != NULL) { + ret = ssh_config_parse_file(session, file); + } else if (string != NULL) { + ret = ssh_config_parse_string(session, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_ssh_return_code_equal(session, ret, expected); +} + +static int setup_config_files(void **state) +{ + (void) state; /* unused */ + + unlink(LIBSSH_TESTCONFIG1); + unlink(LIBSSH_TESTCONFIG2); + unlink(LIBSSH_TESTCONFIG3); + unlink(LIBSSH_TESTCONFIG4); + unlink(LIBSSH_TESTCONFIG5); + unlink(LIBSSH_TESTCONFIG6); + unlink(LIBSSH_TESTCONFIG7); + unlink(LIBSSH_TESTCONFIG8); + unlink(LIBSSH_TESTCONFIG9); + unlink(LIBSSH_TESTCONFIG10); + unlink(LIBSSH_TESTCONFIG11); + unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); + unlink(LIBSSH_TESTCONFIG17); + unlink(LIBSSH_TESTCONFIG18); + unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); + unlink(LIBSSH_TEST_NONEWLINEEND); + unlink(LIBSSH_TEST_NONEWLINEONELINE); + unlink(LIBSSH_TESTCONFIG_MATCH_COMPLEX); + unlink(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING); + unlink(LIBSSH_TESTCONFIG_JUMP); + + torture_write_file(LIBSSH_TESTCONFIG1, + LIBSSH_TESTCONFIG_STRING1); + torture_write_file(LIBSSH_TESTCONFIG2, + LIBSSH_TESTCONFIG_STRING2); + torture_write_file(LIBSSH_TESTCONFIG3, + LIBSSH_TESTCONFIG_STRING3); + + /* Multiple Port settings -> parsing returns early. */ + torture_write_file(LIBSSH_TESTCONFIG4, + LIBSSH_TESTCONFIG_STRING4); + + /* Testing glob include */ + torture_write_file(LIBSSH_TESTCONFIG5, + LIBSSH_TESTCONFIG_STRING5); + + torture_write_file(LIBSSH_TESTCONFIG6, + LIBSSH_TESTCONFIG_STRING6); + + /* new options */ + torture_write_file(LIBSSH_TESTCONFIG7, + LIBSSH_TESTCONFIG_STRING7); + + /* authentication methods */ + torture_write_file(LIBSSH_TESTCONFIG8, + LIBSSH_TESTCONFIG_STRING8); + + /* unsupported options and corner cases */ + torture_write_file(LIBSSH_TESTCONFIG9, + LIBSSH_TESTCONFIG_STRING9); + + /* Match keyword */ + torture_write_file(LIBSSH_TESTCONFIG10, + LIBSSH_TESTCONFIG_STRING10); + + /* ProxyJump */ + torture_write_file(LIBSSH_TESTCONFIG11, + LIBSSH_TESTCONFIG_STRING11); + + /* RekeyLimit combinations */ + torture_write_file(LIBSSH_TESTCONFIG12, + LIBSSH_TESTCONFIG_STRING12); + + /* +,-,^ feature */ + torture_write_file(LIBSSH_TESTCONFIG14, + LIBSSH_TESTCONFIG_STRING14); + torture_write_file(LIBSSH_TESTCONFIG15, + LIBSSH_TESTCONFIG_STRING15); + torture_write_file(LIBSSH_TESTCONFIG16, + LIBSSH_TESTCONFIG_STRING16); + torture_write_file(LIBSSH_TESTCONFIG17, + LIBSSH_TESTCONFIG_STRING17); + torture_write_file(LIBSSH_TESTCONFIG18, + LIBSSH_TESTCONFIG_STRING18); + + torture_write_file(LIBSSH_TEST_PUBKEYTYPES, + LIBSSH_TEST_PUBKEYTYPES_STRING); + + torture_write_file(LIBSSH_TEST_PUBKEYALGORITHMS, + LIBSSH_TEST_PUBKEYALGORITHMS_STRING); + + torture_write_file(LIBSSH_TEST_NONEWLINEEND, + LIBSSH_TEST_NONEWLINEEND_STRING); + + torture_write_file(LIBSSH_TEST_NONEWLINEONELINE, + LIBSSH_TEST_NONEWLINEONELINE_STRING); + + /* Match complex combinations */ + torture_write_file(LIBSSH_TESTCONFIG_MATCH_COMPLEX, + LIBSSH_TESTCONFIG_MATCH_COMPLEX_STRING); + torture_write_file(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING, + LIBSSH_TESTCONFIG_LOGLEVEL_MISSING_STRING); + torture_write_file(LIBSSH_TESTCONFIG_JUMP, + LIBSSH_TESTCONFIG_JUMP_STRING); + + return 0; +} + +static int teardown_config_files(void **state) +{ + (void) state; /* unused */ + + unlink(LIBSSH_TESTCONFIG1); + unlink(LIBSSH_TESTCONFIG2); + unlink(LIBSSH_TESTCONFIG3); + unlink(LIBSSH_TESTCONFIG4); + unlink(LIBSSH_TESTCONFIG5); + unlink(LIBSSH_TESTCONFIG6); + unlink(LIBSSH_TESTCONFIG7); + unlink(LIBSSH_TESTCONFIG8); + unlink(LIBSSH_TESTCONFIG9); + unlink(LIBSSH_TESTCONFIG10); + unlink(LIBSSH_TESTCONFIG11); + unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); + unlink(LIBSSH_TESTCONFIG17); + unlink(LIBSSH_TESTCONFIG18); + unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); + unlink(LIBSSH_TEST_NONEWLINEEND); + unlink(LIBSSH_TEST_NONEWLINEONELINE); + unlink(LIBSSH_TESTCONFIG_MATCH_COMPLEX); + unlink(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING); + unlink(LIBSSH_TESTCONFIG_JUMP); + + return 0; +} + +static int setup(void **state) +{ + ssh_session session = NULL; + char *wd = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + wd = torture_get_current_working_dir(); + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, wd); + free(wd); + + *state = session; + + return 0; +} + +static int setup_no_sshdir(void **state) +{ + ssh_session session = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +/** + * @brief tests ssh config parsing with Include directives + */ +static void torture_config_include(void **state, + const char *file, const char *string) +{ + int ret; + char *v = NULL; + char *fips_algos = NULL; + ssh_session session = *state; + + _parse_config(session, file, string, SSH_OK); + + /* Test the variable presence */ + ret = ssh_options_get(session, SSH_OPTIONS_PROXYCOMMAND, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, PROXYCMD); + SSH_STRING_FREE_CHAR(v); + + ret = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, ID_FILE); + SSH_STRING_FREE_CHAR(v); + + ret = ssh_options_get(session, SSH_OPTIONS_USER, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, USERNAME); + SSH_STRING_FREE_CHAR(v); + + if (ssh_fips_mode()) { + fips_algos = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, HOSTKEYALGORITHMS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); + assert_non_null(fips_algos); + assert_string_equal(session->opts.pubkey_accepted_types, fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_MAC_C_S, MACS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], + fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_MAC_S_C, MACS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + fips_algos); + SAFE_FREE(fips_algos); + } else { + assert_non_null(session->opts.wanted_methods[SSH_KEX]); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + KEXALGORITHMS); + assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + HOSTKEYALGORITHMS); + assert_non_null(session->opts.pubkey_accepted_types); + assert_string_equal(session->opts.pubkey_accepted_types, + PUBKEYACCEPTEDTYPES); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], MACS); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], MACS); + } +} + +/** + * @brief tests ssh_config_parse_file with Include directives from file + */ +static void torture_config_include_file(void **state) +{ + torture_config_include(state, LIBSSH_TESTCONFIG1, NULL); +} + +/** + * @brief tests ssh_config_parse_string with Include directives from string + */ +static void torture_config_include_string(void **state) +{ + torture_config_include(state, NULL, LIBSSH_TESTCONFIG_STRING1); +} + +/** + * @brief tests ssh_config_parse_file with recursive Include directives from file + */ +static void torture_config_include_recursive_file(void **state) +{ + _parse_config(*state, LIBSSH_TEST_RECURSIVE_INCLUDE, NULL, SSH_OK); +} + +/** + * @brief tests ssh_config_parse_string with Include directives from string + */ +static void torture_config_include_recursive_string(void **state) +{ + _parse_config(*state, NULL, LIBSSH_TEST_RECURSIVE_INCLUDE_STRING, SSH_OK); +} + +/** + * @brief tests ssh_config_parse_file with multiple Port settings. + */ +static void torture_config_double_ports_file(void **state) +{ + _parse_config(*state, LIBSSH_TESTCONFIG4, NULL, SSH_OK); +} + +/** + * @brief tests ssh_config_parse_string with multiple Port settings. + */ +static void torture_config_double_ports_string(void **state) +{ + _parse_config(*state, NULL, LIBSSH_TESTCONFIG_STRING4, SSH_OK); +} + +static void torture_config_glob(void **state, + const char *file, const char *string) +{ +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + int ret; + char *v; + ssh_session session = *state; + + _parse_config(session, file, string, SSH_OK); + + /* Test the variable presence */ + + ret = ssh_options_get(session, SSH_OPTIONS_PROXYCOMMAND, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, PROXYCMD); + SSH_STRING_FREE_CHAR(v); + + ret = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, ID_FILE); + SSH_STRING_FREE_CHAR(v); +#endif /* HAVE_GLOB && HAVE_GLOB_GL_FLAGS_MEMBER */ +} + +static void torture_config_glob_file(void **state) +{ + torture_config_glob(state, LIBSSH_TESTCONFIG5, NULL); +} + +static void torture_config_glob_string(void **state) +{ + torture_config_glob(state, NULL, LIBSSH_TESTCONFIG_STRING5); +} + +/** + * @brief Verify the new options are passed from configuration + */ +static void torture_config_new(void ** state, + const char *file, const char *string) +{ + ssh_session session = *state; + + _parse_config(session, file, string, SSH_OK); + + assert_string_equal(session->opts.knownhosts, USER_KNOWN_HOSTS); + assert_string_equal(session->opts.global_knownhosts, GLOBAL_KNOWN_HOSTS); + assert_int_equal(session->opts.timeout, 30); + assert_string_equal(session->opts.bindaddr, BIND_ADDRESS); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); +#endif /* WITH_ZLIB */ + assert_int_equal(session->opts.StrictHostKeyChecking, 0); + assert_int_equal(session->opts.gss_delegate_creds, 1); + assert_string_equal(session->opts.gss_server_identity, "example.com"); + assert_string_equal(session->opts.gss_client_identity, "home.sweet"); +#ifdef WITH_GSSAPI + assert_true(session->opts.gssapi_key_exchange); + assert_string_equal(session->opts.gssapi_key_exchange_algs, + "gss-group14-sha256-"); +#endif /* WITH_GSSAPI */ + + assert_int_equal(ssh_get_log_level(), SSH_LOG_TRACE); + assert_int_equal(session->common.log_verbosity, SSH_LOG_TRACE); + assert_int_equal(session->opts.rsa_min_size, 2233); +} + +static void torture_config_new_file(void **state) +{ + torture_config_new(state, LIBSSH_TESTCONFIG7, NULL); +} + +static void torture_config_new_string(void **state) +{ + torture_config_new(state, NULL, LIBSSH_TESTCONFIG_STRING7); +} + +/** + * @brief Verify the authentication methods from configuration are effective + */ +static void torture_config_auth_methods(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + /* gradually disable all the methods based on different hosts */ + ssh_options_set(session, SSH_OPTIONS_HOST, "nogss"); + _parse_config(session, file, string, SSH_OK); + assert_false(session->opts.flags & SSH_OPT_FLAG_GSSAPI_AUTH); + assert_true(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "nokbd"); + _parse_config(session, file, string, SSH_OK); + assert_false(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "nopass"); + _parse_config(session, file, string, SSH_OK); + assert_false(session->opts.flags & SSH_OPT_FLAG_PASSWORD_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "nopubkey"); + _parse_config(session, file, string, SSH_OK); + assert_false(session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH); + + /* no method should be left enabled */ + assert_int_equal(session->opts.flags, 0); + + /* gradually enable them again */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "gss"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.flags & SSH_OPT_FLAG_GSSAPI_AUTH); + assert_false(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "kbd"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "pass"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.flags & SSH_OPT_FLAG_PASSWORD_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "pubkey"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH); +} + +/** + * @brief Verify the authentication methods from configuration file + * are effective + */ +static void torture_config_auth_methods_file(void **state) +{ + torture_config_auth_methods(state, LIBSSH_TESTCONFIG8, NULL); +} + +/** + * @brief Verify the authentication methods from configuration string + * are effective + */ +static void torture_config_auth_methods_string(void **state) +{ + torture_config_auth_methods(state, NULL, LIBSSH_TESTCONFIG_STRING8); +} + +/** + * @brief Helper for checking hostname, username and port of ssh_jump_info_struct + */ +static void +helper_proxy_jump_check(struct ssh_iterator *jump, + const char *hostname, + const char *username, + const char *port) +{ + struct ssh_jump_info_struct *jis = + ssh_iterator_value(struct ssh_jump_info_struct *, jump); + + assert_string_equal(jis->hostname, hostname); + + if (username != NULL) { + assert_string_equal(jis->username, username); + } else { + assert_null(jis->username); + } + + if (port != NULL) { + int iport = strtol(port, NULL, 10); + assert_int_equal(jis->port, iport); + } else { + /* No port in the ProxyJump spec: left unset for the jump host's own + * configuration to supply. */ + assert_int_equal(jis->port, 0); + } +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options + */ +static void torture_config_unknown(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + int ret = 0; + + /* test corner cases */ + /* Without libssh proxy jump */ + torture_setenv("OPENSSH_PROXYJUMP", "1"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -W '[%h]:%p' many-spaces.com"); + assert_string_equal(session->opts.host, "equal.sign"); + + ret = ssh_config_parse_file(session, "/etc/ssh/ssh_config"); + assert_true(ret == 0); + ret = ssh_config_parse_file(session, GLOBAL_CLIENT_CONFIG); + assert_true(ret == 0); + torture_unsetenv("OPENSSH_PROXYJUMP"); +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options in configuration file + */ +static void torture_config_unknown_file(void **state) +{ + torture_config_unknown(state, LIBSSH_TESTCONFIG9, NULL); +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options in configuration string + */ +static void torture_config_unknown_string(void **state) +{ + torture_config_unknown(state, NULL, LIBSSH_TESTCONFIG_STRING9); +} + +/** + * @brief Verify the configuration parser accepts Match keyword with + * full OpenSSH syntax. + */ +static void torture_config_match(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + char *localuser = NULL; + const char *config = NULL; + char config_string[1024]; + + /* Without any settings we should get all-matched.com hostname */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "all-matched.com"); + + /* Hostname example does simple hostname matching */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "example.com"); + + /* We can match also both hosts from a comma separated list */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example1"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "exampleN"); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example2"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "exampleN"); + + /* We can match by user */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_USER, "guest"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "guest.com"); + + /* We can combine two options on a single line to match both of them */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_USER, "tester"); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "testhost.com"); + + /* We can also negate conditions */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_USER, "not-tester"); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "nonuser-testhost.com"); + + /* In this part, we try various other config files and strings. */ + + /* Match final is not completely supported, but should do quite much the + * same as "match all". The trailing "all" is not mandatory. */ + config = "Match final all\n" + "\tHostName final-all.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "final-all.com"); + + config = "Match final\n" + "\tHostName final.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "final.com"); + + /* Match canonical is not completely supported, but should do quite + * much the same as "match all". The trailing "all" is not mandatory. */ + config = "Match canonical all\n" + "\tHostName canonical-all.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "canonical-all.com"); + + config = "Match canonical all\n" + "\tHostName canonical.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "canonical.com"); + + localuser = ssh_get_local_username(); + assert_non_null(localuser); + snprintf(config_string, sizeof(config_string), + "Match localuser %s\n" + "\tHostName otherhost\n", + localuser); + config = config_string; + free(localuser); + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "otherhost"); + + config = "Match exec true\n" + "\tHostName execed-true.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_string_equal(session->opts.host, "otherhost"); +#else + assert_string_equal(session->opts.host, "execed-true.com"); +#endif + + config = "Match !exec false\n" + "\tHostName execed-false.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_string_equal(session->opts.host, "otherhost"); +#else + assert_string_equal(session->opts.host, "execed-false.com"); +#endif + + config = "Match exec \"test 1 -eq 1\"\n" + "\tHostName execed-arguments.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_string_equal(session->opts.host, "otherhost"); +#else + assert_string_equal(session->opts.host, "execed-arguments.com"); +#endif + + /* Try to create some invalid configurations */ + /* Missing argument to Match*/ + config = "Match\n" + "\tHost missing.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "unmatched"); + + /* Missing argument to unsupported option originalhost */ + config = "Match originalhost\n" + "\tHost originalhost.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing argument to option localuser */ + config = "Match localuser\n" + "\tUser localuser2\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing argument to option user */ + config = "Match user\n" + "\tUser user2\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing argument to option host */ + config = "Match host\n" + "\tUser host2\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing argument to option exec */ + config = "Match exec\n" + "\tUser exec\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Unknown argument to Match keyword */ + config = "Match tagged tag_name\n" + "\tHostName never-matched.com\n" + "Match all\n" + "\tHostName config-host.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "config-host.com"); + + /* Missing argument to Match keyword */ + config = "Match\n" + "\tHostName never-matched.com\n" + "Match all\n" + "\tHostName config-host.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "config-host.com"); +} + +/** + * @brief Verify the configuration parser accepts Match keyword with + * full OpenSSH syntax through configuration file. + */ +static void torture_config_match_file(void **state) +{ + torture_config_match(state, LIBSSH_TESTCONFIG10, NULL); +} + +/** + * @brief Verify the configuration parser accepts Match keyword with + * full OpenSSH syntax through configuration string. + */ +static void torture_config_match_string(void **state) +{ + torture_config_match(state, NULL, LIBSSH_TESTCONFIG_STRING10); +} + +/** + * @brief Verify we can parse ProxyJump configuration option + */ +static void torture_config_proxyjump(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + const char *config = NULL; + + + /* Tests for libssh based proxyjump */ + /* Simplest version with just a hostname */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + NULL, + NULL); + + /* With username */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "user"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + "user", + NULL); + + /* With port */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "port"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + NULL, + "2222"); + + /* Two step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "second", + "u2", + "33"); + helper_proxy_jump_check(session->opts.proxy_jumps->root->next, + "first", + "u1", + "222"); + + /* Three step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "three-step"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "third", + "u3", + "444"); + helper_proxy_jump_check(session->opts.proxy_jumps->root->next, + "second", + "u2", + "33"); + helper_proxy_jump_check(session->opts.proxy_jumps->root->next->next, + "first", + "u1", + "222"); + + /* none */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 0); + + /* If also ProxyCommand is specified, the first is applied */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-command"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, PROXYCMD); + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 0); + + /* If also ProxyCommand is specified, the first is applied */ + torture_reset_config(session); + SAFE_FREE(session->opts.ProxyCommand); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); + _parse_config(session, file, string, SSH_OK); + assert_null(session->opts.ProxyCommand); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + NULL, + NULL); + + /* IPv6 address */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "2620:52:0::fed", + NULL, + NULL); + + torture_reset_config(session); + + /* Tests for proxycommand based proxyjump */ + torture_setenv("OPENSSH_PROXYJUMP", "1"); + + /* Simplest version with just a hostname */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, "ssh -W '[%h]:%p' jumpbox"); + + /* With username */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "user"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l user -W '[%h]:%p' jumpbox"); + + /* With port */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "port"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -p 2222 -W '[%h]:%p' jumpbox"); + + /* Two step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l u1 -p 222 -J u2@second:33 -W '[%h]:%p' first"); + + /* Three step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "three-step"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l u1 -p 222 -J u2@second:33,u3@third:444 -W '[%h]:%p' first"); + + /* none */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.ProxyCommand == NULL); + + /* If also ProxyCommand is specified, the first is applied */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-command"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, PROXYCMD); + + /* If also ProxyCommand is specified, the first is applied */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -W '[%h]:%p' jumpbox"); + + /* IPv6 address */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -W '[%h]:%p' 2620:52:0::fed"); + + + /* Multiple @ is allowed in second jump */ + config = "Host allowed-hostname\n" + "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -J user@principal.com@jumpbox:22 -W '[%h]:%p' localhost"); + + /* Multiple @ is allowed */ + config = "Host allowed-hostname\n" + "\tProxyJump user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l user@principal.com -p 22 -W '[%h]:%p' jumpbox"); + torture_unsetenv("OPENSSH_PROXYJUMP"); + + /* Tests for libssh based proxyjump */ + /* Multiple @ is allowed in second jump */ + config = "Host allowed-hostname\n" + "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + "user@principal.com", + "22"); + + /* Multiple @ is allowed */ + config = "Host allowed-hostname\n" + "\tProxyJump user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + "user@principal.com", + "22"); + torture_reset_config(session); + + /* In this part, we try various other config files and strings. */ + torture_setenv("OPENSSH_PROXYJUMP", "1"); + + /* Try to create some invalid configurations */ + /* Non-numeric port */ + config = "Host bad-port\n" + "\tProxyJump jumpbox:22bad22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); + _parse_config(session, file, string, SSH_ERROR); + + /* Braces mismatch in hostname */ + config = "Host mismatch\n" + "\tProxyJump [::1\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "mismatch"); + _parse_config(session, file, string, SSH_ERROR); + + /* Bad host-port separator */ + config = "Host beef\n" + "\tProxyJump [dead::beef]::22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "beef"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing hostname */ + config = "Host no-host\n" + "\tProxyJump user@:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-host"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing user */ + config = "Host no-user\n" + "\tProxyJump @host:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-user"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing port */ + config = "Host no-port\n" + "\tProxyJump host:\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-port"); + _parse_config(session, file, string, SSH_ERROR); + + /* Non-numeric port in second jump */ + config = "Host bad-port-2\n" + "\tProxyJump localhost,jumpbox:22bad22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port-2"); + _parse_config(session, file, string, SSH_ERROR); + + /* Braces mismatch in second jump */ + config = "Host mismatch\n" + "\tProxyJump localhost,[::1:20\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "mismatch"); + _parse_config(session, file, string, SSH_ERROR); + + /* Bad host-port separator in second jump */ + config = "Host beef\n" + "\tProxyJump localhost,[dead::beef]::22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "beef"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing hostname in second jump */ + config = "Host no-host\n" + "\tProxyJump localhost,user@:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-host"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing user in second jump */ + config = "Host no-user\n" + "\tProxyJump localhost,@host:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-user"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing port in second jump */ + config = "Host no-port\n" + "\tProxyJump localhost,host:\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-port"); + _parse_config(session, file, string, SSH_ERROR); + + torture_unsetenv("OPENSSH_PROXYJUMP"); +} + +/** + * @brief Verify we can parse ProxyJump configuration option from file + */ +static void torture_config_proxyjump_file(void **state) +{ + torture_config_proxyjump(state, LIBSSH_TESTCONFIG11, NULL); +} + +/** + * @brief Verify we can parse ProxyJump configuration option from string + */ +static void torture_config_proxyjump_string(void **state) +{ + torture_config_proxyjump(state, NULL, LIBSSH_TESTCONFIG_STRING11); +} + +/** + * @brief Verify we can parse ControlPath configuration option + */ +static void torture_config_control_path(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_null(session->opts.control_path); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_null(session->opts.control_path); +} + +/** + * @brief Verify we can parse ControlPath configuration option from string + */ +static void torture_config_control_path_string(void **state) +{ + torture_config_control_path(state, NULL, LIBSSH_TESTCONFIG_STRING17); +} + +/** + * @brief Verify we can parse ControlPath configuration option from file + */ +static void torture_config_control_path_file(void **state) +{ + torture_config_control_path(state, LIBSSH_TESTCONFIG17, NULL); +} + +/** + * @brief Verify we can parse ControlMaster configuration option + */ +static void torture_config_control_master(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_NO); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_NO); +} + +/** + * @brief Verify we can parse ControlMaster configuration option from string + */ +static void torture_config_control_master_string(void **state) +{ + torture_config_control_master(state, NULL, LIBSSH_TESTCONFIG_STRING17); +} + +/** + * @brief Verify we can parse ControlMaster configuration option from file + */ +static void torture_config_control_master_file(void **state) +{ + torture_config_control_master(state, LIBSSH_TESTCONFIG17, NULL); +} + +/** + * @brief Verify we can parse AdressFamily configuration option + */ +static void torture_config_address_family(void **state, + const char *file, + const char *string) +{ + ssh_session session = *state; + + const char *config = NULL; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_ANY); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "af"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_ANY); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "af4"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_INET); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "af6"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_INET6); + + /* test for parsing failures */ + config = "Host afmissing\n" + "\tAddressFamily\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "afmissing"); + _parse_config(session, file, string, SSH_ERROR); + + config = "Host afinvalid\n" + "\tAddressFamily wurstkäse\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "afinvalid"); + _parse_config(session, file, string, SSH_ERROR); +} + +/** + * @brief Verify we can parse AdressFamily configuration option from string + */ +static void torture_config_address_family_string(void **state) +{ + torture_config_address_family(state, NULL, LIBSSH_TESTCONFIG_STRING18); +} + +/** + * @brief Verify we can parse AdressFamily configuration option from file + */ +static void torture_config_address_family_file(void **state) +{ + torture_config_address_family(state, LIBSSH_TESTCONFIG18, NULL); +} + +/** + * @brief Verify the configuration parser handles all the possible + * versions of RekeyLimit configuration option. + */ +static void torture_config_rekey(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + /* Default values */ + ssh_options_set(session, SSH_OPTIONS_HOST, "default"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 0); + + /* 42 GB */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "data1"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, + (uint64_t) 42 * 1024 * 1024 * 1024); + assert_int_equal(session->opts.rekey_time, 0); + + /* 41 MB */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "data2"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 31 * 1024 * 1024); + assert_int_equal(session->opts.rekey_time, 0); + + /* 521 KB */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "data3"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 521 * 1024); + assert_int_equal(session->opts.rekey_time, 0); + + /* default 3D */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time1"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 3 * 24 * 60 * 60 * 1000); + + /* default 2h */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time2"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 2 * 60 * 60 * 1000); + + /* default 160m */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time3"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 160 * 60 * 1000); + + /* default 9600 [s] */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time4"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 9600 * 1000); + +} + +/** + * @brief Verify the configuration parser handles all the possible + * versions of RekeyLimit configuration option in file + */ +static void torture_config_rekey_file(void **state) +{ + torture_config_rekey(state, LIBSSH_TESTCONFIG12, NULL); +} + +/** + * @brief Verify the configuration parser handles all the possible + * versions of RekeyLimit configuration option in string + */ +static void torture_config_rekey_string(void **state) +{ + torture_config_rekey(state, NULL, LIBSSH_TESTCONFIG_STRING12); +} + +/** + * @brief Remove substring from a string + * + * @param occurrence 0 means "remove the first occurrence" + * 1 means "remove the second occurrence" and so on + */ +static void helper_remove_substring(char *s, const char *subs, int occurrence) { + char *p; + /* remove the substring from the defaults */ + p = strstr(s, subs); + assert_non_null(p); + /* look for second occurrence */ + for (int i = 0; i < occurrence; i++) { + p = strstr(p + 1, subs); + assert_non_null(p); + } + memmove(p, p + strlen(subs), strlen(p + strlen(subs)) + 1); +} + +/** + * @brief test that openssh style '+' feature works + */ +static void torture_config_plus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_added = ",ssh-rsa"; + const char *ciphers_added = ",aes128-cbc,aes256-cbc"; + const char *kex_added = ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"; + const char *mac_added = ",hmac-sha1,hmac-sha1-etm@openssh.com"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + /* ssh-rsa is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], fips_hostkeys); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_added) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + strlen(hostkeys_added) + 1, + "%s%s", def_hostkeys, hostkeys_added); + assert_int_equal(rc, strlen(def_hostkeys) + strlen(hostkeys_added)); + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + } + + /* check ciphers */ + if (ssh_fips_mode()) { + /* already all supported is in the list */ + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + } else { + awaited = calloc(strlen(def_ciphers) + strlen(ciphers_added) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + strlen(ciphers_added) + 1, + "%s%s", def_ciphers, ciphers_added); + assert_int_equal(rc, strlen(def_ciphers) + strlen(ciphers_added)); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + } + + /* check kex */ + if (ssh_fips_mode()) { + /* sha1 is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_KEX], fips_kex); + } else { + awaited = calloc(strlen(def_kex) + strlen(kex_added) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + strlen(kex_added) + 1, + "%s%s", def_kex, kex_added); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_added)); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + } + + /* check mac */ + if (ssh_fips_mode()) { + /* the added algos are already in the fips_methods */ + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], fips_mac); + } else { + awaited = calloc(strlen(def_mac) + strlen(mac_added) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + strlen(mac_added) + 1, + "%s%s", def_mac, mac_added); + assert_int_equal(rc, strlen(def_mac) + strlen(mac_added)); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); + } +} + +/** + * @brief test that openssh style '+' feature works from file + */ +static void torture_config_plus_file(void **state) +{ + torture_config_plus(state, LIBSSH_TESTCONFIG14, NULL); +} + +/** + * @brief test that openssh style '+' feature works from string + */ +static void torture_config_plus_string(void **state) +{ + torture_config_plus(state, NULL, LIBSSH_TESTCONFIG_STRING14); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_removed = ",rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_removed = ",aes256-ctr"; + const char *kex_removed = ",diffie-hellman-group18-sha512,diffie-hellman-group16-sha512"; + const char *fips_kex_removed = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_removed = "hmac-sha2-256-etm@openssh.com,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(fips_hostkeys) + 1, "%s", fips_hostkeys); + assert_int_equal(rc, strlen(fips_hostkeys)); + } else { + awaited = calloc(strlen(def_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + 1, "%s", def_hostkeys); + assert_int_equal(rc, strlen(def_hostkeys)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(fips_ciphers) + 1, "%s", fips_ciphers); + assert_int_equal(rc, strlen(fips_ciphers)); + } else { + awaited = calloc(strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + 1, "%s", def_ciphers); + assert_int_equal(rc, strlen(def_ciphers)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(fips_kex) + 1, "%s", fips_kex); + assert_int_equal(rc, strlen(fips_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, fips_kex_removed, 0); + } else { + awaited = calloc(strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + 1, "%s", def_kex); + assert_int_equal(rc, strlen(def_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_removed, 0); + } + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(fips_mac) + 1, "%s", fips_mac); + assert_int_equal(rc, strlen(fips_mac)); + } else { + awaited = calloc(strlen(def_mac) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + 1, "%s", def_mac); + assert_int_equal(rc, strlen(def_mac)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, mac_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '-' feature works from file + */ +static void torture_config_minus_file(void **state) +{ + torture_config_minus(state, LIBSSH_TESTCONFIG15, NULL); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus_string(void **state) +{ + torture_config_minus(state, NULL, LIBSSH_TESTCONFIG_STRING15); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_prio = "rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_prio = "aes256-cbc,"; + const char *kex_prio = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,"; + const char *fips_kex_prio = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_prio = "hmac-sha1,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + /* +2 for the added comma and the \0 */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, + "%s,%s", hostkeys_prio, fips_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 1); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_prio) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(def_hostkeys) + 2, + "%s,%s", hostkeys_prio, def_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(def_hostkeys) + 1); + } + + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_prio, 1); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(ciphers_prio) + strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(fips_ciphers) + 1, + "%s%s", ciphers_prio, fips_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(fips_ciphers)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_prio, 1); + } else { + /* + 2 because the '\0' and the comma */ + awaited = calloc(strlen(ciphers_prio) + strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(def_ciphers) + 1, + "%s%s", ciphers_prio, def_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(def_ciphers)); + } + + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(kex_prio) + strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(fips_kex) + 1, + "%s%s", kex_prio, fips_kex); + assert_int_equal(rc, strlen(kex_prio) + strlen(fips_kex)); + /* remove the substring from the defaults */ + /* the default list has different order of these two algos than the fips + * and because here is a braindead string substitution being done, + * change the order and remove the first occurrence of it */ + helper_remove_substring(awaited, fips_kex_prio, 0); + } else { + awaited = calloc(strlen(kex_prio) + strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(def_kex) + 1, + "%s%s", kex_prio, def_kex); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_prio)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_prio, 1); + } + + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(mac_prio) + strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(mac_prio) + strlen(fips_mac) + 1, "%s%s", mac_prio, fips_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(fips_mac)); + /* the fips list contains hmac-sha1 algo */ + helper_remove_substring(awaited, mac_prio, 1); + } else { + awaited = calloc(strlen(mac_prio) + strlen(def_mac) + 1, 1); + /* the mac is not in default; it is added to the list */ + rc = snprintf(awaited, strlen(mac_prio) + strlen(def_mac) + 1, "%s%s", mac_prio, def_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(def_mac)); + } + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '^' feature works from file + */ +static void torture_config_caret_file(void **state) +{ + torture_config_caret(state, LIBSSH_TESTCONFIG16, NULL); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret_string(void **state) +{ + torture_config_caret(state, NULL, LIBSSH_TESTCONFIG_STRING16); +} + +/** + * @brief test PubkeyAcceptedKeyTypes helper function + */ +static void torture_config_pubkeytypes(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + char *fips_algos; + + _parse_config(session, file, string, SSH_OK); + + if (ssh_fips_mode()) { + fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); + assert_non_null(fips_algos); + assert_string_equal(session->opts.pubkey_accepted_types, fips_algos); + SAFE_FREE(fips_algos); + } else { + assert_string_equal(session->opts.pubkey_accepted_types, + PUBKEYACCEPTEDTYPES); + } +} + +/** + * @brief test parsing PubkeyAcceptedKeyTypes from file + */ +static void torture_config_pubkeytypes_file(void **state) +{ + torture_config_pubkeytypes(state, LIBSSH_TEST_PUBKEYTYPES, NULL); +} + +/** + * @brief test parsing PubkeyAcceptedKeyTypes from string + */ +static void torture_config_pubkeytypes_string(void **state) +{ + torture_config_pubkeytypes(state, NULL, LIBSSH_TEST_PUBKEYTYPES_STRING); +} + +/** + * @brief test parsing PubkeyAcceptedKAlgorithms from file + */ +static void torture_config_pubkeyalgorithms_file(void **state) +{ + torture_config_pubkeytypes(state, LIBSSH_TEST_PUBKEYALGORITHMS, NULL); +} + +/** + * @brief test parsing PubkeyAcceptedAlgorithms from string + */ +static void torture_config_pubkeyalgorithms_string(void **state) +{ + torture_config_pubkeytypes(state, NULL, LIBSSH_TEST_PUBKEYALGORITHMS_STRING); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end + */ +static void torture_config_nonewlineend(void **state, + const char *file, const char *string) +{ + _parse_config(*state, file, string, SSH_OK); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of file + */ +static void torture_config_nonewlineend_file(void **state) +{ + torture_config_nonewlineend(state, LIBSSH_TEST_NONEWLINEEND, NULL); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of string + */ +static void torture_config_nonewlineend_string(void **state) +{ + torture_config_nonewlineend(state, NULL, LIBSSH_TEST_NONEWLINEEND_STRING); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end + */ +static void torture_config_nonewlineoneline(void **state, + const char *file, + const char *string) +{ + _parse_config(*state, file, string, SSH_OK); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of file + */ +static void torture_config_nonewlineoneline_file(void **state) +{ + torture_config_nonewlineend(state, LIBSSH_TEST_NONEWLINEONELINE, NULL); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of string + */ +static void torture_config_nonewlineoneline_string(void **state) +{ + torture_config_nonewlineoneline(state, + NULL, LIBSSH_TEST_NONEWLINEONELINE_STRING); +} + +/* ssh_config_get_cmd() does these two things: + * * Strips leading whitespace + * * Terminate on the end of line + */ +static void torture_config_parser_get_cmd(void **state) +{ + char *p = NULL, *tok = NULL; + char data[256]; +#ifdef WITH_EXEC + FILE *outfile = NULL, *infile = NULL; + int pid; + char buffer[256] = {0}; +#endif + (void)state; + + /* Ignore leading whitespace */ + strncpy(data, " \t\t string\n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + /* but keeps the trailing whitespace */ + strncpy(data, "string \t\t \n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "string \t\t "); + assert_int_equal(*p, '\0'); + + /* should not drop the quotes and not split them into separate arguments */ + strncpy(data, "\"multi string\" something\n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "\"multi string\" something"); + assert_int_equal(*p, '\0'); + + /* But it does not split tokens by whitespace + * if they are not quoted, which is weird */ + strncpy(data, "multi string something\n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "multi string something"); + assert_int_equal(*p, '\0'); + + /* Commands in quotes are not treated special */ + sprintf(data, "%s%s%s%s", "\"", SOURCEDIR "/tests/unittests/hello world.sh", "\" ", "\"hello libssh\"\n"); + printf("%s\n", data); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, data); + assert_int_equal(*p, '\0'); + +#ifdef WITH_EXEC + /* Check if the command would get correctly executed + * Use the script file "hello world.sh" to echo the first argument + * Run as <= "/workdir/hello world.sh" "hello libssh" => */ + + /* output to file and check wrong */ + outfile = fopen("output.log", "a+"); + assert_non_null(outfile); + printf("the tok is %s\n", tok); + + pid = fork(); + if (pid == -1) { + perror("fork"); + } else if (pid == 0) { + ssh_execute_command(tok, fileno(outfile), fileno(outfile)); + /* Does not return */ + } else { + /* parent + * wait child process */ + wait(NULL); + infile = fopen("output.log", "r"); + assert_non_null(infile); + p = fgets(buffer, sizeof(buffer), infile); + fclose(infile); + remove("output.log"); + assert_non_null(p); + } + + fclose(outfile); + assert_string_equal(buffer, "hello libssh"); +#endif /* WITH_EXEC */ +} + +/* ssh_config_get_token() should behave as expected + * * Strip leading whitespace + * * Return first token separated by whitespace or equal sign, + * respecting quotes! + * * Correctly treat escaped quotes inside of quotes. + */ +static void torture_config_parser_get_token(void **state) +{ + char *p = NULL, *tok = NULL; + char data[256]; + + (void) state; + + /* Ignore leading whitespace (from get_cmd() already */ + strncpy(data, " \t\t string\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + strncpy(data, " \t\t string", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + /* drops trailing whitespace */ + strncpy(data, "string \t\t \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + strncpy(data, "string \t\t ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + /* Correctly handles tokens in quotes */ + strncpy(data, "\"multi string\" something\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + strncpy(data, "\"multi string\" something", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + /* Consistently splits unquoted strings */ + strncpy(data, "multi string something\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + strncpy(data, "multi string something", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + /* It is made to parse also option=value pairs as well */ + strncpy(data, " key=value \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, " key=value ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* spaces are allowed also around the equal sign */ + strncpy(data, " key = value \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, " key = value ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* correctly parses even key=value pairs with either one in quotes */ + strncpy(data, " key=\"value with spaces\" \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '\"'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value with spaces"); + assert_int_equal(*p, '\0'); + + strncpy(data, " key=\"value with spaces\" ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '\"'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value with spaces"); + assert_int_equal(*p, '\0'); + + /* Only one equal sign is allowed */ + strncpy(data, "key==value\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '='); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, ""); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, "key==value", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '='); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, ""); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* Unmatched quotes */ + strncpy(data, " \"value\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, " \"value", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* Escaped quotes */ + strncpy(data, " \"value with \\\"escaped\\\" quotes\" \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value with \"escaped\" quotes"); + assert_int_equal(*p, '\0'); + + strncpy(data, "\\\"value with \\\"escaped\\\" quotes\\\"\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "\\\"value"); + assert_int_equal(*p, 'w'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "with"); + assert_int_equal(*p, '\\'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "\\\"escaped\\\""); + assert_int_equal(*p, 'q'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "quotes\\\""); + assert_int_equal(*p, '\0'); +} + +/* match_pattern() sanity tests + */ +static void torture_config_match_pattern(void **state) +{ + int rv = 0; + + (void) state; + + /* Simple test "a" matches "a" */ + rv = match_pattern("a", "a"); + assert_int_equal(rv, 1); + + /* Simple test "a" does not match "b" */ + rv = match_pattern("a", "b"); + assert_int_equal(rv, 0); + + /* NULL arguments are correctly handled */ + rv = match_pattern("a", NULL); + assert_int_equal(rv, 0); + rv = match_pattern(NULL, "a"); + assert_int_equal(rv, 0); + + /* Simple wildcard ? is handled in pattern */ + rv = match_pattern("a", "?"); + assert_int_equal(rv, 1); + rv = match_pattern("aa", "?"); + assert_int_equal(rv, 0); + /* Wildcard in search string */ + rv = match_pattern("?", "a"); + assert_int_equal(rv, 0); + rv = match_pattern("?", "?"); + assert_int_equal(rv, 1); + + /* Simple wildcard * is handled in pattern */ + rv = match_pattern("a", "*"); + assert_int_equal(rv, 1); + rv = match_pattern("aa", "*"); + assert_int_equal(rv, 1); + /* Wildcard in search string */ + rv = match_pattern("*", "a"); + assert_int_equal(rv, 0); + rv = match_pattern("*", "*"); + assert_int_equal(rv, 1); + + /* More complicated patterns */ + rv = match_pattern("a", "*a"); + assert_int_equal(rv, 1); + rv = match_pattern("a", "a*"); + assert_int_equal(rv, 1); + rv = match_pattern("abababc", "*abc"); + assert_int_equal(rv, 1); + rv = match_pattern("ababababca", "*abc"); + assert_int_equal(rv, 0); + rv = match_pattern("ababababca", "*abc*"); + assert_int_equal(rv, 1); + + /* Multiple wildcards in row */ + rv = match_pattern("aa", "??"); + assert_int_equal(rv, 1); + rv = match_pattern("bba", "??a"); + assert_int_equal(rv, 1); + rv = match_pattern("aaa", "**a"); + assert_int_equal(rv, 1); + rv = match_pattern("bbb", "**a"); + assert_int_equal(rv, 0); + + /* Consecutive asterisks do not make sense and do not need to recurse */ + rv = match_pattern("hostname", "**********pattern"); + assert_int_equal(rv, 0); + rv = match_pattern("hostname", "pattern**********"); + assert_int_equal(rv, 0); + rv = match_pattern("pattern", "***********pattern"); + assert_int_equal(rv, 1); + rv = match_pattern("pattern", "pattern***********"); + assert_int_equal(rv, 1); + + rv = match_pattern("hostname", "*p*a*t*t*e*r*n*"); + assert_int_equal(rv, 0); + rv = match_pattern("pattern", "*p*a*t*t*e*r*n*"); + assert_int_equal(rv, 1); + + /* Regular Expression Denial of Service */ + rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a"); + assert_int_equal(rv, 1); + rv = match_pattern("ababababababababababababababababababababab", + "*a*b*a*b*a*b*a*b*a*b*a*b*a*b*a*b"); + assert_int_equal(rv, 1); + + /* A lot of backtracking */ + rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax", + "a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*ax"); + assert_int_equal(rv, 1); + + /* Test backtracking: *a matches first 'a', fails on 'b', must backtrack */ + rv = match_pattern("axaxaxb", "*a*b"); + assert_int_equal(rv, 1); + + /* Test greedy consumption with suffix */ + rv = match_pattern("foo_bar_baz_bar", "*bar"); + assert_int_equal(rv, 1); + + /* Test exact suffix requirement (ensure no partial match acceptance) */ + rv = match_pattern("foobar_extra", "*bar"); + assert_int_equal(rv, 0); + + /* Test multiple distinct wildcards */ + rv = match_pattern("a_very_long_string_with_a_pattern", "*long*pattern"); + assert_int_equal(rv, 1); + + /* ? inside a * sequence */ + rv = match_pattern("abcdefg", "a*c?e*g"); + assert_int_equal(rv, 1); + + /* Consecutive mixed wildcards */ + rv = match_pattern("abc", "*?c"); + assert_int_equal(rv, 1); + + /* ? at the very end after * */ + rv = match_pattern("abc", "ab?"); + assert_int_equal(rv, 1); + rv = match_pattern("abc", "ab*?"); + assert_int_equal(rv, 1); + + /* Consecutive stars should be collapsed or handled gracefully */ + rv = match_pattern("abc", "a**c"); + assert_int_equal(rv, 1); + rv = match_pattern("abc", "***"); + assert_int_equal(rv, 1); + + /* Empty string handling */ + rv = match_pattern("", "*"); + assert_int_equal(rv, 1); + rv = match_pattern("", "?"); + assert_int_equal(rv, 0); + rv = match_pattern("", ""); + assert_int_equal(rv, 1); + + /* Pattern longer than string */ + rv = match_pattern("short", "short_but_longer"); + assert_int_equal(rv, 0); +} + +/* Identity file can be specified multiple times in the configuration + */ +static void torture_config_identity(void **state) +{ + const char *id = NULL; + const char *cert = NULL; + struct ssh_iterator *it = NULL; + ssh_session session = *state; + + _parse_config(session, NULL, LIBSSH_TESTCONFIG_STRING13, SSH_OK); + + /* The identities are first added to this temporary list before expanding */ + it = ssh_list_get_iterator(session->opts.identity_non_exp); + assert_non_null(it); + id = it->data; + /* The identities are prepended to the list so we start with second one */ + assert_string_equal(id, "id_ecdsa_two"); + + it = it->next; + assert_non_null(it); + id = it->data; + assert_string_equal(id, "id_rsa_one"); + + /* The certs are first added to this temporary list before expanding */ + it = ssh_list_get_iterator(session->opts.certificate_non_exp); + assert_non_null(it); + cert = it->data; + /* The certs are coming as listed in the configuration file */ + assert_string_equal(cert, "id_rsa_one-cert.pub"); + + it = it->next; + assert_non_null(it); + cert = it->data; + assert_string_equal(cert, "id_ecdsa_two-cert.pub"); + /* and that is all */ + assert_null(it->next); +} + +/* Make absolute path for config include + */ +static void torture_config_make_absolute_int(void **state, bool no_sshdir_fails) +{ + ssh_session session = *state; + char *result = NULL; +#ifndef _WIN32 + char h[256] = {0}; + char *user = NULL; + char *home = NULL; + struct passwd *pw = getpwuid(getuid()); + assert_non_null(pw); + user = strdup(pw->pw_name); + assert_non_null(user); + home = strdup(pw->pw_dir); + assert_non_null(home); +#endif + + /* Absolute path already -- should not change in any case */ + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 0); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + + /* Global is relative to /etc/ssh/ */ + result = ssh_config_make_absolute(session, "ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/test.conf"); + free(result); + result = ssh_config_make_absolute(session, "./ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/./ssh_config.d/test.conf"); + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + result = ssh_config_make_absolute(session, "my_config", 0); + if (no_sshdir_fails) { + assert_null(result); + } else { + /* The path depends on the PWD so lets skip checking the actual path here */ + assert_non_null(result); + } + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, "/tmp/ssh"); + result = ssh_config_make_absolute(session, "my_config", 0); + assert_string_equal(result, "/tmp/ssh/my_config"); + free(result); + +#ifndef _WIN32 + /* Tilde expansion works only in user config */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + /* in global config its just prefixed without expansion */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/~/.ssh/config.d/*.conf"); + free(result); + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 1); + snprintf(h, 256 - 1, "/etc/ssh/~%s/.ssh/config.d/*.conf", user); + assert_string_equal(result, h); + free(result); + free(home); + free(user); +#endif +} + +static void torture_config_make_absolute(void **state) +{ + torture_config_make_absolute_int(state, 0); +} + +static void torture_config_make_absolute_no_sshdir(void **state) +{ + torture_config_make_absolute_int(state, 1); +} + +static void torture_config_parse_uri(void **state) +{ + char *username = NULL; + char *hostname = NULL; + char *port = NULL; + int rc; + + (void)state; /* unused */ + + rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "localhost"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + /* do not want port */ + rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); + + rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true); + assert_int_equal(rc, SSH_ERROR); +} + +/* Complex ssh match configurations + */ +static void torture_config_match_complex(void **state) +{ + ssh_session session = *state; + char *v = NULL; + int ret; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + _parse_config(session, LIBSSH_TESTCONFIG_MATCH_COMPLEX, NULL, SSH_OK); + + /* Test the variable presence */ + ret = ssh_options_get(session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_non_null(v); +#ifndef WITH_EXEC + assert_string_equal(session->opts.host, "Bar"); +#else + assert_string_equal(v, "complex-match"); +#endif + ssh_string_free_char(v); +} + +/* Missing value to LogLevel configuration option + */ +static void torture_config_loglevel_missing_value(void **state) +{ + ssh_session session = *state; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + _parse_config(session, LIBSSH_TESTCONFIG_LOGLEVEL_MISSING, NULL, SSH_OK); +} + +static int before_connection(ssh_session jump_session, void *user) +{ + char *v = NULL; + int ret; + + (void)user; + + /* During the connection, we force parsing the same configuration file + * (would be normally parsed automatically during the connection itself) + */ + ret = ssh_config_parse_file(jump_session, LIBSSH_TESTCONFIG_JUMP); + assert_return_code(ret, errno); + + /* Test the variable presence */ + ret = ssh_options_get(jump_session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "1xxxxxx"); + ssh_string_free_char(v); + + ret = ssh_options_get(jump_session, SSH_OPTIONS_USER, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "ubuntu"); + ssh_string_free_char(v); + + assert_int_equal(jump_session->opts.port, 23); + + /* Fail the connection -- we are in unit tests so it would fail anyway */ + return 1; +} + +static int verify_knownhost(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int authenticate(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} +/* Reproducer for complex proxy jump + */ +static void torture_config_jump(void **state) +{ + ssh_session session = *state; + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate, + }; + char *v = NULL; + int ret; + + ssh_options_set(session, SSH_OPTIONS_HOST, "cisco-router"); + + _parse_config(session, LIBSSH_TESTCONFIG_JUMP, NULL, SSH_OK); + + /* Test the variable presence */ + ret = ssh_options_get(session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "xx.xxxxxxxxx"); + ssh_string_free_char(v); + + ret = ssh_options_get(session, SSH_OPTIONS_USER, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "username"); + ssh_string_free_char(v); + + assert_int_equal(session->opts.port, 5555); + + /* At this point, the configuration file is not parsed for the jump host so + * we are getting just the the hostname -- the port and username will get + * pulled during the session connecting to this host */ + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 1); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "ub-jumphost", + NULL, + NULL); + + /* Set up the callbacks -- they should verify we are going to connect to the + * right host */ + ret = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, ret); + + ret = ssh_connect(session); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + printf("%s: EOF\n", __func__); +} + +/* Invalid configuration files + */ +static void torture_config_invalid(void **state) +{ + ssh_session session = *state; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + /* non-regular file -- ignored (or missing on non-unix) so OK */ + _parse_config(session, "/dev/random", NULL, SSH_OK); + +#ifndef _WIN32 + /* huge file -- ignored (or missing on non-unix) so OK */ + _parse_config(session, "/proc/kcore", NULL, SSH_OK); +#endif +} + +/* Issue #365: a value set via ssh_options_set() before config parsing must + * NOT be overridden by the config file. */ +static void torture_config_user_not_overridden(void **state) +{ + ssh_session session = *state; + char *user = NULL; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "appuser"); + assert_ssh_return_code(session, rc); + + _parse_config(session, NULL, "User configuser\n", SSH_OK); + + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_ssh_return_code(session, rc); + assert_non_null(user); + assert_string_equal(user, "appuser"); + SSH_STRING_FREE_CHAR(user); +} + +/* When the application did NOT set User, the config value still applies. */ +static void torture_config_user_from_config_applies(void **state) +{ + ssh_session session = *state; + char *user = NULL; + int rc; + + _parse_config(session, NULL, "User configuser\n", SSH_OK); + + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_ssh_return_code(session, rc); + assert_non_null(user); + assert_string_equal(user, "configuser"); + SSH_STRING_FREE_CHAR(user); +} + +/* Protection is general, not User-specific: an app-set Port survives config. */ +static void torture_config_port_not_overridden(void **state) +{ + ssh_session session = *state; + unsigned int port = 2020; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &port); + assert_ssh_return_code(session, rc); + + _parse_config(session, NULL, "Port 2222\n", SSH_OK); + assert_int_equal(session->opts.port, 2020); +} + +/* The host match key is NOT protected: config HostName still resolves an + * app-set alias to the real hostname. */ +static void torture_config_hostname_still_resolves(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "myalias"); + assert_ssh_return_code(session, rc); + + _parse_config(session, + NULL, + "Host myalias\n\tHostName real.example.com\n", + SSH_OK); + + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "real.example.com"); +} + +/* HostName keeps its own "first obtained value wins" precedence between config + * entries, independently of the application-set SSH_OPTIONS_HOST lookup key. + * Every target below resolves to the first HostName, matching OpenSSH: + * + * $ ssh -F config -G test | grep ^hostname -> hostname test + * $ ssh -F config -G test2 | grep ^hostname -> hostname test + * $ ssh -F config -G test3 | grep ^hostname -> hostname test + */ +static void torture_config_hostname_first_wins(void **state) +{ + ssh_session session = *state; + const char *config = "HostName test\n" + "HostName test2\n" + "Match host test2\n" + "\tHostName test3\n"; + const char *targets[] = {"test", "test2", "test3"}; + size_t i; + int rc; + + (void)session; + + for (i = 0; i < ARRAY_SIZE(targets); i++) { + ssh_session s = ssh_new(); + assert_non_null(s); + + rc = ssh_options_set(s, SSH_OPTIONS_HOST, targets[i]); + assert_ssh_return_code(s, rc); + + _parse_config(s, NULL, config, SSH_OK); + + assert_non_null(s->opts.host); + assert_string_equal(s->opts.host, "test"); + + ssh_free(s); + } +} + +/* Operational options like log verbosity are NOT protected: config LogLevel + * still applies even if the application set verbosity beforehand. */ +static void torture_config_loglevel_not_overridden(void **state) +{ + ssh_session session = *state; + int level = SSH_LOG_NOLOG; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &level); + assert_ssh_return_code(session, rc); + + _parse_config(session, NULL, "LogLevel DEBUG3\n", SSH_OK); + + assert_int_equal(session->common.log_verbosity, SSH_LOG_TRACE); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_config_include_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_include_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_double_ports_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_double_ports_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_glob_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_glob_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_new_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_new_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_auth_methods_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_auth_methods_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_unknown_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_unknown_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_proxyjump_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_proxyjump_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_control_path_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_control_path_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_control_master_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_control_master_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_address_family_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_address_family_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_rekey_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_rekey_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineend_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineend_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineoneline_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineoneline_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_parser_get_cmd, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_parser_get_token, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_pattern, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir, + setup_no_sshdir, + teardown), + cmocka_unit_test_setup_teardown(torture_config_parse_uri, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_complex, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_loglevel_missing_value, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_jump, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_invalid, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_user_not_overridden, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_user_from_config_applies, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_port_not_overridden, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_hostname_still_resolves, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_hostname_first_wins, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_loglevel_not_overridden, + setup, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_config_files, teardown_config_files); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_config_match_localnetwork.c b/src/libs/libssh-0.12.2/tests/unittests/torture_config_match_localnetwork.c new file mode 100644 index 000000000000..4c20db08ee5a --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_config_match_localnetwork.c @@ -0,0 +1,752 @@ +#include "config.h" +#include "torture.h" +#include "libssh/options.h" +#include "libssh/session.h" +#include "match.c" +#ifdef HAVE_IFADDRS_H +#include +#endif +#include +#include + +/* This list contains common local subnet addresses and more generic ones */ +#define IPV4_LIST \ + "158.46.192.0/18,213.86.215.224/27,61.67.54.0/23,164.155.128.0/21," \ + "171.10.0.0/16,205.59.221.0/24,122.105.209.48/28,10.0.1.0/24," \ + "130.192.28.0/22,172.16.16.0/16,192.168.0.0/24,169.254.0.0/16" + +#define IPV6_LIST "fe80::/64" + +static int +setup(void **state) +{ + ssh_session session = NULL; + char *wd = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + wd = torture_get_current_working_dir(); + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, wd); + free(wd); + + *state = session; + + return 0; +} + +static int +teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +/** + * @brief helper function loading configuration from either file or string + */ +static void +_parse_config(ssh_session session, + const char *file, + const char *string, + int expected) +{ + /* + * Initialisation of ret is not needed, but the compiler is not able to + * understand fail() so it will complain about uninitialised use of ret + * below in assert_ssh_return_code_equal() + */ + int ret = -1; + + /* + * make sure either config file or config string is given, + * not both + */ + assert_int_not_equal(file == NULL, string == NULL); + + if (file != NULL) { + ret = ssh_config_parse_file(session, file); + } else if (string != NULL) { + ret = ssh_config_parse_string(session, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_ssh_return_code_equal(session, ret, expected); +} + +/** + * @brief converts subnet mask to prefix length (IPv4) + */ +static int +subnet_mask_to_prefix_length_4(struct in_addr subnet_mask) +{ + uint32_t mask; + int prefix_length = 0; + + mask = ntohl(subnet_mask.s_addr); + + /* Count the number of consecutive 1 bits */ + while (mask & 0x80000000) { + prefix_length++; + mask <<= 1; + } + return prefix_length; +} + +/** + * @brief converts subnet mask to prefix length (IPv6) + */ +static int +subnet_mask_to_prefix_length_6(struct in6_addr subnet_mask) +{ + uint8_t *mask = NULL, chunk; + int i, j, prefix_length = 0; + + mask = subnet_mask.s6_addr; + + /* Count the number of consecutive 1 bits in each byte chunk */ + for (i = 0; i < 16; i++) { + chunk = mask[i]; + while (chunk) { + for (j = 0; j < 8; j++) { + if (chunk & 0x80) { + prefix_length++; + chunk <<= 1; + } else { + break; + } + } + } + } + return prefix_length; +} + +/** + * @brief helper function returning the IPv4 and IPv6 network ID + * (in CIDR format) corresponding to any of the running local interfaces. + * The network interface corresponding to IPv4 and IPv6 network ID may be + * different. + * + * @note If no non-loopback network interfaces are found for IPv4 or + * IPv6, the function will fall back to using the loopback addresses. + */ +static int +get_network_id(char *net_id_4, char *net_id_6) +{ + struct ifaddrs *ifa = NULL, *ifaddrs = NULL; + struct in_addr addr, network_id_4, subnet_mask_4; + struct in6_addr addr6, network_id_6, subnet_mask_6; + struct sockaddr_in netmask; + struct sockaddr_in6 netmask6; + char address[NI_MAXHOST], *a = NULL; + char *network_id_str = NULL, network_id_str6[INET6_ADDRSTRLEN], + lo_net_id_4[NI_MAXHOST], lo_net_id_6[NI_MAXHOST]; + int i, prefix_length, rc; + int found_4 = 0, found_lo_4 = 0, found_6 = 0, found_lo_6 = 0; + socklen_t sa_len; + + ZERO_STRUCT(addr); + ZERO_STRUCT(network_id_4); + ZERO_STRUCT(subnet_mask_4); + + ZERO_STRUCT(addr6); + ZERO_STRUCT(network_id_6); + ZERO_STRUCT(subnet_mask_6); + + if (getifaddrs(&ifaddrs) != 0) { + goto out; + } + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (found_4 && found_6) { + break; + } + + if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) { + continue; + } + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + if (found_4) { + continue; + } + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + if (found_6) { + continue; + } + sa_len = sizeof(struct sockaddr_in6); + break; + default: + continue; + } + + rc = getnameinfo(ifa->ifa_addr, + sa_len, + address, + sizeof(address), + NULL, + 0, + NI_NUMERICHOST); + if (rc != 0) { + continue; + } + + if (ifa->ifa_addr->sa_family == AF_INET) { + + /* Extract subnet mask */ + memcpy(&netmask, ifa->ifa_netmask, sizeof(struct sockaddr_in)); + subnet_mask_4 = netmask.sin_addr; + + rc = inet_pton(AF_INET, address, &addr); + if (rc == 0) { + continue; + } + + /* Calculate the network ID */ + network_id_4.s_addr = addr.s_addr & subnet_mask_4.s_addr; + + /* Convert network ID to string and compute prefix length */ + network_id_str = inet_ntoa(network_id_4); + if (network_id_str == NULL) { + continue; + } + prefix_length = subnet_mask_to_prefix_length_4(subnet_mask_4); + if (prefix_length > 32) { + continue; + } + + if (strcmp(ifa->ifa_name, "lo") == 0) { + /* Store it temporarily in case needed for fallback */ + snprintf(lo_net_id_4, + NI_MAXHOST, + "%s/%u", + network_id_str, + prefix_length); + found_lo_4 = 1; + } else { + snprintf(net_id_4, + NI_MAXHOST, + "%s/%u", + network_id_str, + prefix_length); + found_4 = 1; + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + + /* Remove interface in case of IPv6 address: addr%interface */ + a = strchr(address, '%'); + if (a != NULL) { + *a = '\0'; + } + + /* Extract subnet mask */ + memcpy(&netmask6, ifa->ifa_netmask, sizeof(struct sockaddr_in6)); + subnet_mask_6 = netmask6.sin6_addr; + + rc = inet_pton(AF_INET6, address, &addr6); + if (rc == 0) { + continue; + } + + /* Calculate the network ID */ + for (i = 0; i < 16; i++) { + network_id_6.s6_addr[i] = + addr6.s6_addr[i] & subnet_mask_6.s6_addr[i]; + } + + /* Convert network ID to string and compute prefix length */ + if (inet_ntop(AF_INET6, + &network_id_6, + network_id_str6, + INET6_ADDRSTRLEN) == NULL) { + continue; + } + prefix_length = subnet_mask_to_prefix_length_6(subnet_mask_6); + if (prefix_length > 128) { + continue; + } + + if (strcmp(ifa->ifa_name, "lo") == 0) { + /* Store it temporarily in case needed for fallback */ + snprintf(lo_net_id_6, + NI_MAXHOST, + "%s/%u", + network_id_str6, + prefix_length); + found_lo_6 = 1; + } else { + snprintf(net_id_6, + NI_MAXHOST, + "%s/%u", + network_id_str6, + prefix_length); + found_6 = 1; + } + } + } + + /* + * Fallback to the loopback network ID (127.0.0.0/8) if no other + * IPv4 network ID has been found. + */ + if (!found_4 && found_lo_4) { + snprintf(net_id_4, NI_MAXHOST, "%s", lo_net_id_4); + found_4 = 1; + } + + /* + * Fallback to the loopback network ID (::1/128) if no other + * IPv6 network ID has been found. + */ + if (!found_6 && found_lo_6) { + snprintf(net_id_6, NI_MAXHOST, "%s", lo_net_id_6); + found_6 = 1; + } + + freeifaddrs(ifaddrs); + +out: + /* if both net_id_4 and net_id_6 are not set then we should fail */ + return (found_4 && found_6) ? 0 : -1; +} + +/** + * @brief Verify the match between a IPv4/IPv6 address and a IPv4/IPv6 subnet + */ +static void +assert_true_match_cidr(const char *try, + const char *match, + unsigned int mask_len, + int af, + int rv) +{ + struct in_addr try_addr, match_addr; + struct in6_addr try_addr6, match_addr6; + int r1, r2; + + switch (af) { + case AF_INET: + ZERO_STRUCT(try_addr); + ZERO_STRUCT(match_addr); + + r1 = inet_pton(AF_INET, try, &try_addr); + r2 = inet_pton(AF_INET, match, &match_addr); + if (r1 == 0 || r2 == 0) { + fail(); + } + assert_int_equal(cidr_match_4(&try_addr, &match_addr, mask_len), rv); + break; + case AF_INET6: + ZERO_STRUCT(try_addr6); + ZERO_STRUCT(match_addr6); + + r1 = inet_pton(AF_INET6, try, &try_addr6); + r2 = inet_pton(AF_INET6, match, &match_addr6); + if (r1 == 0 || r2 == 0) { + fail(); + } + assert_int_equal(cidr_match_6(&try_addr6, &match_addr6, mask_len), rv); + break; + default: + fail(); + } +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + */ +static void +torture_config_match_localnetwork(void **state, bool use_file) +{ + ssh_session session = *state; + const char *config = NULL; + char config_string[2048]; + char network_id_4[NI_MAXHOST], network_id_6[NI_MAXHOST]; + const char *file = NULL, *string = NULL; + + if (use_file == true) { + file = "libssh_testconfig_localnetwork.tmp"; + } + + if (get_network_id(network_id_4, network_id_6) == -1) { + fail(); + } + + /* IPv4 test */ + snprintf(config_string, + sizeof(config_string), + "Match localnetwork %s\n" + "\tHostName expected.com\n", + network_id_4); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "expected.com"); + + /* IPv6 test */ + snprintf(config_string, + sizeof(config_string), + "Match localnetwork %s\n" + "\tHostName expected.com\n", + network_id_6); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "expected.com"); + + /* Test negate condition */ + snprintf(config_string, + sizeof(config_string), + "Match Host station !localnetwork %s\n" + "\tHostName expected.com\n" + "Host station\n" + "\tHostName negate.com\n", + network_id_4); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "station"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "negate.com"); +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + * through configuration file. + */ +static void +torture_config_match_localnetwork_file(void **state) +{ + torture_config_match_localnetwork(state, true); +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + * through configuration string. + */ +static void +torture_config_match_localnetwork_string(void **state) +{ + torture_config_match_localnetwork(state, false); +} + +/** + * @brief Verify the cidr matching function works correctly + * with IPv4 addresses + */ +static void +torture_match_cidr_address_list_ipv4(void **state) +{ + int rc; + (void)state; + + /* Test some valid IPv4 addresses */ + rc = match_cidr_address_list("192.158.50.5", "192.158.50.0/28", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.2.200.200", "10.2.128.0/17", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("192.168.175.40", "192.168.175.0/26", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("172.31.140.100", "172.31.128.0/19", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.3.9.50", "10.3.8.0/23", AF_INET); + assert_int_equal(rc, 1); + + /* Test positive match with unknown host address family */ + rc = match_cidr_address_list("158.15.96.13", "158.12.30.0/12", -1); + assert_int_equal(rc, 1); + + /* Test some valid IPv4 addresses against IPV4_LIST */ + rc = match_cidr_address_list("164.155.128.15", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("158.46.223.71", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("205.59.221.160", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.0.1.254", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("172.16.58.1", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("169.254.20.28", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + + rc = match_cidr_address_list("255.255.255.255", "0.0.0.0/0", AF_INET); + assert_int_equal(rc, 1); + + /* Test some not matching IPv4 addresses */ + rc = match_cidr_address_list("172.21.0.200", "172.20.240.0/20", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("10.10.14.100", "10.10.10.0/22", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("192.168.150.8", "192.168.150.0/29", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("10.238.16.50", "10.255.0.0/12", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("172.31.160.100", "172.31.128.0/19", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("192.168.4.98", IPV4_LIST, AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("0.0.0.0", IPV4_LIST, AF_INET); + assert_int_equal(rc, 0); + + /* Test negative match with unknown host address family */ + rc = match_cidr_address_list("122.105.210.57", IPV4_LIST, -1); + assert_int_equal(rc, 0); + + /* Test some invalid input */ + rc = match_cidr_address_list("192.168.1.x", "192.168.1.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("0.168.f2.b8", "172.0.0.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("10.0.1.2/22", "10.0.1.0/22", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("10.0.1.2/", "10.0.1.0/22", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("172.16.16.5/abc1", "172.16.16.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("172.16.18.251", "172.16.16.0", AF_INET); + assert_int_equal(rc, -1); + + /* Test invalid input with unknown host address family */ + rc = match_cidr_address_list("172.67.3.x", IPV4_LIST, -1); + assert_int_equal(rc, -1); + + /* Test invalid CIDR list */ + rc = match_cidr_address_list(NULL, "192.168.1.0/33", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, "", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, ",", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, ",192.168.1.0/24", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, "10.0.0.0/24 , 192.168.1.0/24", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list( + NULL, + "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255/128junkdata", + -1); + assert_int_equal(rc, -1); +} + +/** + * @brief Verify the cidr matching function works correctly + * with IPv6 addresses + */ +static void +torture_match_cidr_address_list_ipv6(void **state) +{ + /* Test link-local addresses against fe80::/64 */ + int i, rc, valid_addr_len, invalid_addr_len; + const char *valid_addr[] = {"fe80::aadf:b119:507a:986a%abcdef", + "fe80::0000:b418:efd4:5160:0a25%abcdef", + "fe80::c7f5:7f94:4bd9:c35c%abcdef", + "fe80::321f:46c2:0cea:ec54%abcdef", + "fe80::906d:b670:86a2:fd68%abc", + "fe80::b1c2:0000:0039:b598%", + "fe80::07e8:39e6:cb49:9cd4", + "fe80::1%abcdef", + "fe80:0:0:0:202:b3ff:fe1e:8329%abcdef", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329"}; + + const char *invalid_addr[] = {"fe80::8d1d:4d88:68a8:44f8:f3e7%abcdef", + "2001:0db8:85a3::8a2e:0370:7334%abcdef", + "fd00::adf8:7c21:147c:6c97", + "::1%lo", + "fe80::1:4d88:68a8:1200:f3e7%abcdef"}; + + (void)state; + + /* Test valid link-local addresses */ + valid_addr_len = sizeof(valid_addr) / sizeof(valid_addr[0]); + for (i = 0; i < valid_addr_len; i++) { + rc = match_cidr_address_list(valid_addr[i], IPV6_LIST, AF_INET6); + assert_int_equal(rc, 1); + } + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8328/127", + AF_INET6); + assert_int_equal(rc, 1); + + /* Test positive match with unknown host address family */ + rc = match_cidr_address_list("fe80::aadf:b119:507a:986a%abcdef", + IPV6_LIST, + -1); + assert_int_equal(rc, 1); + + /* Test some invalid input */ + invalid_addr_len = sizeof(invalid_addr) / sizeof(invalid_addr[0]); + for (i = 0; i < invalid_addr_len; i++) { + rc = match_cidr_address_list(invalid_addr[i], IPV6_LIST, AF_INET6); + assert_int_equal(rc, 0); + } + + /* Test negative match with unknown host address family */ + rc = match_cidr_address_list("fe80::8d1d:4d88:68a8:44f8:f3e7%abcdef", + IPV6_LIST, + -1); + assert_int_equal(rc, 0); + + /* Test errors */ + rc = match_cidr_address_list("fe80::be50:09ca::2be3", IPV6_LIST, AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:x:202:b3ff:fe1e:8329", + IPV6_LIST, + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80::202:ghfc:zzzz:1a49", + IPV6_LIST, + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329/131", + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329//127", + AF_INET6); + assert_int_equal(rc, -1); + + /* Test invalid input with unknown host address family */ + rc = match_cidr_address_list("fe80::ba67:1002:gffx:zz32", IPV6_LIST, -1); + assert_int_equal(rc, -1); +} + +/** + * @brief Verify the cidr_match_4 function works correctly + */ +static void +torture_match_cidr_v4(void **state) +{ + int af = AF_INET; + (void)state; + + /* Test some matching input */ + assert_true_match_cidr("192.168.1.20", "192.168.1.0", 24, af, 1); + assert_true_match_cidr("172.31.5.128", "172.31.0.0", 16, af, 1); + assert_true_match_cidr("10.0.0.158", "10.0.0.128", 25, af, 1); + assert_true_match_cidr("192.168.255.250", "192.168.255.248", 29, af, 1); + assert_true_match_cidr("122.105.209.57", "122.105.209.48", 28, af, 1); + assert_true_match_cidr("192.168.100.150", "192.168.64.0", 18, af, 1); + + /* Test some not matching input */ + assert_true_match_cidr("172.16.56.30", "172.16.48.0", 21, af, 0); + assert_true_match_cidr("10.18.5.5", "10.10.4.0", 23, af, 0); + assert_true_match_cidr("172.16.32.50", "172.16.0.0", 19, af, 0); + assert_true_match_cidr("203.0.120.10", "203.0.112.0", 21, af, 0); + assert_true_match_cidr("172.31.112.150", "172.31.96.0", 20, af, 0); + assert_true_match_cidr("198.52.20.200", "198.48.0.0", 14, af, 0); +} + +/** + * @brief Verify the cidr_match_6 function works correctly + */ +static void +torture_match_cidr_v6(void **state) +{ + int af = AF_INET6; + (void)state; + + /* Test some matching input */ + assert_true_match_cidr("2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "2001:0db8:85a3:0000::", + 64, + af, + 1); + assert_true_match_cidr("2001:0db8:0000:0042:0000:8a2e:0370:7334", + "2001:0db8:0000::", + 48, + af, + 1); + assert_true_match_cidr("fe80::8a2e:0370:7334", "fe80::", 64, af, 1); + assert_true_match_cidr("fd00::8a2e:0370:7334", "fd00::", 56, af, 1); + assert_true_match_cidr("fe80:0000:0000:0000:0000:0000:fe1e:32ff", + "fe80::", + 96, + af, + 1); + assert_true_match_cidr("2001:0db8:1a2b:3c4d:5e6f:7a8b::18", + "2001:0db8:1a2b:3c4d:5e6f:7a8b::", + 120, + af, + 1); + + /* Test some not matching input */ + assert_true_match_cidr("2001:0db8:1234:5678:9abc:def0:1234:5678", + "2001:0db8:1234:5678::", + 96, + af, + 0); + assert_true_match_cidr("2001:3858:accd::", + "2001:3858:abcd:eaa1::", + 48, + af, + 0); + assert_true_match_cidr("2001:0db8:1234:5678::ff4c", + "2001:0db8:1234:5600::", + 110, + af, + 0); + assert_true_match_cidr("fe80::0001:af12:a1b2:c3d4:e5f7", + "fe80::", + 64, + af, + 0); + assert_true_match_cidr("2001:0db8:84ff:ffff:ffff:ffff:ffff:fffa", + "2001:0db8:8500::", + 80, + af, + 0); + assert_true_match_cidr("::3", "::", 127, af, 0); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_config_match_localnetwork_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_localnetwork_file, + setup, + teardown), + cmocka_unit_test(torture_match_cidr_address_list_ipv4), + cmocka_unit_test(torture_match_cidr_address_list_ipv6), + cmocka_unit_test(torture_match_cidr_v4), + cmocka_unit_test(torture_match_cidr_v6), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, setup, teardown); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_crypto.c b/src/libs/libssh-0.12.2/tests/unittests/torture_crypto.c new file mode 100644 index 000000000000..3f84e19c24a0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_crypto.c @@ -0,0 +1,337 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/crypto.h" +#include "libssh/chacha20-poly1305-common.h" + +uint8_t key[32] = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" + "\x1e\x1f"; + +uint8_t IV[16] = + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e" + "\x1f"; + +uint8_t cleartext[144] = + "\xb4\xfc\x5d\xc2\x49\x8d\x2c\x29\x4a\xc9\x9a\xb0\x1b\xf8\x29" + "\xee\x85\x6d\x8c\x04\x34\x7c\x65\xf4\x89\x97\xc5\x71\x70\x41" + "\x91\x40\x19\x60\xe1\xf1\x8f\x4d\x8c\x17\x51\xd6\xbc\x69\x6e" + "\xf2\x21\x87\x18\x6c\xef\xc4\xf4\xd9\xe6\x1b\x94\xf7\xd8\xb2" + "\xe9\x24\xb9\xe7\xe6\x19\xf5\xec\x55\x80\x9a\xc8\x7d\x70\xa3" + "\x50\xf8\x03\x10\x35\x49\x9b\x53\x58\xd7\x4c\xfc\x5f\x02\xd6" + "\x28\xea\xcc\x43\xee\x5e\x2b\x8a\x7a\x66\xf7\x00\xee\x09\x18" + "\x30\x1b\x47\xa2\x16\x69\xc4\x6e\x44\x3f\xbd\xec\x52\xce\xe5" + "\x41\xf2\xe0\x04\x4f\x5a\x55\x58\x37\xba\x45\x8d\x15\x53\xf6" + "\x31\x91\x13\x8c\x51\xed\x08\x07\xdb"; + +uint8_t aes256_cbc_encrypted[144] = + "\x7f\x1b\x92\xac\xc5\x16\x05\x55\x74\xac\xb4\xe0\x91\x8c\xf8" + "\x0d\xa9\x72\xa5\x09\xb8\x44\xee\x55\x02\x13\xb7\x52\x0a\xf0" + "\xac\xd0\x21\x0e\x58\x7b\x34\xfe\xdb\x36\x01\x60\x7d\x18\x3a" + "\xa9\x15\x18\x5b\x13\xca\xdd\x77\x7d\xdf\x64\xc6\xd5\x75\x4b" + "\x02\x02\x37\xb1\xf4\x33\xff\x93\xe6\x32\x08\xda\xcb\x5d\xa2" + "\x8f\x17\x1f\x99\x92\x60\x22\x9d\x6b\xe6\xb2\x5e\xb0\x5d\x26" + "\x3f\xde\xb8\xc1\xb0\x70\x80\x1c\x00\xd0\x93\x2b\xeb\x0f\xd7" + "\x70\x7a\x9a\x7a\xa6\x21\x23\x2c\x02\xb7\xcd\x88\x10\x9c\x2d" + "\x0c\xd3\xfa\xc1\x33\x5b\xe1\xa1\xd4\x3d\x8f\xb8\x50\xc5\xb5" + "\x72\xdd\x6d\x32\x1f\x58\x00\x48\xbe"; + +static int get_cipher(struct ssh_cipher_struct *cipher, const char *ciphername) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + size_t i; + int cmp; + + assert_non_null(cipher); + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0){ + memcpy(cipher, &ciphers[i], sizeof(*cipher)); + return SSH_OK; + } + } + + return SSH_ERROR; +} + +static void torture_crypto_aes256_cbc(void **state) +{ + uint8_t output[sizeof(cleartext)] = {0}; + uint8_t iv[16] = {0}; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.encrypt); + + memcpy(iv, IV, sizeof(IV)); + cipher.set_encrypt_key(&cipher, + key, + iv + ); + + cipher.encrypt(&cipher, + cleartext, + output, + sizeof(cleartext) + ); + + assert_memory_equal(output, aes256_cbc_encrypted, sizeof(aes256_cbc_encrypted)); + ssh_cipher_clear(&cipher); + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + + assert_non_null(cipher.set_decrypt_key); + assert_non_null(cipher.decrypt); + + memcpy(iv, IV, sizeof(IV)); + cipher.set_decrypt_key(&cipher, + key, + iv + ); + + memset(output, '\0', sizeof(output)); + cipher.decrypt(&cipher, + aes256_cbc_encrypted, + output, + sizeof(aes256_cbc_encrypted) + ); + + assert_memory_equal(output, cleartext, sizeof(cleartext)); + + ssh_cipher_clear(&cipher); +} + +uint8_t chacha20poly1305_key[CHACHA20_KEYLEN*2] = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" + "\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c" + "\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b" + "\x3c\x3d\x3e\x3f"; + +#define CLEARTEXT_LENGTH 144 +uint8_t chacha20poly1305_cleartext[CLEARTEXT_LENGTH] = + "\xb4\xfc\x5d\xc2\x49\x8d\x2c\x29\x4a\xc9\x9a\xb0\x1b\xf8\x29" + "\xee\x85\x6d\x8c\x04\x34\x7c\x65\xf4\x89\x97\xc5\x71\x70\x41" + "\x91\x40\x19\x60\xe1\xf1\x8f\x4d\x8c\x17\x51\xd6\xbc\x69\x6e" + "\xf2\x21\x87\x18\x6c\xef\xc4\xf4\xd9\xe6\x1b\x94\xf7\xd8\xb2" + "\xe9\x24\xb9\xe7\xe6\x19\xf5\xec\x55\x80\x9a\xc8\x7d\x70\xa3" + "\x50\xf8\x03\x10\x35\x49\x9b\x53\x58\xd7\x4c\xfc\x5f\x02\xd6" + "\x28\xea\xcc\x43\xee\x5e\x2b\x8a\x7a\x66\xf7\x00\xee\x09\x18" + "\x30\x1b\x47\xa2\x16\x69\xc4\x6e\x44\x3f\xbd\xec\x52\xce\xe5" + "\x41\xf2\xe0\x04\x4f\x5a\x55\x58\x37\xba\x45\x8d\x15\x53\xf6" + "\x31\x91\x13\x8c\x51\xed\x08\x07\xdb"; + +uint64_t chacha20poly1305_seq = (uint64_t)1234567890 * 98765431; + +uint8_t chacha20poly1305_encrypted[sizeof(uint32_t) + CLEARTEXT_LENGTH + POLY1305_TAGLEN] = + "\xac\x2e\x4c\x54\xf6\x97\x75\xb4\x3b\x8f\xb0\x8e\xb0\x0a\x8e" + "\xb3\x90\x21\x0d\x7a\xb6\xd3\x03\xf6\xbc\x6e\x3a\x32\x67\xe1" + "\x13\x65\x43\x3b\x34\x9d\xcb\x62\x7e\x0a\x80\xb0\x45\x87\x07" + "\x85\x49\x8d\x23\x5f\xac\x9c\x8b\xa8\xd5\x01\x12\xfe\x52\xc6" + "\x99\xb4\xf2\xde\x12\x78\x79\xea\x1c\x5f\x45\xcd\xf7\xe4\xa0" + "\x66\x15\x7f\xe3\xf4\x73\x3b\xe0\x52\xac\x2a\x00\x73\xd0\xd7" + "\x95\xa9\xb9\x3a\xe0\x50\x13\xf4\xdc\xfc\x2a\x64\xb5\xcf\x29" + "\x88\xef\x4c\x56\x10\x30\x28\xbb\x59\xb8\x23\x58\xab\x01\xa2" + "\xab\x6b\xdd\xee\x20\x43\xe1\xec\x7a\xe1\xaa\x8b\x60\x19\xde" + "\x3a\xd1\xd6\x80\x49\x7d\x5c\x81\xb8\x96\xad\x62\x32\xe5\x25" + "\x72\xe9\x63\x96\xa1\x44\x25\x91\xe1\xdc\x01\xc7\x5c\xa9"; + +static void torture_crypto_chacha20poly1305(void **state) +{ + uint8_t input[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)]; + uint8_t output[sizeof(input) + POLY1305_TAGLEN] = {0}; + uint8_t *outtag = output + sizeof(input); + struct ssh_cipher_struct cipher = {0}; + uint32_t in_length; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + assert_int_equal(sizeof(output), sizeof(chacha20poly1305_encrypted)); + + in_length = htonl(sizeof(chacha20poly1305_cleartext)); + memcpy(input, &in_length, sizeof(uint32_t)); + memcpy(input + sizeof(uint32_t), chacha20poly1305_cleartext, + sizeof(chacha20poly1305_cleartext)); + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(sizeof(chacha20poly1305_key) * 8, cipher.keysize); + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.aead_encrypt); + + rc = cipher.set_encrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + cipher.aead_encrypt(&cipher, input, output, sizeof(input), outtag, + chacha20poly1305_seq); + assert_memory_equal(output, chacha20poly1305_encrypted, + sizeof(chacha20poly1305_encrypted)); + ssh_cipher_clear(&cipher); + + memset(output, '\0', sizeof(output)); + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + assert_non_null(cipher.set_decrypt_key); + assert_non_null(cipher.aead_decrypt); + assert_non_null(cipher.aead_decrypt_length); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, chacha20poly1305_encrypted, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, chacha20poly1305_encrypted, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + assert_memory_equal(output, input, sizeof(input)); + + ssh_cipher_clear(&cipher); +} + +static void torture_crypto_chacha20poly1305_bad_packet_length(void **state) +{ + uint8_t output[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)] = {0}; + uint8_t encrypted_bad[sizeof(chacha20poly1305_encrypted)]; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + /* Test corrupted packet length */ + memcpy(encrypted_bad, chacha20poly1305_encrypted, sizeof(encrypted_bad)); + encrypted_bad[1] ^= 1; + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, encrypted_bad, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, encrypted_bad, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_ERROR); + + ssh_cipher_clear(&cipher); +} + +static void torture_crypto_chacha20poly1305_bad_data(void **state) +{ + uint8_t output[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)] = {0}; + uint8_t encrypted_bad[sizeof(chacha20poly1305_encrypted)]; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + /* Test corrupted data */ + memcpy(encrypted_bad, chacha20poly1305_encrypted, sizeof(encrypted_bad)); + encrypted_bad[100] ^= 1; + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, encrypted_bad, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, encrypted_bad, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_ERROR); + + ssh_cipher_clear(&cipher); +} + +static void torture_crypto_chacha20poly1305_bad_tag(void **state) +{ + uint8_t output[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)] = {0}; + uint8_t encrypted_bad[sizeof(chacha20poly1305_encrypted)]; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + /* Test corrupted tag */ + assert_int_equal(sizeof(encrypted_bad), sizeof(chacha20poly1305_encrypted)); + memcpy(encrypted_bad, chacha20poly1305_encrypted, sizeof(encrypted_bad)); + encrypted_bad[sizeof(encrypted_bad) - 1] ^= 1; + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, encrypted_bad, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, encrypted_bad, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_ERROR); + + ssh_cipher_clear(&cipher); +} + +int torture_run_tests(void) { + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_crypto_aes256_cbc), + cmocka_unit_test(torture_crypto_chacha20poly1305), + cmocka_unit_test(torture_crypto_chacha20poly1305_bad_packet_length), + cmocka_unit_test(torture_crypto_chacha20poly1305_bad_data), + cmocka_unit_test(torture_crypto_chacha20poly1305_bad_tag), + }; + + ssh_init(); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_forwarded_tcpip_callback.c b/src/libs/libssh-0.12.2/tests/unittests/torture_forwarded_tcpip_callback.c new file mode 100644 index 000000000000..cf80c87b85f4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_forwarded_tcpip_callback.c @@ -0,0 +1,336 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" + +#include + +#define TEST_SERVER_HOST "127.0.0.1" +#define TEST_SERVER_PORT 2222 +#define TEST_DEST_HOST "127.0.0.1" +#define TEST_DEST_PORT 12345 +#define TEST_ORIG_HOST "127.0.0.1" +#define TEST_ORIG_PORT 54321 + +struct hostkey_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +struct server_thread_args { + struct hostkey_state *h; + bool should_accept; +}; + +static bool is_server_ready = false; +static pthread_mutex_t server_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t server_cond = PTHREAD_COND_INITIALIZER; + +static bool client_callbacks_initialised = false; +static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t client_cond = PTHREAD_COND_INITIALIZER; + +static int setup(void **state) +{ + struct hostkey_state *h = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + h = (struct hostkey_state *)malloc(sizeof(struct hostkey_state)); + assert_non_null(h); + + h->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + assert_non_null(h->hostkey_path); + + mask = umask(S_IRWXO | S_IRWXG); + h->fd = mkstemp(h->hostkey_path); + umask(mask); + assert_return_code(h->fd, errno); + close(h->fd); + + h->key_type = SSH_KEYTYPE_ECDSA_P256; + h->hostkey = torture_get_testkey(h->key_type, 0); + + torture_write_file(h->hostkey_path, h->hostkey); + + *state = h; + + /* Reset before every test */ + is_server_ready = false; + client_callbacks_initialised = false; + + return 0; +} + +static int teardown(void **state) +{ + struct hostkey_state *h = (struct hostkey_state *)*state; + + unlink(h->hostkey_path); + free(h->hostkey_path); + free(h); + + ssh_finalize(); + + return 0; +} + +static int auth_password_accept(ssh_session session, + const char *user, + const char *password, + void *userdata) +{ + /* unused */ + (void)session; + (void)user; + (void)password; + (void)userdata; + + return SSH_AUTH_SUCCESS; +} + +static void *server_thread(void *arg) +{ + struct server_thread_args *args = (struct server_thread_args *)arg; + struct hostkey_state *h = args->h; + bool should_accept = args->should_accept; + ssh_bind sshbind = NULL; + ssh_session server = NULL; + ssh_channel channel = NULL; + ssh_event event = NULL; + int rc; + + struct ssh_server_callbacks_struct server_cb = { + .auth_password_function = auth_password_accept, + }; + ssh_callbacks_init(&server_cb); + + /* Create server */ + sshbind = torture_ssh_bind(TEST_SERVER_HOST, + TEST_SERVER_PORT, + h->key_type, + h->hostkey_path); + assert_non_null(sshbind); + + server = ssh_new(); + assert_non_null(server); + + rc = ssh_set_server_callbacks(server, &server_cb); + assert_int_equal(rc, SSH_OK); + + /* Signal that the server is ready */ + pthread_mutex_lock(&server_mutex); + is_server_ready = true; + pthread_cond_signal(&server_cond); + pthread_mutex_unlock(&server_mutex); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + rc = ssh_handle_key_exchange(server); + assert_int_equal(rc, SSH_OK); + + /* Handle client connection */ + event = ssh_event_new(); + assert_non_null(event); + + rc = ssh_event_add_session(event, server); + assert_int_equal(rc, SSH_OK); + + /* Poll until authentication is complete */ + while (server->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = ssh_event_dopoll(event, -1); + if (rc == SSH_ERROR) { + break; + } + } + + /* Cleanup the event */ + ssh_event_free(event); + + /* Wait for client callbacks to be initialized before proceeding */ + pthread_mutex_lock(&client_mutex); + while (!client_callbacks_initialised) { + pthread_cond_wait(&client_cond, &client_mutex); + } + pthread_mutex_unlock(&client_mutex); + + channel = ssh_channel_new(server); + assert_non_null(channel); + + rc = ssh_channel_open_reverse_forward(channel, + TEST_DEST_HOST, + TEST_DEST_PORT, + TEST_ORIG_HOST, + TEST_ORIG_PORT); + if (should_accept) { + assert_int_equal(rc, SSH_OK); + } else { + assert_int_equal(rc, SSH_ERROR); + } + + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_bind_free(sshbind); + ssh_free(server); + + return NULL; +} + +struct channel_data { + /* Whether the callback should accept the channel open request */ + bool should_accept; + + int req_seen; + char *dest_host; + uint32_t dest_port; + char *orig_host; + uint32_t orig_port; +}; + +static ssh_channel channel_forwarded_tcpip_callback(ssh_session session, + const char *dest_host, + int dest_port, + const char *orig_host, + int orig_port, + void *userdata) +{ + struct channel_data *channel_data = (struct channel_data *)userdata; + ssh_channel channel = NULL; + + /* Record that we've seen a forwarded-tcpip request and store the parameters + */ + channel_data->req_seen = 1; + channel_data->dest_host = strdup(dest_host); + channel_data->dest_port = dest_port; + channel_data->orig_host = strdup(orig_host); + channel_data->orig_port = orig_port; + + /* Create and return a new channel for this request */ + if (channel_data->should_accept) { + channel = ssh_channel_new(session); + } + + return channel; +} + +static void torture_forwarded_tcpip_callback(void **state, bool should_accept) +{ + int rc, event_rc; + pthread_t server_pthread; + ssh_session session = NULL; + ssh_event event = NULL; + struct channel_data channel_data; + unsigned int server_port = TEST_SERVER_PORT; + + struct server_thread_args args = { + .h = (struct hostkey_state *)*state, + .should_accept = should_accept, + }; + + struct ssh_callbacks_struct client_cb = { + .userdata = &channel_data, + .channel_open_request_forwarded_tcpip_function = + channel_forwarded_tcpip_callback, + }; + ssh_callbacks_init(&client_cb); + + memset(&channel_data, 0, sizeof(channel_data)); + channel_data.should_accept = should_accept; + + rc = pthread_create(&server_pthread, NULL, server_thread, &args); + assert_return_code(rc, errno); + + /* Wait for the server to be ready using condition variable */ + pthread_mutex_lock(&server_mutex); + while (!is_server_ready) { + pthread_cond_wait(&server_cond, &server_mutex); + } + pthread_mutex_unlock(&server_mutex); + + session = + torture_ssh_session(NULL, "127.0.0.1", &server_port, "foo", "bar"); + assert_non_null(session); + + rc = ssh_set_callbacks(session, &client_cb); + assert_int_equal(rc, SSH_OK); + + event = ssh_event_new(); + assert_non_null(event); + + rc = ssh_event_add_session(event, session); + assert_int_equal(rc, SSH_OK); + + /* Signal that client callbacks are initialized */ + pthread_mutex_lock(&client_mutex); + client_callbacks_initialised = true; + pthread_cond_signal(&client_cond); + pthread_mutex_unlock(&client_mutex); + + event_rc = SSH_OK; + while (channel_data.req_seen != 1 && event_rc == SSH_OK) { + event_rc = ssh_event_dopoll(event, -1); + } + + /* Cleanup */ + ssh_event_free(event); + ssh_free(session); + + rc = pthread_join(server_pthread, NULL); + assert_int_equal(rc, 0); + + /* Verify forwarded-tcpip request parameters */ + assert_true(channel_data.req_seen); + assert_string_equal(channel_data.dest_host, TEST_DEST_HOST); + assert_int_equal(channel_data.dest_port, TEST_DEST_PORT); + assert_string_equal(channel_data.orig_host, TEST_ORIG_HOST); + assert_int_equal(channel_data.orig_port, TEST_ORIG_PORT); + + /* Free allocated memory */ + free(channel_data.dest_host); + free(channel_data.orig_host); +} + +static void torture_forwarded_tcpip_callback_success(void **state) +{ + torture_forwarded_tcpip_callback(state, true); +} + +static void torture_forwarded_tcpip_callback_failure(void **state) +{ + torture_forwarded_tcpip_callback(state, false); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_forwarded_tcpip_callback_success, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_forwarded_tcpip_callback_failure, + setup, + teardown), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_hashes.c b/src/libs/libssh-0.12.2/tests/unittests/torture_hashes.c new file mode 100644 index 000000000000..a243471a4184 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_hashes.c @@ -0,0 +1,161 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" +#include "legacy.c" +#include "dh.c" + +static int setup_rsa_key(void **state) +{ + int rc=0; + enum ssh_keytypes_e type; + char *b64_key, *p; + ssh_key key; + + const char *q; + + b64_key = strdup(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_non_null(b64_key); + + q = p = b64_key; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_RSA); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + + free(b64_key); + *state = key; + + return 0; +} + +static int teardown(void **state) +{ + SSH_KEY_FREE(*state); + return 0; +} + +static void torture_md5_hash(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *hexa = NULL; + size_t hlen; + int rc = 0; + +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER < 0x30000000L + /* In FIPS mode without OpenSSL providers, we cannot use MD5 */ + if (ssh_fips_mode()) { + skip(); + } +#endif + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, + (unsigned char **)&hash, &hlen); + assert_int_equal(rc, SSH_OK); + + hexa = ssh_get_hexa((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(hexa, + "50:15:a0:9b:92:bf:33:1c:01:c5:8c:fe:18:fa:ce:78"); + SSH_STRING_FREE_CHAR(hexa); +} + +static void torture_sha1_hash(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *sha1 = NULL; + int rc = 0; + size_t hlen; + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA1, + (unsigned char **)&hash, &hlen); + assert_true(rc == 0); + + sha1 = ssh_get_b64_unpadded((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(sha1, "6wP+houujQmxLBiFugTcoeoODCM"); + + SSH_STRING_FREE_CHAR(sha1); +} + +static void torture_sha256_hash(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *sha256 = NULL; + int rc = 0; + size_t hlen; + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256, + (unsigned char **)&hash, &hlen); + assert_true(rc == 0); + + sha256 = ssh_get_b64_unpadded((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(sha256, "jXstVLLe84fSDo1kEYGn6iumnPCSorhaiWxnJz8VTII"); + + SSH_STRING_FREE_CHAR(sha256); + +} + +static void torture_sha256_fingerprint(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *sha256 = NULL; + int rc = 0; + size_t hlen; + + rc = ssh_get_publickey_hash(pubkey, + SSH_PUBLICKEY_HASH_SHA256, + (unsigned char **)&hash, + &hlen); + assert_true(rc == 0); + + sha256 = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256, + (unsigned char *)hash, + hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(sha256, + "SHA256:jXstVLLe84fSDo1kEYGn6iumnPCSorhaiWxnJz8VTII"); + + SSH_STRING_FREE_CHAR(sha256); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_md5_hash, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_sha1_hash, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_sha256_hash, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_sha256_fingerprint, + setup_rsa_key, + teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_init.c b/src/libs/libssh-0.12.2/tests/unittests/torture_init.c new file mode 100644 index 000000000000..f719fc999500 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_init.c @@ -0,0 +1,69 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include "torture.h" +#include "libssh/libssh.h" + +static void torture_ssh_init(void **state) { + int rc; + + (void) state; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); +} + +static void torture_ssh_init_after_finalize(void **state) { + + int rc; + + (void) state; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); +} + +static void torture_is_ssh_initialized(UNUSED_PARAM(void **state)) { + + int rc; + bool initialized = false; + + /* Make sure the library is not initialized */ + while (is_ssh_initialized()) { + rc = ssh_finalize(); + assert_return_code(rc, errno); + } + + rc = ssh_init(); + assert_return_code(rc, errno); + initialized = is_ssh_initialized(); + assert_true(initialized); + rc = ssh_finalize(); + assert_return_code(rc, errno); + initialized = is_ssh_initialized(); + assert_false(initialized); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_init), + cmocka_unit_test(torture_ssh_init_after_finalize), + cmocka_unit_test(torture_is_ssh_initialized), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_isipaddr.c b/src/libs/libssh-0.12.2/tests/unittests/torture_isipaddr.c new file mode 100644 index 000000000000..91fd5a153909 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_isipaddr.c @@ -0,0 +1,66 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" + +#include "misc.c" +#include "error.c" + +/* + * Test the behavior of ssh_is_ipaddr() + */ +static void torture_ssh_is_ipaddr(void **state) +{ + (void)state; + + assert_int_equal(ssh_is_ipaddr("127.0.0.1"),1); + assert_int_equal(ssh_is_ipaddr("0.0.0.0"),1); + assert_int_equal(ssh_is_ipaddr("1.1.1.1"),1); + assert_int_equal(ssh_is_ipaddr("255.255.255.255"),1); + assert_int_equal(ssh_is_ipaddr("128.128.128.128"),1); + assert_int_equal(ssh_is_ipaddr("1.10.100.1"),1); + assert_int_equal(ssh_is_ipaddr("0.1.10.100"),1); + + assert_int_equal(ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),1); + assert_int_equal(ssh_is_ipaddr("fe80:0000:0000:0000:0202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("fe80:0:0:0:202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("fe80::202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("::1"),1); + + assert_int_equal(ssh_is_ipaddr("::ffff:192.0.2.128"),1); + + assert_int_equal(ssh_is_ipaddr("0.0.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr("0.0.0.0.a"),0); + assert_int_equal(ssh_is_ipaddr("a.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr("0a.0.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr(""),0); + assert_int_equal(ssh_is_ipaddr("0.0.0."),0); + assert_int_equal(ssh_is_ipaddr("0.0"),0); + assert_int_equal(ssh_is_ipaddr("0"),0); + + /* + * FIXME: Temporary workaround for Wine bug + */ +#ifndef _WIN32 + assert_int_equal(ssh_is_ipaddr("255.255.255"),0); +#endif + + assert_int_equal(ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334:1002"), 0); + assert_int_equal(ssh_is_ipaddr("fe80:x:202:b3ff:fe1e:8329"), 0); + assert_int_equal(ssh_is_ipaddr("fe80:x:202:b3ff:fe1e:8329"), 0); + assert_int_equal(ssh_is_ipaddr(":1"), 0); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_is_ipaddr) + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_keyfiles.c b/src/libs/libssh-0.12.2/tests/unittests/torture_keyfiles.c new file mode 100644 index 000000000000..2ce47b72bedd --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_keyfiles.c @@ -0,0 +1,256 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" +#include "legacy.c" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" + +static int setup_rsa_key(void **state) +{ + ssh_session session; + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + session = ssh_new(); + *state = session; + + return 0; +} + +static int setup_both_keys(void **state) { + int rc; + + rc = setup_rsa_key(state); + if (rc != 0) { + return rc; + } + + return rc; +} + +static int setup_both_keys_passphrase(void **state) +{ + ssh_session session; + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + session = ssh_new(); + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + ssh_free(*state); + + return 0; +} + +static void torture_pubkey_from_file(void **state) { + ssh_session session = *state; + ssh_string pubkey = NULL; + int type, rc; + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + + assert_true(rc == 0); + + SSH_STRING_FREE(pubkey); + + /* test if it returns 1 if pubkey doesn't exist */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + assert_true(rc == 1); + + /* This free is unnecessary, but the static analyser does not know */ + SSH_STRING_FREE(pubkey); + + /* test if it returns -1 if privkey doesn't exist */ + unlink(LIBSSH_RSA_TESTKEY); + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + assert_true(rc == -1); + + /* This free is unnecessary, but the static analyser does not know */ + SSH_STRING_FREE(pubkey); +} + +static int torture_read_one_line(const char *filename, char *buffer, size_t len) +{ + FILE *fp; + size_t nmemb; + + fp = fopen(filename, "r"); + if (fp == NULL) { + return -1; + } + + nmemb = fread(buffer, len - 2, 1, fp); + if (nmemb != 0 || ferror(fp)) { + fclose(fp); + return -1; + } + buffer[len - 1] = '\0'; + + fclose(fp); + + return 0; +} + +static void torture_pubkey_generate_from_privkey(void **state) { + ssh_session session = *state; + ssh_private_key privkey = NULL; + ssh_public_key pubkey = NULL; + ssh_string pubkey_orig = NULL; + ssh_string pubkey_new = NULL; + char pubkey_line_orig[512] = {0}; + char pubkey_line_new[512] = {0}; + char *p; + int type_orig = 0; + int type_new = 0; + int rc; + + /* read the publickey */ + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey_orig, + &type_orig); + assert_true(rc == 0); + assert_non_null(pubkey_orig); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_line_orig, + sizeof(pubkey_line_orig)); + assert_true(rc == 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + privkey = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, NULL); + assert_non_null(privkey); + + pubkey = publickey_from_privatekey(privkey); + assert_non_null(pubkey); + type_new = privkey->type; + privatekey_free(privkey); + + pubkey_new = publickey_to_string(pubkey); + publickey_free(pubkey); + + assert_non_null(pubkey_new); + + assert_true(ssh_string_len(pubkey_orig) == ssh_string_len(pubkey_new)); + assert_memory_equal(ssh_string_data(pubkey_orig), + ssh_string_data(pubkey_new), + ssh_string_len(pubkey_orig)); + + rc = ssh_publickey_to_file(session, LIBSSH_RSA_TESTKEY ".pub", pubkey_new, type_new); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_line_new, + sizeof(pubkey_line_new)); + assert_true(rc == 0); + + /* do not compare hostname */ + p = strrchr(pubkey_line_orig, ' '); + if (p != NULL) { + *p = '\0'; + } + p = strrchr(pubkey_line_new, ' '); + if (p != NULL) { + *p = '\0'; + } + + assert_string_equal(pubkey_line_orig, pubkey_line_new); + + SSH_STRING_FREE(pubkey_orig); + SSH_STRING_FREE(pubkey_new); +} + +/** + * @brief tests the privatekey_from_file function without passphrase + */ +static void torture_privatekey_from_file(void **state) { + ssh_session session = *state; + ssh_private_key key = NULL; + + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, SSH_KEYTYPE_RSA, NULL); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + /* Test the automatic type discovery */ + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, NULL); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + +} + +/** + * @brief tests the privatekey_from_file function with passphrase + */ +static void torture_privatekey_from_file_passphrase(void **state) { + ssh_session session = *state; + ssh_private_key key = NULL; + + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, SSH_KEYTYPE_RSA, TORTURE_TESTKEY_PASSWORD); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + /* Test the automatic type discovery */ + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, TORTURE_TESTKEY_PASSWORD); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pubkey_from_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pubkey_generate_from_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_privatekey_from_file, + setup_both_keys, + teardown), + cmocka_unit_test_setup_teardown(torture_privatekey_from_file_passphrase, + setup_both_keys_passphrase, + teardown), + }; + + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_knownhosts_parsing.c b/src/libs/libssh-0.12.2/tests/unittests/torture_knownhosts_parsing.c new file mode 100644 index 000000000000..e815ae1d1794 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_knownhosts_parsing.c @@ -0,0 +1,828 @@ +#include "config.h" + +#include + +#define LIBSSH_STATIC +#include + +#include "knownhosts.c" + +#include "torture.h" +#if (defined _WIN32) || (defined _WIN64) +#ifndef S_IRWXO +#define S_IRWXO 0 +#endif +#ifndef S_IRWXG +#define S_IRWXG 0 +#endif +#endif + +#define LOCALHOST_DSS_LINE "localhost,127.0.0.1 ssh-dss AAAAB3NzaC1kc3MAAACBAIK3RTEWBw+rAPcYUM2Qq4kEw59gXpUQ/WvkdeY7QDO64MHaaorySj8xsraNudmQFh4xb/i5Q1EMnNchOFxtilfU5bUJgdTvetyZEWFL+2HxqBs8GaWRyB1vtSFAw3GO8VUEnjF844N3dNyLoc0NX8IvzwNIaQho6KTsueQlG1X9AAAAFQCXUl4a5UvElL4thi/8QlxR5PtEewAAAIBqNpl5MTBxKQu5jT0+WASa7pAqwT53ofv7ZTDIEokYRb57/nwzDgkcs1fsBRrI6eczJ/VlXWwKbsgkx2Nh3ZiWYwC+HY5uqRpDaj3HERC6LMn4dzdcl29fYeziEibCbRjJX5lZF2vIaA1Ewv8yT0UlunyHZRiyw4WlEglkf/NITAAAAIBxLsdBBXn+8qEYwWK9KT+arRqNXC/lrl0Fp5YyxGNGCv82JcnuOShGGTzhYf8AtTCY1u5oixiW9kea6KXGAKgTjfJShr7n47SZVfOPOrBT3VLhRdGGO3GblDUppzfL8wsEdoqXjzrJuxSdrGnkFu8S9QjkPn9dCtScvWEcluHqMw==" +#define LOCALHOST_RSA_LINE "localhost,127.0.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDD7g+vV5cvxxGN0Ldmda4WZCPgRaxV1tV+1KRZoGUNUI61h0X4bmmGaAPRQBCz4G1d9bawqDqEqnpFWazrxBU5cQtISSjzuDJKovLGliky/ShTszee1Thszg3qVNk9gGOWj7jn/HDaOxRlp003Bp47MOdnMnK/oftllFDfY2fF5IRpE6sSIGtg2ZDtF95TV5/9W2oMOIAy8u/83tuibYlNPa1X/von5LgdaPLn6Bk16bQKIhAhlMtFZH8MBYEWe4ZtOGaSWKOsK9MM/RTMlwPi6PkfoHNl4MCMupjx+CdLXwbQEt9Ww+bBIaCui2VWBEiruVbIgJh0W2Tal0e2BzYZ What a Wurst!" +#define LOCALHOST_ECDSA_SHA1_NISTP256_LINE "localhost ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFWmI0n0Tn5+zR7pPGcKYszRbJ/T0T3QfzRBSMMiyebGKRY8tjkU5h2l/UMugzOrOyWqMGQDgQn+a0aMunhKMg0=" +#define LOCALHOST_DEFAULT_ED25519 "localhost ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_PORT_ED25519 "[localhost]:2222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_PATTERN_ED25519 "local* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_HASHED_ED25519 "|1|ayWjmTf9mYgj7PuQNVOa7Lqkj5s=|hkbEh8FN6IkLo6t6GQGuBwamgsM= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_PORT_WILDCARD "[localhost]:* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_STANDARD_PORT "[localhost]:22 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" + +#define TMP_FILE_NAME "/tmp/known_hosts_XXXXXX" + +const char template[] = "temp_dir_XXXXXX"; + +static int setup_knownhosts_file(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_PATTERN_ED25519, + sizeof(char), + strlen(LOCALHOST_PATTERN_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_PATTERN_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite(LOCALHOST_RSA_LINE, + sizeof(char), + strlen(LOCALHOST_RSA_LINE), + fp); + if (nwritten != strlen(LOCALHOST_RSA_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static int setup_knownhosts_file_duplicate(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + /* ed25519 key */ + nwritten = fwrite(LOCALHOST_PATTERN_ED25519, + sizeof(char), + strlen(LOCALHOST_PATTERN_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_PATTERN_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + + /* RSA key */ + nwritten = fwrite(LOCALHOST_RSA_LINE, + sizeof(char), + strlen(LOCALHOST_RSA_LINE), + fp); + if (nwritten != strlen(LOCALHOST_RSA_LINE)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + + /* ed25519 key again */ + nwritten = fwrite(LOCALHOST_PATTERN_ED25519, + sizeof(char), + strlen(LOCALHOST_PATTERN_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_PATTERN_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static int setup_knownhosts_file_unsupported_type(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_DSS_LINE, + sizeof(char), + strlen(LOCALHOST_DSS_LINE), + fp); + if (nwritten != strlen(LOCALHOST_DSS_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static int teardown_knownhosts_file(void **state) +{ + char *tmp_file = *state; + + if (tmp_file == NULL) { + return -1; + } + + unlink(tmp_file); + SAFE_FREE(tmp_file); + + return 0; +} + +static void torture_knownhosts_parse_line_rsa(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_RSA_LINE, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_RSA); + assert_string_equal(entry->comment, "What a Wurst!"); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); + + rc = ssh_known_hosts_parse_line("127.0.0.1", + LOCALHOST_RSA_LINE, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "127.0.0.1"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_RSA); + assert_string_equal(entry->comment, "What a Wurst!"); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_ecdsa(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_ECDSA_SHA1_NISTP256_LINE, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ECDSA_P256); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_default_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_DEFAULT_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_port_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("[localhost]:2222", + LOCALHOST_PORT_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "[localhost]:2222"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_port_wildcard(void **state) +{ + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_PORT_WILDCARD, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_standard_port(void **state) +{ + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_STANDARD_PORT, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_pattern_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_PATTERN_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_hashed_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_HASHED_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_read_file(void **state) +{ + const char *knownhosts_file = *state; + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + struct ssh_knownhosts_entry *entry = NULL; + enum ssh_keytypes_e type; + int rc; + + rc = ssh_known_hosts_read_entries("localhost", + knownhosts_file, + &entry_list); + assert_int_equal(rc, SSH_OK); + assert_non_null(entry_list); + it = ssh_list_get_iterator(entry_list); + assert_non_null(it); + + /* First key in known hosts file is ED25519 */ + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + assert_non_null(entry); + + assert_string_equal(entry->hostname, "localhost"); + type = ssh_key_type(entry->publickey); + assert_int_equal(type, SSH_KEYTYPE_ED25519); + assert_non_null(it->next); + + it = it->next; + + /* Second key in known hosts file is RSA */ + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + assert_non_null(entry); + + assert_string_equal(entry->hostname, "localhost"); + type = ssh_key_type(entry->publickey); + assert_int_equal(type, SSH_KEYTYPE_RSA); + assert_null(it->next); + + it = ssh_list_get_iterator(entry_list); + for (;it != NULL; it = it->next) { + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + SSH_KNOWNHOSTS_ENTRY_FREE(entry); + } + ssh_list_free(entry_list); +} + +static void torture_knownhosts_get_algorithms_names(void **state) +{ + const char *knownhosts_file = *state; + ssh_session session; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa"; + char *names = NULL; + bool process_config = false; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + names = ssh_known_hosts_get_algorithms_names(session); + assert_non_null(names); + assert_string_equal(names, expect); + + SAFE_FREE(names); + ssh_free(session); +} + +/* Do not remove this test if we completely remove DSA support! */ +static void torture_knownhosts_get_algorithms_names_unsupported(void **state) +{ + const char *knownhosts_file = *state; + ssh_session session; + char *names = NULL; + bool process_config = false; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + names = ssh_known_hosts_get_algorithms_names(session); + assert_null(names); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms_wanted(void **state) +{ + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; + bool process_config = false; + const char *wanted = "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256," + "rsa-sha2-256,ecdsa-sha2-nistp521"; + const char *expect = "rsa-sha2-256,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256,ecdsa-sha2-nistp521"; + int verbose = 4; + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + /* Set the wanted list of hostkeys, ordered by preference */ + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + assert_string_equal(algo_list, expect); + free(algo_list); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms_negative(UNUSED_PARAM(void **state)) +{ + const char *wanted = NULL; + const char *expect = NULL; + + char *algo_list = NULL; + + char *cwd = NULL; + char *tmp_dir = NULL; + + bool process_config = false; + int verbose = 4; + int rc = 0; + + ssh_session session; + /* Create temporary directory */ + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + + /* Test with unknown key type in known_hosts */ + wanted = "rsa-sha2-256"; + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + torture_write_file("unknown_key_type", "localhost unknown AAAABBBBCCCC"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "unknown_key_type"); + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + assert_string_equal(algo_list, wanted); + SAFE_FREE(algo_list); + + /* Test with unsupported, but existing types */ + wanted = "rsa-sha2-256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com"; + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + algo_list = ssh_client_select_hostkeys(session); + assert_null(algo_list); + + /* In FIPS mode, test filtering keys not allowed */ + if (ssh_fips_mode()) { + wanted = "ssh-ed25519,rsa-sha2-256,ssh-rsa"; + expect = "rsa-sha2-256"; + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + torture_write_file("no_fips", LOCALHOST_DEFAULT_ED25519); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "no_fips"); + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + assert_string_equal(algo_list, expect); + SAFE_FREE(algo_list); + } + + ssh_free(session); + + /* Teardown */ + rc = torture_change_dir(cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tmp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tmp_dir); + SAFE_FREE(cwd); +} + +#ifndef _WIN32 /* There is no /dev/null on Windows */ +static void torture_knownhosts_host_exists(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + /* This makes sure the system's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "/dev/null"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will not fail when the system's known_hosts is + * not accessible*/ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "./unaccessible"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will fail for an unknown host */ + ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + ssh_free(session); +} + +static void torture_knownhosts_host_exists_global(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + /* This makes sure the user's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will not fail when the user's known_hosts is + * not accessible*/ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "./unaccessible"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will fail for an unknown host */ + ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms(void **state) +{ + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; + bool process_config = false; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256," + "sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com"; + const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + /* This makes sure the system's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "/dev/null"); + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + if (ssh_fips_mode()) { + assert_string_equal(algo_list, expect_fips); + } else { + assert_string_equal(algo_list, expect); + } + free(algo_list); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms_global(void **state) +{ + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; + bool process_config = false; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256," + "sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com"; + const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + /* This makes sure the current-user's known hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + if (ssh_fips_mode()) { + assert_string_equal(algo_list, expect_fips); + } else { + assert_string_equal(algo_list, expect); + } + free(algo_list); + + ssh_free(session); +} + +static int setup_bad_knownhosts_file(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_DEFAULT_ED25519, + sizeof(char), + strlen(LOCALHOST_DEFAULT_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_DEFAULT_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + +#define LOCALHOST_BAD_LINE "localhost \n" + nwritten = fwrite(LOCALHOST_BAD_LINE, + sizeof(char), + strlen(LOCALHOST_BAD_LINE), + fp); + if (nwritten != strlen(LOCALHOST_BAD_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static void torture_knownhosts_has_entry(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + bool process_config = false; + struct ssh_knownhosts_entry *entry = NULL; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + /* This makes sure the current-user's known hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + /* Error is expected -- this tests the memory is not leaked from this + * test case */ + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR); + + found = ssh_session_get_known_hosts_entry(session, &entry); + assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR); + assert_null(entry); + + ssh_free(session); +} +#endif /* _WIN32 There is no /dev/null on Windows */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_knownhosts_parse_line_rsa), + cmocka_unit_test(torture_knownhosts_parse_line_ecdsa), + cmocka_unit_test(torture_knownhosts_parse_line_default_ed25519), + cmocka_unit_test(torture_knownhosts_parse_line_port_ed25519), + cmocka_unit_test(torture_knownhosts_parse_line_port_wildcard), + cmocka_unit_test(torture_knownhosts_parse_line_standard_port), + cmocka_unit_test(torture_knownhosts_parse_line_pattern_ed25519), + cmocka_unit_test(torture_knownhosts_parse_line_hashed_ed25519), + cmocka_unit_test_setup_teardown(torture_knownhosts_read_file, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_read_file, + setup_knownhosts_file_duplicate, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names_unsupported, + setup_knownhosts_file_unsupported_type, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_wanted, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test(torture_knownhosts_algorithms_negative), +#ifndef _WIN32 + cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists_global, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_global, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_has_entry, + setup_bad_knownhosts_file, + teardown_knownhosts_file), +#endif + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_list.c b/src/libs/libssh-0.12.2/tests/unittests/torture_list.c new file mode 100644 index 000000000000..663c5516b743 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_list.c @@ -0,0 +1,131 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "error.c" +#include "misc.c" + +static void torture_ssh_list_new(void **state) { + struct ssh_list *xlist; + + (void) state; + + xlist = ssh_list_new(); + + assert_non_null(xlist); + assert_null(xlist->root); + assert_null(xlist->end); + + assert_int_equal(ssh_list_count(xlist), 0); + + ssh_list_free(xlist); +} + +static void torture_ssh_list_append(void **state) { + struct ssh_list *xlist; + int rc; + + (void) state; + + xlist = ssh_list_new(); + assert_non_null(xlist); + + rc = ssh_list_append(xlist, "item1"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item1"); + + rc = ssh_list_append(xlist, "item2"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + rc = ssh_list_append(xlist, "item3"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->root->next); + assert_non_null(xlist->root->next->data); + assert_non_null(xlist->root->next->next); + assert_non_null(xlist->root->next->next->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->root->next->data, "item2"); + assert_string_equal((const char *) xlist->root->next->next->data, "item3"); + assert_string_equal((const char *) xlist->end->data, "item3"); + + assert_int_equal(ssh_list_count(xlist), 3); + + ssh_list_free(xlist); +} + +static void torture_ssh_list_prepend(void **state) { + struct ssh_list *xlist; + int rc; + + (void) state; + + xlist = ssh_list_new(); + assert_non_null(xlist); + + rc = ssh_list_prepend(xlist, "item1"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item1"); + + rc = ssh_list_append(xlist, "item2"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + rc = ssh_list_prepend(xlist, "item3"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->root->next); + assert_non_null(xlist->root->next->data); + assert_non_null(xlist->root->next->next); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item3"); + assert_string_equal((const char *) xlist->root->next->data, "item1"); + assert_string_equal((const char *) xlist->root->next->next->data, "item2"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + assert_int_equal(ssh_list_count(xlist), 3); + + ssh_list_free(xlist); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_list_new), + cmocka_unit_test(torture_ssh_list_append), + cmocka_unit_test(torture_ssh_list_prepend), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_misc.c b/src/libs/libssh-0.12.2/tests/unittests/torture_misc.c new file mode 100644 index 000000000000..88bb73741d11 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_misc.c @@ -0,0 +1,1329 @@ +#include "config.h" + +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +#ifndef _WIN32 +#define _POSIX_PTHREAD_SEMANTICS +#include +#endif + +#define LIBSSH_STATIC +#include + +#include "misc.c" +#include "torture.h" +#include "error.c" + +#ifdef _WIN32 +#include +#else +#include +#endif + +#define TORTURE_TEST_DIR "/usr/local/bin/truc/much/.." +#define TORTURE_IPV6_LOCAL_LINK "fe80::98e1:82ff:fe8d:28b3%%%s" + +const char template[] = "temp_dir_XXXXXX"; + +static int setup(void **state) +{ + ssh_session session = ssh_new(); + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +static void torture_get_user_home_dir(void **state) { +#ifndef _WIN32 + struct passwd *pwd = getpwuid(getuid()); +#endif /* _WIN32 */ + char *user; + + (void) state; + + user = ssh_get_user_home_dir(NULL); + assert_non_null(user); +#ifndef _WIN32 + assert_string_equal(user, pwd->pw_dir); +#endif /* _WIN32 */ + + SAFE_FREE(user); +} + +static void torture_basename(void **state) { + char *path; + + (void) state; + + path=ssh_basename(TORTURE_TEST_DIR "/test"); + assert_non_null(path); + assert_string_equal(path, "test"); + SAFE_FREE(path); + path=ssh_basename(TORTURE_TEST_DIR "/test/"); + assert_non_null(path); + assert_string_equal(path, "test"); + SAFE_FREE(path); +} + +static void torture_dirname(void **state) { + char *path; + + (void) state; + + path=ssh_dirname(TORTURE_TEST_DIR "/test"); + assert_non_null(path); + assert_string_equal(path, TORTURE_TEST_DIR ); + SAFE_FREE(path); + path=ssh_dirname(TORTURE_TEST_DIR "/test/"); + assert_non_null(path); + assert_string_equal(path, TORTURE_TEST_DIR); + SAFE_FREE(path); +} + +static void torture_ntohll(void **state) { + uint64_t value = 0x0123456789abcdef; + uint32_t sample = 1; + unsigned char *ptr = (unsigned char *) &sample; + uint64_t check; + + (void) state; + + if (ptr[0] == 1){ + /* we're in little endian */ + check = 0xefcdab8967452301; + } else { + /* big endian */ + check = value; + } + value = ntohll(value); + assert_true(value == check); +} + +/** + * @brief Compare fields of two (struct tm) type structures. + * + * @param[in] a Pointer to the first structure to compare + * + * @param[in] b Pointer to the second structure to compare + * + * @returns -1 on error + * @returns 0 if the fields of the structures are the same + * @returns 1 if the fields of the structures are not the same + */ +static int tm_cmp(const struct tm *a, const struct tm *b) +{ + if (a == NULL || b == NULL) { + return -1; + } + + return !(a->tm_sec == b->tm_sec && + a->tm_min == b->tm_min && + a->tm_hour == b->tm_hour && + a->tm_mday == b->tm_mday && + a->tm_mon == b->tm_mon && + a->tm_year == b->tm_year && + a->tm_wday == b->tm_wday && + a->tm_yday == b->tm_yday && + a->tm_isdst == b->tm_isdst); +} + +/** + * @brief Validate that localtime_r() works properly. + * + * This test is mainly to check that the libssh implementation of + * localtime_r() on Windows works properly (Windows does not provide + * localtime_r()) + */ +static void torture_localtime_r(UNUSED_PARAM(void **state)) +{ + /* + * The tm_wday and tm_yday fields of tm1 and tm2 would be filled + * appropriately due to the mktime() call further in the test. + */ + + /* Linux release date: 17/09/1991 (random time: 02:01:00) */ + struct tm tm1 = {.tm_sec = 0, + .tm_min = 1, + .tm_hour = 2, + .tm_mday = 17, + .tm_mon = 9 - 1, + .tm_year = 1991 - 1900, + .tm_isdst = 0}; + + /* Windows release date: 20/11/1985 (random time 05:04:03) */ + struct tm tm2 = {.tm_sec = 3, + .tm_min = 4, + .tm_hour = 5, + .tm_mday = 20, + .tm_mon = 11 - 1, + .tm_year = 1985 - 1900, + .tm_isdst = 0}; + + time_t t1, t2; + struct tm *static_tm_ptr = NULL, *tm_ptr = NULL; + struct tm our_tm = {0}; + int cmp; + + /* + * Convert time represented as (struct tm) to time represented as + * a (time_t) + */ + t1 = mktime(&tm1); + assert_int_not_equal(t1, (time_t)-1); + + t2 = mktime(&tm2); + assert_int_not_equal(t2, (time_t)-1); + + /* Test that localtime_r() gives the correct broken down time */ + tm_ptr = localtime_r(&t1, &our_tm); + assert_ptr_equal(tm_ptr, &our_tm); + + cmp = tm_cmp(&our_tm, &tm1); + assert_int_equal(cmp, 0); + + /* + * Test that localtime_r() does not modify the static structure used by + * localtime(). (This is an attempt to test that the localtime_r() + * implementation does not use localtime() internally) + * + * To test this, we first use localtime() on some time, then use + * localtime_r() on another time and then validate that the time + * corresponding to the pointer (to the static structure) returned by + * the first localtime() call does not change. + */ + static_tm_ptr = localtime(&t1); + assert_non_null(static_tm_ptr); + + cmp = tm_cmp(static_tm_ptr, &tm1); + assert_int_equal(cmp, 0); + + tm_ptr = localtime_r(&t2, &our_tm); + assert_ptr_equal(tm_ptr, &our_tm); + + cmp = tm_cmp(static_tm_ptr, &tm1); + assert_int_equal(cmp, 0); + + /* + * Ideally, it should be checked that the localtime_r() implementation + * is thread safe by testing it under multiple threads, but we are not + * checking that as of now. This is because we trust localtime_r() provided + * by POSIX systems to be thread safe and the libssh implementation of + * localtime_r() on Windows should be a simple wrapper around Windows's + * localtime_s() which should also be thread safe. + */ +} + +#ifdef _WIN32 + +static void torture_path_expand_tilde_win(void **state) { + char *d; + + (void) state; + + d = ssh_path_expand_tilde("~\\.ssh"); + assert_non_null(d); + print_message("Expanded path: %s\n", d); + free(d); + + d = ssh_path_expand_tilde("/guru/meditation"); + assert_string_equal(d, "/guru/meditation"); + free(d); +} + +#else /* _WIN32 */ + +static void torture_path_expand_tilde_unix(void **state) { + char h[256] = {0}; + char *d = NULL; + char *user = NULL; + char *home = NULL; + struct passwd *pw = NULL; + + (void) state; + + pw = getpwuid(getuid()); + assert_non_null(pw); + + user = pw->pw_name; + assert_non_null(user); + home = pw->pw_dir; + assert_non_null(home); + + snprintf(h, 256 - 1, "%s/.ssh", home); + + d = ssh_path_expand_tilde("~/.ssh"); + assert_non_null(d); + assert_string_equal(d, h); + free(d); + + d = ssh_path_expand_tilde("/guru/meditation"); + assert_non_null(d); + assert_string_equal(d, "/guru/meditation"); + free(d); + + snprintf(h, 256 - 1, "~%s/.ssh", user); + d = ssh_path_expand_tilde(h); + assert_non_null(d); + + snprintf(h, 256 - 1, "%s/.ssh", home); + assert_string_equal(d, h); + free(d); +} + +#endif /* _WIN32 */ + +static void torture_path_expand_escape(void **state) { + ssh_session session = *state; + const char *s = "%d/%h/%p/by/%r"; + char *e; + + /* Set the homedir here to prevent querying the NSS DB */ + session->opts.homedir = strdup("guru"); + session->opts.host = strdup("meditation"); + session->opts.port = 0; + session->opts.username = strdup("root"); + + e = ssh_path_expand_escape(session, s); + assert_non_null(e); + assert_string_equal(e, "guru/meditation/22/by/root"); + ssh_string_free_char(e); + + session->opts.port = 222; + + e = ssh_path_expand_escape(session, s); + assert_non_null(e); + assert_string_equal(e, "guru/meditation/222/by/root"); + ssh_string_free_char(e); +} + +static void torture_path_expand_known_hosts(void **state) { + ssh_session session = *state; + char *tmp; + + /* Set the homedir here to prevent querying the NSS DB */ + session->opts.homedir = strdup("/home/guru"); + + tmp = ssh_path_expand_escape(session, "%d/.ssh/known_hosts"); + assert_non_null(tmp); + assert_string_equal(tmp, "/home/guru/.ssh/known_hosts"); + free(tmp); +} + +static void torture_path_expand_percent(void **state) { + ssh_session session = *state; + char *tmp; + + /* Set the homedir here to prevent querying the NSS DB */ + session->opts.homedir = strdup("/home/guru"); + + tmp = ssh_path_expand_escape(session, "%d/.ssh/config%%1"); + assert_non_null(tmp); + assert_string_equal(tmp, "/home/guru/.ssh/config%1"); + free(tmp); +} + +static void torture_timeout_elapsed(void **state){ + struct ssh_timestamp ts; + (void) state; + ssh_timestamp_init(&ts); + usleep(30000); + + assert_true(ssh_timeout_elapsed(&ts,25)); + assert_false(ssh_timeout_elapsed(&ts,30000)); + assert_false(ssh_timeout_elapsed(&ts,300)); + assert_true(ssh_timeout_elapsed(&ts,0)); + assert_false(ssh_timeout_elapsed(&ts,-1)); +} + +static void torture_timeout_update(void **state){ + struct ssh_timestamp ts; + (void) state; + ssh_timestamp_init(&ts); + usleep(50000); + assert_int_equal(ssh_timeout_update(&ts,25), 0); + assert_in_range(ssh_timeout_update(&ts,30000),29000,29960); + assert_in_range(ssh_timeout_update(&ts,500),1,460); + assert_int_equal(ssh_timeout_update(&ts,0),0); + assert_int_equal(ssh_timeout_update(&ts,-1),-1); +} + +static void torture_ssh_analyze_banner(void **state) { + int rc = 0; + ssh_session session = NULL; + (void) state; + +#define reset_banner_test() \ + do { \ + rc = 0; \ + ssh_free(session); \ + session = ssh_new(); \ + assert_non_null(session); \ + } while (0) + +#define assert_banner_rejected(is_server) \ + do { \ + rc = ssh_analyze_banner(session, is_server); \ + assert_int_not_equal(0, rc); \ + } while (0); + +#define assert_client_banner_rejected(banner) \ + do { \ + reset_banner_test(); \ + session->clientbanner = strdup(banner); \ + assert_non_null(session->clientbanner); \ + assert_banner_rejected(1 /*server*/); \ + SAFE_FREE(session->clientbanner); \ + } while (0) + +#define assert_server_banner_rejected(banner) \ + do { \ + reset_banner_test(); \ + session->serverbanner = strdup(banner); \ + assert_non_null(session->serverbanner); \ + assert_banner_rejected(0 /*client*/); \ + SAFE_FREE(session->serverbanner); \ + } while (0) + +#define assert_banner_accepted(is_server) \ + do { \ + rc = ssh_analyze_banner(session, is_server); \ + assert_int_equal(0, rc); \ + } while (0) + +#define assert_client_banner_accepted(banner) \ + do { \ + reset_banner_test(); \ + session->clientbanner = strdup(banner); \ + assert_non_null(session->clientbanner); \ + assert_banner_accepted(1 /*server*/); \ + SAFE_FREE(session->clientbanner); \ + } while (0) + +#define assert_server_banner_accepted(banner) \ + do { \ + reset_banner_test(); \ + session->serverbanner = strdup(banner); \ + assert_non_null(session->serverbanner); \ + assert_banner_accepted(0 /*client*/); \ + SAFE_FREE(session->serverbanner); \ + } while (0) + + /* no banner is set */ + reset_banner_test(); + assert_banner_rejected(0 /*client*/); + reset_banner_test(); + assert_banner_rejected(1 /*server*/); + + /* banner is too short */ + assert_client_banner_rejected("abc"); + assert_server_banner_rejected("abc"); + + /* banner doesn't start "SSH-" */ + assert_client_banner_rejected("abc-2.0"); + assert_server_banner_rejected("abc-2.0"); + + /* SSH v1 */ + assert_client_banner_rejected("SSH-1.0"); + assert_server_banner_rejected("SSH-1.0"); + + /* SSH v1.9 gets counted as both v1 and v2 */ + assert_client_banner_accepted("SSH-1.9"); + assert_server_banner_accepted("SSH-1.9"); + + /* SSH v2 */ + assert_client_banner_accepted("SSH-2.0"); + assert_server_banner_accepted("SSH-2.0"); + + /* OpenSSH banners: too short to extract major and minor versions */ + assert_client_banner_accepted("SSH-2.0-OpenSSH"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH"); + assert_int_equal(0, session->openssh); + + + /* OpenSSH banners: big enough to extract major and minor versions */ + assert_client_banner_accepted("SSH-2.0-OpenSSH_5.9p1"); + assert_int_equal(SSH_VERSION_INT(5, 9, 0), session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_5.9p1"); + assert_int_equal(SSH_VERSION_INT(5, 9, 0), session->openssh); + + assert_client_banner_accepted("SSH-2.0-OpenSSH_1.99"); + assert_int_equal(SSH_VERSION_INT(1, 99, 0), session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_1.99"); + assert_int_equal(SSH_VERSION_INT(1, 99, 0), session->openssh); + + /* OpenSSH banners: major, minor version limits result in zero */ + assert_client_banner_accepted("SSH-2.0-OpenSSH_0.99p1"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_0.99p1"); + assert_int_equal(0, session->openssh); + assert_client_banner_accepted("SSH-2.0-OpenSSH_1.101p1"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_1.101p1"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: bogus major results in zero */ + assert_client_banner_accepted("SSH-2.0-OpenSSH_X.9p1"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_X.9p1"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: bogus minor results in zero */ + assert_server_banner_accepted("SSH-2.0-OpenSSH_5.Yp1"); + assert_int_equal(0, session->openssh); + assert_client_banner_accepted("SSH-2.0-OpenSSH_5.Yp1"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: ssh-keyscan(1) */ + assert_client_banner_accepted("SSH-2.0-OpenSSH-keyscan"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH-keyscan"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: Double digit in major version */ + assert_server_banner_accepted("SSH-2.0-OpenSSH_10.0p1"); + assert_int_equal(SSH_VERSION_INT(10, 0, 0), session->openssh); + + ssh_free(session); +} + +static void torture_ssh_dir_writeable(UNUSED_PARAM(void **state)) +{ + char *tmp_dir = NULL; + int rc = 0; + FILE *file = NULL; + char buffer[256]; + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + rc = ssh_dir_writeable(tmp_dir); + assert_int_equal(rc, 1); + + /* Create a file */ + snprintf(buffer, sizeof(buffer), "%s/a", tmp_dir); + + file = fopen(buffer, "w"); + assert_non_null(file); + + fprintf(file, "Hello world!\n"); + fclose(file); + + /* Negative test for checking a normal file */ + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 0); + + /* Negative test for non existent file */ + snprintf(buffer, sizeof(buffer), "%s/b", tmp_dir); + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 0); + +#ifndef _WIN32 + /* Negative test for directory without write permission */ + rc = ssh_mkdir(buffer, 0400); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 0); +#endif + + torture_rmdirs(tmp_dir); + + SAFE_FREE(tmp_dir); +} + +static void torture_ssh_mkdirs(UNUSED_PARAM(void **state)) +{ + char *tmp_dir = NULL; + char *cwd = NULL; + char buffer[256]; + + ssize_t count = 0; + + int rc; + + /* Get current working directory */ + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + /* Create a base disposable directory */ + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + /* Create a single directory */ + count = snprintf(buffer, sizeof(buffer), "%s/a", tmp_dir); + assert_return_code(count, errno); + + rc = ssh_mkdirs(buffer, 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 1); + + /* Create directories recursively */ + count = snprintf(buffer, sizeof(buffer), "%s/b/c/d", tmp_dir); + assert_return_code(count, errno); + + rc = ssh_mkdirs(buffer, 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 1); + + /* Change directory */ + rc = torture_change_dir(tmp_dir); + assert_return_code(rc, errno); + + /* Create single local directory */ + rc = ssh_mkdirs("e", 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable("e"); + assert_int_equal(rc, 1); + + /* Create local directories recursively */ + rc = ssh_mkdirs("f/g/h", 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable("f/g/h"); + assert_int_equal(rc, 1); + + /* Negative test for creating "." directory */ + rc = ssh_mkdirs(".", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating "/" directory */ + rc = ssh_mkdirs("/", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating "" directory */ + rc = ssh_mkdirs("", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating NULL directory */ + rc = ssh_mkdirs(NULL, 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating existing directory */ + rc = ssh_mkdirs("a", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EEXIST); + + /* Return to original directory */ + rc = torture_change_dir(cwd); + assert_return_code(rc, errno); + + /* Cleanup */ + torture_rmdirs(tmp_dir); + + SAFE_FREE(tmp_dir); + SAFE_FREE(cwd); +} + +static void torture_ssh_quote_file_name(UNUSED_PARAM(void **state)) +{ + char buffer[2048]; + int rc; + + /* Only ordinary chars */ + rc = ssh_quote_file_name("a b", buffer, 2048); + assert_int_equal(rc, 5); + assert_string_equal(buffer, "'a b'"); + + /* Single quote in file name */ + rc = ssh_quote_file_name("a'b", buffer, 2048); + assert_int_equal(rc, 9); + assert_string_equal(buffer, "'a'\"'\"'b'"); + + /* Exclamation in file name */ + rc = ssh_quote_file_name("a!b", buffer, 2048); + assert_int_equal(rc, 8); + assert_string_equal(buffer, "'a'\\!'b'"); + + /* All together */ + rc = ssh_quote_file_name("'a!b'", buffer, 2048); + assert_int_equal(rc, 14); + assert_string_equal(buffer, "\"'\"'a'\\!'b'\"'\""); + + rc = ssh_quote_file_name("a'!b", buffer, 2048); + assert_int_equal(rc, 11); + assert_string_equal(buffer, "'a'\"'\"\\!'b'"); + + rc = ssh_quote_file_name("a'$b", buffer, 2048); + assert_int_equal(rc, 10); + assert_string_equal(buffer, "'a'\"'\"'$b'"); + + rc = ssh_quote_file_name("a'`b", buffer, 2048); + assert_int_equal(rc, 10); + assert_string_equal(buffer, "'a'\"'\"'`b'"); + + + rc = ssh_quote_file_name(" ", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "' '"); + + rc = ssh_quote_file_name(" ", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "' '"); + + + rc = ssh_quote_file_name("\r", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\r'"); + + rc = ssh_quote_file_name("\n", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\n'"); + + rc = ssh_quote_file_name("\r\n", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\r\n'"); + + + rc = ssh_quote_file_name("\\r", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\\r'"); + + rc = ssh_quote_file_name("\\n", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\\n'"); + + rc = ssh_quote_file_name("\\r\\n", buffer, 2048); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "'\\r\\n'"); + + + rc = ssh_quote_file_name("\t", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\t'"); + + rc = ssh_quote_file_name("\v", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\v'"); + + rc = ssh_quote_file_name("\t\v", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\t\v'"); + + + rc = ssh_quote_file_name("'", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "\"'\""); + + rc = ssh_quote_file_name("''", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "\"''\""); + + + rc = ssh_quote_file_name("\"", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\"'"); + + rc = ssh_quote_file_name("\"\"", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\"\"'"); + + rc = ssh_quote_file_name("'\"", buffer, 2048); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "\"'\"'\"'"); + + rc = ssh_quote_file_name("\"'", buffer, 2048); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "'\"'\"'\""); + + + /* Worst case */ + rc = ssh_quote_file_name("a'b'", buffer, 3 * 4 + 1); + assert_int_equal(rc, 12); + assert_string_equal(buffer, "'a'\"'\"'b'\"'\""); + + /* Negative tests */ + + /* NULL params */ + rc = ssh_quote_file_name(NULL, buffer, 3 * 4 + 1); + assert_int_equal(rc, SSH_ERROR); + + /* NULL params */ + rc = ssh_quote_file_name("a b", NULL, 3 * 4 + 1); + assert_int_equal(rc, SSH_ERROR); + + /* Small buffer size */ + rc = ssh_quote_file_name("a b", buffer, 0); + assert_int_equal(rc, SSH_ERROR); + + /* Worst case and small buffer size */ + rc = ssh_quote_file_name("a'b'", buffer, 3 * 4); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_ssh_newline_vis(UNUSED_PARAM(void **state)) +{ + int rc; + char buffer[1024]; + + rc = ssh_newline_vis("\n", buffer, 1024); + assert_int_equal(rc, 2); + assert_string_equal(buffer, "\\n"); + + rc = ssh_newline_vis("\n\n\n\n", buffer, 1024); + assert_int_equal(rc, 8); + assert_string_equal(buffer, "\\n\\n\\n\\n"); + + rc = ssh_newline_vis("a\nb\n", buffer, 1024); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "a\\nb\\n"); +} + +static void torture_ssh_strreplace(void **state) +{ + char test_string1[] = "this;is;a;test"; + char test_string2[] = "test;is;a;this"; + char test_string3[] = "this;test;is;a"; + char *replaced_string = NULL; + + (void) state; + + /* pattern and replacement are of the same size */ + replaced_string = ssh_strreplace(test_string1, "test", "kiwi"); + assert_string_equal(replaced_string, "this;is;a;kiwi"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string2, "test", "kiwi"); + assert_string_equal(replaced_string, "kiwi;is;a;this"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string3, "test", "kiwi"); + assert_string_equal(replaced_string, "this;kiwi;is;a"); + free(replaced_string); + + /* replacement is greater than pattern */ + replaced_string = ssh_strreplace(test_string1, "test", "an;apple"); + assert_string_equal(replaced_string, "this;is;a;an;apple"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string2, "test", "an;apple"); + assert_string_equal(replaced_string, "an;apple;is;a;this"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string3, "test", "an;apple"); + assert_string_equal(replaced_string, "this;an;apple;is;a"); + free(replaced_string); + + /* replacement is less than pattern */ + replaced_string = ssh_strreplace(test_string1, "test", "an"); + assert_string_equal(replaced_string, "this;is;a;an"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string2, "test", "an"); + assert_string_equal(replaced_string, "an;is;a;this"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string3, "test", "an"); + assert_string_equal(replaced_string, "this;an;is;a"); + free(replaced_string); + + /* pattern not found in teststring */ + replaced_string = ssh_strreplace(test_string1, "banana", "an"); + assert_string_equal(replaced_string, test_string1); + free(replaced_string); + + /* pattern is NULL */ + replaced_string = ssh_strreplace(test_string1, NULL , "an"); + assert_string_equal(replaced_string, test_string1); + free(replaced_string); + + /* replacement is NULL */ + replaced_string = ssh_strreplace(test_string1, "test", NULL); + assert_string_equal(replaced_string, test_string1); + free(replaced_string); + + /* src is NULL */ + replaced_string = ssh_strreplace(NULL, "test", "kiwi"); + assert_null(replaced_string); +} + +static void torture_ssh_strerror(void **state) +{ + char buf[1024]; + size_t bufflen = sizeof(buf); + char *out = NULL; + + (void) state; + + out = ssh_strerror(ENOENT, buf, 1); /* too short */ + assert_string_equal(out, "\0"); + + out = ssh_strerror(256, buf, bufflen); /* unknown error code */ + /* This error is always different: + * Freebd: "Unknown error: 256" + * MinGW/Win: "Unknown error" + * Linux/glibc: "Unknown error 256" + * Alpine/musl: "No error information" + */ + assert_non_null(out); + + out = ssh_strerror(ENOMEM, buf, bufflen); + /* This actually differs too for glibc/musl: + * musl: "Out of memory" + * everything else: "Cannot allocate memory" + */ + assert_non_null(out); +} + +static void torture_ssh_readn(void **state) +{ + char *write_buf = NULL, *read_buf = NULL, *file_path = NULL; + size_t data_len = 10 * 1024 * 1024; + size_t read_buf_size = data_len + 1024; + size_t i, total_bytes_written = 0; + + const char *file_template = "libssh_torture_ssh_readn_test_XXXXXX"; + + off_t off; + ssize_t bytes_read, bytes_written; + int fd, rc, flags; + + (void)state; + + write_buf = malloc(data_len); + assert_non_null(write_buf); + + /* Fill the write buffer with random data */ + for (i = 0; i < data_len; ++i) { + rc = rand(); + write_buf[i] = (char)(rc & 0xff); + } + + /* + * The read buffer's size is intentionally kept larger than data_len. + * + * This is done so that we are able to test the scenario when the user + * requests ssh_readn() to read more bytes than the number of bytes present + * in the file. + * + * If the read buffer's size is kept same as data_len and if the test + * requests ssh_readn() to read more than data_len bytes starting from file + * offset 0, it will lead to a valgrind failure as in this case, ssh_readn() + * will pass an unallocated memory address in the last call it makes to + * read(). + */ + read_buf = malloc(read_buf_size); + assert_non_null(read_buf); + + file_path = torture_create_temp_file(file_template); + assert_non_null(file_path); + + /* Open a file for reading and writing */ + flags = O_RDWR; +#ifdef _WIN32 + flags |= O_BINARY; +#endif + + fd = open(file_path, flags, 0); + assert_int_not_equal(fd, -1); + + /* Write the data present in the write buffer to the file */ + do { + bytes_written = write(fd, + write_buf + total_bytes_written, + data_len - total_bytes_written); + + if (bytes_written == -1 && errno == EINTR) { + continue; + } + + assert_int_not_equal(bytes_written, -1); + total_bytes_written += bytes_written; + } while (total_bytes_written < data_len); + + /* Seek to the start of the file */ + off = lseek(fd, 0, SEEK_SET); + assert_int_not_equal(off, -1); + + bytes_read = ssh_readn(fd, read_buf, data_len); + assert_int_equal(bytes_read, data_len); + + /* + * Ensure that the data stored in the read buffer is same as the data + * present in the file and not some garbage. + */ + assert_memory_equal(read_buf, write_buf, data_len); + + /* + * Ensure that the file offset is on EOF and requesting to read more leads + * to 0 bytes getting read. + */ + off = lseek(fd, 0, SEEK_CUR); + assert_int_equal(off, data_len); + + bytes_read = ssh_readn(fd, read_buf, data_len); + assert_int_equal(bytes_read, 0); + + /* Try to read more bytes than what are present in the file */ + off = lseek(fd, 0, SEEK_SET); + assert_int_not_equal(off, -1); + + bytes_read = ssh_readn(fd, read_buf, read_buf_size); + assert_int_equal(bytes_read, data_len); + + /* + * Ensure that the data stored in the read buffer is same as the data + * present in the file and not some garbage. + */ + assert_memory_equal(read_buf, write_buf, data_len); + + /* Negative tests start */ + bytes_read = ssh_readn(-2, read_buf, data_len); + assert_int_equal(bytes_read, -1); + + bytes_read = ssh_readn(fd, NULL, data_len); + assert_int_equal(bytes_read, -1); + + bytes_read = ssh_readn(fd, read_buf, 0); + assert_int_equal(bytes_read, -1); + + /* Clean up */ + rc = close(fd); + assert_int_equal(rc, 0); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(file_path); + free(read_buf); + free(write_buf); +} + +static void torture_ssh_writen(void **state) +{ + char *write_buf = NULL, *read_buf = NULL, *file_path = NULL; + const char *file_template = "libssh_torture_ssh_writen_test_XXXXXX"; + + size_t data_len = 10 * 1024 * 1024; + size_t i, total_bytes_read = 0; + ssize_t bytes_written, bytes_read; + off_t off; + int rc, fd, flags; + + (void)state; + + write_buf = malloc(data_len); + assert_non_null(write_buf); + + /* Fill the write buffer with random data */ + for (i = 0; i < data_len; ++i) { + rc = rand(); + write_buf[i] = (char)(rc & 0xff); + } + + read_buf = malloc(data_len); + assert_non_null(read_buf); + + file_path = torture_create_temp_file(file_template); + assert_non_null(file_path); + + /* Open a file for reading and writing */ + flags = O_RDWR; +#ifdef _WIN32 + flags |= O_BINARY; +#endif + + fd = open(file_path, flags, 0); + assert_int_not_equal(fd, -1); + + /* Write the data present in the write buffer to the file */ + bytes_written = ssh_writen(fd, write_buf, data_len); + assert_int_equal(bytes_written, data_len); + + /* + * Ensure that the file offset is incremented by the number of bytes + * written. + */ + off = lseek(fd, 0, SEEK_CUR); + assert_int_equal(off, data_len); + + /* + * Ensure that the data present in the write buffer has been written to the + * file and not some garbage. + */ + off = lseek(fd, 0, SEEK_SET); + assert_int_not_equal(off, -1); + + do { + bytes_read = read(fd, + read_buf + total_bytes_read, + data_len - total_bytes_read); + + if (bytes_read == -1 && errno == EINTR) { + continue; + } + + assert_int_not_equal(bytes_read, -1); + assert_int_not_equal(bytes_read, 0); + + total_bytes_read += bytes_read; + } while (total_bytes_read < data_len); + + assert_memory_equal(write_buf, read_buf, data_len); + + /* Negative tests start */ + bytes_written = ssh_writen(-3, write_buf, data_len); + assert_int_equal(bytes_written, -1); + + bytes_written = ssh_writen(fd, NULL, data_len); + assert_int_equal(bytes_written, -1); + + bytes_written = ssh_writen(fd, write_buf, 0); + assert_int_equal(bytes_written, -1); + + /* Clean up */ + rc = close(fd); + assert_int_equal(rc, 0); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(file_path); + free(read_buf); + free(write_buf); +} + +static void torture_ssh_check_hostname_syntax(void **state) +{ + int rc; + (void)state; + + rc = ssh_check_hostname_syntax("duckduckgo.com"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("www.libssh.org"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("Some-Thing.com"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.ok"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("lavabo-inter.innocentes-manus-meas"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("localhost"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("a"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("a-0.b-b"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("libssh."); + assert_int_equal(rc, SSH_OK); + // IDN + rc = ssh_check_hostname_syntax("xn--bcher-kva.tld"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_check_hostname_syntax(NULL); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(""); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("/"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("@"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("["); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("`"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("{"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("&"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("|"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("\""); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("`"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(" "); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("*the+giant&\"rooks\".c0m"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("!www.libssh.org"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("--.--"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234.a234567890123456789012345678901234567890123456789012345678901234"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh-"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("fe80::9656:d028:8652:66b6"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("."); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(".."); + assert_int_equal(rc, SSH_ERROR); + // IDN non-encoded + rc = ssh_check_hostname_syntax("bücher.tld"); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_ssh_check_username_syntax(void **state) { + int rc; + (void)state; + + rc = ssh_check_username_syntax("username"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice and Bob"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("n4me?"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_check_username_syntax("alice&bob"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("backslash\\"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("&var|()us\" +#endif +#include + +#include "torture.h" +#include "torture_key.h" +#include +#include +#include +#include +#include +#include +#include +#ifdef WITH_SERVER +#include +#define LIBSSH_CUSTOM_BIND_CONFIG_FILE "my_bind_config" +#endif +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#ifdef HAVE_ECC +#define LIBSSH_ECDSA_521_TESTKEY "libssh_testkey.id_ecdsa521" +#endif + +static int setup(void **state) +{ + ssh_session session; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + session->client = 1; + + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +static void torture_options_set_host(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "localhost"); + + /* IPv4 address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "127.1.1.1"); + assert_null(session->opts.username); + + /* IPv6 address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "::1"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "::1"); + assert_null(session->opts.username); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "meditation"); + assert_non_null(session->opts.username); + assert_string_equal(session->opts.username, "guru"); + + /* more @ in uri is OK -- it should go to the username */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "at@login@hostname"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "hostname"); + assert_non_null(session->opts.username); + assert_string_equal(session->opts.username, "at@login"); + + /* disallow metacharacters in the username */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "shallN()tP4ss -@hostname"); + assert_string_equal(ssh_get_error(session), + "Invalid argument in ssh_options_set"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* IPv6 hostnames should work without square braces */ + SAFE_FREE(session->opts.username); + rc = ssh_options_set(session, + SSH_OPTIONS_HOST, + "fd4d:5449:7400:111:626d:3cff:fedf:4d39"); + assert_return_code(rc, errno); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, + "fd4d:5449:7400:111:626d:3cff:fedf:4d39"); + assert_null(session->opts.username); + + /* IPv6 hostnames should work also with square braces */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOST, + "[fd4d:5449:7400:111:626d:3cff:fedf:4d39]"); + assert_return_code(rc, errno); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, + "fd4d:5449:7400:111:626d:3cff:fedf:4d39"); + assert_null(session->opts.username); + + /* IDN need to be in punycode format */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "xn--bcher-kva.tld"); + assert_return_code(rc, errno); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "xn--bcher-kva.tld"); + assert_null(session->opts.username); + + /* IDN in UTF8 won't work */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "bücher.tld"); + assert_string_equal(ssh_get_error(session), + "Invalid argument in ssh_options_set"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +static void torture_options_set_ciphers(void **state) +{ + ssh_session session = *state; + int rc; + + /* Test known ciphers */ + rc = ssh_options_set(session, + SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes192-ctr,aes256-ctr"); + } + + /* Test one unknown cipher */ + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,unknown-crap@example.com,aes256-ctr"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + + /* Test all unknown ciphers */ + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_get_ciphers(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_CIPHERS_C_S, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "aes256-gcm@openssh.com," + "aes256-ctr," + "aes256-cbc," + "aes128-gcm@openssh.com," + "aes128-ctr," + "aes128-cbc"); + } else { + assert_string_equal(value, + "chacha20-poly1305@openssh.com," + "aes256-gcm@openssh.com," + "aes128-gcm@openssh.com," + "aes256-ctr," + "aes192-ctr," + "aes128-ctr"); + } + ssh_string_free_char(value); + + /* Test explicit ciphers */ + rc = ssh_options_set(session, + SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_CIPHERS_C_S, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(value, "aes128-ctr,aes192-ctr,aes256-ctr"); + } + ssh_string_free_char(value); +} + +static void torture_options_set_key_exchange(void **state) +{ + ssh_session session = *state; + int rc; + + /* Test known kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "sntrup761x25519-sha512," + "sntrup761x25519-sha512@openssh.com," + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "sntrup761x25519-sha512," + "sntrup761x25519-sha512@openssh.com," + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + + /* Test one unknown kex */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "diffie-hellman-group16-sha512," + "unknown-crap@example.com," + "diffie-hellman-group18-sha512"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_KEX]); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"); + + /* Test all unknown kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_get_key_exchange(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + const char *exp_value = NULL; + + /* Test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_KEY_EXCHANGE, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + exp_value = "mlkem768nistp256-sha256," +#ifdef HAVE_MLKEM1024 + "mlkem1024nistp384-sha384," +#endif + "ecdh-sha2-nistp256," + "ecdh-sha2-nistp384," + "ecdh-sha2-nistp521," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256," + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"; + } else { + exp_value = "mlkem768x25519-sha256," + "mlkem768nistp256-sha256," +#ifdef HAVE_MLKEM1024 + "mlkem1024nistp384-sha384," +#endif + "sntrup761x25519-sha512," + "sntrup761x25519-sha512@openssh.com," + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,ecdh-sha2-nistp384," + "ecdh-sha2-nistp521,diffie-hellman-group18-sha512," + "diffie-hellman-group16-sha512," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256"; + } + assert_string_equal(value, exp_value); + ssh_string_free_char(value); + + /* Test explicit kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_KEY_EXCHANGE, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(value, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + ssh_string_free_char(value); +} + +static void torture_options_set_hostkey(void **state) +{ + ssh_session session = *state; + int rc; + + /* Test known host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + /* Test one unknown host key */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ecdsa-sha2-nistp521," + "unknown-crap@example.com," + "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp521," + "rsa-sha2-256"); + + /* Test all unknown host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_get_hostkey(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + rc = ssh_options_get(session, SSH_OPTIONS_HOSTKEYS, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdsa-sha2-nistp521-cert-v01@openssh.com," + "ecdsa-sha2-nistp384-cert-v01@openssh.com," + "ecdsa-sha2-nistp256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com," + "rsa-sha2-256-cert-v01@openssh.com," + "ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256," + "rsa-sha2-512," + "rsa-sha2-256"); + } else { + assert_string_equal(value, + "ssh-ed25519-cert-v01@openssh.com," + "ecdsa-sha2-nistp521-cert-v01@openssh.com," + "ecdsa-sha2-nistp384-cert-v01@openssh.com," + "ecdsa-sha2-nistp256-cert-v01@openssh.com," + "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com," + "rsa-sha2-256-cert-v01@openssh.com," + "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256,sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com," + "rsa-sha2-512,rsa-sha2-256"); + } + ssh_string_free_char(value); + + /* Test explicit host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_HOSTKEYS, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(value, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + ssh_string_free_char(value); +} + +static void torture_options_set_pubkey_accepted_types(void **state) +{ + ssh_session session = *state; + int rc; + enum ssh_digest_e type; + + /* Test known public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.pubkey_accepted_types); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.pubkey_accepted_types, + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(session->opts.pubkey_accepted_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + if (!ssh_fips_mode()) { + /* Test one unknown public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,unknown-crap@example.com,ssh-rsa"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.pubkey_accepted_types); + assert_string_equal(session->opts.pubkey_accepted_types, + "ssh-ed25519,ssh-rsa"); + + /* Test all unknown public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); + + /* Test that the option affects the algorithm selection for RSA keys */ + /* simulate the SHA2 extension was negotiated */ + session->extensions = SSH_EXT_SIG_RSA_SHA256; + + /* previous configuration did not list the SHA2 extension algorithms, so + * it should not be used */ + type = ssh_key_type_to_hash(session, SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_DIGEST_SHA1); + } + + /* now, lets allow the signature from SHA2 extension and expect + * it to be used */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "rsa-sha2-256,ssh-rsa"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.pubkey_accepted_types); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.pubkey_accepted_types, + "rsa-sha2-256"); + } else { + assert_string_equal(session->opts.pubkey_accepted_types, + "rsa-sha2-256,ssh-rsa"); + } + + /* Test that the option affects the algorithm selection for RSA keys */ + /* simulate the SHA2 extension was negotiated */ + session->extensions = SSH_EXT_SIG_RSA_SHA256; + + type = ssh_key_type_to_hash(session, SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_DIGEST_SHA256); +} + +static void torture_options_get_pubkey_accepted_types(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test known public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_get(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(value, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + ssh_string_free_char(value); +} + + +static void torture_options_set_macs(void **state) +{ + ssh_session session = *state; + int rc; + + /* Test known MACs */ + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1"); + + /* Test multiple known MACs */ + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "hmac-sha1-etm@openssh.com," + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1,hmac-sha2-256"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + "hmac-sha1-etm@openssh.com," + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1,hmac-sha2-256"); + + /* Test unknown MACs */ + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "unknown-crap@example.com,hmac-sha1-etm@openssh.com," + "unknown@example.com"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + "hmac-sha1-etm@openssh.com"); + + /* Test all unknown MACs */ + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "unknown-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_get_macs(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_HMAC_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com," + "hmac-sha2-256," + "hmac-sha1," + "hmac-sha2-512"); + } else { + assert_string_equal(value, + "hmac-sha2-256-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com," + "hmac-sha2-256," + "hmac-sha2-512"); + } + ssh_string_free_char(value); + + /* Test known MACs */ + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_HMAC_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + assert_string_equal(value, "hmac-sha1"); + ssh_string_free_char(value); +} + +static void torture_options_set_compression(void **state) +{ + ssh_session session = *state; + int rc; + const char *known_value; + const char *multiple; + +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + known_value = "none"; + multiple = "none,squeeze"; + } else { + known_value = "zlib"; + multiple = "zlib,squeeze"; + } +#else + known_value = "none"; + multiple = "none,squeeze"; +#endif + + /* Test known compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, known_value); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + known_value); + + /* Test multiple known compression */ + if (!ssh_fips_mode()) { + rc = ssh_options_set(session, + SSH_OPTIONS_COMPRESSION_S_C, + "none,zlib@openssh.com"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none,zlib@openssh.com"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], "none"); +#endif + } + + /* Test unknown compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, multiple); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + known_value); + + /* Test all unknown compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "squeeze"); + assert_false(rc == 0); +} + +static void torture_options_get_compression(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + const char *test_value = NULL; + +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + test_value = "none"; + } else { + test_value = "zlib@openssh.com"; + } +#else + test_value = "none"; +#endif + + /* test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_COMPRESSION_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); +#ifdef WITH_ZLIB + assert_string_equal(value, "none,zlib@openssh.com"); +#else + assert_string_equal(value, "none"); +#endif + ssh_string_free_char(value); + + /* Test known compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, test_value); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_COMPRESSION_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + assert_string_equal(value, test_value); + ssh_string_free_char(value); +} + +static void torture_options_get_host(void **state) +{ + ssh_session session = *state; + int rc; + char* host = NULL; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + assert_string_equal(session->opts.host, "localhost"); + + assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host)); + + assert_string_equal(host, "localhost"); + ssh_string_free_char(host); +} + +static void torture_options_set_port(void **state) +{ + ssh_session session = *state; + int rc; + unsigned int port = 42; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &port); + assert_true(rc == 0); + assert_true(session->opts.port == port); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "23"); + assert_true(rc == 0); + assert_true(session->opts.port == 23); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "five"); + assert_true(rc == -1); + assert_int_not_equal(session->opts.port, 0); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, NULL); + assert_true(rc == -1); +} + +static void torture_options_get_port(void **state) +{ + ssh_session session = *state; + unsigned int given_port = 1234; + unsigned int port_container; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &given_port); + assert_true(rc == 0); + rc = ssh_options_get_port(session, &port_container); + assert_true(rc == 0); + assert_int_equal(port_container, 1234); +} + +static void torture_options_get_user(void **state) +{ + ssh_session session = *state; + char *user = NULL; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "magicaltrevor"); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_int_equal(rc, SSH_OK); + assert_non_null(user); + assert_string_equal(user, "magicaltrevor"); + ssh_string_free_char(user); +} + +static void torture_options_set_fd(void **state) +{ + ssh_session session = *state; + socket_t fd = 42; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_FD, &fd); + assert_true(rc == 0); + assert_true(session->opts.fd == fd); + + rc = ssh_options_set(session, SSH_OPTIONS_FD, NULL); + assert_true(rc == SSH_ERROR); + assert_true(session->opts.fd == SSH_INVALID_SOCKET); +} + +static void torture_options_set_user(void **state) +{ + ssh_session session = *state; + int rc; +#ifndef _WIN32 +# ifndef NSS_BUFLEN_PASSWD +# define NSS_BUFLEN_PASSWD 4096 +# endif /* NSS_BUFLEN_PASSWD */ + struct passwd pwd; + struct passwd *pwdbuf; + char buf[NSS_BUFLEN_PASSWD]; + + /* get local username */ + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + assert_true(rc == 0); +#endif /* _WIN32 */ + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "&shallN()tP4ss"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "guru"); + assert_true(rc == 0); + assert_string_equal(session->opts.username, "guru"); + + + rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL); + assert_true(rc == 0); + +#ifndef _WIN32 + assert_string_equal(session->opts.username, pwd.pw_name); +#endif +} + +static void torture_options_set_identity(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); + assert_true(rc == 0); + assert_string_equal(session->opts.identity_non_exp->root->data, + "identity1"); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); + assert_true(rc == 0); + assert_string_equal(session->opts.identity_non_exp->root->data, + "identity2"); + assert_string_equal(session->opts.identity_non_exp->root->next->data, + "identity1"); +} + +static void torture_options_get_identity(void **state) +{ + ssh_session session = *state; + char *identity = NULL; + int rc; + + /* This adds an identity to the head of the list and returns */ + rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); + assert_true(rc == 0); + rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_non_null(identity); + assert_string_equal(identity, "identity1"); + SAFE_FREE(identity); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->opts.identity_non_exp->root->data, + "identity2"); + rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_non_null(identity); + assert_string_equal(identity, "identity2"); + ssh_string_free_char(identity); + + /* Iterate over all of the identities */ + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "identity2"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "identity1"); + SAFE_FREE(identity); + + /* here are the default identities */ + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ed25519"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ecdsa"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_rsa"); + ssh_string_free_char(identity); + +#ifdef WITH_FIDO2 + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ed25519_sk"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ecdsa_sk"); + ssh_string_free_char(identity); +#endif /* WITH_FIDO2 */ + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_EOF); +} + +static void torture_options_set_global_knownhosts(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "/etc/libssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.global_knownhosts, + "/etc/libssh/known_hosts"); +} + +static void torture_options_get_global_knownhosts(void **state) +{ + ssh_session session = *state; + char *str = NULL; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "/etc/libssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.global_knownhosts, + "/etc/libssh/known_hosts"); + + + rc = ssh_options_get(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, &str); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.global_knownhosts, + "/etc/libssh/known_hosts"); + + SSH_STRING_FREE_CHAR(str); +} + +static void torture_options_set_knownhosts(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "/home/libssh/.ssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.knownhosts, + "/home/libssh/.ssh/known_hosts"); + + /* The NULL value should not crash the libssh */ + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, NULL); + assert_ssh_return_code(session, rc); + assert_null(session->opts.knownhosts); + + /* ssh_options_apply() should set the path to correct value */ + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.knownhosts); +} + +static void torture_options_get_knownhosts(void **state) +{ + ssh_session session = *state; + char *str = NULL; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "/home/libssh/.ssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.knownhosts, + "/home/libssh/.ssh/known_hosts"); + + + rc = ssh_options_get(session, SSH_OPTIONS_KNOWNHOSTS, &str); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.knownhosts, + "/home/libssh/.ssh/known_hosts"); + + SSH_STRING_FREE_CHAR(str); +} + +static void torture_options_proxycommand(void **state) { + ssh_session session = *state; + int rc; + + /* Enable ProxyCommand */ + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "ssh -q -A -X -W %h:%p JUMPHOST"); + assert_int_equal(rc, 0); + + assert_string_equal(session->opts.ProxyCommand, "ssh -q -A -X -W %h:%p JUMPHOST"); + + /* Disable ProxyCommand */ + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "none"); + assert_int_equal(rc, 0); + + assert_null(session->opts.ProxyCommand); +} + +static void torture_options_control_master (void **state) +{ + ssh_session session = *state; + int rc, val = SSH_CONTROL_MASTER_NO; + + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_NO); + + val = SSH_CONTROL_MASTER_AUTO; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_AUTO); + + val = SSH_CONTROL_MASTER_YES; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_YES); + + val = SSH_CONTROL_MASTER_ASK; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_ASK); + + val = SSH_CONTROL_MASTER_AUTOASK; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_AUTOASK); + + val = 255; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_options_control_path(void **state) +{ + ssh_session session = *state; + char *str = NULL; + int rc; + + /* Set Control Path */ + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_PATH, + "/tmp/ssh-%r@%h:%p"); + assert_int_equal(rc, 0); + + assert_string_equal(session->opts.control_path, "/tmp/ssh-%r@%h:%p"); + + rc = ssh_options_get(session, SSH_OPTIONS_CONTROL_PATH, &str); + assert_int_equal(rc, 0); + assert_string_equal(str, "/tmp/ssh-%r@%h:%p"); + + /* Disable Multiplexing */ + rc = ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, "none"); + assert_int_equal(rc, 0); + + assert_null(session->opts.control_path); + SSH_STRING_FREE_CHAR(str); +} + +static void torture_options_config_host(void **state) +{ + ssh_session session = *state; + FILE *config = NULL; + + /* create a new config file */ + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Host testhost1\nPort 42\n" + "Host testhost2,testhost3\nPort 43\n" + "Host testhost4 testhost5\nPort 44\n", + config); + fclose(config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1"); + ssh_options_parse_config(session, "test_config"); + + assert_int_equal(session->opts.port, 42); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost2"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 43); + + session->opts.port = 0; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost3"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 43); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost4"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 44); + + session->opts.port = 0; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost5"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 44); + + unlink("test_config"); +} + +static void torture_options_config_match(void **state) +{ + ssh_session session = *state; + char *localuser = NULL; + FILE *config = NULL; + int rv; + + /* Required for options_parse_config() */ + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1"); + + /* The Match keyword requires argument */ + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_OK); + + /* The Match all keyword needs to be the only one (start) */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match all host local\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match all keyword needs to be the only one (end) */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match host local all\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match host keyword requires an argument */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match host\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match user keyword requires an argument */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match user\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match canonical keyword is the same as match all */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match canonical\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_OK); + assert_int_equal(session->opts.port, 33); + + session->opts.port = 0; + + /* The Match originalhost keyword is ignored */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match originalhost origin\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + + session->opts.port = 0; + + /* The Match localuser keyword */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match localuser ", config); + localuser = ssh_get_local_username(); + assert_non_null(localuser); + fputs(localuser, config); + ssh_string_free_char(localuser); + fputs("\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 33); + + session->opts.port = 0; + + /* The Match exec keyword */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec true\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_int_equal(session->opts.port, 34); +#else + assert_int_equal(session->opts.port, 33); +#endif + + session->opts.port = 0; + + /* Commands containing whitespace characters must be quoted. */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec \"true 1\"\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_int_equal(session->opts.port, 34); +#else + assert_int_equal(session->opts.port, 33); +#endif + + session->opts.port = 0; + + unlink("test_config"); +} + +static void torture_options_config_match_multi(void **state) +{ + ssh_session session = *state; + FILE *config = NULL; + struct stat sb; + int rv; + + /* Required for options_parse_config() */ + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1"); + + /* Exec is not executed when it can not be matched */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match host wronghost exec \"touch test_config_wrong\"\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + assert_int_equal(stat("test_config_wrong", &sb), -1); + + session->opts.port = 0; + + /* After matching exec, other conditions can be used */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec true host testhost1\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_int_equal(session->opts.port, 34); +#else + assert_int_equal(session->opts.port, 33); +#endif + + /* After matching exec, other conditions can be used */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec true host otherhost\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + + unlink("test_config"); +} + +static void torture_options_copy(void **state) +{ + ssh_session session = *state, new = NULL; + struct ssh_iterator *it = NULL, *it2 = NULL; + FILE *config = NULL; + int i, level = 9; + int rv; + + /* Required for options_parse_config() */ + ssh_options_set(session, SSH_OPTIONS_HOST, "example"); + + /* Impossible to set through the configuration */ + rv = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_LEVEL, &level); + assert_ssh_return_code(session, rv); + level = 1; + rv = ssh_options_set(session, SSH_OPTIONS_NODELAY, &level); + assert_ssh_return_code(session, rv); + + /* The Match keyword requires argument */ + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("IdentityFile ~/.ssh/id_ecdsa\n" + "IdentityFile ~/.ssh/my_rsa\n" + "CertificateFile ~/.ssh/my_rsa-cert.pub\n" + "CertificateFile ~/.ssh/id_ecdsa-cert.pub\n" + "User tester\n" + "Hostname example.com\n" + "BindAddress 127.0.0.2\n" + "GlobalKnownHostsFile /etc/ssh/known_hosts2\n" + "UserKnownHostsFile ~/.ssh/known_hosts2\n" + "KexAlgorithms curve25519-sha256,sntrup761x25519-sha512@openssh.com,ecdh-sha2-nistp521\n" + "Ciphers aes256-ctr\n" + "MACs hmac-sha2-256\n" + "HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" + "Compression yes\n" + "PubkeyAcceptedAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" + "ProxyCommand nc 127.0.0.10 22\n" + "ControlMaster ask\n" + "ControlPath /tmp/ssh-%r@%h:%p\n" + /* ops.custombanner */ + "ConnectTimeout 42\n" + "Port 222\n" + "StrictHostKeyChecking no\n" + "GSSAPIServerIdentity my.example.com\n" + "GSSAPIClientIdentity home.sweet\n" + "GSSAPIDelegateCredentials yes\n" + "PubkeyAuthentication yes\n" /* sets flags */ + "GSSAPIAuthentication no\n" /* sets flags */ + "AddressFamily inet6\n" + "", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + + rv = ssh_options_copy(session, &new); + assert_ssh_return_code(session, rv); + assert_non_null(new); + + /* Check the identities match */ + it = ssh_list_get_iterator(session->opts.identity_non_exp); + assert_non_null(it); + it2 = ssh_list_get_iterator(new->opts.identity_non_exp); + assert_non_null(it2); + while (it != NULL && it2 != NULL) { + assert_string_equal(it->data, it2->data); + it = it->next; + it2 = it2->next; + } + assert_null(it); + assert_null(it2); + + /* Check the certificates match */ + it = ssh_list_get_iterator(session->opts.certificate_non_exp); + assert_non_null(it); + it2 = ssh_list_get_iterator(new->opts.certificate_non_exp); + assert_non_null(it2); + while (it != NULL && it2 != NULL) { + assert_string_equal(it->data, it2->data); + it = it->next; + it2 = it2->next; + } + assert_null(it); + assert_null(it2); + + assert_string_equal(session->opts.username, new->opts.username); + assert_string_equal(session->opts.host, new->opts.host); + assert_string_equal(session->opts.bindaddr, new->opts.bindaddr); + assert_string_equal(session->opts.sshdir, new->opts.sshdir); + assert_string_equal(session->opts.knownhosts, new->opts.knownhosts); + assert_string_equal(session->opts.global_knownhosts, + new->opts.global_knownhosts); + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (session->opts.wanted_methods[i] == NULL) { + assert_null(new->opts.wanted_methods[i]); + } else { + assert_string_equal(session->opts.wanted_methods[i], + new->opts.wanted_methods[i]); + } + } + assert_string_equal(session->opts.pubkey_accepted_types, + new->opts.pubkey_accepted_types); + assert_string_equal(session->opts.ProxyCommand, new->opts.ProxyCommand); + assert_null(new->opts.control_path); + /* TODO custombanner */ + assert_int_equal(session->opts.timeout, new->opts.timeout); + assert_int_equal(session->opts.timeout_usec, new->opts.timeout_usec); + assert_int_equal(session->opts.port, new->opts.port); + assert_int_equal(session->opts.control_master, new->opts.control_master); + assert_int_equal(session->opts.StrictHostKeyChecking, + new->opts.StrictHostKeyChecking); + assert_int_equal(session->opts.compressionlevel, + new->opts.compressionlevel); + assert_string_equal(session->opts.gss_server_identity, + new->opts.gss_server_identity); + assert_string_equal(session->opts.gss_client_identity, + new->opts.gss_client_identity); + assert_int_equal(session->opts.gss_delegate_creds, + new->opts.gss_delegate_creds); + assert_int_equal(session->opts.flags, new->opts.flags); + assert_int_equal(session->opts.nodelay, new->opts.nodelay); + assert_true(session->opts.config_processed == new->opts.config_processed); + assert_memory_equal(session->opts.options_seen, new->opts.options_seen, + sizeof(session->opts.options_seen)); + assert_int_equal(session->opts.address_family, new->opts.address_family); + + ssh_free(new); + + /* test if ssh_options_apply was called before ssh_options_copy + * the opts.identity list gets copied (percent expanded list) */ + rv = ssh_options_apply(session); + assert_ssh_return_code(session, rv); + + rv = ssh_options_copy(session, &new); + assert_ssh_return_code(session, rv); + assert_non_null(new); + + it = ssh_list_get_iterator(session->opts.identity_non_exp); + assert_null(it); + it2 = ssh_list_get_iterator(new->opts.identity_non_exp); + assert_null(it2); + + it = ssh_list_get_iterator(session->opts.identity); + assert_non_null(it); + it2 = ssh_list_get_iterator(new->opts.identity); + assert_non_null(it2); + while (it != NULL && it2 != NULL) { + assert_string_equal(it->data, it2->data); + it = it->next; + it2 = it2->next; + } + assert_null(it); + assert_null(it2); + + ssh_free(new); +} + +#define EXECUTABLE_NAME "test-exec" +static void torture_options_getopt(void **state) +{ + ssh_session session = *state; + int rc; + int previous_level, new_level; + const char *argv[] = {EXECUTABLE_NAME, "-l", "username", "-p", "222", + "-vv", "-v", "-r", "-c", "aes128-ctr", + "-i", "id_rsa", "-C", "-2", "-1", NULL}; + int argc = sizeof(argv)/sizeof(char *) - 1; + previous_level = ssh_get_log_level(); + + /* Test with all the supported options */ + rc = ssh_options_getopt(session, &argc, (char **)argv); +#ifdef _MSC_VER + UNUSED_VAR(new_level); + + /* Not supported in windows */ + assert_ssh_return_code_equal(session, rc, -1); +#else + assert_ssh_return_code(session, rc); + + /* Restore the log level to previous value first */ + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 3); /* 2 + 1 -v's */ + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); + + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.username, "username"); + assert_int_equal(session->opts.port, 222); + /* The -r (usersa) is noop */ + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr"); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr"); + assert_string_equal(session->opts.identity_non_exp->root->data, "id_rsa"); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); +#endif + /* -1 and -2 are noop */ + + + /* It should ignore unknown arguments */ + argv[1] = "-F"; + argv[2] = "config_file"; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 3); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "-F"); + assert_string_equal(argv[2], "config_file"); + + + /* It should not mess with unknown arguments order */ + argv[1] = "-F"; + argv[2] = "config_file"; + argv[3] = "-M"; + argv[4] = "hmac-sha1"; + argv[5] = "-X"; + argv[6] = NULL; + argc = 6; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 6); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "-F"); + assert_string_equal(argv[2], "config_file"); + assert_string_equal(argv[3], "-M"); + assert_string_equal(argv[4], "hmac-sha1"); + assert_string_equal(argv[5], "-X"); + + + /* Trailing arguments should be passed as they are */ + argv[1] = "-F"; + argv[2] = "config_file"; + argv[3] = "-M"; + argv[4] = "hmac-sha1"; + argv[5] = "example.com"; + argv[6] = NULL; + argc = 6; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 6); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "-F"); + assert_string_equal(argv[2], "config_file"); + assert_string_equal(argv[3], "-M"); + assert_string_equal(argv[4], "hmac-sha1"); + assert_string_equal(argv[5], "example.com"); + + /* Corner case: only one argument */ + argv[1] = "-C"; + argv[2] = NULL; + argc = 2; + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); + assert_ssh_return_code(session, rc); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none,zlib@openssh.com"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none,zlib@openssh.com"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); +#endif + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 1); + assert_string_equal(argv[0], EXECUTABLE_NAME); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); +#endif + + /* Corner case: only hostname is not parsed */ + argv[1] = "example.com"; + argv[2] = NULL; + argc = 2; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 2); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "example.com"); + + /* Corner case: no arguments */ + argv[1] = NULL; + argc = 1; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 1); + assert_string_equal(argv[0], EXECUTABLE_NAME); + +#endif /* _NSC_VER */ +} + +static void torture_options_getopt_o_option(void **state) +{ +#ifndef _MSC_VER + ssh_session session = *state; + int rc; + enum ssh_config_opcode_e opcode = + ssh_config_get_opcode((char *)"compression"); + const char *argv[6] = {EXECUTABLE_NAME, "-o", "Compression nah", NULL}; + int argc = 3; + + // Test: -o with invalid value (e.g., "-o Compression nah") + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + session->opts.options_seen[opcode] = 0; + + // Test: -o with valid value (e.g., "-o Compression yes") + argv[1] = "-o"; + argv[2] = "compression yes"; + argv[3] = NULL; + argc = 3; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(session->opts.options_seen[opcode], 1); + +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], "none"); +#endif + + // Test: -o with missing value (e.g., "-o =") + argv[1] = "-o"; + argv[2] = "="; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + + // Test: -o with only option name, no value (e.g., "-o Compression") + session->opts.options_seen[opcode] = 0; + argv[1] = "-o"; + argv[2] = "Compression"; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: -o with empty string (e.g., "-o ") + argv[1] = "-o"; + argv[2] = ""; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: -o with unsupported option on the cli + argv[1] = "-o"; + argv[2] = "match *"; + argv[3] = NULL; + argc = 3; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: multiple -o options together, one invalid + session->opts.options_seen[opcode] = 0; + argv[1] = "-o"; + argv[2] = "compression yes"; + argv[3] = "-o"; + argv[4] = "enablesshkeysign yes"; + argv[5] = NULL; + argc = 5; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: multiple -o options together, all valid + session->opts.options_seen[opcode] = 0; + argv[1] = "-o"; + argv[2] = "compression no"; + argv[3] = "-o"; + argv[4] = "rekeylimit 1G 1h"; + argv[5] = NULL; + argc = 5; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + + opcode = ssh_config_get_opcode((char *)"compression"); + assert_int_equal(session->opts.options_seen[opcode], 1); + + opcode = ssh_config_get_opcode((char *)"rekeylimit"); + assert_int_equal(session->opts.options_seen[opcode], 1); +#endif /* _MSC_VER */ +} + +static void torture_options_plus_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + char *awaited; + size_t alg_len, algs_len; + + if (ssh_fips_mode()) { + alg = ",rsa-sha2-512-cert-v01@openssh.com"; + algs = ",rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = ",ssh-rsa"; + algs = ",ssh-rsa,ssh-rsa-cert-v01@openssh.com"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + /* in fips mode, the default list is the available list, which means + * we can't append anything because everything enabled is already + * included */ + if (ssh_fips_mode()) { + awaited = strdup(def_host_alg); + assert_non_null(awaited); + } else { + awaited = calloc(strlen(def_host_alg) + alg_len + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + memcpy(awaited+strlen(def_host_alg), alg, alg_len); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+ssh-rsa"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + if (!ssh_fips_mode()) { + /* different algorithm list is used here */ + free(awaited); + + awaited = calloc(strlen(def_host_alg) + algs_len + 1, 1); + assert_non_null(awaited); + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + memcpy(awaited+strlen(def_host_alg), algs, algs_len); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "+rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "+ssh-rsa,ssh-rsa-cert-v01@openssh.com"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, NULL); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + free(awaited); +} + +static void torture_options_minus_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + char *awaited, *p; + size_t alg_len, algs_len; + + if (ssh_fips_mode()) { + alg = "rsa-sha2-512-cert-v01@openssh.com,"; + algs = "rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521,"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = "ssh-ed25519,"; + algs = "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + awaited = calloc(strlen(def_host_alg) + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + p = strstr(awaited, alg); + assert_non_null(p); + memmove(p, p+alg_len, strlen(p + alg_len) + 1); + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-ssh-ed25519"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + p = strstr(awaited, algs); + assert_non_null(p); + memmove(p, p+algs_len, strlen(p + algs_len) + 1); + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + free(awaited); +} + +static void torture_options_caret_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + size_t alg_len, algs_len; + char *awaited, *p; + + if (ssh_fips_mode()) { + alg = "rsa-sha2-512-cert-v01@openssh.com,"; + algs = "rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521,"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = "ssh-rsa,"; + algs = "ssh-rsa,ssh-rsa-cert-v01@openssh.com,"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + awaited = calloc(strlen(def_host_alg) + alg_len + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, alg, alg_len); + memcpy(awaited+alg_len, def_host_alg, strlen(def_host_alg)); + if (ssh_fips_mode()) { + p = strstr(awaited, alg); + /* look for second occurrence */ + p = strstr(p+1, algs); + memmove(p, p+alg_len, strlen(p + alg_len) + 1); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^ssh-rsa"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + /* different algorithm list is used here */ + free(awaited); + + awaited = calloc(strlen(def_host_alg) + algs_len + 1, 1); + assert_non_null(awaited); + memcpy(awaited, algs, algs_len); + memcpy(awaited+algs_len, def_host_alg, strlen(def_host_alg)); + if (ssh_fips_mode()) { + p = strstr(awaited, algs); + /* look for second occurrence */ + p = strstr(p+1, algs); + memmove(p, p+algs_len, strlen(p + algs_len) + 1); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "^rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "^ssh-rsa,ssh-rsa-cert-v01@openssh.com"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + free(awaited); +} + +static void torture_options_apply (void **state) +{ + ssh_session session = *state; + struct ssh_list *awaited_list = NULL; + struct ssh_iterator *it1 = NULL, *it2 = NULL; + char *id = NULL; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "%%d/.ssh/known_hosts"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "/etc/%%u/libssh/known_hosts"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_PROXYCOMMAND, + "exec echo \"Hello libssh %%d!\""); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "%%d/do_not_expand"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + + /* check that the values got expanded */ + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND); + assert_true(ssh_list_count(session->opts.identity_non_exp) == 0); + assert_true(ssh_list_count(session->opts.identity) > 0); + + /* should not change anything calling it again */ + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + + /* check that the expansion was done only once */ + assert_string_equal(session->opts.knownhosts, "%d/.ssh/known_hosts"); + assert_string_equal(session->opts.global_knownhosts, + "/etc/%u/libssh/known_hosts"); + /* no exec should be added if there already is one */ + assert_string_equal(session->opts.ProxyCommand, + "exec echo \"Hello libssh %d!\""); + assert_string_equal(session->opts.identity->root->data, + "%d/do_not_expand"); + + /* apply should keep the freshest setting */ + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "hello there"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "lorem ipsum"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_PROXYCOMMAND, + "mission_impossible"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "007"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "3"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "2"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "1"); + assert_ssh_return_code(session, rc); + + /* check that flags show need of escape expansion */ + assert_false(session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS); + assert_false(session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS); + assert_false(session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND); + assert_false(ssh_list_count(session->opts.identity_non_exp) == 0); + + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + + /* check that the values got expanded */ + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND); + assert_true(ssh_list_count(session->opts.identity_non_exp) == 0); + + assert_string_equal(session->opts.knownhosts, "hello there"); + assert_string_equal(session->opts.global_knownhosts, "lorem ipsum"); + /* check that the "exec " was added at the beginning */ + assert_string_equal(session->opts.ProxyCommand, "exec mission_impossible"); + assert_string_equal(session->opts.identity->root->data, "1"); + + /* check the order of the identity files after double expansion */ + awaited_list = ssh_list_new(); + /* append the new data in order */ + id = strdup("1"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("2"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("3"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("007"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("%d/do_not_expand"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + /* append the defaults; this list is copied from ssh_new@src/session.c */ + id = ssh_path_expand_escape(session, "%d/.ssh/id_ed25519"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#ifdef HAVE_ECC + id = ssh_path_expand_escape(session, "%d/.ssh/id_ecdsa"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#endif + id = ssh_path_expand_escape(session, "%d/.ssh/id_rsa"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#ifdef WITH_FIDO2 + /* Add security key identities */ + id = ssh_path_expand_escape(session, "%d/.ssh/id_ed25519_sk"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + +#ifdef HAVE_ECC + id = ssh_path_expand_escape(session, "%d/.ssh/id_ecdsa_sk"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#endif /* HAVE_ECC */ +#endif /* WITH_FIDO2 */ + + assert_int_equal(ssh_list_count(awaited_list), + ssh_list_count(session->opts.identity)); + + it1 = ssh_list_get_iterator(awaited_list); + assert_non_null(it1); + it2 = ssh_list_get_iterator(session->opts.identity); + assert_non_null(it2); + while (it1 != NULL && it2 != NULL) { + assert_string_equal(it1->data, it2->data); + + free((void*)it1->data); + it1 = it1->next; + it2 = it2->next; + } + assert_null(it1); + assert_null(it2); + + ssh_list_free(awaited_list); +} + +static void torture_options_set_verbosity (void **state) +{ + ssh_session session = *state; + int rc, new_level; + + rc = ssh_options_set(session, + SSH_OPTIONS_LOG_VERBOSITY_STR, + "3"); + assert_int_equal(rc, SSH_OK); + new_level = ssh_get_log_level(); + assert_int_equal(new_level, SSH_LOG_PACKET); + + rc = ssh_options_set(session, + SSH_OPTIONS_LOG_VERBOSITY_STR, + "datsun"); + assert_int_equal(rc, -1); + new_level = ssh_get_log_level(); + assert_int_not_equal(new_level, 0); +} + +static void torture_options_set_rsa_min_size(void **state) +{ + ssh_session session = *state; + int min_allowed = RSA_MIN_KEY_SIZE, key_size, rc; + + /* Check that passing NULL leads to failure */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, NULL); + assert_int_equal(rc, -1); + + /* + * Check that supplying a value less than the allowed minimum leads + * to failure + */ + key_size = min_allowed - 2; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying a negative value leads to failure */ + key_size = -10; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying 0 succeeds (used to revert to default) */ + key_size = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); + + /* Check that supplying allowed minimum succeeds */ + key_size = min_allowed; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); + + /* Check that supplying a value greater than allowed minimum succeeds */ + key_size = min_allowed + 10; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); +} + +#ifdef WITH_SERVER +const char template[] = "temp_dir_XXXXXX"; + +struct bind_st { + char *cwd; + char *temp_dir; + ssh_bind bind; +}; + +static int ssh_bind_setup_files(void **state) +{ + struct bind_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct bind_st *)malloc(sizeof(struct bind_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + /* For ed25519 the test keys are not available in legacy PEM format. Using + * the new OpenSSH format for all algorithms */ + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + + torture_write_file(LIBSSH_ED25519_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); +#ifdef HAVE_ECC + torture_write_file(LIBSSH_ECDSA_521_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); +#endif + torture_write_file(LIBSSH_CUSTOM_BIND_CONFIG_FILE, + "Port 42\n"); + return 0; +} + + +/* sshbind options */ +static int sshbind_setup(void **state) +{ + int rc; + struct bind_st *test_state = NULL; + + rc = ssh_bind_setup_files((void **)&test_state); + assert_int_equal(rc, 0); + assert_non_null(test_state); + + test_state->bind = ssh_bind_new(); + assert_non_null(test_state->bind); + + *state = test_state; + + return 0; +} + +static int sshbind_teardown(void **state) +{ + struct bind_st *test_state = NULL; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + assert_non_null(test_state->bind); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + ssh_bind_free(test_state->bind); + SAFE_FREE(test_state); + + return 0; +} + +static void +torture_bind_options_import_key(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + const char *base64_key; + ssh_key key = ssh_key_new(); + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* set null */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, NULL); + assert_int_equal(rc, -1); + /* set invalid key */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); + + /* set ed25519 key */ + base64_key = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + assert_null(key); + } else { + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + } + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, 0); + } + + /* set rsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, 0); +#ifdef HAVE_ECC + /* set ecdsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, 0); +#endif +} + +static void +torture_bind_options_import_key_str(void **state) +{ + struct bind_st *test_state = NULL; + ssh_bind bind = NULL; + int rc; + const char *base64_key = ""; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* set null */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, NULL); + assert_int_equal(rc, -1); + /* set invalid key */ + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, -1); + + /* set ed25519 key */ + base64_key = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, 0); + } + + /* set rsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, 0); +#ifdef HAVE_ECC + /* set ecdsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, 0); +#endif +} + +static void torture_bind_options_hostkey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test RSA key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_RSA_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); + + /* Test ED25519 key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_ED25519_TESTKEY); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + assert_null(bind->ed25519key); + } else { + assert_int_equal(rc, 0); + assert_non_null(bind->ed25519key); + assert_string_equal(bind->ed25519key, LIBSSH_ED25519_TESTKEY); + } + +#ifdef HAVE_ECC + /* Test ECDSA key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_ECDSA_521_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +#endif +} + +static void torture_bind_options_bindaddr(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + const char *address = "127.0.0.1"; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, address); + assert_int_equal(rc, 0); + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, address); +} + +static void torture_bind_options_bindport(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + unsigned int given_port = 1234; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT, &given_port); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 1234); +} + +static void torture_bind_options_bindport_str(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "23"); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 23); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "twentythree"); + assert_int_equal(rc, -1); + assert_int_not_equal(bind->bindport, 0); +} + +static void torture_bind_options_log_verbosity(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int verbosity = SSH_LOG_PACKET; + int previous_level, new_level; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, verbosity); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_options_log_verbosity_str(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, SSH_LOG_PACKET); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "verbosity"); + assert_int_equal(rc, -1); + new_level = ssh_get_log_level(); + assert_int_not_equal(new_level, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_options_rsakey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_RSA_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); +} + +static void torture_bind_options_set_rsa_min_size(void **state) +{ + struct bind_st *test_state = NULL; + ssh_bind bind = NULL; + int rc, min_allowed = RSA_MIN_KEY_SIZE, key_size; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Check that passing NULL leads to failure */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, NULL); + assert_int_equal(rc, -1); + + /* + * Check that supplying a value less than the allowed minimum leads + * to failure + */ + key_size = min_allowed - 2; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying a negative value leads to failure */ + key_size = -10; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying 0 succeeds (used to revert to default) */ + key_size = 0; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); + + /* Check that supplying allowed minimum succeeds */ + key_size = min_allowed; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); + + /* Check that supplying a value greater than allowed minimum succeeds */ + key_size = min_allowed + 10; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); +} + +#ifdef HAVE_ECC +static void torture_bind_options_ecdsakey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_ECDSA_521_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +} +#endif + +static void torture_bind_options_banner(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + const char *banner = "This is the new banner"; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_BANNER, + banner); + assert_int_equal(rc, 0); + assert_non_null(bind->banner); + assert_string_equal(bind->banner, banner); +} + +static void torture_bind_options_set_ciphers(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + assert_non_null(bind->wanted_methods); + + /* Test known ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes192-ctr,aes256-ctr"); + } + + /* Test one unknown cipher */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, + "aes128-ctr,unknown-crap@example.com,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + + /* Test all unknown ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, + "unknown-crap@example.com,more-crap@example.com"); + assert_int_not_equal(rc, 0); + + /* Test known ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr,aes192-ctr,aes256-ctr"); + } + + /* Test one unknown cipher */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, + "aes128-ctr,unknown-crap@example.com,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr,aes256-ctr"); + + /* Test all unknown ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, + "unknown-crap@example.com,more-crap@example.com"); + assert_int_not_equal(rc, 0); +} + +static void torture_bind_options_set_key_exchange(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + assert_non_null(bind->wanted_methods); + + /* Test known kexes */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + + /* Test one unknown kex */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + "diffie-hellman-group16-sha512," + "unknown-crap@example.com," + "diffie-hellman-group18-sha512"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + assert_string_equal(bind->wanted_methods[SSH_KEX], + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"); + + /* Test all unknown kexes */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + "unknown-crap@example.com,more-crap@example.com"); + assert_int_not_equal(rc, 0); +} + +static void torture_bind_options_set_macs(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + assert_non_null(bind->wanted_methods); + + /* Test known MACs */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], "hmac-sha1"); + + /* Test multiple known MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "hmac-sha1,hmac-sha2-256"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], + "hmac-sha1,hmac-sha2-256"); + + /* Test unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "unknown-crap@example.com," + "hmac-sha1,unknown@example.com"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], "hmac-sha1"); + + /* Test all unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "unknown-crap@example.com"); + assert_int_not_equal(rc, 0); + + /* Test known MACs */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, "hmac-sha1"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], "hmac-sha1"); + + /* Test multiple known MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "hmac-sha1,hmac-sha2-256"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], + "hmac-sha1,hmac-sha2-256"); + + /* Test unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "unknown-crap@example.com," + "hmac-sha1,unknown@example.com"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], "hmac-sha1"); + + /* Test all unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "unknown-crap@example.com"); + assert_int_not_equal(rc, 0); +} + +static void torture_bind_options_parse_config(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + char *cwd = NULL; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_CONFIG_DIR, + (const char *)cwd); + assert_int_equal(rc, 0); + assert_non_null(bind->config_dir); + assert_string_equal(bind->config_dir, cwd); + + rc = ssh_bind_options_parse_config(bind, + "%d/" LIBSSH_CUSTOM_BIND_CONFIG_FILE); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 42); + + SAFE_FREE(cwd); +} + +static void torture_bind_options_config_dir(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + const char *new_dir = "/new/dir/"; + const char *replacement_dir = "/replacement/dir/"; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_CONFIG_DIR, + new_dir); + assert_int_equal(rc, 0); + assert_non_null(bind->config_dir); + assert_string_equal(bind->config_dir, new_dir); + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_CONFIG_DIR, + replacement_dir); + assert_int_equal(rc, 0); + assert_non_null(bind->config_dir); + assert_string_equal(bind->config_dir, replacement_dir); +} + +static void torture_bind_options_set_pubkey_accepted_key_types(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test known Pubkey Types */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + SAFE_FREE(bind->pubkey_accepted_key_types); + + /* Test with some unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384,unknown-type,rsa-sha2-256"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, + "ecdsa-sha2-nistp384,rsa-sha2-256"); + + SAFE_FREE(bind->pubkey_accepted_key_types); + + /* Test with only unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); + assert_int_equal(rc, -1); + assert_null(bind->pubkey_accepted_key_types); + + /* Test with something set and then try unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); + assert_int_equal(rc, -1); + + /* Check that nothing changed */ + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, "ecdsa-sha2-nistp384"); +} + +static void torture_bind_options_set_hostkey_algorithms(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test known Pubkey Types */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with some unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384,unknown-type"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + + SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with only unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); + assert_int_equal(rc, -1); + assert_null(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with something set and then try unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); + assert_int_equal(rc, -1); + + /* Check that nothing changed */ + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); +} + +#endif /* WITH_SERVER */ + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_options_set_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_port, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_port, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_fd, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_user, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_user, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_global_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_global_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_proxycommand, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_control_master, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_control_path, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_ciphers, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_ciphers, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_key_exchange, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_key_exchange, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_hostkey, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_hostkey, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_options_set_pubkey_accepted_types, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_options_get_pubkey_accepted_types, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_macs, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_macs, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_compression, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_compression, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_copy, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_config_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_config_match, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_config_match_multi, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_getopt, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_getopt_o_option, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_plus_sign, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_minus_sign, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_caret_sign, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_apply, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_verbosity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_rsa_min_size, + setup, + teardown), + }; + +#ifdef WITH_SERVER + struct CMUnitTest sshbind_tests[] = { + cmocka_unit_test_setup_teardown(torture_bind_options_import_key, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_import_key_str, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_hostkey, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_bindaddr, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_bindport, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_bindport_str, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity_str, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_rsakey, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_rsa_min_size, + sshbind_setup, + sshbind_teardown), +#ifdef HAVE_ECC + cmocka_unit_test_setup_teardown(torture_bind_options_ecdsakey, + sshbind_setup, + sshbind_teardown), +#endif + cmocka_unit_test_setup_teardown(torture_bind_options_banner, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_ciphers, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_key_exchange, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_macs, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_parse_config, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_config_dir, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown( + torture_bind_options_set_pubkey_accepted_key_types, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown( + torture_bind_options_set_hostkey_algorithms, + sshbind_setup, + sshbind_teardown), + }; +#endif /* WITH_SERVER */ + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); +#ifdef WITH_SERVER + rc += cmocka_run_group_tests(sshbind_tests, NULL, NULL); +#endif /* WITH_SERVER */ + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_packet.c b/src/libs/libssh-0.12.2/tests/unittests/torture_packet.c new file mode 100644 index 000000000000..80e96b4cbf2e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_packet.c @@ -0,0 +1,395 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" +#include "libssh/callbacks.h" + +#include "socket.c" + +uint8_t test_data[]="\x02" + "This is test data. Use it to check the validity of packet functions." + "This is test data. Use it to check the validity of packet functions." + "This is test data. Use it to check the validity of packet functions." + "This is test data. Use it to check the validity of packet functions."; +uint8_t key[]="iekaeshoa7ooCie2shai8shahngee3ONsee3xoishooj0ojei6aeChieth1iraPh"; +uint8_t iv[]="eixaxughoomah4ui7Aew3ohxuolaifuu"; +uint8_t mac[]="thook2Jai0ahmahyae7ChuuruoPhee8Y"; + +static uint8_t *copy_data(uint8_t *data, size_t len){ + uint8_t *ret = malloc(len); + assert_non_null(ret); + memcpy(ret, data, len); + return ret; +} + +static SSH_PACKET_CALLBACK(copy_packet_data){ + uint8_t *response = user; + size_t len = ssh_buffer_get_len(packet); + (void)type; + (void)session; + + if(len > 1024){ + len = 1024; + } + ssh_buffer_get_data(packet, response, len); + + return 0; +} + +static void +torture_packet(const char *cipher, const char *mac_type, + const char *comp_type, size_t payload_len) +{ + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + struct ssh_crypto_struct *crypto; + struct ssh_cipher_struct *in_cipher; + struct ssh_cipher_struct *out_cipher; + int rc; + int sockets[2]; + uint8_t buffer[1024]; + uint8_t response[1024]; + size_t encrypted_packet_len; + size_t processed; + ssh_packet_callback callbacks[]={copy_packet_data}; + struct ssh_packet_callbacks_struct cb = { + .start=2, + .n_callbacks=1, + .callbacks=callbacks, + .user=response + }; + int cmp; + + assert_non_null(session); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + crypto = session->next_crypto; + + rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + assert_int_equal(rc, 0); + + crypto->kex_methods[SSH_KEX] = strdup("curve25519-sha256@libssh.org"); + crypto->kex_methods[SSH_HOSTKEYS] = strdup("ssh-rsa"); + crypto->kex_methods[SSH_CRYPT_C_S] = strdup(cipher); + crypto->kex_methods[SSH_CRYPT_S_C] = strdup(cipher); + crypto->kex_methods[SSH_MAC_C_S] = strdup(mac_type); + crypto->kex_methods[SSH_MAC_S_C] = strdup(mac_type); + crypto->kex_methods[SSH_COMP_C_S] = strdup(comp_type); + crypto->kex_methods[SSH_COMP_S_C] = strdup(comp_type); + crypto->kex_methods[SSH_LANG_C_S] = strdup("none"); + crypto->kex_methods[SSH_LANG_S_C] = strdup("none"); + rc = crypt_set_algorithms_client(session); + assert_int_equal(rc, SSH_OK); + session->current_crypto = session->next_crypto; + session->next_crypto = crypto_new(); + crypto->encryptkey = copy_data(key, sizeof(key)); + crypto->decryptkey = copy_data(key, sizeof(key)); + crypto->encryptIV = copy_data(iv, sizeof(iv)); + crypto->decryptIV = copy_data(iv, sizeof(iv)); + crypto->encryptMAC = copy_data(mac, sizeof(mac)); + crypto->decryptMAC = copy_data(mac, sizeof(mac)); + + in_cipher = session->current_crypto->in_cipher; + if (in_cipher->set_decrypt_key != NULL) { + rc = in_cipher->set_decrypt_key(in_cipher, + session->current_crypto->decryptkey, + session->current_crypto->decryptIV); + assert_int_equal(rc, SSH_OK); + } + + out_cipher = session->current_crypto->out_cipher; + if (out_cipher->set_decrypt_key != NULL) { + rc = out_cipher->set_encrypt_key(out_cipher, + session->current_crypto->encryptkey, + session->current_crypto->encryptIV); + assert_int_equal(rc, SSH_OK); + } + session->current_crypto->used = SSH_DIRECTION_BOTH; + + assert_non_null(session->out_buffer); + ssh_buffer_add_data(session->out_buffer, test_data, payload_len); + session->socket->fd = sockets[0]; + session->socket->write_wontblock = 1; + rc = ssh_packet_send(session); + assert_int_equal(rc, SSH_OK); + + rc = recv(sockets[1], buffer, sizeof(buffer), 0); + assert_true(rc > 0); + encrypted_packet_len = rc; + cmp = strcmp(comp_type, "none"); + if (cmp == 0) { + assert_in_range(encrypted_packet_len, + payload_len + 4, + payload_len + (32 * 3)); + } + rc = send(sockets[0], buffer, encrypted_packet_len, 0); + assert_int_equal(rc, encrypted_packet_len); + + ssh_packet_set_callbacks(session, &cb); + ssh_burn(response, sizeof(response)); + processed = + ssh_packet_socket_callback(buffer, encrypted_packet_len, session); + assert_int_equal(processed, encrypted_packet_len); + if(payload_len > 0){ + assert_memory_equal(response, test_data+1, payload_len-1); + } + close(sockets[0]); + close(sockets[1]); + session->socket->fd = SSH_INVALID_SOCKET; + ssh_free(session); +} + +static void torture_packet_aes128_ctr_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes128-ctr", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes192_ctr_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes192-ctr", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes256_ctr_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes256-ctr", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +#ifdef WITH_INSECURE_NONE +static void torture_packet_none_sha1(UNUSED_PARAM(void **state)) +{ + int i; + + for (i = 1; i < 256; ++i) { + torture_packet("none", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes128_ctr_none(UNUSED_PARAM(void **state)) +{ + int i; + + for (i = 1; i < 256; ++i) { + torture_packet("aes128-ctr", "none", "none", i); + } +} + +static void torture_packet_none_none(UNUSED_PARAM(void **state)) +{ + int i; + + for (i = 1; i < 256; ++i) { + torture_packet("none", "none", "none", i); + } +} +#endif /* WITH_INSECURE_NONE */ + +static void torture_packet_aes128_ctr(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes128-ctr", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes192_ctr(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes192-ctr", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes256_ctr(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-ctr", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes128_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes128-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes192_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes192-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes256_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes128_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes128-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes192_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes192-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes256_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes256-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_3des_cbc(UNUSED_PARAM(void **state)) +{ + int i; + + /* 3des is not completely FIPS-allowed cipher since 140-3 */ + if (ssh_fips_mode()) { + skip(); + } + + for (i=1;i<256;++i){ + torture_packet("3des-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_3des_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + + /* 3des is not completely FIPS-allowed cipher since 140-3 */ + if (ssh_fips_mode()) { + skip(); + } + + for (i = 1; i < 256; ++i) { + torture_packet("3des-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_chacha20(void **state) +{ + int i; + (void)state; /* unused */ + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + for (i=1;i<256;++i){ + torture_packet("chacha20-poly1305@openssh.com", "none", "none", i); + } +} + +static void torture_packet_aes128_gcm(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes128-gcm@openssh.com", "none", "none", i); + } +} + +static void torture_packet_aes256_gcm(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-gcm@openssh.com", "none", "none", i); + } +} + +#ifdef WITH_ZLIB +static void torture_packet_compress_zlib(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-ctr", "hmac-sha1", "zlib", i); + } +} + +static void torture_packet_compress_zlib_openssh(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-ctr", "hmac-sha1", "zlib@openssh.com", i); + } +} +#endif /* WITH_ZLIB */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_packet_aes128_ctr), + cmocka_unit_test(torture_packet_aes192_ctr), + cmocka_unit_test(torture_packet_aes256_ctr), + cmocka_unit_test(torture_packet_aes128_ctr_etm), + cmocka_unit_test(torture_packet_aes192_ctr_etm), + cmocka_unit_test(torture_packet_aes256_ctr_etm), + cmocka_unit_test(torture_packet_aes128_cbc), + cmocka_unit_test(torture_packet_aes192_cbc), + cmocka_unit_test(torture_packet_aes256_cbc), + cmocka_unit_test(torture_packet_aes128_cbc_etm), + cmocka_unit_test(torture_packet_aes192_cbc_etm), + cmocka_unit_test(torture_packet_aes256_cbc_etm), + cmocka_unit_test(torture_packet_3des_cbc), + cmocka_unit_test(torture_packet_3des_cbc_etm), + cmocka_unit_test(torture_packet_chacha20), + cmocka_unit_test(torture_packet_aes128_gcm), + cmocka_unit_test(torture_packet_aes256_gcm), +#ifdef WITH_ZLIB + cmocka_unit_test(torture_packet_compress_zlib), + cmocka_unit_test(torture_packet_compress_zlib_openssh), +#endif /* WITH_ZLIB */ +#ifdef WITH_INSECURE_NONE + cmocka_unit_test(torture_packet_none_sha1), + cmocka_unit_test(torture_packet_aes128_ctr_none), + cmocka_unit_test(torture_packet_none_none), +#endif + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_packet_filter.c b/src/libs/libssh-0.12.2/tests/unittests/torture_packet_filter.c new file mode 100644 index 000000000000..06cd74a77ce4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_packet_filter.c @@ -0,0 +1,631 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* + * This test checks if the messages accepted by the packet filter were intended + * to be accepted. + * + * The process consists in 2 steps: + * - Try the filter with a message type in an arbitrary state + * - If the message is accepted by the filter, check if the message is in the + * set of accepted states. + * + * Only the values selected by the flag (COMPARE_*) are considered. + * */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/session.h" +#include "libssh/auth.h" +#include "libssh/ssh2.h" +#include "libssh/packet.h" + +#include "packet.c" + +#define COMPARE_SESSION_STATE 1 +#define COMPARE_ROLE (1 << 1) +#define COMPARE_DH_STATE (1 << 2) +#define COMPARE_AUTH_STATE (1 << 3) +#define COMPARE_GLOBAL_REQ_STATE (1 << 4) +#define COMPARE_CURRENT_METHOD (1 << 5) + +#define SESSION_STATE_COUNT 11 +#define DH_STATE_COUNT 4 +#define AUTH_STATE_COUNT 15 +#define GLOBAL_REQ_STATE_COUNT 5 +#define MESSAGE_COUNT 100 // from 1 to 100 + +#define ROLE_CLIENT 0 +#define ROLE_SERVER 1 + +/* + * This is the list of currently unfiltered message types. + * Only unrecognized types should be in this list. + * */ +static uint8_t unfiltered[] = { + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 22, 23, 24, 25, 26, 27, 28, 29, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 54, 55, 56, 57, 58, 59, + 62, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 83, 84, 85, 86, 87, 88, 89, +}; + +typedef struct global_state_st { + /* If the bit in this flag is zero, the corresponding state is not + * considered, working as a wildcard (meaning any value is accepted) */ + uint32_t flags; + uint8_t role; + enum ssh_session_state_e session; + enum ssh_dh_state_e dh; + enum ssh_auth_state_e auth; + enum ssh_channel_request_state_e global_req; +} global_state; + +static int cmp_state(const void *e1, const void *e2) +{ + global_state *s1 = (global_state *) e1; + global_state *s2 = (global_state *) e2; + + /* Compare role (client == 0 or server == 1)*/ + if (s1->role < s2->role) { + return -1; + } + else if (s1->role > s2->role) { + return 1; + } + + /* Compare session state */ + if (s1->session < s2->session) { + return -1; + } + else if (s1->session > s2->session) { + return 1; + } + + /* Compare DH state */ + if (s1->dh < s2->dh) { + return -1; + } + else if (s1->dh > s2->dh) { + return 1; + } + + /* Compare auth */ + if (s1->auth < s2->auth) { + return -1; + } + else if (s1->auth > s2->auth) { + return 1; + } + + /* Compare global_req */ + if (s1->global_req < s2->global_req) { + return -1; + } + else if (s1->global_req > s2->global_req) { + return 1; + } + + /* If all equal, they are equal */ + return 0; +} + +static int cmp_state_search(const void *key, const void *array_element) +{ + global_state *s1 = (global_state *) key; + global_state *s2 = (global_state *) array_element; + + int result = 0; + + if (s2->flags & COMPARE_ROLE) { + /* Compare role (client == 0 or server == 1)*/ + if (s1->role < s2->role) { + return -1; + } + else if (s1->role > s2->role) { + return 1; + } + } + + if (s2->flags & COMPARE_SESSION_STATE) { + /* Compare session state */ + if (s1->session < s2->session) { + result = -1; + goto end; + } + else if (s1->session > s2->session) { + result = 1; + goto end; + } + } + + if (s2->flags & COMPARE_DH_STATE) { + /* Compare DH state */ + if (s1->dh < s2->dh) { + result = -1; + goto end; + } + else if (s1->dh > s2->dh) { + result = 1; + goto end; + } + } + + if (s2->flags & COMPARE_AUTH_STATE) { + /* Compare auth */ + if (s1->auth < s2->auth) { + result = -1; + goto end; + } + else if (s1->auth > s2->auth) { + result = 1; + goto end; + } + } + + if (s2->flags & COMPARE_GLOBAL_REQ_STATE) { + /* Compare global_req */ + if (s1->global_req < s2->global_req) { + result = -1; + goto end; + } + else if (s1->global_req > s2->global_req) { + result = 1; + goto end; + } + } + +end: + return result; +} + +static int is_state_accepted(global_state *tested, global_state *accepted, + int accepted_len) +{ + global_state *found = NULL; + + found = bsearch(tested, accepted, accepted_len, sizeof(global_state), + cmp_state_search); + + if (found != NULL) { + return 1; + } + + return 0; +} + +static int cmp_uint8(const void *i, const void *j) +{ + uint8_t e1 = *((uint8_t *)i); + uint8_t e2 = *((uint8_t *)j); + + if (e1 < e2) { + return -1; + } + else if (e1 > e2) { + return 1; + } + + return 0; +} + +static int check_unfiltered(uint8_t msg_type) +{ + uint8_t *found; + + found = bsearch(&msg_type, unfiltered, sizeof(unfiltered)/sizeof(uint8_t), + sizeof(uint8_t), cmp_uint8); + + if (found != NULL) { + return 1; + } + + return 0; +} + +static void torture_packet_filter_check_unfiltered(void **state) +{ + ssh_session session; + + int role_c; + int auth_c; + int session_c; + int dh_c; + int global_req_c; + + uint8_t msg_type; + + enum ssh_packet_filter_result_e rc; + int in_unfiltered; + + (void)state; + + session = ssh_new(); + + for (msg_type = 1; msg_type <= MESSAGE_COUNT; msg_type++) { + session->in_packet.type = msg_type; + for (role_c = 0; role_c < 2; role_c++) { + session->server = role_c; + for (session_c = 0; session_c < SESSION_STATE_COUNT; session_c++) { + session->session_state = session_c; + for (dh_c = 0; dh_c < DH_STATE_COUNT; dh_c++) { + session->dh_handshake_state = dh_c; + for (auth_c = 0; auth_c < AUTH_STATE_COUNT; auth_c++) { + session->auth.state = auth_c; + for (global_req_c = 0; + global_req_c < GLOBAL_REQ_STATE_COUNT; + global_req_c++) + { + session->global_req_state = global_req_c; + + rc = ssh_packet_incoming_filter(session); + + if (rc == SSH_PACKET_UNKNOWN) { + in_unfiltered = check_unfiltered(msg_type); + + if (!in_unfiltered) { + fprintf(stderr, "Message type %d UNFILTERED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_unfiltered, 1); + } + else { + in_unfiltered = check_unfiltered(msg_type); + + if (in_unfiltered) { + fprintf(stderr, "Message type %d NOT UNFILTERED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_unfiltered, 0); + } + } + } + } + } + } + } + ssh_free(session); +} + +static int check_message_in_all_states(global_state accepted[], + int accepted_count, uint8_t msg_type) +{ + ssh_session session; + + int role_c; + int auth_c; + int session_c; + int dh_c; + int global_req_c; + + enum ssh_packet_filter_result_e rc; + int in_accepted; + + global_state key; + + session = ssh_new(); + + /* Sort the accepted array so that the elements can be searched using + * bsearch */ + qsort(accepted, accepted_count, sizeof(global_state), cmp_state); + + session->in_packet.type = msg_type; + + for (role_c = 0; role_c < 2; role_c++) { + session->server = role_c; + key.role = role_c; + for (session_c = 0; session_c < SESSION_STATE_COUNT; session_c++) { + session->session_state = session_c; + key.session = session_c; + for (dh_c = 0; dh_c < DH_STATE_COUNT; dh_c++) { + session->dh_handshake_state = dh_c; + key.dh = dh_c; + for (auth_c = 0; auth_c < AUTH_STATE_COUNT; auth_c++) { + session->auth.state = auth_c; + key.auth = auth_c; + for (global_req_c = 0; + global_req_c < GLOBAL_REQ_STATE_COUNT; + global_req_c++) + { + session->global_req_state = global_req_c; + key.global_req = global_req_c; + + rc = ssh_packet_incoming_filter(session); + + if (rc == SSH_PACKET_ALLOWED) { + in_accepted = is_state_accepted(&key, accepted, + accepted_count); + + if (!in_accepted) { + fprintf(stderr, "Message type %d ALLOWED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_accepted, 1); + } + else if (rc == SSH_PACKET_DENIED) { + in_accepted = is_state_accepted(&key, accepted, accepted_count); + + if (in_accepted) { + fprintf(stderr, "Message type %d DENIED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_accepted, 0); + } + else { + fprintf(stderr, "Message type %d UNFILTERED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + } + } + } + } + } + + ssh_free(session); + return 0; +} + +static void torture_packet_filter_check_auth_success(void **state) +{ + int rc; + + global_state accepted[] = { + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_PASSWORD_AUTH_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_GSSAPI_MIC_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_KBDINT_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE | + COMPARE_CURRENT_METHOD), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_AUTH_NONE_SENT, + } + }; + + int accepted_count = 5; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_USERAUTH_SUCCESS); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_msg_ext_info(void **state) +{ + int rc; + + global_state accepted[] = { + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_DH_STATE), + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_DH_STATE), + .session = SSH_SESSION_STATE_AUTHENTICATED, + .dh = DH_STATE_FINISHED, + }, + }; + + int accepted_count = 2; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_EXT_INFO); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_channel_open(void **state) +{ + int rc; + + /* The only condition to accept a CHANNEL_OPEN is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_CHANNEL_OPEN); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_channel_success(void **state) +{ + int rc; + + /* The only condition to accept a CHANNEL_SUCCESS is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_CHANNEL_SUCCESS); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_channel_failure(void **state) +{ + int rc; + + /* The only condition to accept a CHANNEL_FAILURE is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_CHANNEL_FAILURE); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_request_success(void **state) +{ + int rc; + + /* The only condition to accept a REQUEST_SUCCESS is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_REQUEST_SUCCESS); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_request_failure(void **state) +{ + int rc; + + /* The only condition to accept a REQUEST_FAILURE is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_REQUEST_FAILURE); + + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_packet_filter_check_auth_success), + cmocka_unit_test(torture_packet_filter_check_channel_open), + cmocka_unit_test(torture_packet_filter_check_channel_success), + cmocka_unit_test(torture_packet_filter_check_channel_failure), + cmocka_unit_test(torture_packet_filter_check_request_success), + cmocka_unit_test(torture_packet_filter_check_request_failure), + cmocka_unit_test(torture_packet_filter_check_unfiltered), + cmocka_unit_test(torture_packet_filter_check_msg_ext_info) + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki.c new file mode 100644 index 000000000000..c610e8a2f2e4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki.c @@ -0,0 +1,428 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_cert_dir(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + return 0; +} + +static int teardown_cert_dir(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_keytype(void **state) { + enum ssh_keytypes_e type; + const char *type_c; + + (void) state; /* unused */ + + type = ssh_key_type(NULL); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type = ssh_key_type_from_name(NULL); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type = ssh_key_type_from_name("42"); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type_c = ssh_key_type_to_char(SSH_KEYTYPE_UNKNOWN); + assert_null(type_c); + + type_c = ssh_key_type_to_char(42); + assert_null(type_c); +} + +static void torture_pki_signature(void **state) +{ + ssh_signature sig; + + (void) state; /* unused */ + + sig = ssh_signature_new(); + assert_non_null(sig); + + ssh_signature_free(sig); +} + +struct key_attrs { + int sign; + int verify; + const char *type_c; + int size_arg; + int sig_length; + const char *sig_type_c; + int expect_success; +}; + +struct key_attrs key_attrs_list[][5] = { + { + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA512 */ + }, + /* Cannot remove this as it will break the array indexing used */ + { + {0, 0, "", 0, 0, "", 0}, /* DSS, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA512 */ + }, + { + {1, 1, "ssh-rsa", 2048, 0, "", 0}, /* RSA, AUTO */ + {1, 1, "ssh-rsa", 2048, 20, "ssh-rsa", 1}, /* RSA, SHA1 */ + {1, 1, "ssh-rsa", 2048, 32, "rsa-sha2-256", 1}, /* RSA, SHA256 */ + {1, 1, "ssh-rsa", 2048, 0, "", 0}, /* RSA, SHA384 */ + {1, 1, "ssh-rsa", 2048, 64, "rsa-sha2-512", 1}, /* RSA, SHA512 */ + }, + { + {0, 0, "", 0, 0, "", 0}, /* RSA1, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA512 */ + }, + { + {0, 1, "", 256, 0, "", 0}, /* ECDSA, AUTO */ + {0, 1, "", 256, 0, "", 0}, /* ECDSA, SHA1 */ + {0, 1, "", 256, 0, "", 0}, /* ECDSA, SHA256 */ + {0, 1, "", 384, 0, "", 0}, /* ECDSA, SHA384 */ + {0, 1, "", 521, 0, "", 0}, /* ECDSA, SHA512 */ + }, + { + {1, 1, "ssh-ed25519", 255, 33, "ssh-ed25519", 1}, /* ED25519, AUTO */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA1 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA256 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA384 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA512 */ + }, + { + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA512 */ + }, +#ifdef HAVE_ECC + { + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, AUTO */ + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, SHA1 */ + {1, 1, "ecdsa-sha2-nistp256", 256, 32, "ecdsa-sha2-nistp256", 1}, /* ECDSA P256, SHA256 */ + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, SHA384 */ + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, SHA512 */ + }, + { + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, AUTO */ + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, SHA1 */ + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, SHA256 */ + {1, 1, "ecdsa-sha2-nistp384", 384, 48, "ecdsa-sha2-nistp384", 1}, /* ECDSA P384, SHA384 */ + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, SHA512 */ + }, + { + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, AUTO */ + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, SHA1 */ + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, SHA256 */ + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, SHA384 */ + {1, 1, "ecdsa-sha2-nistp521", 521, 64, "ecdsa-sha2-nistp521", 1}, /* ECDSA P521, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA512 */ + }, +#endif /* HAVE_ECC */ + { + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA512 */ + }, +}; + +/* This tests all the base types and their signatures against each other */ +static void torture_pki_verify_mismatch(void **state) +{ + int rc; + int verbosity = torture_libssh_verbosity(); + ssh_key key = NULL, verify_key = NULL, pubkey = NULL, verify_pubkey = NULL; + ssh_signature sign = NULL, import_sig = NULL, new_sig = NULL; + ssh_string blob; + ssh_session session = ssh_new(); + enum ssh_keytypes_e key_type, sig_type; + enum ssh_digest_e hash; + size_t input_length = sizeof(INPUT); + struct key_attrs skey_attrs, vkey_attrs; + int bits; + + (void) state; + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + for (sig_type = SSH_KEYTYPE_RSA; + sig_type <= SSH_KEYTYPE_ED25519_CERT01; + sig_type++) + { + for (hash = SSH_DIGEST_AUTO; + hash <= SSH_DIGEST_SHA512; + hash++) + { + if (ssh_fips_mode()) { + if (sig_type == SSH_KEYTYPE_ED25519 || + hash == SSH_DIGEST_SHA1) + { + /* In FIPS mode, skip unsupported algorithms */ + continue; + } + } + + skey_attrs = key_attrs_list[sig_type][hash]; + + if (!skey_attrs.sign) { + continue; + } + + rc = ssh_pki_generate(sig_type, skey_attrs.size_arg, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + assert_int_equal(key->type, sig_type); + assert_string_equal(key->type_c, skey_attrs.type_c); + bits = ssh_key_size(key); + assert_int_equal(bits, skey_attrs.size_arg); + + SSH_LOG(SSH_LOG_TRACE, "Creating signature %d with hash %d", + sig_type, hash); + + if (skey_attrs.expect_success == 0) { + /* Expect error */ + sign = pki_do_sign(key, INPUT, input_length, hash); + assert_null(sign); + + SSH_KEY_FREE(key); + continue; + } + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Create a valid signature using this key */ + sign = pki_do_sign(key, INPUT, input_length, hash); + assert_non_null(sign); + assert_int_equal(sign->type, key->type); + assert_string_equal(sign->type_c, skey_attrs.sig_type_c); + + /* Create a signature blob that can be imported and verified */ + blob = pki_signature_to_blob(sign); + assert_non_null(blob); + + /* Import and verify with current key + * (this is not tested anywhere else yet) */ + import_sig = pki_signature_from_blob(key, + blob, + sig_type, + hash); + assert_non_null(import_sig); + assert_int_equal(import_sig->type, key->type); + assert_string_equal(import_sig->type_c, skey_attrs.sig_type_c); + + rc = ssh_pki_signature_verify(session, + import_sig, + pubkey, + INPUT, + input_length); + assert_true(rc == SSH_OK); + + for (key_type = SSH_KEYTYPE_RSA; + key_type <= SSH_KEYTYPE_ED25519_CERT01; + key_type++) + { + if (ssh_fips_mode()) { + if (key_type == SSH_KEYTYPE_ED25519) + { + /* In FIPS mode, skip unsupported algorithms */ + continue; + } + } + + vkey_attrs = key_attrs_list[key_type][hash]; + if (!vkey_attrs.verify) { + continue; + } + + SSH_LOG(SSH_LOG_TRACE, "Trying key %d with signature %d", + key_type, sig_type); + + if (is_cert_type(key_type)) { + torture_write_file("libssh_testkey-cert.pub", + torture_get_testkey_pub(key_type)); + rc = ssh_pki_import_cert_file("libssh_testkey-cert.pub", &verify_pubkey); + verify_key = NULL; + } else { + rc = ssh_pki_generate(key_type, vkey_attrs.size_arg, &verify_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_key); + rc = ssh_pki_export_privkey_to_pubkey(verify_key, &verify_pubkey); + } + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_pubkey); + + /* Should gracefully fail, but not crash */ + rc = ssh_pki_signature_verify(session, + sign, + verify_pubkey, + INPUT, + input_length); + assert_true(rc != SSH_OK); + + /* Try the same with the imported signature */ + rc = ssh_pki_signature_verify(session, + import_sig, + verify_pubkey, + INPUT, + input_length); + assert_true(rc != SSH_OK); + + /* Try to import the signature blob with different key */ + new_sig = pki_signature_from_blob(verify_pubkey, + blob, + sig_type, + import_sig->hash_type); + if (ssh_key_type_plain(verify_pubkey->type) == sig_type) { + /* Importing with the same key type should work */ + assert_non_null(new_sig); + assert_int_equal(new_sig->type, key->type); + assert_string_equal(new_sig->type_c, skey_attrs.sig_type_c); + + /* The verification should not work */ + rc = ssh_pki_signature_verify(session, + new_sig, + verify_pubkey, + INPUT, + input_length); + assert_true(rc != SSH_OK); + + ssh_signature_free(new_sig); + } else { + assert_null(new_sig); + } + SSH_KEY_FREE(verify_key); + SSH_KEY_FREE(verify_pubkey); + } + + ssh_string_free(blob); + ssh_signature_free(sign); + ssh_signature_free(import_sig); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + } + } + ssh_free(session); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_keytype), + cmocka_unit_test(torture_pki_signature), + cmocka_unit_test_setup_teardown(torture_pki_verify_mismatch, + setup_cert_dir, + teardown_cert_dir), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_dsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_dsa.c new file mode 100644 index 000000000000..dfd59a64a184 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_dsa.c @@ -0,0 +1,220 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" +#define LIBSSH_DSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_dsa" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char INPUT[] = "12345678901234567890"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_dsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_DSS, 0)); + torture_write_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, + torture_get_testkey(SSH_KEYTYPE_DSS, 1)); + torture_write_file(LIBSSH_DSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS)); + torture_write_file(LIBSSH_DSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS_CERT01)); + + return 0; +} + +static int setup_openssh_dsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_DSS, 0)); + torture_write_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, + torture_get_openssh_testkey(SSH_KEYTYPE_DSS, 1)); + torture_write_file(LIBSSH_DSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS)); + torture_write_file(LIBSSH_DSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS_CERT01)); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_dsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_DSA_TESTKEY ".pub", &pubkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(pubkey); +} + +static void torture_pki_dsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, &pubkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(pubkey); +} + +static void torture_pki_dsa_import_privkey_base64(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 0), + passphrase, + NULL, + NULL, + &key); + assert_int_equal(rc, SSH_ERROR); + assert_null(key); +} + +static void torture_pki_generate_dsa(void **state) +{ + int rc; + ssh_key key = NULL; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 2048, &key); + assert_int_equal(rc, SSH_ERROR); + assert_null(key); +} + +static void torture_pki_dsa_import_cert_file(void **state) +{ + int rc; + ssh_key cert = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_DSA_TESTKEY "-cert.pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_pubkey_file, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_pubkey_from_openssh_privkey, + setup_openssh_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_privkey_base64, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_privkey_base64, + setup_openssh_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_cert_file, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_generate_dsa, + setup_dsa_key, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa.c new file mode 100644 index 000000000000..8149ca192ba1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa.c @@ -0,0 +1,1297 @@ +#include "config.h" +#include "libssh/libssh.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#define LIBSSH_ECDSA_TESTKEY "libssh_testkey.id_ecdsa" +#define LIBSSH_ECDSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ecdsa" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char INPUT[] = "12345678901234567890"; + +struct pki_st { + char *cwd; + char *temp_dir; + enum ssh_keytypes_e type; +}; + +static int setup_ecdsa_key(void **state, int ecdsa_bits) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + switch (ecdsa_bits) { + case 521: + test_state->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 384: + test_state->type = SSH_KEYTYPE_ECDSA_P384; + break; + default: + test_state->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + torture_write_file(LIBSSH_ECDSA_TESTKEY, + torture_get_testkey(test_state->type, 0)); + torture_write_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + torture_get_testkey(test_state->type, 1)); + torture_write_file(LIBSSH_ECDSA_TESTKEY ".pub", + torture_get_testkey_pub(test_state->type)); + torture_write_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(test_state->type+3)); + return 0; +} + +static int setup_openssh_ecdsa_key(void **state, int ecdsa_bits) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + const char *keystring = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + switch (ecdsa_bits) { + case 521: + test_state->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 384: + test_state->type = SSH_KEYTYPE_ECDSA_P384; + break; + default: + test_state->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + keystring = torture_get_openssh_testkey(test_state->type, 0); + torture_write_file(LIBSSH_ECDSA_TESTKEY, keystring); + + keystring = torture_get_openssh_testkey(test_state->type, 1); + torture_write_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, keystring); + torture_write_file(LIBSSH_ECDSA_TESTKEY ".pub", + torture_get_testkey_pub(test_state->type)); + torture_write_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(test_state->type+3)); + return 0; +} + +static int setup_ecdsa_key_521(void **state) +{ + setup_ecdsa_key(state, 521); + + return 0; +} + +static int setup_ecdsa_key_384(void **state) +{ + setup_ecdsa_key(state, 384); + + return 0; +} + +static int setup_ecdsa_key_256(void **state) +{ + setup_ecdsa_key(state, 256); + + return 0; +} + +static int setup_openssh_ecdsa_key_521(void **state) +{ + setup_openssh_ecdsa_key(state, 521); + + return 0; +} + +static int setup_openssh_ecdsa_key_384(void **state) +{ + setup_openssh_ecdsa_key(state, 384); + + return 0; +} + +static int setup_openssh_ecdsa_key_256(void **state) +{ + setup_openssh_ecdsa_key(state, 256); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ecdsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_format(void **state, + enum ssh_file_format_e format) +{ + int rc; + char *key_str = NULL, *new_key_str = NULL; + ssh_key key = NULL, new_key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void)state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + /* Export */ + rc = ssh_pki_export_privkey_base64_format(key, + passphrase, + NULL, + NULL, + &new_key_str, + format); + assert_int_equal(rc, SSH_OK); + assert_non_null(new_key_str); + + /* and import again */ + rc = ssh_pki_import_privkey_base64(new_key_str, passphrase, NULL, NULL, + &new_key); + assert_int_equal(rc, 0); + assert_non_null(new_key); + + rc = ssh_key_is_private(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PRIVATE | SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + free(key_str); + free(new_key_str); + SSH_KEY_FREE(key); + SSH_KEY_FREE(new_key); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_default(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_DEFAULT); +} + +static void torture_pki_ecdsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + + +static void torture_pki_ecdsa_publickey_from_privatekey(void **state) +{ + int rc; + char *key_str = NULL; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + free(key_str); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_cert_file(void **state) +{ + int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + enum ssh_keytypes_e type, exp_cert_type; + struct pki_st *test_state = *((struct pki_st **)state); + + exp_cert_type = test_state->type + 3; + + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); + assert_int_equal(rc, 0); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + type = ssh_key_type(cert); + assert_int_equal(type, exp_cert_type); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == test_state->type); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + assert_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_int_equal(rc, SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == exp_cert_type); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + /* Generate different key and try to assign it this certificate */ + rc = ssh_pki_generate_key(test_state->type, NULL, &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_int_equal(rc, SSH_ERROR); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key = NULL; + int rc; + struct pki_st *test_state = *((struct pki_st **)state); + + key_buf = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY ".pub"); + assert_non_null(key_buf); + + q = p = key_buf; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_int_equal(type, test_state->type); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_int_equal(rc, 0); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_generate_pubkey_from_privkey(void **state) +{ + char pubkey_original[4096] = {0}; + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + int len; + + (void) state; /* unused */ + + rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", + pubkey_original, + sizeof(pubkey_original)); + assert_int_equal(rc, 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_ECDSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ECDSA_TESTKEY ".pub"); + assert_int_equal(rc, 0); + + rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_int_equal(rc, 0); + len = torture_pubkey_len(pubkey_original); + assert_int_equal(len, torture_pubkey_len(pubkey_generated)); + assert_memory_equal(pubkey_original, pubkey_generated, len); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_duplicate_key(void **state) +{ + int rc; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_int_equal(rc, 0); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_int_equal(rc, 0); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_return_code(rc, errno); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_int_equal(rc, 0); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +/* Test case for bug #147: Private ECDSA key duplication did not carry + * over parts of the key that then caused subsequent key demotion to + * fail. + */ +static void torture_pki_ecdsa_duplicate_then_demote(void **state) +{ + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + int rc; + + (void) state; + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + assert_int_equal(privkey->ecdsa_nid, privkey_dup->ecdsa_nid); + + rc = ssh_pki_export_privkey_to_pubkey(privkey_dup, &pubkey); + assert_int_equal(rc, 0); + assert_non_null(pubkey); + assert_int_equal(pubkey->ecdsa_nid, privkey->ecdsa_nid); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); +} + +static void torture_pki_generate_key_ecdsa(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + const char *type_char = NULL; + const char *etype_char = NULL; + ssh_session session=ssh_new(); + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 0, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp256"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp256"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* deprecated */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 256, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp256"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp256"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P384, 0, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P384); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp384"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp384"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* deprecated */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 384, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P384); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp384"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp384"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P521, 0, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P521); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp521"); + etype_char =ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp521"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* deprecated */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 521, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P521); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp521"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp521"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_ecdsa_cert_verify(void **state) +{ + int rc; + ssh_key privkey = NULL, cert = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + enum ssh_digest_e hash_type; + (void) state; + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); + assert_int_equal(rc, 0); + assert_non_null(cert); + + /* Get the hash type to be used in the signature based on the key type */ + hash_type = ssh_key_type_to_hash(session, privkey->type); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), hash_type); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); + + ssh_free(session); +} + +static int test_sign_verify_data(ssh_key key, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + ssh_signature sig; + ssh_key pubkey = NULL; + int rc; + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, hash_type, input, input_len); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, input, input_len); + assert_int_equal(rc, SSH_OK); + + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + + return rc; +} + +static void torture_pki_sign_data_ecdsa(void **state) +{ + int rc; + ssh_key key = NULL; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 256, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Test using SHA256 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + SSH_KEY_FREE(key); +} + +static void torture_pki_fail_sign_with_incompatible_hash(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + ssh_signature sig, bad_sig; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 256, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_AUTO */ + bad_sig = pki_sign_data(key, SSH_DIGEST_AUTO, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_AUTO */ + sig->hash_type = SSH_DIGEST_AUTO; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA1 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA1, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA1 */ + sig->hash_type = SSH_DIGEST_SHA1; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA384 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA384, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA384 */ + sig->hash_type = SSH_DIGEST_SHA384; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA512 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA512, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA512 */ + sig->hash_type = SSH_DIGEST_SHA512; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(key); +} + +static void +torture_pki_ecdsa_write_privkey_format(void **state, + enum ssh_file_format_e format) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_int_equal(rc, 0); + assert_non_null(origkey); + + unlink(LIBSSH_ECDSA_TESTKEY); + + rc = ssh_pki_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY, + format); + assert_int_equal(rc, 0); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_int_equal(rc, 0); + assert_non_null(origkey); + + unlink(LIBSSH_ECDSA_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + format); + assert_int_equal(rc, 0); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} + +static void +torture_pki_ecdsa_write_privkey(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#ifdef HAVE_LIBCRYPTO +static void +torture_pki_ecdsa_write_privkey_pem(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ecdsa_write_privkey_openssh(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_pem(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_openssh(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_OPENSSH); +} +#endif /* HAVE_LIBCRYPTO */ + +static void torture_pki_ecdsa_name(void **state, const char *expected_name) +{ + int rc; + ssh_key key = NULL; + const char *etype_char = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, NULL, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, expected_name); + + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_name256(void **state) +{ + torture_pki_ecdsa_name(state, "ecdsa-sha2-nistp256"); +} + +static void torture_pki_ecdsa_name384(void **state) +{ + torture_pki_ecdsa_name(state, "ecdsa-sha2-nistp384"); +} + +static void torture_pki_ecdsa_name521(void **state) +{ + torture_pki_ecdsa_name(state, "ecdsa-sha2-nistp521"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_pubkey_from_openssh_privkey, + setup_openssh_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_pubkey_from_openssh_privkey, + setup_openssh_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_openssh_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_then_demote, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_then_demote, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_then_demote, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test(torture_pki_generate_key_ecdsa), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, + setup_ecdsa_key_521, + teardown), +#ifdef HAVE_LIBCRYPTO + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_521, + teardown), +#endif /* HAVE_LIBCRYPTO */ + cmocka_unit_test(torture_pki_sign_data_ecdsa), + cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_name256, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_name384, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_name521, + setup_ecdsa_key_521, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa_uri.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa_uri.c new file mode 100644 index 000000000000..7e6cb8bf41d0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa_uri.c @@ -0,0 +1,584 @@ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#define LIBSSH_ECDSA_TESTKEY "libssh_testkey.id_" +#define LIBSSH_ECDSA_TESTKEY_PEM "libssh_testkey_pem.id_" +#define LABEL_256 "ecdsa256" +#define LABEL_384 "ecdsa384" +#define LABEL_521 "ecdsa521" +#define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" +#define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=1234" +#define PRIV_URI_NO_PUB_FMT "pkcs11:token=%s_no_pub_uri;object=%s_no_pub_uri;type=private?pin-value=1234" + +/** PKCS#11 URIs with invalid fields**/ + +#define PRIV_URI_FMT_384_INVALID_TOKEN "pkcs11:token=ecdsa521;object=ecdsa384;type=private?pin-value=1234" +#define PRIV_URI_FMT_521_INVALID_OBJECT "pkcs11:token=ecdsa521;object=ecdsa384;type=private?pin-value=1234" +#define PUB_URI_FMT_384_INVALID_TOKEN "pkcs11:token=ecdsa521;object=ecdsa384;type=public" +#define PUB_URI_FMT_521_INVALID_OBJECT "pkcs11:token=ecdsa521;object=ecdsa384;type=public" + +const char template[] = "/tmp/temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; +struct pki_st { + char *orig_dir; + char *temp_dir; + enum ssh_keytypes_e type; +}; + +static int setup_tokens_ecdsa(void **state, int ecdsa_bits, const char *obj_tempname, const char *load_public) +{ + + struct pki_st *test_state = *state; + char priv_filename[1024]; + char pub_filename[1024]; + char *cwd = NULL; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + snprintf(priv_filename, sizeof(priv_filename), "%s%s%s%s", cwd, "/", LIBSSH_ECDSA_TESTKEY, obj_tempname); + snprintf(pub_filename, sizeof(pub_filename), "%s%s%s%s%s", cwd, "/", LIBSSH_ECDSA_TESTKEY, obj_tempname, ".pub"); + + switch (ecdsa_bits) { + case 521: + test_state->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 384: + test_state->type = SSH_KEYTYPE_ECDSA_P384; + break; + default: + test_state->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + torture_write_file(priv_filename, + torture_get_testkey(test_state->type, 0)); + torture_write_file(pub_filename, + torture_get_testkey_pub_pem(test_state->type)); + torture_setup_tokens(cwd, priv_filename, obj_tempname, load_public); + + return 0; +} + +static int setup_directory_structure(void **state) +{ + struct pki_st *test_state = NULL; + char *temp_dir; + int rc; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + SAFE_FREE(temp_dir); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + *state = test_state; + + setup_tokens_ecdsa(state, 256, "ecdsa256", "1"); + setup_tokens_ecdsa(state, 384, "ecdsa384", "1"); + setup_tokens_ecdsa(state, 521, "ecdsa521", "1"); + setup_tokens_ecdsa(state, 256, "ecdsa256_no_pub_uri", "0"); + setup_tokens_ecdsa(state, 384, "ecdsa384_no_pub_uri", "0"); + setup_tokens_ecdsa(state, 521, "ecdsa521_no_pub_uri", "0"); + + return 0; +} + +static int teardown_directory_structure(void **state) +{ + struct pki_st *test_state = *state; + int rc; + + torture_cleanup_tokens(test_state->temp_dir); + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ecdsa_import_pubkey_uri(void **state, const char *label) +{ + char uri[128] = {0}; + ssh_key pubkey = NULL; + int rc; + + rc = snprintf(uri, sizeof(uri), PUB_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + + rc = ssh_pki_import_pubkey_file(uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_pubkey_uri_256(void **state) +{ + torture_pki_ecdsa_import_pubkey_uri(state, LABEL_256); +} + +static void torture_pki_ecdsa_import_pubkey_uri_384(void **state) +{ + torture_pki_ecdsa_import_pubkey_uri(state, LABEL_384); +} + +static void torture_pki_ecdsa_import_pubkey_uri_521(void **state) +{ + torture_pki_ecdsa_import_pubkey_uri(state, LABEL_521); +} + +static void +torture_pki_ecdsa_publickey_from_privatekey_uri(void **state, + const char *label, + const char *type) +{ + int rc; + char uri[128] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_string pblob = NULL; + char pubkey_original[4096] = {0}; + char pubkey_generated[4096] = {0}; + char convert_key_to_pem[4096]; + char pub_filename[1024]; + char pub_filename_generated[1024]; + char pub_filename_pem[1024]; + + rc = snprintf(uri, sizeof(uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + + rc = ssh_pki_import_privkey_file(uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_export_pubkey_blob(privkey, &pblob); + assert_return_code(rc, errno); + assert_non_null(pblob); + ssh_string_free(pblob); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + snprintf(pub_filename, sizeof(pub_filename), "%s%s%s", LIBSSH_ECDSA_TESTKEY, type, ".pub"); + snprintf(pub_filename_generated, sizeof(pub_filename_generated), "%s%s%s", + LIBSSH_ECDSA_TESTKEY_PEM, type, "generated.pub"); + snprintf(pub_filename_pem, sizeof(pub_filename_pem), "%s%s%s", LIBSSH_ECDSA_TESTKEY_PEM, type, ".pub"); + + rc = torture_read_one_line(pub_filename, + pubkey_original, + sizeof(pubkey_original)); + assert_return_code(rc, errno); + + rc = ssh_pki_export_pubkey_file(pubkey, pub_filename_generated); + assert_return_code(rc, errno); + + /* remove the public key, generate it from the private key and write it. */ + unlink(pub_filename); + + snprintf(convert_key_to_pem, sizeof(convert_key_to_pem), "ssh-keygen -e -f %s -m PKCS8 > %s ", + pub_filename_generated, pub_filename_pem); + + system(convert_key_to_pem); + + rc = torture_read_one_line(pub_filename_pem, + pubkey_generated, + sizeof(pubkey_generated)); + assert_return_code(rc, errno); + + assert_memory_equal(pubkey_original, pubkey_generated, strlen(pubkey_original)); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_256(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, LABEL_256, "ecdsa256"); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_384(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, LABEL_384, "ecdsa384"); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_521(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, LABEL_521, "ecdsa521"); +} + +static void +import_pubkey_without_loading_public_uri(void **state, const char *label) +{ + int rc; + char uri[128] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_string pblob = NULL; + + rc = snprintf(uri, sizeof(uri), PRIV_URI_NO_PUB_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + + rc = ssh_pki_import_privkey_file(uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_export_pubkey_blob(privkey, &pblob); + assert_int_not_equal(rc, 0); + assert_null(pblob); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + SSH_KEY_FREE(privkey); +} + +static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_256(void **state) +{ + import_pubkey_without_loading_public_uri(state, LABEL_256); +} + +static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_384(void **state) +{ + import_pubkey_without_loading_public_uri(state, LABEL_384); +} + +static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_521(void **state) +{ + import_pubkey_without_loading_public_uri(state, LABEL_521); +} + +static void +torture_ecdsa_sign_verify_uri(void **state, + const char *label, + enum ssh_digest_e dig_type) +{ + int rc; + char uri[128] = {0}; + ssh_key privkey = NULL, pubkey = NULL; + ssh_signature sign = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + const char *type_char = NULL; + const char *etype_char = NULL; + ssh_session session = ssh_new(); + + assert_non_null(session); + + rc = snprintf(uri, sizeof(uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + + rc = ssh_pki_import_privkey_file(uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), dig_type); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + + type = ssh_key_type(privkey); + type_char = ssh_key_type_to_char(type); + etype_char = ssh_pki_key_ecdsa_name(privkey); + + switch (dig_type) { + case SSH_DIGEST_SHA256: + assert_true(type == SSH_KEYTYPE_ECDSA_P256); + assert_string_equal(type_char, "ecdsa-sha2-nistp256"); + assert_string_equal(etype_char, "ecdsa-sha2-nistp256"); + break; + case SSH_DIGEST_SHA384: + assert_true(type == SSH_KEYTYPE_ECDSA_P384); + assert_string_equal(type_char, "ecdsa-sha2-nistp384"); + assert_string_equal(etype_char, "ecdsa-sha2-nistp384"); + break; + case SSH_DIGEST_SHA512: + assert_true(type == SSH_KEYTYPE_ECDSA_P521); + assert_string_equal(type_char, "ecdsa-sha2-nistp521"); + assert_string_equal(etype_char, "ecdsa-sha2-nistp521"); + break; + default: + printf("Invalid hash type: %d\n", dig_type); + } + + ssh_free(session); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_ecdsa_sign_verify_uri_256(void **state) +{ + torture_ecdsa_sign_verify_uri(state, LABEL_256, SSH_DIGEST_SHA256); +} + +static void torture_ecdsa_sign_verify_uri_384(void **state) +{ + torture_ecdsa_sign_verify_uri(state, LABEL_384, SSH_DIGEST_SHA384); +} + +static void torture_ecdsa_sign_verify_uri_521(void **state) +{ + torture_ecdsa_sign_verify_uri(state, LABEL_521, SSH_DIGEST_SHA512); +} + +static void torture_pki_ecdsa_duplicate_key_uri(void **state, const char *label) +{ + int rc; + char pub_uri[128] = {0}; + char priv_uri[128] = {0}; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = snprintf(pub_uri, sizeof(pub_uri), PUB_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(pub_uri) - 1); + rc = snprintf(priv_uri, sizeof(priv_uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(priv_uri) - 1); + + rc = ssh_pki_import_pubkey_file(pub_uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_return_code(rc, errno); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_return_code(rc, errno); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_return_code(rc, errno); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +static void torture_pki_ecdsa_duplicate_key_uri_256(void **state) +{ + torture_pki_ecdsa_duplicate_key_uri(state, LABEL_256); +} + +static void torture_pki_ecdsa_duplicate_key_uri_384(void **state) +{ + torture_pki_ecdsa_duplicate_key_uri(state, LABEL_384); +} + +static void torture_pki_ecdsa_duplicate_key_uri_521(void **state) +{ + torture_pki_ecdsa_duplicate_key_uri(state, LABEL_521); +} + +static void +torture_pki_ecdsa_duplicate_then_demote_uri(void **state, const char *label) +{ + char priv_uri[128] = {0}; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + int rc; + + (void) state; + + rc = snprintf(priv_uri, sizeof(priv_uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(priv_uri) - 1); + + rc = ssh_pki_import_privkey_file(priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + assert_int_equal(privkey->ecdsa_nid, privkey_dup->ecdsa_nid); + + rc = ssh_pki_export_privkey_to_pubkey(privkey_dup, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + assert_int_equal(pubkey->ecdsa_nid, privkey->ecdsa_nid); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri_256(void **state) +{ + torture_pki_ecdsa_duplicate_then_demote_uri(state, LABEL_256); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri_384(void **state) +{ + torture_pki_ecdsa_duplicate_then_demote_uri(state, LABEL_384); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri_521(void **state) +{ + torture_pki_ecdsa_duplicate_then_demote_uri(state, LABEL_521); +} + +static void torture_pki_ecdsa_import_pubkey_uri_invalid_configurations(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + + /** invalid token for already setup Private PKCS #11 URI */ + rc = ssh_pki_import_privkey_file(PRIV_URI_FMT_384_INVALID_TOKEN, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + + /** invalid object for already setup Private PKCS #11 URI */ + rc = ssh_pki_import_privkey_file(PRIV_URI_FMT_521_INVALID_OBJECT, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + /** invalid token for already setup Public PKCS #11 URI */ + rc = ssh_pki_import_pubkey_file(PUB_URI_FMT_384_INVALID_TOKEN, + &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + /** invalid object for already setup Public PKCS #11 URI */ + rc = ssh_pki_import_pubkey_file(PUB_URI_FMT_521_INVALID_OBJECT, + &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_256), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_384), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_521), + cmocka_unit_test(torture_pki_ecdsa_publickey_from_privatekey_uri_256), + cmocka_unit_test(torture_pki_ecdsa_publickey_from_privatekey_uri_384), + cmocka_unit_test(torture_pki_ecdsa_publickey_from_privatekey_uri_521), + cmocka_unit_test(torture_ecdsa_sign_verify_uri_256), + cmocka_unit_test(torture_ecdsa_sign_verify_uri_384), + cmocka_unit_test(torture_ecdsa_sign_verify_uri_521), + cmocka_unit_test(torture_pki_ecdsa_duplicate_key_uri_256), + cmocka_unit_test(torture_pki_ecdsa_duplicate_key_uri_384), + cmocka_unit_test(torture_pki_ecdsa_duplicate_key_uri_521), + cmocka_unit_test(torture_pki_ecdsa_duplicate_then_demote_uri_256), + cmocka_unit_test(torture_pki_ecdsa_duplicate_then_demote_uri_384), + cmocka_unit_test(torture_pki_ecdsa_duplicate_then_demote_uri_521), + + /** Expect fail on these negative test cases **/ + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_invalid_configurations), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_256), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_384), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_521), + }; + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + + ssh_init(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, setup_directory_structure, teardown_directory_structure); + + ssh_free(session); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519.c new file mode 100644 index 000000000000..6a8d04e81fe8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519.c @@ -0,0 +1,1210 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include +#include + +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#define LIBSSH_ED25519_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ed25519" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char HASH[] = "12345678901234567890"; +const uint8_t ref_signature[ED25519_SIG_LEN]= + "\xbb\x8d\x55\x9f\x06\x14\x39\x24\xb4\xe1\x5a\x57\x3d\x9d\xbe\x22" + "\x1b\xc1\x32\xd5\x55\x16\x00\x64\xce\xb4\xc3\xd2\xe3\x6f\x5e\x8d" + "\x10\xa3\x18\x93\xdf\xa4\x96\x81\x11\x8e\x1e\x26\x14\x8a\x08\x1b" + "\x01\x6a\x60\x59\x9c\x4a\x55\xa3\x16\x56\xf6\xc4\x50\x42\x7f\x03"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_ed25519_key(void **state) +{ + const char *keystring = NULL; + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + torture_write_file(LIBSSH_ED25519_TESTKEY, keystring); + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + torture_write_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, keystring); + + torture_write_file(LIBSSH_ED25519_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + torture_write_file(LIBSSH_ED25519_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519_CERT01)); + + return 0; +} + +static int teardown(void **state) { + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ed25519_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_privkey_base64(void **state) +{ + int rc; + char *key_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + SSH_KEY_FREE(key); + +} + +static void torture_pki_ed25519_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); + +} + +static void torture_pki_ed25519_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); + +} + +static void torture_pki_ed25519_import_export_privkey_base64(void **state) +{ + char *b64_key = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + int rc; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_privkey_base64(torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, + false), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_pki_export_privkey_base64(key, + passphrase, + NULL, + NULL, + &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + SSH_KEY_FREE(key); + + rc = ssh_pki_import_privkey_base64(b64_key, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + SSH_STRING_FREE_CHAR(b64_key); + SSH_KEY_FREE(key); +} + +static void torture_pki_ed25519_publickey_from_privatekey(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + const char *keystring = NULL; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_cert_file(void **state) +{ + int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_ED25519_CERT01); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + SSH_KEY_FREE(cert); + SSH_KEY_FREE(pubkey); + skip(); + } + + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == SSH_KEYTYPE_ED25519); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + assert_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_int_equal(rc, SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == SSH_KEYTYPE_ED25519_CERT01); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + /* Generate different key and try to assign it this certificate */ + rc = ssh_pki_generate_key(SSH_KEYTYPE_ED25519, NULL, &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_int_equal(rc, SSH_ERROR); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key = NULL; + int rc; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + assert_non_null(key_buf); + + q = p = key_buf; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_ED25519); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_ed25519_generate_pubkey_from_privkey(void **state) +{ + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + int len; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_ED25519_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ED25519_TESTKEY ".pub"); + assert_return_code(rc, errno); + + rc = torture_read_one_line(LIBSSH_ED25519_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_return_code(rc, errno); + + len = torture_pubkey_len(torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), + pubkey_generated, + len); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_generate_key(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + const char *type_char = NULL; + ssh_session session=ssh_new(); + uint8_t *raw_sig_data = NULL; + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + assert_non_null(session); + + rc = ssh_pki_generate(SSH_KEYTYPE_ED25519, 256, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, HASH, 20, SSH_DIGEST_AUTO); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, HASH, 20); + assert_true(rc == SSH_OK); + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + type_char = ssh_key_type_to_char(type); + assert_true(strcmp(type_char, "ssh-ed25519") == 0); + + /* try an invalid signature */ +#ifdef HAVE_LIBCRYPTO + raw_sig_data = ssh_string_data(sign->raw_sig); +#else + raw_sig_data = (uint8_t *)sign->ed25519_sig; +#endif + assert_non_null(raw_sig_data); + (raw_sig_data)[3]^= 0xff; + rc = ssh_pki_signature_verify(session, sign, pubkey, HASH, 20); + assert_true(rc == SSH_ERROR); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_ed25519_cert_verify(void **state) +{ + int rc; + ssh_key privkey = NULL, cert = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + assert_non_null(session); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + sign = pki_do_sign(privkey, HASH, 20, SSH_DIGEST_AUTO); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, cert, HASH, 20); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); + + ssh_free(session); +} + +static void +torture_pki_ed25519_write_privkey_format(void **state, + enum ssh_file_format_e format) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_return_code(rc, errno); + assert_non_null(origkey); + + unlink(LIBSSH_ED25519_TESTKEY); + + rc = ssh_pki_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_ED25519_TESTKEY, + format); + assert_return_code(rc, errno); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + unlink(LIBSSH_ED25519_TESTKEY); + SSH_KEY_FREE(privkey); + /* do the same with passphrase */ + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY, + format); + assert_return_code(rc, errno); + + /* Opening passphrase protected key will prompt for the pin interactively, + * which would hang in the test */ + if (format != SSH_FILE_FORMAT_PEM) { + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + /* opening without passphrase should fail */ + assert_int_equal(rc, SSH_ERROR); + } + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + unlink(LIBSSH_ED25519_TESTKEY); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_return_code(rc, errno); + assert_non_null(origkey); + + unlink(LIBSSH_ED25519_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY_PASSPHRASE, + format); + assert_return_code(rc, errno); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} + +static void +torture_pki_ed25519_write_privkey(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#ifdef HAVE_LIBCRYPTO +static void +torture_pki_ed25519_write_privkey_pem(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ed25519_write_privkey_openssh(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} +#endif + +static void torture_pki_ed25519_sign(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); + +} + +static void torture_pki_ed25519_sign_openssh_privkey_passphrase(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(keystring, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} + +#ifdef HAVE_LIBCRYPTO +static void torture_pki_ed25519_sign_pkcs8_privkey(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} + +static void torture_pki_ed25519_sign_pkcs8_privkey_passphrase(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(keystring, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} +#endif /* HAVE_LIBCRYPTO */ + +static void torture_pki_ed25519_verify(void **state){ + ssh_key pubkey = NULL; + ssh_signature sig = NULL; + ssh_session session = NULL; + ssh_string blob = ssh_string_new(ED25519_SIG_LEN); + char *pkey_ptr = strdup(strchr(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), ' ') + 1); + char *ptr = NULL; + uint8_t *raw_sig_data = NULL; + int rc; + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + session = ssh_new(); + assert_non_null(session); + + /* remove trailing comment */ + ptr = strchr(pkey_ptr, ' '); + if(ptr != NULL){ + *ptr = '\0'; + } + rc = ssh_pki_import_pubkey_base64(pkey_ptr, SSH_KEYTYPE_ED25519, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_string_fill(blob, ref_signature, ED25519_SIG_LEN); + assert_int_equal(rc, 0); + sig = pki_signature_from_blob(pubkey, blob, SSH_KEYTYPE_ED25519, SSH_DIGEST_AUTO); + assert_non_null(sig); + + rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); + assert_return_code(rc, errno); + + /* Alter signature and expect verification error */ +#ifdef HAVE_LIBCRYPTO + raw_sig_data = ssh_string_data(sig->raw_sig); +#else + raw_sig_data = (uint8_t *)sig->ed25519_sig; +#endif + assert_non_null(raw_sig_data); + (raw_sig_data)[3]^= 0xff; + rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); + assert_true(rc == SSH_ERROR); + + ssh_signature_free(sig); + + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE(blob); + free(pkey_ptr); + ssh_free(session); +} + +static void torture_pki_ed25519_verify_bad(void **state){ + ssh_key pubkey = NULL; + ssh_signature sig = NULL; + ssh_session session = NULL; + ssh_string blob = ssh_string_new(ED25519_SIG_LEN); + char *pkey_ptr = strdup(strchr(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), ' ') + 1); + char *ptr = NULL; + int rc; + int i; + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + session = ssh_new(); + assert_non_null(session); + + /* remove trailing comment */ + ptr = strchr(pkey_ptr, ' '); + if(ptr != NULL){ + *ptr = '\0'; + } + rc = ssh_pki_import_pubkey_base64(pkey_ptr, SSH_KEYTYPE_ED25519, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + /* alter signature and expect false result */ + + for (i=0; i < ED25519_SIG_LEN; ++i){ + rc = ssh_string_fill(blob, ref_signature, ED25519_SIG_LEN); + assert_int_equal(rc, 0); + ((uint8_t *)ssh_string_data(blob))[i] ^= 0xff; + sig = pki_signature_from_blob(pubkey, blob, SSH_KEYTYPE_ED25519, SSH_DIGEST_AUTO); + assert_non_null(sig); + + rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); + assert_true(rc == SSH_ERROR); + ssh_signature_free(sig); + + } + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE(blob); + free(pkey_ptr); + ssh_free(session); +} + +static void torture_pki_ed25519_import_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey = NULL; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + /* same for ED25519 */ + testkey = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(testkey, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); +} + +static void torture_pki_ed25519_privkey_dup(void **state) +{ + const char *passphrase = torture_get_testkey_passphrase(); + ssh_key key = NULL; + ssh_key dup = NULL; + const char *testkey = NULL; + int rc; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + testkey = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + dup = ssh_key_dup(key); + assert_non_null(dup); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(dup); +} + +static void torture_pki_ed25519_pubkey_dup(void **state) +{ + ssh_key pubkey = NULL; + ssh_key dup = NULL; + const char *p = strchr(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), ' '); + char *pub_str = NULL; + char *q = NULL; + int rc; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + pub_str = strdup(p + 1); + assert_non_null(pub_str); + + q = strchr(pub_str, ' '); + assert_non_null(q); + *q = '\0'; + + rc = ssh_pki_import_pubkey_base64(pub_str, + SSH_KEYTYPE_ED25519, + &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_true(rc == 1); + + dup = ssh_key_dup(pubkey); + assert_non_null(dup); + + rc = ssh_key_is_public(dup); + assert_true(rc == 1); + + SAFE_FREE(pub_str); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(dup); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_pubkey_file, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_pubkey_from_openssh_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_privkey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_privkey_base64_comment, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_privkey_base64_whitespace, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_export_privkey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_publickey_from_privatekey, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_cert_file, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_publickey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_generate_pubkey_from_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test(torture_pki_ed25519_generate_key), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_cert_verify, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_write_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test(torture_pki_ed25519_import_privkey_base64_passphrase), + cmocka_unit_test(torture_pki_ed25519_sign), + cmocka_unit_test(torture_pki_ed25519_sign_openssh_privkey_passphrase), +#ifdef HAVE_LIBCRYPTO + cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey), + cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_write_privkey_pem, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_write_privkey_openssh, + setup_ed25519_key, + teardown), +#endif + cmocka_unit_test(torture_pki_ed25519_verify), + cmocka_unit_test(torture_pki_ed25519_verify_bad), + cmocka_unit_test(torture_pki_ed25519_privkey_dup), + cmocka_unit_test(torture_pki_ed25519_pubkey_dup), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519_uri.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519_uri.c new file mode 100644 index 000000000000..09ccab4f87d2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519_uri.c @@ -0,0 +1,357 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Red Hat, Inc. + * + * Authors: Jakub Jelen + * Sahana Prasad + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#define LIBSSH_EDDSA_TESTKEY "libssh_testkey.id_ed25519" +#define LIBSSH_EDDSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ed25519" +#define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" +#define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=%s" + +const char template[] = "/tmp/temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; +struct pki_st { + char *orig_dir; + char *temp_dir; + char *pub_uri; + char *priv_uri; + char *priv_uri_invalid_object; + char *priv_uri_invalid_token; + char *pub_uri_invalid_object; + char *pub_uri_invalid_token; +}; + +static int setup_tokens(void **state) +{ + char keys_path[1024] = {0}; + char keys_path_pub[1024] = {0}; + char *cwd = NULL; + struct pki_st *test_state = *state; + char obj_tempname[] = "label_XXXXXX"; + char pub_uri[1024] = {0}; + char priv_uri[1024] = {0}; + char pub_uri_invalid_object[1024] = {0}; + char priv_uri_invalid_object[1024] = {0}; + char pub_uri_invalid_token[1024] = {0}; + char priv_uri_invalid_token[1024] = {0}; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + ssh_tmpname(obj_tempname); + + snprintf(pub_uri, sizeof(pub_uri), PUB_URI_FMT, obj_tempname, obj_tempname); + + snprintf(priv_uri, + sizeof(priv_uri), + PRIV_URI_FMT, + obj_tempname, + obj_tempname, + "1234"); + + snprintf(pub_uri_invalid_token, + sizeof(pub_uri_invalid_token), + PUB_URI_FMT, + "invalid", + obj_tempname); + + snprintf(priv_uri_invalid_token, + sizeof(priv_uri_invalid_token), + PRIV_URI_FMT, + "invalid", + obj_tempname, + "1234"); + + snprintf(pub_uri_invalid_object, + sizeof(pub_uri_invalid_object), + PUB_URI_FMT, + obj_tempname, + "invalid"); + + snprintf(priv_uri_invalid_object, + sizeof(priv_uri_invalid_object), + PRIV_URI_FMT, + obj_tempname, + "invalid", + "1234"); + + snprintf(keys_path, sizeof(keys_path), "%s/%s", cwd, LIBSSH_EDDSA_TESTKEY); + + snprintf(keys_path_pub, + sizeof(keys_path_pub), + "%s/%s.pub", + cwd, + LIBSSH_EDDSA_TESTKEY); + + test_state->pub_uri = strdup(pub_uri); + test_state->priv_uri = strdup(priv_uri); + test_state->pub_uri_invalid_token = strdup(pub_uri_invalid_token); + test_state->pub_uri_invalid_object = strdup(pub_uri_invalid_object); + test_state->priv_uri_invalid_token = strdup(priv_uri_invalid_token); + test_state->priv_uri_invalid_object = strdup(priv_uri_invalid_object); + + torture_write_file(keys_path, torture_get_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file(keys_path_pub, + torture_get_testkey_pub_pem(SSH_KEYTYPE_ED25519)); + + torture_setup_tokens(cwd, keys_path, obj_tempname, "1"); + + return 0; +} + +static int setup_directory_structure(void **state) +{ + struct pki_st *test_state = NULL; + char *temp_dir = NULL; + int rc; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + free(temp_dir); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + *state = test_state; + + rc = setup_tokens(state); + assert_int_equal(rc, 0); + + return 0; +} + +static int teardown_directory_structure(void **state) +{ + struct pki_st *test_state = *state; + int rc; + + torture_cleanup_tokens(test_state->temp_dir); + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state->priv_uri); + SAFE_FREE(test_state->pub_uri); + SAFE_FREE(test_state->priv_uri_invalid_object); + SAFE_FREE(test_state->pub_uri_invalid_object); + SAFE_FREE(test_state->priv_uri_invalid_token); + SAFE_FREE(test_state->pub_uri_invalid_token); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ed25519_import_pubkey_uri(void **state) +{ + ssh_key pubkey = NULL; + int rc; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_privkey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(privkey); +} + +static void torture_pki_sign_verify_uri(void **state) +{ + int rc; + ssh_key privkey = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session = ssh_new(); + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), SSH_DIGEST_AUTO); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_ed25519_publickey_from_privatekey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_uri_invalid_configurations(void **state) +{ + int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + + struct pki_st *test_state = *state; + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_object, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_token, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_object, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_token, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_ed25519_import_pubkey_uri), + cmocka_unit_test(torture_pki_ed25519_import_privkey_uri), + cmocka_unit_test(torture_pki_sign_verify_uri), + cmocka_unit_test(torture_pki_ed25519_publickey_from_privatekey_uri), + cmocka_unit_test(torture_pki_ed25519_uri_invalid_configurations), + }; + + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + + /* Skip test FIPS mode altogether. */ + if (ssh_fips_mode()) { + return 0; + } + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_directory_structure, + teardown_directory_structure); + + ssh_free(session); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa.c new file mode 100644 index 000000000000..44d445fc3fb5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa.c @@ -0,0 +1,1259 @@ + +#include "config.h" +#include "libssh/libssh.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_rsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + torture_write_file(LIBSSH_RSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA_CERT01)); + + return 0; +} + +static int setup_openssh_rsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + torture_write_file(LIBSSH_RSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA_CERT01)); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_rsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state) +{ + int rc; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + /* test if it returns -1 if key is NULL */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + passphrase, + NULL, + NULL, + NULL); + assert_int_equal(rc, -1); + +} + +static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + /* test if it returns -1 if key_str is NULL */ + rc = ssh_pki_import_privkey_base64(NULL, passphrase, NULL, NULL, &key); + assert_int_equal(rc, -1); + + SSH_KEY_FREE(key); +} + +static void +torture_pki_rsa_import_export_privkey_base64_format(void **state, + enum ssh_file_format_e format) +{ + int rc; + char *key_str = NULL, *new_key_str = NULL; + ssh_key key = NULL, new_key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + /* Import test key */ + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(key); + assert_int_equal(rc, 1); + + /* Export */ + rc = ssh_pki_export_privkey_base64_format(key, + passphrase, + NULL, + NULL, + &new_key_str, + format); + assert_int_equal(rc, SSH_OK); + assert_non_null(new_key_str); + + /* and import again */ + rc = ssh_pki_import_privkey_base64(new_key_str, + passphrase, + NULL, + NULL, + &new_key); + assert_int_equal(rc, 0); + assert_non_null(new_key); + + type = ssh_key_type(new_key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PRIVATE|SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + free(key_str); + free(new_key_str); + SSH_KEY_FREE(key); + SSH_KEY_FREE(new_key); +} + +static void +torture_pki_rsa_import_export_privkey_base64(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_DEFAULT); +} + +static void torture_pki_rsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(key); + assert_int_equal(rc, 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(key); + assert_int_equal(rc, 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_publickey_from_privatekey(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_copy_cert_to_privkey(void **state) +{ + /* + * Tests copying a cert loaded into a public key to a private key. + * The function is encryption type agnostic, no need to run this against + * all supported key types. + */ + int rc; + const char *passphrase = torture_get_testkey_passphrase(); + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void)state; /* unused */ + + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + passphrase, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == SSH_KEYTYPE_RSA); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + assert_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_int_equal(rc, SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == SSH_KEYTYPE_RSA_CERT01); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + /* Generate different key and try to assign it this certificate */ + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_int_equal(rc, SSH_ERROR); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_cert_file(void **state) { + int rc; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + type = ssh_key_type(cert); + assert_int_equal(type, SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(cert); +} + +static void torture_pki_rsa_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key = NULL; + int rc; + + (void) state; /* unused */ + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_non_null(key_buf); + + q = p = key_buf; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_generate_pubkey_from_privkey(void **state) { + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + int len; + + (void) state; /* unused */ + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_RSA_TESTKEY ".pub"); + assert_return_code(rc, errno); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_return_code(rc, errno); + + len = torture_pubkey_len(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_RSA), + pubkey_generated, + len); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_duplicate_key(void **state) +{ + int rc; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_return_code(rc, errno); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_return_code(rc, errno); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_return_code(rc, errno); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +/** + * @brief Test RSA key generation using the deprecated ssh_pki_generate API. + * + * This test is kept for backward compatibility testing of the legacy API. + * For testing the new context-based API, see torture_pki_generate_key_rsa(). + */ +static void torture_pki_generate_rsa_deprecated(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + + (void) state; + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + if (!ssh_fips_mode()) { + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + } + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + ssh_free(session); +} + +static void torture_pki_rsa_sha2(void **state) +{ + int rc; + ssh_key key = NULL, cert = NULL, pubkey = NULL; + ssh_signature sign; + ssh_session session=ssh_new(); + (void) state; + + assert_non_null(session); + + /* Setup */ + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + if (!ssh_fips_mode()) { + /* Sign using old SHA1 digest */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA1); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + ssh_signature_free(sign); + } + + /* Sign using new SHA256 digest */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + ssh_signature_free(sign); + + /* Sign using rsa-sha2-512 algorithm */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + ssh_signature_free(sign); + + /* Test that it fails when using DIGEST_AUTO */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_AUTO); + assert_null(sign); + + /* Test that it fails when using SHA384 */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); + assert_null(sign); + + /* Cleanup */ + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(cert); + ssh_free(session); +} + +static void torture_pki_rsa_key_size(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + unsigned int length = 4096; + + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + + /* Set the minimum RSA key size to 4k */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &length); + assert_ssh_return_code(session, rc); + + /* the verification should fail now */ + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_ERROR); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + ssh_free(session); +} + +/** + * @brief Test RSA key generation using the new context-based + * ssh_pki_generate_key API. + * + * This test validates the new ssh_pki_ctx-based key generation API with + * both positive and negative test cases. + * + * For testing the old/deprecated API, see torture_pki_generate_rsa(). + */ +static void torture_pki_generate_key_rsa(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_pki_ctx ctx = NULL; + int desired = 4096; + int invalid_size = 512; + + (void)state; + + /* Test with NULL context - should use default size */ + rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + assert_int_equal(ssh_key_type(key), SSH_KEYTYPE_RSA); + assert_int_equal(ssh_key_size(key), RSA_DEFAULT_KEY_SIZE); + SSH_KEY_FREE(key); + + /* Test with NULL key pointer - should fail */ + ctx = ssh_pki_ctx_new(); + assert_non_null(ctx); + + rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &desired); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* Test with invalid RSA key size (too small) - should fail */ + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_RSA_KEY_SIZE, + &invalid_size); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &desired); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + assert_int_equal(ssh_key_type(key), SSH_KEYTYPE_RSA); + assert_int_equal(ssh_key_size(key), desired); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + SSH_PKI_CTX_FREE(ctx); +} + +static int test_sign_verify_data(ssh_key key, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + ssh_signature sig; + ssh_key pubkey = NULL; + int rc; + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, hash_type, input, input_len); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, input, input_len); + assert_int_equal(rc, SSH_OK); + + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + + return rc; +} + +static void torture_pki_sign_data_rsa(void **state) +{ + int rc; + ssh_key key = NULL; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + if (!ssh_fips_mode()) { + /* Test using SHA1 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA1, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + } + + /* Test using SHA256 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test using SHA512 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA512, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + SSH_KEY_FREE(key); +} + +static void torture_pki_fail_sign_with_incompatible_hash(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + ssh_signature sig, bad_sig; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_AUTO */ + bad_sig = pki_sign_data(key, SSH_DIGEST_AUTO, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_AUTO */ + sig->hash_type = SSH_DIGEST_AUTO; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(key); +} + +static void +torture_pki_rsa_write_privkey_format(void **state, + enum ssh_file_format_e format) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_return_code(rc, errno); + assert_non_null(origkey); + + unlink(LIBSSH_RSA_TESTKEY); + + rc = ssh_pki_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_RSA_TESTKEY, + format); + assert_return_code(rc, errno); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_return_code(rc, errno); + assert_non_null(origkey); + + unlink(LIBSSH_RSA_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_RSA_TESTKEY_PASSPHRASE, + format); + assert_return_code(rc, errno); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} + +static void +torture_pki_rsa_write_privkey(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#if defined(HAVE_LIBCRYPTO) +static void +torture_pki_rsa_write_privkey_pem(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_rsa_write_privkey_openssh(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} + +static void +torture_pki_rsa_import_export_privkey_base64_pem(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_PEM); +} +static void +torture_pki_rsa_import_export_privkey_base64_openssh(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_OPENSSH); +} +#endif /* HAVE_LIBCRYPTO */ + +static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 1), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 1), + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); + +#ifndef HAVE_LIBCRYPTO + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 1), + NULL, + NULL, + NULL, + &key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); +#endif +} + +static void +torture_pki_rsa_import_openssh_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *keystring = NULL; + + (void) state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 1); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_pubkey_from_openssh_privkey, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_comment, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_whitespace, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_publickey_from_privatekey, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_rsa_import_privkey_base64_passphrase), + cmocka_unit_test( + torture_pki_rsa_import_openssh_privkey_base64_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_rsa_copy_cert_to_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_cert_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_generate_pubkey_from_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_duplicate_key, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_generate_rsa_deprecated), + cmocka_unit_test(torture_pki_rsa_key_size), + cmocka_unit_test(torture_pki_generate_key_rsa), + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey, + setup_rsa_key, + teardown), +#if defined(HAVE_LIBCRYPTO) + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey_pem, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey_openssh, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64_pem, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64_openssh, + setup_openssh_rsa_key, + teardown), +#endif /* HAVE_LIBCRYPTO */ + cmocka_unit_test(torture_pki_sign_data_rsa), + cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), + cmocka_unit_test_setup_teardown(torture_pki_rsa_sha2, + setup_rsa_key, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa_uri.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa_uri.c new file mode 100644 index 000000000000..645f546536ce --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa_uri.c @@ -0,0 +1,310 @@ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" +#define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" +#define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=%s" + +const char template[] = "/tmp/temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; +struct pki_st { + char *orig_dir; + char *temp_dir; + char *pub_uri; + char *priv_uri; + char *priv_uri_invalid_object; + char *priv_uri_invalid_token; + char *pub_uri_invalid_object; + char *pub_uri_invalid_token; +}; + +static int setup_tokens(void **state) +{ + char keys_path[1024] = {0}; + char keys_path_pub[1024] = {0}; + char *cwd = NULL; + struct pki_st *test_state = *state; + char obj_tempname[] = "label_XXXXXX"; + char pub_uri[1024] = {0}; + char priv_uri[1024] = {0}; + char pub_uri_invalid_object[1024] = {0}; + char priv_uri_invalid_object[1024] = {0}; + char pub_uri_invalid_token[1024] = {0}; + char priv_uri_invalid_token[1024] = {0}; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + ssh_tmpname(obj_tempname); + + snprintf(pub_uri, sizeof(pub_uri), PUB_URI_FMT, obj_tempname, obj_tempname); + + snprintf(priv_uri, sizeof(priv_uri), PRIV_URI_FMT, obj_tempname, obj_tempname, "1234"); + + snprintf(pub_uri_invalid_token, sizeof(pub_uri_invalid_token), PUB_URI_FMT, "invalid", + obj_tempname); + + snprintf(priv_uri_invalid_token, sizeof(priv_uri_invalid_token), PRIV_URI_FMT, "invalid", + obj_tempname, "1234"); + + snprintf(pub_uri_invalid_object, sizeof(pub_uri_invalid_object), PUB_URI_FMT, obj_tempname, + "invalid"); + + snprintf(priv_uri_invalid_object, sizeof(priv_uri_invalid_object), PRIV_URI_FMT, obj_tempname, + "invalid", "1234"); + + snprintf(keys_path, sizeof(keys_path), "%s%s%s", cwd, "/", LIBSSH_RSA_TESTKEY); + + snprintf(keys_path_pub, sizeof(keys_path_pub), "%s%s%s%s", cwd, "/", LIBSSH_RSA_TESTKEY, ".pub"); + + test_state->pub_uri = strdup(pub_uri); + test_state->priv_uri = strdup(priv_uri); + test_state->pub_uri_invalid_token = strdup(pub_uri_invalid_token); + test_state->pub_uri_invalid_object = strdup(pub_uri_invalid_object); + test_state->priv_uri_invalid_token = strdup(priv_uri_invalid_token); + test_state->priv_uri_invalid_object = strdup(priv_uri_invalid_object); + + torture_write_file(keys_path, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(keys_path_pub, + torture_get_testkey_pub_pem(SSH_KEYTYPE_RSA)); + + torture_setup_tokens(cwd, keys_path, obj_tempname, "1"); + + return 0; +} + +static int setup_directory_structure(void **state) +{ + struct pki_st *test_state = NULL; + char *temp_dir = NULL; + int rc; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + free(temp_dir); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + *state = test_state; + + rc = setup_tokens(state); + assert_int_equal(rc, 0); + + return 0; +} + +static int teardown_directory_structure(void **state) +{ + struct pki_st *test_state = *state; + int rc; + + torture_cleanup_tokens(test_state->temp_dir); + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state->priv_uri); + SAFE_FREE(test_state->pub_uri); + SAFE_FREE(test_state->priv_uri_invalid_object); + SAFE_FREE(test_state->pub_uri_invalid_object); + SAFE_FREE(test_state->priv_uri_invalid_token); + SAFE_FREE(test_state->pub_uri_invalid_token); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_rsa_import_pubkey_uri(void **state) +{ + ssh_key pubkey = NULL; + int rc; + struct pki_st *test_state = *state; + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_privkey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(privkey); +} + + +static void torture_pki_sign_verify_uri(void **state) +{ + int rc; + ssh_key privkey = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_rsa_publickey_from_privatekey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_uri_invalid_configurations(void **state) +{ + int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + + struct pki_st *test_state = *state; + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_object, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_token, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_object, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_token, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_rsa_import_pubkey_uri), + cmocka_unit_test(torture_pki_rsa_import_privkey_uri), + cmocka_unit_test(torture_pki_sign_verify_uri), + cmocka_unit_test(torture_pki_rsa_publickey_from_privatekey_uri), + cmocka_unit_test(torture_pki_rsa_uri_invalid_configurations), + }; + + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_directory_structure, + teardown_directory_structure); + + ssh_free(session); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk.c new file mode 100644 index 000000000000..10fadc24137f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk.c @@ -0,0 +1,536 @@ +/* + * torture_pki_sk.c - Torture tests for PKI security key functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "sk_common.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_sk.h" + +#include + +/** + * These tests can also be configured to run with the sk-usbhid callbacks + * instead of the default sk-dummy callbacks which can run in a CI + * environment. + * + * To run these tests with the sk-usbhid callbacks, at least one FIDO2 device + * must be connected and the environment variables TORTURE_SK_USBHID and + * TORTURE_SK_PIN must be set. + * + * The TORTURE_SK_PIN environment variable should contain the PIN used to + * unlock the FIDO2 device for operations. + * + * Note that these tests must be run in the order that they are defined in, as + * the signing tests rely on the output of the enrollment tests. + */ + +/* Test constants */ + +/* Default PIN value which will be overridden with the PIN set in the + * environment variable. */ +static const char *test_pin = NULL; +static const char *test_application = "ssh:test@example.com"; +static const unsigned char test_message[] = "Test signing data for SK keys"; + +static const char test_challenge[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + +/* Global keys for testing */ +static ssh_key g_ecdsa_key = NULL; +static ssh_key g_ed25519_key = NULL; + +static const struct ssh_sk_callbacks_struct *g_sk_callbacks = NULL; +bool valid_sk_callbacks = false; + +static int test_pin_callback(UNUSED_PARAM(const char *prompt), + char *buf, + size_t len, + UNUSED_PARAM(int echo), + UNUSED_PARAM(int verify), + UNUSED_PARAM(void *userdata)) +{ + size_t pin_len; + + if (test_pin == NULL) { + return SSH_ERROR; + } + + pin_len = strlen(test_pin); + if (pin_len + 1 > len) { + return -1; /* buffer too small */ + } + + memcpy(buf, test_pin, pin_len); + buf[pin_len] = '\0'; + return SSH_OK; +} + +static void torture_pki_sk_enroll_generic_key(enum ssh_keytypes_e key_type) +{ + ssh_key pubkey = NULL, reimported_privkey = NULL, reimported_pubkey = NULL; + ssh_pki_ctx enroll_ctx = NULL; + const char *privkey_filename = NULL; + const char *pubkey_filename = NULL; + const char *test_user_id = NULL; + ssh_key *ptr_to_g_key = NULL; + ssh_auth_callback pin_callback = NULL; + int rc; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Setup based on key type */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + privkey_filename = "test_sk_ecdsa_private.key"; + pubkey_filename = "test_sk_ecdsa_public.pub"; + test_user_id = "libssh_test_ecdsa_sk"; + ptr_to_g_key = &g_ecdsa_key; + break; + + case SSH_KEYTYPE_SK_ED25519: + privkey_filename = "test_sk_ed25519_private.key"; + pubkey_filename = "test_sk_ed25519_public.pub"; + test_user_id = "libssh_test_ed25519_sk"; + ptr_to_g_key = &g_ed25519_key; + break; + + default: + /* Should never reach here */ + assert_true(0); + return; + } + + if (test_pin != NULL) { + pin_callback = test_pin_callback; + } + + enroll_ctx = torture_create_sk_pki_ctx(test_application, + SSH_SK_USER_PRESENCE_REQD, + test_challenge, + sizeof(test_challenge), + pin_callback, + NULL, + test_user_id, + g_sk_callbacks); + assert_non_null(enroll_ctx); + + rc = ssh_pki_generate_key(key_type, enroll_ctx, ptr_to_g_key); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(*ptr_to_g_key, key_type, true); + + /* Export private key to file */ + rc = ssh_pki_export_privkey_file(*ptr_to_g_key, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + privkey_filename); + assert_int_equal(rc, SSH_OK); + + /* Extract public key from private key */ + rc = ssh_pki_export_privkey_to_pubkey(*ptr_to_g_key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Export public key to file */ + rc = ssh_pki_export_pubkey_file(pubkey, pubkey_filename); + assert_int_equal(rc, SSH_OK); + + /* Verify exported files by importing them back */ + rc = ssh_pki_import_privkey_file(privkey_filename, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &reimported_privkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(reimported_privkey); + + rc = ssh_pki_import_pubkey_file(pubkey_filename, &reimported_pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(reimported_pubkey); + + /* Verify keys match */ + rc = ssh_key_cmp(*ptr_to_g_key, reimported_privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(pubkey, reimported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(*ptr_to_g_key, reimported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(reimported_privkey, pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + /* Cleanup */ + unlink(privkey_filename); + unlink(pubkey_filename); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(reimported_privkey); + SSH_KEY_FREE(reimported_pubkey); + SSH_PKI_CTX_FREE(enroll_ctx); +} + +static void torture_pki_sk_enroll_ecdsa_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_key(SSH_KEYTYPE_SK_ECDSA); +} + +static void torture_pki_sk_enroll_ed25519_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_key(SSH_KEYTYPE_SK_ED25519); +} + +static void +torture_pki_sk_enroll_generic_resident_key(enum ssh_keytypes_e key_type) +{ + ssh_key resident_key = NULL; + ssh_pki_ctx enroll_ctx = NULL; + const char *test_user_id = NULL; + ssh_auth_callback pin_callback = NULL; + int rc, flags; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Setup based on key type */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + test_user_id = "libssh_test_ecdsa_sk"; + break; + + case SSH_KEYTYPE_SK_ED25519: + test_user_id = "libssh_test_ed25519_sk"; + break; + + default: + /* Should never reach here */ + assert_true(0); + return; + } + + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY | + SSH_SK_FORCE_OPERATION; + + if (test_pin != NULL) { + pin_callback = test_pin_callback; + } + + enroll_ctx = torture_create_sk_pki_ctx(test_application, + flags, + test_challenge, + sizeof(test_challenge), + pin_callback, + NULL, + test_user_id, + g_sk_callbacks); + assert_non_null(enroll_ctx); + + rc = ssh_pki_generate_key(key_type, enroll_ctx, &resident_key); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(resident_key, key_type, true); + + SSH_KEY_FREE(resident_key); + SSH_PKI_CTX_FREE(enroll_ctx); +} + +static void torture_pki_sk_enroll_ecdsa_resident_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_resident_key(SSH_KEYTYPE_SK_ECDSA); +} + +static void +torture_pki_sk_enroll_ed25519_resident_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_resident_key(SSH_KEYTYPE_SK_ED25519); +} + +static void torture_pki_sk_sign_generic_key(enum ssh_keytypes_e key_type) +{ + ssh_signature signature = NULL; + ssh_key public_key = NULL; + ssh_pki_ctx sign_ctx = NULL; + ssh_key *ptr_to_g_key = NULL; + ssh_auth_callback pin_callback = NULL; + int rc; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Select the appropriate global key based on key type */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + ptr_to_g_key = &g_ecdsa_key; + break; + + case SSH_KEYTYPE_SK_ED25519: + ptr_to_g_key = &g_ed25519_key; + break; + + default: + /* Should never reach here */ + assert_true(0); + return; + } + + assert_non_null(*ptr_to_g_key); + + rc = ssh_pki_export_privkey_to_pubkey(*ptr_to_g_key, &public_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(public_key); + + if (test_pin != NULL) { + pin_callback = test_pin_callback; + } + + sign_ctx = torture_create_sk_pki_ctx(test_application, + SSH_SK_USER_PRESENCE_REQD, + test_challenge, + sizeof(test_challenge), + pin_callback, + NULL, + NULL, + g_sk_callbacks); + assert_non_null(sign_ctx); + + signature = pki_sk_do_sign(sign_ctx, + *ptr_to_g_key, + test_message, + sizeof(test_message) - 1); + assert_non_null(signature); + assert_sk_signature_valid(signature, + key_type, + public_key, + test_message, + sizeof(test_message) - 1); + + SSH_SIGNATURE_FREE(signature); + SSH_KEY_FREE(public_key); + SSH_PKI_CTX_FREE(sign_ctx); +} + +static void torture_pki_sk_sign_ecdsa_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_sign_generic_key(SSH_KEYTYPE_SK_ECDSA); +} + +static void torture_pki_sk_sign_ed25519_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_sign_generic_key(SSH_KEYTYPE_SK_ED25519); +} + +static void torture_pki_sk_load_resident_keys(UNUSED_PARAM(void **state)) +{ + ssh_pki_ctx load_ctx = NULL; + ssh_key *resident_keys = NULL; + size_t num_keys = 0; + size_t i; + int rc; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks || torture_sk_is_using_sk_dummy()) { + skip(); + } + + load_ctx = ssh_pki_ctx_new(); + assert_non_null(load_ctx); + + assert_non_null(test_pin); + rc = ssh_pki_ctx_set_sk_pin_callback(load_ctx, test_pin_callback, NULL); + assert_int_equal(rc, SSH_OK); + + if (g_sk_callbacks != NULL) { + rc = ssh_pki_ctx_options_set(load_ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + g_sk_callbacks); + assert_int_equal(rc, SSH_OK); + } + + rc = ssh_sk_resident_keys_load(load_ctx, &resident_keys, &num_keys); + assert_int_equal(rc, SSH_OK); + assert_non_null(resident_keys); + assert_true(num_keys > 0); + + for (i = 0; i < num_keys; i++) { + ssh_key key = resident_keys[i]; + assert_non_null(key); + + assert_true(key->type == SSH_KEYTYPE_SK_ECDSA || + key->type == SSH_KEYTYPE_SK_ED25519); + + assert_true(key->sk_flags & SSH_SK_RESIDENT_KEY); + + assert_true(key->sk_flags & SSH_SK_USER_PRESENCE_REQD); + + if (key->type == SSH_KEYTYPE_SK_ECDSA) { + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + } else if (key->type == SSH_KEYTYPE_SK_ED25519) { + if (!ssh_fips_mode()) { + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + } + } + } + + for (i = 0; i < num_keys; i++) { + SSH_KEY_FREE(resident_keys[i]); + } + SAFE_FREE(resident_keys); + + SSH_PKI_CTX_FREE(load_ctx); +} + +static void +torture_pki_ctx_sk_callbacks_options_clear(UNUSED_PARAM(void **state)) +{ + ssh_pki_ctx ctx = NULL; + int rc; + + /* Test with NULL context - should return SSH_ERROR */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(NULL); + assert_int_equal(rc, SSH_ERROR); + + /* Create a new PKI context */ + ctx = ssh_pki_ctx_new(); + assert_non_null(ctx); + + /* Test clearing options on a context with no options set - should succeed + */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + /* Add some options to the context */ + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + "/dev/hidraw0", + false); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_USER_ID, + "test_user", + true); + assert_int_equal(rc, SSH_OK); + + /* Clear all options - should succeed */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + /* Verify that we can add options again after clearing */ + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + "/dev/hidraw1", + false); + assert_int_equal(rc, SSH_OK); + + /* Clear options again */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + /* Test multiple clears on same context - should succeed */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + SSH_PKI_CTX_FREE(ctx); +} + +/* Setup function to run before all tests */ +static int setup_global_state(UNUSED_PARAM(void **state)) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + const char *test_pin_env = NULL; + + sk_callbacks = torture_get_sk_callbacks(); + if (sk_callbacks != NULL) { + g_sk_callbacks = sk_callbacks; + valid_sk_callbacks = true; + } + + test_pin_env = torture_get_sk_pin(); + if (test_pin_env != NULL) { + test_pin = test_pin_env; + } + + return 0; +} + +/* Teardown function to run after all tests */ +static int teardown_global_state(UNUSED_PARAM(void **state)) +{ + + /* Clean up global keys */ + SSH_KEY_FREE(g_ecdsa_key); + SSH_KEY_FREE(g_ed25519_key); + + return 0; +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_sk_enroll_ecdsa_key), + cmocka_unit_test(torture_pki_sk_enroll_ed25519_key), + cmocka_unit_test(torture_pki_sk_enroll_ecdsa_resident_key), + cmocka_unit_test(torture_pki_sk_enroll_ed25519_resident_key), + cmocka_unit_test(torture_pki_sk_sign_ecdsa_key), + cmocka_unit_test(torture_pki_sk_sign_ed25519_key), + cmocka_unit_test(torture_pki_sk_load_resident_keys), + cmocka_unit_test(torture_pki_ctx_sk_callbacks_options_clear), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_global_state, + teardown_global_state); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ecdsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ecdsa.c new file mode 100644 index 000000000000..83e4e4c3f7d4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ecdsa.c @@ -0,0 +1,487 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include "torture_sk.h" + +/* Test constants */ +#define LIBSSH_SK_ECDSA_TESTKEY "libssh_testkey.id_ecdsa_sk" +#define LIBSSH_SK_ECDSA_TESTKEY_PASSPHRASE \ + "libssh_testkey_passphrase.id_ecdsa_sk" + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_sk_ecdsa_key(void **state) +{ + const char *keystring = NULL; + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + torture_write_file(LIBSSH_SK_ECDSA_TESTKEY, keystring); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 1); + torture_write_file(LIBSSH_SK_ECDSA_TESTKEY_PASSPHRASE, keystring); + + keystring = torture_get_testkey_pub(SSH_KEYTYPE_SK_ECDSA); + torture_write_file(LIBSSH_SK_ECDSA_TESTKEY ".pub", keystring); + + return 0; +} + +static int teardown(void **state) +{ + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_sk_ecdsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + rc = ssh_pki_import_pubkey_file(LIBSSH_SK_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_sk_ecdsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + rc = + ssh_pki_import_pubkey_file(LIBSSH_SK_ECDSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64(void **state) +{ + ssh_key privkey = NULL; + char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_pki_read_file(LIBSSH_SK_ECDSA_TESTKEY); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + SAFE_FREE(keystring); + SSH_KEY_FREE(privkey); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + keystring = torture_pki_read_file(LIBSSH_SK_ECDSA_TESTKEY); + assert_non_null(keystring); + + file_str_len = strlen(comment_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, keystring); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \t\t\t\n\n\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + keystring = torture_pki_read_file(LIBSSH_SK_ECDSA_TESTKEY); + assert_non_null(keystring); + + file_str_len = 2 * strlen(whitespace_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, + file_str_len, + "%s%s%s", + whitespace_str, + keystring, + whitespace_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_sk_ecdsa_import_export_privkey_base64(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + char *key_buf = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &origkey); + assert_return_code(rc, errno); + assert_sk_key_valid(origkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_base64(origkey, NULL, NULL, NULL, &key_buf); + assert_return_code(rc, errno); + assert_non_null(key_buf); + + rc = ssh_pki_import_privkey_base64(key_buf, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE_CHAR(key_buf); +} + +static void torture_pki_sk_ecdsa_publickey_from_privatekey(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + rc = ssh_key_cmp(privkey, pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64_passphrase(void **state) +{ + ssh_key privkey = NULL; + const char *keystring = NULL; + const char *passphrase = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 1); + assert_non_null(keystring); + + passphrase = torture_get_testkey_passphrase(); + assert_non_null(passphrase); + + /* Import with a passphrase */ + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + SSH_KEY_FREE(privkey); + + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); +} + +static void torture_pki_sk_ecdsa_duplicate_key(void **state) +{ + ssh_key privkey = NULL; + ssh_key duplicated = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + duplicated = ssh_key_dup(privkey); + assert_sk_key_valid(duplicated, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_key_cmp(privkey, duplicated, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(duplicated); +} + +static void torture_pki_sk_ecdsa_import_pubkey_base64(void **state) +{ + ssh_key key = NULL; + ssh_key pubkey = NULL; + char *b64_key = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + /* Import private key to extract public key */ + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + /* Export public key to base64 */ + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* Import public key from base64 */ + rc = ssh_pki_import_pubkey_base64(b64_key, SSH_KEYTYPE_SK_ECDSA, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE_CHAR(b64_key); +} + +static void torture_pki_sk_ecdsa_pubkey_blob(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_key imported_pubkey = NULL; + ssh_string pub_blob = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + /* Export public key to blob */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pub_blob); + assert_int_equal(rc, SSH_OK); + assert_non_null(pub_blob); + + /* Import public key from blob */ + rc = ssh_pki_import_pubkey_blob(pub_blob, &imported_pubkey); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(imported_pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + /* Compare keys */ + rc = ssh_key_cmp(pubkey, imported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + ssh_string_free(pub_blob); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(imported_pubkey); +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_sk_ecdsa_import_pubkey_file, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_pubkey_from_openssh_privkey, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64_comment, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64_whitespace, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_export_privkey_base64, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_publickey_from_privatekey, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_pubkey_base64, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64_passphrase, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_sk_ecdsa_duplicate_key, + setup_sk_ecdsa_key, + teardown), + + cmocka_unit_test_setup_teardown(torture_pki_sk_ecdsa_pubkey_blob, + setup_sk_ecdsa_key, + teardown), + + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ed25519.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ed25519.c new file mode 100644 index 000000000000..8641b5f5b83f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ed25519.c @@ -0,0 +1,543 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include "torture_sk.h" + +/* Test constants */ +#define LIBSSH_SK_ED25519_TESTKEY "libssh_testkey.id_ed25519_sk" +#define LIBSSH_SK_ED25519_TESTKEY_PASSPHRASE \ + "libssh_testkey_passphrase.id_ed25519_sk" + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_sk_ed25519_key(void **state) +{ + const char *keystring = NULL; + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + torture_write_file(LIBSSH_SK_ED25519_TESTKEY, keystring); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 1); + torture_write_file(LIBSSH_SK_ED25519_TESTKEY_PASSPHRASE, keystring); + + keystring = torture_get_testkey_pub(SSH_KEYTYPE_SK_ED25519); + torture_write_file(LIBSSH_SK_ED25519_TESTKEY ".pub", keystring); + + return 0; +} + +static int teardown(void **state) +{ + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_sk_ed25519_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_pubkey_file(LIBSSH_SK_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_sk_ed25519_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_pubkey_file(LIBSSH_SK_ED25519_TESTKEY_PASSPHRASE, + &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_sk_ed25519_import_privkey_base64(void **state) +{ + ssh_key privkey = NULL; + char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_pki_read_file(LIBSSH_SK_ED25519_TESTKEY); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + SAFE_FREE(keystring); + SSH_KEY_FREE(privkey); +} + +static void torture_pki_sk_ed25519_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_pki_read_file(LIBSSH_SK_ED25519_TESTKEY); + assert_non_null(keystring); + + file_str_len = strlen(comment_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, keystring); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void +torture_pki_sk_ed25519_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \t\t\t\n\n\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_pki_read_file(LIBSSH_SK_ED25519_TESTKEY); + assert_non_null(keystring); + + file_str_len = 2 * strlen(whitespace_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, + file_str_len, + "%s%s%s", + whitespace_str, + keystring, + whitespace_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_sk_ed25519_import_export_privkey_base64(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + char *key_buf = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &origkey); + assert_return_code(rc, errno); + assert_sk_key_valid(origkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_base64(origkey, NULL, NULL, NULL, &key_buf); + assert_return_code(rc, errno); + assert_non_null(key_buf); + + rc = ssh_pki_import_privkey_base64(key_buf, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE_CHAR(key_buf); +} + +static void torture_pki_sk_ed25519_publickey_from_privatekey(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + rc = ssh_key_cmp(privkey, pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_sk_ed25519_import_privkey_base64_passphrase(void **state) +{ + ssh_key privkey = NULL; + const char *keystring = NULL; + const char *passphrase = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 1); + assert_non_null(keystring); + + passphrase = torture_get_testkey_passphrase(); + assert_non_null(passphrase); + + /* Import with a passphrase */ + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + SSH_KEY_FREE(privkey); + + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); +} + +static void torture_pki_sk_ed25519_duplicate_key(void **state) +{ + ssh_key privkey = NULL; + ssh_key duplicated = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + duplicated = ssh_key_dup(privkey); + assert_sk_key_valid(duplicated, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_key_cmp(privkey, duplicated, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(duplicated); +} + +static void torture_pki_sk_ed25519_import_pubkey_base64(void **state) +{ + ssh_key key = NULL; + ssh_key pubkey = NULL; + char *b64_key = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + /* Import private key to extract public key */ + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + /* Export public key to base64 */ + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* Import public key from base64 */ + rc = ssh_pki_import_pubkey_base64(b64_key, SSH_KEYTYPE_SK_ED25519, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE_CHAR(b64_key); +} + +static void torture_pki_sk_ed25519_pubkey_blob(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_key imported_pubkey = NULL; + ssh_string pub_blob = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + /* Export public key to blob */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pub_blob); + assert_int_equal(rc, SSH_OK); + assert_non_null(pub_blob); + + /* Import public key from blob */ + rc = ssh_pki_import_pubkey_blob(pub_blob, &imported_pubkey); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(imported_pubkey, SSH_KEYTYPE_SK_ED25519, false); + + /* Compare keys */ + rc = ssh_key_cmp(pubkey, imported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + ssh_string_free(pub_blob); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(imported_pubkey); +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_pubkey_file, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_pubkey_from_openssh_privkey, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64_comment, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64_whitespace, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_export_privkey_base64, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_publickey_from_privatekey, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_pubkey_base64, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64_passphrase, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_sk_ed25519_duplicate_key, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_sk_ed25519_pubkey_blob, + setup_sk_ed25519_key, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sshsig.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sshsig.c new file mode 100644 index 000000000000..85b07a22ec6d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sshsig.c @@ -0,0 +1,831 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/pki.h" +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#ifdef WITH_FIDO2 +#include "libssh/libssh.h" +#include "torture_sk.h" +#endif + +#include +#include +#include + +/** + * The tests for the sk-type keys can also be configured to run with + * the sk-usbhid callbacks instead of the default sk-dummy callbacks which can + * run in a CI environment. + * + * To run these tests with the sk-usbhid callbacks, at least one FIDO2 device + * must be connected and the environment variables TORTURE_SK_USBHID must be + * set. + */ + +static const char template[] = "tmp_XXXXXX"; +static const char input[] = "Test input\0string with null byte"; +static const size_t input_len = sizeof(input) - 1; /* -1 to exclude final \0 */ +static const char *test_namespace = "file"; + +struct key_hash_combo { + enum ssh_keytypes_e key_type; + enum sshsig_digest_e hash_alg; + const char *key_name; +}; + +struct sshsig_st { + /* + * The original current working directory at the start of the test. + * + * During setup, the current working directory is changed to a newly + * created temporary directory (temp_dir). + * + * During cleanup, the current working directory is restored back + * to original_cwd. + */ + char *original_cwd; + char *temp_dir; + ssh_key rsa_key; + ssh_key ed25519_key; + ssh_key ecdsa_key; + +#ifdef WITH_FIDO2 + ssh_pki_ctx pki_ctx; + ssh_key sk_ecdsa_key; + ssh_key sk_ed25519_key; +#endif + + const char *ssh_keygen_path; + const struct key_hash_combo *test_combinations; + size_t num_combinations; +}; + +static struct key_hash_combo test_combinations[] = { + {SSH_KEYTYPE_RSA, SSHSIG_DIGEST_SHA2_256, "rsa"}, + {SSH_KEYTYPE_RSA, SSHSIG_DIGEST_SHA2_512, "rsa"}, + {SSH_KEYTYPE_ED25519, SSHSIG_DIGEST_SHA2_256, "ed25519"}, + {SSH_KEYTYPE_ED25519, SSHSIG_DIGEST_SHA2_512, "ed25519"}, +#ifdef HAVE_ECC + {SSH_KEYTYPE_ECDSA_P256, SSHSIG_DIGEST_SHA2_256, "ecdsa"}, + {SSH_KEYTYPE_ECDSA_P256, SSHSIG_DIGEST_SHA2_512, "ecdsa"}, +# ifdef WITH_FIDO2 + {SSH_KEYTYPE_SK_ECDSA, SSHSIG_DIGEST_SHA2_256, "sk_ecdsa"}, + {SSH_KEYTYPE_SK_ECDSA, SSHSIG_DIGEST_SHA2_512, "sk_ecdsa"}, +# endif /* WITH_FIDO2 */ +#endif /* HAVE_ECC */ + +#ifdef WITH_FIDO2 + {SSH_KEYTYPE_SK_ED25519, SSHSIG_DIGEST_SHA2_256, "sk_ed25519"}, + {SSH_KEYTYPE_SK_ED25519, SSHSIG_DIGEST_SHA2_512, "sk_ed25519"}, +#endif +}; + +static ssh_key get_test_key(struct sshsig_st *test_state, + enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA: + return test_state->rsa_key; + case SSH_KEYTYPE_ED25519: + if (ssh_fips_mode()) { + return NULL; + } else { + return test_state->ed25519_key; + } +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA_P256: + return test_state->ecdsa_key; +# ifdef WITH_FIDO2 + case SSH_KEYTYPE_SK_ECDSA: + return test_state->sk_ecdsa_key; +# endif /* WITH_FIDO2 */ +#endif /* HAVE_ECC */ + +#ifdef WITH_FIDO2 + case SSH_KEYTYPE_SK_ED25519: + if (ssh_fips_mode()) { + return NULL; + } else { + return test_state->sk_ed25519_key; + } +#endif + default: + return NULL; + } +} + +static int setup_sshsig_compat(void **state) +{ + struct sshsig_st *test_state = NULL; + char *original_cwd = NULL; + char *temp_dir = NULL; + int rc = 0; + +#ifdef WITH_FIDO2 + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; +#endif + + test_state = calloc(1, sizeof(struct sshsig_st)); + assert_non_null(test_state); + + original_cwd = torture_get_current_working_dir(); + assert_non_null(original_cwd); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + test_state->original_cwd = original_cwd; + test_state->temp_dir = temp_dir; + test_state->test_combinations = test_combinations; + test_state->num_combinations = + sizeof(test_combinations) / sizeof(test_combinations[0]); + + *state = test_state; + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + /* Check if openssh is available and supports SSH signatures */ +#ifdef OPENSSH_SUPPORTS_SSHSIG + test_state->ssh_keygen_path = SSH_KEYGEN_EXECUTABLE; +#else + test_state->ssh_keygen_path = NULL; + printf("OpenSSH version does not support SSH signatures (requires " + "8.1+), skipping compatibility tests\n"); +#endif /* OPENSSH_SUPPORTS_SSHSIG */ + + /* Load pre-generated test keys using torture functions */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + NULL, + NULL, + NULL, + &test_state->rsa_key); + assert_int_equal(rc, SSH_OK); + + /* Skip ed25519 if in FIPS mode */ + if (!ssh_fips_mode()) { + /* mbedtls and libgcrypt don't fully support PKCS#8 PEM */ + /* thus parse the key with OpenSSH */ + rc = ssh_pki_import_privkey_base64( + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0), + NULL, + NULL, + NULL, + &test_state->ed25519_key); + assert_int_equal(rc, SSH_OK); + } + +#ifdef HAVE_ECC + rc = ssh_pki_import_privkey_base64( + torture_get_testkey(SSH_KEYTYPE_ECDSA_P256, 0), + NULL, + NULL, + NULL, + &test_state->ecdsa_key); + assert_int_equal(rc, SSH_OK); +#endif + +#ifdef WITH_FIDO2 + /* Create and configure PKI context for SK operations */ + sk_callbacks = torture_get_sk_callbacks(); + if (sk_callbacks != NULL) { + test_state->pki_ctx = ssh_pki_ctx_new(); + assert_non_null(test_state->pki_ctx); + + rc = ssh_pki_ctx_options_set(test_state->pki_ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + sk_callbacks); + assert_int_equal(rc, SSH_OK); + +# ifdef HAVE_ECC + rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ECDSA, + test_state->pki_ctx, + &test_state->sk_ecdsa_key); + assert_int_equal(rc, SSH_OK); +# endif /* HAVE_ECC */ + + if (!ssh_fips_mode()) { + rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ED25519, + test_state->pki_ctx, + &test_state->sk_ed25519_key); + assert_int_equal(rc, SSH_OK); + } + } + +#endif /* WITH_FIDO2 */ + + /* Write keys to files for openssh compatibility testing */ + if (test_state->ssh_keygen_path != NULL) { + torture_write_file("test_rsa", torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file("test_rsa.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + if (!ssh_fips_mode()) { + torture_write_file( + "test_ed25519", + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file("test_ed25519.pub", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + } + +#ifdef HAVE_ECC + torture_write_file("test_ecdsa", + torture_get_testkey(SSH_KEYTYPE_ECDSA_P256, 0)); + torture_write_file("test_ecdsa.pub", + torture_get_testkey_pub(SSH_KEYTYPE_ECDSA_P256)); +#endif /* HAVE_ECC */ + +#ifdef WITH_FIDO2 +# ifdef HAVE_ECC + /* Write SK keys to files if they were successfully generated */ + if (test_state->sk_ecdsa_key != NULL) { + char *sk_ecdsa_priv = NULL; + char *sk_ecdsa_pub = NULL; + + rc = ssh_pki_export_privkey_base64(test_state->sk_ecdsa_key, + NULL, + NULL, + NULL, + &sk_ecdsa_priv); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_export_pubkey_base64(test_state->sk_ecdsa_key, + &sk_ecdsa_pub); + assert_int_equal(rc, SSH_OK); + + torture_write_file("test_sk_ecdsa", sk_ecdsa_priv); + torture_write_file("test_sk_ecdsa.pub", sk_ecdsa_pub); + + SAFE_FREE(sk_ecdsa_priv); + SAFE_FREE(sk_ecdsa_pub); + } +# endif /* HAVE_ECC */ + + if (!ssh_fips_mode() && test_state->sk_ed25519_key != NULL) { + char *sk_ed25519_priv = NULL; + char *sk_ed25519_pub = NULL; + + rc = ssh_pki_export_privkey_base64(test_state->sk_ed25519_key, + NULL, + NULL, + NULL, + &sk_ed25519_priv); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_export_pubkey_base64(test_state->sk_ed25519_key, + &sk_ed25519_pub); + assert_int_equal(rc, SSH_OK); + + torture_write_file("test_sk_ed25519", sk_ed25519_priv); + torture_write_file("test_sk_ed25519.pub", sk_ed25519_pub); + + SAFE_FREE(sk_ed25519_priv); + SAFE_FREE(sk_ed25519_pub); + } +#endif /* WITH_FIDO2 */ + + rc = chmod("test_rsa", 0600); + assert_return_code(rc, errno); + if (!ssh_fips_mode()) { + rc = chmod("test_ed25519", 0600); + assert_return_code(rc, errno); + } +#ifdef HAVE_ECC + rc = chmod("test_ecdsa", 0600); + assert_return_code(rc, errno); +#endif + +#ifdef WITH_FIDO2 + /* Set permissions for SK key files */ +# ifdef HAVE_ECC + if (test_state->sk_ecdsa_key != NULL) { + rc = chmod("test_sk_ecdsa", 0600); + assert_return_code(rc, errno); + } +# endif /* HAVE_ECC */ + if (!ssh_fips_mode() && test_state->sk_ed25519_key != NULL) { + rc = chmod("test_sk_ed25519", 0600); + assert_return_code(rc, errno); + } +#endif /* WITH_FIDO2 */ + } + + return 0; +} + +static int teardown_sshsig_compat(void **state) +{ + struct sshsig_st *test_state = *state; + int rc = 0; + + assert_non_null(test_state); + + ssh_key_free(test_state->rsa_key); + ssh_key_free(test_state->ed25519_key); + ssh_key_free(test_state->ecdsa_key); + +#ifdef WITH_FIDO2 + SSH_PKI_CTX_FREE(test_state->pki_ctx); + ssh_key_free(test_state->sk_ecdsa_key); + ssh_key_free(test_state->sk_ed25519_key); +#endif + + rc = torture_change_dir(test_state->original_cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->original_cwd); + SAFE_FREE(test_state); + + return 0; +} + +static int run_openssh_command(const char *cmd) +{ + char full_cmd[2048]; + int rc; + +#if defined(WITH_FIDO2) && defined(SK_DUMMY_LIBRARY_PATH) + /* Set SSH_SK_PROVIDER to sk-dummy library when using sk-dummy callbacks */ + if (torture_sk_is_using_sk_dummy()) { + snprintf(full_cmd, + sizeof(full_cmd), + "SSH_SK_PROVIDER=\"%s\" %s", + SK_DUMMY_LIBRARY_PATH, + cmd); + } else { + snprintf(full_cmd, sizeof(full_cmd), "%s", cmd); + } +#else + snprintf(full_cmd, sizeof(full_cmd), "%s", cmd); +#endif + + rc = system(full_cmd); + return WIFEXITED(rc) ? WEXITSTATUS(rc) : -1; +} + +static void torture_pki_sshsig_armor_dearmor(UNUSED_PARAM(void **state)) +{ + ssh_buffer test_buffer = NULL; + ssh_buffer dearmored_buffer = NULL; + char *armored_sig = NULL; + const char test_data[] = "test signature data"; + int rc; + + test_buffer = ssh_buffer_new(); + assert_non_null(test_buffer); + + rc = ssh_buffer_add_data(test_buffer, test_data, strlen(test_data)); + assert_int_equal(rc, SSH_OK); + + rc = sshsig_armor(test_buffer, &armored_sig); + assert_int_equal(rc, SSH_OK); + assert_non_null(armored_sig); + + /* Test with NULL armored_sig */ + rc = sshsig_armor(test_buffer, NULL); + assert_int_equal(rc, SSH_ERROR); + + assert_non_null(strstr(armored_sig, SSHSIG_BEGIN_SIGNATURE)); + assert_non_null(strstr(armored_sig, SSHSIG_END_SIGNATURE)); + + /* Test with NULL dearmored_buffer */ + rc = sshsig_dearmor(armored_sig, NULL); + assert_int_equal(rc, SSH_ERROR); + + rc = sshsig_dearmor(armored_sig, &dearmored_buffer); + assert_int_equal(rc, SSH_OK); + assert_non_null(dearmored_buffer); + + assert_int_equal(ssh_buffer_get_len(test_buffer), + ssh_buffer_get_len(dearmored_buffer)); + assert_memory_equal(ssh_buffer_get(test_buffer), + ssh_buffer_get(dearmored_buffer), + ssh_buffer_get_len(test_buffer)); + + ssh_buffer_free(test_buffer); + ssh_buffer_free(dearmored_buffer); + free(armored_sig); +} + +static void torture_pki_sshsig_armor_dearmor_invalid(UNUSED_PARAM(void **state)) +{ + ssh_buffer dearmored_buffer = NULL; + char *armored_sig = NULL; + int rc; + const char *invalid_sig = "-----BEGIN INVALID SIGNATURE-----\n" + "data\n" + "-----END INVALID SIGNATURE-----\n"; + + const char *incomplete_sig = "-----BEGIN SSH SIGNATURE----\n" + "U1NIU0lH\n"; + + /* Test with NULL buffer */ + rc = sshsig_armor(NULL, &armored_sig); + assert_int_equal(rc, SSH_ERROR); + + /* Test dearmoring with invalid signature */ + rc = sshsig_dearmor(invalid_sig, &dearmored_buffer); + assert_int_equal(rc, SSH_ERROR); + + /* Test dearmoring with NULL input */ + rc = sshsig_dearmor(NULL, &dearmored_buffer); + assert_int_equal(rc, SSH_ERROR); + + /* Test dearmoring with missing end marker */ + rc = sshsig_dearmor(incomplete_sig, &dearmored_buffer); + assert_int_equal(rc, SSH_ERROR); +} + +static void test_libssh_sign_verify_combo(struct sshsig_st *test_state, + const struct key_hash_combo *combo) +{ + char *signature = NULL; + ssh_key verify_key = NULL; + ssh_key test_key = NULL; + ssh_pki_ctx pki_context = NULL; + int rc; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + skip(); + } + + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not available + */ + skip(); + } + + assert_non_null(test_key); + +#ifdef WITH_FIDO2 + /* Use PKI context for SK keys */ + if (is_sk_key_type(combo->key_type)) { + pki_context = test_state->pki_ctx; + } +#endif + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + test_namespace, + combo->hash_alg, + &signature); + assert_int_equal(rc, SSH_OK); + assert_non_null(signature); + + rc = + sshsig_verify(input, input_len, signature, test_namespace, &verify_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_key); + + rc = ssh_key_cmp(test_key, verify_key, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + ssh_key_free(verify_key); + free(signature); +} + +static void +test_openssh_sign_libssh_verify_combo(struct sshsig_st *test_state, + const struct key_hash_combo *combo) +{ + char cmd[1024]; + char *openssh_sig = NULL; + ssh_key verify_key = NULL; + ssh_key test_key = NULL; + FILE *fp = NULL; + int rc; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + skip(); + } + + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not available + */ + skip(); + } + + fp = fopen("test_message.txt", "wb"); + assert_non_null(fp); + /* Write binary data including null byte */ + rc = fwrite(input, input_len, 1, fp); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + snprintf(cmd, + sizeof(cmd), + "%s -Y sign -f test_%s -n %s test_message.txt", + test_state->ssh_keygen_path, + combo->key_name, + test_namespace); + rc = run_openssh_command(cmd); + + assert_int_equal(rc, 0); + openssh_sig = torture_pki_read_file("test_message.txt.sig"); + assert_non_null(openssh_sig); + + rc = sshsig_verify(input, + input_len, + openssh_sig, + test_namespace, + &verify_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_key); + + ssh_key_free(verify_key); + free(openssh_sig); + rc = unlink("test_message.txt.sig"); + assert_return_code(rc, errno); + rc = unlink("test_message.txt"); + assert_return_code(rc, errno); +} + +static void +test_libssh_sign_openssh_verify_combo(struct sshsig_st *test_state, + const struct key_hash_combo *combo) +{ + char *libssh_sig = NULL; + char cmd[1024]; + FILE *fp = NULL; + int rc; + char *pubkey_b64 = NULL; + ssh_key test_key = NULL; + ssh_pki_ctx pki_context = NULL; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + skip(); + } + + printf("Testing key type: %s\n", combo->key_name); + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not available + */ + skip(); + } + assert_non_null(test_key); + +#ifdef WITH_FIDO2 + /* Use PKI context for SK keys */ + if (is_sk_key_type(combo->key_type)) { + pki_context = test_state->pki_ctx; + } +#endif + + fp = fopen("test_message.txt", "wb"); + assert_non_null(fp); + /* Write binary data including null byte */ + rc = fwrite(input, input_len, 1, fp); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + test_namespace, + combo->hash_alg, + &libssh_sig); + assert_int_equal(rc, SSH_OK); + assert_non_null(libssh_sig); + + fp = fopen("test_message.txt.sig", "w"); + assert_non_null(fp); + rc = fputs(libssh_sig, fp); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + rc = ssh_pki_export_pubkey_base64(test_key, &pubkey_b64); + assert_int_equal(rc, SSH_OK); + + fp = fopen("allowed_signers", "w"); + assert_non_null(fp); + rc = fprintf(fp, "test %s %s\n", test_key->type_c, pubkey_b64); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + snprintf(cmd, + sizeof(cmd), + "%s -Y verify -f allowed_signers -I test -n %s -s " + "test_message.txt.sig < test_message.txt", + test_state->ssh_keygen_path, + test_namespace); + rc = run_openssh_command(cmd); + assert_int_equal(rc, 0); + + free(libssh_sig); + free(pubkey_b64); + rc = unlink("test_message.txt.sig"); + assert_return_code(rc, errno); + rc = unlink("allowed_signers"); + assert_return_code(rc, errno); + rc = unlink("test_message.txt"); + assert_return_code(rc, errno); +} + +static void torture_sshsig_libssh_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + size_t i; + + for (i = 0; i < test_state->num_combinations; i++) { + test_libssh_sign_verify_combo(test_state, + &test_state->test_combinations[i]); + } +} + +static void torture_sshsig_openssh_libssh_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + size_t i; + + if (test_state->ssh_keygen_path == NULL) { + skip(); + } + + for (i = 0; i < test_state->num_combinations; i++) { + test_openssh_sign_libssh_verify_combo( + test_state, + &test_state->test_combinations[i]); + } +} + +static void torture_sshsig_libssh_openssh_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + size_t i; + + if (test_state->ssh_keygen_path == NULL) { + skip(); + } + + for (i = 0; i < test_state->num_combinations; i++) { + test_libssh_sign_openssh_verify_combo( + test_state, + &test_state->test_combinations[i]); + } +} + +static void torture_sshsig_error_cases_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + char *signature = NULL; + ssh_key verify_key = NULL; + int rc; + size_t i; + char tampered_data[] = "Tampered\0data"; + + for (i = 0; i < test_state->num_combinations; i++) { + const struct key_hash_combo *combo = &test_state->test_combinations[i]; + ssh_key test_key = NULL; + ssh_pki_ctx pki_context = NULL; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + continue; + } + + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not + * available */ + continue; + } + assert_non_null(test_key); + +#ifdef WITH_FIDO2 + if (is_sk_key_type(combo->key_type)) { + pki_context = test_state->pki_ctx; + } +#endif + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + "", /* Test empty string namespace */ + combo->hash_alg, + &signature); + assert_int_equal(rc, SSH_ERROR); + assert_null(signature); + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + test_namespace, + combo->hash_alg, + &signature); + assert_int_equal(rc, SSH_OK); + assert_non_null(signature); + + rc = sshsig_verify(input, + input_len, + signature, + "wrong_namespace", + &verify_key); + assert_int_equal(rc, SSH_ERROR); + assert_null(verify_key); + + rc = sshsig_verify(input, + input_len, + signature, + "", /* Test empty string namespace */ + &verify_key); + assert_int_equal(rc, SSH_ERROR); + assert_null(verify_key); + + rc = sshsig_verify(tampered_data, + sizeof(tampered_data) - 1, + signature, + test_namespace, + &verify_key); + assert_int_equal(rc, SSH_ERROR); + assert_null(verify_key); + + free(signature); + signature = NULL; + } + + /* Test invalid hash algorithm */ + rc = sshsig_sign(input, + input_len, + test_state->rsa_key, + NULL, /* pki_context */ + test_namespace, + 2, + &signature); + assert_int_equal(rc, SSH_ERROR); + + /* Test NULL parameters */ + rc = sshsig_sign(input, + input_len, + NULL, + NULL, /* pki_context */ + test_namespace, + SSHSIG_DIGEST_SHA2_256, + &signature); + assert_int_equal(rc, SSH_ERROR); + + rc = + sshsig_verify(input, input_len, "invalid", test_namespace, &verify_key); + assert_int_equal(rc, SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_sshsig_armor_dearmor), + cmocka_unit_test(torture_pki_sshsig_armor_dearmor_invalid), + /* Comprehensive combination tests */ + cmocka_unit_test_setup_teardown(torture_sshsig_libssh_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + cmocka_unit_test_setup_teardown( + torture_sshsig_openssh_libssh_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + cmocka_unit_test_setup_teardown( + torture_sshsig_libssh_openssh_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + + /* Comprehensive error case testing */ + cmocka_unit_test_setup_teardown( + torture_sshsig_error_cases_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_push_pop_dir.c b/src/libs/libssh-0.12.2/tests/unittests/torture_push_pop_dir.c new file mode 100644 index 000000000000..faf1086606b5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_push_pop_dir.c @@ -0,0 +1,78 @@ +#include "config.h" + +#include "torture.h" +#define LIBSSH_STATIC + +const char template[] = "temp_dir_XXXXXX"; + +static int setup(void **state) +{ + char *temp_dir = NULL; + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + *state = (void *)temp_dir; + + return 0; +} + +static int teardown(void **state) +{ + char *temp_dir = *((char **)state); + + torture_rmdirs((const char *)temp_dir); + + free(temp_dir); + + return 0; +} + +static void torture_back_and_forth(void **state) +{ + char *temp_dir = *((char **)state); + char *cwd = NULL; + char *after_change = NULL; + char *after_changing_back = NULL; + int rc = 0; + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + printf("Current dir: %s\n", cwd); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + after_change = torture_get_current_working_dir(); + assert_non_null(after_change); + + printf("Current dir after change: %s\n", after_change); + + rc = torture_change_dir(cwd); + assert_int_equal(rc, 0); + + after_changing_back = torture_get_current_working_dir(); + assert_non_null(after_changing_back); + + printf("Back to dir: %s\n", after_changing_back); + + SAFE_FREE(cwd); + SAFE_FREE(after_change); + SAFE_FREE(after_changing_back); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_back_and_forth, + setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_rand.c b/src/libs/libssh-0.12.2/tests/unittests/torture_rand.c new file mode 100644 index 000000000000..58abc7c9593d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_rand.c @@ -0,0 +1,83 @@ +#include "config.h" + +#define LIBSSH_STATIC +#include +#include +#include +#include +#include "torture.h" + +#ifdef HAVE_LIBGCRYPT +#define NUM_LOOPS 1000 +#else +/* openssl is much faster */ +#define NUM_LOOPS 20000 +#endif +#define NUM_THREADS 100 + +static int setup(void **state) { + int rc; + + (void) state; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + return 0; +} + +static int teardown(void **state) { + (void) state; + + ssh_finalize(); + + return 0; +} + +static void *torture_rand_thread(void *threadid) { + char buffer[12]; + int i; + int ok; + + (void) threadid; + + buffer[0] = buffer[1] = buffer[10] = buffer[11] = 'X'; + for(i = 0; i < NUM_LOOPS; ++i) { + ok = ssh_get_random(&buffer[2], i % 8 + 1, 0); + assert_true(ok); + } + + pthread_exit(NULL); +} + +static void torture_rand_threading(void **state) { + pthread_t threads[NUM_THREADS]; + int i; + int err; + + (void) state; + + for(i = 0; i < NUM_THREADS; ++i) { + err = pthread_create(&threads[i], NULL, torture_rand_thread, NULL); + assert_int_equal(err, 0); + } + for(i = 0; i < NUM_THREADS; ++i) { + err=pthread_join(threads[i], NULL); + assert_int_equal(err, 0); + } +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_rand_threading, setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_server_direct_tcpip.c b/src/libs/libssh-0.12.2/tests/unittests/torture_server_direct_tcpip.c new file mode 100644 index 000000000000..bad0896b0cf0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_server_direct_tcpip.c @@ -0,0 +1,265 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" + +#include + +#define TEST_SERVER_HOST "127.0.0.1" +#define TEST_SERVER_PORT 2222 +#define TEST_DEST_HOST "127.0.0.1" +#define TEST_DEST_PORT 12345 +#define TEST_ORIG_HOST "127.0.0.1" +#define TEST_ORIG_PORT 54321 + +struct hostkey_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) +{ + struct hostkey_state *h = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + h = malloc(sizeof(struct hostkey_state)); + assert_non_null(h); + + h->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + assert_non_null(h->hostkey_path); + + mask = umask(S_IRWXO | S_IRWXG); + h->fd = mkstemp(h->hostkey_path); + umask(mask); + assert_return_code(h->fd, errno); + close(h->fd); + + h->key_type = SSH_KEYTYPE_ECDSA_P256; + h->hostkey = torture_get_testkey(h->key_type, 0); + + torture_write_file(h->hostkey_path, h->hostkey); + + *state = h; + + return 0; +} + +static int teardown(void **state) +{ + struct hostkey_state *h = (struct hostkey_state *)*state; + + unlink(h->hostkey_path); + free(h->hostkey_path); + free(h); + + ssh_finalize(); + + return 0; +} + +static void *client_thread(void *arg) +{ + unsigned int test_port = TEST_SERVER_PORT; + int rc; + ssh_session session = NULL; + ssh_channel channel = NULL; + bool should_accept = *(bool *)arg; + + session = + torture_ssh_session(NULL, TEST_SERVER_HOST, &test_port, "foo", "bar"); + assert_non_null(session); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + /* Open a direct-tcpip channel instead of a session channel */ + rc = ssh_channel_open_forward(channel, + TEST_DEST_HOST, + TEST_DEST_PORT, + TEST_ORIG_HOST, + TEST_ORIG_PORT); + if (should_accept) { + assert_int_equal(rc, SSH_OK); + } else { + assert_int_equal(rc, SSH_ERROR); + } + + /* Close the channel and session */ + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_free(session); + + return NULL; +} + +static int auth_password_accept(ssh_session session, + const char *user, + const char *password, + void *userdata) +{ + /* unused */ + (void)session; + (void)user; + (void)password; + (void)userdata; + + return SSH_AUTH_SUCCESS; +} + +struct channel_data { + /* Whether the callback should accept the channel open request */ + bool should_accept; + + int req_seen; + char *dest_host; + uint32_t dest_port; + char *orig_host; + uint32_t orig_port; +}; + +static ssh_channel channel_direct_tcpip_callback(ssh_session session, + const char *dest_host, + int dest_port, + const char *orig_host, + int orig_port, + void *userdata) +{ + struct channel_data *channel_data = userdata; + ssh_channel channel = NULL; + + /* Record that we've seen a direct-tcpip request and store the parameters */ + channel_data->req_seen = 1; + channel_data->dest_host = strdup(dest_host); + channel_data->dest_port = dest_port; + channel_data->orig_host = strdup(orig_host); + channel_data->orig_port = orig_port; + + /* Create and return a new channel for this request */ + if (channel_data->should_accept) { + channel = ssh_channel_new(session); + } + return channel; +} + +static void torture_ssh_channel_direct_tcpip(void **state, int should_accept) +{ + struct hostkey_state *h = (struct hostkey_state *)*state; + int rc, event_rc; + pthread_t client_pthread; + ssh_bind sshbind = NULL; + ssh_session server = NULL; + ssh_event event = NULL; + + struct channel_data channel_data; + struct ssh_server_callbacks_struct server_cb = { + .userdata = &channel_data, + .auth_password_function = auth_password_accept, + .channel_open_request_direct_tcpip_function = + channel_direct_tcpip_callback, + }; + + memset(&channel_data, 0, sizeof(channel_data)); + ssh_callbacks_init(&server_cb); + + /* Create server */ + sshbind = torture_ssh_bind(TEST_SERVER_HOST, + TEST_SERVER_PORT, + h->key_type, + h->hostkey_path); + assert_non_null(sshbind); + + channel_data.should_accept = should_accept; + + /* Get client to connect */ + rc = pthread_create(&client_pthread, + NULL, + client_thread, + &channel_data.should_accept); + assert_return_code(rc, errno); + + server = ssh_new(); + assert_non_null(server); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Handle client connection */ + ssh_set_server_callbacks(server, &server_cb); + + rc = ssh_handle_key_exchange(server); + assert_int_equal(rc, SSH_OK); + + event = ssh_event_new(); + assert_non_null(event); + + ssh_event_add_session(event, server); + + event_rc = SSH_OK; + while (!channel_data.req_seen && event_rc == SSH_OK) { + event_rc = ssh_event_dopoll(event, -1); + } + + /* Cleanup */ + ssh_event_free(event); + ssh_free(server); + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); + + /* Verify direct-tcpip request parameters */ + assert_true(channel_data.req_seen); + assert_string_equal(channel_data.dest_host, TEST_DEST_HOST); + assert_int_equal(channel_data.dest_port, TEST_DEST_PORT); + assert_string_equal(channel_data.orig_host, TEST_ORIG_HOST); + assert_int_equal(channel_data.orig_port, TEST_ORIG_PORT); + + /* Free allocated memory */ + free(channel_data.dest_host); + free(channel_data.orig_host); +} + +static void torture_ssh_channel_direct_tcpip_success(void **state) +{ + torture_ssh_channel_direct_tcpip(state, true); +} + +static void torture_ssh_channel_direct_tcpip_failure(void **state) +{ + torture_ssh_channel_direct_tcpip(state, false); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_ssh_channel_direct_tcpip_success, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_ssh_channel_direct_tcpip_failure, + setup, + teardown), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_server_x11.c b/src/libs/libssh-0.12.2/tests/unittests/torture_server_x11.c new file mode 100644 index 000000000000..98423cf786c9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_server_x11.c @@ -0,0 +1,238 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include + +#define TEST_SERVER_HOST "127.0.0.1" +#define TEST_SERVER_PORT 2222 + +struct hostkey_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) { + struct hostkey_state *h; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + h = malloc(sizeof(struct hostkey_state)); + assert_non_null(h); + + h->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + + mask = umask(S_IRWXO | S_IRWXG); + h->fd = mkstemp(h->hostkey_path); + umask(mask); + assert_return_code(h->fd, errno); + close(h->fd); + + h->key_type = SSH_KEYTYPE_ECDSA_P256; + h->hostkey = torture_get_testkey(h->key_type, 0); + + torture_write_file(h->hostkey_path, h->hostkey); + + *state = h; + + return 0; +} + +static int teardown(void **state) { + struct hostkey_state *h = (struct hostkey_state *)*state; + + unlink(h->hostkey); + free(h->hostkey_path); + free(h); + + ssh_finalize(); + + return 0; +} + +/* For x11_screen_number, need something that is not equal to htonl + itself */ +static const uint32_t x11_screen_number = 1; + +static void *client_thread(void *arg) { + unsigned int test_port = TEST_SERVER_PORT; + int rc; + ssh_session session; + ssh_channel channel; + + /* unused */ + (void)arg; + + usleep(200); + session = torture_ssh_session(NULL, TEST_SERVER_HOST, + &test_port, + "foo", "bar"); + assert_non_null(session); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_int_equal(rc, SSH_OK); + + rc = ssh_channel_request_x11(channel, 0, NULL, NULL, + (uint32_t)x11_screen_number); + assert_int_equal(rc, SSH_OK); + + ssh_free(session); + return NULL; +} + +static int auth_password_accept(ssh_session session, + const char *user, + const char *password, + void *userdata) { + /* unused */ + (void)session; + (void)user; + (void)password; + (void)userdata; + + return SSH_AUTH_SUCCESS; +} + +struct channel_data { + int req_seen; + uint32_t screen_number; +}; + +static void ssh_channel_x11_req(ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata) { + struct channel_data *channel_data = userdata; + + /* unused */ + (void)session; + (void)channel; + (void)single_connection; + (void)auth_protocol; + (void)auth_cookie; + + /* We've seen an x11 request. Record the screen number */ + channel_data->req_seen = 1; + channel_data->screen_number = screen_number; +} + +static ssh_channel channel_open(ssh_session session, void *userdata) { + ssh_channel channel = NULL; + ssh_channel_callbacks channel_cb = userdata; + + /* unused */ + (void)userdata; + + channel = ssh_channel_new(session); + if (channel == NULL) { + goto out; + } + ssh_set_channel_callbacks(channel, channel_cb); + + out: + return channel; +} + +static void test_ssh_channel_request_x11(void **state) { + struct hostkey_state *h = (struct hostkey_state *)*state; + int rc, event_rc; + pthread_t client_pthread; + ssh_bind sshbind; + ssh_session server; + ssh_event event; + + struct channel_data channel_data; + struct ssh_channel_callbacks_struct channel_cb = { + .userdata = &channel_data, + .channel_x11_req_function = ssh_channel_x11_req + }; + struct ssh_server_callbacks_struct server_cb = { + .userdata = &channel_cb, + .auth_password_function = auth_password_accept, + .channel_open_request_session_function = channel_open + }; + + memset(&channel_data, 0, sizeof(channel_data)); + ssh_callbacks_init(&channel_cb); + ssh_callbacks_init(&server_cb); + + /* Create server */ + sshbind = torture_ssh_bind(TEST_SERVER_HOST, + TEST_SERVER_PORT, + h->key_type, + h->hostkey_path); + assert_non_null(sshbind); + + /* Get client to connect */ + rc = pthread_create(&client_pthread, NULL, client_thread, NULL); + assert_return_code(rc, errno); + + server = ssh_new(); + assert_non_null(server); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Handle client connection */ + ssh_set_server_callbacks(server, &server_cb); + + rc = ssh_handle_key_exchange(server); + assert_int_equal(rc, SSH_OK); + + event = ssh_event_new(); + assert_non_null(event); + + ssh_event_add_session(event, server); + + event_rc = SSH_OK; + while (!channel_data.req_seen && event_rc == SSH_OK) { + event_rc = ssh_event_dopoll(event, -1); + } + + /* Cleanup */ + ssh_event_free(event); + ssh_free(server); + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); + + assert_true(channel_data.req_seen); + assert_int_equal(channel_data.screen_number, + x11_screen_number); +} + +int torture_run_tests(void) { + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_ssh_channel_request_x11, + setup, + teardown) + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_session_keys.c b/src/libs/libssh-0.12.2/tests/unittests/torture_session_keys.c new file mode 100644 index 000000000000..1ae3831213c1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_session_keys.c @@ -0,0 +1,107 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/bignum.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" + +uint8_t key[32] = + "\xf7\xa0\xe6\xdf\x1f\x87\x7d\x22\x68\xd2\xc4\xb0\xc5\x93\xa4" + "\x8e\x30\x17\xc6\xab\xca\xf3\x9a\xa4\x9f\x7b\xed\x51\xb1\xe8" + "\x8a\x42"; +uint8_t secret[32] = + "\x33\x64\x8e\x7f\xea\xd9\xd7\xee\x89\x4f\xd8\xd0\xe5\x83\x00" + "\x3d\x53\x17\xbc\xa8\x8b\x6b\x2a\x31\x50\xcc\x08\xe9\xea\x87" + "\xb4\x23"; + +uint8_t eIV[32] = + "\x9a\x2b\x40\x9d\x29\x8e\x22\x70\x86\xdf\x0e\x72\x9b\x91\x31" + "\x90\x5d\x69\xc5\x87\x79\x83\x72\x63\x4e\x67\xf5\x9e\x00\x77" + "\x8c\x7f"; +uint8_t dIV[32] = + "\x10\xdd\x7f\x31\x6d\xe3\x49\x28\xbf\x99\x80\x08\x16\xb3\x99" + "\xff\x8c\x61\x9b\xb9\xc2\xdd\x40\xfb\x36\xf9\x97\xd8\x8c\x55" + "\xbf\xa0"; +uint8_t eK[24] = + "\xe1\x99\x36\xb8\xe6\x1f\x3d\x54\xc3\xa2\xdd\x79\xf0\xfe\x78" + "\x9e\x87\xd5\x05\x54\x26\x34\x21\xd0"; +uint8_t dK[24] = + "\xf8\xdd\xc3\xea\x5a\x59\x98\xb9\x86\xaa\x77\x29\x67\x51\x46" + "\x21\x73\xc2\x6a\x6b\xed\xf2\x49\x98"; +uint8_t encrypt_MAC[32] = + "\x0f\xbd\x1f\xe9\x2a\xaa\x84\xdc\xb5\xfc\xfb\x68\x2c\xa5\xe0" + "\xba\xf2\x6f\xe5\x80\xee\x8f\x5c\x5b\x30\x55\x25\xb3\x7b\x21" + "\xdc\xe5"; +uint8_t decrypt_MAC[32] = + "\xa3\x52\x6e\x72\xa8\x8b\xde\xc5\x68\x66\x89\xae\x0a\xd2\x83" + "\x23\x21\x4b\x3f\x04\x2e\x7f\x86\x04\x0f\xa8\x04\x3c\x62\xad" + "\x74\x91"; + +struct ssh_cipher_struct fake_in_cipher = { + .keysize = 192 +}; + +struct ssh_cipher_struct fake_out_cipher = { + .keysize = 192 +}; + +struct ssh_crypto_struct test_crypto = { + .session_id_len = 32, + .session_id = secret, + .digest_len = 32, + .secret_hash = secret, + .in_cipher = &fake_in_cipher, + .out_cipher = &fake_out_cipher, + .in_hmac = SSH_HMAC_SHA256, + .out_hmac = SSH_HMAC_SHA256, + .digest_type = SSH_KDF_SHA256, +}; + +struct ssh_session_struct session = { + .next_crypto = &test_crypto +}; + +static void torture_session_keys(UNUSED_PARAM(void **state)) +{ + ssh_string k_string; + int rc; + + k_string = ssh_string_new(32); + rc = ssh_string_fill(k_string, key, 32); + assert_int_equal(rc, 0); + + test_crypto.shared_secret = ssh_make_string_bn(k_string); + SSH_STRING_FREE(k_string); + + rc = ssh_generate_session_keys(&session); + assert_int_equal(rc, 0); + + assert_memory_equal(test_crypto.encryptIV, eIV, 32); + assert_memory_equal(test_crypto.decryptIV, dIV, 32); + assert_memory_equal(test_crypto.encryptkey, eK, 24); + assert_memory_equal(test_crypto.decryptkey, dK, 24); + assert_memory_equal(test_crypto.encryptMAC, encrypt_MAC, 32); + assert_memory_equal(test_crypto.decryptMAC, decrypt_MAC, 32); + + bignum_safe_free(test_crypto.shared_secret); + SAFE_FREE(test_crypto.encryptIV); + SAFE_FREE(test_crypto.decryptIV); + SAFE_FREE(test_crypto.encryptkey); + SAFE_FREE(test_crypto.decryptkey); + SAFE_FREE(test_crypto.encryptMAC); + SAFE_FREE(test_crypto.decryptMAC); +} + +int torture_run_tests(void) { + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_session_keys), + }; + + ssh_init(); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_sk_usbhid.c b/src/libs/libssh-0.12.2/tests/unittests/torture_sk_usbhid.c new file mode 100644 index 000000000000..19ab720d3dda --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_sk_usbhid.c @@ -0,0 +1,383 @@ +/* + * torture_sk_usbhid.c - Torture tests for security key USB-HID + * callbacks. + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/sk_common.h" +#include "torture.h" +#include "torture_sk.h" + +/** + * These tests require at least one FIDO2 device to be connected + * and the environment variables TORTURE_SK_USBHID and TORTURE_SK_PIN to be set. + * + * If TORTURE_SK_USBHID is not set, these tests will be skipped. + * To enable these tests, set both environment variables before running: + * + * export TORTURE_SK_USBHID=1 + * export TORTURE_SK_PIN=your_device_pin + * + * The TORTURE_SK_PIN environment variable should contain the PIN used to + * unlock the FIDO2 device for operations. + * + * Note that these tests must be run in the order that they are defined in, as + * the signing tests rely on the output of the enrollment tests. + */ + +static const char *test_pin = NULL; +static const char *test_application = "ssh:test@example.com"; + +static const uint8_t dummy_data[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + +/* Global variables to store key handles for signing tests */ +static uint8_t *ecdsa_key_handle = NULL; +static size_t ecdsa_key_handle_len = 0; +static uint8_t *ed25519_key_handle = NULL; +static size_t ed25519_key_handle_len = 0; + +/* Check if tests should run */ +static bool should_run_tests(void) +{ + char *env = getenv("TORTURE_SK_USBHID"); + return (env != NULL && env[0] != '\0'); +} + +static struct sk_option **create_user_id_option(const char *user_id) +{ + struct sk_option **array = NULL, *option = NULL; + + array = calloc(2, sizeof(struct sk_option *)); + assert_non_null(array); + + option = calloc(1, sizeof(struct sk_option)); + assert_non_null(option); + + option->name = strdup(SSH_SK_OPTION_NAME_USER_ID); + assert_non_null(option->name); + option->value = strdup(user_id); + assert_non_null(option->value); + option->required = 0; + + array[0] = option; + array[1] = NULL; + + return array; +} + +static void torture_sk_usbhid_enroll_generic_key(enum ssh_keytypes_e key_type) +{ + const struct ssh_sk_callbacks_struct *callbacks = NULL; + struct sk_enroll_response *response = NULL; + struct sk_option **options = NULL; + const char *user_id = NULL; + uint8_t **key_handle_out = NULL; + size_t *key_handle_len_out = NULL; + int rc, flags; + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, enroll)); + + /* Setup based on key type */ + switch (key_type) { + case SSH_SK_ECDSA: + user_id = "libssh_test_ecdsa_sk"; + key_handle_out = &ecdsa_key_handle; + key_handle_len_out = &ecdsa_key_handle_len; + break; + case SSH_SK_ED25519: + user_id = "libssh_test_ed25519_sk"; + key_handle_out = &ed25519_key_handle; + key_handle_len_out = &ed25519_key_handle_len; + break; + default: + /* Should never reach here */ + assert_true(0); + return; + } + + options = create_user_id_option(user_id); + + /* Enroll non-resident key */ + flags = SSH_SK_USER_PRESENCE_REQD; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_enroll_response(response, flags); + + /* Store the non-resident key handle for signing tests */ + *key_handle_out = calloc(response->key_handle_len, 1); + assert_non_null(*key_handle_out); + memcpy(*key_handle_out, response->key_handle, response->key_handle_len); + *key_handle_len_out = response->key_handle_len; + + SK_ENROLL_RESPONSE_FREE(response); + SK_OPTIONS_FREE(options); +} + +static void +torture_sk_usbhid_enroll_generic_resident_key(enum ssh_keytypes_e key_type) +{ + const struct ssh_sk_callbacks_struct *callbacks = NULL; + struct sk_enroll_response *response = NULL; + struct sk_option **options = NULL; + const char *user_id = NULL; + int rc, flags; + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, enroll)); + + /* Setup based on key type */ + switch (key_type) { + case SSH_SK_ECDSA: + user_id = "libssh_test_ecdsa_sk"; + break; + case SSH_SK_ED25519: + user_id = "libssh_test_ed25519_sk"; + break; + default: + /* Should never reach here */ + assert_true(0); + return; + } + + options = create_user_id_option(user_id); + + /* Enroll first resident key */ + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY | + SSH_SK_FORCE_OPERATION; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_enroll_response(response, flags); + SK_ENROLL_RESPONSE_FREE(response); + + /* Try to enroll same resident key again - should fail with + * SSH_SK_ERR_CREDENTIAL_EXISTS */ + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_SK_ERR_CREDENTIAL_EXISTS); + SK_ENROLL_RESPONSE_FREE(response); + + /* The force operation flag should overwrite the existing resident key with + * new one */ + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY | + SSH_SK_FORCE_OPERATION; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_enroll_response(response, flags); + SK_ENROLL_RESPONSE_FREE(response); + SK_OPTIONS_FREE(options); +} + +static void torture_sk_usbhid_enroll_ecdsa_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_key(SSH_SK_ECDSA); +} + +static void torture_sk_usbhid_enroll_ed25519_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_key(SSH_SK_ED25519); +} + +static void +torture_sk_usbhid_enroll_ecdsa_resident_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_resident_key(SSH_SK_ECDSA); +} + +static void +torture_sk_usbhid_enroll_ed25519_resident_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_resident_key(SSH_SK_ED25519); +} + +static void torture_sk_usbhid_sign_generic(enum ssh_keytypes_e key_type) +{ + const struct ssh_sk_callbacks_struct *callbacks; + struct sk_sign_response *response = NULL; + uint8_t *key_handle = NULL; + size_t key_handle_len = 0; + int rc, flags; + + /* Setup based on key type */ + switch (key_type) { + case SSH_SK_ECDSA: + key_handle = ecdsa_key_handle; + key_handle_len = ecdsa_key_handle_len; + break; + case SSH_SK_ED25519: + key_handle = ed25519_key_handle; + key_handle_len = ed25519_key_handle_len; + break; + default: + /* Should never reach here */ + assert_true(0); + return; + } + + assert_non_null(key_handle); + assert_true(key_handle_len > 0); + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, sign)); + + flags = SSH_SK_USER_PRESENCE_REQD; + rc = callbacks->sign(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + key_handle, + key_handle_len, + flags, + test_pin, + NULL, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_sign_response(response, key_type); + + SK_SIGN_RESPONSE_FREE(response); +} + +static void torture_sk_usbhid_sign_ecdsa(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_sign_generic(SSH_SK_ECDSA); +} + +static void torture_sk_usbhid_sign_ed25519(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_sign_generic(SSH_SK_ED25519); +} + +static void torture_sk_usbhid_load_resident_keys(UNUSED_PARAM(void **state)) +{ + const struct ssh_sk_callbacks_struct *callbacks; + struct sk_resident_key **resident_keys = NULL; + size_t num_keys = 0; + int rc; + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, load_resident_keys)); + + rc = callbacks->load_resident_keys(test_pin, + NULL, + &resident_keys, + &num_keys); + assert_int_equal(rc, SSH_OK); + assert_non_null(resident_keys); + assert_true(num_keys > 0); + + for (size_t i = 0; i < num_keys; i++) { + assert_sk_resident_key(resident_keys[i]); + SK_RESIDENT_KEY_FREE(resident_keys[i]); + } + + free(resident_keys); +} + +static int setup(UNUSED_PARAM(void **state)) +{ + const char *test_pin_env = NULL; + + test_pin_env = torture_get_sk_pin(); + if (test_pin_env != NULL) { + test_pin = test_pin_env; + } + + return 0; +} + +static int cleanup(UNUSED_PARAM(void **state)) +{ + SAFE_FREE(ecdsa_key_handle); + SAFE_FREE(ed25519_key_handle); + + return 0; +} + +int torture_run_tests(void) +{ + int rc; + bool should_run; + + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_sk_usbhid_enroll_ecdsa_key), + cmocka_unit_test(torture_sk_usbhid_enroll_ed25519_key), + cmocka_unit_test(torture_sk_usbhid_enroll_ecdsa_resident_key), + cmocka_unit_test(torture_sk_usbhid_enroll_ed25519_resident_key), + cmocka_unit_test(torture_sk_usbhid_sign_ecdsa), + cmocka_unit_test(torture_sk_usbhid_sign_ed25519), + cmocka_unit_test(torture_sk_usbhid_load_resident_keys), + }; + + /* + * Only run tests if TORTURE_SK_USBHID environment variable is set + * and we expect a FIDO2 device to be available. + */ + should_run = should_run_tests(); + if (!should_run) { + printf("Skipping sk_usbhid tests: TORTURE_SK_USBHID not set\n"); + return 0; /* Success, but no tests run */ + } + + ssh_init(); + rc = cmocka_run_group_tests(tests, setup, cleanup); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_string.c b/src/libs/libssh-0.12.2/tests/unittests/torture_string.c new file mode 100644 index 000000000000..404676cbe4fc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_string.c @@ -0,0 +1,424 @@ +/* + * torture_string.c - torture tests for ssh_string functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#define LIBSSH_STATIC + +#include "libssh/string.h" +#include "string.c" +#include "torture.h" + +static void torture_ssh_string_new(void **state) +{ + struct ssh_string_struct *str = NULL; + + (void)state; + + /* Test normal allocation */ + str = ssh_string_new(100); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), 100); + ssh_string_free(str); + + /* Test zero size */ + str = ssh_string_new(0); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), 0); + ssh_string_free(str); + + /* Test maximum size */ + str = ssh_string_new(STRING_SIZE_MAX - 1); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), STRING_SIZE_MAX - 1); + ssh_string_free(str); + + /* Test size too large - should fail */ + str = ssh_string_new(STRING_SIZE_MAX + 1); + assert_null(str); + assert_int_equal(errno, EINVAL); +} + +static void torture_ssh_string_from_char(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_string = "Hello, World!"; + const char *empty_string = ""; + + (void)state; + + /* Test normal string */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), strlen(test_string)); + assert_memory_equal(ssh_string_data(str), test_string, strlen(test_string)); + ssh_string_free(str); + + /* Test empty string */ + str = ssh_string_from_char(empty_string); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), 0); + ssh_string_free(str); + + /* Test NULL input */ + str = ssh_string_from_char(NULL); + assert_null(str); + assert_int_equal(errno, EINVAL); +} + +static void torture_ssh_string_from_data(void **state) +{ + ssh_string s; + const unsigned char raw[] = {0x00, 0x01, 0x00, 0x42, 0xFF}; + + (void)state; + + /* Basic: copy arbitrary binary data (with embedded NUL) */ + s = ssh_string_from_data(raw, sizeof(raw)); + assert_non_null(s); + assert_int_equal(ssh_string_len(s), sizeof(raw)); + assert_memory_equal(ssh_string_data(s), raw, sizeof(raw)); + ssh_string_free(s); + + /* Empty: len == 0 with NULL data returns empty string */ + s = ssh_string_from_data(NULL, 0); + assert_non_null(s); + assert_int_equal(ssh_string_len(s), 0); + ssh_string_free(s); + + /* Invalid: len > 0 with NULL data fails and sets errno */ + errno = 0; + s = ssh_string_from_data(NULL, 42); + assert_null(s); + assert_int_equal(errno, EINVAL); +} + +static void torture_ssh_string_fill(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_data = "Test data"; + int rc; + + (void)state; + + /* Test normal fill */ + str = ssh_string_new(20); + assert_non_null(str); + + rc = ssh_string_fill(str, test_data, strlen(test_data)); + assert_int_equal(rc, 0); + assert_memory_equal(ssh_string_data(str), test_data, strlen(test_data)); + ssh_string_free(str); + + /* Test fill with exact size */ + str = ssh_string_new(strlen(test_data)); + assert_non_null(str); + + rc = ssh_string_fill(str, test_data, strlen(test_data)); + assert_int_equal(rc, 0); + ssh_string_free(str); + + /* Test NULL data */ + str = ssh_string_new(10); + assert_non_null(str); + + rc = ssh_string_fill(str, NULL, 5); + assert_int_equal(rc, -1); + ssh_string_free(str); + + /* Test zero length */ + str = ssh_string_new(10); + assert_non_null(str); + + rc = ssh_string_fill(str, test_data, 0); + assert_int_equal(rc, -1); + ssh_string_free(str); +} + +static void torture_ssh_string_to_char(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_string = "Convert to char"; + char *result = NULL; + + (void)state; + + /* Test normal string */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + + result = ssh_string_to_char(str); + assert_non_null(result); + assert_string_equal(result, test_string); + + ssh_string_free_char(result); + ssh_string_free(str); + + /* Test empty string */ + str = ssh_string_from_char(""); + assert_non_null(str); + + result = ssh_string_to_char(str); + assert_non_null(result); + assert_string_equal(result, ""); + + ssh_string_free_char(result); + ssh_string_free(str); + + /* Test NULL string */ + result = ssh_string_to_char(NULL); + assert_null(result); +} + +static void torture_ssh_string_copy(void **state) +{ + struct ssh_string_struct *str = NULL, *copy = NULL; + const char *test_string = "Copy me!"; + + (void)state; + + /* Test normal copy */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + + copy = ssh_string_copy(str); + assert_non_null(copy); + assert_int_equal(ssh_string_len(copy), ssh_string_len(str)); + assert_memory_equal(ssh_string_data(copy), + ssh_string_data(str), + ssh_string_len(str)); + + /* Ensure they are different objects */ + assert_ptr_not_equal(str, copy); + assert_ptr_not_equal(ssh_string_data(str), ssh_string_data(copy)); + + ssh_string_free(str); + ssh_string_free(copy); + + /* Test copy of empty string */ + str = ssh_string_from_char(""); + assert_non_null(str); + + copy = ssh_string_copy(str); + assert_non_null(copy); + assert_int_equal(ssh_string_len(copy), 0); + + ssh_string_free(str); + ssh_string_free(copy); + + /* Test NULL string */ + copy = ssh_string_copy(NULL); + assert_null(copy); +} + +static void torture_ssh_string_burn(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_string = "Secret data"; + void *data = NULL; + size_t len; + int i; + + (void)state; + + /* Test burning a string */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + + data = ssh_string_data(str); + len = ssh_string_len(str); + + /* Verify data is there initially */ + assert_memory_equal(data, test_string, len); + + /* Burn the string */ + ssh_string_burn(str); + + /* Verify data is zeroed out */ + for (i = 0; i < (int)len; i++) { + assert_int_equal(((unsigned char *)data)[i], 0); + } + + ssh_string_free(str); + + /* Test burning NULL string (should not crash) */ + ssh_string_burn(NULL); + + /* Test burning zero-size string */ + str = ssh_string_new(0); + assert_non_null(str); + ssh_string_burn(str); + ssh_string_free(str); +} + +static void torture_ssh_string_cmp(void **state) +{ + struct ssh_string_struct *str1 = NULL, *str2 = NULL; + const char *test_string1 = "Hello, World!"; + const char *test_string2 = "Hello, libssh"; + const char *test_string3 = "Hello"; + const char *test_string4 = "Apple"; + + const char data1[] = "Hello\x00World!"; + const char data2[] = "Hello\x00libssh"; + const char data3[] = "Hello"; + + int rc; + (void)state; + + /* Test comparing two NULL strings - should be equal */ + assert_int_equal(ssh_string_cmp(NULL, NULL), 0); + + /* Test comparing NULL with non-NULL string - NULL should be less */ + str1 = ssh_string_from_char(test_string1); + assert_non_null(str1); + assert_true(ssh_string_cmp(NULL, str1) < 0); + assert_true(ssh_string_cmp(str1, NULL) > 0); + ssh_string_free(str1); + + /* Test comparing empty strings */ + str1 = ssh_string_from_char(""); + str2 = ssh_string_from_char(""); + assert_non_null(str1); + assert_non_null(str2); + + /* Both empty strings should be equal */ + assert_int_equal(ssh_string_cmp(str1, str2), 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing empty string with non-empty string */ + str1 = ssh_string_from_char(""); + str2 = ssh_string_from_char("test"); + assert_non_null(str1); + assert_non_null(str2); + + /* Empty string should be less than non-empty string */ + assert_true(ssh_string_cmp(str1, str2) < 0); + assert_true(ssh_string_cmp(str2, str1) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing strings where one is a prefix of another */ + str1 = ssh_string_from_char(test_string1); /* "Hello, World!" */ + str2 = ssh_string_from_char(test_string3); /* "Hello" - prefix */ + assert_non_null(str1); + assert_non_null(str2); + + /* "Hello" is shorter and a prefix, so it should be < "Hello, World!" */ + assert_true(ssh_string_cmp(str2, str1) < 0); + assert_true(ssh_string_cmp(str1, str2) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing different strings with same length */ + str1 = ssh_string_from_char(test_string1); /* "Hello, World!" */ + str2 = ssh_string_from_char(test_string2); /* "Hello, libssh" */ + assert_non_null(str1); + assert_non_null(str2); + + /* "Hello, World!" vs "Hello, libssh" - 'W' < 'l' */ + assert_true(ssh_string_cmp(str1, str2) < 0); + assert_true(ssh_string_cmp(str2, str1) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing strings with different lengths and different characters */ + str1 = ssh_string_from_char(test_string1); /* "Hello, World!" */ + str2 = ssh_string_from_char(test_string4); /* "Apple" */ + assert_non_null(str1); + assert_non_null(str2); + + /* 'A' < 'H' so "Apple" < "Hello, World!" */ + assert_true(ssh_string_cmp(str2, str1) < 0); + assert_true(ssh_string_cmp(str1, str2) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing identical strings - should be equal */ + str1 = ssh_string_from_char(test_string1); + str2 = ssh_string_from_char(test_string1); + assert_non_null(str1); + assert_non_null(str2); + assert_int_equal(ssh_string_cmp(str1, str2), 0); + assert_int_equal(ssh_string_cmp(str2, str1), 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing strings with embedded null characters */ + str1 = ssh_string_new(sizeof(data1)); /* "Hello\x00World!" */ + str2 = ssh_string_new(sizeof(data3)); /* "Hello" */ + assert_non_null(str1); + assert_non_null(str2); + rc = ssh_string_fill(str1, data1, sizeof(data1)); + assert_int_equal(rc, 0); + rc = ssh_string_fill(str2, data3, sizeof(data3)); + assert_int_equal(rc, 0); + + /* "Hello\x00World!" > "Hello" because its length is greater */ + assert_true(ssh_string_cmp(str1, str2) > 0); /* data1 > data3 */ + assert_true(ssh_string_cmp(str2, str1) < 0); /* data3 < data1 */ + ssh_string_free(str1); + ssh_string_free(str2); + + /* Comparing binary strings with same length, but different characters */ + str1 = ssh_string_new(sizeof(data1)); /* "Hello\x00World!" */ + str2 = ssh_string_new(sizeof(data2)); /* "Hello\x00libssh" */ + assert_non_null(str1); + assert_non_null(str2); + rc = ssh_string_fill(str1, data1, sizeof(data1)); + assert_int_equal(rc, 0); + rc = ssh_string_fill(str2, data2, sizeof(data2)); + assert_int_equal(rc, 0); + + /* 'W' < 'l' so str1 < str2 */ + assert_true(ssh_string_cmp(str1, str2) < 0); /* data1 < data2 */ + assert_true(ssh_string_cmp(str2, str1) > 0); /* data2 > data1 */ + ssh_string_free(str1); + ssh_string_free(str2); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_string_new), + cmocka_unit_test(torture_ssh_string_from_char), + cmocka_unit_test(torture_ssh_string_from_data), + cmocka_unit_test(torture_ssh_string_fill), + cmocka_unit_test(torture_ssh_string_to_char), + cmocka_unit_test(torture_ssh_string_copy), + cmocka_unit_test(torture_ssh_string_burn), + cmocka_unit_test(torture_ssh_string_cmp), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_temp_dir.c b/src/libs/libssh-0.12.2/tests/unittests/torture_temp_dir.c new file mode 100644 index 000000000000..feff23c7f1ea --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_temp_dir.c @@ -0,0 +1,51 @@ +#include "config.h" + +#include "torture.h" +#define LIBSSH_STATIC + +const char template[] = "temp_dir_XXXXXX"; + +static int setup(void **state) +{ + char *temp_dir = NULL; + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + *state = (void *)temp_dir; + + return 0; +} + +static int teardown(void **state) +{ + char *temp_dir = *((char **)state); + + torture_rmdirs((const char *)temp_dir); + + free(temp_dir); + + return 0; +} + + +static void torture_create_temp_dir(void **state) +{ + char *temp_dir = *((char **)state); + + printf("Created temp dir: %s\n", temp_dir); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_create_temp_dir, setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_temp_file.c b/src/libs/libssh-0.12.2/tests/unittests/torture_temp_file.c new file mode 100644 index 000000000000..793d6c12ce5a --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_temp_file.c @@ -0,0 +1,63 @@ +#include "config.h" + +#include "torture.h" +#define LIBSSH_STATIC + +const char template[] = "temp_file_XXXXXX"; + +static int setup(void **state) +{ + char *file_name = NULL; + + file_name = torture_create_temp_file(template); + assert_non_null(file_name); + + *state = (void *)file_name; + + return 0; +} + +static int teardown(void **state) +{ + int rc; + char *file_name = *((char **)state); + + assert_non_null(file_name); + + rc = unlink(file_name); + assert_int_equal(rc, 0); + + SAFE_FREE(file_name); + + return 0; +} + + +static void torture_temp_file(void **state) +{ + char *file_name = *((char **)state); + FILE *fp = NULL; + + assert_non_null(file_name); + + fp = fopen(file_name, "r"); + assert_non_null(fp); + + fclose(fp); + + printf("Created temp file: %s\n", file_name); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_temp_file, setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_threads_buffer.c b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_buffer.c new file mode 100644 index 000000000000..dfd9c57186b2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_buffer.c @@ -0,0 +1,602 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#define DEBUG_BUFFER +#include "buffer.c" + +#include + +#define NUM_THREADS 20 + +#define BUFFER_LIMIT (8 * 1024 * 1024) + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result = NULL; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity + */ +static void *thread_growing_buffer(void *threadid) +{ + ssh_buffer buffer = NULL; + int i; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + for (i = 0; i < BUFFER_LIMIT; ++i) { + ssh_buffer_add_data(buffer,"A",1); + if (buffer->used >= 128) { + if (ssh_buffer_get_len(buffer) * 2 < buffer->allocated) { + assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated); + } + } + } + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_growing_buffer(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_growing_buffer); + assert_int_equal(rc, 0); +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity, when we remove 1 byte after each call (sliding window) + */ +static void *thread_growing_buffer_shifting(void *threadid) +{ + ssh_buffer buffer; + int i; + unsigned char c; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + /* dummy analyzers ... */ + return NULL; + } + ssh_buffer_set_secure(buffer); + + + for (i = 0; i < 1024; ++i) { + ssh_buffer_add_data(buffer,"S",1); + } + + for (i = 0; i < BUFFER_LIMIT; ++i) { + ssh_buffer_get_u8(buffer,&c); + ssh_buffer_add_data(buffer,"A",1); + if (buffer->used >= 128) { + if (ssh_buffer_get_len(buffer) * 4 < buffer->allocated) { + assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated); + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); + } + } + } + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_growing_buffer_shifting(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_growing_buffer_shifting); + assert_int_equal(rc, 0); +} + +/* + * Test the behavior of ssh_buffer_prepend_data + */ +static void *thread_buffer_prepend(void *threadid) +{ + ssh_buffer buffer = NULL; + uint32_t v; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + ssh_buffer_add_data(buffer, "abcdef", 6); + ssh_buffer_prepend_data(buffer, "xyz", 3); + assert_int_equal(ssh_buffer_get_len(buffer), 9); + assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9); + + /* Now remove 4 bytes and see if we can replace them */ + ssh_buffer_get_u32(buffer, &v); + assert_int_equal(ssh_buffer_get_len(buffer), 5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer, "aris", 4); + assert_int_equal(ssh_buffer_get_len(buffer), 9); + assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9); + + /* same thing but we add 5 bytes now */ + ssh_buffer_get_u32(buffer, &v); + assert_int_equal(ssh_buffer_get_len(buffer), 5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer, "12345", 5); + assert_int_equal(ssh_buffer_get_len(buffer), 10); + assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_buffer_prepend(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_buffer_prepend); + assert_int_equal(rc, 0); +} + +/* + * Test the behavior of ssh_buffer_get_ssh_string with invalid data + */ +static void *thread_ssh_buffer_get_ssh_string(void *threadid) +{ + ssh_buffer buffer = NULL; + size_t i, j, k, l; + int rc; + /* some values that can go wrong */ + uint32_t values[] = { + 0xffffffff, 0xfffffffe, 0xfffffffc, 0xffffff00, + 0x80000000, 0x80000004, 0x7fffffff}; + char data[128] = {0}; + + /* Unused */ + (void)threadid; + + memset(data, 'X', sizeof(data)); + + for (i = 0; i < ARRAY_SIZE(values); ++i) { + for (j = 0; j < (int)sizeof(data); ++j) { + for (k = 1; k < 5; ++k) { + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + for (l = 0; l < k; ++l) { + rc = ssh_buffer_add_u32(buffer, htonl(values[i])); + assert_int_equal(rc, 0); + } + rc = ssh_buffer_add_data(buffer,data,j); + assert_int_equal(rc, 0); + for (l = 0; l < k; ++l) { + ssh_string str = ssh_buffer_get_ssh_string(buffer); + assert_null(str); + SSH_STRING_FREE(str); + } + SSH_BUFFER_FREE(buffer); + } + } + } + + pthread_exit(NULL); +} + +static void torture_ssh_buffer_get_ssh_string(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_get_ssh_string); + assert_int_equal(rc, 0); +} + +static void *thread_ssh_buffer_add_format(void *threadid) +{ + ssh_buffer buffer = NULL; + uint8_t b; + uint16_t w; + uint32_t d; + uint64_t q; + ssh_string s = NULL; + int rc; + size_t len; + uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" + "\xac\xbd\xce\xdf" + "\x00\x00\x00\x06" "libssh" + "\x00\x00\x00\x05" "rocks" + "So much" + "Fun!"; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + b = 0x42; + w = 0x1337; + d = 0xbadc0de; + q = 0x13243546acbdcedf; + s = ssh_string_from_char("libssh"); + rc = ssh_buffer_pack(buffer, + "bwdqSsPt", + b, + w, + d, + q, + s, + "rocks", + (size_t)7, + "So much", + "Fun!"); + assert_int_equal(rc, SSH_OK); + + len = ssh_buffer_get_len(buffer); + assert_int_equal(len, sizeof(verif) - 1); + assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) -1); + + SSH_STRING_FREE(s); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_ssh_buffer_add_format(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_add_format); + assert_int_equal(rc, 0); +} + +static void *thread_ssh_buffer_get_format(void *threadid) { + ssh_buffer buffer; + uint8_t b = 0; + uint16_t w = 0; + uint32_t d = 0; + uint64_t q = 0; + ssh_string s = NULL; + char *s1 = NULL, *s2 = NULL; + int rc; + size_t len; + uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" + "\xac\xbd\xce\xdf" + "\x00\x00\x00\x06" "libssh" + "\x00\x00\x00\x05" "rocks" + "So much"; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_unpack(buffer, + "bwdqSsP", + &b, + &w, + &d, + &q, + &s, + &s1, + (size_t)7, + &s2); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(b, 0x42); + assert_int_equal(w, 0x1337); + + assert_true(d == 0xbadc0de); + assert_true(q == 0x13243546acbdcedf); + + assert_non_null(s); + assert_int_equal(ssh_string_len(s), 6); + assert_memory_equal(ssh_string_data(s), "libssh", 6); + + assert_non_null(s1); + assert_string_equal(s1, "rocks"); + + assert_non_null(s2); + assert_memory_equal(s2, "So much", 7); + + len = ssh_buffer_get_len(buffer); + assert_int_equal(len, 0); + SAFE_FREE(s); + SAFE_FREE(s1); + SAFE_FREE(s2); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_ssh_buffer_get_format(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_get_format); + assert_int_equal(rc, 0); +} + +static void *thread_ssh_buffer_get_format_error(void *threadid) +{ + ssh_buffer buffer = NULL; + uint8_t b = 0; + uint16_t w = 0; + uint32_t d = 0; + uint64_t q = 0; + ssh_string s = NULL; + char *s1 = NULL, *s2 = NULL; + int rc; + uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" + "\xac\xbd\xce\xdf" + "\x00\x00\x00\x06" "libssh" + "\x00\x00\x00\x05" "rocks" + "So much"; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1); + assert_int_equal(rc, SSH_OK); + rc = ssh_buffer_unpack(buffer, + "bwdqSsPb", + &b, + &w, + &d, + &q, + &s, + &s1, + (size_t)7, + &s2, + &b); + assert_int_equal(rc, SSH_ERROR); + + assert_null(s); + assert_null(s1); + assert_null(s2); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_ssh_buffer_get_format_error(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_get_format_error); + assert_int_equal(rc, 0); +} + +static void *thread_buffer_pack_badformat(void *threadid) +{ + ssh_buffer buffer = NULL; + uint8_t b = 42; + int rc; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + /* first with missing format */ + rc = ssh_buffer_pack(buffer, "b", b, b); + assert_int_equal(rc, SSH_ERROR); + ssh_buffer_reinit(buffer); + + /* with additional format */ + rc = ssh_buffer_pack(buffer, "bb", b); + /* check that we detect the missing parameter */ + assert_int_equal(rc, SSH_ERROR); + + /* unpack with missing format */ + ssh_buffer_reinit(buffer); + + rc = ssh_buffer_pack(buffer, "bb", 42, 43); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_unpack(buffer, "b", &b, &b); + assert_int_equal(rc, SSH_ERROR); + + /* not doing the test with additional format as + * it could crash the process */ + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_buffer_pack_badformat(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_buffer_pack_badformat); + assert_int_equal(rc, 0); +} + +#define NUM_TESTS 8 + +static void torture_mixed(void **state) +{ + pthread_t threads[NUM_TESTS][NUM_THREADS]; + int i; + int f; + int rc; + + /* Array of functions to run on threads */ + static void *(*funcs[NUM_TESTS])(void *) = { + thread_growing_buffer, + thread_growing_buffer_shifting, + thread_buffer_prepend, + thread_ssh_buffer_get_ssh_string, + thread_ssh_buffer_add_format, + thread_ssh_buffer_get_format, + thread_ssh_buffer_get_format_error, + thread_buffer_pack_badformat + }; + + (void) state; + + /* Call tests in a round-robin fashion */ + for (i = 0; i < NUM_THREADS; ++i) { + for (f = 0; f < NUM_TESTS; f++) { + rc = pthread_create(&threads[f][i], NULL, funcs[f], NULL); + assert_int_equal(rc, 0); + } + } + + for (f = 0; f < NUM_TESTS; f++) { + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result = NULL; + + rc = pthread_join(threads[f][i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + } +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_growing_buffer), + cmocka_unit_test(torture_growing_buffer_shifting), + cmocka_unit_test(torture_buffer_prepend), + cmocka_unit_test(torture_ssh_buffer_get_ssh_string), + cmocka_unit_test(torture_ssh_buffer_add_format), + cmocka_unit_test(torture_ssh_buffer_get_format), + cmocka_unit_test(torture_ssh_buffer_get_format_error), + cmocka_unit_test(torture_buffer_pack_badformat), + cmocka_unit_test(torture_mixed), + }; + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_threads_crypto.c b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_crypto.c new file mode 100644 index 000000000000..871f29200858 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_crypto.c @@ -0,0 +1,205 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/crypto.h" + +#include + +#define NUM_THREADS 100 + +static int8_t key[32] = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" + "\x1e\x1f"; + +static uint8_t IV[16] = + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e" + "\x1f"; + +static uint8_t cleartext[144] = + "\xb4\xfc\x5d\xc2\x49\x8d\x2c\x29\x4a\xc9\x9a\xb0\x1b\xf8\x29" + "\xee\x85\x6d\x8c\x04\x34\x7c\x65\xf4\x89\x97\xc5\x71\x70\x41" + "\x91\x40\x19\x60\xe1\xf1\x8f\x4d\x8c\x17\x51\xd6\xbc\x69\x6e" + "\xf2\x21\x87\x18\x6c\xef\xc4\xf4\xd9\xe6\x1b\x94\xf7\xd8\xb2" + "\xe9\x24\xb9\xe7\xe6\x19\xf5\xec\x55\x80\x9a\xc8\x7d\x70\xa3" + "\x50\xf8\x03\x10\x35\x49\x9b\x53\x58\xd7\x4c\xfc\x5f\x02\xd6" + "\x28\xea\xcc\x43\xee\x5e\x2b\x8a\x7a\x66\xf7\x00\xee\x09\x18" + "\x30\x1b\x47\xa2\x16\x69\xc4\x6e\x44\x3f\xbd\xec\x52\xce\xe5" + "\x41\xf2\xe0\x04\x4f\x5a\x55\x58\x37\xba\x45\x8d\x15\x53\xf6" + "\x31\x91\x13\x8c\x51\xed\x08\x07\xdb"; + +static uint8_t aes256_cbc_encrypted[144] = + "\x7f\x1b\x92\xac\xc5\x16\x05\x55\x74\xac\xb4\xe0\x91\x8c\xf8" + "\x0d\xa9\x72\xa5\x09\xb8\x44\xee\x55\x02\x13\xb7\x52\x0a\xf0" + "\xac\xd0\x21\x0e\x58\x7b\x34\xfe\xdb\x36\x01\x60\x7d\x18\x3a" + "\xa9\x15\x18\x5b\x13\xca\xdd\x77\x7d\xdf\x64\xc6\xd5\x75\x4b" + "\x02\x02\x37\xb1\xf4\x33\xff\x93\xe6\x32\x08\xda\xcb\x5d\xa2" + "\x8f\x17\x1f\x99\x92\x60\x22\x9d\x6b\xe6\xb2\x5e\xb0\x5d\x26" + "\x3f\xde\xb8\xc1\xb0\x70\x80\x1c\x00\xd0\x93\x2b\xeb\x0f\xd7" + "\x70\x7a\x9a\x7a\xa6\x21\x23\x2c\x02\xb7\xcd\x88\x10\x9c\x2d" + "\x0c\xd3\xfa\xc1\x33\x5b\xe1\xa1\xd4\x3d\x8f\xb8\x50\xc5\xb5" + "\x72\xdd\x6d\x32\x1f\x58\x00\x48\xbe"; + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +static int get_cipher(struct ssh_cipher_struct *cipher, const char *ciphername) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + int i, cmp; + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0) { + memcpy(cipher, &ciphers[i], sizeof(*cipher)); + return SSH_OK; + } + } + + return SSH_ERROR; +} + +static void *thread_crypto_aes256_cbc(void *threadid) +{ + uint8_t output[sizeof(cleartext)] = {0}; + uint8_t iv[16] = {0}; + struct ssh_cipher_struct cipher = { + .name = NULL, + }; + int rc; + + /* Unused */ + (void) threadid; + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.encrypt); + + /* This is for dump static analizyer without modelling support */ + if (cipher.set_encrypt_key == NULL || + cipher.encrypt == NULL) { + return NULL; + } + + memcpy(iv, IV, sizeof(IV)); + cipher.set_encrypt_key(&cipher, + key, + iv + ); + + cipher.encrypt(&cipher, + cleartext, + output, + sizeof(cleartext) + ); + + assert_memory_equal(output, + aes256_cbc_encrypted, + sizeof(aes256_cbc_encrypted)); + ssh_cipher_clear(&cipher); + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.encrypt); + + /* This is for dump static analizyer without modelling support */ + if (cipher.set_encrypt_key == NULL || + cipher.encrypt == NULL) { + return NULL; + } + + memcpy(iv, IV, sizeof(IV)); + cipher.set_decrypt_key(&cipher, + key, + iv + ); + + memset(output, '\0', sizeof(output)); + cipher.decrypt(&cipher, + aes256_cbc_encrypted, + output, + sizeof(aes256_cbc_encrypted) + ); + + assert_memory_equal(output, cleartext, sizeof(cleartext)); + + ssh_cipher_clear(&cipher); + + pthread_exit(NULL); +} + +static void torture_crypto_aes256_cbc(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_crypto_aes256_cbc); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_crypto_aes256_cbc), + }; + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_threads_init.c b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_init.c new file mode 100644 index 000000000000..e49bde10c4b2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_init.c @@ -0,0 +1,98 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" + +#include + +#define NUM_THREADS 20 + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +static void *thread_ssh_init(UNUSED_PARAM(void *threadid)) +{ + int rc; + + (void) threadid; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); + + pthread_exit(NULL); +} + +static void torture_ssh_init(UNUSED_PARAM(void **state)) +{ + int rc; + + rc = run_on_threads(thread_ssh_init); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_init), + }; + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_threads_pki_rsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_pki_rsa.c new file mode 100644 index 000000000000..796748436229 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_pki_rsa.c @@ -0,0 +1,791 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#include + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" + +#define NUM_THREADS 10 + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char RSA_HASH[] = "12345678901234567890"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_join(threads[i], NULL); + assert_int_equal(rc, 0); + } + + return rc; +} + +static int setup_rsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + torture_write_file(LIBSSH_RSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA_CERT01)); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void +disable_secmem(void) +{ +#if defined(HAVE_LIBGCRYPT) + /* gcrypt currently is configured to use only 4kB of locked secmem + * (see ssh_crypto_init() in src/libcrypt.c) + * + * This is insufficient to run the RSA key generation in many threads. + * To avoid the expected warning, disable the secure memory. + * */ + + gcry_control(GCRYCTL_SUSPEND_SECMEM_WARN); + gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +#endif +} + +static void *thread_pki_rsa_import_pubkey_file(void *threadid) +{ + ssh_key pubkey = NULL; + int rc; + + (void) threadid; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); + + return NULL; +} + +static void torture_pki_rsa_import_pubkey_file(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_pubkey_file); + assert_int_equal(rc, 0); +} + + +static void *thread_pki_rsa_import_privkey_base64_NULL_key(void *threadid) +{ + int rc; + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey; + + (void) threadid; /* unused */ + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + assert_non_null(testkey); + + /* test if it returns -1 if key is NULL */ + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + NULL); + assert_true(rc == -1); + return NULL; +} + +static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64_NULL_key); + assert_int_equal(rc, 0); +} + + +static void *thread_pki_rsa_import_privkey_base64_NULL_str(void *threadid) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) threadid; /* unused */ + + /* test if it returns -1 if key_str is NULL */ + rc = ssh_pki_import_privkey_base64(NULL, passphrase, NULL, NULL, &key); + assert_true(rc == -1); + + SSH_KEY_FREE(key); + + return NULL; +} + +static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64_NULL_str); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_import_privkey_base64(void *threadid) +{ + const char *passphrase = torture_get_testkey_passphrase(); + char *key_str = NULL; + ssh_key key = NULL; + enum ssh_keytypes_e type; + int ok; + int rc; + + (void) threadid; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_RSA); + + ok = ssh_key_is_private(key); + assert_true(ok); + + ok = ssh_key_is_public(key); + assert_true(ok); + + free(key_str); + SSH_KEY_FREE(key); + + return NULL; +} + +static void torture_pki_rsa_import_privkey_base64(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_publickey_from_privatekey(void *threadid) +{ + const char *passphrase = NULL; + const char *testkey; + ssh_key pubkey = NULL; + ssh_key key = NULL; + int rc; + int ok; + + (void) threadid; /* unused */ + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + ok = ssh_key_is_private(key); + assert_true(ok); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + return NULL; +} + +static void torture_pki_rsa_publickey_from_privatekey(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_publickey_from_privatekey); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_copy_cert_to_privkey(void *threadid) +{ + /* + * Tests copying a cert loaded into a public key to a private key. + * The function is encryption type agnostic, no need to run this against + * all supported key types. + */ + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey = NULL; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + int rc; + + (void) threadid; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == SSH_OK); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + assert_non_null(testkey); + + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_true(rc == SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_true(rc == SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_true(rc == SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_true(rc == SSH_OK); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_true(rc == SSH_OK); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_true(rc == SSH_ERROR); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + return NULL; +} + +static void torture_pki_rsa_copy_cert_to_privkey(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_copy_cert_to_privkey); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_import_cert_file(void *threadid) +{ + int rc; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) threadid; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_true(rc == 1); + + SSH_KEY_FREE(cert); + + return NULL; +} + +static void torture_pki_rsa_import_cert_file(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_cert_file); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_publickey_base64(void *threadid) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key; + int rc; + + (void) threadid; /* unused */ + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_non_null(key_buf); + + q = p = key_buf; + while (*p != ' ') p++; + *p = '\0'; + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_RSA); + + q = ++p; + while (*p != ' ') p++; + *p = '\0'; + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); + + return NULL; +} + +static void torture_pki_rsa_publickey_base64(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_publickey_base64); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_duplicate_key(void *threadid) +{ + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + int cmp; + int rc; + + (void) threadid; + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + SSH_KEY_FREE(pubkey); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key_gen); + assert_true(rc == 0); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + cmp = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(cmp == 0); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); + + return NULL; +} + +static void torture_pki_rsa_duplicate_key(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_duplicate_key); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_generate_key(void *threadid) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session = NULL; + + (void) threadid; + + session = ssh_new(); + assert_non_null(session); + + if (!ssh_fips_mode()) { + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); + assert_ssh_return_code(session, rc); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); + assert_ssh_return_code(session, rc); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + } + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_ssh_return_code(session, rc); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); + assert_ssh_return_code(session, rc); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); + assert_true(rc == SSH_OK); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); + + return NULL; +} + +static void torture_pki_rsa_generate_key(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_generate_key); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_import_privkey_base64_passphrase(void *threadid) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey; + + (void) threadid; /* unused */ + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 1); + assert_non_null(testkey); + + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(testkey, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); + +#ifndef HAVE_LIBCRYPTO + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(testkey, + NULL, + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); +#endif + + return NULL; +} + +static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64_passphrase); + assert_int_equal(rc, 0); +} + +#define NUM_TESTS 11 + +static void torture_mixed(void **state) +{ + pthread_t threads[NUM_TESTS][NUM_THREADS]; + + int i; + int f; + int rc; + + /* Array of functions to run on threads */ + static void *(*funcs[NUM_TESTS])(void *) = { + thread_pki_rsa_import_pubkey_file, + thread_pki_rsa_import_privkey_base64_NULL_key, + thread_pki_rsa_import_privkey_base64_NULL_str, + thread_pki_rsa_import_privkey_base64, + thread_pki_rsa_publickey_from_privatekey, + thread_pki_rsa_import_privkey_base64_passphrase, + thread_pki_rsa_copy_cert_to_privkey, + thread_pki_rsa_import_cert_file, + thread_pki_rsa_publickey_base64, + thread_pki_rsa_duplicate_key, + thread_pki_rsa_generate_key, + }; + + (void) state; + + /* Call tests in a round-robin fashion */ + for (i = 0; i < NUM_THREADS; ++i) { + for (f = 0; f < NUM_TESTS; f++) { + rc = pthread_create(&threads[f][i], NULL, funcs[f], NULL); + assert_int_equal(rc, 0); + } + } + + for (f = 0; f < NUM_TESTS; f++) { + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_join(threads[f][i], NULL); + assert_int_equal(rc, 0); + } + } +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_publickey_from_privatekey, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_rsa_import_privkey_base64_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_rsa_copy_cert_to_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_cert_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_duplicate_key, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_rsa_generate_key), + cmocka_unit_test_setup_teardown(torture_mixed, setup_rsa_key, teardown), + }; + + /* + * Not testing: + * - pki_rsa_generate_pubkey_from_privkey + * - pki_rsa_write_privkey + * + * The original tests in torture_pki_rsa.c require files to be erased + */ + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + disable_secmem(); + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_tokens.c b/src/libs/libssh-0.12.2/tests/unittests/torture_tokens.c new file mode 100644 index 000000000000..438538ded415 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_tokens.c @@ -0,0 +1,349 @@ +/* + * torture_tokens.c - Tests for tokens list handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/token.h" +#include "libssh/priv.h" + +static void torture_find_matching(UNUSED_PARAM(void **state)) +{ + char *matching; + + /* Match with single token */ + matching = ssh_find_matching("a,b,c", "b"); + assert_non_null(matching); + assert_string_equal(matching, "b"); + SAFE_FREE(matching); + + /* Match sequence, get first preferred */ + matching = ssh_find_matching("a,b,c", "b,c"); + assert_non_null(matching); + assert_string_equal(matching, "b"); + SAFE_FREE(matching); + + /* Only one token allowed */ + matching = ssh_find_matching("c", "a,b,c"); + assert_non_null(matching); + assert_string_equal(matching, "c"); + SAFE_FREE(matching); + + /* Different order in allowed and preferred; gets preferred */ + matching = ssh_find_matching("c,b,a", "a,b,c"); + assert_non_null(matching); + assert_string_equal(matching, "a"); + SAFE_FREE(matching); + + /* No matching returns NULL */ + matching = ssh_find_matching("c,b,a", "d,e,f"); + assert_null(matching); +} + +static void torture_find_all_matching(UNUSED_PARAM(void **state)) +{ + char *matching; + + /* Match with single token */ + matching = ssh_find_all_matching("a,b,c", "b"); + assert_non_null(matching); + assert_string_equal(matching, "b"); + SAFE_FREE(matching); + + /* Match sequence, get first preferred */ + matching = ssh_find_all_matching("a,b,c", "b,c"); + assert_non_null(matching); + assert_string_equal(matching, "b,c"); + SAFE_FREE(matching); + + /* Only one token allowed */ + matching = ssh_find_all_matching("c", "a,b,c"); + assert_non_null(matching); + assert_string_equal(matching, "c"); + SAFE_FREE(matching); + + /* Different order in allowed and preferred; gets preferred */ + matching = ssh_find_all_matching("c,b,a", "a,c,b"); + assert_non_null(matching); + assert_string_equal(matching, "a,c,b"); + SAFE_FREE(matching); + + /* No matching returns NULL */ + matching = ssh_find_all_matching("c,b,a", "d,e,f"); + assert_null(matching); +} + +static void tokenize_compare_expected(const char *chain, const char **expected, + size_t num_expected) +{ + struct ssh_tokens_st *tokens; + size_t i; + + tokens = ssh_tokenize(chain, ','); + assert_non_null(tokens); + + if (expected != NULL) { + assert_non_null(tokens->tokens); + for (i = 0; i < num_expected; i++) { + assert_non_null(tokens->tokens[i]); + assert_non_null(expected[i]); + assert_string_equal(tokens->tokens[i], expected[i]); + } + + assert_null(tokens->tokens[i]); + + i = 0; + printf("Tokenizing \"%s\" resulted in: ", chain); + while (tokens->tokens[i]) { + printf("\"%s\" ", tokens->tokens[i++]); + } + printf("\n"); + } + + ssh_tokens_free(tokens); +} + +static void torture_tokens_sanity(UNUSED_PARAM(void **state)) +{ + const char *simple[] = {"a", "b", "c"}; + const char *colon_first[] = {"", "a", "b", "c"}; + const char *colon_end[] = {"a", "b", "c"}; + const char *colon_both[] = {"", "a", "b", "c"}; + const char *single[] = {"abc"}; + const char *empty[] = {""}; + const char *single_colon[] = {""}; + + tokenize_compare_expected("a,b,c", simple, 3); + tokenize_compare_expected(",a,b,c", colon_first, 4); + tokenize_compare_expected("a,b,c,", colon_end, 3); + tokenize_compare_expected(",a,b,c,", colon_both, 4); + tokenize_compare_expected("abc", single, 1); + tokenize_compare_expected("", empty, 1); + tokenize_compare_expected(",", single_colon, 1); +} + +static void torture_remove_duplicate(UNUSED_PARAM(void **state)) +{ + + const char *simple[] = {"a,a,b,b,c,c", + "a,b,c,a,b,c", + "a,b,c,c,b,a", + "a,a,,b,b,,c,c", + ",a,a,b,b,c,c", + "a,a,b,b,c,c,"}; + const char *empty[] = {"", + ",,,,,,,,,", + NULL}; + char *ret = NULL; + int i; + + for (i = 0; i < 6; i++) { + ret = ssh_remove_duplicates(simple[i]); + assert_non_null(ret); + assert_string_equal("a,b,c", ret); + printf("simple[%d] resulted in '%s'\n", i, ret); + SAFE_FREE(ret); + } + + for (i = 0; i < 3; i++) { + ret = ssh_remove_duplicates(empty[i]); + if (ret != NULL) { + printf("empty[%d] resulted in '%s'\n", i, ret); + } + assert_null(ret); + } + + ret = ssh_remove_duplicates("a"); + assert_non_null(ret); + assert_string_equal("a", ret); + SAFE_FREE(ret); +} + +static void torture_append_without_duplicate(UNUSED_PARAM(void **state)) +{ + const char *s1[] = {"a,a,b,b,c,c", + "a,b,c,a,b,c", + "a,b,c,c,b,a", + "a,a,,b,b,,c,c", + ",a,a,b,b,c,c", + "a,a,b,b,c,c,"}; + const char *s2[] = {"a,a,b,b,c,c,d,d", + "a,b,c,d,a,b,c,d", + "a,b,c,d,d,c,b,a", + "a,a,,b,b,,c,c,,d,d", + ",a,a,b,b,c,c,d,d", + "a,a,b,b,c,c,d,d,", + "d"}; + const char *empty[] = {"", + ",,,,,,,,,", + NULL, + NULL}; + char *ret = NULL; + int i, j; + + ret = ssh_append_without_duplicates("a", "a"); + assert_non_null(ret); + assert_string_equal("a", ret); + SAFE_FREE(ret); + + ret = ssh_append_without_duplicates("a", "b"); + assert_non_null(ret); + assert_string_equal("a,b", ret); + SAFE_FREE(ret); + + ret = ssh_append_without_duplicates("a", NULL); + assert_non_null(ret); + assert_string_equal("a", ret); + SAFE_FREE(ret); + + ret = ssh_append_without_duplicates(NULL, "b"); + assert_non_null(ret); + assert_string_equal("b", ret); + SAFE_FREE(ret); + + for (i = 0; i < 6; i++) { + for (j = 0; j < 7; j++) { + ret = ssh_append_without_duplicates(s1[i], s2[j]); + assert_non_null(ret); + printf("s1[%d] + s2[%d] resulted in '%s'\n", i, j, ret); + assert_string_equal("a,b,c,d", ret); + SAFE_FREE(ret); + } + } + + for (i = 0; i < 6; i++) { + for (j = 0; j < 3; j++) { + ret = ssh_append_without_duplicates(s1[i], empty[j]); + assert_non_null(ret); + printf("s1[%d] + empty[%d] resulted in '%s'\n", i, j, ret); + assert_string_equal("a,b,c", ret); + SAFE_FREE(ret); + } + } + + for (i = 0; i < 3; i++) { + for (j = 0; j < 6; j++) { + ret = ssh_append_without_duplicates(empty[i], s1[j]); + assert_non_null(ret); + printf("empty[%d] + s1[%d] resulted in '%s'\n", i, j, ret); + assert_string_equal("a,b,c", ret); + SAFE_FREE(ret); + } + } + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + ret = ssh_append_without_duplicates(empty[i], empty[j]); + if (ret != NULL) { + printf("empty[%d] + empty[%d] resulted in '%s'\n", i, j, ret); + } + assert_null(ret); + } + } +} + +static void torture_remove_all_matching (UNUSED_PARAM(void** state)) { + char *p; + + p = ssh_remove_all_matching(NULL, NULL); + assert_null(p); + + p = ssh_remove_all_matching("don't remove", NULL); + assert_non_null(p); + assert_string_equal(p, "don't remove"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "b"); + assert_non_null(p); + assert_string_equal(p, "a,c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "a,b"); + assert_non_null(p); + assert_string_equal(p, "c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "d"); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "a,b,c"); + assert_null(p); +} + +static void torture_prefix_without_duplicates (UNUSED_PARAM(void** state)) { + char *p; + + p = ssh_prefix_without_duplicates(NULL, NULL); + assert_null(p); + + p = ssh_prefix_without_duplicates("a,b,c", NULL); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "a"); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "b"); + assert_non_null(p); + assert_string_equal(p, "b,a,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "x"); + assert_non_null(p); + assert_string_equal(p, "x,a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "c,x"); + assert_non_null(p); + assert_string_equal(p, "c,x,a,b"); + free(p); +} + + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_tokens_sanity), + cmocka_unit_test(torture_find_matching), + cmocka_unit_test(torture_find_all_matching), + cmocka_unit_test(torture_remove_duplicate), + cmocka_unit_test(torture_append_without_duplicate), + cmocka_unit_test(torture_remove_all_matching), + cmocka_unit_test(torture_prefix_without_duplicates), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_unit_server.c b/src/libs/libssh-0.12.2/tests/unittests/torture_unit_server.c new file mode 100644 index 000000000000..2fd4be72d656 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_unit_server.c @@ -0,0 +1,195 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "torture.h" +#include "torture_key.h" + +#define TEST_SERVER_PORT 2222 + +#if 0 +struct test_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) +{ + struct test_state *ts = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + ts = malloc(sizeof(struct test_state)); + assert_non_null(ts); + + ts->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + + mask = umask(S_IRWXO | S_IRWXG); + ts->fd = mkstemp(ts->hostkey_path); + umask(mask); + assert_return_code(ts->fd, errno); + close(ts->fd); + + ts->key_type = SSH_KEYTYPE_ECDSA_P256; + ts->hostkey = torture_get_testkey(ts->key_type, 0); + + torture_write_file(ts->hostkey_path, ts->hostkey); + + *state = ts; + + return 0; +} + +static int teardown(void **state) +{ + struct test_state *ts = (struct test_state *)*state; + + unlink(ts->hostkey); + free(ts->hostkey_path); + free(ts); + + ssh_finalize(); + + return 0; +} + +/* TODO the signals are handled by cmocka so they are not testable her :( */ +static void *int_thread(void *arg) +{ + usleep(1); + kill(getpid(), SIGUSR1); + return NULL; +} + +static void *client_thread(void *arg) +{ + unsigned int test_port = TEST_SERVER_PORT; + int rc; + ssh_session session; + ssh_channel channel; + + /* unused */ + (void)arg; + + usleep(200); + session = torture_ssh_session(NULL, "localhost", + &test_port, + "foo", "bar"); + assert_non_null(session); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_int_equal(rc, SSH_OK); + + ssh_free(session); + return NULL; +} + +static void test_ssh_accept_interrupt(void **state) +{ + struct test_state *ts = (struct test_state *)*state; + int rc; + pthread_t client_pthread, interrupt_pthread; + ssh_bind sshbind = NULL; + ssh_session server; + + /* Create server */ + sshbind = torture_ssh_bind("localhost", + TEST_SERVER_PORT, + ts->key_type, + ts->hostkey_path); + assert_non_null(sshbind); + + server = ssh_new(); + assert_non_null(server); + + /* Send interrupt in 1 second */ + rc = pthread_create(&interrupt_pthread, NULL, int_thread, NULL); + assert_return_code(rc, errno); + + rc = pthread_join(interrupt_pthread, NULL); + assert_int_equal(rc, 0); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_ERROR); + assert_int_equal(ssh_get_error_code(sshbind), SSH_EINTR); + + /* Get client to connect now */ + rc = pthread_create(&client_pthread, NULL, client_thread, NULL); + assert_return_code(rc, errno); + + /* Now, try again */ + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); +} +#endif + + +static void test_default_hostkey_paths(void **state) +{ + int rc; + ssh_bind sshbind = NULL; + + /* state not used */ + (void)state; + + /* Create server */ + rc = ssh_init(); + assert_int_equal(rc, 0); + + sshbind = ssh_bind_new(); + assert_non_null(sshbind); + + /* This will fail because we don't have permission to import keys unless we run as root + * TODO: Implement some filesystem wrapper, that would allow this check to pass by + * reading the keys from some accessible test location */ + ssh_bind_listen(sshbind); + + assert_string_equal(sshbind->rsakey, "/etc/ssh/ssh_host_rsa_key"); + assert_string_equal(sshbind->ecdsakey, "/etc/ssh/ssh_host_ecdsa_key"); + assert_string_equal(sshbind->ed25519key, "/etc/ssh/ssh_host_ed25519_key"); + + /* Cleanup */ + ssh_bind_free(sshbind); + ssh_finalize(); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_default_hostkey_paths), + /* Not working correctly the signals are not testable under cmocka + cmocka_unit_test_setup_teardown(test_ssh_accept_interrupt, + setup, + teardown) */ + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_unit_sftp.c b/src/libs/libssh-0.12.2/tests/unittests/torture_unit_sftp.c new file mode 100644 index 000000000000..12940039308d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_unit_sftp.c @@ -0,0 +1,86 @@ +#include "config.h" + +#include "sftp_common.c" +#include "torture.h" + +#define LIBSSH_STATIC + +static void test_sftp_parse_longname(void **state) +{ + const char *lname = NULL; + char *value = NULL; + + /* state not used */ + (void)state; + + /* Valid example from SFTP draft, page 18: + * https://datatracker.ietf.org/doc/draft-spaghetti-sshm-filexfer/ + */ + lname = "-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer"; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, "-rwxr-xr-x"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_OWNER); + assert_string_equal(value, "mjos"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_GROUP); + assert_string_equal(value, "staff"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_SIZE); + assert_string_equal(value, "348911"); + free(value); + /* This function is broken further as the date contains space which breaks + * the parsing altogether */ + value = sftp_parse_longname(lname, SFTP_LONGNAME_DATE); + assert_string_equal(value, "Mar"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_TIME); + assert_string_equal(value, "25"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_string_equal(value, "14:29"); + free(value); +} + +static void test_sftp_parse_longname_invalid(void **state) +{ + const char *lname = NULL; + char *value = NULL; + + /* state not used */ + (void)state; + + /* Invalid inputs should not crash + */ + lname = NULL; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_null(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); + + lname = ""; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, ""); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); + + lname = "-rwxr-xr-x 1"; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, "-rwxr-xr-x"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_sftp_parse_longname), + cmocka_unit_test(test_sftp_parse_longname_invalid), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/valgrind.supp b/src/libs/libssh-0.12.2/tests/valgrind.supp new file mode 100644 index 000000000000..34f8b7c7b174 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/valgrind.supp @@ -0,0 +1,515 @@ +### GLIBC +{ + glibc_regcomp + Memcheck:Leak + fun:*alloc + ... + fun:regcomp +} +{ + glibc_getaddrinfo_leak + Memcheck:Leak + fun:malloc + fun:make_request + fun:__check_pf + fun:getaddrinfo + fun:getai + fun:ssh_connect_host_nonblocking +} + +{ + glibc_dlopen_getdelim_selinux + Memcheck:Leak + fun:malloc + fun:getdelim + obj:/lib64/libselinux.so.1 + fun:call_init + fun:_dl_init + obj:/lib64/ld-2.15.so +} + +{ + glibc_dlopen_alloc + Memcheck:Leak + fun:calloc + fun:_dlerror_run + fun:dlopen@@GLIBC_2.2.5 +} + +### VALGRIND +{ + valgrind_exit_free_bug + Memcheck:Free + fun:free + fun:__libc_freeres + fun:_vgnU_freeres + fun:__run_exit_handlers + fun:exit +} + + +### OPENSSL +{ + openssl_crypto_value8 + Memcheck:Value8 + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_crypto_value4 + Memcheck:Value4 + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_crypto_cond + Memcheck:Cond + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_BN_cond + Memcheck:Cond + fun:BN_* +} + +{ + openssl_bn_value8 + Memcheck:Value8 + fun:bn_* +} + +{ + openssl_bn_value4 + Memcheck:Value4 + fun:bn_* +} + +{ + openssl_AES_cond + Memcheck:Cond + fun:AES_* +} + +{ + openssl_DES_cond + Memcheck:Cond + fun:DES_* +} + +{ + openssl_DES_value8 + Memcheck:Value8 + fun:DES_* +} + +{ + openssl_DES_value4 + Memcheck:Value4 + fun:DES_* +} + +{ + openssl_BF_cond + Memcheck:Cond + fun:BF_* +} + +{ + openssl_SHA1_cond + Memcheck:Cond + fun:SHA1_* +} +{ + openssl_CRYPTO_leak + Memcheck:Cond + fun:OPENSSL_cleanse +} +{ + openssl_FIPS_dlopen_leak + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:_dlerror_run + fun:dlopen* + obj:/lib64/libcrypto.so* + fun:FIPS_module_mode_set + fun:FIPS_mode_set + fun:OPENSSL_init_library +} +{ + Threads + Failed PEM decoder do not play well openssl/openssl#29077 + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:CRYPTO_malloc + fun:CRYPTO_zalloc + fun:ossl_rcu_read_lock + fun:module_find + fun:module_run + fun:CONF_modules_load + fun:CONF_modules_load_file_ex + fun:ossl_config_int + fun:ossl_config_int + fun:ossl_init_config + fun:ossl_init_config_ossl_ + fun:__pthread_once_slow.isra.0 + fun:pthread_once@@GLIBC_2.34 + fun:CRYPTO_THREAD_run_once + fun:OPENSSL_init_crypto + fun:ossl_provider_doall_activated + fun:ossl_algorithm_do_all + fun:ossl_method_construct.constprop.0 + fun:inner_evp_generic_fetch.constprop.0 + fun:evp_generic_do_all + fun:EVP_KEYMGMT_do_all_provided + fun:ossl_decoder_ctx_setup_for_pkey + fun:OSSL_DECODER_CTX_new_for_pkey + fun:pem_read_bio_key_decoder + fun:pem_read_bio_key + fun:PEM_read_bio_PrivateKey_ex + fun:pki_private_key_from_base64 + ... +} +# Cmocka +{ + This looks like leak from cmocka when the forked server is not properly terminated + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + +## libgcrypt +{ + Reachable allocations from libgcrypt + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_check_version + fun:ssh_crypto_init + fun:_ssh_init + fun:libssh_constructor + ... +} +{ + randomize in libgcrypt keeps some memory around + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:ssh_get_random + ... +} +{ + EC key operation allocs some reachable memory + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_pk_sign + ... +} +{ + EC key operation allocs some reachable memory + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_pk_verify + ... +} +{ + EC key generation allocs some reachable memory + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_pk_genkey + ... +} +# NSS +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:__nss_module_get_function + ... + fun:getaddrinfo + ... + fun:torture_* + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} +## libkrb5 +# krb5_mcc_generate_new allocates a hashtab on a static global variable +# It doesn't get freed. +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_load_cache_lookup + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_exception + fun:_dl_catch_error + fun:dlerror_run + ... + fun:getaddrinfo + ... + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx + ... + fun:ssh_userauth_gssapi + fun:torture_gssapi_auth_server_identity + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:UnknownInlinedFun + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_exception + fun:_dl_catch_error + fun:dlerror_run + ... + fun:getaddrinfo + ... + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx + ... + fun:ssh_userauth_gssapi + fun:torture_gssapi_auth_server_identity + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + +{ + Reachable memory from libkrb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:k5_hashtab_create + ... + fun:krb5_mcc_generate_new* +} +{ + Error string from acquire creds in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:krb5_gss_save_error_string + ... + fun:acquire_cred_context.isra.0 + fun:acquire_cred_from.isra.0 + fun:gss_add_cred_from + fun:gss_acquire_cred_from +} +{ + error string from gss init sec context + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:krb5_gss_save_error_string + ... + fun:krb5_gss_init_sec_context_ext + fun:krb5_gss_init_sec_context + fun:gss_init_sec_context +} + + +## sk-dummy.so +# The sk-dummy.so enroll function allocates 1-byte memory for the signature, but marks the signature length as 0. +# Since, we use burn_free to free the signature, it skips the freeing because the size is 0, which results in a memory leak. +{ + sk-dummy.so memory leak in sk_enroll + Memcheck:Leak + match-leak-kinds: definite + fun:calloc + fun:sk_enroll + fun:pki_sk_enroll_key + ... +} + + +{ + malloc inside expand_hostname + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:expand_hostname + fun:canonicalize_princ + fun:krb5_sname_to_principal + fun:krb5_gss_import_name + fun:gssint_import_internal_name + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx +} +{ + malloc in krb5_build_principal + Memcheck:Leak + match-leak-kinds: indirect + fun:malloc + ... + fun:krb5_build_principal_alloc_va + fun:krb5_build_principal + ... + fun:gss_add_cred_from + fun:gss_acquire_cred_from + fun:gss_acquire_cred +} +{ + malloc in krb5_build_principal + Memcheck:Leak + match-leak-kinds: indirect,definite + fun:malloc + fun:krb5_build_principal_alloc_va + fun:krb5_build_principal + ... + fun:gss_add_cred_from + fun:gss_acquire_cred_from + fun:gss_acquire_cred +} +{ + calloc in krb5_build_principal + Memcheck:Leak + match-leak-kinds: indirect + fun:calloc + ... + fun:krb5_build_principal_alloc_va + fun:krb5_build_principal + ... + fun:gss_add_cred_from + fun:gss_acquire_cred_from + fun:gss_acquire_cred +} + +# Function mecherror_copy called in various +# functions of the krb5 library copies entries +# to the global error mapping table (mecherrmap m). +{ + Global error mapping table in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:mecherror_copy +} + +# Function add_error_table called in various +# functions of the krb5 library adds entries +# to a global list of error tables et_list. +{ + Global list of error tables in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:add_error_table +} + +# Function build_mechSet builds the global +# gss_OID_set_desc g_mechSet which is only +# free'd when initialized again. +{ + Global OID set in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:build_mechSet +} + +# Function gssint_register_mechinfo() +# called from gssint_mechglue_init() adds +# entries to a global linked list g_mechList. +{ + Global list of gss_mech_info in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gssint_register_mechinfo* + ... + fun:gssint_mechglue_init +} + +# Function addConfigEntry() called during +# updateMechList() adds entries to +# a global linked list g_mechList. +{ + Global list of gss_mech_info in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:addConfigEntry + ... + fun:updateMechList +} + +# Function loadInterMech() called during +# updateMechList() loops through the global +# linked list g_mechList and updates its entries +# with heap-alloced "interposer fields". +{ + Global list of gss_mech_info in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:loadInterMech + ... + fun:updateMechList +} + +# Multiple krb5 functions call krb5int_open_plugin +# which opens shared libraries using dlopen. +# The plugin handle then seems to be stored in the +# main krb5 context. +{ + Plugin handles stored in the krb5 context + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:dlopen* + ... + fun:krb5int_open_plugin +} From 94872a16f749ed8880b68c184452c3510ac6d0a9 Mon Sep 17 00:00:00 2001 From: Klaus Espenlaub Date: Fri, 7 Aug 2026 15:59:20 +0000 Subject: [PATCH 038/176] Config.kmk, configure.py, src/libs/Makefile.kmk, src/VBox/Devices/Makefile.kmk, src/VBox/RDP/server/Makefile.kmk, src/VBox/RDP/server/jpegtest/Makefile.kmk: Turn libjpeg-turbo into a proper SDK and implement basic configure script handling. svn:sync-xref-src-repo-rev: r174732 --- Config.kmk | 18 ++++-- configure.py | 75 ++++++++++++----------- src/VBox/Devices/Makefile.kmk | 9 +-- src/VBox/RDP/server/Makefile.kmk | 9 +-- src/VBox/RDP/server/jpegtest/Makefile.kmk | 9 +-- src/libs/Makefile.kmk | 5 +- 6 files changed, 62 insertions(+), 63 deletions(-) diff --git a/Config.kmk b/Config.kmk index ac3fce0962fb..f7e6daa5000f 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ +# $Id: Config.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -3207,9 +3207,6 @@ VBOX_PATH_X11_ROOT = $(PATH_ROOT)/src/VBox/Additions/x11/x11include # Miscellaneous includes # VBOX_GRAPHICS_INCS = $(PATH_ROOT)/include/VBox/Graphics -VBOX_JPEG_INCS = \ - $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0/src \ - $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0 # The icons to use. @@ -5329,6 +5326,15 @@ SDK_VBoxLibXml2_DEFS.win ?= WIN32 _WINDOWS _MBCS HAVE_WIN32_THREADS HAVE_CO # Note: no linking to LIB here, we do that explicitly in src/VBox/Runtime/Makefile.kmk to link # libxml against VBoxRT +# libjpeg +SDK_VBoxLibJpeg := libjpeg for dll linking. +SDK_VBoxLibJpeg_DEFAULT_INCS ?= $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0/src \ + $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0 +SDK_VBoxLibJpeg_INCS ?= $(SDK_VBoxLibJpeg_DEFAULT_INCS) +SDK_VBoxLibJpeg_LIBS ?= $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) + # zlib SDK_VBoxZlib := zlib for dll linking. SDK_VBoxZlib_VBOX_DEFAULT_INCS := $(PATH_ROOT)/src/libs/zlib-1.3.2 @@ -9598,7 +9604,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114891 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114892 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9612,7 +9618,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114891 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114892 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif diff --git a/configure.py b/configure.py index bd5e2feda4bc..18a51aa5c56e 100755 --- a/configure.py +++ b/configure.py @@ -11,7 +11,7 @@ # pylint: disable=invalid-name # pylint: disable=multiple-statements # pylint: disable=line-too-long -# $Id: configure.py 114640 2026-07-07 17:56:17Z klaus.espenlaub@oracle.com $ +# $Id: configure.py 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ # # The following checks for the right (i.e. most recent) Python binary available # and re-starts the script using that binary (like a shell wrapper). @@ -90,7 +90,7 @@ # External Python modules or other dependencies are not allowed! # -__revision__ = "$Revision: 114640 $" +__revision__ = ''.join(c for c in "$Revision: 114892 $" if c.isdigit()) import argparse import collections; @@ -1287,6 +1287,23 @@ def compileAndRun(self, fErrorsAsWarnings): self.sVer = sStdOut; return fRc, sStdOut, sStdErr; + def getSdkLibs(self): + """ + Returns the library references in the form expected by kBuild SDKs. + """ + asSdkLibs = []; + for sLib in self.asLibFiles: + if self.enmBuildTarget == BuildTarget.WINDOWS: + asSdkLibs.append(withLibSuff(sLib)); + elif os.path.dirname(sLib): + asSdkLibs.append(sLib); + else: + sLibName = os.path.basename(sLib); + if sLibName.startswith('lib'): + sLibName = sLibName[3:]; + asSdkLibs.append(sLibName); + return asSdkLibs; + def setArgs(self, args): """ Applies argparse options for disabling and custom paths. @@ -1297,6 +1314,8 @@ def setArgs(self, args): self.fUseInTree = fUseInTree; # Only set if explicitly specified on command line -- otherwise take the lib's default. self.fDisabled = getattr(args, f'config_libs_disable_{sAttr}', False); self.sRootPath = getattr(args, f'config_libs_path_{sAttr}', None); + if self.sRootPath: + self.fUseInTree = False; # An explicitly specified root path overrides using in-tree library. return True; @@ -1534,7 +1553,8 @@ def checkHdr(self): self.printVerbose(1, 'Found header files:'); for sHdr, sPath in setHdrFound.items(): self.printVerbose(1, f'\t{os.path.join(sPath, sHdr)}'); - asIncPaths.extend([ sPath ]); + if sPath != '/usr/include': + asIncPaths.extend([ sPath ]); for sHdr in asHdrToSearch: if sHdr not in setHdrFound: @@ -1605,31 +1625,6 @@ def checkLib(self, fStatic = False): return False, None, None; - def checkPackage(self, sPackageName): - """" - Checks a given package. - """ - if not self.sSdkName: # No SDK (our term for package in our dev tools)? Bail out. - return True; - - self.printVerbose(1, f"Package Information for {sPackageName}:"); - fRc, sBinDir = getPackageVar(sPackageName, PkgMgrVar.BINDIR); - self.printVerbose(1, f' BINDIR: {sBinDir if fRc else ""}'); - fRc, sLibDir = getPackageVar(sPackageName, PkgMgrVar.LIBDIR); - self.printVerbose(1, f' LIBDIR: {sLibDir if fRc else ""}'); - fRc, sCFlags = getPackageVar(sPackageName, PkgMgrVar.CFLAGS); - self.printVerbose(1, f' CFLAGS: {sCFlags if fRc else ""}'); - - #if self.sRootPath: - # g_oEnv.set(f'PATH_SDK_{self.sSdkName}', self.sRootPath); - # sPathLibExec = os.path.join(sPathBase, 'libexec'); -# - #if self.asIncPaths: - # g_oEnv.set(f'PATH_SDK_{self.sSdkName}_LIB', self.asLibPaths[0]); - #if self.asLibPaths: - # g_oEnv.set(f'PATH_SDK_{self.sSdkName}_INC', self.asIncPaths[0]); - return True; - def performCheck(self): """ Run library detection. @@ -1677,6 +1672,10 @@ def performCheck(self): if self.fUseInTree and not self.fIsInTree: self.printWarn('Library needs to be used from in-tree sources but was not detected there -- might lead to build errors'); + if self.fHave and self.sSdkName and not self.fIsInTree: + g_oEnv.set(f'SDK_{self.sSdkName}_INCS', ' '.join(self.asIncPaths)); + g_oEnv.set(f'SDK_{self.sSdkName}_LIBS', ' '.join(self.getSdkLibs())); + if not fRc: if self.dictArgsToSetIfFailed: # Implies being optional. self.printWarn('Library check failed and is optional'); @@ -3463,8 +3462,9 @@ def show_syntax_help(): sSdkName = "VBoxLibCurl"), LibraryCheck("libdevmapper", [ "libdevmapper.h" ], [ "libdevmapper" ], aeTargets = [ BuildTarget.LINUX ], sCode = '#include \nint main() { char v[64]; dm_get_library_version(v, sizeof(v)); printf("%s", v); return 0; }\n'), - LibraryCheck("libjpeg-turbo", [ "turbojpeg.h" ], [ "libturbojpeg" ], aeTargets = [ BuildTarget.ANY ], fUseInTree = True, - sCode = '#include \nint main() { tjInitCompress(); printf(""); return 0; }\n'), + LibraryCheck("libjpeg-turbo", [ "jpeglib.h" ], [ "libjpeg" ], aeTargets = [ BuildTarget.ANY ], fUseInTree = True, + sCode = '#include \n#ifndef LIBJPEG_TURBO_VERSION\n#error "libjpeg-turbo required"\n#endif\n#define VBOX_JPEG_STRINGIFY_INNER(a) #a\n#define VBOX_JPEG_STRINGIFY(a) VBOX_JPEG_STRINGIFY_INNER(a)\nint main() { struct jpeg_error_mgr error; jpeg_std_error(&error); printf("%s", VBOX_JPEG_STRINGIFY(LIBJPEG_TURBO_VERSION)); return 0; }\n', + sSdkName = "VBoxLibJpeg"), LibraryCheck("liblzf", [ "lzf.h" ], [ "liblzf" ], aeTargets = [ BuildTarget.ANY ], fUseInTree = True, sCode = '#include \nint main() { printf("%d.%d", LZF_VERSION >> 8, LZF_VERSION & 0xff);\n#if LZF_VERSION >= 0x0105\nreturn 0;\n#else\nreturn 1;\n#endif\n }\n'), LibraryCheck("liblzma", [ "lzma.h" ], [ "liblzma" ], aeTargets = [ BuildTarget.ANY ], fUseInTree = True, @@ -3816,13 +3816,14 @@ def main(): oParser.add_argument('-v', '--verbose', help="Enables verbose output", action='count', default=0, dest='config_verbose'); oParser.add_argument('-V', '--version', help="Prints the version of this script", action='store_true'); for oLibCur in g_aoLibs: - oParser.add_argument(f'--build-{oLibCur.sName}', help=f'Explicitly build {oLibCur.sName} from in-tree sources', action='store_true', default=None, dest=f'config_libs_build_{oLibCur.sName}'); - oParser.add_argument(f'--disable-{oLibCur.sName}', f'--without-{oLibCur.sName}', help=f'Disables using {oLibCur.sName}', action='store_true', default=None, dest=f'config_libs_disable_{oLibCur.sName}'); - oParser.add_argument(f'--with-{oLibCur.sName}-path', help=f'Sets the (root) path for {oLibCur.sName}', dest=f'config_libs_path_{oLibCur.sName}'); + sLibName = oLibCur.name2Attr(); # So that we can use variables directly w/o getattr. + oParser.add_argument(f'--build-{oLibCur.sName}', help=f'Explicitly build {oLibCur.sName} from in-tree sources', action='store_true', default=None, dest=f'config_libs_build_{sLibName}'); + oParser.add_argument(f'--disable-{oLibCur.sName}', f'--without-{oLibCur.sName}', help=f'Disables using {oLibCur.sName}', action='store_true', default=None, dest=f'config_libs_disable_{sLibName}'); + oParser.add_argument(f'--with-{oLibCur.sName}-path', help=f'Sets the (root) path for {oLibCur.sName}', dest=f'config_libs_path_{sLibName}'); # For debugging / development only. We don't expose this in the syntax help. - oParser.add_argument(f'--only-{oLibCur.sName}', help=argparse.SUPPRESS, action='store_true', default=None, dest=f'config_libs_only_{oLibCur.sName}'); + oParser.add_argument(f'--only-{oLibCur.sName}', help=argparse.SUPPRESS, action='store_true', default=None, dest=f'config_libs_only_{sLibName}'); for oToolCur in g_aoTools: - sToolName = oToolCur.sName.replace("-", "_"); # So that we can use variables directly w/o getattr. + sToolName = oToolCur.name2Attr(); # So that we can use variables directly w/o getattr. oParser.add_argument(f'--disable-{oToolCur.sName}', f'--without-{oToolCur.sName}', help=f'Disables using {oToolCur.sName}', action='store_true', default=None, dest=f'config_tools_disable_{sToolName}'); oParser.add_argument(f'--with-{oToolCur.sName}-path', help=f'Sets the (root) path for {oToolCur.sName}', dest=f'config_tools_path_{sToolName}'); # For debugging / development only. We don't expose this in the syntax help. @@ -4023,8 +4024,8 @@ def main(): # Filter libs and tools based on --only-XXX flags. # Replace '-' with '_' so that we can use variables directly w/o getattr lateron. - aoOnlyLibs = [lib for lib in g_aoLibs if getattr(g_oArgs, f'config_libs_only_{lib.sName.replace("-", "_")}', False)]; - aoOnlyTools = [tool for tool in g_aoTools if getattr(g_oArgs, f'config_tools_only_{tool.sName.replace("-", "_")}', False)]; + aoOnlyLibs = [lib for lib in g_aoLibs if getattr(g_oArgs, f'config_libs_only_{lib.name2Attr()}', False)]; + aoOnlyTools = [tool for tool in g_aoTools if getattr(g_oArgs, f'config_tools_only_{tool.name2Attr()}', False)]; aoLibsToCheck = aoOnlyLibs if aoOnlyLibs else g_aoLibs; aoToolsToCheck = aoOnlyTools if aoOnlyTools else g_aoTools; # Filter libs and tools based on build target. diff --git a/src/VBox/Devices/Makefile.kmk b/src/VBox/Devices/Makefile.kmk index cd9d0a6e634f..6ba6a1a47707 100644 --- a/src/VBox/Devices/Makefile.kmk +++ b/src/VBox/Devices/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114879 2026-08-06 22:04:52Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ ## @file # Top-level sub-makefile for the devices, drivers and services. # @@ -514,7 +514,7 @@ if !defined(VBOX_ONLY_EXTPACKS) && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SU if defined(VBOX_WITH_USB_VIDEO_IMPL) VBoxDD_DEFS += VBOX_WITH_USB_VIDEO_IMPL VBoxDD_INCS += \ - $(VBOX_JPEG_INCS) + $(SDK_VBoxLibJpeg_INCS) VBoxDD_SOURCES += \ Video/UsbWebcam.cpp \ Video/UsbWebcamDesc.cpp \ @@ -529,10 +529,7 @@ if !defined(VBOX_ONLY_EXTPACKS) && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SU Video/HostWebcam-v4l2.cpp VBoxDD_SOURCES.win += \ Video/HostWebcam-win.cpp - VBoxDD_LIBS += \ - $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) + VBoxDD_LIBS += $(SDK_VBoxLibJpeg_LIBS) VBoxDD_LIBS.win += \ strmiids.lib VBoxDD_LDFLAGS.darwin += \ diff --git a/src/VBox/RDP/server/Makefile.kmk b/src/VBox/RDP/server/Makefile.kmk index 23e9cdc36827..f8b2a339774e 100644 --- a/src/VBox/RDP/server/Makefile.kmk +++ b/src/VBox/RDP/server/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ ## @file # Sub-Makefile for the VBox RDP server. # @@ -40,11 +40,8 @@ VBoxVRDP_LIBS = $(LIB_RUNTIME) VBoxVRDP_SDKS = VBoxOpenSsl VBoxVRDP_DEFS = IN_VRDP VBoxVRDP_DEFS += NOVOL -VBoxVRDP_INCS += $(VBOX_JPEG_INCS) $(VBOX_GRAPHICS_INCS) -VBoxVRDP_LIBS += \ - $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) +VBoxVRDP_INCS += $(SDK_VBoxLibJpeg_INCS) $(VBOX_GRAPHICS_INCS) +VBoxVRDP_LIBS += $(SDK_VBoxLibJpeg_LIBS) ifdef VBOX_WITH_VRDP_ENABLE_LOGREL VBoxVRDP_DEFS += VRDP_ENABLE_LOGREL diff --git a/src/VBox/RDP/server/jpegtest/Makefile.kmk b/src/VBox/RDP/server/jpegtest/Makefile.kmk index b463dd428bf6..3053770db724 100644 --- a/src/VBox/RDP/server/jpegtest/Makefile.kmk +++ b/src/VBox/RDP/server/jpegtest/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ ## @file # Makefile for jpegtest - not for general consumption, see OTHERS. # @@ -38,11 +38,8 @@ ifdef TJ jpegtest_INCS += $(PATH_ROOT)/src/libs/libjpeg-turbo-1.0.0/win jpegtest_LIBS += $(PATH_STAGE_LIB)/VBox-libjpeg-t$(VBOX_SUFF_LIB) else - jpegtest_INCS += $(VBOX_JPEG_INCS) $(VBOX_GRAPHICS_INCS) - jpegtest_LIBS += \ - $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) + jpegtest_INCS += $(SDK_VBoxLibJpeg_INCS) $(VBOX_GRAPHICS_INCS) + jpegtest_LIBS += $(SDK_VBoxLibJpeg_LIBS) endif ifneq ($(KBUILD_TARGET),win) jpegtest_CXXFLAGS += -Wno-unused-function -Wno-multichar diff --git a/src/libs/Makefile.kmk b/src/libs/Makefile.kmk index ccf589eba3d9..c4d7c312c9c0 100644 --- a/src/libs/Makefile.kmk +++ b/src/libs/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ +# $Id: Makefile.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ ## @file # Top-level makefile for the external libraries. # @@ -69,7 +69,8 @@ endif if (defined(VBOX_WITH_VRDP) || defined(VBOX_WITH_USB_VIDEO_IMPL)) \ && !defined(VBOX_ONLY_ADDITIONS) \ && !defined(VBOX_ONLY_SDK) \ - && !defined(VBOX_ONLY_VALIDATIONKIT) + && !defined(VBOX_ONLY_VALIDATIONKIT) \ + && ("$(SDK_VBoxJpeg_INCS)" == "$(SDK_VBoxLibJpeg_DEFAULT_INCS)") include $(PATH_SUB_CURRENT)/libjpeg-turbo-3.1.0/Makefile.kmk endif From 64e4a4d8d977020ea4c05f65a0c5ca1c2363033d Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Fri, 7 Aug 2026 16:18:28 +0000 Subject: [PATCH 039/176] Devices/Graphics: simplified legacy VBVA code. bugref:11131 svn:sync-xref-src-repo-rev: r174733 --- src/VBox/Devices/Graphics/DevVGA_VBVA.cpp | 86 ++++++----------------- 1 file changed, 23 insertions(+), 63 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp b/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp index e64260ae89cd..87d5a81b9568 100644 --- a/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp +++ b/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA_VBVA.cpp 113822 2026-04-12 21:10:19Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA_VBVA.cpp 114893 2026-08-07 16:18:28Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox Video Acceleration (VBVA). */ @@ -138,9 +138,8 @@ static bool vbvaFetchBytes(VBVADATA *pVBVAData, uint8_t *pbDst, uint32_t cb) const uint8_t RT_UNTRUSTED_VOLATILE_GUEST *pbSrc = &pVBVAData->guest.pu8Data[pVBVAData->off32Data]; const uint32_t u32BytesTillBoundary = pVBVAData->cbData - pVBVAData->off32Data; - const int32_t i32Diff = cb - u32BytesTillBoundary; - if (i32Diff <= 0) + if (cb <= u32BytesTillBoundary) { /* Chunk will not cross buffer boundary. */ RT_BCOPY_VOLATILE(pbDst, pbSrc, cb); @@ -149,7 +148,7 @@ static bool vbvaFetchBytes(VBVADATA *pVBVAData, uint8_t *pbDst, uint32_t cb) { /* Chunk crosses buffer boundary. */ RT_BCOPY_VOLATILE(pbDst, pbSrc, u32BytesTillBoundary); - RT_BCOPY_VOLATILE(pbDst + u32BytesTillBoundary, &pVBVAData->guest.pu8Data[0], i32Diff); + RT_BCOPY_VOLATILE(pbDst + u32BytesTillBoundary, &pVBVAData->guest.pu8Data[0], cb - u32BytesTillBoundary); } /* Advance data offset and sync with guest. */ @@ -192,12 +191,18 @@ static bool vbvaPartialRead(uint32_t cbRecord, VBVADATA *pVBVAData) Log(("vbvaPartialRead: failed to (re)alocate memory for partial record!!! cbRecord 0x%08X\n", cbRecord)); + RTMemFree(pPartialRecord->pu8); + pPartialRecord->pu8 = NULL; + pPartialRecord->cb = 0; return false; } /* Fetch data from the ring buffer. */ if (!vbvaFetchBytes(pVBVAData, pu8New + pPartialRecord->cb, cbChunk)) { + RTMemFree(pu8New); + pPartialRecord->pu8 = NULL; + pPartialRecord->cb = 0; return false; } @@ -208,8 +213,7 @@ static bool vbvaPartialRead(uint32_t cbRecord, VBVADATA *pVBVAData) } /** - * For contiguous chunks just return the address in the buffer. For crossing - * boundary - allocate a buffer from heap. + * Always allocate a bounce buffer from heap. */ static bool vbvaFetchCmd(VBVADATA *pVBVAData, VBVACMDHDR RT_UNTRUSTED_VOLATILE_GUEST **ppHdr, uint32_t *pcbCmd) { @@ -310,36 +314,18 @@ static bool vbvaFetchCmd(VBVADATA *pVBVAData, VBVACMDHDR RT_UNTRUSTED_VOLATILE_G if (cbRecord) { - /* The size of largest contiguous chunk in the ring buffer. */ - uint32_t u32BytesTillBoundary = pVBVAData->cbData - pVBVAData->off32Data; - - /* The pointer to data in the ring buffer. */ - uint8_t RT_UNTRUSTED_VOLATILE_GUEST *pbSrc = &pVBVAData->guest.pu8Data[pVBVAData->off32Data]; - - /* Fetch or point the data. */ - if (u32BytesTillBoundary >= cbRecord) + /* Make a copy of the data. */ + uint8_t *pbDst = (uint8_t *)RTMemAlloc(cbRecord); + if (!pbDst) { - /* The command does not cross buffer boundary. Return address in the buffer. */ - *ppHdr = (VBVACMDHDR RT_UNTRUSTED_VOLATILE_GUEST *)pbSrc; - - /* The data offset will be updated in vbvaReleaseCmd. */ + LogFlowFunc (("could not allocate %d bytes from heap!!!\n", cbRecord)); + return false; } - else - { - /* The command crosses buffer boundary. Rare case, so not optimized. */ - uint8_t *pbDst = (uint8_t *)RTMemAlloc(cbRecord); - if (!pbDst) - { - LogFlowFunc (("could not allocate %d bytes from heap!!!\n", cbRecord)); - return false; - } - - vbvaFetchBytes(pVBVAData, pbDst, cbRecord); - *ppHdr = (VBVACMDHDR *)pbDst; + if (!vbvaFetchBytes(pVBVAData, pbDst, cbRecord)) + return false; - LOGVBVABUFFER(("Allocated from heap %p\n", pbDst)); - } + *ppHdr = (VBVACMDHDR *)pbDst; } *pcbCmd = cbRecord; @@ -356,38 +342,11 @@ static bool vbvaFetchCmd(VBVADATA *pVBVAData, VBVACMDHDR RT_UNTRUSTED_VOLATILE_G static void vbvaReleaseCmd(VBVADATA *pVBVAData, VBVACMDHDR RT_UNTRUSTED_VOLATILE_GUEST *pHdr, uint32_t cbCmd) { - VBVAPARTIALRECORD *pPartialRecord = &pVBVAData->partialRecord; - const uint8_t RT_UNTRUSTED_VOLATILE_GUEST *pbRingBuffer = pVBVAData->guest.pu8Data; + RT_NOREF(pVBVAData, cbCmd); - if ( (uintptr_t)pHdr >= (uintptr_t)pbRingBuffer - && (uintptr_t)pHdr < (uintptr_t)&pbRingBuffer[pVBVAData->cbData]) - { - /* The pointer is inside ring buffer. Must be continuous chunk. */ - Assert(pVBVAData->cbData - (uint32_t)((uint8_t *)pHdr - pbRingBuffer) >= cbCmd); - - /* Advance data offset and sync with guest. */ - pVBVAData->off32Data = (pVBVAData->off32Data + cbCmd) % pVBVAData->cbData; - pVBVAData->guest.pVBVA->off32Data = pVBVAData->off32Data; + LOGVBVABUFFER(("Free heap %p\n", pHdr)); - Assert(!pPartialRecord->pu8 && pPartialRecord->cb == 0); - } - else - { - /* The pointer is outside. It is then an allocated copy. */ - LOGVBVABUFFER(("Free heap %p\n", pHdr)); - - if ((uint8_t *)pHdr == pPartialRecord->pu8) - { - pPartialRecord->pu8 = NULL; - pPartialRecord->cb = 0; - } - else - { - Assert(!pPartialRecord->pu8 && pPartialRecord->cb == 0); - } - - RTMemFree((void *)pHdr); - } + RTMemFree((void *)pHdr); } static int vbvaFlushProcess(PVGASTATECC pThisCC, VBVADATA *pVBVAData, unsigned uScreenId) @@ -428,9 +387,10 @@ static int vbvaFlushProcess(PVGASTATECC pThisCC, VBVADATA *pVBVAData, unsigned u if (cbCmd < sizeof(VBVACMDHDR)) { - LogFunc(("short command. off32Data = %d, off32Free = %d, cbCmd %d!!!\n", + LogFunc(("command length: off32Data = %d, off32Free = %d, cbCmd %d!!!\n", pVBVAData->off32Data, pVBVAData->guest.pVBVA->off32Free, cbCmd)); + vbvaReleaseCmd(pVBVAData, pHdr, cbCmd); return VERR_NOT_SUPPORTED; } From d4fdf148f7771fe89ad9c1603e8b32700024fc87 Mon Sep 17 00:00:00 2001 From: Klaus Espenlaub Date: Fri, 7 Aug 2026 16:50:21 +0000 Subject: [PATCH 040/176] src/libs/Makefile.kmk: Fix typo in previous change which broke building the in-tree libjpeg-turbo. svn:sync-xref-src-repo-rev: r174736 --- src/libs/Makefile.kmk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Makefile.kmk b/src/libs/Makefile.kmk index c4d7c312c9c0..490af4fde1c5 100644 --- a/src/libs/Makefile.kmk +++ b/src/libs/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ +# $Id: Makefile.kmk 114896 2026-08-07 16:50:21Z klaus.espenlaub@oracle.com $ ## @file # Top-level makefile for the external libraries. # @@ -70,7 +70,7 @@ if (defined(VBOX_WITH_VRDP) || defined(VBOX_WITH_USB_VIDEO_IMPL)) \ && !defined(VBOX_ONLY_ADDITIONS) \ && !defined(VBOX_ONLY_SDK) \ && !defined(VBOX_ONLY_VALIDATIONKIT) \ - && ("$(SDK_VBoxJpeg_INCS)" == "$(SDK_VBoxLibJpeg_DEFAULT_INCS)") + && "$(SDK_VBoxLibJpeg_INCS)" == "$(SDK_VBoxLibJpeg_DEFAULT_INCS)" include $(PATH_SUB_CURRENT)/libjpeg-turbo-3.1.0/Makefile.kmk endif From c0dfc59cb80b242706d7c25d233eb1d788c26bcc Mon Sep 17 00:00:00 2001 From: Klaus Espenlaub Date: Fri, 7 Aug 2026 17:54:46 +0000 Subject: [PATCH 041/176] Config.kmk, tools/Makefile.kmk: Eliminate the use of YASM by default, switch to NASM everywhere. svn:sync-xref-src-repo-rev: r174738 --- Config.kmk | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Config.kmk b/Config.kmk index f7e6daa5000f..6fe14ad7dcce 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ +# $Id: Config.kmk 114898 2026-08-07 17:54:46Z klaus.espenlaub@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -1970,9 +1970,8 @@ VBOX_LDR_FMT := $(firstword $(VBOX_LDR_FMT.$(KBUILD_TARGET).$(KBUILD_T # Legacy: VBOX_LDR_FMT32 := $(firstword $(VBOX_LDR_FMT.$(KBUILD_TARGET).x86) $(VBOX_LDR_FMT)) VBOX_LDR_FMT64 := $(firstword $(VBOX_LDR_FMT.$(KBUILD_TARGET).amd64) $(VBOX_LDR_FMT)) -if1of (os2, $(KBUILD_TARGET) $(KBUILD_HOST)) # This isn't too helpful - DONT_USE_YASM = 1 # yasm doesn't implement omf yet. -endif +# By default use NASM for everything +DONT_USE_YASM = 1 # # Assembler setup. @@ -3581,6 +3580,7 @@ ifndef VBOX_NOINC_DYNAMIC_CONFIG_KMK endif ifdef VBOX_NASM_CHECK # NASM (--allow-64-bit: 2.12rc2) + $(QUIET)$(APPEND) '$@' '# debug: TOOL_NASM_AS="$(TOOL_NASM_AS)"' $(QUIET)$(APPEND) '$@' 'VBOX_NASM_allow_64_bit ?= $(call VBOX_NASM_CHECK,--allow-64-bit,)' endif @@ -9604,7 +9604,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114892 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114898 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9618,7 +9618,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114892 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114898 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif From 446b6a3446b103639951d2a18a2f7411b6cf389a Mon Sep 17 00:00:00 2001 From: Klaus Espenlaub Date: Fri, 7 Aug 2026 18:17:56 +0000 Subject: [PATCH 042/176] Config.kmk: whitespace fix svn:sync-xref-src-repo-rev: r174739 --- Config.kmk | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Config.kmk b/Config.kmk index 6fe14ad7dcce..db634b994f19 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114898 2026-08-07 17:54:46Z klaus.espenlaub@oracle.com $ +# $Id: Config.kmk 114899 2026-08-07 18:17:56Z klaus.espenlaub@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -5329,11 +5329,11 @@ SDK_VBoxLibXml2_DEFS.win ?= WIN32 _WINDOWS _MBCS HAVE_WIN32_THREADS HAVE_CO # libjpeg SDK_VBoxLibJpeg := libjpeg for dll linking. SDK_VBoxLibJpeg_DEFAULT_INCS ?= $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0/src \ - $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0 + $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0 SDK_VBoxLibJpeg_INCS ?= $(SDK_VBoxLibJpeg_DEFAULT_INCS) -SDK_VBoxLibJpeg_LIBS ?= $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) +SDK_VBoxLibJpeg_LIBS ?= $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) # zlib SDK_VBoxZlib := zlib for dll linking. @@ -9604,7 +9604,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114898 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114899 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9618,7 +9618,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114898 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114899 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif From af7f2f1ef6b87e14947c33c6d567c9cdecbd685c Mon Sep 17 00:00:00 2001 From: Klaus Espenlaub Date: Fri, 7 Aug 2026 20:49:14 +0000 Subject: [PATCH 043/176] Config.kmk, tools/Makefile.kmk: Back out the "no more YASM" change. Needs more work. svn:sync-xref-src-repo-rev: r174741 --- Config.kmk | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Config.kmk b/Config.kmk index db634b994f19..d6b21ecf8a2f 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114899 2026-08-07 18:17:56Z klaus.espenlaub@oracle.com $ +# $Id: Config.kmk 114901 2026-08-07 20:49:14Z klaus.espenlaub@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -1970,8 +1970,9 @@ VBOX_LDR_FMT := $(firstword $(VBOX_LDR_FMT.$(KBUILD_TARGET).$(KBUILD_T # Legacy: VBOX_LDR_FMT32 := $(firstword $(VBOX_LDR_FMT.$(KBUILD_TARGET).x86) $(VBOX_LDR_FMT)) VBOX_LDR_FMT64 := $(firstword $(VBOX_LDR_FMT.$(KBUILD_TARGET).amd64) $(VBOX_LDR_FMT)) -# By default use NASM for everything -DONT_USE_YASM = 1 +if1of (os2, $(KBUILD_TARGET) $(KBUILD_HOST)) # This isn't too helpful + DONT_USE_YASM = 1 # yasm doesn't implement omf yet. +endif # # Assembler setup. @@ -3580,7 +3581,6 @@ ifndef VBOX_NOINC_DYNAMIC_CONFIG_KMK endif ifdef VBOX_NASM_CHECK # NASM (--allow-64-bit: 2.12rc2) - $(QUIET)$(APPEND) '$@' '# debug: TOOL_NASM_AS="$(TOOL_NASM_AS)"' $(QUIET)$(APPEND) '$@' 'VBOX_NASM_allow_64_bit ?= $(call VBOX_NASM_CHECK,--allow-64-bit,)' endif @@ -9604,7 +9604,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114899 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114901 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9618,7 +9618,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114899 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114901 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif From e934e961a1e1bf21e986685dfcf605034af56de2 Mon Sep 17 00:00:00 2001 From: Klaus Espenlaub Date: Fri, 7 Aug 2026 21:57:38 +0000 Subject: [PATCH 044/176] configure.py: Kludge to hopefully suppress the additional SDK_QT6 variables sabotaging the build because the include path is for the wrong c++ compiler apparently (on Linux). Needs more investigation and a proper fix. svn:sync-xref-src-repo-rev: r174742 --- configure.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/configure.py b/configure.py index 18a51aa5c56e..4d9ef0559e68 100755 --- a/configure.py +++ b/configure.py @@ -11,7 +11,7 @@ # pylint: disable=invalid-name # pylint: disable=multiple-statements # pylint: disable=line-too-long -# $Id: configure.py 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ +# $Id: configure.py 114902 2026-08-07 21:57:38Z klaus.espenlaub@oracle.com $ # # The following checks for the right (i.e. most recent) Python binary available # and re-starts the script using that binary (like a shell wrapper). @@ -90,7 +90,7 @@ # External Python modules or other dependencies are not allowed! # -__revision__ = ''.join(c for c in "$Revision: 114892 $" if c.isdigit()) +__revision__ = ''.join(c for c in "$Revision: 114902 $" if c.isdigit()) import argparse import collections; @@ -1672,7 +1672,8 @@ def performCheck(self): if self.fUseInTree and not self.fIsInTree: self.printWarn('Library needs to be used from in-tree sources but was not detected there -- might lead to build errors'); - if self.fHave and self.sSdkName and not self.fIsInTree: + ## @todo r=klaus the hack with skipping this for QT6 is fixing the build (the include paths appear wrong) but needs a proper solution. + if self.fHave and self.sSdkName and self.sSdkName != 'QT6' and not self.fIsInTree: g_oEnv.set(f'SDK_{self.sSdkName}_INCS', ' '.join(self.asIncPaths)); g_oEnv.set(f'SDK_{self.sSdkName}_LIBS', ' '.join(self.getSdkLibs())); From 510d3cbab1da9f063c3ddbeb22c5c283da00b8d7 Mon Sep 17 00:00:00 2001 From: Teknomancer Date: Mon, 10 Aug 2026 07:19:06 +0000 Subject: [PATCH 045/176] Config.kmk: Merge GitHub PR #803 (Fix username with hyphens in Config.kmk), github:gh-803 github-merge-author: Teknomancer svn:sync-xref-src-repo-rev: r174743 --- Config.kmk | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Config.kmk b/Config.kmk index d6b21ecf8a2f..f4006bd336cc 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114901 2026-08-07 20:49:14Z klaus.espenlaub@oracle.com $ +# $Id: Config.kmk 114903 2026-08-10 07:19:06Z alexander.eichner@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -2061,7 +2061,8 @@ ASTOOL := $(VBOX_ASTOOL) # INCS += $(PATH_ROOT)/include $(PATH_OUT) DEFS += VBOX -DEFS.debug := DEBUG DEBUG_$(subst $(subst _, ,_),_,$(USERNAME)) DEBUG_USERNAME=$(subst $(subst _, ,_),_,$(USERNAME)) +USERNAME_FIXUP := $(subst -,_,$(subst $(subst _, ,_),_,$(USERNAME))) +DEFS.debug := DEBUG DEBUG_$(USERNAME_FIXUP) DEBUG_USERNAME=$(USERNAME_FIXUP) DEFS.dbgopt = $(DEFS.debug) DEFS.profile = VBOX_WITH_STATISTICS DEFS.strict = RT_STRICT VBOX_STRICT @@ -9604,7 +9605,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114901 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114903 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9618,7 +9619,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114901 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114903 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif From 79097eb6e1cf5871a12ba8ce3f975bb5c9ece773 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 08:28:37 +0000 Subject: [PATCH 046/176] Shared Folders/HostService: More validation for SHFL_FN_MAP_FOLDER. bugref:11141 svn:sync-xref-src-repo-rev: r174744 --- .../SharedFolders/VBoxSharedFoldersSvc.cpp | 22 +++--- .../testcase/tstSharedFolderService.cpp | 69 ++++++++++++++++++- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp index 039dfc3c7bad..5bf6574d13ff 100644 --- a/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp +++ b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedFoldersSvc.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxSharedFoldersSvc.cpp 114904 2026-08-10 08:28:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Folders - Host service entry points. */ @@ -1064,12 +1064,6 @@ static DECLCALLBACK(void) svcCall(void *, VBOXHGCMCALLHANDLE callHandle, uint32_ pStat = &g_StatMapFolder; pStatFail = &g_StatMapFolderFail; Log(("SharedFolders host service: svcCall: SHFL_FN_MAP_FOLDER\n")); - if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) - Log(("SharedFolders host service: request to map folder '%s'\n", - ((PSHFLSTRING)paParms[0].u.pointer.addr)->String.utf8)); - else - Log(("SharedFolders host service: request to map folder '%ls'\n", - ((PSHFLSTRING)paParms[0].u.pointer.addr)->String.utf16)); /* Verify parameter count and types. */ if (cParms != SHFL_CPARMS_MAP_FOLDER) @@ -1101,11 +1095,11 @@ static DECLCALLBACK(void) svcCall(void *, VBOXHGCMCALLHANDLE callHandle, uint32_ { rc = VERR_INVALID_PARAMETER; - /* Fudge for windows GAs getting the length wrong by one char. */ + /* Fudge for windows GAs getting the length wrong by one char. Let the + validator check the adjusted length and terminator before accessing it. */ if ( !(pClient->fu32Flags & SHFL_CF_UTF8) && paParms[0].u.pointer.size >= sizeof(SHFLSTRING) - && pszMapName->u16Length >= 2 - && pszMapName->String.utf16[pszMapName->u16Length / 2 - 1] == 0x0000) + && pszMapName->u16Length >= 2) { pszMapName->u16Length -= 2; if (ShflStringIsValidIn(pszMapName, paParms[0].u.pointer.size, false /*fUtf8Not16*/)) @@ -1117,7 +1111,14 @@ static DECLCALLBACK(void) svcCall(void *, VBOXHGCMCALLHANDLE callHandle, uint32_ /* Execute the function. */ if (RT_SUCCESS(rc)) + { + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + Log(("SharedFolders host service: request to map folder '%s'\n", pszMapName->String.utf8)); + else + Log(("SharedFolders host service: request to map folder '%ls'\n", pszMapName->String.utf16)); + rc = vbsfMapFolder(pClient, pszMapName, delimiter, fCaseSensitive, &root); + } if (RT_SUCCESS(rc)) { @@ -1973,4 +1974,3 @@ extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pta return rc; } - diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp index 6fc13b45d2b7..63e3e79a1101 100644 --- a/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp +++ b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp @@ -1,4 +1,4 @@ -/* $Id: tstSharedFolderService.cpp 114418 2026-06-18 06:56:36Z andreas.loeffler@oracle.com $ */ +/* $Id: tstSharedFolderService.cpp 114904 2026-08-10 08:28:37Z andreas.loeffler@oracle.com $ */ /** @file * Testcase for the shared folder service vbsf API. * @@ -630,7 +630,72 @@ void testMapFolderTwice(RTTEST hTest) { RT_NOREF1(hTest); } void testMapFolderDelimiter(RTTEST hTest) { RT_NOREF1(hTest); } void testMapFolderCaseSensitive(RTTEST hTest) { RT_NOREF1(hTest); } void testMapFolderCaseInsensitive(RTTEST hTest) { RT_NOREF1(hTest); } -void testMapFolderBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderBadParameters(RTTEST hTest) +{ + RTTestSub(hTest, "Map folder string validation"); + + VBOXHGCMSVCFNTABLE SvcTable; + VBOXHGCMSVCHELPERS SvcHelpers; + RT_ZERO(SvcTable); + RT_ZERO(SvcHelpers); + initTable(&SvcTable, &SvcHelpers); + int rc = VBoxHGCMSvcLoad(&SvcTable); + RTTEST_CHECK_RC_RETV(hTest, rc, VINF_SUCCESS); + + AssertRelease(SvcTable.pvService = RTTestGuardedAllocTail(hTest, SvcTable.cbClient)); + RT_BZERO(SvcTable.pvService, SvcTable.cbClient); + + /* Place the supplied six-byte buffer directly before a guard page. A length of four + used to make the compatibility workaround read the first two bytes beyond it. */ + PSHFLSTRING pMalformed = (PSHFLSTRING)RTTestGuardedAllocTail(hTest, sizeof(*pMalformed)); + AssertRelease(pMalformed); + pMalformed->u16Size = sizeof(RTUTF16); + pMalformed->u16Length = sizeof(RTUTF16) * 2; + pMalformed->String.utf16[0] = 0; + + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_MAP_FOLDER]; + HGCMSvcSetPv(&aParms[0], pMalformed, sizeof(*pMalformed)); + HGCMSvcSetU32(&aParms[1], 0); /* root */ + HGCMSvcSetU32(&aParms[2], '/'); /* delimiter */ + HGCMSvcSetU32(&aParms[3], true); /* fCaseSensitive */ + + VBOXHGCMCALLHANDLE_TYPEDEF CallHandle = { VERR_INTERNAL_ERROR }; + SvcTable.pfnCall(SvcTable.pvService, &CallHandle, 0, SvcTable.pvService, SHFL_FN_MAP_FOLDER, + SHFL_CPARMS_MAP_FOLDER, aParms, 0); + RTTEST_CHECK_RC(hTest, CallHandle.rc, VERR_INVALID_PARAMETER); + RTTEST_CHECK(hTest, pMalformed->u16Length == sizeof(RTUTF16) * 2); + + /* Also cover the largest even length accepted by the wire format. */ + pMalformed->u16Length = UINT16_MAX - 1; + CallHandle.rc = VERR_INTERNAL_ERROR; + SvcTable.pfnCall(SvcTable.pvService, &CallHandle, 0, SvcTable.pvService, SHFL_FN_MAP_FOLDER, + SHFL_CPARMS_MAP_FOLDER, aParms, 0); + RTTEST_CHECK_RC(hTest, CallHandle.rc, VERR_INVALID_PARAMETER); + RTTEST_CHECK(hTest, pMalformed->u16Length == UINT16_MAX - 1); + + /* Preserve the legacy case: old Windows clients included the terminator in u16Length. */ + PSHFLSTRING pLegacy = (PSHFLSTRING)RTTestGuardedAllocTail(hTest, + SHFLSTRING_HEADER_SIZE + sizeof(RTUTF16) * 2); + AssertRelease(pLegacy); + pLegacy->u16Size = sizeof(RTUTF16) * 2; + pLegacy->u16Length = sizeof(RTUTF16) * 2; + pLegacy->String.utf16[0] = 'x'; + pLegacy->String.utf16[1] = 0; + HGCMSvcSetPv(&aParms[0], pLegacy, SHFLSTRING_HEADER_SIZE + pLegacy->u16Size); + CallHandle.rc = VERR_INTERNAL_ERROR; + SvcTable.pfnCall(SvcTable.pvService, &CallHandle, 0, SvcTable.pvService, SHFL_FN_MAP_FOLDER, + SHFL_CPARMS_MAP_FOLDER, aParms, 0); + RTTEST_CHECK_RC(hTest, CallHandle.rc, VERR_FILE_NOT_FOUND); + RTTEST_CHECK(hTest, pLegacy->u16Length == sizeof(RTUTF16)); + + rc = SvcTable.pfnDisconnect(NULL, 0, SvcTable.pvService); + RTTEST_CHECK_RC(hTest, rc, VINF_SUCCESS); + rc = SvcTable.pfnUnload(NULL); + RTTEST_CHECK_RC(hTest, rc, VINF_SUCCESS); + RTTEST_CHECK_RC(hTest, RTTestGuardedFree(hTest, pLegacy), VINF_SUCCESS); + RTTEST_CHECK_RC(hTest, RTTestGuardedFree(hTest, pMalformed), VINF_SUCCESS); + RTTEST_CHECK_RC(hTest, RTTestGuardedFree(hTest, SvcTable.pvService), VINF_SUCCESS); +} /* Sub-tests for testUnmapFolder(). */ void testUnmapFolderValid(RTTEST hTest) { RT_NOREF1(hTest); } From d01cfd8f68453d733e4c6a465493165483e6ddfc Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 08:44:49 +0000 Subject: [PATCH 047/176] Shared Clipboard: Fixed event lifetime race during source reset. bugref:11145 svn:sync-xref-src-repo-rev: r174747 --- .../SharedClipboard/clipboard-common.cpp | 44 ++++--- .../testcase/tstClipboardTransfers.cpp | 121 +++++++++++++++++- 2 files changed, 147 insertions(+), 18 deletions(-) diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp index 4287287b1133..e43921511604 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-common.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-common.cpp 114907 2026-08-10 08:44:49Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common helper objects. */ @@ -213,13 +213,12 @@ static void shClEventSourceResetInternal(PSHCLEVENTSOURCE pSource) if (!fDealloc) Log3Func(("Event %RU32 has %RU32 references left, skipping de-allocation\n", pEvIt->idEvent, pEvIt->cRefs)); - shClEventDestroy(pEvIt); - int rc2 = shClEventSourceUnregisterEvent(pSource, pEvIt); AssertRC(rc2); if (fDealloc) { + shClEventDestroy(pEvIt); RTMemFree(pEvIt); pEvIt = NULL; } @@ -583,27 +582,38 @@ uint32_t ShClEventRelease(PSHCLEVENT pEvent) AssertReturn(ASMAtomicReadU32(&pEvent->cRefs) > 0, UINT32_MAX); - uint32_t const cRefs = ASMAtomicDecU32(&pEvent->cRefs); - if (cRefs == 0) + /* Serialize the final release with a source reset, which can detach the event while we wait for the source lock. */ + uint32_t cRefs; + int rc = VINF_SUCCESS; + PSHCLEVENTSOURCE pParent = pEvent->pParent; + if ( pParent + && RTCritSectIsInitialized(&pParent->CritSect)) { - int rc; - PSHCLEVENTSOURCE pParent = pEvent->pParent; - if ( pParent - && RTCritSectIsInitialized(&pParent->CritSect)) + rc = RTCritSectEnter(&pParent->CritSect); + if (RT_SUCCESS(rc)) { - rc = RTCritSectEnter(&pParent->CritSect); - if (RT_SUCCESS(rc)) - { + cRefs = ASMAtomicDecU32(&pEvent->cRefs); + if ( cRefs == 0 + && pEvent->pParent == pParent) rc = shClEventSourceUnregisterEvent(pParent, pEvent); - int rc2 = RTCritSectLeave(&pParent->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; - } + int rc2 = RTCritSectLeave(&pParent->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; } - else + else if (pEvent->pParent == NULL) + { + cRefs = ASMAtomicDecU32(&pEvent->cRefs); rc = VINF_SUCCESS; + } + else + return UINT32_MAX; + } + else + cRefs = ASMAtomicDecU32(&pEvent->cRefs); + if (cRefs == 0) + { if (RT_SUCCESS(rc)) { shClEventDestroy(pEvent); diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp index fdee0153ede8..20586e0610eb 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardTransfers.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardTransfers.cpp 114907 2026-08-10 08:44:49Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard transfers test case. */ @@ -351,6 +351,32 @@ static void testPathSanitize(void) RTTESTI_CHECK_RC(rc, VERR_BUFFER_OVERFLOW); } +typedef struct TESTEVENTWAITCTX +{ + PSHCLEVENT pEvent; + PSHCLEVENTPAYLOAD pPayload; + int rcWait; +} TESTEVENTWAITCTX; +typedef TESTEVENTWAITCTX *PTESTEVENTWAITCTX; + +static DECLCALLBACK(int) testEventWaitThread(RTTHREAD hThread, void *pvUser) +{ + PTESTEVENTWAITCTX pCtx = (PTESTEVENTWAITCTX)pvUser; + + int rc = RTThreadUserSignal(hThread); + if (RT_SUCCESS(rc)) + pCtx->rcWait = rc = ShClEventWait(pCtx->pEvent, RT_MS_5SEC, &pCtx->pPayload); + return rc; +} + +static DECLCALLBACK(int) testEventReleaseThread(RTTHREAD hThread, void *pvUser) +{ + int rc = RTThreadUserSignal(hThread); + if (RT_SUCCESS(rc)) + rc = ShClEventRelease((PSHCLEVENT)pvUser) == 0 ? VINF_SUCCESS : VERR_INTERNAL_ERROR; + return rc; +} + static void testEvents(void) { RTTestISub("Testing events"); @@ -364,12 +390,105 @@ static void testEvents(void) RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); PSHCLEVENT pEvent; RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); + + uint32_t const uPayloadData = UINT32_C(0x12345678); + PSHCLEVENTPAYLOAD pPayload = NULL; + RTTESTI_CHECK_RC_OK(ShClPayloadCreateDupData(42, &uPayloadData, sizeof(uPayloadData), &pPayload)); + int rc = ShClEventSignal(pEvent, pPayload); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + ShClPayloadDestroy(pPayload); + + /* Reset must not destroy resources owned by an event which is still referenced. */ ShClEventSourceReset(&Source); RTTESTI_CHECK(ShClEventSourceGetLast(&Source) == NULL); /* Event still valid, but removed from the source. */ + + PSHCLEVENTPAYLOAD pPayloadResult = NULL; + RTTESTI_CHECK_RC_OK(ShClEventWait(pEvent, 0, &pPayloadResult)); + RTTESTI_CHECK(pPayloadResult != NULL); + if (pPayloadResult) + { + RTTESTI_CHECK(pPayloadResult->uID == 42); + RTTESTI_CHECK(pPayloadResult->cbData == sizeof(uPayloadData)); + RTTESTI_CHECK(pPayloadResult->pvData != NULL); + if (pPayloadResult->pvData) + RTTESTI_CHECK(*(uint32_t *)pPayloadResult->pvData == uPayloadData); + ShClPayloadDestroy(pPayloadResult); + } + RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); /* Free'd event, as ref count is 0. */ RTTESTI_CHECK(ShClEventSourceGetLast(&Source) == NULL); /* Now it should be empty. */ RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); + /* Reset an event source while another thread waits on one of its events. */ + RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); + RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); + + TESTEVENTWAITCTX WaitCtx; + RT_ZERO(WaitCtx); + WaitCtx.pEvent = pEvent; + WaitCtx.rcWait = VERR_IPE_UNINITIALIZED_STATUS; + + RTTHREAD hThread; + rc = RTThreadCreate(&hThread, testEventWaitThread, &WaitCtx, 0, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "ShClEvtWait"); + RTTESTI_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC_OK(RTThreadUserWait(hThread, RT_MS_5SEC)); + RTThreadSleep(10); /* Let the thread enter ShClEventWait(). */ + + ShClEventSourceReset(&Source); + RTTESTI_CHECK_RC_OK(ShClEventSignal(pEvent, NULL)); + + int rcThread; + int rcWait = RTThreadWait(hThread, RT_MS_5SEC, &rcThread); + RTTESTI_CHECK_RC_OK(rcWait); + if (RT_FAILURE(rcWait)) /* Do not release the event while the waiter might still be using it. */ + rcWait = RTThreadWait(hThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rcWait)) + { + RTTESTI_CHECK_RC_OK(rcThread); + RTTESTI_CHECK_RC_OK(WaitCtx.rcWait); + RTTESTI_CHECK(WaitCtx.pPayload == NULL); + } + } + RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); + RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); + + /* A final release must take the source lock before publishing a zero reference count. */ + RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); + RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); + RTTESTI_CHECK_RC_OK(RTCritSectEnter(&Source.CritSect)); + + rc = RTThreadCreate(&hThread, testEventReleaseThread, pEvent, 0, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "ShClEvtRel"); + RTTESTI_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC_OK(RTThreadUserWait(hThread, RT_MS_5SEC)); + for (unsigned i = 0; i < 1000 && RTCritSectGetWaiters(&Source.CritSect) == 0; ++i) + RTThreadSleep(1); + RTTESTI_CHECK(RTCritSectGetWaiters(&Source.CritSect) > 0); + RTTESTI_CHECK(ShClEventGetRefs(pEvent) == 1); + + ShClEventSourceReset(&Source); + } + RTTESTI_CHECK_RC_OK(RTCritSectLeave(&Source.CritSect)); + if (RT_SUCCESS(rc)) + { + int rcThread; + int rcWait = RTThreadWait(hThread, RT_MS_5SEC, &rcThread); + RTTESTI_CHECK_RC_OK(rcWait); + if (RT_FAILURE(rcWait)) /* Do not terminate the source while the releaser might still be using it. */ + rcWait = RTThreadWait(hThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rcWait)) + RTTESTI_CHECK_RC_OK(rcThread); + } + else + RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); + RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); + /* Test delayed destruction of the event by retaining it. */ RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); From 70bfddd8ae364e212914d83318426281d604901f Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Mon, 10 Aug 2026 11:42:35 +0000 Subject: [PATCH 048/176] Storage/CUE.cpp: Track number must be greater than 0, bugref:11127 svn:sync-xref-src-repo-rev: r174750 --- src/VBox/Storage/CUE.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VBox/Storage/CUE.cpp b/src/VBox/Storage/CUE.cpp index 0628d3c90728..11dcc9389f40 100644 --- a/src/VBox/Storage/CUE.cpp +++ b/src/VBox/Storage/CUE.cpp @@ -1,4 +1,4 @@ -/* $Id: CUE.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: CUE.cpp 114910 2026-08-10 11:42:35Z alexander.eichner@oracle.com $ */ /** @file * CUE - CUE/BIN Disk image, Core Code. */ @@ -954,7 +954,8 @@ static int cueParseTrack(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer) if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_INTEGER_UNSIGNED) { uint64_t u64Track = cueTokenizerConsumeInteger(pTokenizer); - if (u64Track <= 99) + if ( u64Track >= 1 + && u64Track <= 99) { /* Parse the data mode. */ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD) From 4a6a2ba5887ae390feddb88518d91b6d5031db46 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Mon, 10 Aug 2026 12:03:20 +0000 Subject: [PATCH 049/176] VRDP,Main: disabled processing of obsolete commands. bugref:11131 svn:sync-xref-src-repo-rev: r174752 --- src/VBox/Main/src-client/DisplayImpl.cpp | 33 +++---- .../Main/src-client/DisplayImplLegacy.cpp | 29 +++--- src/VBox/RDP/server/bmpcache.h | 22 ++++- src/VBox/RDP/server/output.cpp | 9 +- src/VBox/RDP/server/shadowbuffer.cpp | 93 +++++++++++++------ src/VBox/RDP/server/shadowbuffer.h | 3 +- src/VBox/RDP/server/textcache.cpp | 74 ++++++++++----- src/VBox/RDP/server/textcache.h | 4 +- src/VBox/RDP/server/videodetector.cpp | 7 +- src/VBox/RDP/server/videostream.h | 3 +- src/VBox/RDP/server/vrdpapi.cpp | 5 +- 11 files changed, 190 insertions(+), 92 deletions(-) diff --git a/src/VBox/Main/src-client/DisplayImpl.cpp b/src/VBox/Main/src-client/DisplayImpl.cpp index 95e3727a2a18..b0fab7019ab7 100644 --- a/src/VBox/Main/src-client/DisplayImpl.cpp +++ b/src/VBox/Main/src-client/DisplayImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: DisplayImpl.cpp 114707 2026-07-14 13:40:18Z vitali.pelenjow@oracle.com $ */ +/* $Id: DisplayImpl.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox COM class implementation */ @@ -3576,8 +3576,10 @@ DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pIn struct VBVACMDHDR const RT_UNTRUSTED_VOLATILE_GUEST *pCmd, size_t cbCmd) { LogFlowFunc(("uScreenId %d pCmd %p cbCmd %d, @%d,%d %dx%d\n", uScreenId, pCmd, cbCmd, pCmd->x, pCmd->y, pCmd->w, pCmd->h)); - VBVACMDHDR hdrSaved; - RT_COPY_VOLATILE(hdrSaved, *pCmd); + AssertReturnVoid(cbCmd >= sizeof(VBVACMDHDR)); + + VBVACMDHDR hdr; + RT_COPY_VOLATILE(hdr, *pCmd); RT_UNTRUSTED_NONVOLATILE_COPY_FENCE(); PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); @@ -3593,7 +3595,7 @@ DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pIn if ( uScreenId == VBOX_VIDEO_PRIMARY_SCREEN && !pFBInfo->fDisabled) { - pDrv->pUpPort->pfnUpdateDisplayRect(pDrv->pUpPort, hdrSaved.x, hdrSaved.y, hdrSaved.w, hdrSaved.h); + pDrv->pUpPort->pfnUpdateDisplayRect(pDrv->pUpPort, hdr.x, hdr.y, hdr.w, hdr.h); } else if ( !pFBInfo->pSourceBitmap.isNull() && !pFBInfo->fDisabled @@ -3615,12 +3617,12 @@ DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pIn &bitmapFormat); if (SUCCEEDED(hrc)) { - uint32_t width = hdrSaved.w; - uint32_t height = hdrSaved.h; + uint32_t width = hdr.w; + uint32_t height = hdr.h; const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM; - int32_t xSrc = hdrSaved.x - pFBInfo->xOrigin; - int32_t ySrc = hdrSaved.y - pFBInfo->yOrigin; + int32_t xSrc = hdr.x - pFBInfo->xOrigin; + int32_t ySrc = hdr.y - pFBInfo->yOrigin; uint32_t u32SrcWidth = pFBInfo->w; uint32_t u32SrcHeight = pFBInfo->h; uint32_t u32SrcLineSize = pFBInfo->u32LineSize; @@ -3648,18 +3650,11 @@ DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pIn } } - /* - * Here is your classic 'temporary' solution. - */ - /** @todo New SendUpdate entry which can get a separate cmd header or coords. */ - VBVACMDHDR *pHdrUnconst = (VBVACMDHDR *)pCmd; - - pHdrUnconst->x -= (int16_t)pFBInfo->xOrigin; - pHdrUnconst->y -= (int16_t)pFBInfo->yOrigin; - - pThis->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, pHdrUnconst, (uint32_t)cbCmd); + /* Always send just a bitmap update to the VRDP server. */ + hdr.x -= (int16_t)pFBInfo->xOrigin; + hdr.y -= (int16_t)pFBInfo->yOrigin; - *pHdrUnconst = hdrSaved; + pThis->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, &hdr, sizeof(hdr)); } DECLCALLBACK(void) Display::i_displayVBVAUpdateEnd(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId, int32_t x, int32_t y, diff --git a/src/VBox/Main/src-client/DisplayImplLegacy.cpp b/src/VBox/Main/src-client/DisplayImplLegacy.cpp index 337561834158..d7a8aaead193 100644 --- a/src/VBox/Main/src-client/DisplayImplLegacy.cpp +++ b/src/VBox/Main/src-client/DisplayImplLegacy.cpp @@ -1,4 +1,4 @@ -/* $Id: DisplayImplLegacy.cpp 114870 2026-08-06 18:20:51Z vitali.pelenjow@oracle.com $ */ +/* $Id: DisplayImplLegacy.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox IDisplay implementation, helpers for legacy GAs. * @@ -678,19 +678,18 @@ int Display::i_videoAccelFlush(PPDMIDISPLAYPORT pUpPort) cbCmd, phdr->x, phdr->y, phdr->w, phdr->h)); #endif /* DEBUG_sunlover */ - VBVACMDHDR hdrSaved = *phdr; - - int x = phdr->x; - int y = phdr->y; - int w = phdr->w; - int h = phdr->h; + VBVACMDHDR hdr = *phdr; + int x = hdr.x; + int y = hdr.y; + int w = hdr.w; + int h = hdr.h; uScreenId = mapCoordsToScreen(maFramebuffers, mcMonitors, &x, &y, &w, &h); - phdr->x = (int16_t)x; - phdr->y = (int16_t)y; - phdr->w = (uint16_t)w; - phdr->h = (uint16_t)h; + hdr.x = (int16_t)x; + hdr.y = (int16_t)y; + hdr.w = (uint16_t)w; + hdr.h = (uint16_t)h; /* Handle the command. * @@ -705,12 +704,10 @@ int Display::i_videoAccelFlush(PPDMIDISPLAYPORT pUpPort) */ /* Accumulate the update. */ - vbvaRgnDirtyRect(&rgn, uScreenId, phdr); - - /* Forward the command to VRDP server. */ - mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, phdr, cbCmd); + vbvaRgnDirtyRect(&rgn, uScreenId, &hdr); - *phdr = hdrSaved; + /* Forward the command to VRDP server as a bitmap update. */ + mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, &hdr, sizeof(hdr)); } i_vbvaReleaseCmd(pVideoAccel, phdr, cbCmd); diff --git a/src/VBox/RDP/server/bmpcache.h b/src/VBox/RDP/server/bmpcache.h index e3c0e6a40b95..f537a0b23c0a 100644 --- a/src/VBox/RDP/server/bmpcache.h +++ b/src/VBox/RDP/server/bmpcache.h @@ -1,4 +1,4 @@ -/* $Id: bmpcache.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: bmpcache.h 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -88,4 +88,24 @@ void *BCBitmapHeapBlockQuery(PBMPCACHE pbc, const BCHEAPHANDLE *pHandle, int32_t void BCBitmapHeapBlockRelease(PBMPCACHE pbc, const BCHEAPHANDLE *pHandle); void BCBitmapHeapBlockFree(PBMPCACHE pbc, const BCHEAPHANDLE *pHandle); +DECLINLINE(bool) isValidBitsHdr(VRDEDATABITS const *pBitsHdr) +{ + switch (pBitsHdr->cbPixel) + { + case 2: + case 3: + case 4: break; + + default: + return false; + } + + /* uint16_t * uint16_t * uint8_t */ + uint64_t const u64BitmapSize = (uint64_t)pBitsHdr->cWidth * pBitsHdr->cHeight * pBitsHdr->cbPixel; + if (pBitsHdr->cb < u64BitmapSize) + return false; + + return true; +} + #endif /* !VRDP_INCLUDED_SRC_bmpcache_h */ diff --git a/src/VBox/RDP/server/output.cpp b/src/VBox/RDP/server/output.cpp index f6fa3a76b483..193de874f554 100644 --- a/src/VBox/RDP/server/output.cpp +++ b/src/VBox/RDP/server/output.cpp @@ -1,4 +1,4 @@ -/* $Id: output.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: output.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -608,6 +608,11 @@ int VRDPServer::OutputThread (RTTHREAD self, VRDPServerThreadStartCtx *pCtx) SERVERLOG(("OutputThread: VRDE_ORDER_MEMBLT %d,%d %dx%d from %d,%d rop 0x%02X\n", pOrder->x, pOrder->y, pOrder->w, pOrder->h, pOrder->xSrc, pOrder->ySrc, pOrder->rop)); + if (!shadowBufferAreOrderCoordsValid(action.uScreenId, pOrder->x, pOrder->y, pOrder->w, pOrder->h)) + { + break; + } + /* Locate the bitmap in the cache. */ PBMPCACHEENTRY pbce = BCFindBitmap (m_pbc, &pOrder->hash); @@ -852,7 +857,7 @@ int VRDPServer::OutputThread (RTTHREAD self, VRDPServerThreadStartCtx *pCtx) */ TCFONTTEXT2 *pFontText2 = NULL; - bool fSuccess = TCCacheGlyphs (m_ptc, pOrder, &pFontText2); + bool fSuccess = TCCacheGlyphs (m_ptc, pOrder, action.u.order.cbOrder, &pFontText2); if (!fSuccess) { diff --git a/src/VBox/RDP/server/shadowbuffer.cpp b/src/VBox/RDP/server/shadowbuffer.cpp index 9f9df4431b2b..d3ef82dedead 100644 --- a/src/VBox/RDP/server/shadowbuffer.cpp +++ b/src/VBox/RDP/server/shadowbuffer.cpp @@ -1,4 +1,4 @@ -/* $Id: shadowbuffer.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: shadowbuffer.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -360,11 +360,13 @@ typedef struct _VRDPSBSCREEN static DECLCALLBACK(bool) vscVideoSourceStreamStart(void *pvCallback, uint32_t u32SourceStreamId, const RGNRECT *prect, int64_t timeStart); static DECLCALLBACK(void) vscVideoSourceStreamStop(void *pvCallback, uint32_t u32SourceStreamId, const RGNRECT *prect); +static DECLCALLBACK(bool) vscIsValidRect(void *pvCallback, const RGNRECT *prect); static VIDEOSTREAMCALLBACKS vsCallbacks = { vscVideoSourceStreamStart, - vscVideoSourceStreamStop + vscVideoSourceStreamStop, + vscIsValidRect, }; #define VRDP_SB_TO_SCREEN(__psb) ((VRDPSBSCREEN *)((uint8_t *)(__psb) - RT_UOFFSETOF(VRDPSBSCREEN, sb))) @@ -2385,6 +2387,12 @@ static DECLCALLBACK(void) vscVideoSourceStreamStop(void *pvCallback, uint32_t u3 return; } +static DECLCALLBACK(bool) vscIsValidRect(void *pvCallback, const RGNRECT *prect) +{ + VRDPSBSCREEN *pScreen = (VRDPSBSCREEN *)pvCallback; + AssertPtrReturn(pScreen, false); + return rgnIsRectWithin(&pScreen->sb.pixelBuffer.rect, prect); +} void shadowBufferUpdateComplete(void) { @@ -2998,6 +3006,40 @@ void shadowBufferQueryRect (unsigned uScreenId, RGNRECT *prect) } } +bool shadowBufferAreOrderCoordsValid(unsigned uScreenId, int32_t x, int32_t y, uint32_t w, uint32_t h) +{ + SBLOG(("Enter: uScreenId = %d\n", uScreenId)); + + bool fValid = false; + + if (sbLock (uScreenId)) + { + VRDPSBSCREEN *pScreen = sbResolveScreenId (uScreenId); + + if (pScreen != NULL) + { + RGNRECT rect; + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + RGNRECT rectFB; + rectFB.x = 0; + rectFB.y = 0; + rectFB.w = pScreen->sb.transform.cFBWidth; + rectFB.h = pScreen->sb.transform.cFBHeight; + + fValid = rgnIsRectWithin(&rectFB, &rect); + } + + sbUnlock (); + } + + return fValid; +} + + void shadowBufferTransformRect (unsigned uScreenId, RGNRECT *prect) { SBLOG(("Enter: uScreenId = %d\n", uScreenId)); @@ -3578,6 +3620,11 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) rectAffected.y = hdr.y; rectAffected.w = hdr.w; rectAffected.h = hdr.h; + if (!rgnIsRectWithin(&pScreen->sb.pixelBuffer.rect, &rectAffected)) + { + sbUnlock (); + return; + } pScreen->sb.transform.pfnTransformRect (&rectAffected, pScreen->sb.transform.cSBWidth, pScreen->sb.transform.cSBHeight); @@ -3606,18 +3653,6 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) pBitsHdr->cb, pBitsHdr->x, pBitsHdr->y, pBitsHdr->cWidth, pBitsHdr->cHeight, pBitsHdr->cbPixel, *(uint32_t *)&pOrder->hash[0], *(uint32_t *)&pOrder->hash[4], *(uint32_t *)&pOrder->hash[8], *(uint32_t *)&pOrder->hash[12])); - switch (pBitsHdr->cbPixel) - { - case 2: - case 3: - case 4: break; - - default: - SBLOG(("Unsupported cbPixel (%d)!!!", pBitsHdr->cbPixel)); - sbUnlock (); - return; - } - /* Verify that the buffer is big enough for those bits. */ if (pBitsHdr->cb > cbSrcRemaining) { @@ -3628,6 +3663,12 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) return; } + if (!isValidBitsHdr(pBitsHdr)) + { + sbUnlock (); + return; + } + BCHEAPHANDLE hBmp; int rc = BCStore(&hBmp, g_pCtx->pServer->BC(), VRDE_ORDER_CACHED_BITMAP, @@ -3693,6 +3734,12 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) SBLOG(("VRDE_ORDER_SAVESCREEN: pBitsHdr cb = %d, x = %d, y = %d, cWidth = %d, cHeight = %d, cbPixel = %d\n", pBitsHdr->cb, pBitsHdr->x, pBitsHdr->y, pBitsHdr->cWidth, pBitsHdr->cHeight, pBitsHdr->cbPixel)); + if (!isValidBitsHdr(pBitsHdr)) + { + sbUnlock (); + return; + } + /* Save bitmap in the bmpcache intermediate heap, if that fails, do a bitmap update. */ BCHEAPHANDLE hBmp; int rc = BCStore(&hBmp, g_pCtx->pServer->BC(), @@ -3752,18 +3799,6 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) SBLOG(("VRDE_ORDER_DIRTY_RECT: cb %d, %d,%d %dx%d, cbPixel %d\n", pBitsHdr->cb, pBitsHdr->x, pBitsHdr->y, pBitsHdr->cWidth, pBitsHdr->cHeight, pBitsHdr->cbPixel)); - switch (pBitsHdr->cbPixel) - { - case 2: - case 3: - case 4: break; - - default: - SBLOG(("Unsupported cbPixel (%d)!!!", pBitsHdr->cbPixel)); - sbUnlock (); - return; - } - /* Verify that the buffer is big enough for those bits. */ if (pBitsHdr->cb > cbSrcRemaining) { @@ -3774,6 +3809,12 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) return; } + if (!isValidBitsHdr(pBitsHdr)) + { + sbUnlock (); + return; + } + /* Copy bits to pixel buffers. */ uint32_t cbLine = pBitsHdr->cWidth * pBitsHdr->cbPixel; diff --git a/src/VBox/RDP/server/shadowbuffer.h b/src/VBox/RDP/server/shadowbuffer.h index 89495833996e..66f640c25371 100644 --- a/src/VBox/RDP/server/shadowbuffer.h +++ b/src/VBox/RDP/server/shadowbuffer.h @@ -1,4 +1,4 @@ -/* $Id: shadowbuffer.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: shadowbuffer.h 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -98,6 +98,7 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata); void shadowBufferQueryRect (unsigned uScreenId, RGNRECT *prect); +bool shadowBufferAreOrderCoordsValid(unsigned uScreenId, int32_t x, int32_t y, uint32_t w, uint32_t h); void shadowBufferTransformRect (unsigned uScreenId, RGNRECT *prect); void shadowBufferTransformRectGeneric (unsigned uScreenId, RGNRECT *prect, unsigned w, unsigned h); void shadowBufferTransformWidthHeight(unsigned uScreenId, unsigned *pw, unsigned *ph); diff --git a/src/VBox/RDP/server/textcache.cpp b/src/VBox/RDP/server/textcache.cpp index 2c418d9c598d..2343cbd6b2be 100644 --- a/src/VBox/RDP/server/textcache.cpp +++ b/src/VBox/RDP/server/textcache.cpp @@ -1,4 +1,4 @@ -/* $Id: textcache.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: textcache.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -122,9 +122,8 @@ static uint8_t *tcGlyphBitmap (const TCGLYPHFONT *pFont, int iGlyph) return NULL; } -#ifdef RT_STRICT /* Return maximum size of a bitmap for this font. */ -static int tcGlyphBitmapMaxSize (const TCGLYPHFONT *pFont) +static uint32_t tcGlyphBitmapMaxSize (const TCGLYPHFONT *pFont) { switch (pFont->iRDPFontHandle) { @@ -140,7 +139,6 @@ static int tcGlyphBitmapMaxSize (const TCGLYPHFONT *pFont) AssertFailed (); return 0; } -#endif /* Convert the server font array index to the RDP font handle. */ static int tcRDPHandleFromServerIndex (int index) @@ -222,19 +220,22 @@ static void tcClearFontCache (TCGLYPHFONT *pFont) pFont->cGlyphsCached = 0; } -static TCCACHEDGLYPH *tcCacheGlyph (TCGLYPHFONT *pFont, const VRDEORDERGLYPH *pGlyph) +static TCCACHEDGLYPH *tcCacheGlyph (TCGLYPHFONT *pFont, const VRDEORDERGLYPH *pGlyph, uint32_t cbGlyphBitmap) { - Assert (pFont->cGlyphsCached < pFont->cGlyphsMax); - - /* Allocate place for the new glyph, remember its index. */ - int iGlyph = pFont->cGlyphsCached++; - if (pFont->cGlyphsCached >= pFont->cGlyphsMax) { /* No place for the new glyph. */ return NULL; } + if (cbGlyphBitmap > tcGlyphBitmapMaxSize (pFont)) + { + return NULL; + } + + /* Allocate place for the new glyph, remember its index. */ + int iGlyph = pFont->cGlyphsCached++; + /* Convert the VRDEORDERGLYPH to the cache glyph format. */ TCCACHEDGLYPH *pCachedGlyph = &pFont->aGlyphs[iGlyph]; @@ -250,13 +251,7 @@ static TCCACHEDGLYPH *tcCacheGlyph (TCGLYPHFONT *pFont, const VRDEORDERGLYPH *pG pCachedGlyph->pu8Bitmap = tcGlyphBitmap (pFont, iGlyph); - int cbBitmap = (pCachedGlyph->w + 7) / 8; /* Line size in bytes. */ - cbBitmap *= pCachedGlyph->h; /* Size of bitmap. */ - cbBitmap = (cbBitmap + 3) & ~3; /* 32 bit DWORD align. */ - - Assert (cbBitmap <= tcGlyphBitmapMaxSize (pFont)); - - memcpy (pCachedGlyph->pu8Bitmap, pGlyph->au8Bitmap, cbBitmap); + memcpy (pCachedGlyph->pu8Bitmap, pGlyph->au8Bitmap, cbGlyphBitmap); return pCachedGlyph; } @@ -280,22 +275,51 @@ static TCCACHEDGLYPH *tcFindCachedGlyph (TCGLYPHFONT *pFont, const VRDEORDERGLYP } -static int tcTryCacheGlyphs (const VRDEORDERTEXT *pOrder, TCGLYPHFONT *pFont, TCFONTTEXT2 *pFontText2) +static int tcTryCacheGlyphs (const VRDEORDERTEXT *pOrder, uint32_t cbOrder, TCGLYPHFONT *pFont, TCFONTTEXT2 *pFontText2) { int rc = VINF_SUCCESS; /* Scan the string and check glyphs. */ const VRDEORDERGLYPH *pGlyph = (VRDEORDERGLYPH *)(&pOrder[1]); /* Glyphs follow the order structure. */ + uint32_t cbLeft = cbOrder; unsigned i; for (i = 0; i < pOrder->u8Glyphs; i++) { + if (cbLeft < RT_UOFFSETOF(VRDEORDERGLYPH, au8Bitmap)) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + if (pGlyph->o32NextGlyph > cbLeft) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + uint32_t cbGlyphBitmap = (pGlyph->w + 7) / 8; /* Line size in bytes. */ + cbGlyphBitmap *= pGlyph->h; /* Size of bitmap. */ + cbGlyphBitmap = (cbGlyphBitmap + 3) & ~3; /* 32 bit DWORD align. */ + + if (cbGlyphBitmap > cbLeft - RT_UOFFSETOF(VRDEORDERGLYPH, au8Bitmap)) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + if (cbGlyphBitmap > pOrder->u16MaxGlyph) + { + rc = VERR_INVALID_PARAMETER; + break; + } + /* Find the glyph in the cache. */ TCCACHEDGLYPH *pCachedGlyph = tcFindCachedGlyph (pFont, pGlyph); if (!pCachedGlyph) { - pCachedGlyph = tcCacheGlyph (pFont, pGlyph); + pCachedGlyph = tcCacheGlyph (pFont, pGlyph, cbGlyphBitmap); } if (!pCachedGlyph) @@ -322,6 +346,7 @@ static int tcTryCacheGlyphs (const VRDEORDERTEXT *pOrder, TCGLYPHFONT *pFont, TC pFontText2->cGlyphs++; pGlyph = (const VRDEORDERGLYPH *)((uint8_t *)pGlyph + pGlyph->o32NextGlyph); + cbLeft -= pGlyph->o32NextGlyph; } return rc; @@ -363,7 +388,7 @@ static int tcSetupFontText2 (TCFONTTEXT2 *pFontText2, TCGLYPHFONT *pFont, const } -bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **ppFontText2) +bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, uint32_t cbOrder, TCFONTTEXT2 **ppFontText2) { /* Check which glyphs are already cached. The original order is copied * to the TCFONTTEXT2 structure and the status of each glyph is determined. @@ -378,6 +403,11 @@ bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **p * to the string: ID - the cache index. CB is the string length. */ + if (cbOrder < sizeof(VRDEORDERTEXT)) + { + return false; + } + int iRDPFontHandle = tcSelectRDPHandle (ptc, pOrder); if (iRDPFontHandle == -1) @@ -394,7 +424,7 @@ bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **p TCGLYPHFONT *pFont = &ptc->glyphs.fonts[ tcServerIndexFromRDPHandle (iRDPFontHandle) ]; - int rc = tcTryCacheGlyphs (pOrder, pFont, pFontText2); + int rc = tcTryCacheGlyphs (pOrder, cbOrder, pFont, pFontText2); if (RT_FAILURE(rc)) { @@ -402,7 +432,7 @@ bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **p memset (pFontText2, 0, sizeof (TCFONTTEXT2)); - rc = tcTryCacheGlyphs (pOrder, pFont, pFontText2); + rc = tcTryCacheGlyphs (pOrder, cbOrder, pFont, pFontText2); } if (RT_SUCCESS (rc)) diff --git a/src/VBox/RDP/server/textcache.h b/src/VBox/RDP/server/textcache.h index 1babcccedbff..b747c731fff7 100644 --- a/src/VBox/RDP/server/textcache.h +++ b/src/VBox/RDP/server/textcache.h @@ -1,4 +1,4 @@ -/* $Id: textcache.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: textcache.h 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -171,7 +171,7 @@ typedef struct _TCFONTTEXT2 PTEXTCACHE TCCreate (void); void TCDelete (PTEXTCACHE ptc); -bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **ppFontText2); +bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, uint32_t cbOrder, TCFONTTEXT2 **ppFontText2); void TCFreeFontText2 (TCFONTTEXT2 *pFontText2); #endif /* !VRDP_INCLUDED_SRC_textcache_h */ diff --git a/src/VBox/RDP/server/videodetector.cpp b/src/VBox/RDP/server/videodetector.cpp index d7d0b20ff5fc..83e88d144879 100644 --- a/src/VBox/RDP/server/videodetector.cpp +++ b/src/VBox/RDP/server/videodetector.cpp @@ -1,4 +1,4 @@ -/* $Id: videodetector.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: videodetector.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -356,6 +356,11 @@ bool videoDetectorBitmapUpdate(VDCONTEXT *pCtx, return false; } + if (!pCtx->pCallbacks->pfnIsValidRect(pCtx->pvCallback, prectUpdate)) + { + return false; + } + #ifdef DEBUG_sunlover VIDEOLOG(("@%d,%d %dx%d\n", prectUpdate->x, prectUpdate->y, prectUpdate->w, prectUpdate->h)); #endif /* DEBUG_sunlover */ diff --git a/src/VBox/RDP/server/videostream.h b/src/VBox/RDP/server/videostream.h index 4e2810ce64c7..1138b8ac92c5 100644 --- a/src/VBox/RDP/server/videostream.h +++ b/src/VBox/RDP/server/videostream.h @@ -1,4 +1,4 @@ -/* $Id: videostream.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: videostream.h 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -47,6 +47,7 @@ typedef struct VIDEOSTREAMCALLBACKS { DECLR3CALLBACKMEMBER(bool, pfnVideoSourceStreamStart, (void *pvCallback, uint32_t u32SourceStreamId, const RGNRECT *prect, int64_t timeStart)); DECLR3CALLBACKMEMBER(void, pfnVideoSourceStreamStop, (void *pvCallback, uint32_t u32SourceStreamId, const RGNRECT *prect)); + DECLR3CALLBACKMEMBER(bool, pfnIsValidRect, (void *pvCallback, const RGNRECT *prect)); } VIDEOSTREAMCALLBACKS; typedef struct VDCONTEXT VDCONTEXT; diff --git a/src/VBox/RDP/server/vrdpapi.cpp b/src/VBox/RDP/server/vrdpapi.cpp index 79a5b273557d..c9de92ee52af 100644 --- a/src/VBox/RDP/server/vrdpapi.cpp +++ b/src/VBox/RDP/server/vrdpapi.cpp @@ -1,4 +1,4 @@ -/* $Id: vrdpapi.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: vrdpapi.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol - Public API functions. */ @@ -96,6 +96,7 @@ static DECLCALLBACK(void) VRDPUpdate (HVRDESERVER hServer, if (pServer) { +#if 0 /* obsolete */ if (cbUpdate > sizeof (VRDEORDERHDR)) { /* The update includes VRDP order information. */ @@ -103,6 +104,8 @@ static DECLCALLBACK(void) VRDPUpdate (HVRDESERVER hServer, pServer->ProcessOutputUpdate (uScreenId, pvUpdate, cbUpdate); } else if (cbUpdate == sizeof (VRDEORDERHDR)) +#endif + if (cbUpdate == sizeof (VRDEORDERHDR)) { /* This is just a bitmap update. */ VRDPAPILOG(("%p, %d, %d (bitmap)\n", pServer, cbUpdate, sizeof (VRDEORDERHDR))); From bb9ad93d9b3867daf820887de714235ee0a0f0b0 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Mon, 10 Aug 2026 12:06:34 +0000 Subject: [PATCH 050/176] Devices/Storage/DevLsiLogicSCSI.cpp: Fix range check for the firmware image header load adress, bugref:11129 svn:sync-xref-src-repo-rev: r174754 --- src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp index a74ab92d5fa4..d582e6e969d5 100644 --- a/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp +++ b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp @@ -1,4 +1,4 @@ -/* $Id: DevLsiLogicSCSI.cpp 114731 2026-07-20 09:16:25Z michal.necasek@oracle.com $ */ +/* $Id: DevLsiLogicSCSI.cpp 114914 2026-08-10 12:06:34Z alexander.eichner@oracle.com $ */ /** @file * DevLsiLogicSCSI - LsiLogic LSI53c1030 SCSI controller. */ @@ -1195,7 +1195,7 @@ static int lsilogicR3ProcessMessageRequest(PPDMDEVINS pDevIns, PLSILOGICSCSI pTh if (pRegion) { uint32_t offImgHdr = (LSILOGIC_FWIMGHDR_LOAD_ADDRESS - pRegion->u32AddrStart); - if (pRegion->u32AddrEnd - offImgHdr + 1 >= sizeof(FwImageHdr)) /* End address is inclusive. */ + if (pRegion->u32AddrEnd - LSILOGIC_FWIMGHDR_LOAD_ADDRESS + 1 >= sizeof(FwImageHdr)) /* End address is inclusive. */ { PFwImageHdr pFwImgHdr = (PFwImageHdr)&pRegion->au32Data[offImgHdr / 4]; From d21ea17dae598c48d5e1db311cb80152e7f15599 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Mon, 10 Aug 2026 12:09:50 +0000 Subject: [PATCH 051/176] Devices/Network/UsbNet.cpp: Additional locking for certain operations, bugref:11143 svn:sync-xref-src-repo-rev: r174759 --- src/VBox/Devices/Network/UsbNet.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/VBox/Devices/Network/UsbNet.cpp b/src/VBox/Devices/Network/UsbNet.cpp index 45bf2e91e217..172b56ead345 100644 --- a/src/VBox/Devices/Network/UsbNet.cpp +++ b/src/VBox/Devices/Network/UsbNet.cpp @@ -1,4 +1,4 @@ -/* $Id: UsbNet.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: UsbNet.cpp 114919 2026-08-10 12:09:50Z alexander.eichner@oracle.com $ */ /** @file * UsbNet - USB NCM Ethernet Device Emulation. */ @@ -2081,6 +2081,7 @@ static DECLCALLBACK(int) usbNetUsbSetInterface(PPDMUSBINS pUsbIns, uint8_t bInte PUSBNET pThis = PDMINS_2_DATA(pUsbIns, PUSBNET); LogFlowFunc(("/#%u/ bInterfaceNumber=%u bAlternateSetting=%u\n", pUsbIns->iInstance, bInterfaceNumber, bAlternateSetting)); Assert(bAlternateSetting == 0 || bAlternateSetting == 1); + RTCritSectEnter(&pThis->CritSect); if (pThis->bAlternateSetting != bAlternateSetting) { if (bAlternateSetting == 0) @@ -2097,6 +2098,7 @@ static DECLCALLBACK(int) usbNetUsbSetInterface(PPDMUSBINS pUsbIns, uint8_t bInte } pThis->bAlternateSetting = bAlternateSetting; } + RTCritSectLeave(&pThis->CritSect); return VINF_SUCCESS; } @@ -2224,8 +2226,10 @@ static DECLCALLBACK(void) usbNetVMReset(PPDMUSBINS pUsbIns) PUSBNET pThis = PDMINS_2_DATA(pUsbIns, PUSBNET); LogFlowFunc(("/#%u/\n", pUsbIns->iInstance)); + RTCritSectEnter(&pThis->CritSect); int rc = usbNetResetWorker(pThis, NULL, false /*fSetConfig*/); AssertRC(rc); + RTCritSectLeave(&pThis->CritSect); } From 3ab6c9a9bcad71ae08fa61479e60a140413690f1 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Mon, 10 Aug 2026 12:13:36 +0000 Subject: [PATCH 052/176] Devices/Storage/DevNVMe.cpp: Additional cleanup when deallocating an I/O submission/cleanup queue, bugref:11144 svn:sync-xref-src-repo-rev: r174761 --- src/VBox/Devices/Storage/DevNVMe.cpp | 33 ++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/VBox/Devices/Storage/DevNVMe.cpp b/src/VBox/Devices/Storage/DevNVMe.cpp index 0f1f0cb2f478..b1446eb4ed76 100644 --- a/src/VBox/Devices/Storage/DevNVMe.cpp +++ b/src/VBox/Devices/Storage/DevNVMe.cpp @@ -1,4 +1,4 @@ -/* $Id: DevNVMe.cpp 114273 2026-06-09 06:54:50Z alexander.eichner@oracle.com $ */ +/* $Id: DevNVMe.cpp 114921 2026-08-10 12:13:36Z alexander.eichner@oracle.com $ */ /** @file * DevNVMe - Non Volatile Memory express (previous name: NVMHCI) */ @@ -4345,7 +4345,21 @@ static int nvmeR3CmdAdminProcessCqDelete(PPDMDEVINS pDevIns, PNVME pThis, PNVMEC uintptr_t idxIoQueueComp = pIoQueueComp - &pThis->aQueuesComp[0]; Assert(idxIoQueueComp < RT_ELEMENTS(pThis->aQueuesComp)); - int rc = RTSemFastMutexDestroy(pThisCC->aQueuesComp[idxIoQueueComp].hMtx); + + PNVMEQUEUECOMPR3 pNvmeQueueR3 = &pThisCC->aQueuesComp[idxIoQueueComp]; + RTSemFastMutexRequest(pNvmeQueueR3->hMtx); + /* Destroy all waiters. */ + PNVMECOMPQUEUEWAITER pWaiter; + PNVMECOMPQUEUEWAITER pWaiterNext; + RTListForEachSafe(&pNvmeQueueR3->LstCompletionsWaiting, pWaiter, pWaiterNext, NVMECOMPQUEUEWAITER, NdLstQueue) + { + RTListNodeRemove(&pWaiter->NdLstQueue); + RTMemFree(pWaiter); + } + Assert(RTListIsEmpty(&pNvmeQueueR3->LstCompletionsWaiting)); + RTSemFastMutexRelease(pNvmeQueueR3->hMtx); + + int rc = RTSemFastMutexDestroy(pNvmeQueueR3->hMtx); AssertRC(rc); pIoQueueComp->Hdr.u16Id = 0; @@ -5107,6 +5121,15 @@ static void nvmeR3IoReqComplete(PPDMDEVINS pDevIns, PNVME pThis, PNVMECC pThisCC uint32_t cActivities = ASMAtomicDecU32(&pThis->cActivities); ASMAtomicDecU32(&pQueueSubm->cReqsActive); + /* Deallocate queue if it was deferred previously. */ + if ( ASMAtomicReadU32(&pQueueSubm->cReqsActive) == 0 + && ASMAtomicUoReadU32((volatile uint32_t *)&pQueueSubm->Hdr.enmState) == NVMEQUEUESTATE_DEALLOCATING) + { + /* The original submission queue for the deallocation request must be the admin queue. */ + PNVMEQUEUESUBM pAdmQueueSubm = &pThis->aQueuesSubm[NVME_ADM_QUEUE_ID]; + nvmeR3QueueSubmDeallocateDeferred(pDevIns, pThis, pThisCC, pAdmQueueSubm, pQueueSubm); + } + if (RT_SUCCESS(rcReq)) rc = nvmeR3CmdCompleteWithSuccess(pDevIns, pThis, pThisCC, pQueueSubm, u16Cid, 0); else if ( rcReq == VERR_PDM_MEDIAEX_IOBUF_OVERFLOW @@ -6859,7 +6882,8 @@ static void nvmeR3SuspendOrPowerOff(PPDMDEVINS pDevIns) * 0 so skip the decrement to avoid any hangs. */ if ( ( pThis->enmState == NVMESTATE_READY - || pThis->enmState == NVMESTATE_PAUSED) + || pThis->enmState == NVMESTATE_PAUSED + || pThis->enmState == NVMESTATE_FAULT) && ASMAtomicReadU32(&pThis->cActivities) > 0) ASMAtomicDecU32(&pThis->cActivities); @@ -7023,7 +7047,8 @@ static DECLCALLBACK(void) nvmeR3Reset(PPDMDEVINS pDevIns) LogFlow(("nvmeR3Reset:\n")); if ( ( pThis->enmState == NVMESTATE_READY - || pThis->enmState == NVMESTATE_PAUSED)) + || pThis->enmState == NVMESTATE_PAUSED + || pThis->enmState == NVMESTATE_FAULT)) ASMAtomicDecU32(&pThis->cActivities); ASMAtomicWriteBool(&pThisCC->fSignalIdle, true); From 0d5e9e84ac8e47e13ff1111f4db4caa1ad79b2d5 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Mon, 10 Aug 2026 12:18:33 +0000 Subject: [PATCH 053/176] Devices/Graphics: ignore incorrect pitch. bugref:11128 svn:sync-xref-src-repo-rev: r174764 --- src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp | 14 +++++----- src/VBox/Devices/Graphics/DevVGA-SVGA.cpp | 24 ++++++++++++----- src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp | 26 +++++++++++++------ src/VBox/Devices/Graphics/DevVGA-SVGA3d.h | 6 ++--- 4 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp index e63c96ed1c77..c10719be420f 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA-cmd.cpp 114811 2026-07-28 14:08:48Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA-cmd.cpp 114924 2026-08-10 12:18:33Z vitali.pelenjow@oracle.com $ */ /** @file * VMware SVGA device - implementation of VMSVGA commands. */ @@ -2624,7 +2624,7 @@ static void vmsvga3dCmdDestroyGBScreenTarget(PVGASTATE pThis, PVGASTATECC pThisC /* SVGA_3D_CMD_BIND_GB_SCREENTARGET 1126 */ -static void vmsvga3dCmdBindGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdBindGBScreenTarget const *pCmd) +static void vmsvga3dCmdBindGBScreenTarget(PVGASTATE pThis, PVGASTATECC pThisCC, SVGA3dCmdBindGBScreenTarget const *pCmd) { //DEBUG_BREAKPOINT_TEST(); PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; @@ -2673,7 +2673,7 @@ static void vmsvga3dCmdBindGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdBindGBSc rect.y = 0; rect.w = entry.width; rect.h = entry.height; - vmsvga3dScreenUpdateFromScreenTarget(pThisCC, pScreen, rect, entry.image); + vmsvga3dScreenUpdateFromScreenTarget(pThis, pThisCC, pScreen, rect, entry.image); } } } @@ -2682,7 +2682,7 @@ static void vmsvga3dCmdBindGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdBindGBSc /* SVGA_3D_CMD_UPDATE_GB_SCREENTARGET 1127 */ -static void vmsvga3dCmdUpdateGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdUpdateGBScreenTarget const *pCmd) +static void vmsvga3dCmdUpdateGBScreenTarget(PVGASTATE pThis, PVGASTATECC pThisCC, SVGA3dCmdUpdateGBScreenTarget const *pCmd) { //DEBUG_BREAKPOINT_TEST(); PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; @@ -2713,7 +2713,7 @@ static void vmsvga3dCmdUpdateGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdUpdate RT_UNTRUSTED_VALIDATED_FENCE(); VMSVGASCREENOBJECT *pScreen = &pSvgaR3State->aScreens[pCmd->stid]; - vmsvga3dScreenUpdateFromScreenTarget(pThisCC, pScreen, pCmd->rect, entryScreenTarget.image); + vmsvga3dScreenUpdateFromScreenTarget(pThis, pThisCC, pScreen, pCmd->rect, entryScreenTarget.image); } } } @@ -6348,7 +6348,7 @@ int vmsvgaR3Process3dCmd(PVGASTATE pThis, PVGASTATECC pThisCC, uint32_t idDXCont { SVGA3dCmdBindGBScreenTarget *pCmd = (SVGA3dCmdBindGBScreenTarget *)pvCmd; VMSVGAFIFO_CHECK_3D_CMD_MIN_SIZE_BREAK(sizeof(*pCmd)); - vmsvga3dCmdBindGBScreenTarget(pThisCC, pCmd); + vmsvga3dCmdBindGBScreenTarget(pThis, pThisCC, pCmd); break; } @@ -6356,7 +6356,7 @@ int vmsvgaR3Process3dCmd(PVGASTATE pThis, PVGASTATECC pThisCC, uint32_t idDXCont { SVGA3dCmdUpdateGBScreenTarget *pCmd = (SVGA3dCmdUpdateGBScreenTarget *)pvCmd; VMSVGAFIFO_CHECK_3D_CMD_MIN_SIZE_BREAK(sizeof(*pCmd)); - vmsvga3dCmdUpdateGBScreenTarget(pThisCC, pCmd); + vmsvga3dCmdUpdateGBScreenTarget(pThis, pThisCC, pCmd); break; } diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp index a3d3d752b65a..d4fc93c680e5 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA.cpp 114796 2026-07-27 16:22:58Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA.cpp 114924 2026-08-10 12:18:33Z vitali.pelenjow@oracle.com $ */ /** @file * VMware SVGA device. * @@ -1689,9 +1689,9 @@ int vmsvgaR3ChangeMode(PVGASTATE pThis, PVGASTATECC pThisCC) VMSVGASCREENOBJECT *pScreen = &pSVGAState->aScreens[0]; Assert(pScreen->idScreen == 0); - if ( pScreen->cWidth == VMSVGA_VAL_UNINITIALIZED - || pScreen->cHeight == VMSVGA_VAL_UNINITIALIZED - || pScreen->cBpp == VMSVGA_VAL_UNINITIALIZED) + if ( pThis->svga.uWidth == VMSVGA_VAL_UNINITIALIZED + || pThis->svga.uHeight == VMSVGA_VAL_UNINITIALIZED + || pThis->svga.uBpp == VMSVGA_VAL_UNINITIALIZED) { /* Do not apply the change if the guest has not finished updating registers. * This is necessary in order to make a full mode change. @@ -1699,13 +1699,22 @@ int vmsvgaR3ChangeMode(PVGASTATE pThis, PVGASTATECC pThisCC) return VINF_SUCCESS; } + ASSERT_GUEST_RETURN(pThis->svga.uWidth > 0 && pThis->svga.uWidth <= pThis->svga.u32MaxWidth, VERR_INVALID_STATE); + ASSERT_GUEST_RETURN(pThis->svga.uHeight > 0 && pThis->svga.uHeight <= pThis->svga.u32MaxHeight, VERR_INVALID_STATE); + + /* Height can't exceed the available VRAM. */ + uint32_t const cbPitch = pThis->svga.cbScanline + ? pThis->svga.cbScanline + : (uint32_t)pThis->svga.uWidth * (RT_ALIGN(pThis->svga.uBpp, 8) / 8); + ASSERT_GUEST_RETURN(pThis->svga.uHeight <= pThis->vram_size / cbPitch, VERR_INVALID_STATE); + pScreen->fDefined = true; pScreen->fModified = true; pScreen->fuScreen = SVGA_SCREEN_MUST_BE_SET | SVGA_SCREEN_IS_PRIMARY; pScreen->xOrigin = 0; pScreen->yOrigin = 0; pScreen->offVRAM = 0; - pScreen->cbPitch = pThis->svga.cbScanline; + pScreen->cbPitch = cbPitch; pScreen->cWidth = pThis->svga.uWidth; pScreen->cHeight = pThis->svga.uHeight; pScreen->cBpp = pThis->svga.uBpp; @@ -7273,13 +7282,16 @@ static int vmsvgaR3LoadExecFifo(PCPDMDEVHLPR3 pHlp, PVGASTATE pThis, PVGASTATECC /* Try to setup at least the first screen. */ VMSVGASCREENOBJECT *pScreen = &pSVGAState->aScreens[0]; Assert(pScreen->idScreen == 0); + uint32_t const cbPitch = pThis->svga.cbScanline + ? pThis->svga.cbScanline + : (uint32_t)pThis->svga.uWidth * (RT_ALIGN(pThis->svga.uBpp, 8) / 8); pScreen->fDefined = true; pScreen->fModified = true; pScreen->fuScreen = SVGA_SCREEN_MUST_BE_SET | SVGA_SCREEN_IS_PRIMARY; pScreen->xOrigin = 0; pScreen->yOrigin = 0; pScreen->offVRAM = pThis->svga.uScreenOffset; - pScreen->cbPitch = pThis->svga.cbScanline; + pScreen->cbPitch = cbPitch; pScreen->cWidth = pThis->svga.uWidth; pScreen->cHeight = pThis->svga.uHeight; pScreen->cBpp = pThis->svga.uBpp; diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp index 52eb0514e045..878e0a8065e2 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d.cpp 114812 2026-07-28 14:10:11Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d.cpp 114924 2026-08-10 12:18:33Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - Common core code. */ @@ -1073,7 +1073,7 @@ int vmsvga3dSurfaceBlitToScreen(PVGASTATE pThis, PVGASTATECC pThisCC, uint32_t i PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; if (pSvgaR3State->pFuncsMap) - return vmsvga3dScreenUpdateFromSurface(pThisCC, pScreen, destRect, src, srcRect, cRects, pRect); + return vmsvga3dScreenUpdateFromSurface(pThis, pThisCC, pScreen, destRect, src, srcRect, cRects, pRect); /* Screens which are associated with screen targets should be handled by vmsvga3dScreenUpdateFromSurface * because the code below updates the guest VRAM and screen targets have a separate memory buffer for the @@ -1152,7 +1152,7 @@ int vmsvga3dSurfaceBlitToScreen(PVGASTATE pThis, PVGASTATECC pThisCC, uint32_t i } -static int vmsvga3dScreenUpdate(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGASignedRect const &dstRect, +static int vmsvga3dScreenUpdate(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGASignedRect const &dstRect, SVGA3dSurfaceImageId const &srcImage, SVGASignedRect const &srcRect, uint32_t cDstClipRects, SVGASignedRect *paDstClipRect) { @@ -1196,6 +1196,9 @@ static int vmsvga3dScreenUpdate(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen if ( dstRect.right <= dstRect.left || dstRect.bottom <= dstRect.top) return VINF_SUCCESS; /* Empty dst rect. */ + + if (pScreen->offVRAM != VMSVGA_VRAM_OFFSET_SCREEN_TARGET) + ASSERT_GUEST_RETURN(pScreen->offVRAM < pThis->vram_size, VERR_INVALID_PARAMETER); /* paranoia, ensured elsewhere. */ RT_UNTRUSTED_VALIDATED_FENCE(); ASSERT_GUEST_RETURN( srcRect.right - srcRect.left == dstRect.right - dstRect.left @@ -1257,12 +1260,19 @@ static int vmsvga3dScreenUpdate(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen uint8_t const *pu8Src = (uint8_t *)srcMap.pvData; - uint32_t const cbDst = pScreen->cHeight * pScreen->cbPitch; + uint32_t cbDst = pScreen->cHeight * pScreen->cbPitch; uint8_t *pu8Dst; if (pScreen->offVRAM == VMSVGA_VRAM_OFFSET_SCREEN_TARGET) + { + cbDst = RT_MIN(cbDst, pScreen->pScreenOutputTarget->desc.cbOutputBuffer); pu8Dst = (uint8_t *)pScreen->pScreenOutputTarget->desc.pvOutputBuffer; + } else + { + uint32_t const cbVRAM = pThis->vram_size - pScreen->offVRAM; + cbDst = RT_MIN(cbDst, cbVRAM); /* paranoia: pScreen->cHeight and pScreen->cbPitch has been verified. */ pu8Dst = (uint8_t *)pThisCC->pbVRam + pScreen->offVRAM; + } SVGASignedRect dstClipRect; if (cDstClipRects == 0) @@ -1348,7 +1358,7 @@ static int vmsvga3dScreenUpdate(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen } -int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGA3dRect const &rect, +int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGA3dRect const &rect, SVGA3dSurfaceImageId const &srcImage) { PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; @@ -1374,11 +1384,11 @@ int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATECC pThisCC, VMSVGASCREENOBJECT r.right = rect.x + rect.w; r.bottom = rect.y + rect.h; - return vmsvga3dScreenUpdate(pThisCC, pScreen, r, srcImage, r, 0, NULL); + return vmsvga3dScreenUpdate(pThis, pThisCC, pScreen, r, srcImage, r, 0, NULL); } -int vmsvga3dScreenUpdateFromSurface(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGASignedRect const &dstRect, +int vmsvga3dScreenUpdateFromSurface(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGASignedRect const &dstRect, SVGA3dSurfaceImageId const &srcImage, SVGASignedRect const &srcRect, uint32_t cDstClipRects, SVGASignedRect *paDstClipRect) { @@ -1400,7 +1410,7 @@ int vmsvga3dScreenUpdateFromSurface(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pSc } } - return vmsvga3dScreenUpdate(pThisCC, pScreen, dstRect, srcImage, srcRect, cDstClipRects, paDstClipRect); + return vmsvga3dScreenUpdate(pThis, pThisCC, pScreen, dstRect, srcImage, srcRect, cDstClipRects, paDstClipRect); } diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h index 214e91e5ac82..4c25ccc55a97 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d.h 114460 2026-06-19 14:01:13Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d.h 114924 2026-08-10 12:18:33Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device - 3D part. */ @@ -144,10 +144,10 @@ int vmsvga3dChangeMode(PVGASTATECC pThisCC); int vmsvga3dDefineScreen(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen); int vmsvga3dDestroyScreen(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen); -int vmsvga3dScreenUpdateFromSurface(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pDstScreen, SVGASignedRect const &dstRect, +int vmsvga3dScreenUpdateFromSurface(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pDstScreen, SVGASignedRect const &dstRect, SVGA3dSurfaceImageId const &srcImage, SVGASignedRect const &srcRect, uint32_t cDstClipRects, SVGASignedRect *paDstClipRect); -int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pDstScreen, SVGA3dRect const &rect, +int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pDstScreen, SVGA3dRect const &rect, SVGA3dSurfaceImageId const &srcImage); void vmsvga3dProcessPendingTasks(PVGASTATE pThis, PVGASTATECC pThisCC); From 3694a379f79888846e0a0573a6ac0b973086ee64 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Mon, 10 Aug 2026 12:20:59 +0000 Subject: [PATCH 054/176] Devices/USB/DevEHCI.cpp: Don't allow resetting the host controller when it isn't halted, bugref:11136 svn:sync-xref-src-repo-rev: r174765 --- src/VBox/Devices/USB/DevEHCI.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/VBox/Devices/USB/DevEHCI.cpp b/src/VBox/Devices/USB/DevEHCI.cpp index 4ee1a96bd28d..1289d8c1b565 100644 --- a/src/VBox/Devices/USB/DevEHCI.cpp +++ b/src/VBox/Devices/USB/DevEHCI.cpp @@ -1,4 +1,4 @@ -/* $Id: DevEHCI.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DevEHCI.cpp 114925 2026-08-10 12:20:59Z alexander.eichner@oracle.com $ */ /** @file * DevEHCI - Enhanced Host Controller Interface for USB. */ @@ -1270,6 +1270,12 @@ static void ehciR3DoReset(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, uint LogFunc(("%s reset%s\n", fNewMode == EHCI_USB_RESET ? "hardware" : "software", fResetOnLinux ? " (reset on linux)" : "")); + if (!(pThis->intr_status & EHCI_STATUS_HCHALTED)) + { + LogRel(("EHCI: Ignoring reset while not halted!\n")); + return; + } + /* * Cancel all outstanding URBs. * @@ -5054,6 +5060,7 @@ static DECLCALLBACK(int) ehciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFG /* * Do a hardware reset. */ + pThis->intr_status = EHCI_STATUS_HCHALTED; ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, false /* don't reset devices */); #ifdef VBOX_WITH_STATISTICS From 07fc467d2d679ea755b6d3701c4399d0d9625bff Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Mon, 10 Aug 2026 12:24:46 +0000 Subject: [PATCH 055/176] Devices/USB/VUSBUrb.cpp: vusbMsgSetup() might have reallocated the complete struct, bugref:11135 svn:sync-xref-src-repo-rev: r174768 --- src/VBox/Devices/USB/VUSBUrb.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VBox/Devices/USB/VUSBUrb.cpp b/src/VBox/Devices/USB/VUSBUrb.cpp index 287d5095ad44..4f4dbe2896d5 100644 --- a/src/VBox/Devices/USB/VUSBUrb.cpp +++ b/src/VBox/Devices/USB/VUSBUrb.cpp @@ -1,4 +1,4 @@ -/* $Id: VUSBUrb.cpp 114696 2026-07-14 11:50:33Z michal.necasek@oracle.com $ */ +/* $Id: VUSBUrb.cpp 114928 2026-08-10 12:24:46Z alexander.eichner@oracle.com $ */ /** @file * Virtual USB - URBs. */ @@ -934,7 +934,8 @@ static int vusbUrbSubmitCtrl(PVUSBURB pUrb) break; } - /* vusbMsgSetup() may have reallocated pMsg */ + /* vusbMsgSetup() may have reallocated just pMsg or the entire extra struct. */ + pExtra = pPipe->pCtrl; pSetup = pExtra->pMsg; /* pre-buffer our output if it's device-to-host */ From 27509dce707fb7e4d7bc99e45d08214c1a97a481 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Mon, 10 Aug 2026 12:38:24 +0000 Subject: [PATCH 056/176] Devices/Graphics: disable obsolete commands and check resolution. bugref:11132 svn:sync-xref-src-repo-rev: r174772 --- src/VBox/Devices/Graphics/DevVGA.cpp | 18 ++++++++++++++---- src/VBox/Devices/Graphics/DevVGA_VBVA.cpp | 18 +++++++++++++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA.cpp b/src/VBox/Devices/Graphics/DevVGA.cpp index 913c84d4bec1..36fdfc8ef73a 100644 --- a/src/VBox/Devices/Graphics/DevVGA.cpp +++ b/src/VBox/Devices/Graphics/DevVGA.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA.cpp 114719 2026-07-16 15:46:04Z aleksey.ilyushin@oracle.com $ */ +/* $Id: DevVGA.cpp 114932 2026-08-10 12:38:24Z vitali.pelenjow@oracle.com $ */ /** @file * DevVGA - VBox VGA/VESA device. */ @@ -2868,6 +2868,13 @@ static int vgaR3DrawBlank(PVGASTATE pThis, PVGASTATER3 pThisCC, bool full_update d = pDrv->pbData; if (pThis->fRenderVRAM) { + /* If VRAM rendering is enabled, then check that the provided target memory buffer has a correct size. */ + if ( pDrv->cx != pThis->last_scr_width + || pDrv->cy != pThis->last_scr_height) + { + return VINF_SUCCESS; + } + for(i = 0; i < (int)pThis->last_scr_height; i++) { memset(d, val, w); d += cbScanline; @@ -5091,10 +5098,13 @@ static DECLCALLBACK(int) vgaR3PortUpdateDisplay(PPDMIDISPLAYPORT pInterface) # ifndef VBOX_WITH_HGSMI /* This should be called only in non VBVA mode. */ # else - if (VBVAUpdateDisplay(pThis, pThisCC) == VINF_SUCCESS) + if (!pThis->fVMSVGAEnabled) { - PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); - return VINF_SUCCESS; + if (VBVAUpdateDisplay(pThis, pThisCC) == VINF_SUCCESS) + { + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + return VINF_SUCCESS; + } } # endif /* VBOX_WITH_HGSMI */ diff --git a/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp b/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp index 87d5a81b9568..d62d9ce2f800 100644 --- a/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp +++ b/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA_VBVA.cpp 114893 2026-08-07 16:18:28Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA_VBVA.cpp 114932 2026-08-10 12:38:24Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox Video Acceleration (VBVA). */ @@ -1551,6 +1551,22 @@ static DECLCALLBACK(int) vbvaChannelHandler(void *pvHandler, uint16_t u16Channel PHGSMIINSTANCE pIns = pThisCC->pHGSMI; VBVACONTEXT *pCtx = (VBVACONTEXT *)HGSMIContext(pIns); + if (pThis->fVMSVGAEnabled) + { + if ( u16ChannelInfo == VBVA_QUERY_CONF32 + || u16ChannelInfo == VBVA_INFO_HEAP + || u16ChannelInfo == VBVA_MOUSE_POINTER_SHAPE + || u16ChannelInfo == VBVA_INFO_CAPS + || u16ChannelInfo == VBVA_CURSOR_POSITION + ) + { /* allowed */ } + else + { + /* NOPs for VMSVGA mode. */ + return VINF_SUCCESS; + } + } + switch (u16ChannelInfo) { #ifdef VBOX_WITH_VDMA From 2add046e9cde123d81d909852311ef538fb509a7 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Mon, 10 Aug 2026 12:46:46 +0000 Subject: [PATCH 057/176] VRDP: cancel pending requests is client re-announces smartcard device. bugref:11134 svn:sync-xref-src-repo-rev: r174775 --- src/VBox/Main/src-client/UsbCardReader.cpp | 12 ++++-- src/VBox/RDP/server/rdpdr.cpp | 47 +++++++++++++++++++++- src/VBox/RDP/server/vrdp.h | 3 +- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/VBox/Main/src-client/UsbCardReader.cpp b/src/VBox/Main/src-client/UsbCardReader.cpp index ca0d6911c3f1..7e9feb9a2e03 100644 --- a/src/VBox/Main/src-client/UsbCardReader.cpp +++ b/src/VBox/Main/src-client/UsbCardReader.cpp @@ -1,4 +1,4 @@ -/* $Id: UsbCardReader.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: UsbCardReader.cpp 114935 2026-08-10 12:46:46Z vitali.pelenjow@oracle.com $ */ /** @file * UsbCardReader - Driver Interface to USB Smart Card Reader emulation. */ @@ -708,10 +708,14 @@ int UsbCardReader::VRDENotify(uint32_t u32Id, void *pvData, uint32_t cbData) case VRDE_SCARD_NOTIFY_DETACH: { - VRDESCARDNOTIFYDETACH *p = (VRDESCARDNOTIFYDETACH *)pvData; NOREF(p); - Assert(cbData == sizeof(VRDESCARDNOTIFYDETACH)); + AssertBreakStmt(cbData == sizeof(VRDESCARDNOTIFYDETACH), vrc = VERR_INVALID_PARAMETER); + VRDESCARDNOTIFYDETACH *p = (VRDESCARDNOTIFYDETACH *)pvData; - /** @todo Just free. There should be no pending requests, because VRDP cancels them. */ + AssertBreakStmt(m_pRemote, vrc = VERR_INVALID_STATE); + AssertBreakStmt(p->u32ClientId == m_pRemote->u32ClientId, vrc = VERR_INVALID_PARAMETER); + AssertBreakStmt(p->u32DeviceId == m_pRemote->u32DeviceId, vrc = VERR_INVALID_PARAMETER); + + /* Just free. There are no pending requests, because VRDP cancels them. */ RTMemFree(m_pRemote); m_pRemote = NULL; } break; diff --git a/src/VBox/RDP/server/rdpdr.cpp b/src/VBox/RDP/server/rdpdr.cpp index 1e09cbea3cc0..c7c2adc31e66 100644 --- a/src/VBox/RDP/server/rdpdr.cpp +++ b/src/VBox/RDP/server/rdpdr.cpp @@ -1,4 +1,4 @@ -/* $Id: rdpdr.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: rdpdr.cpp 114935 2026-08-10 12:46:46Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol - "RDPDR" static virtual channel: File System Virtual Channel Extension. */ @@ -692,6 +692,45 @@ int VRDPChannelRDPDR::fetchIO(uint32_t u32CompletionId, return rc; } +/** @thread INPUT */ +void VRDPChannelRDPDR::cancelIOForDevice(uint32_t u32DeviceId) +{ + RTLISTANCHOR ListPendingIO; + RTListInit(&ListPendingIO); + + RDPDRIOCompletion *pIter; + RDPDRIOCompletion *pNext; + + bool fLocked = RT_SUCCESS(VRDPLock::Lock(m_pLock)); + + RTListForEachSafe(&m_IOCompletion.ListUsed, pIter, pNext, RDPDRIOCompletion, NodeIOCompletion) + { + if (pIter->u32DeviceId == u32DeviceId) + { + RTListNodeRemove(&pIter->NodeIOCompletion); + RTListAppend(&ListPendingIO, &pIter->NodeIOCompletion); + } + } + + if (fLocked) + { + VRDPLock::Unlock(m_pLock); + } + + RTListForEachSafe(&ListPendingIO, pIter, pNext, RDPDRIOCompletion, NodeIOCompletion) + { + RDPDRIOCTL *pIO = (RDPDRIOCTL *)pIter->pPktHdr; + + RDPDRLOG(("cancelIOForDevice: pending IO %p [%d,%d]\n", + pIO, m_pvrdptp->Client()->Id(), u32DeviceId)); + + rdpdrDispatchIOCompletion(pIO, u32DeviceId, RDPDR_STATUS_UNSUCCESSFUL); + RDPDRPktRelease(&pIO->hdr); + + VRDPMemFree(pIter); + } +} + /** @thread OUTPUT */ int VRDPChannelRDPDR::ProcessOutput (const void *pvData, uint32_t cbData) { @@ -1192,6 +1231,12 @@ int VRDPChannelRDPDR::rdpdrOnDeviceAdd(const DEVICE_ANNOUNCE *pDevHdr, #ifdef DEBUG_sunlover Assert(!m_smartcard.fEnabled); #endif + if (m_smartcard.fEnabled) + { + /* Cancel pending IOCTLs for the existing device */ + cancelIOForDevice(m_smartcard.u32DeviceId); + } + m_smartcard.fEnabled = true; m_smartcard.u32DeviceId = pDevHdr->u32DeviceId; m_pvrdptp->Client()->Server()->SCard()->SCardAttach(m_pvrdptp->Client()->Id(), m_smartcard.u32DeviceId); diff --git a/src/VBox/RDP/server/vrdp.h b/src/VBox/RDP/server/vrdp.h index 8b88554446f2..5dad7e8abb57 100644 --- a/src/VBox/RDP/server/vrdp.h +++ b/src/VBox/RDP/server/vrdp.h @@ -1,4 +1,4 @@ -/* $Id: vrdp.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: vrdp.h 114935 2026-08-10 12:46:46Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -658,6 +658,7 @@ class VRDPChannelRDPDR: public VRDPChannel int createIO(uint32_t *pu32CompletionId, RDPDRPKTHDR *pHdr, uint32_t u32MajorFunction, uint32_t u32DeviceId); int fetchIO(uint32_t u32CompletionId, RDPDRPKTHDR **ppHdr, uint32_t *pu32MajorFunction, uint32_t *pu32DeviceId); + void cancelIOForDevice(uint32_t u32DeviceId); int rdpdrSendServerCoreCapability(void); int rdpdrSendServerClientIdConfirm(void); From 456eac4ea8d6ca35d9149cdb43a24f1a3182b266 Mon Sep 17 00:00:00 2001 From: Serkan Bayraktar Date: Mon, 10 Aug 2026 12:48:12 +0000 Subject: [PATCH 058/176] API: bugref:11137 Do not allow shared folder settings to be imported. svn:sync-xref-src-repo-rev: r174776 --- include/VBox/settings.h | 5 +++++ src/VBox/Main/src-server/ApplianceImplImport.cpp | 13 ++++++++++--- src/VBox/Main/xml/Settings.cpp | 7 ++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/include/VBox/settings.h b/include/VBox/settings.h index fe96a2fe5f3f..0fd124aa7dca 100644 --- a/include/VBox/settings.h +++ b/include/VBox/settings.h @@ -1585,7 +1585,12 @@ class MachineConfigFile : public ConfigFileBase void buildMachineXML(xml::ElementNode &elmMachine, uint32_t fl, std::list *pllElementsWithUuidAttributes); + /** + * sanitizeXXXX functions are possibly called during apliance import to make sure + * security wise risky settings are cleaned up. + */ void sanitizeImportedSerialPorts(); + void sanitizeSharedFolderSettings(); static bool isAudioDriverAllowedOnThisHost(AudioDriverType_T enmDrvType); static AudioDriverType_T getHostDefaultAudioDriver(); diff --git a/src/VBox/Main/src-server/ApplianceImplImport.cpp b/src/VBox/Main/src-server/ApplianceImplImport.cpp index 1e4f063ab377..a6ddb89c0bba 100644 --- a/src/VBox/Main/src-server/ApplianceImplImport.cpp +++ b/src/VBox/Main/src-server/ApplianceImplImport.cpp @@ -1,4 +1,4 @@ -/* $Id: ApplianceImplImport.cpp 114705 2026-07-14 13:35:15Z alexander.eichner@oracle.com $ */ +/* $Id: ApplianceImplImport.cpp 114936 2026-08-10 12:48:12Z serkan.bayraktar@oracle.com $ */ /** @file * IAppliance and IVirtualSystem COM class implementations. */ @@ -381,9 +381,9 @@ HRESULT Appliance::interpret() /* The NVRAM file does not have a entry so we only need to check the OVF details. */ if (vsysThis.strNvramPath.isNotEmpty()) pNewDesc->i_addEntry(VirtualSystemDescriptionType_NVRAM, "", vsysThis.strNvramPath, vsysThis.strNvramPath); - /* Check if any of the serial ports is configured with mode raw file. */ if (vsysThis.pelmVBoxMachine) { + /* Check if any of the serial ports is configured with mode raw file. */ settings::SerialPortsList const &llSerialPorts = pNewDesc->m->pConfig->hardwareMachine.llSerialPorts; for (settings::SerialPortsList::const_iterator port_it = llSerialPorts.begin(); @@ -397,6 +397,13 @@ HRESULT Appliance::interpret() break; } } + /* Check if shared folders are configured. */ + + if (!pNewDesc->m->pConfig->hardwareMachine.llSharedFolders.empty()) + { + i_addWarning(tr("Virtual appliance \"%s\" was configured with machine shared folder(s) " + "This setting will not be imported."), vsysThis.strName.c_str()); + } } /* Audio */ Utf8Str strSoundCard; @@ -5598,7 +5605,6 @@ void Appliance::i_importVBoxMachine(ComObjPtr &vsdescT strSrcFilePath.append(RTPATH_SLASH_STR); strSrcFilePath.append(stack.strNvramPath); } - /* The basename of the destination filename needs to be the VM's name in * order to match the VM's INvramStore::nonVolatileStorageFile attribute so * that EFI can find it when booting the VM. */ @@ -6134,6 +6140,7 @@ void Appliance::i_importVBoxMachine(ComObjPtr &vsdescT hrc = pNewMachine.createObject(); if (FAILED(hrc)) throw hrc; config.sanitizeImportedSerialPorts(); + config.sanitizeSharedFolderSettings(); // this magic constructor fills the new machine object with the MachineConfig // instance that we created from the vbox:Machine diff --git a/src/VBox/Main/xml/Settings.cpp b/src/VBox/Main/xml/Settings.cpp index 137785135a35..67370cc80885 100644 --- a/src/VBox/Main/xml/Settings.cpp +++ b/src/VBox/Main/xml/Settings.cpp @@ -1,4 +1,4 @@ -/* $Id: Settings.cpp 114800 2026-07-27 16:53:06Z andreas.loeffler@oracle.com $ */ +/* $Id: Settings.cpp 114936 2026-08-10 12:48:12Z serkan.bayraktar@oracle.com $ */ /** @file * Settings File Manipulation API. * @@ -9710,6 +9710,11 @@ void MachineConfigFile::sanitizeImportedSerialPorts() } } +void MachineConfigFile::sanitizeSharedFolderSettings() +{ + hardwareMachine.llSharedFolders.clear(); +} + /** * Called from write() before calling ConfigFileBase::createStubDocument(). * This adjusts the settings version in m->sv if incompatible settings require From d226cb30a1ede20a908f4a399b4e5cff7b73ede9 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Mon, 10 Aug 2026 12:54:06 +0000 Subject: [PATCH 059/176] Devices/Graphics: report error if command buffer context is not started. bugref:11146 svn:sync-xref-src-repo-rev: r174779 --- src/VBox/Devices/Graphics/DevVGA-SVGA.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp index d4fc93c680e5..8fd9da2fba86 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA.cpp 114924 2026-08-10 12:18:33Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA.cpp 114939 2026-08-10 12:54:06Z vitali.pelenjow@oracle.com $ */ /** @file * VMware SVGA device. * @@ -3613,13 +3613,15 @@ static SVGACBStatus vmsvgaR3CmdBufDCPreempt(PPDMDEVINS pDevIns, PVMSVGAR3STATE p RT_UNTRUSTED_VALIDATED_FENCE(); PVMSVGACMDBUFCTX const pCmdBufCtx = pSvgaR3State->apCmdBufCtxs[pCmd->context]; + if (!pCmdBufCtx) + return SVGA_CB_STATUS_COMMAND_ERROR; + RTLISTANCHOR listPreempted; + RTListInit(&listPreempted); int rc = RTCritSectEnter(&pSvgaR3State->CritSectCmdBuf); AssertRC(rc); - RTListInit(&listPreempted); - PVMSVGACMDBUF pIter, pNext; RTListForEachSafe(&pCmdBufCtx->listSubmitted, pIter, pNext, VMSVGACMDBUF, nodeBuffer) { From a832b065c020db205465efae6c655a8ed2a77956 Mon Sep 17 00:00:00 2001 From: Aleksey Ilyushin Date: Mon, 10 Aug 2026 12:59:13 +0000 Subject: [PATCH 060/176] Devices/Storage/DevVirtioSCSI.cpp: use the actual transfer size returned by SCSI request bugref:11130 svn:sync-xref-src-repo-rev: r174783 --- src/VBox/Devices/Storage/DevVirtioSCSI.cpp | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/VBox/Devices/Storage/DevVirtioSCSI.cpp b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp index 8411c76f511b..c165c0bf64f8 100644 --- a/src/VBox/Devices/Storage/DevVirtioSCSI.cpp +++ b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVirtioSCSI.cpp 114856 2026-08-05 09:09:50Z andreas.loeffler@oracle.com $ */ +/* $Id: DevVirtioSCSI.cpp 114943 2026-08-10 12:59:13Z aleksey.ilyushin@oracle.com $ */ /** @file * VBox storage devices - Virtio SCSI Driver * @@ -1110,7 +1110,7 @@ static DECLCALLBACK(int) virtioScsiR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterfac PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort); PPDMDEVINS pDevIns = pTarget->pDevIns; PVIRTIOSCSIREQ pReq = (PVIRTIOSCSIREQ)pvIoReqAlloc; - RT_NOREF(hIoReq, cbCopy); + RT_NOREF(hIoReq); if (!pReq->cbDataIn) return VINF_SUCCESS; @@ -1120,8 +1120,7 @@ static DECLCALLBACK(int) virtioScsiR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterfac PVIRTIOSGBUF pSgPhysReturn = pReq->pVirtqBuf->pSgPhysReturn; virtioCoreGCPhysChainAdvance(pSgPhysReturn, offDst); - size_t cbCopied = 0; - size_t cbRemain = pReq->cbDataIn; + size_t cbRemain = RT_MIN(cbCopy, pReq->cbDataIn); /* Skip past the REQ_RESP_HDR_T and sense code if we're at the start of the buffer. */ if (!pSgPhysReturn->idxSeg && pSgPhysReturn->cbSegLeft == pSgPhysReturn->paSegs[0].cbSeg) @@ -1129,12 +1128,17 @@ static DECLCALLBACK(int) virtioScsiR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterfac while (cbRemain) { - cbCopied = RT_MIN(pSgBuf->cbSegLeft, pSgPhysReturn->cbSegLeft); - Assert(cbCopied > 0); - PDMDevHlpPCIPhysWriteUser(pDevIns, pSgPhysReturn->GCPhysCur, pSgBuf->pvSegCur, cbCopied); - RTSgBufAdvance(pSgBuf, cbCopied); - virtioCoreGCPhysChainAdvance(pSgPhysReturn, cbCopied); - cbRemain -= cbCopied; + size_t cbThisCopy = 0; + const void *pvSrc = RTSgBufGetCurrentSegment(pSgBuf, RT_MIN(cbRemain, pSgPhysReturn->cbSegLeft), &cbThisCopy); + + Assert(cbThisCopy); + if (!cbThisCopy) + break; + + PDMDevHlpPCIPhysWriteUser(pDevIns, pSgPhysReturn->GCPhysCur, pvSrc, cbThisCopy); + RTSgBufAdvance(pSgBuf, cbThisCopy); + virtioCoreGCPhysChainAdvance(pSgPhysReturn, cbThisCopy); + cbRemain -= cbThisCopy; } RT_UNTRUSTED_NONVOLATILE_COPY_FENCE(); /* needed? */ From 19e3164a787469835c60959235c54ff5256b2c36 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Mon, 10 Aug 2026 13:06:53 +0000 Subject: [PATCH 061/176] Devices/Graphics: deallocate host shadow buffer when resource is created or invalidated, saved state update for guest-backed surfaces. bugref:10934 svn:sync-xref-src-repo-rev: r174785 --- src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp | 17 ++-- src/VBox/Devices/Graphics/DevVGA-SVGA.cpp | 19 ++++- .../Graphics/DevVGA-SVGA3d-dx-dx11.cpp | 13 +++- .../Graphics/DevVGA-SVGA3d-dx-savedstate.cpp | 78 ++++++++++++++++--- .../Devices/Graphics/DevVGA-SVGA3d-internal.h | 10 ++- .../Graphics/DevVGA-SVGA3d-savedstate.cpp | 4 +- .../Devices/Graphics/DevVGA-SVGA3d-win.cpp | 4 +- src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp | 48 +++++++++--- src/VBox/Devices/Graphics/DevVGA-SVGA3d.h | 10 ++- src/VBox/Devices/Graphics/DevVGASavedState.h | 5 +- 10 files changed, 172 insertions(+), 36 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp index c10719be420f..4f63ab9ff609 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA-cmd.cpp 114924 2026-08-10 12:18:33Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA-cmd.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * VMware SVGA device - implementation of VMSVGA commands. */ @@ -1936,7 +1936,8 @@ static void vmsvga3dCmdDefineSurface(PVGASTATECC pThisCC, SVGA3dCmdDefineSurface SVGA3dMSQualityLevel const qualityLevel = pCmd->multisampleCount > 1 ? SVGA3D_MS_QUALITY_FULL : SVGA3D_MS_QUALITY_NONE; vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, multisamplePattern, qualityLevel, pCmd->autogenFilter, - pCmd->face[0].numMipLevels, &paMipLevelSizes[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ true); + pCmd->face[0].numMipLevels, &paMipLevelSizes[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, + VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS); } @@ -2024,7 +2025,8 @@ static void vmsvga3dCmdDefineGBSurface(PVGASTATECC pThisCC, SVGA3dCmdDefineGBSur SVGA3dMSQualityLevel const qualityLevel = pCmd->multisampleCount > 1 ? SVGA3D_MS_QUALITY_FULL : SVGA3D_MS_QUALITY_NONE; vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, multisamplePattern, qualityLevel, pCmd->autogenFilter, - pCmd->numMipLevels, &pCmd->size, /* arraySize = */ 0, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ false); + pCmd->numMipLevels, &pCmd->size, /* arraySize = */ 0, /* bufferByteStride = */ 0, + VMSVGA3D_SURFACE_DEFINE_F_GB); } } @@ -2754,7 +2756,8 @@ static void vmsvga3dCmdDefineGBSurface_v2(PVGASTATECC pThisCC, SVGA3dCmdDefineGB SVGA3dMSQualityLevel const qualityLevel = pCmd->multisampleCount > 1 ? SVGA3D_MS_QUALITY_FULL : SVGA3D_MS_QUALITY_NONE; vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, multisamplePattern, qualityLevel, pCmd->autogenFilter, - pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ false); + pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, /* bufferByteStride = */ 0, + VMSVGA3D_SURFACE_DEFINE_F_GB); } } @@ -4489,7 +4492,8 @@ static int vmsvga3dCmdDefineGBSurface_v3(PVGASTATECC pThisCC, SVGA3dCmdDefineGBS /* Create the host surface. */ vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, pCmd->multisamplePattern, pCmd->qualityLevel, pCmd->autogenFilter, - pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ false); + pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, /* bufferByteStride = */ 0, + VMSVGA3D_SURFACE_DEFINE_F_GB); } return rc; #else @@ -4896,7 +4900,8 @@ static int vmsvga3dCmdDefineGBSurface_v4(PVGASTATECC pThisCC, SVGA3dCmdDefineGBS /* Create the host surface. */ vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, pCmd->multisamplePattern, pCmd->qualityLevel, pCmd->autogenFilter, - pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, pCmd->bufferByteStride, /* fAllocMipLevels = */ false); + pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, pCmd->bufferByteStride, + VMSVGA3D_SURFACE_DEFINE_F_GB); } return rc; #else diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp index 8fd9da2fba86..d6aa33330d44 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA.cpp 114939 2026-08-10 12:54:06Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * VMware SVGA device. * @@ -5406,7 +5406,22 @@ static void vmsvgaR3FifoHandleExtCmd(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGAST # ifdef VBOX_WITH_VMSVGA3D if (pThis->svga.f3DEnabled || pThis->svga.fVMSVGA2dGBO) { - if (vmsvga3dIsLegacyBackend(pThisCC)) + bool fUseLegacyLoadExec; + if (pThis->svga.fVMSVGA2dGBO && pLoadState->uVersion < VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF) + { + /* 'vmsvga3dIsLegacyBackend' was true in 'fVMSVGA2dGBO' mode, because 'vmsvga3dIsLegacyBackend' + * checked pFuncsDX but 'fVMSVGA2dGBO' did not create this interface. + * So 'fVMSVGA2dGBO' used the legacy saved state function even though the DX backend was used. + * Now, with VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF, 'vmsvga3dIsLegacyBackend' checks for pFuncsGBO, + * which is the essential interface of the VPGU10 capable DX backend, and 'vmsvga3dIsLegacyBackend' + * works correctly for 'fVMSVGA2dGBO' too. + */ + fUseLegacyLoadExec = true; + } + else + fUseLegacyLoadExec = vmsvga3dIsLegacyBackend(pThisCC); + + if (fUseLegacyLoadExec) vmsvga3dLoadExec(pDevIns, pThis, pThisCC, pLoadState->pSSM, pLoadState->uVersion, pLoadState->uPass); # ifdef VMSVGA3D_DX else diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp index 0a4774c7b4dd..ed0198dfe076 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114854 2026-08-04 18:26:47Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -3360,6 +3360,8 @@ static int dxEnsureResource(PVGASTATECC pThisCC, uint32_t sid, rc = vmsvga3dBackSurfaceCreateResource(pThisCC, pSurface); AssertRCReturn(rc, rc); LogFunc(("Created for sid = %u\n", sid)); + + vmsvga3dSurfaceFreeMipLevels(pSurface); } ID3D11Resource *pResource = dxResource(pSurface); @@ -4722,6 +4724,14 @@ static DECLCALLBACK(int) vmsvga3dBackSurfaceUnmap(PVGASTATECC pThisCC, SVGA3dSur } +static DECLCALLBACK(int) vmsvga3dBackEnsureResource(PVGASTATECC pThisCC, uint32_t sid) +{ + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + return dxEnsureResource(pThisCC, sid, &pSurface, &pResource); +} + + static DECLCALLBACK(int) vmsvga3dScreenTargetBind(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, uint32_t sid) { int rc = VINF_SUCCESS; @@ -14427,6 +14437,7 @@ static DECLCALLBACK(int) vmsvga3dBackQueryInterface(PVGASTATECC pThisCC, char co VMSVGA3DBACKENDFUNCSMAP *p = (VMSVGA3DBACKENDFUNCSMAP *)pvInterfaceFuncs; p->pfnSurfaceMap = vmsvga3dBackSurfaceMap; p->pfnSurfaceUnmap = vmsvga3dBackSurfaceUnmap; + p->pfnEnsureResource = vmsvga3dBackEnsureResource; } } else diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-savedstate.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-savedstate.cpp index 83c987acf075..f4096ce73fef 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-savedstate.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-savedstate.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-savedstate.cpp 114266 2026-06-08 15:03:54Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-savedstate.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - DX backend saved state. */ @@ -53,7 +53,7 @@ * Load */ -static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHANDLE pSSM) +static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHANDLE pSSM, uint32_t uVersion) { PVMSVGA3DSTATE p3dState = pThisCC->svga.p3dState; int rc; @@ -65,17 +65,32 @@ static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA if (sid == SVGA3D_INVALID_ID) return VINF_SUCCESS; + bool fGB = true; /* DX backend uses GB surfaces. */ + bool fHW = false; + if (uVersion >= VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF) + { + pHlp->pfnSSMGetBool(pSSM, &fGB); + rc = pHlp->pfnSSMGetBool(pSSM, &fHW); + AssertRCReturn(rc, rc); + AssertReturn(fGB, VERR_INVALID_STATE); + } + /* Define the surface. */ SVGAOTableSurfaceEntry entrySurface; rc = vmsvgaR3OTableReadSurface(pThisCC->svga.pSvgaR3State, sid, &entrySurface); AssertRCReturn(rc, rc); - /** @todo fAllocMipLevels=false and alloc miplevels if there is data to be loaded. */ + /* Alloc miplevels if there is data to be loaded. */ + uint32_t defineFlags = 0; + if (fGB) + defineFlags |= VMSVGA3D_SURFACE_DEFINE_F_GB; + if (p3dState->fVMSVGA2dGBO) + defineFlags |= VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS; rc = vmsvga3dSurfaceDefine(pThisCC, sid, RT_MAKE_U64(entrySurface.surface1Flags, entrySurface.surface2Flags), entrySurface.format, entrySurface.multisampleCount, (SVGA3dMSPattern)entrySurface.multisamplePattern, (SVGA3dMSQualityLevel)entrySurface.qualityLevel, entrySurface.autogenFilter, entrySurface.numMipLevels, &entrySurface.size, entrySurface.arraySize, entrySurface.bufferByteStride, - /* fAllocMipLevels = */ true); + defineFlags); AssertRCReturn(rc, rc); PVMSVGA3DSURFACE pSurface = p3dState->papSurfaces[sid]; @@ -85,13 +100,25 @@ static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA pHlp->pfnSSMGetU32(pSSM, &pSurface->idAssociatedContext); /* Load miplevels data to the surface buffers. */ + bool fAllocated = RT_BOOL(defineFlags & VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS); for (uint32_t j = 0; j < pSurface->cLevels * pSurface->surfaceDesc.numArrayElements; j++) { PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->paMipmapLevels[j]; - /* vmsvga3dSurfaceDefine already allocated the surface data buffer. */ - Assert(pMipmapLevel->cbSurface); - AssertReturn(pMipmapLevel->pSurfaceData, VERR_INTERNAL_ERROR); + if (uVersion >= VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF) + { + bool fUpdated; + rc = pHlp->pfnSSMGetBool(pSSM, &fUpdated); + AssertRCReturn(rc, rc); + pMipmapLevel->fUpdated = fUpdated; + + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.x); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.y); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.z); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.w); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.h); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.d); + } /* Fetch the data present boolean first. */ bool fDataPresent; @@ -100,6 +127,13 @@ static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA if (fDataPresent) { + if (!fAllocated) + { + rc = vmsvga3dSurfaceAllocMipLevels(pSurface); + AssertRCReturn(rc, rc); + fAllocated = true; + } + rc = pHlp->pfnSSMGetMem(pSSM, pMipmapLevel->pSurfaceData, pMipmapLevel->cbSurface); AssertRCReturn(rc, rc); @@ -110,6 +144,9 @@ static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA pMipmapLevel->fDirty = false; } + if (fHW) + vmsvga3dEnsureResource(pThisCC, sid); + return VINF_SUCCESS; } @@ -278,7 +315,7 @@ int vmsvga3dDXLoadExec(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGASTATECC pThisCC, for (uint32_t i = 0; i < p3dState->cSurfaces; ++i) { - rc = vmsvga3dDXLoadSurface(pHlp, pThisCC, pSSM); + rc = vmsvga3dDXLoadSurface(pHlp, pThisCC, pSSM, uVersion); AssertRCReturn(rc, rc); } } @@ -320,15 +357,27 @@ int vmsvga3dDXLoadExec(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGASTATECC pThisCC, static int vmsvga3dDXSaveSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHANDLE pSSM, PVMSVGA3DSURFACE pSurface) { - RT_NOREF(pThisCC); int rc; + /* DX backend uses GB surfaces. If a non GB surface was created (which should not happen), then skip it. */ + if (pSurface->id != SVGA3D_INVALID_ID && !pSurface->fGB) + { + AssertFailed(); + return pHlp->pfnSSMPutU32(pSSM, SVGA3D_INVALID_ID); + } + rc = pHlp->pfnSSMPutU32(pSSM, pSurface->id); AssertRCReturn(rc, rc); if (pSurface->id == SVGA3D_INVALID_ID) return VINF_SUCCESS; + /* VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF begin */ + pHlp->pfnSSMPutBool(pSSM, pSurface->fGB); + rc = pHlp->pfnSSMPutBool(pSSM, VMSVGA3DSURFACE_HAS_HW_SURFACE(pSurface)); + AssertRCReturn(rc, rc); + /* VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF end */ + /* Save the surface fields which are not part of SVGAOTableSurfaceEntry. */ pHlp->pfnSSMPutU32(pSSM, pSurface->idAssociatedContext); @@ -339,6 +388,17 @@ static int vmsvga3dDXSaveSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA uint32_t idx = iMipmap + iArray * pSurface->cLevels; PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->paMipmapLevels[idx]; + /* VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF begin */ + pHlp->pfnSSMPutBool(pSSM, pMipmapLevel->fUpdated); + + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.x); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.y); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.z); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.w); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.h); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.d); + /* VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF end */ + /* Multisample surface content can't be accessed. */ if (pSurface->surfaceDesc.multisampleCount > 1) { diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h index 66d1f952d85f..45d60c575917 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-internal.h 114455 2026-06-19 11:33:52Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-internal.h 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device - 3D part, internal header. */ @@ -438,6 +438,10 @@ typedef struct VMSVGA3DMIPMAPLEVEL /** Set if pvSurfaceData contains data not realized in hardware or pushed to the * hardware surface yet. */ bool fDirty; + /* Whether the GB surface (VMSVGA3DSURFACE::fGB == true) has been updated by the guest. */ + bool fUpdated : 1; + /* The updated region of the GB surface (if fUpdated is true). */ + SVGA3dBox boxUpdated; } VMSVGA3DMIPMAPLEVEL; /** Pointer to a mipmap level. */ typedef VMSVGA3DMIPMAPLEVEL *PVMSVGA3DMIPMAPLEVEL; @@ -570,6 +574,7 @@ typedef struct VMSVGA3DSURFACE /** @todo Only numArrayElements field is used currently. The code uses old fields cLevels, etc for anything else. */ VMSVGA3D_SURFACE_DESC surfaceDesc; + bool fGB : 1; /* Whether this is a guest backed surface. */ union { @@ -1510,6 +1515,9 @@ DECLINLINE(uint32_t) vmsvga3dClampedUMul32(uint32_t a, uint32_t b) return UINT32_C(0xFFFFFFFF); } +int vmsvga3dSurfaceAllocMipLevels(PVMSVGA3DSURFACE pSurface); /* For saved state. */ +void vmsvga3dSurfaceFreeMipLevels(PVMSVGA3DSURFACE pSurface); /* For backend. */ + #if defined(VMSVGA3D_DIRECT3D) HRESULT D3D9UpdateTexture(PVMSVGA3DCONTEXT pContext, PVMSVGA3DSURFACE pSurface); diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-savedstate.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-savedstate.cpp index 5f98f4b9012b..551c1e3cc133 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-savedstate.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-savedstate.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-savedstate.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-savedstate.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - Saved state and assocated stuff. */ @@ -685,7 +685,7 @@ int vmsvga3dLoadExec(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGASTATECC pThisCC, P rc = vmsvga3dSurfaceDefine(pThisCC, sid, surface.f.surfaceFlags, surface.format, surface.multiSampleCount, SVGA3D_MS_PATTERN_NONE, SVGA3D_MS_QUALITY_NONE, - surface.autogenFilter, surface.cLevels, &pMipmapLevelSize[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ true); + surface.autogenFilter, surface.cLevels, &pMipmapLevelSize[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS); AssertRCReturn(rc, rc); RTMemFree(pMipmapLevelSize); diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp index bcb34c28de56..f71ec0886bed 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-win.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-win.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -2741,7 +2741,7 @@ static DECLCALLBACK(int) vmsvga3dBackContextDestroy(PVGASTATECC pThisCC, uint32_ AssertRC(rc); rc = vmsvga3dSurfaceDefine(pThisCC, sid, surfaceFlags, format, multisampleCount, SVGA3D_MS_PATTERN_NONE, SVGA3D_MS_QUALITY_NONE, autogenFilter, - cMipLevels, &pMipLevelSize[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ true); + cMipLevels, &pMipLevelSize[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS); AssertRC(rc); Assert(!pSurface->u.pSurface); diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp index 878e0a8065e2..17559edbc857 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d.cpp 114924 2026-08-10 12:18:33Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - Common core code. */ @@ -140,7 +140,7 @@ static void vmsvgaSurfaceStats(PVGASTATECC pThisCC) } -static int vmsvga3dSurfaceAllocMipLevels(PVMSVGA3DSURFACE pSurface) +int vmsvga3dSurfaceAllocMipLevels(PVMSVGA3DSURFACE pSurface) { /* Allocate buffer to hold the surface data until we can move it into a D3D object */ for (uint32_t i = 0; i < pSurface->cLevels * pSurface->surfaceDesc.numArrayElements; ++i) @@ -154,7 +154,7 @@ static int vmsvga3dSurfaceAllocMipLevels(PVMSVGA3DSURFACE pSurface) } -static void vmsvga3dSurfaceFreeMipLevels(PVMSVGA3DSURFACE pSurface) +void vmsvga3dSurfaceFreeMipLevels(PVMSVGA3DSURFACE pSurface) { for (uint32_t i = 0; i < pSurface->cLevels * pSurface->surfaceDesc.numArrayElements; ++i) { @@ -182,11 +182,11 @@ static void vmsvga3dSurfaceFreeMipLevels(PVMSVGA3DSURFACE pSurface) * @param pMipLevel0Size . * @param arraySize Number of elements in a texture array. * @param bufferByteStride . - * @param fAllocMipLevels . + * @param defineFlags . */ int vmsvga3dSurfaceDefine(PVGASTATECC pThisCC, uint32_t sid, SVGA3dSurfaceAllFlags surfaceFlags, SVGA3dSurfaceFormat format, uint32_t multisampleCount, SVGA3dMSPattern multisamplePattern, SVGA3dMSQualityLevel qualityLevel, SVGA3dTextureFilter autogenFilter, - uint32_t numMipLevels, SVGA3dSize const *pMipLevel0Size, uint32_t arraySize, uint32_t bufferByteStride, bool fAllocMipLevels) + uint32_t numMipLevels, SVGA3dSize const *pMipLevel0Size, uint32_t arraySize, uint32_t bufferByteStride, uint32_t defineFlags) { PVMSVGA3DSURFACE pSurface; PVMSVGA3DSTATE pState = pThisCC->svga.p3dState; @@ -264,6 +264,8 @@ int vmsvga3dSurfaceDefine(PVGASTATECC pThisCC, uint32_t sid, SVGA3dSurfaceAllFla pSurface->surfaceDesc.qualityLevel = RT_CLAMP(qualityLevel, SVGA3D_MS_QUALITY_MIN, SVGA3D_MS_QUALITY_MAX); pSurface->surfaceDesc.bufferByteStride = bufferByteStride; + pSurface->fGB = RT_BOOL(defineFlags & VMSVGA3D_SURFACE_DEFINE_F_GB); + /** @todo This 'switch' and the surfaceFlags tweaks should not be necessary. * The actual surface type will be figured out when the surface is actually used later. * The backends code must be reviewed for unnecessary dependencies on the surfaceFlags value. @@ -498,7 +500,7 @@ int vmsvga3dSurfaceDefine(PVGASTATECC pThisCC, uint32_t sid, SVGA3dSurfaceAllFla Assert(!VMSVGA3DSURFACE_HAS_HW_SURFACE(pSurface)); - if (fAllocMipLevels || pState->fVMSVGA2dGBO) + if (RT_BOOL(defineFlags & VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS) || pState->fVMSVGA2dGBO) { rc = vmsvga3dSurfaceAllocMipLevels(pSurface); AssertRCReturn(rc, rc); @@ -1553,6 +1555,8 @@ int vmsvga3dSurfaceInvalidate(PVGASTATECC pThisCC, uint32_t sid, uint32_t face, PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->paMipmapLevels[i]; pMipmapLevel->fDirty = true; } + + vmsvga3dSurfaceFreeMipLevels(pSurface); } else { @@ -1904,6 +1908,16 @@ int vmsvga3dSurfaceMap(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, int rc = vmsvga3dSurfaceFromSid(pThisCC->svga.p3dState, pImage->sid, &pSurface); AssertRCReturn(rc, rc); + if (fMapFlags & VMSVGA3D_MAP_F_ENSURE_RESOURCE) + { + PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; + AssertReturn(pSvgaR3State->pFuncsMap, VERR_NOT_IMPLEMENTED); + + /* Ensure that the HW resource exists. */ + rc = pSvgaR3State->pFuncsMap->pfnEnsureResource(pThisCC, pImage->sid); + AssertRCReturn(rc, rc); + } + if (VMSVGA3DSURFACE_HAS_HW_SURFACE(pSurface)) { PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; @@ -1979,6 +1993,19 @@ int vmsvga3dSurfaceUnmap(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage } +int vmsvga3dEnsureResource(PVGASTATECC pThisCC, SVGA3dSurfaceId sid) +{ + PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; + AssertReturn(pSvgaR3State->pFuncsMap, VERR_NOT_IMPLEMENTED); + + /* Ensure that the HW resource exists. */ + int rc = pSvgaR3State->pFuncsMap->pfnEnsureResource(pThisCC, sid); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + + int vmsvga3dCalcSurfaceMipmapAndFace(PVGASTATECC pThisCC, uint32_t sid, uint32_t iSubresource, uint32_t *piMipmap, uint32_t *piFace) { PVMSVGA3DSURFACE pSurface; @@ -2114,14 +2141,17 @@ int vmsvga3dGetBoxDimensions(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pI /* - * Whether a legacy 3D backend is used. - * The new DX context can be built together with the legacy D3D9 or OpenGL backend. + * Whether a legacy VGPU9 3D backend is used. + * The new DX backend can be built together with the legacy D3D9 or OpenGL backend. * The actual backend is selected at the VM startup. */ bool vmsvga3dIsLegacyBackend(PVGASTATECC pThisCC) { PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; - return pSvgaR3State->pFuncsDX == NULL; + /* 'pFuncsGBO' is the essential, base interface of the VGPU10 capable backend. + * 'pFuncsGBO' is created and used for both 3D enabled and 'fVMSVGA2dGBO' modes. + */ + return pSvgaR3State->pFuncsGBO == NULL; } diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h index 4c25ccc55a97..7a23716765af 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d.h 114924 2026-08-10 12:18:33Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d.h 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device - 3D part. */ @@ -89,6 +89,7 @@ typedef enum VMSVGA3D_SURFACE_MAP #define VMSVGA3D_MAP_F_DYNAMIC_INTERMEDIATE 0x00000001 #define VMSVGA3D_MAP_F_STAGING_INTERMEDIATE 0x00000002 #define VMSVGA3D_MAP_F_EXACT_REGION 0x00000004 +#define VMSVGA3D_MAP_F_ENSURE_RESOURCE 0x00000008 typedef struct VMSVGA3D_MAPPED_SURFACE { @@ -124,9 +125,12 @@ int vmsvga3dSaveExec(PPDMDEVINS pDevIns, PVGASTATECC pThisCC, PSSMHANDLE pSSM); void vmsvga3dUpdateHostScreenViewport(PVGASTATECC pThisCC, uint32_t idScreen, VMSVGAVIEWPORT const *pOldViewport); int vmsvga3dQueryCaps(PVGASTATECC pThisCC, SVGA3dDevCapIndex idx3dCaps, uint32_t *pu32Val); +#define VMSVGA3D_SURFACE_DEFINE_F_NONE 0x0 +#define VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS 0x1 +#define VMSVGA3D_SURFACE_DEFINE_F_GB 0x2 int vmsvga3dSurfaceDefine(PVGASTATECC pThisCC, uint32_t sid, SVGA3dSurfaceAllFlags surfaceFlags, SVGA3dSurfaceFormat format, uint32_t multisampleCount, SVGA3dMSPattern multisamplePattern, SVGA3dMSQualityLevel qualityLevel, SVGA3dTextureFilter autogenFilter, - uint32_t numMipLevels, SVGA3dSize const *pMipLevel0Size, uint32_t arraySize, uint32_t bufferByteStride, bool fAllocMipLevels); + uint32_t numMipLevels, SVGA3dSize const *pMipLevel0Size, uint32_t arraySize, uint32_t bufferByteStride, uint32_t defineFlags); int vmsvga3dSurfaceDestroy(PVGASTATECC pThisCC, uint32_t sid); int vmsvga3dSurfaceCopy(PVGASTATECC pThisCC, SVGA3dSurfaceImageId dest, SVGA3dSurfaceImageId src, uint32_t cCopyBoxes, SVGA3dCopyBox *pBox); @@ -185,6 +189,7 @@ int vmsvga3dSurfaceInvalidate(PVGASTATECC pThisCC, uint32_t sid, uint32_t face, int vmsvga3dSurfaceMap(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, SVGA3dBox const *pBox, VMSVGA3D_SURFACE_MAP enmMapType, uint32_t fMapFlags, VMSVGA3D_MAPPED_SURFACE *pMap); int vmsvga3dSurfaceUnmap(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, VMSVGA3D_MAPPED_SURFACE *pMap, bool fWritten); +int vmsvga3dEnsureResource(PVGASTATECC pThisCC, SVGA3dSurfaceId sid); uint32_t vmsvga3dCalcSubresourceOffset(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage); @@ -478,6 +483,7 @@ typedef struct { DECLCALLBACKMEMBER(int, pfnSurfaceMap, (PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, SVGA3dBox const *pBox, VMSVGA3D_SURFACE_MAP enmMapType, uint32_t fMapFlags, VMSVGA3D_MAPPED_SURFACE *pMap)); DECLCALLBACKMEMBER(int, pfnSurfaceUnmap, (PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, VMSVGA3D_MAPPED_SURFACE *pMap, bool fWritten)); + DECLCALLBACKMEMBER(int, pfnEnsureResource, (PVGASTATECC pThisCC, uint32_t sid)); } VMSVGA3DBACKENDFUNCSMAP; typedef struct VMSVGA3DSHADER *PVMSVGA3DSHADER; diff --git a/src/VBox/Devices/Graphics/DevVGASavedState.h b/src/VBox/Devices/Graphics/DevVGASavedState.h index 64339da9d64b..cf4e1d74d75a 100644 --- a/src/VBox/Devices/Graphics/DevVGASavedState.h +++ b/src/VBox/Devices/Graphics/DevVGASavedState.h @@ -1,4 +1,4 @@ -/* $Id: DevVGASavedState.h 112846 2026-02-05 17:22:12Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGASavedState.h 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevVGA - Saved state versions. * @@ -56,7 +56,8 @@ } \ } while (0) -#define VGA_SAVEDSTATE_VERSION 33 +#define VGA_SAVEDSTATE_VERSION 34 +#define VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF 34 /* Manage host shadow buffers for GB surfaces. @bugref{10934} */ #define VGA_SAVEDSTATE_VERSION_VMSVGA_HOST_CMDS 33 /* Host commands. See @bugref{11042}. */ #define VGA_SAVEDSTATE_VERSION_VMSVGA_CURSOR_MOB 32 /* Cursor MOB support. See @bugref{11042}. */ #define VGA_SAVEDSTATE_VERSION_VMSVGA_COTABLES 31 /* COTable content. See @bugref{11021}. */ From c450a1960f6f4e43df000f9d32d3655c4cd2b2d8 Mon Sep 17 00:00:00 2001 From: Aleksey Ilyushin Date: Mon, 10 Aug 2026 13:26:53 +0000 Subject: [PATCH 062/176] Devices/Storage/DevVirtioSCSI.cpp: prevent resource leak by not returning prematurely bugref:11133 svn:sync-xref-src-repo-rev: r174787 --- src/VBox/Devices/Storage/DevVirtioSCSI.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/VBox/Devices/Storage/DevVirtioSCSI.cpp b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp index c165c0bf64f8..24a1f83c34dc 100644 --- a/src/VBox/Devices/Storage/DevVirtioSCSI.cpp +++ b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVirtioSCSI.cpp 114943 2026-08-10 12:59:13Z aleksey.ilyushin@oracle.com $ */ +/* $Id: DevVirtioSCSI.cpp 114947 2026-08-10 13:26:53Z aleksey.ilyushin@oracle.com $ */ /** @file * VBox storage devices - Virtio SCSI Driver * @@ -1084,13 +1084,14 @@ static DECLCALLBACK(int) virtioScsiR3IoReqFinish(PPDMIMEDIAEXPORT pInterface, PD RTSgBufInit(&ReqSgBuf, aReqSegs, RT_ELEMENTS(aReqSegs)); size_t cbReqSgBuf = RTSgBufCalcTotalLength(&ReqSgBuf); - /** @todo r=bird: Returning here looks a little bogus... */ - AssertMsgReturn(cbReqSgBuf <= pReq->pVirtqBuf->cbPhysReturn, - ("Guest expected less req data (space needed: %zu, avail: %u)\n", - cbReqSgBuf, pReq->pVirtqBuf->cbPhysReturn), - VERR_BUFFER_OVERFLOW); - - virtioScsiR3VirtqUsedBufPutAndSync(pDevIns, &pThis->Virtio, pReq->uVirtqNbr, &ReqSgBuf, pReq->pVirtqBuf); + if (cbReqSgBuf <= pReq->pVirtqBuf->cbPhysReturn) + virtioScsiR3VirtqUsedBufPutAndSync(pDevIns, &pThis->Virtio, pReq->uVirtqNbr, &ReqSgBuf, pReq->pVirtqBuf); + else + { + AssertMsgFailed(("Guest expected less req data (space needed: %zu, avail: %u)\n", + cbReqSgBuf, pReq->pVirtqBuf->cbPhysReturn)); + rc = VERR_BUFFER_OVERFLOW; + } Log2(("-----------------------------------------------------------------------------------------\n")); } From af4b7cf968bad7f008bd6276374c00006e444cf6 Mon Sep 17 00:00:00 2001 From: Serkan Bayraktar Date: Mon, 10 Aug 2026 13:32:24 +0000 Subject: [PATCH 063/176] API: bugref:11147 Disable importing nvram file path settings from vobx:machine section. svn:sync-xref-src-repo-rev: r174789 --- include/VBox/settings.h | 1 + src/VBox/Main/src-server/ApplianceImplImport.cpp | 10 ++++++++-- src/VBox/Main/xml/Settings.cpp | 7 ++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/include/VBox/settings.h b/include/VBox/settings.h index 0fd124aa7dca..1de93ad0dcc2 100644 --- a/include/VBox/settings.h +++ b/include/VBox/settings.h @@ -1591,6 +1591,7 @@ class MachineConfigFile : public ConfigFileBase */ void sanitizeImportedSerialPorts(); void sanitizeSharedFolderSettings(); + void sanitizeImportedNvramSettings(); static bool isAudioDriverAllowedOnThisHost(AudioDriverType_T enmDrvType); static AudioDriverType_T getHostDefaultAudioDriver(); diff --git a/src/VBox/Main/src-server/ApplianceImplImport.cpp b/src/VBox/Main/src-server/ApplianceImplImport.cpp index a6ddb89c0bba..4a3aeb6e7f89 100644 --- a/src/VBox/Main/src-server/ApplianceImplImport.cpp +++ b/src/VBox/Main/src-server/ApplianceImplImport.cpp @@ -1,4 +1,4 @@ -/* $Id: ApplianceImplImport.cpp 114936 2026-08-10 12:48:12Z serkan.bayraktar@oracle.com $ */ +/* $Id: ApplianceImplImport.cpp 114949 2026-08-10 13:32:24Z serkan.bayraktar@oracle.com $ */ /** @file * IAppliance and IVirtualSystem COM class implementations. */ @@ -398,12 +398,17 @@ HRESULT Appliance::interpret() } } /* Check if shared folders are configured. */ - if (!pNewDesc->m->pConfig->hardwareMachine.llSharedFolders.empty()) { i_addWarning(tr("Virtual appliance \"%s\" was configured with machine shared folder(s) " "This setting will not be imported."), vsysThis.strName.c_str()); } + /* Check if a custom NVRAM file path is configured. */ + if (pNewDesc->m->pConfig->hardwareMachine.nvramSettings.strNvramPath.isNotEmpty()) + { + i_addWarning(tr("Virtual appliance \"%s\" was configured with a custom NVRAM file path. " + "This setting will not be imported."), vsysThis.strName.c_str()); + } } /* Audio */ Utf8Str strSoundCard; @@ -6141,6 +6146,7 @@ void Appliance::i_importVBoxMachine(ComObjPtr &vsdescT if (FAILED(hrc)) throw hrc; config.sanitizeImportedSerialPorts(); config.sanitizeSharedFolderSettings(); + config.sanitizeImportedNvramSettings(); // this magic constructor fills the new machine object with the MachineConfig // instance that we created from the vbox:Machine diff --git a/src/VBox/Main/xml/Settings.cpp b/src/VBox/Main/xml/Settings.cpp index 67370cc80885..f17af645cdab 100644 --- a/src/VBox/Main/xml/Settings.cpp +++ b/src/VBox/Main/xml/Settings.cpp @@ -1,4 +1,4 @@ -/* $Id: Settings.cpp 114936 2026-08-10 12:48:12Z serkan.bayraktar@oracle.com $ */ +/* $Id: Settings.cpp 114949 2026-08-10 13:32:24Z serkan.bayraktar@oracle.com $ */ /** @file * Settings File Manipulation API. * @@ -9715,6 +9715,11 @@ void MachineConfigFile::sanitizeSharedFolderSettings() hardwareMachine.llSharedFolders.clear(); } +void MachineConfigFile::sanitizeImportedNvramSettings() +{ + hardwareMachine.nvramSettings.strNvramPath.setNull(); +} + /** * Called from write() before calling ConfigFileBase::createStubDocument(). * This adjusts the settings version in m->sv if incompatible settings require From eb4d31da6b30b4f25ca7a50b87c087dad7ba8989 Mon Sep 17 00:00:00 2001 From: Aleksey Ilyushin Date: Mon, 10 Aug 2026 13:40:47 +0000 Subject: [PATCH 064/176] Devices/Storage/DrvSCSI.cpp: Initialize allocated buffers with zeros bugref:11140 svn:sync-xref-src-repo-rev: r174792 --- src/VBox/Devices/Storage/DrvSCSI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VBox/Devices/Storage/DrvSCSI.cpp b/src/VBox/Devices/Storage/DrvSCSI.cpp index 3bb53495436e..09356deaef8f 100644 --- a/src/VBox/Devices/Storage/DrvSCSI.cpp +++ b/src/VBox/Devices/Storage/DrvSCSI.cpp @@ -1,4 +1,4 @@ -/* $Id: DrvSCSI.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DrvSCSI.cpp 114952 2026-08-10 13:40:47Z aleksey.ilyushin@oracle.com $ */ /** @file * VBox storage drivers: Generic SCSI command parser and execution driver */ @@ -943,7 +943,7 @@ static DECLCALLBACK(int) drvscsiIoReqSendScsiCmd(PPDMIMEDIAEX pInterface, PDMMED /* Allocate and sync buffers if a data transfer is indicated. */ if (cbBuf) { - pReq->pvBuf = RTMemAlloc(cbBuf); + pReq->pvBuf = RTMemAllocZ(cbBuf); if (RT_UNLIKELY(!pReq->pvBuf)) rc = VERR_NO_MEMORY; } From 6b71ea1d50b2c3024a70d677661e928d6abe4ca4 Mon Sep 17 00:00:00 2001 From: Aleksey Ilyushin Date: Mon, 10 Aug 2026 13:52:40 +0000 Subject: [PATCH 065/176] Devices/Storage/DrvSCSI.cpp: Prevent eject race bugref:11142 svn:sync-xref-src-repo-rev: r174795 --- src/VBox/Devices/Storage/DrvSCSI.cpp | 42 +++++++++++++++++----------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/VBox/Devices/Storage/DrvSCSI.cpp b/src/VBox/Devices/Storage/DrvSCSI.cpp index 09356deaef8f..f28c4eea305e 100644 --- a/src/VBox/Devices/Storage/DrvSCSI.cpp +++ b/src/VBox/Devices/Storage/DrvSCSI.cpp @@ -1,4 +1,4 @@ -/* $Id: DrvSCSI.cpp 114952 2026-08-10 13:40:47Z aleksey.ilyushin@oracle.com $ */ +/* $Id: DrvSCSI.cpp 114955 2026-08-10 13:52:40Z aleksey.ilyushin@oracle.com $ */ /** @file * VBox storage drivers: Generic SCSI command parser and execution driver */ @@ -146,6 +146,8 @@ typedef struct DRVSCSI /** Indicates whether PDMDrvHlpAsyncNotificationCompleted should be called by * any of the dummy functions. */ bool volatile fDummySignal; + /** Flag whether an eject operation is pending. */ + bool volatile fEjectPending; /** Current I/O depth. */ volatile uint32_t StatIoDepth; /** Errors printed in the release log. */ @@ -317,28 +319,36 @@ static DECLCALLBACK(int) drvscsiEject(VSCSILUN hVScsiLun, void *pvScsiLunUser) RT_NOREF(hVScsiLun); PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser; int rc = VINF_SUCCESS; - RTSEMEVENT hSemEvt = NIL_RTSEMEVENT; - /* This must be done from EMT. */ - rc = RTSemEventCreate(&hSemEvt); - if (RT_SUCCESS(rc)) + if (!ASMAtomicXchgBool(&pThis->fEjectPending, true)) { - PDRVSCSIEJECTSTATE pEjectState = (PDRVSCSIEJECTSTATE)PDMDrvHlpQueueAlloc(pThis->pDrvIns, pThis->hQueue); - if (pEjectState) + RTSEMEVENT hSemEvt = NIL_RTSEMEVENT; + + /* This must be done from EMT. */ + rc = RTSemEventCreate(&hSemEvt); + if (RT_SUCCESS(rc)) { - pEjectState->hSemEvt = hSemEvt; - PDMDrvHlpQueueInsert(pThis->pDrvIns, pThis->hQueue, &pEjectState->Core); + PDRVSCSIEJECTSTATE pEjectState = (PDRVSCSIEJECTSTATE)PDMDrvHlpQueueAlloc(pThis->pDrvIns, pThis->hQueue); + if (pEjectState) + { + pEjectState->hSemEvt = hSemEvt; + PDMDrvHlpQueueInsert(pThis->pDrvIns, pThis->hQueue, &pEjectState->Core); - /* Wait for completion. */ - rc = RTSemEventWait(pEjectState->hSemEvt, RT_INDEFINITE_WAIT); - if (RT_SUCCESS(rc)) - rc = pEjectState->rcReq; + /* Wait for completion. */ + rc = RTSemEventWait(pEjectState->hSemEvt, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + rc = pEjectState->rcReq; + } + else + rc = VERR_NO_MEMORY; + + RTSemEventDestroy(hSemEvt); } - else - rc = VERR_NO_MEMORY; - RTSemEventDestroy(pEjectState->hSemEvt); + ASMAtomicXchgBool(&pThis->fEjectPending, false); } + else + rc = VERR_ALREADY_EXISTS; return rc; } From 4cc22cb659a7fe72c18d25ed7d95b1ae26aaa8c2 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 14:15:56 +0000 Subject: [PATCH 066/176] Shared Clipboard/X11: Fixed host-to-guest file transfers getting stuck during guest transfer initialization. The remote provider rejects root-list reads until the transfer is initialized. Keep guest-to-host root preparation in pfnOnInitialize and defer host-to-guest root-list reading and HTTP registration to pfnOnInitialized. No Shared Clipboard protocol changes. bugref:9437 svn:sync-xref-src-repo-rev: r174798 --- .../x11/VBoxClient/clipboard-x11.cpp | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index bce8fa65f1ec..c7f15ffc827b 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 114867 2026-08-06 15:19:51Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-x11.cpp 114958 2026-08-10 14:15:56Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -305,30 +305,12 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA } case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ - { - /* Retrieve the root entries as a first action, so that the transfer is ready to go - * once it gets registered to HTTP server. */ - rc = ShClTransferRootListRead(pTransfer); - if (RT_SUCCESS(rc)) - { - if (ShClTransferRootsCount(pTransfer)) - /* As soon as we register the transfer with the HTTP server, the transfer needs to have its roots set. */ - rc = ShClTransferHttpServerRegisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer); - else - rc = VERR_SHCLPB_NO_DATA; - } break; - } default: break; } - if ( RT_FAILURE(rc) - && ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE - && vbclX11TransferStateMatches(pCtx, pTransfer)) - vbclX11TransferStateComplete(pCtx, pTransfer, NULL, 0, rc); - LogFlowFuncLeaveRC(rc); return rc; } @@ -336,8 +318,9 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialized * - * Builds and publishes URI-list data only for the transfer ID and generation - * captured when the current asynchronous request was registered. + * Reads the root list, then builds and publishes URI-list data only for the + * transfer ID and generation captured when the current asynchronous request + * was registered. * * @thread Clipboard main thread. */ @@ -352,10 +335,25 @@ static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALL if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE && vbclX11TransferStateMatches(pCtx, pTransfer)) { + /* The remote provider rejects root-list reads until ShClTransferInit() + * has changed the transfer state to INITIALIZED. Registering the HTTP + * transfer from pfnOnInitialize therefore races ahead of that state + * transition and leaves URI-list conversion waiting forever. */ + int rc = ShClTransferRootListRead(pTransfer); + if (RT_SUCCESS(rc)) + { + if (ShClTransferRootsCount(pTransfer)) + rc = ShClTransferHttpServerRegisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer); + else + rc = VERR_SHCLPB_NO_DATA; + } + char *pszUriList = NULL; size_t cbUriList = 0; - int rc = ShClTransferHttpConvertToStringList(&pCtx->X11.HttpCtx.HttpServer, pTransfer, + if (RT_SUCCESS(rc)) + rc = ShClTransferHttpConvertToStringList(&pCtx->X11.HttpCtx.HttpServer, pTransfer, &pszUriList, &cbUriList); + vbclX11TransferStateComplete(pCtx, pTransfer, pszUriList, cbUriList, rc); RTStrFree(pszUriList); } @@ -367,7 +365,7 @@ static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALL * This binds pending transfer preparation to the newly registered transfer's * exact ID and generation, and starts the HTTP server if necessary. The * transfer itself is added to the HTTP server after its roots have been read by - * the initialization callback. + * the initialized callback. * * @thread Clipboard main thread. */ From 04af1cd2d3e9d57af75218c6f9e28ffbf4738dfb Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 14:41:23 +0000 Subject: [PATCH 067/176] IntNet/R3: Comment spelling fixes. bugref:11149 svn:sync-xref-src-repo-rev: r174799 --- include/VBox/intnetr3ipc.h | 6 +- .../IntNetSwitch/VBoxIntNetSwitch.cpp | 16 +- src/VBox/NetworkServices/NetLib/IntNetIf.cpp | 14 +- .../testcase/tstVBoxIntNetR3Switch.cpp | 1647 +++++++++++++++++ .../testcase/tstVBoxNatGuestSide.cpp | 10 +- 5 files changed, 1670 insertions(+), 23 deletions(-) create mode 100644 src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp diff --git a/include/VBox/intnetr3ipc.h b/include/VBox/intnetr3ipc.h index afa2585dcea0..500b7185c0ef 100644 --- a/include/VBox/intnetr3ipc.h +++ b/include/VBox/intnetr3ipc.h @@ -1,4 +1,4 @@ -/* $Id: intnetr3ipc.h 114876 2026-08-06 21:41:36Z andreas.loeffler@oracle.com $ */ +/* $Id: intnetr3ipc.h 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ /** @file * Internal networking Ring-3 service IPC protocol. */ @@ -48,7 +48,7 @@ RT_C_DECLS_BEGIN #ifndef INTNET_R3_SVC_NAME # define INTNET_R3_SVC_NAME "org.virtualbox.intnet" #endif -/** Maximum generated per-user Local IPC service name length, including the terminator. */ +/** Maximum generated per-user local IPC service name length, including the terminator. */ #define INTNET_R3_IPC_MAX_SERVICE_NAME 64 /** Protocol version. */ #define INTNET_R3_IPC_VERSION UINT16_C(1) @@ -56,7 +56,7 @@ RT_C_DECLS_BEGIN #define INTNET_R3_IPC_MAX_REQ UINT32_C(65536) /** Maximum shared-memory object name length, including the terminator. */ #define INTNET_R3_IPC_MAX_SHMEM_NAME 256 -/** Maximum time allowed to complete a started Local IPC frame. */ +/** Maximum time allowed to complete a started local IPC frame. */ #define INTNET_R3_IPC_FRAME_TIMEOUT_MS UINT32_C(30000) /** Local IPC request header magic. */ diff --git a/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp b/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp index e71121153f78..47f0a86ed948 100644 --- a/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp +++ b/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxIntNetSwitch.cpp 114877 2026-08-06 21:53:20Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxIntNetSwitch.cpp 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ /** @file * Internal networking - Wrapper for the R0 network service. * @@ -36,7 +36,7 @@ #define IN_INTNET_R3 #if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) # if !defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) -# error "The Local IPC R3 IntNet service implementation is not enabled!" +# error "The local IPC R3 IntNet service implementation is not enabled!" # endif #endif #include "IntNetSwitchInternal.h" @@ -92,9 +92,9 @@ static int intnetR3LocalIpcSendPoke(struct SUPDRVSESSION *pSession); #define INTNETR3_MAX_BUFFER_SIZE UINT64_C(134217728) /** Maximum number of active transport connections. */ #define INTNETR3_MAX_CONNECTIONS UINT32_C(128) -/** Maximum number of Local IPC request and notification worker threads. */ +/** Maximum number of local IPC request and notification worker threads. */ #define INTNETR3_MAX_THREADS UINT32_C(256) -/** Number of worker threads reserved by each Local IPC session. */ +/** Number of worker threads reserved by each local IPC session. */ #define INTNETR3_THREADS_PER_LOCALIPC_SESSION UINT32_C(2) /** Maximum aggregate shared memory allocated by the service. */ #define INTNETR3_MAX_AGGREGATE_SHMEM_SIZE UINT64_C(1073741824) @@ -147,13 +147,13 @@ typedef struct SUPDRVDEVEXT { /** Number of active transport connections. */ uint32_t volatile cRefs; - /** Number of active Local IPC worker threads. */ + /** Number of active local IPC worker threads. */ uint32_t volatile cThreads; /** Aggregate size of all shared-memory allocations. */ uint64_t cbShMem; /** Maximum number of active transport connections. */ uint32_t cMaxConnections; - /** Maximum number of active Local IPC worker threads. */ + /** Maximum number of active local IPC worker threads. */ uint32_t cMaxThreads; /** Maximum aggregate size of all shared-memory allocations. */ uint64_t cbMaxShMem; @@ -383,7 +383,7 @@ static int intnetR3ValidateOpenBufferSizes(uint32_t cbSend, uint32_t cbRecv) /** - * Transport-agnostic IntNet request processor used by both XPC (Darwin) and Local IPC (non-Darwin). + * Transport-agnostic IntNet request processor used by both XPC (Darwin) and local IPC (non-Darwin). * * This helper validates the header, dispatches the request to the existing IntNetR3/R0 handlers, * updates the in/out request buffer, and indicates whether a reply should be sent and/or a @@ -1232,7 +1232,7 @@ static int intnetR3LocalIpcGetServiceName(char *pszService, size_t cbService) # endif -/** Verifies that a Local IPC client belongs to the user running the switch. */ +/** Verifies that a local IPC client belongs to the user running the switch. */ static int intnetR3LocalIpcVerifyPeer(RTLOCALIPCSESSION hSession) { int rc = RTLocalIpcSessionVerifySameUser(hSession); diff --git a/src/VBox/NetworkServices/NetLib/IntNetIf.cpp b/src/VBox/NetworkServices/NetLib/IntNetIf.cpp index 98a280455f26..cd57abd3cd59 100644 --- a/src/VBox/NetworkServices/NetLib/IntNetIf.cpp +++ b/src/VBox/NetworkServices/NetLib/IntNetIf.cpp @@ -1,4 +1,4 @@ -/* $Id: IntNetIf.cpp 114878 2026-08-06 21:59:49Z andreas.loeffler@oracle.com $ */ +/* $Id: IntNetIf.cpp 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ /** @file * IntNetIfCtx - Abstract API implementing an IntNet connection using the R0 support driver or some R3 IPC variant. */ @@ -107,9 +107,9 @@ typedef struct INTNETIFCTXINT # elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) /** Local IPC session to the R3 internal network switch service. */ RTLOCALIPCSESSION hIpcSession; - /** Thread receiving and demultiplexing Local IPC replies and notifications. */ + /** Thread receiving and demultiplexing local IPC replies and notifications. */ RTTHREAD hIpcRecvThread; - /** Serializes Local IPC request/reply calls. */ + /** Serializes local IPC request/reply calls. */ RTSEMMUTEX hIpcCallMtx; /** Serializes short socket read and write operations; data waits happen outside it. */ RTSEMMUTEX hIpcIoMtx; @@ -309,7 +309,7 @@ static int intnetR3IfLocalIpcStartService(void) # endif -/** Returns the absolute frame-read timeout, shortened only by Local IPC testcases. */ +/** Returns the absolute frame-read timeout, shortened only by local IPC testcases. */ static uint32_t intnetR3IfLocalIpcGetFrameTimeout(void) { # ifdef VBOX_INTNET_TESTCASE_LOCALIPC @@ -326,7 +326,7 @@ static uint32_t intnetR3IfLocalIpcGetFrameTimeout(void) } -/** Returns the synchronous reply wait timeout, overridden independently only by Local IPC testcases. */ +/** Returns the synchronous reply wait timeout, overridden independently only by local IPC testcases. */ static uint32_t intnetR3IfLocalIpcGetReplyTimeout(void) { # ifdef VBOX_INTNET_TESTCASE_LOCALIPC @@ -472,7 +472,7 @@ static DECLCALLBACK(int) intnetR3IfLocalIpcRecvThread(RTTHREAD hThread, void *pv } -/** Stops the receiver and closes a failed or no-longer-needed Local IPC transport. */ +/** Stops the receiver and closes a failed or no-longer-needed local IPC transport. */ static void intnetR3IfLocalIpcRetireSession(PINTNETIFCTXINT pThis) { if ( pThis->hIpcSession != NIL_RTLOCALIPCSESSION @@ -609,7 +609,7 @@ static int intnetR3IfLocalIpcReadPoke(PINTNETIFCTXINT pThis, uint32_t cMillies) } -/** Executes a synchronous Local IPC call while the caller owns hIpcCallMtx. */ +/** Executes a synchronous local IPC call while the caller owns hIpcCallMtx. */ static int intnetR3IfLocalIpcCallLocked(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMMR0REQHDR pReqHdr) { size_t const cbReq = pReqHdr->cbReq; diff --git a/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp b/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp new file mode 100644 index 000000000000..9a9b0f1387bd --- /dev/null +++ b/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp @@ -0,0 +1,1647 @@ +/* $Id: tstVBoxIntNetR3Switch.cpp 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ +/** @file + * tstVBoxIntNetR3Switch - Self-contained testcase for R3 IntNet/IntNetSwitch communication. + * + * This RTTest auto-starts a standalone helper built from the production Local + * IPC switch service and R3 wrapper sources. This exercises the same process, + * IPC, shared-memory, and switching boundaries used on non-Darwin hosts. + * + * The client side uses NetLib/IntNetIf (IntNetR3If*) to exercise the production + * driverless path which talks to the R3 service via local IPC. + * + * Covered scenarios (non-exhaustive, but focused on IPC correctness): + * - A squatter occupying the legacy global endpoint cannot block service startup. + * - Missing-service detection, automatic process startup, client attach, and shared-memory mapping. + * - Basic broadcast send on interface A and receive on interface B (same network name). + * - Concurrent Wait/Abort semantics: a blocked IntNetR3IfWait is woken by IntNetR3IfWaitAbort. + * - Repeated send/wait notification registration without lost wakeups. + * - Network isolation: interface C on a different network name does not receive frames. + * - Connection, worker-thread, and aggregate shared-memory limits with recovery. + * - Idle-unverified, partial-request, and partial-reply read timeouts. + * + * The production R3 path on macOS uses XPC. To keep this testcase self-contained + * while still exercising the production local IPC implementation, its build + * selects local IPC on all host platforms, including Darwin. + * + * Usage: + * - Build with VBOX_WITH_TESTCASES=1 and VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC defined. + * kmk -C src/VBox/NetworkServices/testcase tstVBoxIntNetR3Switch + * - Run the produced binary from the staged testcase directory. + * + * Security: No external inputs; all frames, names, and shared memory are local-only. + */ + +/** + * @page pg_tstVBoxIntNetR3Switch R3 IntNet/IntNetSwitch IPC self-contained testcase + * + * Purpose: + * - Validate the production IntNet R3 service local IPC path across a real + * client/service process boundary, including automatic service startup. + * + * Scope: + * - Automatic server startup, client attach/map via IntNetR3If* APIs. + * - Broadcast delivery between two interfaces on the same internal network name. + * - Isolation for a third interface on a different internal network name. + * - Concurrent Wait/Abort and repeated notification coverage for the async notification path. + * - Deferred local IPC notification output that cannot block IntNet delivery. + * + * Build: + * - Ensure VBOX_WITH_TESTCASES=1 and VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC are set. + * - Build just this testcase: kmk -C src/VBox/NetworkServices/testcase tstVBoxIntNetR3Switch + * + * Run: + * - Run tstVBoxIntNetR3Switch from out/.//testcase or the + * platform-specific staged testcase directory. + * + * Notes: + * - Darwin production builds use XPC. This testcase explicitly selects the same + * local IPC implementation used by the other supported hosts. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# include +#endif +#include "../NetLib/IntNetIf.h" + +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# include +#endif + + +/********************************************************************************************************************************* +* Constants & Helpers * +*********************************************************************************************************************************/ +static RTTEST g_hTest = NIL_RTTEST; + +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +DECLHIDDEN(void) intnetR3IfTestSetWaitRace(INTNETIFCTX hIfCtx, RTSEMEVENT hReached, RTSEMEVENT hContinue); + +# define TST_MAX_FRAME 1600 +# define TST_WAIT_MS 1000 +# define TST_STRESS_FRAMES 128 +# define TST_READ_TIMEOUT_MS 1000 + +# define TST_ENV_MAX_CONNECTIONS "VBOX_INTNET_R3_TEST_MAX_CONNECTIONS" +# define TST_ENV_MAX_THREADS "VBOX_INTNET_R3_TEST_MAX_THREADS" +# define TST_ENV_MAX_SHMEM "VBOX_INTNET_R3_TEST_MAX_SHMEM" +# define TST_ENV_READ_TIMEOUT "VBOX_INTNET_R3_TEST_READ_TIMEOUT_MS" +# define TST_ENV_REPLY_TIMEOUT "VBOX_INTNET_R3_TEST_REPLY_TIMEOUT_MS" +# define TST_ENV_POKE_BLOCK_FILE "VBOX_INTNET_R3_TEST_POKE_BLOCK_FILE" + +#define TST_CHECK(a_Expr) \ + do { if (!(a_Expr)) RTTestFailed(g_hTest, "%s:%u: %s", __FILE__, __LINE__, #a_Expr); } while (0) + +#define TST_CHECK_RC_OK(a_rc) \ + do { int rc__ = (a_rc); if (RT_FAILURE(rc__)) RTTestFailed(g_hTest, "%s:%u: %s -> %Rrc", __FILE__, __LINE__, #a_rc, rc__); } while (0) + +static int tstMakeUuidName(const char *pszPrefix, char *pszOut, size_t cbOut) +{ + RTUUID Uuid; char szUuid[RTUUID_STR_LENGTH]; + int rc = RTUuidCreate(&Uuid); if (RT_FAILURE(rc)) return rc; + RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + ssize_t cch = RTStrPrintf2(pszOut, cbOut, "%s-%s", pszPrefix, szUuid); + return cch > 0 && (size_t)cch < cbOut ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW; +} + + +/********************************************************************************************************************************* +* External R3 Switch Service * +*********************************************************************************************************************************/ +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + +typedef struct TSTSWITCHSVC +{ + RTPROCESS hProcess; + bool fProcessReaped; + bool fTempDirCreated; +# ifdef RT_OS_WINDOWS + RTLOCALIPCSERVER hLegacySquatter; +# else + bool fLegacySquatterCreated; + bool fRuntimeDirCreated; + bool fRuntimeDirChanged; + bool fRuntimeDirWasSet; + char *pszSavedRuntimeDir; + char szLegacySquatter[RTPATH_MAX]; + char szRuntimeDir[RTPATH_MAX]; +# endif + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; + char szExec[RTPATH_MAX]; + char szTempDir[RTPATH_MAX]; + char szPidFile[RTPATH_MAX]; + char szLockFile[RTPATH_MAX]; +} TSTSWITCHSVC; + +static bool svcIsAbsent(int rc) +{ + return rc == VERR_FILE_NOT_FOUND + || rc == VERR_PATH_NOT_FOUND + || rc == VERR_NET_CONNECTION_REFUSED + || rc == VERR_PIPE_NOT_CONNECTED; +} + + +/** Returns whether @a rc is a transport-disconnect status from a rejected or closed peer. */ +static bool svcIsDisconnected(int rc) +{ + return rc == VERR_BROKEN_PIPE + || rc == VERR_PIPE_NOT_CONNECTED + || rc == VERR_NET_CONNECTION_RESET + || rc == VERR_NET_CONNECTION_RESET_BY_PEER + || rc == VERR_NET_CONNECTION_REFUSED; +} + + +static int svcReadPid(TSTSWITCHSVC *pSvc) +{ + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpen(&hFile, pSvc->szPidFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + char szPid[32]; + size_t cbRead = 0; + rc = RTFileRead(hFile, szPid, sizeof(szPid) - 1, &cbRead); + int const rc2 = RTFileClose(hFile); + if (RT_SUCCESS(rc)) + rc = rc2; + if (RT_SUCCESS(rc)) + { + szPid[cbRead] = '\0'; + uint32_t uPid = NIL_RTPROCESS; + rc = RTStrToUInt32Full(szPid, 10, &uPid); + if (RT_SUCCESS(rc)) + { + if (uPid == NIL_RTPROCESS || uPid == RTProcSelf()) + rc = VERR_INVALID_PARAMETER; + else + pSvc->hProcess = uPid; + } + } + } + return rc; +} + + +static int svcStart(TSTSWITCHSVC *pSvc) +{ + RT_ZERO(*pSvc); + pSvc->hProcess = NIL_RTPROCESS; +# ifdef RT_OS_WINDOWS + pSvc->hLegacySquatter = NIL_RTLOCALIPCSERVER; +# endif + + int rc = RTPathTemp(pSvc->szTempDir, sizeof(pSvc->szTempDir)); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pSvc->szTempDir, sizeof(pSvc->szTempDir), "tstVBoxIntNetR3Switch-XXXXXX"); + if (RT_SUCCESS(rc)) + { + rc = RTDirCreateTemp(pSvc->szTempDir, 0700); + if (RT_SUCCESS(rc)) + pSvc->fTempDirCreated = true; + } + if (RT_SUCCESS(rc)) + rc = tstMakeUuidName("tst-vbi", pSvc->szService, sizeof(pSvc->szService)); +# ifndef RT_OS_WINDOWS + if (RT_SUCCESS(rc)) + { + pSvc->fRuntimeDirWasSet = RTEnvExist("XDG_RUNTIME_DIR"); + if (pSvc->fRuntimeDirWasSet) + { + pSvc->pszSavedRuntimeDir = RTEnvDupEx(RTENV_DEFAULT, "XDG_RUNTIME_DIR"); + if (!pSvc->pszSavedRuntimeDir) + rc = VERR_NO_MEMORY; + } + } + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pSvc->szRuntimeDir, sizeof(pSvc->szRuntimeDir), + "/tmp/tst-vbi-runtime-%u-XXXXXX", (unsigned)RTProcSelf()); + if (cch <= 0 || (size_t)cch >= sizeof(pSvc->szRuntimeDir)) + rc = VERR_BUFFER_OVERFLOW; + else + { + rc = RTDirCreateTemp(pSvc->szRuntimeDir, 0700); + if (RT_SUCCESS(rc)) + pSvc->fRuntimeDirCreated = true; + } + } + if (RT_SUCCESS(rc)) + { + rc = RTEnvSet("XDG_RUNTIME_DIR", pSvc->szRuntimeDir); + if (RT_SUCCESS(rc)) + pSvc->fRuntimeDirChanged = true; + } +# endif + if (RT_SUCCESS(rc)) + { +# ifdef RT_OS_WINDOWS + rc = RTLocalIpcServerCreate(&pSvc->hLegacySquatter, pSvc->szService, 0 /*fFlags*/); +# else + ssize_t const cch = RTStrPrintf2(pSvc->szLegacySquatter, sizeof(pSvc->szLegacySquatter), + "/tmp/.iprt-localipc-%s", pSvc->szService); + if (cch <= 0 || (size_t)cch >= sizeof(pSvc->szLegacySquatter)) + rc = VERR_BUFFER_OVERFLOW; + else + { + RTDirRemove(pSvc->szLegacySquatter); + rc = RTDirCreate(pSvc->szLegacySquatter, 0700, 0 /*fCreate*/); + if (RT_SUCCESS(rc)) + pSvc->fLegacySquatterCreated = true; + } +# endif + } + if (RT_SUCCESS(rc)) + rc = RTPathExecDir(pSvc->szExec, sizeof(pSvc->szExec)); + if (RT_SUCCESS(rc)) +# ifdef RT_OS_WINDOWS + rc = RTPathAppend(pSvc->szExec, sizeof(pSvc->szExec), "VBoxIntNetR3SwitchTestHelper.exe"); +# else + rc = RTPathAppend(pSvc->szExec, sizeof(pSvc->szExec), "VBoxIntNetR3SwitchTestHelper"); +# endif + if (RT_SUCCESS(rc)) + rc = RTStrCopy(pSvc->szPidFile, sizeof(pSvc->szPidFile), pSvc->szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pSvc->szPidFile, sizeof(pSvc->szPidFile), "switch.pid"); + if (RT_SUCCESS(rc)) + rc = RTStrCopy(pSvc->szLockFile, sizeof(pSvc->szLockFile), pSvc->szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pSvc->szLockFile, sizeof(pSvc->szLockFile), "switch.lock"); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SVC_NAME", pSvc->szService); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_EXE", pSvc->szExec); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_PID_FILE", pSvc->szPidFile); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_LOCK_FILE", pSvc->szLockFile); + if (RT_SUCCESS(rc)) + { + RTLOCALIPCSESSION hExisting = NIL_RTLOCALIPCSESSION; + rc = RTLocalIpcSessionConnect(&hExisting, pSvc->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + { + RTLocalIpcSessionClose(hExisting); + rc = VERR_ALREADY_EXISTS; + } + else if (svcIsAbsent(rc)) + rc = VINF_SUCCESS; + } + return rc; +} + + +static void svcCleanupEndpoint(TSTSWITCHSVC *pSvc) +{ + if (!pSvc->szService[0]) + return; + + RTLOCALIPCSESSION hExisting = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcSessionConnect(&hExisting, pSvc->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + { + RTLocalIpcSessionClose(hExisting); + RTTestFailed(g_hTest, "Switch IPC endpoint still accepts clients after service exit"); + return; + } + if (rc == VERR_NET_CONNECTION_REFUSED) + RTTestFailed(g_hTest, "Switch IPC endpoint pathname was left behind after service exit"); + else if (!svcIsAbsent(rc)) + { + RTTestFailed(g_hTest, "Checking switch IPC endpoint failed: %Rrc", rc); + return; + } + + RTLOCALIPCSERVER hCleanup = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hCleanup, pSvc->szService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + RTLocalIpcServerDestroy(hCleanup); + else + RTTestFailed(g_hTest, "Cleaning switch IPC endpoint failed: %Rrc", rc); +} + + +static void svcStop(TSTSWITCHSVC *pSvc) +{ + if ( !pSvc->fProcessReaped + && pSvc->hProcess == NIL_RTPROCESS + && pSvc->szPidFile[0]) + svcReadPid(pSvc); + + if (pSvc->hProcess != NIL_RTPROCESS) + { + RTPROCSTATUS Status; + int rc = VERR_PROCESS_RUNNING; + uint64_t const msStart = RTTimeMilliTS(); + while ( rc == VERR_PROCESS_RUNNING + && RTTimeMilliTS() - msStart < RT_MS_5SEC) + { + rc = RTProcWait(pSvc->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &Status); + if (rc == VERR_PROCESS_RUNNING) + RTThreadSleep(10); + } + if (rc == VERR_PROCESS_RUNNING) + { + RTTestFailed(g_hTest, "Switch helper did not exit after its last client disconnected"); + int const rcTerm = RTProcTerminate(pSvc->hProcess); + if (RT_SUCCESS(rcTerm)) + rc = RTProcWait(pSvc->hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status); + else + rc = rcTerm; + } + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Waiting for switch helper failed: %Rrc", rc); + else if ( Status.enmReason != RTPROCEXITREASON_NORMAL + || Status.iStatus != RTEXITCODE_SUCCESS) + RTTestFailed(g_hTest, "Switch helper exit reason/status: %d/%d", Status.enmReason, Status.iStatus); + if (RT_SUCCESS(rc)) + pSvc->fProcessReaped = true; + pSvc->hProcess = NIL_RTPROCESS; + } + + svcCleanupEndpoint(pSvc); + + RTEnvUnset("VBOX_INTNET_R3_SWITCH_LOCK_FILE"); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_PID_FILE"); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_EXE"); + RTEnvUnset("VBOX_INTNET_R3_SVC_NAME"); + RTEnvUnset(TST_ENV_MAX_CONNECTIONS); + RTEnvUnset(TST_ENV_MAX_THREADS); + RTEnvUnset(TST_ENV_MAX_SHMEM); + RTEnvUnset(TST_ENV_READ_TIMEOUT); + RTEnvUnset(TST_ENV_REPLY_TIMEOUT); + RTEnvUnset(TST_ENV_POKE_BLOCK_FILE); + +# ifndef RT_OS_WINDOWS + if (pSvc->fRuntimeDirChanged) + { + int const rc = pSvc->fRuntimeDirWasSet + ? RTEnvSet("XDG_RUNTIME_DIR", pSvc->pszSavedRuntimeDir) + : RTEnvUnset("XDG_RUNTIME_DIR"); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Restoring XDG_RUNTIME_DIR failed: %Rrc", rc); + pSvc->fRuntimeDirChanged = false; + } + RTStrFree(pSvc->pszSavedRuntimeDir); + pSvc->pszSavedRuntimeDir = NULL; +# endif + +# ifdef RT_OS_WINDOWS + if (pSvc->hLegacySquatter != NIL_RTLOCALIPCSERVER) + { + int const rc = RTLocalIpcServerDestroy(pSvc->hLegacySquatter); + if (rc != VINF_OBJECT_DESTROYED) + RTTestFailed(g_hTest, "Destroying legacy endpoint squatter failed: %Rrc", rc); + pSvc->hLegacySquatter = NIL_RTLOCALIPCSERVER; + } +# else + if (pSvc->fLegacySquatterCreated) + { + int const rc = RTDirRemove(pSvc->szLegacySquatter); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Removing legacy endpoint squatter failed: %Rrc", rc); + else + pSvc->fLegacySquatterCreated = false; + } + if (pSvc->fRuntimeDirCreated) + { + int const rc = RTDirRemove(pSvc->szRuntimeDir); + if (RT_FAILURE(rc) && rc != VERR_PATH_NOT_FOUND && rc != VERR_FILE_NOT_FOUND) + RTTestFailed(g_hTest, "Removing local IPC runtime directory failed: %Rrc", rc); + else + pSvc->fRuntimeDirCreated = false; + } +# endif + + if (pSvc->szPidFile[0]) + RTFileDelete(pSvc->szPidFile); + if (pSvc->szLockFile[0]) + RTFileDelete(pSvc->szLockFile); + if (pSvc->fTempDirCreated) + { + int const rc = RTDirRemove(pSvc->szTempDir); + if (RT_FAILURE(rc) && rc != VERR_PATH_NOT_FOUND && rc != VERR_FILE_NOT_FOUND) + RTTestFailed(g_hTest, "Removing switch helper temp directory failed: %Rrc", rc); + else + pSvc->fTempDirCreated = false; + } +} + +#endif /* !RT_OS_DARWIN || VBOX_INTNET_TESTCASE_LOCALIPC */ + + +/********************************************************************************************************************************* +* Simple client-side RX collector * +*********************************************************************************************************************************/ +typedef struct TSTRXCOLLECT +{ + INTNETIFCTX hIf; + RTSEMEVENT hEvt; + RTTHREAD hThread; + uint32_t cFrames; + uint8_t abLast[TST_MAX_FRAME]; + uint32_t cbLast; +} TSTRXCOLLECT; + +static DECLCALLBACK(void) tstRxCb(void *pvUser, void *pvFrame, uint32_t cbFrame) +{ + TSTRXCOLLECT *p = (TSTRXCOLLECT *)pvUser; + if (cbFrame > sizeof(p->abLast)) + cbFrame = sizeof(p->abLast); + memcpy(p->abLast, pvFrame, cbFrame); + p->cbLast = cbFrame; + ASMAtomicIncU32(&p->cFrames); + RTSemEventSignal(p->hEvt); +} + +static DECLCALLBACK(int) tstRxThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + TSTRXCOLLECT *p = (TSTRXCOLLECT *)pvUser; + return IntNetR3IfPumpPkts(p->hIf, tstRxCb, p, NULL, NULL); +} + +static int tstRxStart(TSTRXCOLLECT *p, INTNETIFCTX hIf) +{ + RT_ZERO(*p); p->hIf = hIf; p->hEvt = NIL_RTSEMEVENT; p->hThread = NIL_RTTHREAD; + int rc = RTSemEventCreate(&p->hEvt); if (RT_FAILURE(rc)) return rc; + return RTThreadCreate(&p->hThread, tstRxThread, p, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetRx"); +} + +static bool tstRxStop(TSTRXCOLLECT *p) +{ + if (p->hIf) + { + int const rc = IntNetR3IfWaitAbort(p->hIf); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Failed to abort receive thread wait: %Rrc", rc); + } + if (p->hThread != NIL_RTTHREAD) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rc = RTThreadWait(p->hThread, RT_MS_5SEC, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Receive thread did not terminate: %Rrc", rc); + return false; /* The thread may still be using the event and interface. */ + } + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Receive thread returned %Rrc, expected %Rrc", rcThread, VERR_SEM_DESTROYED); + p->hThread = NIL_RTTHREAD; + } + if (p->hEvt != NIL_RTSEMEVENT) + { + RTSemEventDestroy(p->hEvt); + p->hEvt = NIL_RTSEMEVENT; + } + return true; +} + + +/** Joins a receive thread after svcStop has forced its IPC connection closed. */ +static bool tstRxJoinAfterServiceStop(TSTRXCOLLECT *p) +{ + if (p->hThread != NIL_RTTHREAD) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rc = RTThreadWait(p->hThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Joining receive thread after service stop failed: %Rrc", rc); + return false; + } + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Receive thread returned %Rrc after service stop, expected %Rrc", + rcThread, VERR_SEM_DESTROYED); + p->hThread = NIL_RTTHREAD; + } + if (p->hEvt != NIL_RTSEMEVENT) + { + RTSemEventDestroy(p->hEvt); + p->hEvt = NIL_RTSEMEVENT; + } + return true; +} + + +static DECLCALLBACK(int) tstWaitThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + return IntNetR3IfWait((INTNETIFCTX)pvUser, RT_INDEFINITE_WAIT); +} + +static bool tstConcurrentWaitAbort(INTNETIFCTX hIf, PRTTHREAD phThread) +{ + *phThread = NIL_RTTHREAD; + int rc = RTThreadCreate(phThread, tstWaitThread, hIf, 0 /*cbStack*/, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "IntNetWait"); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Failed to create wait thread: %Rrc", rc); + return true; + } + + /* Once the client is sleeping on its receive event, its WAIT request has + already been written to the ordered IPC stream. A following ABORT must + therefore cancel a registered wait, not merely win a scheduling race. */ + uint64_t const msStart = RTTimeMilliTS(); + RTTHREADSTATE enmState; + do + { + enmState = RTThreadGetState(*phThread); + if (enmState == RTTHREADSTATE_EVENT || enmState == RTTHREADSTATE_TERMINATED) + break; + RTThreadSleep(1); + } while (RTTimeMilliTS() - msStart < RT_MS_5SEC); + + if (enmState != RTTHREADSTATE_EVENT) + RTTestFailed(g_hTest, "Wait thread did not block on the receive event (state %d)", enmState); + + rc = IntNetR3IfWaitAbort(hIf); + TST_CHECK_RC_OK(rc); + + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(*phThread, RT_MS_5SEC, &rcThread); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + return false; /* The thread may still be using the interface. */ + *phThread = NIL_RTTHREAD; + if (RT_SUCCESS(rc) && rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Wait thread returned %Rrc, expected %Rrc", rcThread, VERR_SEM_DESTROYED); + + rc = IntNetR3IfWait(hIf, 0 /*cMillies*/); + if (rc != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Wait after abort returned %Rrc, expected %Rrc", rc, VERR_SEM_DESTROYED); + return true; +} + + +typedef struct TSTWAITRACE +{ + RTTHREAD hThread; + RTSEMEVENT hReached; + RTSEMEVENT hContinue; +} TSTWAITRACE; + + +/** Exercises Abort completing between Wait's initial and serialized no-more-waits checks. */ +static bool tstInverseWaitAbortRace(INTNETIFCTX hIf, TSTWAITRACE *pRace) +{ + RT_ZERO(*pRace); + pRace->hThread = NIL_RTTHREAD; + pRace->hReached = NIL_RTSEMEVENT; + pRace->hContinue = NIL_RTSEMEVENT; + + int rc = RTSemEventCreate(&pRace->hReached); + if (RT_SUCCESS(rc)) + rc = RTSemEventCreate(&pRace->hContinue); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Creating inverse-race events failed: %Rrc", rc); + if (pRace->hReached != NIL_RTSEMEVENT) + RTSemEventDestroy(pRace->hReached); + pRace->hReached = NIL_RTSEMEVENT; + return true; + } + + intnetR3IfTestSetWaitRace(hIf, pRace->hReached, pRace->hContinue); + rc = RTThreadCreate(&pRace->hThread, tstWaitThread, hIf, 0 /*cbStack*/, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "IntNetRace"); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Creating inverse-race wait thread failed: %Rrc", rc); + intnetR3IfTestSetWaitRace(NULL, NIL_RTSEMEVENT, NIL_RTSEMEVENT); + RTSemEventDestroy(pRace->hContinue); + RTSemEventDestroy(pRace->hReached); + pRace->hContinue = NIL_RTSEMEVENT; + pRace->hReached = NIL_RTSEMEVENT; + return true; + } + + rc = RTSemEventWait(pRace->hReached, RT_MS_5SEC); + TST_CHECK_RC_OK(rc); + + int const rcAbort = IntNetR3IfWaitAbort(hIf); + TST_CHECK_RC_OK(rcAbort); + TST_CHECK_RC_OK(RTSemEventSignal(pRace->hContinue)); + intnetR3IfTestSetWaitRace(NULL, NIL_RTSEMEVENT, NIL_RTSEMEVENT); + + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(pRace->hThread, RT_MS_5SEC, &rcThread); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + return false; + pRace->hThread = NIL_RTTHREAD; + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Inverse-race wait returned %Rrc, expected %Rrc", rcThread, VERR_SEM_DESTROYED); + + rc = IntNetR3IfWait(hIf, 0 /*cMillies*/); + if (rc != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Post-abort wait returned %Rrc, expected %Rrc", rc, VERR_SEM_DESTROYED); + + RTSemEventDestroy(pRace->hContinue); + RTSemEventDestroy(pRace->hReached); + pRace->hContinue = NIL_RTSEMEVENT; + pRace->hReached = NIL_RTSEMEVENT; + return true; +} + + +/** Joins and releases an inverse-race waiter after the service has been stopped. */ +static bool tstWaitRaceJoinAfterServiceStop(TSTWAITRACE *pRace) +{ + if (pRace->hThread != NIL_RTTHREAD) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rc = RTThreadWait(pRace->hThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Joining inverse-race wait after service stop failed: %Rrc", rc); + return false; + } + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Inverse-race wait returned %Rrc after service stop, expected %Rrc", + rcThread, VERR_SEM_DESTROYED); + pRace->hThread = NIL_RTTHREAD; + } + if (pRace->hContinue != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pRace->hContinue); + pRace->hContinue = NIL_RTSEMEVENT; + } + if (pRace->hReached != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pRace->hReached); + pRace->hReached = NIL_RTSEMEVENT; + } + return true; +} + +static int tstSendFrame(INTNETIFCTX hIf, uint8_t bFill) +{ + INTNETFRAME Frame; + int rc = IntNetR3IfQueryOutputFrame(hIf, 64, &Frame); + if (RT_SUCCESS(rc)) + { + memset(Frame.pvFrame, bFill, 64); + rc = IntNetR3IfOutputFrameCommit(hIf, &Frame); + } + return rc; +} + + +/** Sends one frame from a worker so the test can detect callback blocking. */ +static DECLCALLBACK(int) tstSendThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + return tstSendFrame((INTNETIFCTX)pvUser, 0x5a); +} + + +/** Ensures a blocked notification writer cannot stall the IntNet delivery callback. */ +static void tstDeferredNotification(void) +{ + RTTestSub(g_hTest, "Deferred receive notification"); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing switch helper failed: %Rrc", rc); + svcStop(&Svc); + return; + } + + char szBlockFile[RTPATH_MAX]; + rc = RTStrCopy(szBlockFile, sizeof(szBlockFile), Svc.szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szBlockFile, sizeof(szBlockFile), "poke-block"); + RTFILE hBlockFile = NIL_RTFILE; + bool fBlockFileCreated = false; + if (RT_SUCCESS(rc)) + { + rc = RTFileOpen(&hBlockFile, szBlockFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_ALL + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + fBlockFileCreated = true; + rc = RTFileClose(hBlockFile); + } + } + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + rc = RTEnvSet(TST_ENV_POKE_BLOCK_FILE, szBlockFile); + TST_CHECK_RC_OK(rc); + + INTNETIFCTX hTx = NULL; + INTNETIFCTX hSlow = NULL; + RTTHREAD hWaitThread = NIL_RTTHREAD; + RTTHREAD hSendThread = NIL_RTTHREAD; + bool fNotificationTriggered = false; + bool fServiceStopped = false; + do + { + if (RT_FAILURE(rc)) + break; + + char szNetwork[128]; + RTTESTI_CHECK_RC_OK_BREAK(tstMakeUuidName("tst-IntNet-deferred", szNetwork, sizeof(szNetwork))); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hTx, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hSlow, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hTx, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hSlow, true)); + + RTTESTI_CHECK_RC_OK_BREAK(RTThreadCreate(&hWaitThread, tstWaitThread, hSlow, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetSlow")); + uint64_t const msWaitStart = RTTimeMilliTS(); + RTTHREADSTATE enmState; + do + { + enmState = RTThreadGetState(hWaitThread); + if (enmState == RTTHREADSTATE_EVENT || enmState == RTTHREADSTATE_TERMINATED) + break; + RTThreadSleep(1); + } while (RTTimeMilliTS() - msWaitStart < RT_MS_5SEC); + if (enmState != RTTHREADSTATE_EVENT) + { + RTTestFailed(g_hTest, "Slow receive waiter did not block on its event (state %d)", enmState); + break; + } + + /* This ordered synchronous request proves that the preceding asynchronous + WAIT has reached the service and armed its delivery callback. */ + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hSlow, true)); + + RTTESTI_CHECK_RC_OK_BREAK(RTThreadCreate(&hSendThread, tstSendThread, hTx, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetSend")); + fNotificationTriggered = true; + int rcSend = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hSendThread, RT_MS_5SEC, &rcSend); + if (rc == VERR_TIMEOUT) + RTTestFailed(g_hTest, "Frame delivery remained blocked behind the notification writer"); + else + { + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + hSendThread = NIL_RTTHREAD; + TST_CHECK_RC_OK(rcSend); + } + } + } while (0); + + if (fNotificationTriggered && hWaitThread != NIL_RTTHREAD) + { + int rcWait = VERR_IPE_UNINITIALIZED_STATUS; + int const rc2 = RTThreadWait(hWaitThread, 100 /*cMillies*/, &rcWait); + if (rc2 != VERR_TIMEOUT) + { + RTTestFailed(g_hTest, "Notification block gate was ineffective: %Rrc (%Rrc)", rc2, rcWait); + if (RT_SUCCESS(rc2)) + hWaitThread = NIL_RTTHREAD; + } + } + + if (fBlockFileCreated) + { + int const rc2 = RTFileDelete(szBlockFile); + TST_CHECK_RC_OK(rc2); + if ( RT_SUCCESS(rc2) + || rc2 == VERR_FILE_NOT_FOUND + || rc2 == VERR_PATH_NOT_FOUND) + fBlockFileCreated = false; + } + if (fBlockFileCreated) + { + /* The helper gate is still active, so synchronous cleanup requests may + block as well. Stop the helper before joining either client thread. */ + svcStop(&Svc); + fServiceStopped = true; + + int const rc2 = RTFileDelete(szBlockFile); + if ( RT_SUCCESS(rc2) + || rc2 == VERR_FILE_NOT_FOUND + || rc2 == VERR_PATH_NOT_FOUND) + fBlockFileCreated = false; + } + + if (hSendThread != NIL_RTTHREAD) + { + int rcSend = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hSendThread, RT_MS_5SEC, &rcSend); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + { + if (!fServiceStopped) + { + svcStop(&Svc); + fServiceStopped = true; + } + rc = RTThreadWait(hSendThread, RT_INDEFINITE_WAIT, &rcSend); + } + if (RT_SUCCESS(rc)) + { + hSendThread = NIL_RTTHREAD; + if (!fServiceStopped) + TST_CHECK_RC_OK(rcSend); + } + } + if (hWaitThread != NIL_RTTHREAD) + { + int rcWait = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hWaitThread, RT_MS_5SEC, &rcWait); + if (rc == VERR_TIMEOUT) + { + if (fNotificationTriggered) + RTTestFailed(g_hTest, "Slow receive waiter was not notified after releasing the writer"); + if (!fServiceStopped) + { + svcStop(&Svc); + fServiceStopped = true; + } + rc = RTThreadWait(hWaitThread, RT_INDEFINITE_WAIT, &rcWait); + } + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + hWaitThread = NIL_RTTHREAD; + if (!fServiceStopped && rcWait != VINF_SUCCESS) + RTTestFailed(g_hTest, "Slow receive waiter returned %Rrc, expected %Rrc", rcWait, VINF_SUCCESS); + } + } + + if (hSlow) + IntNetR3IfDestroy(hSlow); + if (hTx) + IntNetR3IfDestroy(hTx); + svcStop(&Svc); +} + + +/** Retries interface creation while a just-closed worker releases its slot. */ +static int tstCreateIfRetry(PINTNETIFCTX phIf, const char *pszNetwork, uint32_t cbSend, uint32_t cbRecv) +{ + uint64_t const msStart = RTTimeMilliTS(); + int rc; + do + { + rc = IntNetR3IfCreateEx(phIf, pszNetwork, kIntNetTrunkType_WhateverNone, "", cbSend, cbRecv, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + return rc; + Assert(*phIf == NULL); + RTThreadSleep(10); + } while (RTTimeMilliTS() - msStart < RT_MS_5SEC); + return rc; +} + + +/** Exercises either the connection or worker-thread admission limit. */ +static void tstSessionSlotLimit(const char *pszSubTest, const char *pszMaxConnections, const char *pszMaxThreads) +{ + RTTestSub(g_hTest, pszSubTest); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing switch helper failed: %Rrc", rc); + svcStop(&Svc); + return; + } + + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_MAX_CONNECTIONS, pszMaxConnections)); + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_MAX_THREADS, pszMaxThreads)); + + char szNetwork[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-limit", szNetwork, sizeof(szNetwork))); + INTNETIFCTX hAnchor = NULL; + INTNETIFCTX hHolder = NULL; + INTNETIFCTX hRejected = NULL; + INTNETIFCTX hRetry = NULL; + do + { + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hAnchor, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hHolder, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + + rc = IntNetR3IfCreateEx(&hRejected, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/); + if (RT_SUCCESS(rc) || hRejected != NULL) + RTTestFailed(g_hTest, "Over-limit interface unexpectedly succeeded: %Rrc (%p)", rc, hRejected); + else if (!svcIsDisconnected(rc)) + RTTestFailed(g_hTest, "Over-limit interface returned unexpected status: %Rrc", rc); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hAnchor, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hAnchor, false)); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfDestroy(hHolder)); + hHolder = NULL; + RTTESTI_CHECK_RC_OK_BREAK(tstCreateIfRetry(&hRetry, szNetwork, _64K, _64K)); + } while (0); + + if (hRejected) + IntNetR3IfDestroy(hRejected); + if (hRetry) + IntNetR3IfDestroy(hRetry); + if (hHolder) + IntNetR3IfDestroy(hHolder); + if (hAnchor) + IntNetR3IfDestroy(hAnchor); + svcStop(&Svc); +} + + +/** Exercises the service-wide shared-memory byte quota and release accounting. */ +static void tstAggregateShMemLimit(void) +{ + RTTestSub(g_hTest, "Aggregate shared-memory limit"); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing switch helper failed: %Rrc", rc); + svcStop(&Svc); + return; + } + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_MAX_SHMEM, "1048576")); + + char szNetwork[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-shmem", szNetwork, sizeof(szNetwork))); + INTNETIFCTX hAnchor = NULL; + INTNETIFCTX hLarge = NULL; + INTNETIFCTX hRejected = NULL; + INTNETIFCTX hRetry = NULL; + do + { + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hAnchor, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hLarge, szNetwork, kIntNetTrunkType_WhateverNone, "", + _256K, _256K, 0 /*fFlags*/)); + + rc = IntNetR3IfCreateEx(&hRejected, szNetwork, kIntNetTrunkType_WhateverNone, "", + _256K, _256K, 0 /*fFlags*/); + if (rc != VERR_OUT_OF_RESOURCES) + RTTestFailed(g_hTest, "Over-quota interface returned %Rrc, expected %Rrc", rc, VERR_OUT_OF_RESOURCES); + if (hRejected != NULL) + RTTestFailed(g_hTest, "Over-quota interface unexpectedly returned a context"); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfDestroy(hLarge)); + hLarge = NULL; + RTTESTI_CHECK_RC_OK_BREAK(tstCreateIfRetry(&hRetry, szNetwork, _256K, _256K)); + } while (0); + + if (hRejected) + IntNetR3IfDestroy(hRejected); + if (hRetry) + IntNetR3IfDestroy(hRetry); + if (hLarge) + IntNetR3IfDestroy(hLarge); + if (hAnchor) + IntNetR3IfDestroy(hAnchor); + svcStop(&Svc); +} + + +/** Sends a deliberately incomplete request and waits for the service to close it. */ +static void tstServerReadTimeoutOne(TSTSWITCHSVC *pSvc, unsigned iPart) +{ + RTLOCALIPCSESSION hSession = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcSessionConnect(&hSession, pSvc->szService, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + return; + + if (iPart == 1) + { + uint8_t bPartial = 0; + rc = RTLocalIpcSessionWrite(hSession, &bPartial, sizeof(bPartial)); + } + else if (iPart == 2 || iPart == 3) + { + INTNETR3IPCREQHDR Hdr; + Hdr.u32Magic = INTNET_R3_IPC_REQ_MAGIC; + Hdr.u16Version = INTNET_R3_IPC_VERSION; + Hdr.cbHdr = sizeof(Hdr); + Hdr.cbReq = sizeof(INTNETIFCLOSEREQ); + Hdr.uOperation = VMMR0_DO_INTNET_IF_CLOSE; + rc = RTLocalIpcSessionWrite(hSession, &Hdr, sizeof(Hdr)); + if (RT_SUCCESS(rc)) + { + uint8_t bPartial = 0; + rc = RTLocalIpcSessionWrite(hSession, &bPartial, sizeof(bPartial)); + } + } + if (RT_SUCCESS(rc) && iPart != 0) + rc = RTLocalIpcSessionFlush(hSession); + + if (RT_SUCCESS(rc) && iPart == 3) + { + uint64_t const msStart = RTTimeMilliTS(); + do + { + rc = RTLocalIpcSessionWaitForData(hSession, 0 /*cMillies*/); + if (svcIsDisconnected(rc)) + break; + if (rc != VERR_TIMEOUT) + break; + RTThreadSleep(TST_READ_TIMEOUT_MS / 4); + uint8_t bPartial = 0; + rc = RTLocalIpcSessionWrite(hSession, &bPartial, sizeof(bPartial)); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(hSession); + } while ( RT_SUCCESS(rc) + && RTTimeMilliTS() - msStart < TST_READ_TIMEOUT_MS * 2); + } + else if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionWaitForData(hSession, RT_MS_5SEC); + + if (!svcIsDisconnected(rc)) + RTTestFailed(g_hTest, "Incomplete request part %u returned %Rrc, expected a disconnect", iPart, rc); + RTLocalIpcSessionClose(hSession); +} + + +/** Ensures a complete but unsuccessful pre-OPEN request does not disable the idle deadline. */ +static void tstServerPreOpenIdleTimeout(TSTSWITCHSVC *pSvc) +{ + RTLOCALIPCSESSION hSession = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcSessionConnect(&hSession, pSvc->szService, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + return; + + INTNETIFCLOSEREQ Req; + RT_ZERO(Req); + Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + Req.Hdr.cbReq = sizeof(Req); + Req.hIf = INTNET_HANDLE_INVALID; + + INTNETR3IPCREQHDR Hdr; + Hdr.u32Magic = INTNET_R3_IPC_REQ_MAGIC; + Hdr.u16Version = INTNET_R3_IPC_VERSION; + Hdr.cbHdr = sizeof(Hdr); + Hdr.cbReq = sizeof(Req); + Hdr.uOperation = VMMR0_DO_INTNET_IF_CLOSE; + rc = RTLocalIpcSessionWrite(hSession, &Hdr, sizeof(Hdr)); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionWrite(hSession, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(hSession); + + INTNETR3IPCREPLYHDR ReplyHdr; + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionRead(hSession, &ReplyHdr, sizeof(ReplyHdr), NULL /*pcbRead*/); + if (RT_SUCCESS(rc)) + { + if ( ReplyHdr.u32Magic != INTNET_R3_IPC_REPLY_MAGIC + || ReplyHdr.u16Version != INTNET_R3_IPC_VERSION + || ReplyHdr.cbHdr != sizeof(ReplyHdr) + || ReplyHdr.cbReply != sizeof(Req) + || ReplyHdr.cbShMemName != 0 + || ReplyHdr.cbShMem != 0) + rc = VERR_INVALID_PARAMETER; + } + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionRead(hSession, &Req, sizeof(Req), NULL /*pcbRead*/); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionWaitForData(hSession, RT_MS_5SEC); + if (!svcIsDisconnected(rc)) + RTTestFailed(g_hTest, "Idle pre-OPEN session returned %Rrc, expected a disconnect", rc); + RTLocalIpcSessionClose(hSession); +} + + +/** Exercises initial-byte and absolute request-frame deadlines. */ +static void tstServerReadTimeout(void) +{ + RTTestSub(g_hTest, "Server read timeout"); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing switch helper failed: %Rrc", rc); + svcStop(&Svc); + return; + } + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_READ_TIMEOUT, "1000")); + + char szNetwork[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-timeout", szNetwork, sizeof(szNetwork))); + INTNETIFCTX hAnchor = NULL; + INTNETIFCTX hFresh = NULL; + do + { + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hAnchor, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + + /* Established interfaces may be idle for longer than the frame timeout. */ + RTThreadSleep(TST_READ_TIMEOUT_MS * 2); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hAnchor, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hAnchor, false)); + + tstServerReadTimeoutOne(&Svc, 0 /* no bytes */); + tstServerReadTimeoutOne(&Svc, 1 /* partial header */); + tstServerReadTimeoutOne(&Svc, 2 /* partial payload */); + tstServerReadTimeoutOne(&Svc, 3 /* drip-fed payload */); + tstServerPreOpenIdleTimeout(&Svc); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hFresh, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + } while (0); + + if (hFresh) + IntNetR3IfDestroy(hFresh); + if (hAnchor) + IntNetR3IfDestroy(hAnchor); + svcStop(&Svc); +} + + +/** Fake restricted service sending an incomplete reply frame. */ +typedef struct TSTPARTIALREPLYSERVER +{ + RTLOCALIPCSERVER hServer; + RTSEMEVENT hRelease; + unsigned iMode; +} TSTPARTIALREPLYSERVER; + + +/** Reads and validates one request accepted by the fake service. */ +static int tstPartialReplyServerReadRequest(RTLOCALIPCSESSION hSession, bool fVerifyPeer, + PINTNETR3IPCREQHDR pReqHdr, void **ppvReq) +{ + *ppvReq = NULL; + int rc = RTLocalIpcSessionRead(hSession, pReqHdr, sizeof(*pReqHdr), NULL /*pcbRead*/); + if (RT_SUCCESS(rc) && fVerifyPeer) + { + rc = RTLocalIpcSessionVerifySameUser(hSession); + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + { + if ( pReqHdr->u32Magic != INTNET_R3_IPC_REQ_MAGIC + || pReqHdr->u16Version != INTNET_R3_IPC_VERSION + || pReqHdr->cbHdr != sizeof(*pReqHdr) + || pReqHdr->cbReq < sizeof(SUPVMMR0REQHDR) + || pReqHdr->cbReq > INTNET_R3_IPC_MAX_REQ) + rc = VERR_INVALID_PARAMETER; + } + if (RT_SUCCESS(rc)) + { + *ppvReq = RTMemTmpAlloc(pReqHdr->cbReq); + rc = *ppvReq ? RTLocalIpcSessionRead(hSession, *ppvReq, pReqHdr->cbReq, NULL /*pcbRead*/) + : VERR_NO_TMP_MEMORY; + } + return rc; +} + + +/** Sends one complete successful fake-service reply. */ +static int tstPartialReplyServerSendReply(RTLOCALIPCSESSION hSession, INTNETR3IPCREQHDR const *pReqHdr, + const void *pvReq) +{ + INTNETR3IPCREPLYHDR ReplyHdr; + ReplyHdr.u32Magic = INTNET_R3_IPC_REPLY_MAGIC; + ReplyHdr.u16Version = INTNET_R3_IPC_VERSION; + ReplyHdr.cbHdr = sizeof(ReplyHdr); + ReplyHdr.rc = VINF_SUCCESS; + ReplyHdr.cbReply = pReqHdr->cbReq; + ReplyHdr.cbShMemName = 0; + ReplyHdr.cbShMem = 0; + int rc = RTLocalIpcSessionWrite(hSession, &ReplyHdr, sizeof(ReplyHdr)); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionWrite(hSession, pvReq, pReqHdr->cbReq); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(hSession); + return rc; +} + + +/** Accepts requests and deliberately stalls before or during a reply. */ +static DECLCALLBACK(int) tstPartialReplyServerThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + TSTPARTIALREPLYSERVER *pServer = (TSTPARTIALREPLYSERVER *)pvUser; + + RTLOCALIPCSESSION hSession = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcServerListen(pServer->hServer, &hSession); + if (RT_SUCCESS(rc)) + { + INTNETR3IPCREQHDR ReqHdr; + void *pvReq = NULL; + rc = tstPartialReplyServerReadRequest(hSession, true /*fVerifyPeer*/, &ReqHdr, &pvReq); + if (RT_SUCCESS(rc) && pServer->iMode == 3) + { + if (ReqHdr.uOperation != VMMR0_DO_INTNET_OPEN || ReqHdr.cbReq != sizeof(INTNETOPENREQ)) + rc = VERR_INVALID_PARAMETER; + else + { + ((PINTNETOPENREQ)pvReq)->hIf = 1; + rc = tstPartialReplyServerSendReply(hSession, &ReqHdr, pvReq); + } + RTMemTmpFree(pvReq); + pvReq = NULL; + if (RT_SUCCESS(rc)) + rc = tstPartialReplyServerReadRequest(hSession, false /*fVerifyPeer*/, &ReqHdr, &pvReq); + if ( RT_SUCCESS(rc) + && ReqHdr.uOperation != VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS) + rc = VERR_INVALID_PARAMETER; + } + if (RT_SUCCESS(rc)) + { + INTNETR3IPCREPLYHDR ReplyHdr; + ReplyHdr.u32Magic = INTNET_R3_IPC_REPLY_MAGIC; + ReplyHdr.u16Version = INTNET_R3_IPC_VERSION; + ReplyHdr.cbHdr = sizeof(ReplyHdr); + ReplyHdr.rc = VINF_SUCCESS; + ReplyHdr.cbReply = ReqHdr.cbReq; + ReplyHdr.cbShMemName = 0; + ReplyHdr.cbShMem = 0; + if (pServer->iMode == 1) + rc = RTLocalIpcSessionWrite(hSession, &ReplyHdr, 1); + else if (pServer->iMode == 2) + { + rc = RTLocalIpcSessionWrite(hSession, &ReplyHdr, sizeof(ReplyHdr)); + if (RT_SUCCESS(rc)) + { + uint8_t bPartial = 0; + rc = RTLocalIpcSessionWrite(hSession, &bPartial, sizeof(bPartial)); + } + } + if (RT_SUCCESS(rc) && (pServer->iMode == 1 || pServer->iMode == 2)) + rc = RTLocalIpcSessionFlush(hSession); + if (RT_SUCCESS(rc)) + rc = RTSemEventWait(pServer->hRelease, RT_MS_5SEC); + } + RTMemTmpFree(pvReq); + RTLocalIpcSessionClose(hSession); + } + return rc; +} + + +/** Exercises no-reply, partial-frame, and post-OPEN client read deadlines. */ +static void tstClientReadTimeout(void) +{ + RTTestSub(g_hTest, "Client read timeout"); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing fake service failed: %Rrc", rc); + svcStop(&Svc); + return; + } + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_READ_TIMEOUT, "1000")); + + RTLOCALIPCSERVER hServer = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hServer, Svc.szService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + for (unsigned iMode = 0; iMode < 4; iMode++) + { + /* A partial frame must be retired by the receiver's frame deadline, + not by the indistinguishable synchronous-call deadline. */ + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_REPLY_TIMEOUT, + iMode == 1 || iMode == 2 ? "4294967295" : "1000")); + + TSTPARTIALREPLYSERVER Server; + Server.hServer = hServer; + Server.hRelease = NIL_RTSEMEVENT; + Server.iMode = iMode; + RTTHREAD hThread = NIL_RTTHREAD; + rc = RTSemEventCreate(&Server.hRelease); + if (RT_SUCCESS(rc)) + rc = RTThreadCreate(&hThread, tstPartialReplyServerThread, &Server, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetFake"); + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + char szNetwork[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-client-timeout", szNetwork, sizeof(szNetwork))); + INTNETIFCTX hIf = NULL; + rc = IntNetR3IfCreateEx(&hIf, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/); + if (rc != VERR_TIMEOUT) + RTTestFailed(g_hTest, "Incomplete reply mode %u returned %Rrc, expected %Rrc", + iMode, rc, VERR_TIMEOUT); + if (hIf != NULL) + { + RTTestFailed(g_hTest, "Incomplete reply mode %u unexpectedly returned an interface", iMode); + IntNetR3IfDestroy(hIf); + } + + TST_CHECK_RC_OK(RTSemEventSignal(Server.hRelease)); + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hThread, RT_MS_5SEC, &rcThread); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + rc = RTThreadWait(hThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rc) && RT_FAILURE(rcThread)) + RTTestFailed(g_hTest, "Fake service mode %u returned %Rrc", iMode, rcThread); + } + if (Server.hRelease != NIL_RTSEMEVENT) + RTSemEventDestroy(Server.hRelease); + } + RTLocalIpcServerDestroy(hServer); + } + svcStop(&Svc); +} +#endif /* !RT_OS_DARWIN || VBOX_INTNET_TESTCASE_LOCALIPC */ + + +/********************************************************************************************************************************* +* Test body * +*********************************************************************************************************************************/ +static void tstIntNetR3Switch(void) +{ + RTTestSub(g_hTest, "R3 switch IPC (local IPC)"); + +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + RTTestSub(g_hTest, "R3 switch IPC (Darwin/XPC)"); + RTTestSkipped(g_hTest, "Darwin uses XPC for R3 IntNet switch; in-process embedding is not implemented in this testcase."); + return; +#else + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Failed to prepare standalone switch service: %Rrc", rc); + svcStop(&Svc); + return; + } + + char szNetA[128], szNetB[128], szNetWait[128], szNetRace[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-A", szNetA, sizeof(szNetA))); + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-B", szNetB, sizeof(szNetB))); + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-Wait", szNetWait, sizeof(szNetWait))); + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-Race", szNetRace, sizeof(szNetRace))); + INTNETIFCTX hA = NULL, hB = NULL, hC = NULL, hWait = NULL, hRace = NULL; + RTSEMEVENT hWaitEvt = NIL_RTSEMEVENT; + RTTHREAD hWaitThread = NIL_RTTHREAD; + TSTRXCOLLECT RxB; RT_ZERO(RxB); + TSTRXCOLLECT RxC; RT_ZERO(RxC); + TSTWAITRACE WaitRace; RT_ZERO(WaitRace); + WaitRace.hThread = NIL_RTTHREAD; + WaitRace.hReached = NIL_RTSEMEVENT; + WaitRace.hContinue = NIL_RTSEMEVENT; + bool fWaitThreadStopped = true; + bool fRaceThreadStopped = true; + do + { + /* No service is running yet. This first interface creation must auto-start it. */ + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hA, szNetA, kIntNetTrunkType_WhateverNone, "", _256K, _256K, 0)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + RTPROCSTATUS Status; + rc = RTProcWait(Svc.hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &Status); + if (rc != VERR_PROCESS_RUNNING) + { + RTTestFailed(g_hTest, "Auto-started switch helper is not running: %Rrc", rc); + if (RT_SUCCESS(rc)) + { + Svc.fProcessReaped = true; + Svc.hProcess = NIL_RTPROCESS; + } + break; + } + + /* Reject hostile sizes before they reach IntNet's uint32_t alignment arithmetic. */ + INTNETIFCTX hOversized = NULL; + rc = IntNetR3IfCreateEx(&hOversized, szNetA, kIntNetTrunkType_WhateverNone, "", + UINT32_MAX, UINT32_MAX, 0); + if (rc != VERR_OUT_OF_RANGE) + RTTestFailed(g_hTest, "Oversized open returned %Rrc, expected %Rrc", rc, VERR_OUT_OF_RANGE); + if (hOversized != NULL) + { + RTTestFailed(g_hTest, "Oversized open unexpectedly returned an interface context"); + IntNetR3IfDestroy(hOversized); + } + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hB, szNetA, kIntNetTrunkType_WhateverNone, "", _256K, _256K, 0)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hC, szNetB, kIntNetTrunkType_WhateverNone, "", _128K, _128K, 0)); + RTTESTI_CHECK_RC_OK_BREAK(RTSemEventCreate(&hWaitEvt)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateExWithRecvEvent(&hWait, szNetWait, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0, hWaitEvt)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hRace, szNetRace, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0)); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hA, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hB, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hC, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hWait, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hRace, true)); + + RTTESTI_CHECK_RC_OK_BREAK(tstRxStart(&RxB, hB)); + RTTESTI_CHECK_RC_OK_BREAK(tstRxStart(&RxC, hC)); + + /* Build and send a small broadcast-like frame on A. */ + RTTESTI_CHECK_RC_OK_BREAK(tstSendFrame(hA, 0x42)); /* content isn't parsed by switch */ + + /* Wait for B to receive, ensure C did not (isolation by network name). */ + uint64_t msStart = RTTimeMilliTS(); + bool fGotB = false; bool fGotC = false; + while (RTTimeMilliTS() - msStart < TST_WAIT_MS) + { + fGotB = ASMAtomicReadU32(&RxB.cFrames) != 0; + fGotC = ASMAtomicReadU32(&RxC.cFrames) != 0; + if (fGotC) + break; + RTThreadSleep(10); + } + TST_CHECK(fGotB); + TST_CHECK(!fGotC); + + /* Send the next frame as soon as the preceding callback runs. This + repeatedly overlaps delivery with the collector re-registering its + wait and exercises the server's lost-wakeup avoidance. */ + uint32_t const cFramesBefore = ASMAtomicReadU32(&RxB.cFrames); + uint64_t const msStressStart = RTTimeMilliTS(); + uint32_t cFramesSent = 0; + while (cFramesSent < TST_STRESS_FRAMES) + { + rc = tstSendFrame(hA, (uint8_t)cFramesSent); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Stress frame %RU32 failed: %Rrc", cFramesSent, rc); + break; + } + cFramesSent++; + while ( ASMAtomicReadU32(&RxB.cFrames) < cFramesBefore + cFramesSent + && RTTimeMilliTS() - msStressStart < RT_MS_5SEC) + RTThreadSleep(1); + if (ASMAtomicReadU32(&RxB.cFrames) < cFramesBefore + cFramesSent) + { + RTTestFailed(g_hTest, "Stress receive stalled after %RU32 frames", cFramesSent); + break; + } + } + TST_CHECK(ASMAtomicReadU32(&RxB.cFrames) >= cFramesBefore + cFramesSent); + TST_CHECK(ASMAtomicReadU32(&RxC.cFrames) == 0); + + fWaitThreadStopped = tstConcurrentWaitAbort(hWait, &hWaitThread); + fRaceThreadStopped = tstInverseWaitAbortRace(hRace, &WaitRace); + + } while (0); + + /* Cleanup RX helpers before tearing down interfaces, including error paths. */ + bool const fRxBStopped = tstRxStop(&RxB); + bool const fRxCStopped = tstRxStop(&RxC); + + if (hA) { IntNetR3IfSetActive(hA, false); IntNetR3IfDestroy(hA); } + if (hB && fRxBStopped) { IntNetR3IfSetActive(hB, false); IntNetR3IfDestroy(hB); } + if (hC && fRxCStopped) { IntNetR3IfSetActive(hC, false); IntNetR3IfDestroy(hC); } + if (hWait && fWaitThreadStopped) + { + IntNetR3IfSetActive(hWait, false); + IntNetR3IfDestroy(hWait); + hWait = NULL; + + /* The caller-owned event must remain valid after interface teardown. */ + TST_CHECK_RC_OK(RTSemEventSignal(hWaitEvt)); + TST_CHECK_RC_OK(RTSemEventWait(hWaitEvt, 0 /*cMillies*/)); + } + if (hWaitEvt != NIL_RTSEMEVENT && fWaitThreadStopped) + RTSemEventDestroy(hWaitEvt); + if (hRace && fRaceThreadStopped) + { + IntNetR3IfSetActive(hRace, false); + IntNetR3IfDestroy(hRace); + hRace = NULL; + } + + svcStop(&Svc); + + /* If a bounded pre-stop join timed out, forcing the helper down closes the + IPC stream. Join before stack storage, events, or interfaces disappear. */ + if (!fRxBStopped && tstRxJoinAfterServiceStop(&RxB) && hB) + { + IntNetR3IfDestroy(hB); + hB = NULL; + } + if (!fRxCStopped && tstRxJoinAfterServiceStop(&RxC) && hC) + { + IntNetR3IfDestroy(hC); + hC = NULL; + } + if (!fWaitThreadStopped) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hWaitThread, RT_INDEFINITE_WAIT, &rcThread); + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc) && rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Wait thread returned %Rrc after service stop, expected %Rrc", + rcThread, VERR_SEM_DESTROYED); + if (RT_SUCCESS(rc) && hWait) + { + IntNetR3IfDestroy(hWait); + hWait = NULL; + } + if (RT_SUCCESS(rc) && hWaitEvt != NIL_RTSEMEVENT) + { + RTSemEventDestroy(hWaitEvt); + hWaitEvt = NIL_RTSEMEVENT; + } + } + if (!fRaceThreadStopped && tstWaitRaceJoinAfterServiceStop(&WaitRace) && hRace) + { + IntNetR3IfDestroy(hRace); + hRace = NULL; + } +#endif +} + + +int main(int argc, char **argv) +{ + /* The testcase build of IntNetIf forces the driverless R3 path and does not initialize SUPLib. */ + int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + RTTEST hTest = NIL_RTTEST; + rc = RTTestCreate("tstVBoxIntNetR3Switch", &hTest); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + g_hTest = hTest; + + tstIntNetR3Switch(); +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + tstDeferredNotification(); + tstSessionSlotLimit("Connection limit", "2", "8"); + tstSessionSlotLimit("Worker-thread limit", "8", "4"); + tstAggregateShMemLimit(); + tstServerReadTimeout(); + tstClientReadTimeout(); +#endif + + return RTTestSummaryAndDestroy(hTest); +} diff --git a/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp index cbc8c6971c5f..a160290eceb0 100644 --- a/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp +++ b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVBoxNatGuestSide.cpp 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ */ +/* $Id: tstVBoxNatGuestSide.cpp 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ /** @file * tstVBoxNatGuestSide - Guest-side NAT over Ring-3 IntNet testcase. */ @@ -129,7 +129,7 @@ typedef struct TSTSWITCHSERVICE bool fProcessReaped; /** Whether the temporary directory was created. */ bool fTempDirCreated; - /** Unique Local IPC service name. */ + /** Unique local IPC service name. */ char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; /** Absolute helper executable path. */ char szExec[RTPATH_MAX]; @@ -748,7 +748,7 @@ static int tstMakeUuidName(const char *pszPrefix, char *pszName, size_t cbName) } -/** Returns whether a Local IPC connection failure means no server is present. */ +/** Returns whether a local IPC connection failure means no server is present. */ static bool tstServiceIsAbsent(int rc) { return rc == VERR_FILE_NOT_FOUND @@ -867,7 +867,7 @@ static int tstServiceStart(PTSTSWITCHSERVICE pService) } -/** Removes a stale POSIX Local IPC filesystem node after the helper exits. */ +/** Removes a stale POSIX local IPC filesystem node after the helper exits. */ static void tstServiceCleanupEndpoint(PTSTSWITCHSERVICE pService) { if (!pService->szService[0]) @@ -1083,7 +1083,7 @@ static int tstIntNetSend(INTNETIFCTX hIf, PCTSTFRAME pFrame) } -/** Verifies an ARP request/reply through the external Local IPC R3 switch. */ +/** Verifies an ARP request/reply through the external local IPC R3 switch. */ static void tstR3IntNetNatArp(void) { RTTestSub(g_hTest, "R3 IntNet switch -> restricted NAT -> R3 IntNet"); From 7cb93335ccc974620ec709f3400110df2d40ea39 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 14:50:43 +0000 Subject: [PATCH 068/176] IntNet/R3: Added VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES so that those testcases can also be run on Darwin (where XPC is being used by default instead of local IPC). bugref:11149 svn:sync-xref-src-repo-rev: r174800 --- Config.kmk | 9 +- src/VBox/NetworkServices/Dhcpd/Makefile.kmk | 4 +- .../NetworkServices/Dhcpd/testcase/.gitignore | 0 .../Dhcpd/testcase/Makefile.kmk | 95 +++++++++++++++++++ src/VBox/NetworkServices/Makefile.kmk | 4 +- .../NetworkServices/testcase/Makefile.kmk | 6 +- 6 files changed, 108 insertions(+), 10 deletions(-) delete mode 100644 src/VBox/NetworkServices/Dhcpd/testcase/.gitignore create mode 100644 src/VBox/NetworkServices/Dhcpd/testcase/Makefile.kmk diff --git a/Config.kmk b/Config.kmk index f4006bd336cc..6a0c2a37f4d4 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114903 2026-08-10 07:19:06Z alexander.eichner@oracle.com $ +# $Id: Config.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -568,11 +568,14 @@ endif if1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), darwin.amd64 darwin.arm64 linux.amd64 linux.arm64 win.arm64) VBOX_WITH_DRIVERLESS_NEM_FALLBACK = 1 endif +# Enable local IPC testcase coverage together with the production transport. +VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES = $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),1,) # Enable forced driverless mode by default. if1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), darwin.amd64 darwin.arm64) VBOX_WITH_DRIVERLESS_FORCED = 1 if1of ($(KBUILD_TARGET), darwin) VBOX_WITH_INTNET_SERVICE_IN_R3 = 1 + VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES = 1 endif endif # The local IPC implementation of the R3 IntNet service is @@ -9605,7 +9608,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114903 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114960 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9619,7 +9622,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114903 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114960 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif diff --git a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk index 9fd2d3dd6d26..ee13f5afaef3 100644 --- a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk +++ b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114886 2026-08-07 08:28:10Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for the DHCP server. # @@ -29,7 +29,7 @@ SUB_DEPTH := ../../../.. include $(KBUILD_PATH)/subheader.kmk # Include testcases. -if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES) include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk endif diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/.gitignore b/src/VBox/NetworkServices/Dhcpd/testcase/.gitignore deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/Makefile.kmk b/src/VBox/NetworkServices/Dhcpd/testcase/Makefile.kmk new file mode 100644 index 000000000000..41cb6f311fa8 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/testcase/Makefile.kmk @@ -0,0 +1,95 @@ +# $Id: Makefile.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ +## @file +# Sub-Makefile for VBoxNetDHCP testcases. + +# +# Copyright (C) 2026 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH := ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES) + + PROGRAMS += tstVBoxNetDhcpd + + tstVBoxNetDhcpd_TEMPLATE = VBoxR3TstExe + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstVBoxNetDhcpd_0_OUTDIR)/tstVBoxNetDhcpd.run + endif + tstVBoxNetDhcpd_CLEAN = $(tstVBoxNetDhcpd_0_OUTDIR)/tstVBoxNetDhcpd.run + + ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + tstVBoxNetDhcpd_DEFS = KBUILD_TYPE="$(KBUILD_TYPE)" + else + tstVBoxNetDhcpd_DEFS = KBUILD_TYPE=\"$(KBUILD_TYPE)\" + endif + + tstVBoxNetDhcpd_DEFS += \ + TESTCASE VBOXNETDHCPD_INPROC_TESTING VBOX_WITH_INTNET_SERVICE_IN_R3 VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC \ + VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH VBOX_INTNET_TESTCASE_LOCALIPC + + ifneq ($(KBUILD_TARGET),win) + tstVBoxNetDhcpd_DEFS += VBOX_WITH_XPCOM + tstVBoxNetDhcpd_INCS += $(VBOX_XPCOM_INCS) + endif + + tstVBoxNetDhcpd_SDKS += VBoxLwipDhcp + + tstVBoxNetDhcpd_INCS += \ + .. \ + ../../NetLib \ + ../../IntNetSwitch + + # Do not list ../VBoxNetDhcpd.cpp directly here. The wrapper below includes it + # after defining VBOXNETDHCPD_INPROC_TESTING, guaranteeing that the in-process + # testcase exports are compiled even on Windows/kBuild targets. + tstVBoxNetDhcpd_SOURCES = \ + tstVBoxNetDhcpd.cpp \ + VBoxNetDhcpdInProc.cpp \ + VBoxIntNetSwitchInProc.cpp \ + ../ClientId.cpp \ + ../Config.cpp \ + ../DHCPD.cpp \ + ../Db.cpp \ + ../DhcpMessage.cpp \ + ../DhcpOptions.cpp \ + ../IPv4Pool.cpp \ + ../Timestamp.cpp \ + ../../IntNetSwitch/SrvIntNetWrapper.cpp \ + ../../NetLib/IntNetIf.cpp \ + ../../../Main/glue/VBoxLogRelCreate.cpp \ + ../../../Main/glue/GetVBoxUserHomeDirectory.cpp + + tstVBoxNetDhcpd_LIBS = \ + $(LIB_RUNTIME) + + tstVBoxNetDhcpd_LIBS.solaris += socket nsl + + $$(tstVBoxNetDhcpd_0_OUTDIR)/tstVBoxNetDhcpd.run: \ + $$(tstVBoxNetDhcpd_1_STAGE_TARGET) \ + | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstVBoxNetDhcpd_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + +endif # VBOX_WITH_TESTCASES && VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/NetworkServices/Makefile.kmk b/src/VBox/NetworkServices/Makefile.kmk index c2d66540ca7f..7b9990709aed 100644 --- a/src/VBox/NetworkServices/Makefile.kmk +++ b/src/VBox/NetworkServices/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114881 2026-08-07 06:50:28Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ ## @file # Top-level makefile for the VBox Network Services. # @@ -43,7 +43,7 @@ if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) include $(PATH_SUB_CURRENT)/IntNetSwitch/Makefile.kmk endif -if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES) include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk endif diff --git a/src/VBox/NetworkServices/testcase/Makefile.kmk b/src/VBox/NetworkServices/testcase/Makefile.kmk index 3ad704c9ed69..21367ee02484 100644 --- a/src/VBox/NetworkServices/testcase/Makefile.kmk +++ b/src/VBox/NetworkServices/testcase/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ ## @file # Network Services testcases. # @@ -28,7 +28,7 @@ SUB_DEPTH = ../../../.. include $(KBUILD_PATH)/subheader.kmk -if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES) if !defined(VBOX_ONLY_BUILD) \ && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SUPPORTED_HOST_ARCHS))" != "" @@ -119,6 +119,6 @@ if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALI VBoxIntNetR3SwitchTestHelper_LIBS = \ $(LIB_RUNTIME) -endif # VBOX_WITH_TESTCASES && VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC +endif # VBOX_WITH_TESTCASES && VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES include $(FILE_KBUILD_SUB_FOOTER) From 7541f102c057af051d79bb2c797c80bd2f32ed9f Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 15:01:12 +0000 Subject: [PATCH 069/176] Shared Clipboard/Main: Fix required for running the new testcases of r174443. svn:sync-xref-src-repo-rev: r174801 --- .../SharedClipboard/clipboard-transfers-provider-local.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp index 4deaa49e627b..ba80e34740fa 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-provider-local.cpp 114661 2026-07-08 10:39:13Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-provider-local.cpp 114961 2026-08-10 15:01:12Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Transfers interface implementation for local file systems. */ @@ -828,6 +828,8 @@ static DECLCALLBACK(int) shclTransferIfaceLocalObjWrite(PSHCLTXPROVIDERCTX pCtx, */ PSHCLTXPROVIDERIFACE ShClTransferProviderLocalQueryInterface(PSHCLTXPROVIDER pProvider) { + RT_ZERO(pProvider->Interface); + pProvider->Interface.pfnRootListRead = shclTransferIfaceLocalRootListRead; pProvider->Interface.pfnListOpen = shclTransferIfaceLocalListOpen; pProvider->Interface.pfnListClose = shclTransferIfaceLocalListClose; @@ -840,4 +842,3 @@ PSHCLTXPROVIDERIFACE ShClTransferProviderLocalQueryInterface(PSHCLTXPROVIDER pPr return &pProvider->Interface; } - From 75fa8360c9ad9ce4d4ba1e93d531a5971c746fc9 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 15:04:45 +0000 Subject: [PATCH 070/176] =?UTF-8?q?IntNet/R3:=20Added=20VBOX=5FWITH=5FINTN?= =?UTF-8?q?ET=5FSERVICE=5FIN=5FR3=5FLOCALIPC=5FTESTCASES=20so=20that=20tho?= =?UTF-8?q?se=20testcases=20can=20also=20be=20run=20on=20Darwin=20(where?= =?UTF-8?q?=20XPC=20is=20being=20used=20by=20default=20instead=20of=20loca?= =?UTF-8?q?l=20IPC)=20[bulld=20fix].=20=E2=80=8Bbugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174802 --- src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp | 6 +++--- src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp b/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp index 47f0a86ed948..9af735fc934a 100644 --- a/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp +++ b/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxIntNetSwitch.cpp 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxIntNetSwitch.cpp 114962 2026-08-10 15:04:45Z andreas.loeffler@oracle.com $ */ /** @file * Internal networking - Wrapper for the R0 network service. * @@ -1431,7 +1431,7 @@ static DECLCALLBACK(int) intnetR3LocalIpcPokeThread(RTTHREAD hThread, void *pvUs rc = VINF_SUCCESS; uint32_t const cThreads = ASMAtomicDecU32(&pDevExt->cThreads); Assert(cThreads < cMaxThreads); - RT_NOREF(cThreads); + RT_NOREF(cThreads, cMaxThreads); return rc; } @@ -1572,7 +1572,7 @@ static DECLCALLBACK(int) intnetR3LocalIpcSessionThread(RTTHREAD hThread, void *p intnetR3SessionDestroy(pSession); uint32_t const cThreads = ASMAtomicDecU32(&pDevExt->cThreads); Assert(cThreads < cMaxThreads); - RT_NOREF(cThreads); + RT_NOREF(cThreads, cMaxThreads); return VINF_SUCCESS; } diff --git a/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp b/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp index 9a9b0f1387bd..07f287f8bb67 100644 --- a/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp +++ b/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVBoxIntNetR3Switch.cpp 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ +/* $Id: tstVBoxIntNetR3Switch.cpp 114962 2026-08-10 15:04:45Z andreas.loeffler@oracle.com $ */ /** @file * tstVBoxIntNetR3Switch - Self-contained testcase for R3 IntNet/IntNetSwitch communication. * @@ -383,6 +383,7 @@ static void svcStop(TSTSWITCHSVC *pSvc) if (pSvc->hProcess != NIL_RTPROCESS) { RTPROCSTATUS Status; + RT_ZERO(Status); int rc = VERR_PROCESS_RUNNING; uint64_t const msStart = RTTimeMilliTS(); while ( rc == VERR_PROCESS_RUNNING @@ -1152,6 +1153,7 @@ static void tstServerPreOpenIdleTimeout(TSTSWITCHSVC *pSvc) rc = RTLocalIpcSessionFlush(hSession); INTNETR3IPCREPLYHDR ReplyHdr; + RT_ZERO(ReplyHdr); if (RT_SUCCESS(rc)) rc = RTLocalIpcSessionRead(hSession, &ReplyHdr, sizeof(ReplyHdr), NULL /*pcbRead*/); if (RT_SUCCESS(rc)) From f514049f76093460adbd82d01eb70200a634ea23 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 15:18:23 +0000 Subject: [PATCH 071/176] =?UTF-8?q?IntNet/R3:=20Added=20VBOX=5FWITH=5FINTN?= =?UTF-8?q?ET=5FSERVICE=5FIN=5FR3=5FLOCALIPC=5FTESTCASES=20so=20that=20tho?= =?UTF-8?q?se=20testcases=20can=20also=20be=20run=20on=20Darwin=20(where?= =?UTF-8?q?=20XPC=20is=20being=20used=20by=20default=20instead=20of=20loca?= =?UTF-8?q?l=20IPC)=20[bulld=20fix].=20=E2=80=8Bbugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174804 --- src/VBox/NetworkServices/NetLib/IntNetIf.cpp | 3 ++- src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/VBox/NetworkServices/NetLib/IntNetIf.cpp b/src/VBox/NetworkServices/NetLib/IntNetIf.cpp index cd57abd3cd59..5d9d273b0e1d 100644 --- a/src/VBox/NetworkServices/NetLib/IntNetIf.cpp +++ b/src/VBox/NetworkServices/NetLib/IntNetIf.cpp @@ -1,4 +1,4 @@ -/* $Id: IntNetIf.cpp 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ +/* $Id: IntNetIf.cpp 114964 2026-08-10 15:18:23Z andreas.loeffler@oracle.com $ */ /** @file * IntNetIfCtx - Abstract API implementing an IntNet connection using the R0 support driver or some R3 IPC variant. */ @@ -979,6 +979,7 @@ static void intnetR3IfClose(PINTNETIFCTXINT pThis) #else AssertRC(rc); #endif + RT_NOREF(rc); } } diff --git a/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp index a160290eceb0..4d3f5b5f0d34 100644 --- a/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp +++ b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVBoxNatGuestSide.cpp 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ +/* $Id: tstVBoxNatGuestSide.cpp 114964 2026-08-10 15:18:23Z andreas.loeffler@oracle.com $ */ /** @file * tstVBoxNatGuestSide - Guest-side NAT over Ring-3 IntNet testcase. */ @@ -920,6 +920,7 @@ static void tstServiceStop(PTSTSWITCHSERVICE pService) if (pService->hProcess != NIL_RTPROCESS) { RTPROCSTATUS Status; + RT_ZERO(Status); int rc = VERR_PROCESS_RUNNING; uint64_t const msStart = RTTimeMilliTS(); while (rc == VERR_PROCESS_RUNNING && RTTimeMilliTS() - msStart < RT_MS_5SEC) From 736bdb3ccdc651d66f24fddc65f9b0b7ffec008e Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 15:39:37 +0000 Subject: [PATCH 072/176] =?UTF-8?q?IntNet/R3:=20Added=20VBOX=5FWITH=5FINTN?= =?UTF-8?q?ET=5FSERVICE=5FIN=5FR3=5FLOCALIPC=5FTESTCASES=20so=20that=20tho?= =?UTF-8?q?se=20testcases=20can=20also=20be=20run=20on=20Darwin=20(where?= =?UTF-8?q?=20XPC=20is=20being=20used=20by=20default=20instead=20of=20loca?= =?UTF-8?q?l=20IPC)=20[bulld=20fix].=20=E2=80=8Bbugref:11149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174805 --- src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp b/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp index c1fc3503f739..e15c55fcaf9a 100644 --- a/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp +++ b/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetSlirpNATTest.cpp 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxNetSlirpNATTest.cpp 114965 2026-08-10 15:39:37Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetSlirpNAT - Wrapper for guest-side tests. */ @@ -189,7 +189,7 @@ vboxNetSlirpNATTestTimerNew(SlirpTimerCb pfnCallback, void *pvCallback, void *pv { PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; AssertPtrReturn(pThis, NULL); - AssertPtrReturn(pfnCallback, NULL); + AssertReturn(pfnCallback != NULL, NULL); PVBOXNETSLIRPNATTESTTIMER pTimer = (PVBOXNETSLIRPNATTESTTIMER)RTMemAllocZ(sizeof(*pTimer)); if (pTimer) From 8c6eb1a5952da38728b3a73d143a50512137bc92 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 15:41:13 +0000 Subject: [PATCH 073/176] Shared Clipboard/HostService: Mark VBOX_SHCL_HOST_FN_SET_HEADLESS as being deprecated. bugref:4697 svn:sync-xref-src-repo-rev: r174806 --- include/VBox/HostServices/VBoxClipboardSvc.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/VBox/HostServices/VBoxClipboardSvc.h b/include/VBox/HostServices/VBoxClipboardSvc.h index 2bd26cd905b7..13a901fc8bdb 100644 --- a/include/VBox/HostServices/VBoxClipboardSvc.h +++ b/include/VBox/HostServices/VBoxClipboardSvc.h @@ -94,8 +94,11 @@ * Operates on the VBOX_SHCL_TRANSFERS_XXX defines. * @since 6.1 */ #define VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE 2 -/** Deprecated for > 7.2: Run headless on the host, i.e. do not touch the host clipboard. - * Kept for compatibility with older Guest Additions and legacy service clients. */ +/** Runs headless on the host, i.e. does not touch the host clipboard. + * + * @deprecated For versions after 7.2. Retained only for compatibility with + * legacy host service clients; do not use in new code. + */ #define VBOX_SHCL_HOST_FN_SET_HEADLESS 3 /** Reports cancellation of the current operation to the guest. @@ -1249,4 +1252,3 @@ typedef struct _VBoxShClParmNegotiateChunkSize #pragma pack() #endif /* !VBOX_INCLUDED_HostServices_VBoxClipboardSvc_h */ - From 4d045d8298d32df94b150249f8f446d5f72eaa5a Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 16:15:33 +0000 Subject: [PATCH 074/176] Shared Clipboard/Host Service: Validate and fix host data reads. bugref:4697 svn:sync-xref-src-repo-rev: r174807 --- .../VBoxSharedClipboardSvc-client.cpp | 24 ++- .../testcase/tstClipboardServiceHost.cpp | 199 +++++++++++++++++- 2 files changed, 219 insertions(+), 4 deletions(-) diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp index d8d6b86d11e0..dd4335177102 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-client.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-client.cpp 114967 2026-08-10 16:15:33Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Client/session and message queue handling. */ @@ -970,6 +970,9 @@ int shClSvcClientMsgReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCM * * @returns VBox status code. * @retval VINF_BUFFER_OVERFLOW if the guest supplied a smaller buffer than needed in order to read the host clipboard data. + * @retval VERR_INVALID_PARAMETER if the requested format is invalid. + * @retval VERR_ACCESS_DENIED if the guest requests file transfer data without + * having file transfers enabled and negotiated. * @param pClient Client that wants to read host clipboard data. * @param cParms Number of HGCM parameters supplied in \a paParms. * @param paParms Array of HGCM parameters. @@ -1041,6 +1044,23 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA } Assert(iParm == cParms); + if (!ShClFormatIsValid(uFormat)) + { + LogRelMax2(16, ("Shared Clipboard: Rejecting host clipboard data request with invalid format %#x\n", uFormat)); + return VERR_INVALID_PARAMETER; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if ( uFormat == VBOX_SHCL_FMT_URI_LIST + && shClSvcHandleFormats(true /* fHostToGuest */, pClient, uFormat) != uFormat) +#else + if (uFormat == VBOX_SHCL_FMT_URI_LIST) +#endif + { + LogRelMax2(16, ("Shared Clipboard: Rejecting guest file transfer data request without enabled and negotiated transfers\n")); + return VERR_ACCESS_DENIED; + } + /* * For some reason we need to do this (makes absolutely no sense to bird). */ @@ -1099,7 +1119,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA HGCMSvcSetU32(&paParms[3], cbActual); /* If the data to return exceeds the buffer the guest supplies, tell it (and let it try again). */ - if (cbActual >= cbData) + if (cbActual > cbData) rc = VINF_BUFFER_OVERFLOW; } diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp index 2fda14eec094..254925e35ce3 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardServiceHost.cpp 114650 2026-07-08 09:14:39Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardServiceHost.cpp 114967 2026-08-10 16:15:33Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard host service test case. */ @@ -66,7 +66,23 @@ static int tstShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUC static int tstShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } static int tstShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } static int tstShClBackendReportFormatsToGuest(PSHCLBACKEND, PSHCLCLIENT, uint32_t) { AssertFailed(); return VINF_SUCCESS; } -static int tstShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } +static const void *g_pvBackendReadData = NULL; +static uint32_t g_cbBackendReadData = 0; +static SHCLFORMAT g_uBackendReadFormat = VBOX_SHCL_FMT_NONE; +static uint32_t g_cBackendReadDataCalls = 0; +static int tstShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT uFormat, + void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + g_cBackendReadDataCalls++; + AssertPtrReturn(g_pvBackendReadData, VERR_WRONG_ORDER); + AssertReturn(uFormat == g_uBackendReadFormat, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + + *pcbActual = g_cbBackendReadData; + if (g_cbBackendReadData <= cbData) + memcpy(pvData, g_pvBackendReadData, g_cbBackendReadData); + return VINF_SUCCESS; +} static int tstShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } static SHCLCLIENT g_Client; @@ -218,6 +234,26 @@ static int setupTable(VBOXHGCMSVCFNTABLE *pTable) return pTable->pfnRegisterExtension(pTable->pvService, tstHgcmMockSvcDispatcher, NULL); } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Issues a host clipboard data read as a guest. */ +static int testHostDataReadCall(VBOXHGCMSVCFNTABLE *pTable, SHCLFORMAT uFormat, + void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + VBOXHGCMSVCPARM aParms[VBOX_SHCL_CPARMS_DATA_READ]; + HGCMSvcSetU32(&aParms[0], uFormat); + HGCMSvcSetPv(&aParms[1], pvData, cbData); + HGCMSvcSetU32(&aParms[2], 0); + + VBOXHGCMCALLHANDLE_TYPEDEF Call; + Call.rc = VERR_IPE_UNINITIALIZED_STATUS; + pTable->pfnCall(NULL, &Call, 1 /* clientId */, &g_Client, + VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aParms), aParms, 0); + if (pcbActual) + *pcbActual = aParms[2].u.uint32; + return Call.rc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + static void testSetMode(void) { struct VBOXHGCMSVCPARM parms[2]; @@ -342,6 +378,163 @@ static void testSetTransferKeyParms(VBOXHGCMSVCPARM aParms[], SHCLSESSIONID idSe HGCMSvcSetU64(&aParms[1], uGeneration); } +/** Tests short and exactly-sized host clipboard data reads. */ +static void testHostDataReadBufferSizing(void) +{ + VBOXHGCMSVCFNTABLE table; + VBOXHGCMSVCPARM parms[1]; + VBOXHGCMSVCPARM aReadParms[VBOX_SHCL_CPARMS_DATA_READ]; + VBOXHGCMCALLHANDLE_TYPEDEF call; + static uint8_t s_abData[_4K + 17]; + uint8_t abShort[_4K]; + uint8_t abExact[sizeof(s_abData)]; + + RTTestISub("Testing host data read buffer sizing"); + int rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + + HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, RT_ELEMENTS(parms), parms); + RTTESTI_CHECK_RC_OK(rc); + + RT_ZERO(g_Client); + rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + + for (uint32_t off = 0; off < sizeof(s_abData); off++) + s_abData[off] = (uint8_t)off; + g_pvBackendReadData = s_abData; + g_cbBackendReadData = sizeof(s_abData); + g_uBackendReadFormat = VBOX_SHCL_FMT_UNICODETEXT; + g_cBackendReadDataCalls = 0; + + HGCMSvcSetU32(&aReadParms[0], g_uBackendReadFormat); + HGCMSvcSetPv(&aReadParms[1], abShort, sizeof(abShort)); + HGCMSvcSetU32(&aReadParms[2], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, + VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aReadParms), aReadParms, 0); + RTTESTI_CHECK_RC(call.rc, VINF_BUFFER_OVERFLOW); + RTTESTI_CHECK(aReadParms[2].u.uint32 == sizeof(s_abData)); + + HGCMSvcSetPv(&aReadParms[1], abExact, sizeof(abExact)); + HGCMSvcSetU32(&aReadParms[2], 0); + call.rc = VERR_IPE_UNINITIALIZED_STATUS; + table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, + VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aReadParms), aReadParms, 0); + RTTESTI_CHECK_RC_OK(call.rc); + RTTESTI_CHECK(aReadParms[2].u.uint32 == sizeof(s_abData)); + RTTESTI_CHECK(memcmp(abExact, s_abData, sizeof(s_abData)) == 0); + RTTESTI_CHECK(g_cBackendReadDataCalls == 2); + + g_pvBackendReadData = NULL; + g_cbBackendReadData = 0; + g_uBackendReadFormat = VBOX_SHCL_FMT_NONE; + g_cBackendReadDataCalls = 0; + + rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + RTTESTI_CHECK_RC_OK(rc); + rc = table.pfnUnload(NULL); + RTTESTI_CHECK_RC_OK(rc); +} + +/** Tests validation and feature gating for host clipboard data reads. */ +static void testHostDataReadValidation(void) +{ + VBOXHGCMSVCFNTABLE table; + VBOXHGCMSVCPARM Parm; + static const char s_szUriList[] = "file:///private/host-file.txt\r\n"; + char szData[sizeof(s_szUriList)]; + + RTTestISub("Testing host data read validation"); + int rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_MODE_BIDIRECTIONAL); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, &Parm); + RTTESTI_CHECK_RC_OK(rc); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_NONE); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC_OK(rc); + + RT_ZERO(g_Client); + rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + + g_pvBackendReadData = s_szUriList; + g_cbBackendReadData = sizeof(s_szUriList); + g_uBackendReadFormat = VBOX_SHCL_FMT_UNICODETEXT; + g_cBackendReadDataCalls = 0; + + g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_NONE; + uint32_t cbActual = 0; + rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData), &cbActual); + RTTESTI_CHECK_RC_OK(rc); + RTTESTI_CHECK(cbActual == sizeof(s_szUriList)); + RTTESTI_CHECK(memcmp(szData, s_szUriList, sizeof(s_szUriList)) == 0); + RTTESTI_CHECK(g_cBackendReadDataCalls == 1); + + g_uBackendReadFormat = VBOX_SHCL_FMT_URI_LIST; + g_cBackendReadDataCalls = 0; + g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; + rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_URI_LIST, szData, sizeof(szData), &cbActual); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(g_cBackendReadDataCalls == 0); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC_OK(rc); + + static const uint64_t s_afMissingFeatures[] = + { + VBOX_SHCL_GF_NONE, + VBOX_SHCL_GF_0_CONTEXT_ID, + VBOX_SHCL_GF_0_TRANSFERS + }; + for (size_t i = 0; i < RT_ELEMENTS(s_afMissingFeatures); i++) + { + g_Client.State.fGuestFeatures0 = s_afMissingFeatures[i]; + rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_URI_LIST, szData, sizeof(szData), &cbActual); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(g_cBackendReadDataCalls == 0); + } + + g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; + rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_URI_LIST, szData, sizeof(szData), &cbActual); + RTTESTI_CHECK_RC_OK(rc); + RTTESTI_CHECK(cbActual == sizeof(s_szUriList)); + RTTESTI_CHECK(memcmp(szData, s_szUriList, sizeof(s_szUriList)) == 0); + RTTESTI_CHECK(g_cBackendReadDataCalls == 1); + + rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_URI_LIST | VBOX_SHCL_FMT_UNICODETEXT, + szData, sizeof(szData), &cbActual); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + RTTESTI_CHECK(g_cBackendReadDataCalls == 1); + + rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_NONE, szData, sizeof(szData), &cbActual); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + RTTESTI_CHECK(g_cBackendReadDataCalls == 1); + + rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_VALID_MASK + 1, szData, sizeof(szData), &cbActual); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + RTTESTI_CHECK(g_cBackendReadDataCalls == 1); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_NONE); + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC_OK(rc); + + g_pvBackendReadData = NULL; + g_cbBackendReadData = 0; + g_uBackendReadFormat = VBOX_SHCL_FMT_NONE; + g_cBackendReadDataCalls = 0; + + rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); + RTTESTI_CHECK_RC_OK(rc); + rc = table.pfnUnload(NULL); + RTTESTI_CHECK_RC_OK(rc); +} + /** * Tests transfer format filtering for disabled or unsupported transfers. */ @@ -1013,6 +1206,8 @@ static void testHostCall(void) testTransferFormatFiltering(); testTransferGuestFeatures(); testTransferHostCancelError(); + testHostDataReadBufferSizing(); + testHostDataReadValidation(); #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ testSetHeadless(); testHeadlessBackendConnect(); From d84cde34404d2ea872f8cb91f4318fde5948eecb Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 16:16:50 +0000 Subject: [PATCH 075/176] Shared Clipboard/VBoxGuestLib: Route transfer status by its transfer context. bugref:4697 svn:sync-xref-src-repo-rev: r174808 --- .../VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp | 22 +- .../testcase/tstClipboardMockHGCM.cpp | 290 +++++++++++++++++- 2 files changed, 306 insertions(+), 6 deletions(-) diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp index 2cf64083f875..1e681cb3edc5 100644 --- a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxGuestR3LibClipboard.cpp 114839 2026-07-31 13:09:02Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxGuestR3LibClipboard.cpp 114968 2026-08-10 16:16:50Z andreas.loeffler@oracle.com $ */ /** @file * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared Clipboard. */ @@ -1027,7 +1027,9 @@ static int vbglR3ClipboardTransferSendStatusEx(PVBGLR3SHCLCMDCTX pCtx, uint64_t * * @returns VBox status code. * @param pCtx Shared Clipboard command context to use for the connection. - * @param pTransfer Transfer of report to reply to. + * @param pTransfer Transfer to report status for. Optional when + * replying to a context for which no local + * transfer exists. * @param uStatus Tranfer status to reply. * @param rcTransfer Result code (rc) to reply. */ @@ -1035,9 +1037,21 @@ VBGLR3DECL(int) VbglR3ClipboardTransferSendStatus(PVBGLR3SHCLCMDCTX pCtx, PSHCLT SHCLTRANSFERSTATUS uStatus, int rcTransfer) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); - RT_NOREF(pTransfer); /* Currently not used (yet). */ - int rc = vbglR3ClipboardTransferSendStatusEx(pCtx, pCtx->idContext, uStatus, rcTransfer); + uint64_t idContext = pCtx->idContext; + if (pTransfer) + { + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + AssertReturn(ShClTransferIdIsValid(idTransfer), VERR_INVALID_PARAMETER); + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + AssertReturn(idSession != 0 && idSession != NIL_SHCLSESSIONID, VERR_INVALID_PARAMETER); + SHCLEVENTID const idEvent = VBOX_SHCL_CONTEXTID_GET_SESSION(idContext) == idSession + && VBOX_SHCL_CONTEXTID_GET_TRANSFER(idContext) == idTransfer + ? VBOX_SHCL_CONTEXTID_GET_EVENT(idContext) : 0; + idContext = VBOX_SHCL_CONTEXTID_MAKE(idSession, idTransfer, idEvent); + } + + int rc = vbglR3ClipboardTransferSendStatusEx(pCtx, idContext, uStatus, rcTransfer); LogFlowFuncLeaveRC(rc); return rc; diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp index e8c0fa27f25d..b53d45530dab 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardMockHGCM.cpp 114425 2026-06-18 08:30:00Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardMockHGCM.cpp 114968 2026-08-10 16:16:50Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard host service test case. */ @@ -32,6 +32,10 @@ #include #include #include +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include +# include "VBoxSharedClipboardSvc-transfers.h" +#endif #if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) # include # include @@ -240,6 +244,285 @@ static void testSetTransferMode(void) rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); RTTESTI_CHECK_RC(rc, VINF_SUCCESS); } + +/** + * Verifies that transfer status replies use the transfer ID of the supplied + * transfer rather than a stale transfer ID in the command context. Also + * verifies that callers without a local transfer can use the command context + * unchanged. + */ +static void testTransferStatusContextRouting(void) +{ + RTTestISub("Testing transfer status context routing"); + + PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst(); + SHCLTRANSFERID const idTargetTransfer = 42; + SHCLTRANSFERID const idAmbientTransfer = 43; + + VBGLR3SHCLCMDCTX CmdCtx; + RT_ZERO(CmdCtx); + SHCLTRANSFERCTX GuestTransferCtx; + RT_ZERO(GuestTransferCtx); + SHCLTRANSFERCTX StaleGuestTransferCtx; + RT_ZERO(StaleGuestTransferCtx); + + HGCMCLIENTID const idNextClient = pSvc->uNextClientId; + bool fConnected = false; + bool fGuestCtxInit = false; + bool fGuestRegistered = false; + bool fStaleGuestCtxInit = false; + bool fStaleGuestRegistered = false; + PSHCLCLIENT pClient = NULL; + PSHCLTRANSFER pGuestTransfer = NULL; + PSHCLTRANSFER pStaleGuestTransfer = NULL; + PSHCLEVENT pTargetEvent = NULL; + PSHCLEVENT pAmbientEvent = NULL; + int rc = VINF_SUCCESS; + + do + { + /* Avoid requiring an X11 display just to exercise HGCM routing. */ + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, true); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, &Parm); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + rc = tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_BIDIRECTIONAL); + if (RT_FAILURE(rc)) + break; + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + rc = VbglR3ClipboardConnectEx(&CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + fConnected = true; + + RTTESTI_CHECK_MSG_BREAK(CmdCtx.idClient < RT_ELEMENTS(pSvc->aHgcmClient), + ("Client ID %RU32 is out of range\n", CmdCtx.idClient)); + PTSTHGCMMOCKCLIENT const pMockClient = &pSvc->aHgcmClient[CmdCtx.idClient]; + RTTESTI_CHECK_MSG_BREAK(TstHgcmMockSvcWaitForConnect(pSvc) == pMockClient, + ("Unexpected mock client connected for ID %RU32\n", CmdCtx.idClient)); + pClient = (PSHCLCLIENT)pMockClient->pvClient; + RTTESTI_CHECK_MSG_BREAK(pClient != NULL, ("Missing service client for ID %RU32\n", CmdCtx.idClient)); + + PSHCLTRANSFER pHostTarget = NULL; + rc = ShClTransferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, NULL, &pHostTarget); + if (RT_SUCCESS(rc)) + rc = ShClTransferCtxRegisterById(&pClient->Transfers.Ctx, pHostTarget, idTargetTransfer); + if (RT_FAILURE(rc)) + ShClTransferDestroy(pHostTarget); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + rc = ShClTransferInit(pHostTarget); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + PSHCLTRANSFER pHostAmbient = NULL; + rc = ShClTransferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, NULL, &pHostAmbient); + if (RT_SUCCESS(rc)) + rc = ShClTransferCtxRegisterById(&pClient->Transfers.Ctx, pHostAmbient, idAmbientTransfer); + if (RT_FAILURE(rc)) + ShClTransferDestroy(pHostAmbient); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + rc = ShClTransferInit(pHostAmbient); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + rc = ShClTransferCtxInit(&GuestTransferCtx); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + fGuestCtxInit = true; + + rc = ShClTransferCtxBeginSession(&GuestTransferCtx, pClient->State.uSessionID); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + rc = ShClTransferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, NULL, &pGuestTransfer); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + rc = ShClTransferCtxRegisterById(&GuestTransferCtx, pGuestTransfer, idTargetTransfer); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + fGuestRegistered = true; + + rc = ShClTransferCtxInit(&StaleGuestTransferCtx); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + fStaleGuestCtxInit = true; + + SHCLSESSIONID const idStaleSession = pClient->State.uSessionID != 1 ? 1 : 2; + rc = ShClTransferCtxBeginSession(&StaleGuestTransferCtx, idStaleSession); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + rc = ShClTransferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, NULL, &pStaleGuestTransfer); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + rc = ShClTransferCtxRegisterById(&StaleGuestTransferCtx, pStaleGuestTransfer, idTargetTransfer); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + fStaleGuestRegistered = true; + + /* Both transfers deliberately have the same valid event ID. */ + pHostTarget->Events.idNextEvent = 1234; + pHostAmbient->Events.idNextEvent = pHostTarget->Events.idNextEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pHostTarget->Events, &pTargetEvent); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + rc = ShClEventSourceGenerateAndRegisterEvent(&pHostAmbient->Events, &pAmbientEvent); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + RTTESTI_CHECK_MSG_BREAK(pTargetEvent->idEvent == pAmbientEvent->idEvent, + ("Expected matching event IDs, got %RU32 and %RU32\n", + pTargetEvent->idEvent, pAmbientEvent->idEvent)); + + /* + * The command context deliberately refers to a different live + * transfer. The supplied transfer must select the target transfer. + */ + CmdCtx.idContext = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, + idAmbientTransfer, pAmbientEvent->idEvent); + rc = VbglR3ClipboardTransferSendStatus(&CmdCtx, pGuestTransfer, + SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + /* A mismatched ambient transfer also makes its event stale. */ + int const rcTargetEvent = ShClEventWait(pTargetEvent, 0 /* msTimeout */, NULL /* ppPayload */); + int const rcAmbientEvent = ShClEventWait(pAmbientEvent, 0 /* msTimeout */, NULL /* ppPayload */); + RTTESTI_CHECK_RC(rcTargetEvent, VERR_TIMEOUT); + RTTESTI_CHECK_RC(rcAmbientEvent, VERR_TIMEOUT); + if (rcTargetEvent != VERR_TIMEOUT || rcAmbientEvent != VERR_TIMEOUT) + break; + + /* A stale transfer's session must not be replaced by the ambient session. */ + rc = VbglR3ClipboardTransferSendStatus(&CmdCtx, pStaleGuestTransfer, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); + if (rc != VERR_INVALID_CONTEXT) + break; + RTTESTI_CHECK(ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idTargetTransfer) != NULL); + RTTESTI_CHECK(ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idAmbientTransfer) != NULL); + RTTESTI_CHECK_RC(ShClEventWait(pTargetEvent, 0 /* msTimeout */, NULL /* ppPayload */), VERR_TIMEOUT); + RTTESTI_CHECK_RC(ShClEventWait(pAmbientEvent, 0 /* msTimeout */, NULL /* ppPayload */), VERR_TIMEOUT); + + RTTESTI_CHECK(ShClEventRelease(pTargetEvent) == 0); + pTargetEvent = NULL; + RTTESTI_CHECK(ShClEventRelease(pAmbientEvent) == 0); + pAmbientEvent = NULL; + + rc = VbglR3ClipboardTransferSendStatus(&CmdCtx, pGuestTransfer, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + + PSHCLTRANSFER const pTargetAfter + = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idTargetTransfer); + PSHCLTRANSFER const pAmbientAfter + = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idAmbientTransfer); + RTTESTI_CHECK_MSG(pTargetAfter == NULL, + ("Target transfer %RU16 was not canceled\n", idTargetTransfer)); + RTTESTI_CHECK_MSG(pAmbientAfter != NULL, + ("Ambient transfer %RU16 was canceled instead\n", idAmbientTransfer)); + if (pTargetAfter != NULL || pAmbientAfter == NULL) + break; + + /* No local transfer is available on some error paths: keep the context unchanged. */ + rc = VbglR3ClipboardTransferSendStatus(&CmdCtx, NULL, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + RTTESTI_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + break; + RTTESTI_CHECK_MSG(ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idAmbientTransfer) == NULL, + ("Context transfer %RU16 was not canceled\n", idAmbientTransfer)); + } while (0); + + if (pClient) + { + if (pTargetEvent) + { + RTTESTI_CHECK(ShClEventRelease(pTargetEvent) == 0); + pTargetEvent = NULL; + } + if (pAmbientEvent) + { + RTTESTI_CHECK(ShClEventRelease(pAmbientEvent) == 0); + pAmbientEvent = NULL; + } + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idTargetTransfer); + if (pTransfer) + ShClSvcTransferDestroy(pClient, pTransfer); + pTransfer = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idAmbientTransfer); + if (pTransfer) + ShClSvcTransferDestroy(pClient, pTransfer); + } + if (fGuestCtxInit) + { + if (!fGuestRegistered && pGuestTransfer) + ShClTransferDestroy(pGuestTransfer); + ShClTransferCtxDestroy(&GuestTransferCtx); + } + else if (pGuestTransfer) + ShClTransferDestroy(pGuestTransfer); + + if (fStaleGuestCtxInit) + { + if (!fStaleGuestRegistered && pStaleGuestTransfer) + ShClTransferDestroy(pStaleGuestTransfer); + ShClTransferCtxDestroy(&StaleGuestTransferCtx); + } + else if (pStaleGuestTransfer) + ShClTransferDestroy(pStaleGuestTransfer); + + if (fConnected) + { + int const rcDisconnect = VbglR3ClipboardDisconnectEx(&CmdCtx); + RTTESTI_CHECK_RC_OK(rcDisconnect); + if (RT_SUCCESS(rcDisconnect)) + { + /* This mock has four monotonically allocated client slots. Reclaim + the fully disconnected slot so this test does not consume one. */ + RTTESTI_CHECK(pSvc->uNextClientId == idNextClient + 1); + pSvc->uNextClientId = idNextClient; + } + } + + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_NONE); + RTTESTI_CHECK_RC_OK(TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm)); + RTTESTI_CHECK_RC_OK(tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_OFF)); + HGCMSvcSetU32(&Parm, false); + RTTESTI_CHECK_RC_OK(TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, &Parm)); +} #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ static void testGuestSimple(void) @@ -913,6 +1196,10 @@ int main() TstHGCMUtilsTaskInit(pTask); pTask->pvUser = &g_TstCtx.Task; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + testTransferStatusContextRouting(); +#endif + /* * Run the tests. The tstOne() and testGuestSimple() tests rely on an X11 * display on Unix systems so skip them if not applicable. @@ -941,4 +1228,3 @@ int main() */ return RTTestSummaryAndDestroy(g_hTest); } - From e62c20b0bd3b439344f02250941d9987c73f14ef Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 16:19:05 +0000 Subject: [PATCH 076/176] Shared Clipboard/darwin: Read host-to-guest file URLs in readFromPasteboard(). bugref:4697 svn:sync-xref-src-repo-rev: r174809 --- .../src-client/darwin/darwin-pasteboard.cpp | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp index 9813d517d16b..14fa00c477b6 100644 --- a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp +++ b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp @@ -1,4 +1,4 @@ -/* $Id: darwin-pasteboard.cpp 114863 2026-08-06 10:19:52Z andreas.loeffler@oracle.com $ */ +/* $Id: darwin-pasteboard.cpp 114969 2026-08-10 16:19:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host implementation. */ @@ -466,6 +466,30 @@ DECLHIDDEN(int) readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, { Log(("readFromPasteboard: fFormat = %02X\n", fFormat)); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fFormat & VBOX_SHCL_FMT_URI_LIST) + { + char *pszRoots = NULL; + size_t cbRoots = 0; + int vrc = readFileURLsFromPasteboard(pPasteboard, &pszRoots, &cbRoots); + if (RT_SUCCESS(vrc)) + { + if (cbRoots > UINT32_MAX) + vrc = VERR_TOO_MUCH_DATA; + else + { + *pcbActual = (uint32_t)cbRoots; + if (cbRoots <= cb) + memcpy(pv, pszRoots, cbRoots); + else + vrc = VINF_BUFFER_OVERFLOW; + } + } + RTStrFree(pszRoots); + return vrc; + } +#endif + /* Make sure all is in sync */ PasteboardSynchronize(pPasteboard); From e2d3a10df397cc141d4e7996743d692b9b380e0b Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 16:20:56 +0000 Subject: [PATCH 077/176] Shared Clipboard/X11: Harden initialized host-to-guest transfer binding. bugref:4697 svn:sync-xref-src-repo-rev: r174810 --- .../x11/VBoxClient/clipboard-x11.cpp | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index c7f15ffc827b..17089e07b062 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 114958 2026-08-10 14:15:56Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-x11.cpp 114970 2026-08-10 16:20:56Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -332,14 +332,27 @@ static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALL PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); - if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE - && vbclX11TransferStateMatches(pCtx, pTransfer)) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) { + if (!vbclX11TransferStateMatches(pCtx, pTransfer)) + { + LogRel2(("Shared Clipboard: Rejecting unbound initialized transfer %RU16/%RU64\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); + int rc2 = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pTransfer, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + if (RT_FAILURE(rc2)) + LogRel(("Shared Clipboard: Canceling unbound transfer %RU16/%RU64 failed with %Rrc\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer), rc2)); + return; + } + /* The remote provider rejects root-list reads until ShClTransferInit() * has changed the transfer state to INITIALIZED. Registering the HTTP * transfer from pfnOnInitialize therefore races ahead of that state * transition and leaves URI-list conversion waiting forever. */ - int rc = ShClTransferRootListRead(pTransfer); + int rc = ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx); + if (RT_SUCCESS(rc)) + rc = ShClTransferRootListRead(pTransfer); if (RT_SUCCESS(rc)) { if (ShClTransferRootsCount(pTransfer)) @@ -363,9 +376,8 @@ static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALL * @copydoc SHCLTRANSFERCALLBACKS::pfnOnRegistered * * This binds pending transfer preparation to the newly registered transfer's - * exact ID and generation, and starts the HTTP server if necessary. The - * transfer itself is added to the HTTP server after its roots have been read by - * the initialized callback. + * exact ID and generation. The HTTP server is started and the transfer is + * added after its roots have been read by the initialized callback. * * @thread Clipboard main thread. */ @@ -403,9 +415,6 @@ static DECLCALLBACK(void) vbclX11OnTransferRegisteredCallback(PSHCLTRANSFERCALLB pX11TransferState->uTransferGeneration, ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); - int rc2 = ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx); - if (RT_FAILURE(rc2)) - LogRel(("Shared Clipboard: Registering HTTP transfer failed: %Rrc\n", rc2)); } LogFlowFuncLeave(); From 7d92aa064e3a5c16425000436df64f62ce6815c3 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 17:29:14 +0000 Subject: [PATCH 078/176] Shared Clipboard: Removed VBOX_SHCL_HOST_FN_SET_HEADLESS and related functionality; documented deprecation. bugref:4697 svn:sync-xref-src-repo-rev: r174811 --- include/VBox/GuestHost/SharedClipboard-x11.h | 14 +- include/VBox/HostServices/VBoxClipboardExt.h | 2 + include/VBox/HostServices/VBoxClipboardSvc.h | 7 +- .../HostServices/VBoxSharedClipboardSvc.h | 6 +- .../x11/VBoxClient/clipboard-x11.cpp | 4 +- .../SharedClipboard/clipboard-x11.cpp | 134 +++++++---------- .../testcase/tstClipboardGH-X11.cpp | 30 +--- .../testcase/tstClipboardGH-X11Smoke.cpp | 5 +- .../testcase/tstClipboardHttpServer.cpp | 4 +- .../VBoxSharedClipboardSvc-backend.cpp | 17 +-- .../VBoxSharedClipboardSvc-host.cpp | 17 +-- .../VBoxSharedClipboardSvc-internal.h | 6 +- .../testcase/tstClipboardMockHGCM.cpp | 121 ++++++++++------ .../testcase/tstClipboardServiceHost.cpp | 135 +++--------------- .../testcase/tstClipboardServiceImpl.cpp | 5 +- .../HostServices/testcase/TstHGCMMock.cpp | 8 +- .../src-client/ConsoleImplConfigCommon.cpp | 13 +- src/VBox/Main/src-client/GuestShClSvcExt.cpp | 5 +- .../darwin/ClipboardBackendDarwin.cpp | 6 +- .../src-client/linux/ClipboardBackendX11.cpp | 12 +- .../src-client/win/ClipboardBackendWin.cpp | 6 +- 21 files changed, 192 insertions(+), 365 deletions(-) diff --git a/include/VBox/GuestHost/SharedClipboard-x11.h b/include/VBox/GuestHost/SharedClipboard-x11.h index 3b1826db4edb..fbfffc999c3b 100644 --- a/include/VBox/GuestHost/SharedClipboard-x11.h +++ b/include/VBox/GuestHost/SharedClipboard-x11.h @@ -148,14 +148,6 @@ typedef struct SHCLX11CTX PSHCLCONTEXT pFrontend; /** Our callback table to use. */ SHCLCALLBACKS Callbacks; - /** - * Are we running in headless mode? - * - * This is a special situation for running on UNIX-y environments, where an - * X server could not be available when running on a server without any - * desktop environment available, for example. - */ - bool fHeadless; /** The X Toolkit application context structure. */ XtAppContext pAppContext; /** We have a separate thread to wait for window and clipboard events. */ @@ -188,9 +180,9 @@ typedef struct SHCLX11CTX SHCLCACHE Cache; /** When we wish the clipboard to exit, we have to wake up the event * loop. We do this by writing into a pipe. This end of the pipe is - * the end that another thread can write to. */ + * the end that another thread can write to. -1 if not created. */ int wakeupPipeWrite; - /** The reader end of the pipe. */ + /** The reader end of the pipe. -1 if not created. */ int wakeupPipeRead; /** A pointer to the XFixesSelectSelectionInput function. */ void (*fixesSelectInput)(Display *, Window, Atom, unsigned long); @@ -306,7 +298,7 @@ typedef SHCLX11RESPONSE *PSHCLX11RESPONSE; /** @name Shared Clipboard APIs for X11. * @{ */ -int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent, bool fHeadless); +int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent); int ShClX11Term(PSHCLX11CTX pCtx); int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool grab); int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab); diff --git a/include/VBox/HostServices/VBoxClipboardExt.h b/include/VBox/HostServices/VBoxClipboardExt.h index 13f2728c73f3..db6ac3553936 100644 --- a/include/VBox/HostServices/VBoxClipboardExt.h +++ b/include/VBox/HostServices/VBoxClipboardExt.h @@ -101,6 +101,8 @@ typedef struct _SHCLEXTPARMS PSHCLBACKEND pBackend; VBOXHGCMSVCFNTABLE *pTable; PSHCLCLIENTCMDCTX pCmdCtx; + /** Whether the backend connection should avoid the host clipboard. + * Legacy field retained for binary compatibility. Must be set to false. */ bool fHeadless; } ReadWriteData; /** Sets a read / write callback. */ diff --git a/include/VBox/HostServices/VBoxClipboardSvc.h b/include/VBox/HostServices/VBoxClipboardSvc.h index 13a901fc8bdb..78c3532e4074 100644 --- a/include/VBox/HostServices/VBoxClipboardSvc.h +++ b/include/VBox/HostServices/VBoxClipboardSvc.h @@ -94,11 +94,8 @@ * Operates on the VBOX_SHCL_TRANSFERS_XXX defines. * @since 6.1 */ #define VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE 2 -/** Runs headless on the host, i.e. does not touch the host clipboard. - * - * @deprecated For versions after 7.2. Retained only for compatibility with - * legacy host service clients; do not use in new code. - */ +/** Legacy request to run headless on the host without touching the host clipboard. + * No longer implemented; the function number must not be reused. */ #define VBOX_SHCL_HOST_FN_SET_HEADLESS 3 /** Reports cancellation of the current operation to the guest. diff --git a/include/VBox/HostServices/VBoxSharedClipboardSvc.h b/include/VBox/HostServices/VBoxSharedClipboardSvc.h index c18a2ac47b2a..aa8798710525 100644 --- a/include/VBox/HostServices/VBoxSharedClipboardSvc.h +++ b/include/VBox/HostServices/VBoxSharedClipboardSvc.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc.h 114831 2026-07-31 10:10:53Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.h 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - header file for shared clipboard data transfer * interfaces and platform-dependent backend functionality. @@ -385,7 +385,6 @@ int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLF int ShClSvcReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats); PSHCLBACKEND ShClSvcGetBackend(void); uint32_t ShClSvcGetMode(void); -bool ShClSvcGetHeadless(void); bool ShClSvcLock(void); void ShClSvcUnlock(void); /** @} */ @@ -440,9 +439,8 @@ void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks); * @returns VBox status code. * @param pBackend Shared Clipboard backend to connect to. * @param pClient Shared Clipboard client context. - * @param fHeadless Whether this is a headless connection or not. */ -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless); +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); /** * Called when a HGCM client disconnects. diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index 17089e07b062..a09795a428a0 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 114970 2026-08-10 16:20:56Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-x11.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -601,7 +601,7 @@ int VBClX11ClipboardInit(void) Callbacks.pfnReportFormats = vbclX11ReportFormatsCallback; Callbacks.pfnOnRequestDataFromSource = vbclX11OnRequestDataFromSourceCallback; - rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx, false /* fHeadless */); + rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx); if (RT_SUCCESS(rc)) { rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */); diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp index 0c0cd3f6bff1..8d3fd36852bf 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -1274,22 +1274,6 @@ static void clipUninitInternal(PSHCLX11CTX pCtx) LogFlowFuncLeaveRC(VINF_SUCCESS); } -/** - * Helper function for public X11 Shared Clipboard APIs to know whether we're running in headless mode or not. - * - * Headless mode either could mean that we don't want to touch the X11 clipboard, or that X simply isn't installed and/or - * isn't available (e.g. running on a pure server installation w/o any desktop environment). - * - * Goal here is to make the X11 API transparent for the caller whether X is available or not. - * - * @returns \c true if running in headless mode, or \c false if not. - * @param pCtx The X11 clipboard context to use. - */ -DECLINLINE(bool) shClX11HeadlessIsEnabled(PSHCLX11CTX pCtx) -{ - return pCtx->fHeadless; -} - /** * Sets the callback table, internal version. * @@ -1324,15 +1308,16 @@ void ShClX11SetCallbacks(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks) * @param pCtx The clipboard context to initialize. * @param pCallbacks Callbacks to use (copied, not used directly). Optional. * @param pParent Parent context to use. - * @param fHeadless Whether the code runs in a headless environment or not. */ -int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent, bool fHeadless) +int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); LogFlowFunc(("pCtx=%p\n", pCtx)); RT_BZERO(pCtx, sizeof(SHCLX11CTX)); + pCtx->wakeupPipeRead = -1; + pCtx->wakeupPipeWrite = -1; /* Init clipboard cache. */ ShClCacheInit(&pCtx->Cache); @@ -1340,8 +1325,7 @@ int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParen /* Install given callbacks. */ shClX11SetCallbacksInternal(pCtx, pCallbacks); - pCtx->fHeadless = fHeadless; - pCtx->pFrontend = pParent; + pCtx->pFrontend = pParent; #ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY pCtx->fXtBusy = false; @@ -1350,24 +1334,21 @@ int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParen int rc = VINF_SUCCESS; - LogRel(("Shared Clipboard: Initializing X11 clipboard (%s mode)\n", fHeadless ? "headless" : "regular")); + LogRel(("Shared Clipboard: Initializing X11 clipboard\n")); - if (!pCtx->fHeadless) - { #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - rc = ShClTransferHttpServerInit(&pCtx->HttpCtx.HttpServer); + rc = ShClTransferHttpServerInit(&pCtx->HttpCtx.HttpServer); #endif #ifdef TESTCASE + if (RT_SUCCESS(rc)) + { + /** @todo The testcases currently do not utilize the threading code. So init stuff here. */ + rc = clipInitInternal(pCtx); if (RT_SUCCESS(rc)) - { - /** @todo The testcases currently do not utilize the threading code. So init stuff here. */ - rc = clipInitInternal(pCtx); - if (RT_SUCCESS(rc)) - rc = clipRegisterContext(pCtx); - } -#endif + rc = clipRegisterContext(pCtx); } +#endif if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Initializing X11 clipboard failed with %Rrc\n", rc)); @@ -1394,8 +1375,7 @@ int ShClX11Term(PSHCLX11CTX pCtx) int rc = VINF_SUCCESS; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - if (!shClX11HeadlessIsEnabled(pCtx)) - rc = ShClTransferHttpServerDestroy(&pCtx->HttpCtx.HttpServer); + rc = ShClTransferHttpServerDestroy(&pCtx->HttpCtx.HttpServer); #endif #ifdef TESTCASE @@ -1404,18 +1384,31 @@ int ShClX11Term(PSHCLX11CTX pCtx) clipUninitInternal(pCtx); #endif - if (!shClX11HeadlessIsEnabled(pCtx)) - { - /* We set this to NULL when the event thread exits. It really should - * have exited at this point, when we are about to unload the code from - * memory. */ - AssertStmt(pCtx->pWidget == NULL, rc = VERR_WRONG_ORDER); - } + /* We set this to NULL when the event thread exits. It really should + * have exited at this point, when we are about to unload the code from + * memory. */ + AssertStmt(pCtx->pWidget == NULL, rc = VERR_WRONG_ORDER); return rc; } #ifndef TESTCASE +/** Closes the wakeup pipe created for the X11 event thread. */ +static void clipThreadCloseWakeupPipe(PSHCLX11CTX pCtx) +{ + if (pCtx->wakeupPipeRead >= 0) + { + close(pCtx->wakeupPipeRead); + pCtx->wakeupPipeRead = -1; + } + + if (pCtx->wakeupPipeWrite >= 0) + { + close(pCtx->wakeupPipeWrite); + pCtx->wakeupPipeWrite = -1; + } +} + /** * Starts our own Xt even thread for handling Shared Clipboard messages, extended version. * @@ -1428,9 +1421,6 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; - pCtx->fGrabClipboardOnStart = fGrab; clipResetX11Formats(pCtx); @@ -1452,7 +1442,10 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) rc = VINF_SUCCESS; } else + { rc = RTErrConvertFromErrno(errno); + clipThreadCloseWakeupPipe(pCtx); + } } else rc = RTErrConvertFromErrno(errno); @@ -1464,7 +1457,12 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) rc = RTThreadCreate(&pCtx->Thread, clipThreadMain, pCtx, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, pszName); if (RT_SUCCESS(rc)) - rc = RTThreadUserWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */); + { + /* The backend must not release pCtx while the worker initializes it. */ + rc = RTThreadUserWait(pCtx->Thread, RT_INDEFINITE_WAIT); + } + else + clipThreadCloseWakeupPipe(pCtx); if (RT_FAILURE(rc)) { @@ -1475,7 +1473,16 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) { if (!pCtx->fThreadStarted) { - LogRel(("Shared Clipboard: X11 event thread reported an error while starting\n")); + /* The worker signalled its terminal startup failure; reap it before returning. */ + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int rc2 = RTThreadWait(pCtx->Thread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rc2)) + rc = RT_FAILURE(rcThread) ? rcThread : VERR_GENERAL_FAILURE; + else + rc = rc2; + + clipThreadCloseWakeupPipe(pCtx); + LogRel(("Shared Clipboard: X11 event thread reported an error while starting: %Rrc\n", rc)); } else LogRel2(("Shared Clipboard: X11 event thread started\n")); @@ -1511,9 +1518,6 @@ int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool fGrab) */ int ShClX11ThreadStop(PSHCLX11CTX pCtx) { - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; - LogRel2(("Shared Clipboard: Signalling the X11 event thread to stop\n")); /* Write to the "stop" pipe. */ @@ -1532,17 +1536,7 @@ int ShClX11ThreadStop(PSHCLX11CTX pCtx) rc = rcThread; if (RT_SUCCESS(rc)) { - if (pCtx->wakeupPipeRead != 0) - { - close(pCtx->wakeupPipeRead); - pCtx->wakeupPipeRead = 0; - } - - if (pCtx->wakeupPipeWrite != 0) - { - close(pCtx->wakeupPipeWrite); - pCtx->wakeupPipeWrite = 0; - } + clipThreadCloseWakeupPipe(pCtx); } if (RT_SUCCESS(rc)) @@ -2204,9 +2198,6 @@ int ShClX11ReportFormatsToX11AsyncEx(PSHCLX11CTX pCtx, SHCLFORMATS fFormats, SHC else AssertReturn(!pvCache && !cbCache, VERR_INVALID_PARAMETER); - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; - int rc; PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)RTMemAllocZ(sizeof(SHCLX11REQUEST)); @@ -2982,9 +2973,6 @@ int ShClX11ReadDataFromX11Async(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, uint32_t cbMa { AssertPtrReturn(pEvent, VERR_INVALID_POINTER); - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; - int rc = VINF_SUCCESS; PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)RTMemAllocZ(sizeof(SHCLX11REQUEST)); @@ -3094,12 +3082,6 @@ int ShClX11ReadDataFromX11Ex(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RT AssertPtrReturn(ppvBuf, VERR_INVALID_POINTER); AssertPtrReturn(pcbBuf, VERR_INVALID_POINTER); - if (shClX11HeadlessIsEnabled(pCtx)) - { - *pcbBuf = 0; - return VINF_SUCCESS; - } - PSHCLX11RESPONSE pResp; int rc = shClX11ReadDataFromX11Internal(pCtx, pEventSource, msTimeout, uFmt, UINT32_MAX, &pResp); if (RT_SUCCESS(rc)) @@ -3140,13 +3122,6 @@ int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMS AssertReturn(cbBuf, VERR_INVALID_PARAMETER); /* pcbRead is optional. */ - if (shClX11HeadlessIsEnabled(pCtx)) - { - if (pcbRead) - *pcbRead = 0; - return VINF_SUCCESS; - } - PSHCLX11RESPONSE pResp; int rc = shClX11ReadDataFromX11Internal(pCtx, pEventSource, msTimeout, uFmt, cbBuf, &pResp); if (RT_SUCCESS(rc)) @@ -3182,9 +3157,6 @@ int ShClX11WriteDataToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS uFmts, const void * AssertReturn(cbBuf, VERR_INVALID_PARAMETER); /* pEvent not used yet. */ RT_NOREF(pEvent); - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; - int rc = ShClCacheSetMultiple(&pCtx->Cache, uFmts, pvBuf, cbBuf); if (RT_SUCCESS(rc)) { diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp index 6bccf94ad528..057586a73047 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardGH-X11.cpp 114867 2026-08-06 15:19:51Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardGH-X11.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard guest/host X11 code test cases. */ @@ -775,7 +775,7 @@ int main() Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDestCallback; SHCLX11CTX X11Ctx; - rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */); + rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */); AssertRCReturn(rc, RTEXITCODE_FAILURE); uint32_t cbActual = 0; @@ -1028,32 +1028,6 @@ int main() ShClX11Term(&X11Ctx); - /* - * Headless clipboard tests - */ - RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, true /* fHeadless */)); - - /* Read from X11 */ - RTTestSub(hTest, "reading from X11, headless clipboard"); - - /* Simple test */ - tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", sizeof("") * 2); - tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world", sizeof("hello world"), 8); - rc = ShClX11ReadDataFromX11(&X11Ctx, &g_EventSource, g_msTimeout, VBOX_SHCL_FMT_UNICODETEXT, abBuf, sizeof(abBuf), &cbActual); - RTTEST_CHECK_MSG(hTest, cbActual == 0, (hTest, "expected 0 but got %RU32\n", cbActual)); - RTTEST_CHECK_MSG(hTest, rc == VINF_SUCCESS, (hTest, "expected VINF_SUCCESS but got %Rrc\n", rc)); - - /* Read from VBox */ - RTTestSub(hTest, "reading from VBox, headless clipboard"); - - /* Simple test */ - tstClipEmptyVBox(&X11Ctx, VERR_WRONG_ORDER); - tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8); - tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world", - sizeof("hello world") * 2); - tstNoSelectionOwnership(&X11Ctx, "reading from VBox, headless clipboard"); - - RTTEST_CHECK_RC_OK(hTest, ShClX11Term(&X11Ctx)); ShClEventSourceTerm(&g_EventSource); return RTTestSummaryAndDestroy(hTest); diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp index 89742260f6fb..dfb4b5706ae6 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardGH-X11Smoke.cpp 114412 2026-06-17 21:20:59Z knut.osmundsen@oracle.com $ */ +/* $Id: tstClipboardGH-X11Smoke.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard guest/host X11 code smoke tests. */ @@ -89,7 +89,7 @@ int main() Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDest; SHCLX11CTX X11Ctx; - RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */)); + RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */)); RTTEST_CHECK_RC_OK(hTest, ShClX11ThreadStart(&X11Ctx, false /* fGrab */)); /* Give the clipboard time to synchronise. */ @@ -100,4 +100,3 @@ int main() return RTTestSummaryAndDestroy(hTest); } - diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp index 0ebf106e8494..5856c62d3dab 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardHttpServer.cpp 114865 2026-08-06 10:27:20Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardHttpServer.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard HTTP server test case. */ @@ -314,7 +314,7 @@ static void tstManual(RTTEST hTest, PSHCLTRANSFERCTX pTransferCtx, PSHCLHTTPSERV SHCLCALLBACKS Callbacks; RT_ZERO(Callbacks); - RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */)); + RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */)); RTTEST_CHECK_RC_OK(hTest, ShClX11ThreadStart(&X11Ctx, false /* fGrab */)); RTTEST_CHECK_RC_OK(hTest, ShClEventSourceInit(&EventSource, 0)); RTTEST_CHECK_RC_OK(hTest, ShClX11WriteDataToX11(&X11Ctx, &EventSource, RT_MS_30SEC, diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp index ac23fb647293..f2f1069be207 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-backend.cpp 114425 2026-06-18 08:30:00Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-backend.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Backend and extension bridge handling. */ @@ -63,17 +63,6 @@ PSHCLBACKEND ShClSvcGetBackend(void) } -/** - * Getter for headless setting. Also needed by testcase. - * - * @returns Whether service currently running in headless mode or not. - */ -bool ShClSvcGetHeadless(void) -{ - return g_fHeadless; -} - - static int shClSvcBackendHostCallback(uint32_t u32Function, PSHCLEXTPARMS pvParms, uint32_t cbParms) { LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); @@ -109,9 +98,7 @@ int shClSvcBackendConnect(PSHCLCLIENT pClient) parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); parms.u.ReadWriteData.pClient = pClient; - parms.u.ReadWriteData.fHeadless = ShClSvcGetHeadless(); - - /* The backend in Main calls: ShClBackendConnect(&g_ShClBackend, pClient, ShClSvcGetHeadless())); */ + /* The backend in Main calls: ShClBackendConnect(&g_ShClBackend, pClient); */ return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT, &parms, sizeof(parms)); } diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp index 511c693acbb5..824b715f4214 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-host.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-host.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host-controlled service handling. */ @@ -137,21 +137,8 @@ DECLCALLBACK(int) shClSvcHostCall(void *, uint32_t u32Function, uint32_t cParms, } case VBOX_SHCL_HOST_FN_SET_HEADLESS: - { - if (cParms != 1) - rc = VERR_INVALID_PARAMETER; - else - { - uint32_t uHeadless; - rc = HGCMSvcGetU32(&paParms[0], &uHeadless); - if (RT_SUCCESS(rc)) - { - g_fHeadless = RT_BOOL(uHeadless); - LogRel(("Shared Clipboard: Service running in %s mode\n", g_fHeadless ? "headless" : "normal")); - } - } + rc = VERR_NOT_IMPLEMENTED; break; - } case VBOX_SHCL_HOST_FN_CANCEL: { diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h index 36ebd24ad3c0..5430fe086d83 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-internal.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-internal.h 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal service instance state. */ @@ -57,8 +57,6 @@ typedef struct SHCLSERVICE /** Next non-zero service session ID to assign to a client. */ SHCLSESSIONID idNextSession; #endif - /** Whether the service runs in headless mode. */ - bool fHeadless; /** Service extension state. */ SHCLEXTSTATE ExtState; /** Connected HGCM clients keyed by client ID. */ @@ -76,7 +74,6 @@ typedef struct SHCLSERVICE , fTransferMode(VBOX_SHCL_TRANSFER_MODE_F_NONE) , idNextSession(1) #endif - , fHeadless(false) , fHostFeatures0(VBOX_SHCL_HF_0_CONTEXT_ID #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS | VBOX_SHCL_HF_0_TRANSFERS @@ -105,7 +102,6 @@ extern SHCLSERVICE g_ShClSvc; # define g_fTransferMode (g_ShClSvc.fTransferMode) # define g_idNextSession (g_ShClSvc.idNextSession) #endif -#define g_fHeadless (g_ShClSvc.fHeadless) #define g_ExtState (g_ShClSvc.ExtState) #define g_mapClients (g_ShClSvc.mapClients) #define g_listClientsDeferred (g_ShClSvc.listClientsDeferred) diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp index b53d45530dab..426363d9ba89 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardMockHGCM.cpp 114968 2026-08-10 16:16:50Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardMockHGCM.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard host service test case. */ @@ -30,6 +30,7 @@ * Header Files * *********************************************************************************************************************************/ #include +#include #include #include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS @@ -50,6 +51,7 @@ #include #include +#include #include #include #include @@ -64,6 +66,23 @@ *********************************************************************************************************************************/ static RTTEST g_hTest; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +DECLCALLBACK(int) tstHgcmMockSvcDispatcher(void *pvExtension, uint32_t u32Function, + void *pvParms, uint32_t cbParms); + +/** Test dispatcher which keeps protocol-only transfer tests independent of a display server. */ +static DECLCALLBACK(int) tstClipboardTransferStatusContextDispatcher(void *pvExtension, uint32_t u32Function, + void *pvParms, uint32_t cbParms) +{ + if ( u32Function == VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT + || u32Function == VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT + || u32Function == VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT + || u32Function == VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC) + return VINF_SUCCESS; + return tstHgcmMockSvcDispatcher(pvExtension, u32Function, pvParms, cbParms); +} +#endif + /********************************************************************************************************************************* * Shared Clipboard testing * @@ -272,6 +291,7 @@ static void testTransferStatusContextRouting(void) bool fGuestRegistered = false; bool fStaleGuestCtxInit = false; bool fStaleGuestRegistered = false; + bool fTestDispatcher = false; PSHCLCLIENT pClient = NULL; PSHCLTRANSFER pGuestTransfer = NULL; PSHCLTRANSFER pStaleGuestTransfer = NULL; @@ -281,13 +301,13 @@ static void testTransferStatusContextRouting(void) do { - /* Avoid requiring an X11 display just to exercise HGCM routing. */ VBOXHGCMSVCPARM Parm; - HGCMSvcSetU32(&Parm, true); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, &Parm); + rc = pSvc->fnTable.pfnRegisterExtension(pSvc->fnTable.pvService, + tstClipboardTransferStatusContextDispatcher, NULL); RTTESTI_CHECK_RC_OK(rc); if (RT_FAILURE(rc)) break; + fTestDispatcher = true; rc = tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_BIDIRECTIONAL); if (RT_FAILURE(rc)) @@ -516,15 +536,62 @@ static void testTransferStatusContextRouting(void) } } + if (fTestDispatcher) + { + int const rcRestore = pSvc->fnTable.pfnRegisterExtension(pSvc->fnTable.pvService, + tstHgcmMockSvcDispatcher, NULL); + RTTESTI_CHECK_RC_OK(rcRestore); + } + VBOXHGCMSVCPARM Parm; HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_NONE); RTTESTI_CHECK_RC_OK(TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm)); RTTESTI_CHECK_RC_OK(tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_OFF)); - HGCMSvcSetU32(&Parm, false); - RTTESTI_CHECK_RC_OK(TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, &Parm)); } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ +#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) +/** Verifies that the X11 backend fails cleanly when no display is available. */ +static void testX11UnavailableBackend(void) +{ + RTTestISub("Testing X11 backend without a display"); + + PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst(); + void *pvClient = RTMemAllocZ(pSvc->fnTable.cbClient); + RTTESTI_CHECK_MSG_RETV(pvClient, ("Failed to allocate a service client\n")); + + const char *pszDisplay = RTEnvGet("DISPLAY"); + char *pszDisplaySaved = pszDisplay ? RTStrDup(pszDisplay) : NULL; + if (pszDisplay && !pszDisplaySaved) + { + RTTESTI_CHECK_MSG(false, ("Failed to save DISPLAY before the test\n")); + RTMemFree(pvClient); + return; + } + + int rc = RTEnvUnset("DISPLAY"); + if (RT_FAILURE(rc)) + { + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTStrFree(pszDisplaySaved); + RTMemFree(pvClient); + return; + } + + rc = pSvc->fnTable.pfnConnect(pSvc->fnTable.pvService, UINT32_C(100), pvClient, + VMMDEV_REQUESTOR_USR_NOT_GIVEN /* fRequestor */, false /* fRestoring */); + + int const rcRestore = pszDisplaySaved ? RTEnvSet("DISPLAY", pszDisplaySaved) : VINF_SUCCESS; + RTStrFree(pszDisplaySaved); + RTTESTI_CHECK_RC(rcRestore, VINF_SUCCESS); + + RTTESTI_CHECK_RC(rc, VERR_NOT_SUPPORTED); + if (RT_SUCCESS(rc)) + RTTESTI_CHECK_RC_OK(pSvc->fnTable.pfnDisconnect(pSvc->fnTable.pvService, UINT32_C(100), pvClient)); + RTMemFree(pvClient); +} +#endif + static void testGuestSimple(void) { RTTestISub("Testing client (guest) API - Simple"); @@ -648,44 +715,12 @@ static PRTUTF16 tstGenerateUtf16StringA(uint32_t uCch) } #endif /* RT_OS_WINDOWS) || RT_OS_OS2 */ -static void testSetHeadless(void) -{ - RTTestISub("Testing HOST_FN_SET_HEADLESS"); - - PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); - - VBOXHGCMSVCPARM parms[2]; - HGCMSvcSetU32(&parms[0], false); - int rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - bool fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless)); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 0, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - HGCMSvcSetU64(&parms[0], 99); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - HGCMSvcSetU32(&parms[0], true); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); - HGCMSvcSetU32(&parms[0], 99); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); -} - static void testHostCall(void) { tstOperationModes(); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS testSetTransferMode(); #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - testSetHeadless(); } @@ -747,7 +782,7 @@ static void tstTestReadFromHost_MockInit(PTSTUSERMOCK pUsrMock, const char *pszN pUsrMock->pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); AssertPtrReturnVoid(pUsrMock->pCtx); - ShClX11Init(&pUsrMock->X11Ctx, &Callbacks, pUsrMock->pCtx, false); + ShClX11Init(&pUsrMock->X11Ctx, &Callbacks, pUsrMock->pCtx); ShClX11ThreadStartEx(&pUsrMock->X11Ctx, pszName, false /* fGrab */); /* Give the clipboard time to synchronise. */ RTThreadSleep(500); @@ -1200,9 +1235,13 @@ int main() testTransferStatusContextRouting(); #endif +#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) + testX11UnavailableBackend(); +#endif + /* - * Run the tests. The tstOne() and testGuestSimple() tests rely on an X11 - * display on Unix systems so skip them if not applicable. + * Run the remaining guest/backend tests only when an X11 display is + * available on Unix systems. */ #if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) VBGHDISPLAYSERVERTYPE const enmDisplayType = VBGHDisplayServerTypeDetect(); diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp index 254925e35ce3..e73a2fcba3d6 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardServiceHost.cpp 114967 2026-08-10 16:15:33Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardServiceHost.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard host service test case. */ @@ -52,16 +52,9 @@ extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *pt * Note: These host service tests exercise the HGCM service layer, * not the platform clipboard backends! */ -static bool g_fBackendConnectCalled = false; -static bool g_fBackendConnectHeadless = false; static int tstShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) { pBackend->pHelpers = pTable->pHelpers; return VINF_SUCCESS; } static void tstShClBackendDestroy(PSHCLBACKEND) { } -static int tstShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool fHeadless) -{ - g_fBackendConnectCalled = true; - g_fBackendConnectHeadless = fHeadless; - return VINF_SUCCESS; -} +static int tstShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } static int tstShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } static int tstShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } static int tstShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } @@ -196,8 +189,7 @@ DECLCALLBACK(int) tstHgcmMockSvcDispatcher(void *pvExtension, uint32_t u32Functi { PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - bool fHeadless = pParms->u.ReadWriteData.fHeadless; - rc = tstShClBackendConnect(pBackend, pClient, fHeadless); + rc = tstShClBackendConnect(pBackend, pClient); break; } @@ -305,6 +297,22 @@ static void testSetMode(void) RTTESTI_CHECK_RC(rc, VINF_SUCCESS); } +/** Tests that the removed legacy headless host function remains reserved. */ +static void testReservedHostFunction(void) +{ + VBOXHGCMSVCFNTABLE table; + + RTTestISub("Testing removed VBOX_SHCL_HOST_FN_SET_HEADLESS"); + int rc = setupTable(&table); + RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); + + rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 0, NULL); + RTTESTI_CHECK_RC(rc, VERR_NOT_IMPLEMENTED); + + rc = table.pfnUnload(NULL); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +} + #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS static void testSetTransferMode(void) { @@ -1096,111 +1104,10 @@ static void testGuestDataWriteRejectsInvalidFormats(void) RTTESTI_CHECK_RC(rc, VINF_SUCCESS); } -/** - * Tests VBOX_SHCL_HOST_FN_SET_HEADLESS host calls. - * - * This host function is deprecated but intentionally kept for older Guest - * Additions and compatibility with the historic service protocol. - */ -static void testSetHeadless(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - bool fHeadless; - int rc; - - RTTestISub("Testing HOST_FN_SET_HEADLESS"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - /* Reset global variable which doesn't reset itself. */ - HGCMSvcSetU32(&parms[0], false); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless)); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 0, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - HGCMSvcSetU64(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - HGCMSvcSetU32(&parms[0], true); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); - HGCMSvcSetU32(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -static void testHeadlessBackendConnect(void) -{ - struct VBOXHGCMSVCPARM parms[1]; - VBOXHGCMSVCFNTABLE table; - int rc; - - RTTestISub("Testing HOST_FN_SET_HEADLESS backend connect propagation"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], false); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - RT_ZERO(g_Client); - g_fBackendConnectCalled = false; - g_fBackendConnectHeadless = true; - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(g_fBackendConnectCalled); - RTTESTI_CHECK_MSG(g_fBackendConnectHeadless == false, - ("g_fBackendConnectHeadless=%RTbool\n", g_fBackendConnectHeadless)); - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - HGCMSvcSetU32(&parms[0], true); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - RT_ZERO(g_Client); - g_fBackendConnectCalled = false; - g_fBackendConnectHeadless = false; - rc = table.pfnConnect(NULL, 2 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(g_fBackendConnectCalled); - RTTESTI_CHECK_MSG(g_fBackendConnectHeadless == true, - ("g_fBackendConnectHeadless=%RTbool\n", g_fBackendConnectHeadless)); - rc = table.pfnDisconnect(NULL, 2 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - HGCMSvcSetU32(&parms[0], false); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - static void testHostCall(void) { testSetMode(); + testReservedHostFunction(); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS testSetTransferMode(); testTransferFormatFiltering(); @@ -1209,8 +1116,6 @@ static void testHostCall(void) testHostDataReadBufferSizing(); testHostDataReadValidation(); #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - testSetHeadless(); - testHeadlessBackendConnect(); testGuestDataSignalExpiredEvent(); testGuestDataSignalRejectsMismatches(); testGuestDataWriteRejectsInvalidFormats(); diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp index f4e43ed4030a..bc40897e4aa1 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardServiceImpl.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: tstClipboardServiceImpl.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard host service implementation (backend) test case. */ @@ -66,7 +66,7 @@ static int setupTable(VBOXHGCMSVCFNTABLE *pTable) int ShClBackendInit(PSHCLBACKEND, VBOXHGCMSVCFNTABLE *) { return VINF_SUCCESS; } void ShClBackendDestroy(PSHCLBACKEND) { } int ShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } -int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; } +int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } int ShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } int ShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } int ShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } @@ -201,4 +201,3 @@ int main(int argc, char *argv[]) */ return RTTestSummaryAndDestroy(hTest); } - diff --git a/src/VBox/HostServices/testcase/TstHGCMMock.cpp b/src/VBox/HostServices/testcase/TstHGCMMock.cpp index d7efd9d2aba4..d06417b0171f 100644 --- a/src/VBox/HostServices/testcase/TstHGCMMock.cpp +++ b/src/VBox/HostServices/testcase/TstHGCMMock.cpp @@ -1,4 +1,4 @@ -/* $Id: TstHGCMMock.cpp 114158 2026-05-20 15:21:01Z andreas.loeffler@oracle.com $ */ +/* $Id: TstHGCMMock.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * TstHGCMMock.cpp - Mocking framework for testing HGCM-based host services. * @@ -290,9 +290,6 @@ static DECLCALLBACK(int) tstHgcmMockSvcCallComplete(VBOXHGCMCALLHANDLE callHandl } #ifdef VBOX_WITH_SHARED_CLIPBOARD -//int tstShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; } -//int tstShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } - DECLCALLBACK(int) tstHgcmMockSvcDispatcher(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms) { @@ -381,8 +378,7 @@ DECLCALLBACK(int) tstHgcmMockSvcDispatcher(void *pvExtension, uint32_t u32Functi { PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - bool fHeadless = pParms->u.ReadWriteData.fHeadless; - rc = ShClBackendConnect(pBackend, pClient, fHeadless); + rc = ShClBackendConnect(pBackend, pClient); break; } diff --git a/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp b/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp index 53a7657fc301..ce6b21d514c4 100644 --- a/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp +++ b/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp @@ -1,4 +1,4 @@ -/* $Id: ConsoleImplConfigCommon.cpp 114532 2026-06-25 11:46:49Z andreas.loeffler@oracle.com $ */ +/* $Id: ConsoleImplConfigCommon.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console COM Class implementation - VM Configuration Bits. * @@ -4001,17 +4001,6 @@ int Console::i_configVmmDev(ComPtr pMachine, BusAssignmentManager *pBu { LogRel(("Shared Clipboard: Service loaded\n")); - /* Let the service avoid touching the host clipboard/X11 for headless frontends. - * This is legacy behavior and needs to be removed. See @bugref{4697}. */ - Bstr bstrSessionName; - hrc = pMachine->COMGETTER(SessionName)(bstrSessionName.asOutParam()); H(); - const bool fHeadless = bstrSessionName.compare(Bstr("headless").raw()) == 0; - VBOXHGCMSVCPARM parmHeadless; - HGCMSvcSetU32(&parmHeadless, fHeadless); - vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, &parmHeadless); - AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set headless mode (%RTbool): vrc=%Rrc\n", - fHeadless, vrc)); - /* Set initial clipboard mode. */ AssertPtrReturn(i_getClipboard(), VERR_INVALID_POINTER); vrc = i_getClipboard()->i_changeMode(enmClipboardMode); diff --git a/src/VBox/Main/src-client/GuestShClSvcExt.cpp b/src/VBox/Main/src-client/GuestShClSvcExt.cpp index 2cecba8cab98..ca8f7e56b8dc 100644 --- a/src/VBox/Main/src-client/GuestShClSvcExt.cpp +++ b/src/VBox/Main/src-client/GuestShClSvcExt.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClSvcExt.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClSvcExt.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard service extension handling for Main. */ @@ -555,8 +555,7 @@ int GuestShCl::i_handleSvcExtBackendConnect(PSHCLEXTPARMS pParms, void *pvParms, { PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - bool fHeadless = pParms->u.ReadWriteData.fHeadless; - vrc = ShClBackendConnect(pBackend, pClient, fHeadless); + vrc = ShClBackendConnect(pBackend, pClient); if (RT_SUCCESS(vrc)) { lock(); diff --git a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp index e9dd1cd7e4ce..73fed0300dca 100644 --- a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp +++ b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendDarwin.cpp 114863 2026-08-06 10:19:52Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendDarwin.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host. */ @@ -304,9 +304,9 @@ void ShClBackendDestroy(PSHCLBACKEND pBackend) RTCritSectDelete(&g_ctx.CritSect); } -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) { - RT_NOREF(pBackend, fHeadless); + RT_NOREF(pBackend); RTCritSectEnter(&g_ctx.CritSect); diff --git a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp index 2ad32d642030..be137d6b8786 100644 --- a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp +++ b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendX11.cpp 114867 2026-08-06 15:19:51Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendX11.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - X11 backend. */ @@ -312,7 +312,7 @@ void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks) * @note On the host, we assume that some other application already owns * the clipboard and leave ownership to X11. */ -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) { int vrc; @@ -330,7 +330,7 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadles vrc = RTCritSectInit(&pCtx->CritSect); if (RT_SUCCESS(vrc)) { - vrc = ShClX11Init(&pCtx->X11, &pBackend->Callbacks, pCtx, fHeadless); + vrc = ShClX11Init(&pCtx->X11, &pBackend->Callbacks, pCtx); if (RT_SUCCESS(vrc)) { pClient->State.pCtx = pCtx; @@ -355,8 +355,7 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadles #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - if (!fHeadless) - vrc = shClSvcX11TransferPreparationStart(pCtx); + vrc = shClSvcX11TransferPreparationStart(pCtx); #endif if (RT_SUCCESS(vrc)) vrc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */); @@ -487,9 +486,6 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR PSHCLCONTEXT pCtx = pClient->State.pCtx; AssertPtrReturn(pCtx, VERR_INVALID_POINTER); - if (pCtx->X11.fHeadless) - return ShClX11ReportFormatsToX11Async(&pCtx->X11, fFormats); - int vrc = RTCritSectEnter(&pCtx->CritSect); if (RT_SUCCESS(vrc)) { diff --git a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp index 42789ce24347..99eae9b6c4ba 100644 --- a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp +++ b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendWin.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendWin.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Win32 host. */ @@ -862,9 +862,9 @@ void ShClBackendDestroy(PSHCLBACKEND pBackend) #endif } -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) { - RT_NOREF(pBackend, fHeadless); + RT_NOREF(pBackend); LogFlowFuncEnter(); From 4219b43e3314f08b7d99b6b6188393bff89a555e Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 17:34:02 +0000 Subject: [PATCH 079/176] Shared Clipboard: Removed VBOX_SHCL_HOST_FN_SET_HEADLESS and related functionality; documented deprecation [build fix]. bugref:4697 svn:sync-xref-src-repo-rev: r174812 --- .../SharedClipboard/testcase/tstClipboardGH-X11.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp index 057586a73047..c258b785da99 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardGH-X11.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardGH-X11.cpp 114972 2026-08-10 17:34:02Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard guest/host X11 code test cases. */ @@ -690,12 +690,6 @@ static void tstStringFromVBoxFailed(RTTEST hTest, PSHCLX11CTX pCtx, const char * XtFree((char *)value); } -static void tstNoSelectionOwnership(PSHCLX11CTX pCtx, const char *pcszTestCtx) -{ - RT_NOREF(pCtx); - RTTESTI_CHECK_MSG(!g_tst_fOwnsSel, ("context: %s\n", pcszTestCtx)); -} - static void tstBadFormatRequestFromHost(RTTEST hTest, PSHCLX11CTX pCtx) { tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world", From d6491785de5fdfde1e128aa6370a876375b20957 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 17:47:03 +0000 Subject: [PATCH 080/176] Shared Clipboard: More headless cruft removed. bugref:4697 svn:sync-xref-src-repo-rev: r174814 --- include/VBox/HostServices/VBoxClipboardExt.h | 4 ++-- include/VBox/HostServices/VBoxClipboardSvc.h | 5 +++-- src/VBox/HostServices/SharedClipboard/Makefile.kmk | 11 +---------- .../testcase/tstClipboardServiceHost.cpp | 6 +++--- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/include/VBox/HostServices/VBoxClipboardExt.h b/include/VBox/HostServices/VBoxClipboardExt.h index db6ac3553936..12cf361481ce 100644 --- a/include/VBox/HostServices/VBoxClipboardExt.h +++ b/include/VBox/HostServices/VBoxClipboardExt.h @@ -101,8 +101,8 @@ typedef struct _SHCLEXTPARMS PSHCLBACKEND pBackend; VBOXHGCMSVCFNTABLE *pTable; PSHCLCLIENTCMDCTX pCmdCtx; - /** Whether the backend connection should avoid the host clipboard. - * Legacy field retained for binary compatibility. Must be set to false. */ + /** Legacy flag indicating that the backend was to avoid host clipboard access. + * @deprecated Ignored; retained for binary compatibility. Must be false. */ bool fHeadless; } ReadWriteData; /** Sets a read / write callback. */ diff --git a/include/VBox/HostServices/VBoxClipboardSvc.h b/include/VBox/HostServices/VBoxClipboardSvc.h index 78c3532e4074..4c225b155c5c 100644 --- a/include/VBox/HostServices/VBoxClipboardSvc.h +++ b/include/VBox/HostServices/VBoxClipboardSvc.h @@ -94,8 +94,9 @@ * Operates on the VBOX_SHCL_TRANSFERS_XXX defines. * @since 6.1 */ #define VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE 2 -/** Legacy request to run headless on the host without touching the host clipboard. - * No longer implemented; the function number must not be reused. */ +/** Legacy request to suppress host clipboard access. + * @deprecated No longer implemented; retained for protocol and source + * compatibility. Function number 3 must not be reused. */ #define VBOX_SHCL_HOST_FN_SET_HEADLESS 3 /** Reports cancellation of the current operation to the guest. diff --git a/src/VBox/HostServices/SharedClipboard/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/Makefile.kmk index 10816e9e919b..958df11c2287 100644 --- a/src/VBox/HostServices/SharedClipboard/Makefile.kmk +++ b/src/VBox/HostServices/SharedClipboard/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114575 2026-06-30 15:58:06Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114974 2026-08-10 17:47:03Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the Shared Clipboard Host Service. # @@ -73,15 +73,6 @@ endif VBoxSharedClipboard_LIBS = \ $(LIB_RUNTIME) -if1of ($(KBUILD_TARGET), linux solaris freebsd) - ifndef VBOX_HEADLESS - VBoxSharedClipboard_LIBPATH = \ - $(VBOX_LIBPATH_X11) - VBoxSharedClipboard_LIBS += \ - Xt \ - X11 - endif -endif VBoxSharedClipboard_LDFLAGS.darwin = \ -framework ApplicationServices -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedClipboard.dylib diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp index e73a2fcba3d6..8baa19c77713 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardServiceHost.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardServiceHost.cpp 114974 2026-08-10 17:47:03Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard host service test case. */ @@ -297,12 +297,12 @@ static void testSetMode(void) RTTESTI_CHECK_RC(rc, VINF_SUCCESS); } -/** Tests that the removed legacy headless host function remains reserved. */ +/** Tests that the legacy headless host function ID remains reserved and unimplemented. */ static void testReservedHostFunction(void) { VBOXHGCMSVCFNTABLE table; - RTTestISub("Testing removed VBOX_SHCL_HOST_FN_SET_HEADLESS"); + RTTestISub("Testing unimplemented VBOX_SHCL_HOST_FN_SET_HEADLESS"); int rc = setupTable(&table); RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); From 9cade8e8407bbb3c3616a3421b5687b73c642e1a Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 18:04:13 +0000 Subject: [PATCH 081/176] Shared Clipboard: Doxygen fixes. bugref:4697 svn:sync-xref-src-repo-rev: r174815 --- src/VBox/Main/src-client/ClipboardTransferImpl.cpp | 7 +------ .../src-client/ClipboardTransferManagerImpl.cpp | 13 ++----------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/VBox/Main/src-client/ClipboardTransferImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferImpl.cpp index e5f5b202e879..e02216c7b952 100644 --- a/src/VBox/Main/src-client/ClipboardTransferImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferImpl.cpp 114975 2026-08-10 18:04:13Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer object. */ @@ -454,11 +454,6 @@ void ClipboardTransfer::FinalRelease() * @param aAction Clipboard transfer action. * @param aItem Clipboard item being transferred. * @param aProgress Progress object for the transfer. - * @param aTransfer Optional Shared Clipboard transfer backing the data - * plane. If @a fOwnTransfer is false, this method - * borrows the transfer for the lifetime of this object. - * @param fOwnTransfer Whether to take ownership of @a aTransfer and - * destroy it during uninitialization. */ HRESULT ClipboardTransfer::init(ULONG aId, ClipboardTransferDirection_T aDirection, diff --git a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp index e212f0fc7351..bb7684ab05d7 100644 --- a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferManagerImpl.cpp 114859 2026-08-05 15:28:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferManagerImpl.cpp 114975 2026-08-10 18:04:13Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer manager object. */ @@ -317,20 +317,11 @@ HRESULT ClipboardTransferManager::getTransfers(ClipboardTransferDirection_T aDir } -/** - * Creates and tracks a Main-owned clipboard transfer. - * - * @todo Defer the owner bridge until the producer has configured the transfer +/* TODO: Defer the owner bridge until the producer has configured the transfer * source. Then register the backing transfer with the active Shared * Clipboard service context and platform backend, record its assigned * session/transfer/generation key, and define rollback, cancellation, * unregistration and lifetime handling. - * - * @returns COM status code. - * @param aDirection Transfer direction. - * @param aSource Clipboard source owning the transfer. - * @param aAction Clipboard transfer action. - * @param aTransfer Where to return the transfer object. */ HRESULT ClipboardTransferManager::create(ClipboardTransferDirection_T aDirection, ClipboardSource_T aSource, From 0b12bc5ff76e387d1f639659c3511374a542c944 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 10 Aug 2026 19:27:36 +0000 Subject: [PATCH 082/176] Shared Clipboard: Doxygen fixes. bugref:4697 svn:sync-xref-src-repo-rev: r174817 --- src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp index bb7684ab05d7..b7ba618892e8 100644 --- a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferManagerImpl.cpp 114975 2026-08-10 18:04:13Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferManagerImpl.cpp 114977 2026-08-10 19:27:36Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer manager object. */ @@ -317,12 +317,6 @@ HRESULT ClipboardTransferManager::getTransfers(ClipboardTransferDirection_T aDir } -/* TODO: Defer the owner bridge until the producer has configured the transfer - * source. Then register the backing transfer with the active Shared - * Clipboard service context and platform backend, record its assigned - * session/transfer/generation key, and define rollback, cancellation, - * unregistration and lifetime handling. - */ HRESULT ClipboardTransferManager::create(ClipboardTransferDirection_T aDirection, ClipboardSource_T aSource, ClipboardAction_T aAction, From 0c65144fd2aa6121db18c082569febca6d44e4e1 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 11 Aug 2026 08:33:42 +0000 Subject: [PATCH 083/176] IPRT/tstRTLocalIpc: Fixed testcase when running on Linux (needed for r174711). svn:sync-xref-src-repo-rev: r174819 --- src/VBox/Runtime/testcase/tstRTLocalIpc.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp index 224523f8de4e..57914adfd917 100644 --- a/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp +++ b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp @@ -1,4 +1,4 @@ -/* $Id: tstRTLocalIpc.cpp 114874 2026-08-06 21:28:04Z andreas.loeffler@oracle.com $ */ +/* $Id: tstRTLocalIpc.cpp 114978 2026-08-11 08:33:42Z andreas.loeffler@oracle.com $ */ /** @file * IPRT Testcase - RTLocalIpc API. */ @@ -210,7 +210,8 @@ static void testRestrictedNamespaceProperties(void) if (RT_SUCCESS(rcUnsafe)) RTTESTI_CHECK_RC(rcUnsafe = RTPathAppend(szFallbackNamespace, sizeof(szFallbackNamespace), ".iprt-localipc"), VINF_SUCCESS); - if (RT_SUCCESS(rcUnsafe)) + /* Linux may select /run/user/ before reaching the home directory fallback. */ + if (RT_SUCCESS(rcUnsafe) && RTDirExists(szFallbackNamespace)) RTTESTI_CHECK_RC(RTDirRemove(szFallbackNamespace), VINF_SUCCESS); RTTESTI_CHECK_RC(RTDirRemove(szFallbackHome), VINF_SUCCESS); From c918ce471b26ab0025bc1408383d7ab2f0f680e2 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Tue, 11 Aug 2026 08:57:17 +0000 Subject: [PATCH 084/176] VBoxManage: nits svn:sync-xref-src-repo-rev: r174820 --- .../VBoxManage/VBoxManageModifyVM.cpp | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp index 96023c1c0fa3..e992dc501026 100644 --- a/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp +++ b/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxManageModifyVM.cpp 114362 2026-06-15 18:31:38Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxManageModifyVM.cpp 114979 2026-08-11 08:57:17Z knut.osmundsen@oracle.com $ */ /** @file * VBoxManage - Implementation of modifyvm command. */ @@ -2285,7 +2285,8 @@ RTEXITCODE handleModifyVM(HandlerArg *a) } break; } - #undef ITERATE_TO_NEXT_TERM +#undef ITERATE_TO_NEXT_TERM + case MODIFYVM_NATALIASMODE: { ComPtr nic; @@ -2548,20 +2549,16 @@ RTEXITCODE handleModifyVM(HandlerArg *a) { bool fEnableUsb = false; if (!RTStrICmp(ValueUnion.psz, "ps2")) - { CHECK_ERROR(sessionMachine, COMSETTER(KeyboardHIDType)(KeyboardHIDType_PS2Keyboard)); - } else if (!RTStrICmp(ValueUnion.psz, "usb")) { CHECK_ERROR(sessionMachine, COMSETTER(KeyboardHIDType)(KeyboardHIDType_USBKeyboard)); - if (SUCCEEDED(hrc)) - fEnableUsb = true; + fEnableUsb = SUCCEEDED(hrc); } else if (!RTStrICmp(ValueUnion.psz, "none")) { CHECK_ERROR(sessionMachine, COMSETTER(KeyboardHIDType)(KeyboardHIDType_None)); - if (SUCCEEDED(hrc)) - fEnableUsb = true; + fEnableUsb = SUCCEEDED(hrc); } else { @@ -2574,7 +2571,8 @@ RTEXITCODE handleModifyVM(HandlerArg *a) ULONG cOhciCtrls = 0; ULONG cXhciCtrls = 0; hrc = sessionMachine->GetUSBControllerCountByType(USBControllerType_OHCI, &cOhciCtrls); - if (SUCCEEDED(hrc)) { + if (SUCCEEDED(hrc)) + { hrc = sessionMachine->GetUSBControllerCountByType(USBControllerType_XHCI, &cXhciCtrls); if ( SUCCEEDED(hrc) && cOhciCtrls + cXhciCtrls == 0) @@ -2597,9 +2595,7 @@ RTEXITCODE handleModifyVM(HandlerArg *a) ASSERT(uart); if (!RTStrICmp(ValueUnion.psz, "disconnected")) - { CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_Disconnected)); - } else if ( !RTStrICmp(ValueUnion.psz, "server") || !RTStrICmp(ValueUnion.psz, "client") || !RTStrICmp(ValueUnion.psz, "tcpserver") @@ -2656,20 +2652,14 @@ RTEXITCODE handleModifyVM(HandlerArg *a) ASSERT(uart); if (!RTStrICmp(ValueUnion.psz, "16450")) - { CHECK_ERROR(uart, COMSETTER(UartType)(UartType_U16450)); - } else if (!RTStrICmp(ValueUnion.psz, "16550A")) - { CHECK_ERROR(uart, COMSETTER(UartType)(UartType_U16550A)); - } else if (!RTStrICmp(ValueUnion.psz, "16750")) - { CHECK_ERROR(uart, COMSETTER(UartType)(UartType_U16750)); - } else - return errorSyntax(ModifyVM::tr("Invalid argument to '%s'"), - GetOptState.pDef->pszLong); + return errorSyntax(ModifyVM::tr("Invalid argument to '%s': %s"), + GetOptState.pDef->pszLong, ValueUnion.psz); break; } From 18e1be4661c8379fbfd543ca562a0e4f87b0b1c7 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 11 Aug 2026 09:04:39 +0000 Subject: [PATCH 085/176] VMM/tstVMREQ: Made it use driverless VM creation by avoiding initializing SUPLib before VMR3Create on supported hosts. Only on non-hardened builds. Should fix VERR_VMX_IN_VMX_ROOT_MODE errors on certain testboxes. svn:sync-xref-src-repo-rev: r174821 --- src/VBox/VMM/testcase/tstVMREQ.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/VBox/VMM/testcase/tstVMREQ.cpp b/src/VBox/VMM/testcase/tstVMREQ.cpp index 30ad15ce5a14..e08d9be100af 100644 --- a/src/VBox/VMM/testcase/tstVMREQ.cpp +++ b/src/VBox/VMM/testcase/tstVMREQ.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVMREQ.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: tstVMREQ.cpp 114980 2026-08-11 09:04:39Z andreas.loeffler@oracle.com $ */ /** @file * VMM Testcase. */ @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -643,15 +644,27 @@ tstVMREQConfigConstructor(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, void *pvUser) extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp) { RT_NOREF1(envp); - RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB); - RTTestCreate(TESTCASE, &g_hTest); +#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || (defined(RT_OS_WINDOWS) && !defined(VBOX_WITH_HARDENING)) + /* VMR3Create initializes SUPLib in driverless mode, so don't initialize it here first. */ + uint32_t const fRtInit = 0; + uint64_t const fVmCreate = VMCREATE_F_DRIVERLESS; +#else + uint32_t const fRtInit = RTR3INIT_FLAGS_TRY_SUPLIB; + uint64_t const fVmCreate = 0; +#endif + int rc = RTR3InitExe(argc, &argv, fRtInit); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + rc = RTTestCreate(TESTCASE, &g_hTest); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("RTTestCreate failed: %Rrc", rc); RTTestSub(g_hTest, "Setup..."); /* * Create empty VM. */ PUVM pUVM; - int rc = VMR3Create(1 /*cCpus*/, NULL, 0 /*fFlags*/, NULL, NULL, tstVMREQConfigConstructor, NULL, NULL, &pUVM); + rc = VMR3Create(1 /*cCpus*/, NULL, fVmCreate, NULL, NULL, tstVMREQConfigConstructor, NULL, NULL, &pUVM); if (RT_SUCCESS(rc)) { /* From 8205068bf2076c60692df6c788c1d46cfe365c1c Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Tue, 11 Aug 2026 09:05:54 +0000 Subject: [PATCH 086/176] VMM/PDMR0: Disabled completely unused code. bugref:10093 bugref:11139 svn:sync-xref-src-repo-rev: r174822 --- src/VBox/VMM/VMMR0/PDMR0Driver.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/VBox/VMM/VMMR0/PDMR0Driver.cpp b/src/VBox/VMM/VMMR0/PDMR0Driver.cpp index deaca4a77ce0..a671075ad342 100644 --- a/src/VBox/VMM/VMMR0/PDMR0Driver.cpp +++ b/src/VBox/VMM/VMMR0/PDMR0Driver.cpp @@ -1,4 +1,4 @@ -/* $Id: PDMR0Driver.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: PDMR0Driver.cpp 114981 2026-08-11 09:05:54Z knut.osmundsen@oracle.com $ */ /** @file * PDM - Pluggable Device and Driver Manager, R0 Driver parts. */ @@ -227,6 +227,7 @@ VMMR0_INT_DECL(int) PDMR0DriverCallReqHandler(PGVM pGVM, PPDMDRIVERCALLREQHANDLE AssertPtrReturn(pReq, VERR_INVALID_POINTER); AssertMsgReturn(pReq->Hdr.cbReq == sizeof(*pReq), ("%#x != %#x\n", pReq->Hdr.cbReq, sizeof(*pReq)), VERR_INVALID_PARAMETER); +#if 0 /** @todo ring-3/0 separation */ PPDMDRVINS pDrvIns = pReq->pDrvInsR0; AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); AssertReturn(pDrvIns->Internal.s.pVMR0 == pGVM, VERR_INVALID_PARAMETER); @@ -235,6 +236,9 @@ VMMR0_INT_DECL(int) PDMR0DriverCallReqHandler(PGVM pGVM, PPDMDRIVERCALLREQHANDLE AssertPtrReturn(pfnReqHandlerR0, VERR_INVALID_POINTER); rc = pfnReqHandlerR0(pDrvIns, pReq->uOperation, pReq->u64Arg); +#else + rc = VERR_NOT_SUPPORTED; +#endif } return rc; } From 1b0aeaf1dfc40854d4eaedb7357815006c42c18e Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 11 Aug 2026 09:27:15 +0000 Subject: [PATCH 087/176] IPRT/semrw-generic: Signal / wakeup blocked readers on a final writer error / timeout. Should also fix tstRTSemRW tests in some cases. svn:sync-xref-src-repo-rev: r174825 --- src/VBox/Runtime/generic/semrw-generic.cpp | 23 +++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/VBox/Runtime/generic/semrw-generic.cpp b/src/VBox/Runtime/generic/semrw-generic.cpp index 703ac5c56bd3..95dba5ce9c27 100644 --- a/src/VBox/Runtime/generic/semrw-generic.cpp +++ b/src/VBox/Runtime/generic/semrw-generic.cpp @@ -1,4 +1,4 @@ -/* $Id: semrw-generic.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: semrw-generic.cpp 114984 2026-08-11 09:27:15Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Read-Write Semaphore, Generic. * @@ -746,9 +746,26 @@ DECL_FORCE_INLINE(int) rtSemRWRequestWrite(RTSEMRW hRWSem, RTMSINTERVAL cMillies */ if (pThis->u32Magic == RTSEMRW_MAGIC) { - RTCritSectEnter(&pThis->CritSect); - /* Adjust this counter, whether we got the critsect or not. */ + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc2, rc2); + + /* Adjust the waiting-writer count. */ + Assert(pThis->cWritesWaiting > 0); pThis->cWritesWaiting--; + + /* + * If this was the final waiting writer, we may have consumed the + * wakeup which kept readers blocked. So signal remaining readers to + * not run into waiting timeouts. + */ + if ( !pThis->cWritesWaiting + && !pThis->cWrites + && !pThis->cReads) + { + rc2 = RTSemEventMultiSignal(pThis->ReadEvent); + AssertMsgRC(rc2, ("Failed to signal readers on rwsem %p, rc=%Rrc\n", hRWSem, rc2)); + pThis->fNeedResetReadEvent = true; + } RTCritSectLeave(&pThis->CritSect); } return rc; From ab208a11212fa6dbe133b1ce99d902d932d90b38 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 11 Aug 2026 09:55:45 +0000 Subject: [PATCH 088/176] vsheriff: Try detect 1450 / ERROR_NO_SYSTEM_RESOURCES on Windows (un)installation. svn:sync-xref-src-repo-rev: r174826 --- .../testmanager/batch/virtual_test_sheriff.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py b/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py index c23f8d70a965..1e2f7c8c47e6 100755 --- a/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py +++ b/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# $Id: virtual_test_sheriff.py 113949 2026-04-17 23:48:48Z knut.osmundsen@oracle.com $ +# $Id: virtual_test_sheriff.py 114985 2026-08-11 09:55:45Z andreas.loeffler@oracle.com $ # pylint: disable=line-too-long """ @@ -45,7 +45,7 @@ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 """ -__version__ = "$Revision: 113949 $" +__version__ = "$Revision: 114985 $" # Standard python imports @@ -353,7 +353,7 @@ def __init__(self): if self.oConfig.sLogFile: self.oLogFile = open(self.oConfig.sLogFile, "a"); # pylint: disable=consider-using-with,unspecified-encoding - self.oLogFile.write('VirtualTestSheriff: $Revision: 113949 $ \n'); + self.oLogFile.write('VirtualTestSheriff: $Revision: 114985 $ \n'); def eprint(self, sText): @@ -768,7 +768,7 @@ def caseClosed(self, oCaseFile): for idTestResult, tReason in dReasonForResultId.items(): oFailureReason = self.getFailureReason(tReason); if oFailureReason is not None: - sComment = 'Set by $Revision: 113949 $' # Handy for reverting later. + sComment = 'Set by $Revision: 114985 $' # Handy for reverting later. if idTestResult in dCommentForResultId: sComment += ': ' + dCommentForResultId[idTestResult]; @@ -900,6 +900,7 @@ def findInAnyAndReturnRestOfLine(asHaystacks, sNeedle): ], 'win': [ # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Host_HostMemoryLow, 'Error = 0x000005AA' ), ( True, ktReason_Host_InstallationWantReboot, 'ERROR_SUCCESS_REBOOT_REQUIRED' ), ( False, ktReason_Host_InstallationFailed, 'Installation error.' ), ( True, ktReason_Host_InvalidPackage, 'Uninstaller failed, exit code: 1620' ), From bdacf93d0e87d230cb85425009e0ff3fe487b763 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Tue, 11 Aug 2026 13:26:17 +0000 Subject: [PATCH 089/176] /Config.kmk: Some VBoxR0 compiler hardening. bugref:11138 svn:sync-xref-src-repo-rev: r174827 --- Config.kmk | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Config.kmk b/Config.kmk index 6a0c2a37f4d4..53d48b1beec2 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ +# $Id: Config.kmk 114986 2026-08-11 13:26:17Z knut.osmundsen@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -3486,6 +3486,7 @@ ifndef VBOX_NOINC_DYNAMIC_CONFIG_KMK $(QUIET)$(APPEND) '$@' 'VBOX_GCC_msse4.1 ?= $(call VBOX_GCC_CHECK_CC,-msse4.1,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_mavx ?= $(call VBOX_GCC_CHECK_CC,-mavx,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_mavx2 ?= $(call VBOX_GCC_CHECK_CC,-mavx2,)' + $(QUIET)$(APPEND) '$@' 'VBOX_GCC_mharden-sls-all ?= $(call VBOX_GCC_CHECK_CXX,-mharden-sls=all,)' ifdef VBOX_USE_CLANG $(QUIET)$(APPEND) '$@' 'VBOX_GCC_no-pie ?= $(call VBOX_GCC_CHECK_LD,--no-pie,)' else @@ -5686,6 +5687,9 @@ ifeq ($(VBOX_LDR_FMT),pe) if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Don't waste space on x86/amd64-on-arm emulation optimizations. TEMPLATE_VBoxR0_CXXFLAGS += /volatileMetadata- endif + if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Prevent speculative execution past indirect jumps. + TEMPLATE_VBoxR0_CXXFLAGS += -Qspectre-jmp + endif ifdef VBOX_WITH_MSC_ANALYZE_THIS TEMPLATE_VBoxR0_CXXFLAGS += /analyze endif @@ -5739,8 +5743,8 @@ ifeq ($(VBOX_LDR_FMT),elf) if $(VBOX_GCC_VERSION_CC) >= 40500 # 4.1.2 complains, 4.5.2 is okay, didn't check which version inbetween made it okay with g++. TEMPLATE_VBoxR0_CXXFLAGS += -ffreestanding endif - TEMPLATE_VBoxR0_CFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables -ffreestanding - TEMPLATE_VBoxR0_CXXFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables + TEMPLATE_VBoxR0_CFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables $(VBOX_GCC_mharden-sls-all) -ffreestanding + TEMPLATE_VBoxR0_CXXFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables $(VBOX_GCC_mharden-sls-all) TEMPLATE_VBoxR0_CXXFLAGS.arm64 = -mabi=lp64 -mgeneral-regs-only -ffixed-x18 -fconserve-stack \ -fno-stack-check $(VBOX_GCC_fno-stack-clash-protection) $(VBOX_GCC_fno-allow-store-data-races) -fno-delete-null-pointer-checks # (linux 6.11) # arm64: Add -mbranch-protection=pac-ret ? @@ -9608,7 +9612,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114960 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114986 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9622,7 +9626,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114960 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 114986 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif From 5d7a59834a6a2c8c25267709eefd8317c8b5d17d Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 11 Aug 2026 13:50:56 +0000 Subject: [PATCH 090/176] Shared Clipboard/darwin: Added a force flag to make synchronization of clipboard reliable. bugref:4697 svn:sync-xref-src-repo-rev: r174828 --- src/VBox/Main/include/darwin-pasteboard.h | 4 ++-- .../src-client/darwin/ClipboardBackendDarwin.cpp | 12 +++++++----- .../Main/src-client/darwin/darwin-pasteboard.cpp | 9 ++++++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/VBox/Main/include/darwin-pasteboard.h b/src/VBox/Main/include/darwin-pasteboard.h index 2094ca63ea68..87613ce509bf 100644 --- a/src/VBox/Main/include/darwin-pasteboard.h +++ b/src/VBox/Main/include/darwin-pasteboard.h @@ -1,4 +1,4 @@ -/* $Id: darwin-pasteboard.h 114863 2026-08-06 10:19:52Z andreas.loeffler@oracle.com $ */ +/* $Id: darwin-pasteboard.h 114987 2026-08-11 13:50:56Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host implementation. */ @@ -39,7 +39,7 @@ DECLHIDDEN(int) initPasteboard(PasteboardRef *pPasteboardRef); DECLHIDDEN(void) destroyPasteboard(PasteboardRef *pPasteboardRef); DECLHIDDEN(int) queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, - uint32_t *pfFormats, bool *pfChanged); + bool fForce, uint32_t *pfFormats, bool *pfChanged); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS DECLHIDDEN(int) readFileURLsFromPasteboard(PasteboardRef hPasteboard, char **ppszRoots, size_t *pcbRoots); #endif diff --git a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp index 73fed0300dca..9e6af7ab50ef 100644 --- a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp +++ b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendDarwin.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendDarwin.cpp 114987 2026-08-11 13:50:56Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host. */ @@ -166,9 +166,11 @@ static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMA * * @returns IPRT status code (ignored). * @param pCtx The context. + * @param fForce Whether to report the current pasteboard content even if + * its change was already observed. * */ -static int vboxClipboardChanged(SHCLCONTEXT *pCtx) +static int vboxClipboardChanged(SHCLCONTEXT *pCtx, bool fForce) { int vrc = VINF_SUCCESS; uint32_t fFormats = 0; @@ -184,7 +186,7 @@ static int vboxClipboardChanged(SHCLCONTEXT *pCtx) if (RT_SUCCESS(vrc)) { vrc = queryNewPasteboardFormats(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->hStrOwnershipFlavor, - &fFormats, &fChanged); + fForce, &fFormats, &fChanged); int const vrc2 = RTCritSectLeave(&pCtx->CritSectPasteboard); AssertRC(vrc2); @@ -221,7 +223,7 @@ static DECLCALLBACK(int) vboxClipboardThread(RTTHREAD ThreadSelf, void *pvUser) while (!ASMAtomicReadBool(&pCtx->fTerminate)) { - vboxClipboardChanged(pCtx); + vboxClipboardChanged(pCtx, false /* fForce */); /* Sleep for 200 msecs before next poll */ vrc = RTThreadUserWait(ThreadSelf, 200); @@ -349,7 +351,7 @@ int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) /* Sync the host clipboard content with the client. */ if (RT_SUCCESS(vrc)) - vrc = vboxClipboardChanged(pClient->State.pCtx); + vrc = vboxClipboardChanged(pClient->State.pCtx, true /* fForce */); return vrc; } diff --git a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp index 14fa00c477b6..46030d87787b 100644 --- a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp +++ b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp @@ -1,4 +1,4 @@ -/* $Id: darwin-pasteboard.cpp 114969 2026-08-10 16:19:05Z andreas.loeffler@oracle.com $ */ +/* $Id: darwin-pasteboard.cpp 114987 2026-08-11 13:50:56Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host implementation. */ @@ -110,6 +110,8 @@ DECLHIDDEN(void) destroyPasteboard(PasteboardRef *pPasteboardRef) * @param idOwnership Our ownership ID. * @param hStrOwnershipFlavor The ownership flavor string reference returned * by takePasteboardOwnership(). + * @param fForce Whether to inspect the current content even if + * the pasteboard change was already observed. * @param pfFormats Pointer for the bit combination of the * supported types. * @param pfChanged True if something has changed after the @@ -118,7 +120,7 @@ DECLHIDDEN(void) destroyPasteboard(PasteboardRef *pPasteboardRef) * @returns VINF_SUCCESS. */ DECLHIDDEN(int) queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, - uint32_t *pfFormats, bool *pfChanged) + bool fForce, uint32_t *pfFormats, bool *pfChanged) { AssertPtrReturn(hPasteboard, VERR_INVALID_POINTER); AssertPtrReturn(pfFormats, VERR_INVALID_POINTER); @@ -130,7 +132,8 @@ DECLHIDDEN(int) queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t id /* Make sure all is in sync */ PasteboardSyncFlags const syncFlags = PasteboardSynchronize(hPasteboard); /* If nothing changed return */ - if (!(syncFlags & kPasteboardModified)) + if ( !(syncFlags & kPasteboardModified) + && !fForce) { *pfChanged = false; Log2(("queryNewPasteboardFormats: no change\n")); From 5a54900110f068c77a13c7e437e9001bf33ed189 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 11 Aug 2026 13:58:58 +0000 Subject: [PATCH 091/176] Shared Clipboard/VbglR3: More code for transfer session checking and retirement. bugref:4697 svn:sync-xref-src-repo-rev: r174829 --- .../VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp | 108 +++++++++++++++++- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp index 1e681cb3edc5..b49b5ba31902 100644 --- a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxGuestR3LibClipboard.cpp 114968 2026-08-10 16:16:50Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxGuestR3LibClipboard.cpp 114988 2026-08-11 13:58:58Z andreas.loeffler@oracle.com $ */ /** @file * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared Clipboard. */ @@ -2097,6 +2097,82 @@ static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceHGObjRead(PSHCLTXPROVIDERCT return rc; } +/** Retires every transfer belonging to the current host service session. */ +static int vbglR3ClipboardTransferCtxRetireAll(PSHCLTRANSFERCTX pTransferCtx) +{ + for (;;) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferLast(pTransferCtx); + if (!pTransfer) + return VINF_SUCCESS; + + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + int rc = ShClTransferCtxUnregisterById(pTransferCtx, idTransfer); + if (RT_SUCCESS(rc)) + rc = ShClTransferDestroy(pTransfer); + if (RT_FAILURE(rc)) + { + LogRel(("Shared Clipboard: Retiring stale transfer %RU16 failed with %Rrc\n", idTransfer, rc)); + return rc; + } + } +} + +/** + * Binds a transfer context to the service session carried by an incoming + * transfer-status context ID. + * + * Note! The service session is not part of feature negotiation, so the first + * transfer-status message is the earliest authoritative source. A changed + * session invalidates every transfer created by the previous host service + * incarnation (for example after restoring a VM). + */ +static int vbglR3ClipboardTransferCtxEnsureSession(PSHCLTRANSFERCTX pTransferCtx, uint64_t idContext) +{ + SHCLSESSIONID const idSession = VBOX_SHCL_CONTEXTID_GET_SESSION(idContext); + if ( idSession == 0 + || idSession == NIL_SHCLSESSIONID) + return VERR_INVALID_CONTEXT; + + if (pTransferCtx->idSession == idSession) + return VINF_SUCCESS; + + SHCLSESSIONID const idOldSession = pTransferCtx->idSession; + int rc = VINF_SUCCESS; + uint32_t const cTransfers = ShClTransferCtxGetTotalTransfers(pTransferCtx); + if (cTransfers) + { + LogRel(("Shared Clipboard: Service session changed from %RU16 to %RU16; retiring %RU32 stale transfer(s)\n", + idOldSession, idSession, cTransfers)); + rc = vbglR3ClipboardTransferCtxRetireAll(pTransferCtx); + } + else if (idOldSession != NIL_SHCLSESSIONID) + LogRel2(("Shared Clipboard: Service session changed from %RU16 to %RU16\n", idOldSession, idSession)); + + if (RT_SUCCESS(rc)) + rc = ShClTransferCtxBeginSession(pTransferCtx, idSession); + if (RT_SUCCESS(rc)) + LogRel2(("Shared Clipboard: Bound guest transfers to service session %RU16\n", idSession)); + + return rc; +} + +/** Validates that an incoming transfer command belongs to the active service session. */ +static int vbglR3ClipboardTransferCtxCheckSession(PSHCLTRANSFERCTX pTransferCtx, uint64_t idContext) +{ + SHCLSESSIONID const idSession = VBOX_SHCL_CONTEXTID_GET_SESSION(idContext); + if ( idSession == 0 + || idSession == NIL_SHCLSESSIONID + || pTransferCtx->idSession != idSession) + { + LogRel(("Shared Clipboard: Rejecting transfer command for stale service session %RU16 (active %RU16)\n", + idSession, pTransferCtx->idSession)); + return VERR_INVALID_CONTEXT; + } + + return VINF_SUCCESS; +} + /** * Creates (and registers) a transfer on the guest side. * @@ -2318,12 +2394,15 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, SHCLTRANSFERDIR enmDir; SHCLTRANSFERREPORT transferReport; rc = VbglR3ClipboarTransferStatusRecv(pCmdCtx, &enmDir, &transferReport); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxEnsureSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { const SHCLTRANSFERID idTransfer = VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext); - LogRel2(("Shared Clipboard: Received status %s (%Rrc) for transfer %RU16\n", - ShClTransferStatusToStr(transferReport.uStatus), transferReport.rc, idTransfer)); + LogRel2(("Shared Clipboard: Received status %s (%Rrc) for transfer %RU16 in session %RU16\n", + ShClTransferStatusToStr(transferReport.uStatus), transferReport.rc, idTransfer, + VBOX_SHCL_CONTEXTID_GET_SESSION(pCmdCtx->idContext))); SHCLSOURCE enmSource = SHCLSOURCE_INVALID; @@ -2458,6 +2537,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, /** @todo Validate / handle fRoots. */ + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2481,6 +2562,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, uint64_t uIndex; uint32_t fInfo; rc = VbglR3ClipboardTransferRootListEntryReadReq(pCmdCtx, &uIndex, &fInfo); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2505,6 +2588,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, if (RT_SUCCESS(rc)) { rc = VbglR3ClipboardTransferListOpenRecv(pCmdCtx, &openParmsList); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2531,6 +2616,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, { SHCLLISTHANDLE hList; rc = VbglR3ClipboardTransferListCloseRecv(pCmdCtx, &hList); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2554,6 +2641,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; uint32_t fFlags = 0; rc = VbglR3ClipboardTransferListHdrReadRecvReq(pCmdCtx, &hList, &fFlags); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2580,6 +2669,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, SHCLLISTHANDLE hList; uint32_t fInfo; rc = VbglR3ClipboardTransferListEntryReadRecvReq(pCmdCtx, &hList, &fInfo); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2617,6 +2708,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, if (RT_SUCCESS(rc)) { rc = VbglR3ClipboardTransferObjOpenRecv(pCmdCtx, &openParms); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2641,6 +2734,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, { SHCLOBJHANDLE hObj; rc = VbglR3ClipboardTransferObjCloseRecv(pCmdCtx, &hObj); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2663,6 +2758,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, uint32_t cbBuf; uint32_t fFlags; rc = VbglR3ClipboardTransferObjReadRecv(pCmdCtx, &hObj, &cbBuf, &fFlags); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2702,8 +2799,11 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, } } + /* A stale context must fail closed locally. Echoing it in an error + * reply would address a transfer owned by another service session. */ if ( !fErrorSent - && RT_FAILURE(rc)) + && RT_FAILURE(rc) + && rc != VERR_INVALID_CONTEXT) { /* Report transfer-specific error back to the host. */ int rc2 = vbglR3ClipboardTransferSendStatusEx(pCmdCtx, pCmdCtx->idContext, SHCLTRANSFERSTATUS_ERROR, rc); From 3b1cf9729275899d4733a5ce2e98dbcbd3d2fd5f Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 11 Aug 2026 14:13:05 +0000 Subject: [PATCH 092/176] Shared Clipboard/Transfers: More code for transfer registration and server lifetime; give each HTTP request its own object handle, retain registrations while requests are active, drain callbacks before destroying transfers, and use the complete session/transfer/generation identity for lookups. bugref:4697 svn:sync-xref-src-repo-rev: r174830 --- .../GuestHost/SharedClipboard-transfers.h | 5 +- .../clipboard-transfers-http.cpp | 668 +++++++++++---- .../testcase/tstClipboardHttpServer.cpp | 785 +++++++++++++++++- 3 files changed, 1301 insertions(+), 157 deletions(-) diff --git a/include/VBox/GuestHost/SharedClipboard-transfers.h b/include/VBox/GuestHost/SharedClipboard-transfers.h index f3c846f1a8f7..a5ca7bfdb809 100644 --- a/include/VBox/GuestHost/SharedClipboard-transfers.h +++ b/include/VBox/GuestHost/SharedClipboard-transfers.h @@ -1,4 +1,4 @@ -/* $Id: SharedClipboard-transfers.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: SharedClipboard-transfers.h 114989 2026-08-11 14:13:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Shared transfer functions between host and guest. */ @@ -1028,6 +1028,9 @@ typedef struct _SHCLHTTPSERVER bool fInitialized; /** Running indicator. */ bool fRunning; + /** Stop operation in progress. Prevents a new server from being started + * while callbacks of the old server are being drained. */ + bool fStopping; /** Current status. */ SHCLHTTPSERVERSTATUS enmStatus; /** Handle of the HTTP server instance. */ diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp index 0a29676beafd..26c978b4ab61 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-http.cpp 114865 2026-08-06 10:27:20Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-http.cpp 114989 2026-08-11 14:13:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts. */ @@ -60,7 +60,6 @@ #include #include -#include #include @@ -78,18 +77,40 @@ typedef struct _SHCLHTTPSERVERTRANSFER { /** The node list. */ RTLISTNODE Node; - /** Pointer to associated transfer. */ + /** Pointer to associated transfer. Held by one transfer reference while this structure exists. */ PSHCLTRANSFER pTransfer; - /** Critical section for serializing access. */ + /** Critical section protecting the request count and drain event state. */ RTCRITSECT CritSect; - /** The handle we're going to use for this HTTP transfer. */ - SHCLOBJHANDLE hObj; + /** Number of references to this registration (list owner, requests and an eventual drain waiter). */ + volatile uint32_t cRefs; + /** Number of active HTTP requests using this registration. */ + uint32_t cRequests; + /** Signaled when the active request count transitions to zero. */ + RTSEMEVENTMULTI hRequestsDrained; + /** Whether the registration still is visible in the server lookup list. Protected by the server lock. */ + bool fRegistered; + /** Service session portion of the immutable transfer key. */ + SHCLSESSIONID idSession; + /** Transfer ID portion of the immutable transfer key. */ + SHCLTRANSFERID idTransfer; + /** Generation portion of the immutable transfer key. */ + SHCLTRANSFERGEN uGeneration; /** The virtual path of the HTTP server's root directory for this transfer. * Always has to start with a "/". Unescaped. */ char szPathVirtual[RTPATH_MAX]; } SHCLHTTPSERVERTRANSFER; typedef SHCLHTTPSERVERTRANSFER *PSHCLHTTPSERVERTRANSFER; +/** Per-request state. In particular, object handles must never be shared by two HTTP requests. */ +typedef struct _SHCLHTTPSERVERREQUEST +{ + /** Retained registration used by this request. */ + PSHCLHTTPSERVERTRANSFER pSrvTx; + /** Object handle opened for this request. */ + SHCLOBJHANDLE hObj; +} SHCLHTTPSERVERREQUEST; +typedef SHCLHTTPSERVERREQUEST *PSHCLHTTPSERVERREQUEST; + /********************************************************************************************************************************* * Prototypes * @@ -146,7 +167,6 @@ DECLINLINE(void) shClTransferHttpServerUnlock(PSHCLHTTPSERVER pSrv) AssertRC(rc2); } -#if 0 /* unused */ /** * Locks an HTTP transfer. * @@ -168,7 +188,131 @@ DECLINLINE(void) shClHttpTransferUnlock(PSHCLHTTPSERVERTRANSFER pSrvTx) int rc2 = RTCritSectLeave(&pSrvTx->CritSect); AssertRC(rc2); } -#endif + +/** + * Retains an HTTP transfer registration. + * + * @returns New reference count. + * @param pSrvTx HTTP transfer registration to retain. + */ +static uint32_t shClHttpTransferRetain(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + uint32_t const cRefs = ASMAtomicIncU32(&pSrvTx->cRefs); + Assert(cRefs > 1 && cRefs < UINT32_MAX / 2); + return cRefs; +} + +/** + * Releases an HTTP transfer registration and destroys it on the final release. + * + * @returns New reference count. + * @param pSrvTx HTTP transfer registration to release. + */ +static uint32_t shClHttpTransferRelease(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + Assert(ASMAtomicReadU32(&pSrvTx->cRefs) > 0); + + uint32_t const cRefs = ASMAtomicDecU32(&pSrvTx->cRefs); + if (cRefs == 0) + { + Assert(!pSrvTx->fRegistered); + Assert(pSrvTx->cRequests == 0); + + if (RTCritSectIsInitialized(&pSrvTx->CritSect)) + { + int rc2 = RTCritSectDelete(&pSrvTx->CritSect); + AssertRC(rc2); + } + + int rc2 = RTSemEventMultiDestroy(pSrvTx->hRequestsDrained); + AssertRC(rc2); + pSrvTx->hRequestsDrained = NIL_RTSEMEVENTMULTI; + + ShClTransferRelease(pSrvTx->pTransfer); + pSrvTx->pTransfer = NULL; + + RTMemFree(pSrvTx); + } + + return cRefs; +} + +/** + * Adds an active request reference to a registered HTTP transfer. + * + * @returns VBox status code. + * @param pSrvTx HTTP transfer registration to retain for a request. + * + * @note The caller must own the HTTP server lock, which prevents the + * registration from being detached while the request is added. + */ +static int shClHttpTransferRequestRetain(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + Assert(pSrvTx->fRegistered); + + shClHttpTransferRetain(pSrvTx); + shClHttpTransferLock(pSrvTx); + + int rc = VINF_SUCCESS; + if (pSrvTx->cRequests == 0) + rc = RTSemEventMultiReset(pSrvTx->hRequestsDrained); + if (RT_SUCCESS(rc)) + pSrvTx->cRequests++; + + shClHttpTransferUnlock(pSrvTx); + + if (RT_FAILURE(rc)) + shClHttpTransferRelease(pSrvTx); + return rc; +} + +/** + * Releases an active request reference to an HTTP transfer registration. + * + * @param pSrvTx HTTP transfer registration to release for a request. + */ +static void shClHttpTransferRequestRelease(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + shClHttpTransferLock(pSrvTx); + + Assert(pSrvTx->cRequests > 0); + pSrvTx->cRequests--; + if (pSrvTx->cRequests == 0) + { + int rc2 = RTSemEventMultiSignal(pSrvTx->hRequestsDrained); + AssertRC(rc2); + } + + shClHttpTransferUnlock(pSrvTx); + shClHttpTransferRelease(pSrvTx); +} + +/** + * Waits for all requests using a detached HTTP transfer registration to end. + * + * @returns VBox status code. + * @param pSrvTx Detached HTTP transfer registration to drain. + */ +static int shClHttpTransferDrainRequests(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + Assert(!pSrvTx->fRegistered); + + int rc = VINF_SUCCESS; + for (;;) + { + shClHttpTransferLock(pSrvTx); + uint32_t const cRequests = pSrvTx->cRequests; + shClHttpTransferUnlock(pSrvTx); + if (cRequests == 0) + break; + + rc = RTSemEventMultiWait(pSrvTx->hRequestsDrained, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + } + + return rc; +} /** * Creates an URL from a given path, extended version. @@ -227,13 +371,46 @@ static int shClTransferHttpURLCreateFromPath(const char *pszPath, char **ppszURL * @returns Pointer to HTTP server transfer if found, NULL if not found. * @param pSrv HTTP server instance. * @param idTransfer Transfer ID to return HTTP server transfer for. + * + * @note Caller needs to take the server critical section. */ DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpServerGetTransferById(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer) { + Assert(RTCritSectIsOwner(&pSrv->CritSect)); + PSHCLHTTPSERVERTRANSFER pSrvTx; RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node) /** @todo Slow O(n) lookup, but does it for now. */ { - if (pSrvTx->pTransfer->State.uID == idTransfer) + if (pSrvTx->idTransfer == idTransfer) + return pSrvTx; + } + + return NULL; +} + +/** + * Returns the HTTP server transfer matching an exact transfer key. + * + * @returns Pointer to HTTP server transfer if found, NULL if not found. + * @param pSrv HTTP server instance. + * @param idSession Service session ID to match. + * @param idTransfer Transfer ID to match. + * @param uGeneration Transfer generation to match. + * + * @note Caller needs to take the server critical section. + */ +DECLINLINE(PSHCLHTTPSERVERTRANSFER) +shClTransferHttpServerGetTransferByKey(PSHCLHTTPSERVER pSrv, SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration) +{ + Assert(RTCritSectIsOwner(&pSrv->CritSect)); + + PSHCLHTTPSERVERTRANSFER pSrvTx; + RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node) + { + if ( pSrvTx->idSession == idSession + && pSrvTx->idTransfer == idTransfer + && pSrvTx->uGeneration == uGeneration) return pSrvTx; } @@ -244,17 +421,20 @@ DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpServerGetTransferById(PSHCLH * Returns a HTTP server transfer from a given URL. * * @returns Pointer to HTTP server transfer if found, NULL if not found. - * @param pThis HTTP server instance data. + * @param pSrv HTTP server instance data. * @param pszUrl URL to validate. + * + * @note Caller needs to take the server critical section. */ -DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTPSERVER pThis, const char *pszUrl) +DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTPSERVER pSrv, const char *pszUrl) { AssertPtrReturn(pszUrl, NULL); + Assert(RTCritSectIsOwner(&pSrv->CritSect)); PSHCLHTTPSERVERTRANSFER pSrvTx = NULL; PSHCLHTTPSERVERTRANSFER pSrvTxCur; - RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node) + RTListForEach(&pSrv->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node) { AssertPtr(pSrvTxCur->pTransfer); @@ -311,35 +491,88 @@ DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromHandle(PSHCLH * HTTP server callback implementations * *********************************************************************************************************************************/ +/** + * Closes the object owned by an HTTP request, if any. + * + * @returns VBox status code. + * @param pHttpReq HTTP request state whose object to close. + * + * @note The request relinquishes the handle even if the provider reports a + * close error. The transfer's final reset remains the last-resort + * cleanup for provider-owned object state. + */ +static int shClTransferHttpRequestClose(PSHCLHTTPSERVERREQUEST pHttpReq) +{ + if (pHttpReq->hObj == NIL_SHCLOBJHANDLE) + return VINF_SUCCESS; + + SHCLOBJHANDLE const hObj = pHttpReq->hObj; + pHttpReq->hObj = NIL_SHCLOBJHANDLE; + + int const rc = ShClTransferObjClose(pHttpReq->pSrvTx->pTransfer, hObj); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Error closing HTTP request object (handle %RU64), rc=%Rrc\n", hObj, rc)); + return rc; +} + /** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestBegin */ static DECLCALLBACK(int) shClTransferHttpBegin(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq) { - PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis); + PSHCLHTTPSERVER pSrv = (PSHCLHTTPSERVER)pData->pvUser; Assert(pData->cbUser == sizeof(SHCLHTTPSERVER)); LogRel2(("Shared Clipboard: HTTP request begin\n")); - PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromUrl(pThis, pReq->pszUrl); + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)RTMemAllocZ(sizeof(SHCLHTTPSERVERREQUEST)); + if (!pHttpReq) + return VERR_NO_MEMORY; + + int rc = VERR_NOT_FOUND; + + shClTransferHttpServerLock(pSrv); + + PSHCLHTTPSERVERTRANSFER pSrvTx = NULL; + if (pSrv->fRunning) + pSrvTx = shClTransferHttpGetTransferFromUrl(pSrv, pReq->pszUrl); if (pSrvTx) { - pReq->pvUser = pSrvTx; + rc = shClHttpTransferRequestRetain(pSrvTx); + if (RT_SUCCESS(rc)) + { + pHttpReq->pSrvTx = pSrvTx; + pHttpReq->hObj = NIL_SHCLOBJHANDLE; + pReq->pvUser = pHttpReq; + } } + shClTransferHttpServerUnlock(pSrv); + + if (RT_FAILURE(rc)) + RTMemFree(pHttpReq); + + /* Keep request-begin lookup failures transparent to the HTTP server. The + * method callback maps a request without private state to HTTP 404. */ return VINF_SUCCESS; } /** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestEnd */ static DECLCALLBACK(int) shClTransferHttpEnd(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq) { - PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis); Assert(pData->cbUser == sizeof(SHCLHTTPSERVER)); LogRel2(("Shared Clipboard: HTTP request end\n")); - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pReq->pvUser; + if (pHttpReq) { pReq->pvUser = NULL; + + int rc2 = shClTransferHttpRequestClose(pHttpReq); + AssertRC(rc2); + + shClHttpTransferRequestRelease(pHttpReq->pSrvTx); + pHttpReq->pSrvTx = NULL; + RTMemFree(pHttpReq); } return VINF_SUCCESS; @@ -349,20 +582,24 @@ static DECLCALLBACK(int) shClTransferHttpEnd(PRTHTTPCALLBACKDATA pData, PRTHTTPS /** @copydoc RTHTTPSERVERCALLBACKS::pfnOpen */ static DECLCALLBACK(int) shClTransferHttpOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle) { - PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis); + RT_NOREF(pData); Assert(pData->cbUser == sizeof(SHCLHTTPSERVER)); int rc; AssertPtr(pReq->pvUser); - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pReq->pvUser; + if (pHttpReq) { - LogRel2(("Shared Clipboard: HTTP transfer (handle %RU64) started ...\n", pSrvTx->hObj)); + LogRel2(("Shared Clipboard: HTTP transfer (handle %RU64) started ...\n", pHttpReq->hObj)); - Assert(pSrvTx->hObj != NIL_SHCLOBJHANDLE); - *ppvHandle = &pSrvTx->hObj; - rc = VINF_SUCCESS; + if (pHttpReq->hObj != NIL_SHCLOBJHANDLE) + { + *ppvHandle = pHttpReq; + rc = VINF_SUCCESS; + } + else + rc = VERR_NOT_FOUND; } else rc = VERR_NOT_FOUND; @@ -393,19 +630,20 @@ static DECLCALLBACK(int) shClTransferHttpRead(PRTHTTPCALLBACKDATA pData, PRTHTTP LogRel3(("Shared Clipboard: Reading %RU32 bytes from HTTP ...\n", cbBuf)); AssertPtr(pReq->pvUser); - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pvHandle; + if ( pHttpReq + && pReq->pvUser == pHttpReq) { - PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle; - if (phObj) + if (pHttpReq->hObj != NIL_SHCLOBJHANDLE) { uint32_t cbRead; - rc = ShClTransferObjRead(pSrvTx->pTransfer, *phObj, pvBuf, cbBuf, 0 /* fFlags */, &cbRead); + rc = ShClTransferObjRead(pHttpReq->pSrvTx->pTransfer, pHttpReq->hObj, + pvBuf, (uint32_t)cbBuf, 0 /* fFlags */, &cbRead); if (RT_SUCCESS(rc)) *pcbRead = (uint32_t)cbRead; if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Error reading HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc)); + LogRel(("Shared Clipboard: Error reading HTTP transfer (handle %RU64), rc=%Rrc\n", pHttpReq->hObj, rc)); } else rc = VERR_NOT_FOUND; @@ -425,22 +663,18 @@ static DECLCALLBACK(int) shClTransferHttpClose(PRTHTTPCALLBACKDATA pData, PRTHTT int rc; AssertPtr(pReq->pvUser); - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pvHandle; + if ( pHttpReq + && pReq->pvUser == pHttpReq) { - PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle; - if (phObj) + if (pHttpReq->hObj != NIL_SHCLOBJHANDLE) { - Assert(*phObj != NIL_SHCLOBJHANDLE); - rc = ShClTransferObjClose(pSrvTx->pTransfer, *phObj); + SHCLOBJHANDLE const hObj = pHttpReq->hObj; + rc = shClTransferHttpRequestClose(pHttpReq); if (RT_SUCCESS(rc)) - { - pSrvTx->hObj = NIL_SHCLOBJHANDLE; - LogRel2(("Shared Clipboard: HTTP transfer %RU16 done\n", pSrvTx->pTransfer->State.uID)); - } - - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Error closing HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc)); + LogRel2(("Shared Clipboard: HTTP transfer %RU16 done\n", pHttpReq->pSrvTx->idTransfer)); + else + LogRel(("Shared Clipboard: Error closing HTTP transfer (handle %RU64), rc=%Rrc\n", hObj, rc)); } else rc = VERR_NOT_FOUND; @@ -477,9 +711,10 @@ static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData, size_t const cchParsedPath = strlen(pszParsedPath); /* For now we only know the transfer -- now we need to figure out the entry we want to serve. */ - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pReq->pvUser; + if (pHttpReq) { + PSHCLHTTPSERVERTRANSFER pSrvTx = pHttpReq->pSrvTx; size_t const cchVirtual = strlen(pSrvTx->szPathVirtual); size_t const cchRoot = cchVirtual + 1 /* Skip slash separating the base from the rest */; const char *pszRoot = NULL; @@ -523,7 +758,8 @@ static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData, rc = RTStrCopy(openParms.pszPath, openParms.cbPath, pEntry->pszName); if (RT_SUCCESS(rc)) { - rc = ShClTransferObjOpen(pTx, &openParms, &pSrvTx->hObj); + Assert(pHttpReq->hObj == NIL_SHCLOBJHANDLE); + rc = ShClTransferObjOpen(pTx, &openParms, &pHttpReq->hObj); if (RT_SUCCESS(rc)) { rc = VERR_NOT_SUPPORTED; /* Play safe by default. */ @@ -551,11 +787,10 @@ static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData, pEntry->pszName, pEntry->fInfo, pEntry->cbInfo)); /* Note: Directories / symlinks or other fancy stuff is not supported here (yet) -- would require using WebDAV. */ if ( RT_FAILURE(rc) - && pSrvTx->hObj != NIL_SHCLOBJHANDLE) + && pHttpReq->hObj != NIL_SHCLOBJHANDLE) { - int rc2 = ShClTransferObjClose(pTx, pSrvTx->hObj); + int rc2 = shClTransferHttpRequestClose(pHttpReq); AssertRC(rc2); - pSrvTx->hObj = NIL_SHCLOBJHANDLE; } } else if ( rc == VERR_NOT_A_FILE @@ -572,11 +807,10 @@ static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData, } if ( pReq->enmMethod == RTHTTPMETHOD_HEAD - && pSrvTx->hObj != NIL_SHCLOBJHANDLE) + && pHttpReq->hObj != NIL_SHCLOBJHANDLE) { - int rc2 = ShClTransferObjClose(pSrvTx->pTransfer, pSrvTx->hObj); + int rc2 = shClTransferHttpRequestClose(pHttpReq); AssertRC(rc2); - pSrvTx->hObj = NIL_SHCLOBJHANDLE; } } else @@ -619,14 +853,15 @@ static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv) LogFlowFuncEnter(); - pSrv->fInitialized = false; - pSrv->fRunning = false; + ASMAtomicXchgBool(&pSrv->fInitialized, false); + ASMAtomicXchgBool(&pSrv->fRunning, false); + pSrv->fStopping = false; int rc = VINF_SUCCESS; - PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext; - RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node) + while (!RTListIsEmpty(&pSrv->lstTransfers)) { + PSHCLHTTPSERVERTRANSFER pSrvTx = RTListGetFirst(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node); int rc2 = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx); if (RT_SUCCESS(rc)) rc = rc2; @@ -660,22 +895,39 @@ static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv) */ static int shClTransferHttpServerInitInternal(PSHCLHTTPSERVER pSrv) { + ASMAtomicXchgBool(&pSrv->fInitialized, false); + ASMAtomicXchgBool(&pSrv->fRunning, false); + pSrv->fStopping = false; + pSrv->StatusEvent = NIL_RTSEMEVENT; + pSrv->hHTTPServer = NIL_RTHTTPSERVER; + int rc = RTCritSectInit(&pSrv->CritSect); AssertRCReturn(rc, rc); rc = RTSemEventCreate(&pSrv->StatusEvent); - AssertRCReturn(rc, rc); + if (RT_FAILURE(rc)) + { + RTCritSectDelete(&pSrv->CritSect); + return rc; + } - pSrv->hHTTPServer = NIL_RTHTTPSERVER; pSrv->uPort = 0; RTListInit(&pSrv->lstTransfers); pSrv->cTransfers = 0; + pSrv->cDownloaded = 0; + pSrv->enmStatus = SHCLHTTPSERVERSTATUS_NONE; rc = RTHttpServerResponseInit(&pSrv->Resp); - AssertRCReturn(rc, rc); + if (RT_FAILURE(rc)) + { + RTSemEventDestroy(pSrv->StatusEvent); + pSrv->StatusEvent = NIL_RTSEMEVENT; + RTCritSectDelete(&pSrv->CritSect); + return rc; + } ASMAtomicXchgBool(&pSrv->fInitialized, true); - ASMAtomicXchgBool(&pSrv->fRunning, false); + pSrv->fStopping = false; return rc; } @@ -733,11 +985,20 @@ int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort) { AssertPtrReturn(pSrv, VERR_INVALID_POINTER); AssertReturn(uPort, VERR_INVALID_PARAMETER); + AssertReturn(ASMAtomicReadBool(&pSrv->fInitialized), VERR_WRONG_ORDER); AssertReturn(!shClTransferHttpServerPortIsBuggy(uPort), VERR_ADDRESS_CONFLICT); shClTransferHttpServerLock(pSrv); + if ( pSrv->fRunning + || pSrv->fStopping + || pSrv->hHTTPServer != NIL_RTHTTPSERVER) + { + shClTransferHttpServerUnlock(pSrv); + return VERR_WRONG_ORDER; + } + RTHTTPSERVERCALLBACKS Callbacks; RT_ZERO(Callbacks); @@ -862,36 +1123,60 @@ int ShClTransferHttpServerStart(PSHCLHTTPSERVER pSrv, unsigned cMaxAttempts, uin */ int ShClTransferHttpServerStop(PSHCLHTTPSERVER pSrv) { - LogFlowFuncEnter(); + AssertPtrReturn(pSrv, VERR_INVALID_POINTER); - shClTransferHttpServerLock(pSrv); + LogFlowFuncEnter(); - int rc = VINF_SUCCESS; + if (!ASMAtomicReadBool(&pSrv->fInitialized)) + return VINF_SUCCESS; - if (pSrv->fRunning) - { - Assert(pSrv->hHTTPServer != NIL_RTHTTPSERVER); + shClTransferHttpServerLock(pSrv); - rc = RTHttpServerDestroy(pSrv->hHTTPServer); - if (RT_SUCCESS(rc)) - { - pSrv->hHTTPServer = NIL_RTHTTPSERVER; - pSrv->fRunning = false; + int rc = VINF_SUCCESS; + RTHTTPSERVER hHTTPServer = NIL_RTHTTPSERVER; - /* Let any eventual waiters know. */ - shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_STOPPED); + if (pSrv->fStopping) + rc = VERR_WRONG_ORDER; + else if (pSrv->fRunning) + { + Assert(pSrv->hHTTPServer != NIL_RTHTTPSERVER); - LogRel2(("Shared Clipboard: HTTP server stopped\n")); - } - } + hHTTPServer = pSrv->hHTTPServer; + pSrv->fRunning = false; + pSrv->fStopping = true; + } - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: HTTP server failed to stop, rc=%Rrc\n", rc)); + shClTransferHttpServerUnlock(pSrv); - shClTransferHttpServerUnlock(pSrv); + if (hHTTPServer != NIL_RTHTTPSERVER) + { + /* Do not hold the server lock while stopping worker callbacks. Request + * begin needs that lock in order to take a registration reference. */ + rc = RTHttpServerDestroy(hHTTPServer); - LogFlowFuncLeaveRC(rc); - return rc; + shClTransferHttpServerLock(pSrv); + + pSrv->fStopping = false; + if (RT_SUCCESS(rc)) + { + pSrv->hHTTPServer = NIL_RTHTTPSERVER; + + /* Let any eventual waiters know. */ + shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_STOPPED); + + LogRel2(("Shared Clipboard: HTTP server stopped\n")); + } + else + pSrv->fRunning = true; + + shClTransferHttpServerUnlock(pSrv); + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: HTTP server failed to stop, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; } /** @@ -904,13 +1189,13 @@ int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv) { AssertPtrReturn(pSrv, VERR_INVALID_POINTER); + if (!ASMAtomicReadBool(&pSrv->fInitialized)) + return VINF_SUCCESS; + int rc = ShClTransferHttpServerStop(pSrv); if (RT_FAILURE(rc)) return rc; - if (!ASMAtomicReadBool(&pSrv->fInitialized)) - return VINF_SUCCESS; - shClTransferHttpServerLock(pSrv); rc = shClTransferHttpServerDestroyInternal(pSrv); @@ -940,42 +1225,45 @@ static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv) * @returns VBox status code. * @param pSrv HTTP server instance to unregister transfer from. * @param pSrvTx HTTP server transfer to destroy. - * The pointer will be invalid on success. + * The pointer must not be used after return. * - * @note Caller needs to take the server critical section. + * @note Caller needs to take the server critical section. This function + * temporarily releases it while synchronously draining requests and + * owns it again on return. */ static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx) { Assert(RTCritSectIsOwner(&pSrv->CritSect)); + Assert(pSrvTx->fRegistered); - if (pSrvTx->hObj != NIL_SHCLOBJHANDLE) - { - int rc = ShClTransferObjClose(pSrvTx->pTransfer, pSrvTx->hObj); - AssertRCReturn(rc, rc); - pSrvTx->hObj = NIL_SHCLOBJHANDLE; - } - - if (RTCritSectIsInitialized(&pSrvTx->CritSect)) - { - int rc = RTCritSectDelete(&pSrvTx->CritSect); - AssertRCReturn(rc, rc); - } + /* Keep the registration alive for this drain operation before dropping + * the list-owner reference below. */ + shClHttpTransferRetain(pSrvTx); + pSrvTx->fRegistered = false; RTListNodeRemove(&pSrvTx->Node); Assert(pSrv->cTransfers); pSrv->cTransfers--; - LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n", - pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers)); + LogFunc(("pTransfer=%p, idSession=%RU16, idTransfer=%RU16, uGeneration=%RU64, szPath=%s -> %RU32 transfers\n", + pSrvTx->pTransfer, pSrvTx->idSession, pSrvTx->idTransfer, pSrvTx->uGeneration, + pSrvTx->szPathVirtual, pSrv->cTransfers)); LogRel2(("Shared Clipboard: Destroyed HTTP transfer %RU16, now %RU32 HTTP transfers total\n", - pSrvTx->pTransfer->State.uID, pSrv->cTransfers)); + pSrvTx->idTransfer, pSrv->cTransfers)); - RTMemFree(pSrvTx); - pSrvTx = NULL; + /* Drop the list owner only after the registration is unreachable to new + * requests. Existing requests keep their own references. */ + shClHttpTransferRelease(pSrvTx); - return VINF_SUCCESS; + shClTransferHttpServerUnlock(pSrv); + + int rc = shClHttpTransferDrainRequests(pSrvTx); + shClHttpTransferRelease(pSrvTx); /* Drain waiter reference. */ + + shClTransferHttpServerLock(pSrv); + return rc; } @@ -987,7 +1275,7 @@ static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTP * Registers a Shared Clipboard transfer to a HTTP server instance. * * @returns VBox status code. - * @retval VERR_ALREADY_EXISTS if the transfer ID already is registered. + * @retval VERR_ALREADY_EXISTS if the exact transfer key already is registered. * @param pSrv HTTP server instance to register transfer for. * @param pTransfer Transfer to register. Needs to be on the heap. */ @@ -995,11 +1283,13 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p { AssertPtrReturn(pSrv, VERR_INVALID_POINTER); AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + AssertReturn(ASMAtomicReadBool(&pSrv->fInitialized), VERR_WRONG_ORDER); - AssertMsgReturn( pTransfer->State.uID != NIL_SHCLTRANSFERID - && pTransfer->State.uID > 0 - && pTransfer->State.uID < VBOX_SHCL_MAX_TRANSFERS - 1, - ("Transfer needs to be registered with a transfer context first\n"), VERR_INVALID_PARAMETER); + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + SHCLTRANSFERGEN const uGeneration = ShClTransferGetGeneration(pTransfer); + AssertMsgReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), + ("Transfer needs a valid session/ID/generation key before HTTP registration\n"), VERR_INVALID_PARAMETER); uint64_t const cRoots = ShClTransferRootsCount(pTransfer); AssertMsgReturn(cRoots > 0, ("Transfer has no root entries\n"), VERR_INVALID_PARAMETER); @@ -1009,9 +1299,10 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p PSHCLHTTPSERVERTRANSFER pSrvTx = NULL; bool fCritSectInitialized = false; + bool fDrainEventCreated = false; int rc = VINF_SUCCESS; - if (shClTransferHttpServerGetTransferById(pSrv, pTransfer->State.uID)) + if (shClTransferHttpServerGetTransferByKey(pSrv, idSession, idTransfer, uGeneration)) rc = VERR_ALREADY_EXISTS; else { @@ -1031,18 +1322,26 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p { fCritSectInitialized = true; + rc = RTSemEventMultiCreate(&pSrvTx->hRequestsDrained); + if (RT_SUCCESS(rc)) + fDrainEventCreated = true; + /* Create the virtual HTTP path for the transfer. * Every transfer has a dedicated HTTP path (but live in the same URL namespace). */ - char *pszPath; + char *pszPath = NULL; + ssize_t cch = -1; + if (RT_SUCCESS(rc)) + { #ifdef VBOX_SHCL_DEBUG_HTTPSERVER # ifdef DEBUG_andy /** Too lazy to specify a different transfer ID for debugging. */ - ssize_t cch = RTStrAPrintf(&pszPath, "/transfer"); + cch = RTStrAPrintf(&pszPath, "/transfer"); # else - ssize_t cch = RTStrAPrintf(&pszPath, "/transfer%RU16", pTransfer->State.uID); + cch = RTStrAPrintf(&pszPath, "/transfer%RU16", idTransfer); # endif #else /* Release mode */ - ssize_t cch = RTStrAPrintf(&pszPath, "/%s/%s", SHCL_HTTPT_URL_NAMESPACE, szUuid); + cch = RTStrAPrintf(&pszPath, "/%s/%s", SHCL_HTTPT_URL_NAMESPACE, szUuid); #endif + } if (cch >= 0) { char *pszURI; @@ -1061,30 +1360,39 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p else rc = VERR_NO_MEMORY; - RTStrFree(pszPath); - pszPath = NULL; } - else + else if (RT_SUCCESS(rc)) rc = VERR_NO_MEMORY; + RTStrFree(pszPath); + pszPath = NULL; if (RT_SUCCESS(rc)) { - pSrvTx->pTransfer = pTransfer; - pSrvTx->hObj = NIL_SHCLOBJHANDLE; + pSrvTx->pTransfer = pTransfer; + pSrvTx->cRefs = 1; /* Registration list owner. */ + pSrvTx->cRequests = 0; + pSrvTx->fRegistered = true; + pSrvTx->idSession = idSession; + pSrvTx->idTransfer = idTransfer; + pSrvTx->uGeneration = uGeneration; + + ShClTransferAcquire(pTransfer); RTListAppend(&pSrv->lstTransfers, &pSrvTx->Node); pSrv->cTransfers++; shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED); - LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n", - pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers)); + LogFunc(("pTransfer=%p, idSession=%RU16, idTransfer=%RU16, uGeneration=%RU64, szPath=%s -> %RU32 transfers\n", + pSrvTx->pTransfer, pSrvTx->idSession, pSrvTx->idTransfer, pSrvTx->uGeneration, + pSrvTx->szPathVirtual, pSrv->cTransfers)); LogRel2(("Shared Clipboard: Registered HTTP transfer %RU16, now %RU32 HTTP transfers total\n", - pTransfer->State.uID, pSrv->cTransfers)); + idTransfer, pSrv->cTransfers)); pSrvTx = NULL; fCritSectInitialized = false; + fDrainEventCreated = false; } } } @@ -1096,6 +1404,12 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p if (pSrvTx) { + if (fDrainEventCreated) + { + int rc2 = RTSemEventMultiDestroy(pSrvTx->hRequestsDrained); + AssertRC(rc2); + pSrvTx->hRequestsDrained = NIL_RTSEMEVENTMULTI; + } if (fCritSectInitialized) { int rc2 = RTCritSectDelete(&pSrvTx->CritSect); @@ -1117,30 +1431,35 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p * @param pSrv HTTP server instance to unregister transfer from. * @param pTransfer Transfer to unregister. * - * @note Removes all registrations matching the transfer ID to recover from - * stale duplicate entries. + * @note Removes all registrations matching the exact session/ID/generation + * key to recover from stale duplicate entries without disturbing a + * newer generation which happens to reuse the same transfer ID. + * The call synchronously drains active requests before returning so + * the caller may destroy the transfer immediately afterwards. */ int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer) { AssertPtrReturn(pSrv, VERR_INVALID_POINTER); AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + AssertReturn(ASMAtomicReadBool(&pSrv->fInitialized), VERR_WRONG_ORDER); + + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + SHCLTRANSFERGEN const uGeneration = ShClTransferGetGeneration(pTransfer); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), VERR_INVALID_PARAMETER); shClTransferHttpServerLock(pSrv); int rc = VINF_SUCCESS; - PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext; - RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node) + PSHCLHTTPSERVERTRANSFER pSrvTx; + while ((pSrvTx = shClTransferHttpServerGetTransferByKey(pSrv, idSession, idTransfer, uGeneration)) != NULL) { - AssertPtr(pSrvTx->pTransfer); - if (pSrvTx->pTransfer->State.uID == pTransfer->State.uID) - { - rc = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx); - if (RT_SUCCESS(rc)) - shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_UNREGISTERED); - else - break; - } + rc = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx); + if (RT_SUCCESS(rc)) + shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_UNREGISTERED); + else + break; } shClTransferHttpServerUnlock(pSrv); @@ -1288,6 +1607,62 @@ char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv) return pszAddress; } +/** + * Returns an allocated URL for a locked HTTP transfer registration. + * + * @returns Allocated URL, or NULL if the entry does not exist or allocation failed. + * @param pSrv HTTP server instance. + * @param pSrvTx HTTP transfer registration. + * @param idxEntry Root entry index, or UINT64_MAX for the base URL. + * + * @note Caller needs to take the server critical section. + */ +static char *shClTransferHttpServerGetUrlLocked(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx, uint64_t idxEntry) +{ + Assert(RTCritSectIsOwner(&pSrv->CritSect)); + AssertPtrReturn(pSrvTx, NULL); + + char *pszUrl = NULL; + + if (RT_LIKELY(idxEntry != UINT64_MAX)) + { + /* For now this only supports root entries. */ + PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pSrvTx->pTransfer, idxEntry); + if ( pEntry + && RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX)) + pszUrl = RTStrAPrintf2("%s:%RU16%s/%RMpp", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, + pSrvTx->szPathVirtual, pEntry->pszName); + } + else /* Only return the base. */ + pszUrl = RTStrAPrintf2("%s:%RU16%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual); + + return pszUrl; +} + +/** + * Returns an allocated URL for an exact Shared Clipboard transfer key. + * + * @returns Allocated URL, or NULL if the registration or entry was not found. + * @param pSrv HTTP server instance. + * @param pTransfer Transfer whose exact registration to use. + * @param idxEntry Root entry index, or UINT64_MAX for the base URL. + */ +static char *shClTransferHttpServerGetUrlForTransferA(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer, uint64_t idxEntry) +{ + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + SHCLTRANSFERGEN const uGeneration = ShClTransferGetGeneration(pTransfer); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), NULL); + + shClTransferHttpServerLock(pSrv); + + PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpServerGetTransferByKey(pSrv, idSession, idTransfer, uGeneration); + char *pszUrl = pSrvTx ? shClTransferHttpServerGetUrlLocked(pSrv, pSrvTx, idxEntry) : NULL; + + shClTransferHttpServerUnlock(pSrv); + return pszUrl; +} + /** * Returns an allocated string with the URL of a given Shared Clipboard transfer ID. * @@ -1313,24 +1688,7 @@ char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTrans return NULL; } - PSHCLTRANSFER pTx = pSrvTx->pTransfer; - AssertPtr(pTx); - - char *pszUrl = NULL; - - if (RT_LIKELY(idxEntry != UINT64_MAX)) - { - /* For now this only supports root entries. */ - PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTx, idxEntry); - if (pEntry) - { - AssertReturn(RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX), NULL); - pszUrl = RTStrAPrintf2("%s:%RU16%s/%RMpp", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, - pSrvTx->szPathVirtual, pEntry->pszName); - } - } - else /* Only return the base. */ - pszUrl = RTStrAPrintf2("%s:%RU16%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual); + char *pszUrl = shClTransferHttpServerGetUrlLocked(pSrv, pSrvTx, idxEntry); shClTransferHttpServerUnlock(pSrv); return pszUrl; @@ -1361,7 +1719,7 @@ static int shClTransferHttpConvertToStringListEx(PSHCLHTTPSERVER pSrv, PSHCLTRAN uint64_t const cRoots = ShClTransferRootsCount(pTransfer); for (uint32_t i = 0; i < cRoots; i++) { - char *pszEntry = ShClTransferHttpServerGetUrlA(pSrv, ShClTransferGetID(pTransfer), i /* Entry index */); + char *pszEntry = shClTransferHttpServerGetUrlForTransferA(pSrv, pTransfer, i /* Entry index */); AssertPtrBreakStmt(pszEntry, rc = VERR_NO_MEMORY); if (i > 0) diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp index 5856c62d3dab..203ebec5ec64 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardHttpServer.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardHttpServer.cpp 114989 2026-08-11 14:13:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard HTTP server test case. */ @@ -34,9 +34,11 @@ #include #include #include +#include #include #include #include +#include #include #ifdef TESTCASE_WITH_X11 @@ -264,6 +266,778 @@ static void tstDuplicateTransferRegistration(RTTEST hTest, PSHCLTRANSFERCTX pTra } } + +/** Provider wrapper state used for observing and pausing HTTP object access. */ +typedef struct TSTHTTPPROVIDERCTX +{ + /** The wrapped local provider interface. */ + SHCLTXPROVIDERIFACE LocalIface; + /** Signalled whenever a read enters the provider. */ + RTSEMEVENT hReadEntered; + /** Releases all reads paused by the test. */ + RTSEMEVENTMULTI hReadContinue; + /** Number of successful object opens. */ + volatile uint32_t cObjOpens; + /** Number of successful object closes. */ + volatile uint32_t cObjCloses; + /** Number of object reads. */ + volatile uint32_t cObjReads; + /** Number of initial reads to pause. */ + uint32_t cReadsToPause; +} TSTHTTPPROVIDERCTX; +/** Pointer to an HTTP provider wrapper state. */ +typedef TSTHTTPPROVIDERCTX *PTSTHTTPPROVIDERCTX; + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjOpen */ +static DECLCALLBACK(int) tstHttpProviderObjOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTHTTPPROVIDERCTX), VERR_INVALID_PARAMETER); + PTSTHTTPPROVIDERCTX pThis = (PTSTHTTPPROVIDERCTX)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + int rc = pThis->LocalIface.pfnObjOpen(pCtx, pCreateParms, phObj); + if (RT_SUCCESS(rc)) + ASMAtomicIncU32(&pThis->cObjOpens); + return rc; +} + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjClose */ +static DECLCALLBACK(int) tstHttpProviderObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTHTTPPROVIDERCTX), VERR_INVALID_PARAMETER); + PTSTHTTPPROVIDERCTX pThis = (PTSTHTTPPROVIDERCTX)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + int rc = pThis->LocalIface.pfnObjClose(pCtx, hObj); + if (RT_SUCCESS(rc)) + ASMAtomicIncU32(&pThis->cObjCloses); + return rc; +} + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjRead */ +static DECLCALLBACK(int) tstHttpProviderObjRead(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, void *pvData, + uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTHTTPPROVIDERCTX), VERR_INVALID_PARAMETER); + PTSTHTTPPROVIDERCTX pThis = (PTSTHTTPPROVIDERCTX)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + uint32_t const iRead = ASMAtomicIncU32(&pThis->cObjReads); + if (iRead <= pThis->cReadsToPause) + { + int rc = RTSemEventSignal(pThis->hReadEntered); + if (RT_SUCCESS(rc)) + rc = RTSemEventMultiWait(pThis->hReadContinue, RT_MS_30SEC); + if (RT_FAILURE(rc)) + return rc; + } + + return pThis->LocalIface.pfnObjRead(pCtx, hObj, pvData, cbData, fFlags, pcbRead); +} + +/** + * Initializes an observable local provider. + * + * @returns VBox status code. + * @param pThis Provider wrapper state to initialize. + * @param pProvider Provider interface to initialize. + */ +static int tstHttpProviderInit(PTSTHTTPPROVIDERCTX pThis, PSHCLTXPROVIDER pProvider) +{ + RT_ZERO(*pThis); + RT_ZERO(*pProvider); + + int rc = RTSemEventCreate(&pThis->hReadEntered); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventMultiCreate(&pThis->hReadContinue); + if (RT_SUCCESS(rc)) + { + AssertPtrReturn(ShClTransferProviderLocalQueryInterface(pProvider), VERR_INTERNAL_ERROR); + pThis->LocalIface = pProvider->Interface; + + pProvider->Interface.pfnObjOpen = tstHttpProviderObjOpen; + pProvider->Interface.pfnObjClose = tstHttpProviderObjClose; + pProvider->Interface.pfnObjRead = tstHttpProviderObjRead; + pProvider->pvUser = pThis; + pProvider->cbUser = sizeof(*pThis); + return VINF_SUCCESS; + } + + RTSemEventDestroy(pThis->hReadEntered); + pThis->hReadEntered = NIL_RTSEMEVENT; + } + + return rc; +} + +/** + * Terminates an observable local provider. + * + * @param pThis Provider wrapper state to terminate. + */ +static void tstHttpProviderTerm(PTSTHTTPPROVIDERCTX pThis) +{ + if (pThis->hReadContinue != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiSignal(pThis->hReadContinue); + RTSemEventMultiDestroy(pThis->hReadContinue); + pThis->hReadContinue = NIL_RTSEMEVENTMULTI; + } + if (pThis->hReadEntered != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pThis->hReadEntered); + pThis->hReadEntered = NIL_RTSEMEVENT; + } +} + +/** + * Creates a deterministic file for an HTTP lifetime test. + * + * @returns VBox status code. + * @param pszPath File path. + * @param cbFile File size in bytes. + */ +static int tstCreatePatternFile(const char *pszPath, size_t cbFile) +{ + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszPath, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + uint8_t abBuf[_64K]; + for (size_t i = 0; i < sizeof(abBuf); i++) + abBuf[i] = (uint8_t)(i * 131U + 17U); + + while (cbFile > 0 && RT_SUCCESS(rc)) + { + size_t const cbToWrite = RT_MIN(cbFile, sizeof(abBuf)); + rc = RTFileWrite(hFile, abBuf, cbToWrite, NULL); + cbFile -= cbToWrite; + } + + int rc2 = RTFileClose(hFile); + if (RT_SUCCESS(rc)) + rc = rc2; + } + return rc; +} + +/** + * Creates and registers one transfer with both a transfer context and HTTP server. + * + * @returns VBox status code. + * @param pTransferCtx Transfer context to register with. + * @param pSrv HTTP server to register with. + * @param pszPath Local path to serve. + * @param pProvider Provider to use. + * @param idTransfer Transfer ID to request, or NIL_SHCLTRANSFERID for an automatically allocated ID. + * @param ppTransfer Where to return the transfer on success. + */ +static int tstCreateRegisteredTransfer(PSHCLTRANSFERCTX pTransferCtx, PSHCLHTTPSERVER pSrv, const char *pszPath, + PSHCLTXPROVIDER pProvider, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) +{ + PSHCLTRANSFER pTransfer = NULL; + bool fCtxRegistered = false; + bool fHttpRegistered = false; + + int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); + if (RT_SUCCESS(rc)) + rc = ShClTransferSetProvider(pTransfer, pProvider); + if (RT_SUCCESS(rc)) + rc = ShClTransferRootsSetFromPath(pTransfer, pszPath); + if (RT_SUCCESS(rc)) + rc = ShClTransferInit(pTransfer); + if (RT_SUCCESS(rc)) + { + if (idTransfer == NIL_SHCLTRANSFERID) + rc = ShClTransferCtxRegister(pTransferCtx, pTransfer, NULL); + else + rc = ShClTransferCtxRegisterById(pTransferCtx, pTransfer, idTransfer); + fCtxRegistered = RT_SUCCESS(rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferHttpServerRegisterTransfer(pSrv, pTransfer); + fHttpRegistered = RT_SUCCESS(rc); + } + + if (RT_SUCCESS(rc)) + { + *ppTransfer = pTransfer; + return VINF_SUCCESS; + } + + if (fHttpRegistered) + ShClTransferHttpServerUnregisterTransfer(pSrv, pTransfer); + if (fCtxRegistered) + ShClTransferCtxUnregisterById(pTransferCtx, ShClTransferGetID(pTransfer)); + if (pTransfer) + ShClTransferDestroy(pTransfer); + return rc; +} + +/** HTTP GET worker context. */ +typedef struct TSTHTTPGETCTX +{ + /** URL to download. */ + const char *pszUrl; + /** Destination file path. */ + const char *pszDst; + /** Result of the HTTP operation. */ + int rc; +} TSTHTTPGETCTX; +/** Pointer to an HTTP GET worker context. */ +typedef TSTHTTPGETCTX *PTSTHTTPGETCTX; + +/** Performs an HTTP GET on a worker thread. */ +static DECLCALLBACK(int) tstHttpGetThread(RTTHREAD hThread, void *pvUser) +{ + PTSTHTTPGETCTX pCtx = (PTSTHTTPGETCTX)pvUser; + + int rc = RTThreadUserSignal(hThread); + if (RT_SUCCESS(rc)) + { + RTHTTP hClient; + rc = RTHttpCreate(&hClient); + if (RT_SUCCESS(rc)) + { + rc = RTHttpSetProxy(hClient, NULL /* pszProxyUrl */, 0 /* uPort */, + NULL /* pszProxyUser */, NULL /* pszProxyPwd */); + if (RT_SUCCESS(rc)) + rc = RTHttpGetFile(hClient, pCtx->pszUrl, pCtx->pszDst); + + int rc2 = RTHttpDestroy(hClient); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + + pCtx->rc = rc; + return rc; +} + +/** HTTP transfer unregister worker context. */ +typedef struct TSTHTTPUNREGISTERCTX +{ + /** HTTP server to unregister from. */ + PSHCLHTTPSERVER pSrv; + /** Transfer to unregister. */ + PSHCLTRANSFER pTransfer; + /** Result of the unregister operation. */ + int rc; +} TSTHTTPUNREGISTERCTX; +/** Pointer to an HTTP transfer unregister worker context. */ +typedef TSTHTTPUNREGISTERCTX *PTSTHTTPUNREGISTERCTX; + +/** Unregisters an HTTP transfer on a worker thread. */ +static DECLCALLBACK(int) tstHttpUnregisterThread(RTTHREAD hThread, void *pvUser) +{ + PTSTHTTPUNREGISTERCTX pCtx = (PTSTHTTPUNREGISTERCTX)pvUser; + + int rc = RTThreadUserSignal(hThread); + if (RT_SUCCESS(rc)) + rc = ShClTransferHttpServerUnregisterTransfer(pCtx->pSrv, pCtx->pTransfer); + + pCtx->rc = rc; + return rc; +} + +/** + * Checks that HEAD and sequential GET requests each own and close their object handle. + * + * @param hTest The test handle. + * @param pszTempDir Temporary directory for test files. + */ +static void tstRepeatedRequestHandles(RTTEST hTest, const char *pszTempDir) +{ + RTTestSub(hTest, "request-owned object handles"); + + char szSrcFile[RTPATH_MAX]; + RTTEST_CHECK_RETV(hTest, RTStrPrintf2(szSrcFile, sizeof(szSrcFile), "%s", pszTempDir) > 0); + RTTEST_CHECK_RC_OK_RETV(hTest, RTPathAppend(szSrcFile, sizeof(szSrcFile), "request-handles.bin")); + RTTEST_CHECK_RC_OK_RETV(hTest, tstCreatePatternFile(szSrcFile, _128K)); + + TSTHTTPPROVIDERCTX ProviderCtx; + SHCLTXPROVIDER Provider; + bool fProviderInitialized = false; + bool fServerInitialized = false; + bool fCtxInitialized = false; + PSHCLTRANSFER pTransfer = NULL; + SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; + char *pszUrl = NULL; + RTHTTP hClient = NIL_RTHTTP; + + SHCLHTTPSERVER HttpSrv; + SHCLTRANSFERCTX TransferCtx; + uint16_t uPort = 0; + int rc = tstHttpProviderInit(&ProviderCtx, &Provider); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + fProviderInitialized = true; + rc = ShClTransferHttpServerInit(&HttpSrv); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fServerInitialized = true; + rc = ShClTransferHttpServerStart(&HttpSrv, 32 /* cMaxAttempts */, &uPort); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxInit(&TransferCtx); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fCtxInitialized = true; + rc = ShClTransferCtxBeginSession(&TransferCtx, 101); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = tstCreateRegisteredTransfer(&TransferCtx, &HttpSrv, szSrcFile, &Provider, + NIL_SHCLTRANSFERID, &pTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + idTransfer = ShClTransferGetID(pTransfer); + pszUrl = ShClTransferHttpServerGetUrlA(&HttpSrv, idTransfer, 0 /* idxEntry */); + RTTEST_CHECK(hTest, pszUrl != NULL); + if (!pszUrl) + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + rc = RTHttpCreate(&hClient); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = RTHttpSetProxy(hClient, NULL /* pszProxyUrl */, 0 /* uPort */, + NULL /* pszProxyUser */, NULL /* pszProxyPwd */); + RTTEST_CHECK_RC_OK(hTest, rc); + } + + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < 3; i++) + { + void *pvResponse = NULL; + size_t cbResponse = 0; + rc = RTHttpGetHeaderBinary(hClient, pszUrl, &pvResponse, &cbResponse); + RTTEST_CHECK_RC_OK(hTest, rc); + RTHttpFreeResponse(pvResponse); + if (RT_FAILURE(rc)) + break; + + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjOpens) == i + 1); + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjCloses) == i + 1); + } + } + + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < 2; i++) + { + char szDstFile[RTPATH_MAX]; + RTTEST_CHECK_BREAK(hTest, RTStrPrintf(szDstFile, sizeof(szDstFile), "%s/request-handles-%RU32.bin", + pszTempDir, i) > 0); + + rc = RTHttpGetFile(hClient, pszUrl, szDstFile); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + RTTEST_CHECK_RC_OK(hTest, RTFileCompare(szSrcFile, szDstFile)); + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szDstFile)); + if (RT_FAILURE(rc)) + break; + + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjOpens) == i + 4); + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjCloses) == i + 4); + } + } + + if (hClient != NIL_RTHTTP) + RTTEST_CHECK_RC_OK(hTest, RTHttpDestroy(hClient)); + RTStrFree(pszUrl); + + if (pTransfer) + { + if (fServerInitialized && ShClTransferHttpServerGetTransfer(&HttpSrv, idTransfer)) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pTransfer)); + if (fCtxInitialized && ShClTransferCtxGetTransferById(&TransferCtx, idTransfer) == pTransfer) + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxUnregisterById(&TransferCtx, idTransfer)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferDestroy(pTransfer)); + } + if (fCtxInitialized) + ShClTransferCtxDestroy(&TransferCtx); + if (fServerInitialized) + { + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerDestroy(&HttpSrv)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerDestroy(&HttpSrv)); + } + if (fProviderInitialized) + tstHttpProviderTerm(&ProviderCtx); + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szSrcFile)); +} + +/** + * Checks that a stale transfer key cannot unregister a newer transfer which reused its numeric ID. + * + * @param hTest The test handle. + * @param pszTempDir Temporary directory for test files. + */ +static void tstStaleKeyUnregister(RTTEST hTest, const char *pszTempDir) +{ + RTTestSub(hTest, "stale transfer key unregister"); + + char szSrcFile[RTPATH_MAX]; + RTTEST_CHECK_RETV(hTest, RTStrPrintf2(szSrcFile, sizeof(szSrcFile), "%s", pszTempDir) > 0); + RTTEST_CHECK_RC_OK_RETV(hTest, RTPathAppend(szSrcFile, sizeof(szSrcFile), "stale-key.bin")); + RTTEST_CHECK_RC_OK_RETV(hTest, tstCreatePatternFile(szSrcFile, _4K)); + + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); + RTTEST_CHECK_RETV(hTest, ShClTransferProviderLocalQueryInterface(&Provider) != NULL); + + SHCLHTTPSERVER HttpSrv; + SHCLTRANSFERCTX OldTransferCtx; + SHCLTRANSFERCTX NewTransferCtx; + bool fServerInitialized = false; + bool fOldCtxInitialized = false; + bool fNewCtxInitialized = false; + PSHCLTRANSFER pOldTransfer = NULL; + PSHCLTRANSFER pNewTransfer = NULL; + char *pszOldUrl = NULL; + char *pszNewUrl = NULL; + size_t cbOldUrl = 0; + size_t cbNewUrl = 0; + uint16_t uPort = 0; + + int rc = ShClTransferHttpServerInit(&HttpSrv); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + fServerInitialized = true; + rc = ShClTransferHttpServerStart(&HttpSrv, 32 /* cMaxAttempts */, &uPort); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxInit(&OldTransferCtx); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fOldCtxInitialized = true; + rc = ShClTransferCtxBeginSession(&OldTransferCtx, 201); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxInit(&NewTransferCtx); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fNewCtxInitialized = true; + rc = ShClTransferCtxBeginSession(&NewTransferCtx, 202); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = tstCreateRegisteredTransfer(&OldTransferCtx, &HttpSrv, szSrcFile, &Provider, 42, &pOldTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = tstCreateRegisteredTransfer(&NewTransferCtx, &HttpSrv, szSrcFile, &Provider, 42, &pNewTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + RTTEST_CHECK(hTest, ShClTransferKeyIsValid(ShClTransferGetSessionId(pOldTransfer), 42, + ShClTransferGetGeneration(pOldTransfer))); + RTTEST_CHECK(hTest, ShClTransferKeyIsValid(ShClTransferGetSessionId(pNewTransfer), 42, + ShClTransferGetGeneration(pNewTransfer))); + RTTEST_CHECK(hTest, ShClTransferGetSessionId(pOldTransfer) != ShClTransferGetSessionId(pNewTransfer)); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 2); + + rc = ShClTransferHttpConvertToStringList(&HttpSrv, pOldTransfer, &pszOldUrl, &cbOldUrl); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferHttpConvertToStringList(&HttpSrv, pNewTransfer, &pszNewUrl, &cbNewUrl); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + RTTEST_CHECK(hTest, cbOldUrl == strlen(pszOldUrl) + 1); + RTTEST_CHECK(hTest, cbNewUrl == strlen(pszNewUrl) + 1); + RTTEST_CHECK(hTest, RTStrCmp(pszOldUrl, pszNewUrl) != 0); + + rc = ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pOldTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 1); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransfer(&HttpSrv, 42)); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pOldTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 1); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransfer(&HttpSrv, 42)); + } + if (RT_SUCCESS(rc)) + { + RTHTTP hClient; + rc = RTHttpCreate(&hClient); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + rc = RTHttpSetProxy(hClient, NULL /* pszProxyUrl */, 0 /* uPort */, + NULL /* pszProxyUser */, NULL /* pszProxyPwd */); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + tstMalformedGet(hTest, hClient, pszOldUrl, VERR_HTTP_NOT_FOUND); + + char szDstFile[RTPATH_MAX]; + RTTEST_CHECK(hTest, RTStrPrintf(szDstFile, sizeof(szDstFile), "%s/stale-key-copy.bin", pszTempDir) > 0); + rc = RTHttpGetFile(hClient, pszNewUrl, szDstFile); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + RTTEST_CHECK_RC_OK(hTest, RTFileCompare(szSrcFile, szDstFile)); + if (RTFileExists(szDstFile)) + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szDstFile)); + } + + int rc2 = RTHttpDestroy(hClient); + RTTEST_CHECK_RC_OK(hTest, rc2); + } + } + + RTStrFree(pszNewUrl); + RTStrFree(pszOldUrl); + + if (pNewTransfer) + { + if (fServerInitialized) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pNewTransfer)); + if (fNewCtxInitialized && ShClTransferCtxGetTransferById(&NewTransferCtx, 42) == pNewTransfer) + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxUnregisterById(&NewTransferCtx, 42)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferDestroy(pNewTransfer)); + } + if (pOldTransfer) + { + if (fServerInitialized) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pOldTransfer)); + if (fOldCtxInitialized && ShClTransferCtxGetTransferById(&OldTransferCtx, 42) == pOldTransfer) + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxUnregisterById(&OldTransferCtx, 42)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferDestroy(pOldTransfer)); + } + if (fNewCtxInitialized) + ShClTransferCtxDestroy(&NewTransferCtx); + if (fOldCtxInitialized) + ShClTransferCtxDestroy(&OldTransferCtx); + if (fServerInitialized) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerDestroy(&HttpSrv)); + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szSrcFile)); +} + +/** + * Checks that unregister waits for an active request while retaining its transfer until request end. + * + * @param hTest The test handle. + * @param pszTempDir Temporary directory for test files. + */ +static void tstUnregisterDuringRequest(RTTEST hTest, const char *pszTempDir) +{ + RTTestSub(hTest, "unregister during active request"); + + char szSrcFile[RTPATH_MAX]; + char szDstFile[RTPATH_MAX]; + RTTEST_CHECK_RETV(hTest, RTStrPrintf2(szSrcFile, sizeof(szSrcFile), "%s", pszTempDir) > 0); + RTTEST_CHECK_RC_OK_RETV(hTest, RTPathAppend(szSrcFile, sizeof(szSrcFile), "active-request.bin")); + RTTEST_CHECK_RETV(hTest, RTStrPrintf2(szDstFile, sizeof(szDstFile), "%s", pszTempDir) > 0); + RTTEST_CHECK_RC_OK_RETV(hTest, RTPathAppend(szDstFile, sizeof(szDstFile), "active-request-copy.bin")); + RTTEST_CHECK_RC_OK_RETV(hTest, tstCreatePatternFile(szSrcFile, _128K)); + + TSTHTTPPROVIDERCTX ProviderCtx; + SHCLTXPROVIDER Provider; + bool fProviderInitialized = false; + bool fServerInitialized = false; + bool fCtxInitialized = false; + PSHCLTRANSFER pTransfer = NULL; + SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; + char *pszUrl = NULL; + RTTHREAD hGetThread = NIL_RTTHREAD; + RTTHREAD hUnregisterThread = NIL_RTTHREAD; + bool fGetThreadStarted = false; + bool fUnregisterThreadStarted = false; + bool fUnregisterThreadJoined = false; + + SHCLHTTPSERVER HttpSrv; + SHCLTRANSFERCTX TransferCtx; + uint16_t uPort = 0; + int rc = tstHttpProviderInit(&ProviderCtx, &Provider); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + fProviderInitialized = true; + ProviderCtx.cReadsToPause = 1; + rc = ShClTransferHttpServerInit(&HttpSrv); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fServerInitialized = true; + rc = ShClTransferHttpServerStart(&HttpSrv, 32 /* cMaxAttempts */, &uPort); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxInit(&TransferCtx); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fCtxInitialized = true; + rc = ShClTransferCtxBeginSession(&TransferCtx, 301); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = tstCreateRegisteredTransfer(&TransferCtx, &HttpSrv, szSrcFile, &Provider, + NIL_SHCLTRANSFERID, &pTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + idTransfer = ShClTransferGetID(pTransfer); + pszUrl = ShClTransferHttpServerGetUrlA(&HttpSrv, idTransfer, 0 /* idxEntry */); + RTTEST_CHECK(hTest, pszUrl != NULL); + if (!pszUrl) + rc = VERR_NO_MEMORY; + } + + TSTHTTPGETCTX GetCtx = { pszUrl, szDstFile, VERR_IPE_UNINITIALIZED_STATUS }; + TSTHTTPUNREGISTERCTX UnregisterCtx = { &HttpSrv, pTransfer, VERR_IPE_UNINITIALIZED_STATUS }; + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&hGetThread, tstHttpGetThread, &GetCtx, 0 /* cbStack */, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "ShClHttpGet"); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fGetThreadStarted = true; + rc = RTThreadUserWait(hGetThread, RT_MS_5SEC); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = RTSemEventWait(ProviderCtx.hReadEntered, RT_MS_10SEC); + RTTEST_CHECK_RC_OK(hTest, rc); + } + + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&hUnregisterThread, tstHttpUnregisterThread, &UnregisterCtx, 0 /* cbStack */, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "ShClHttpUnreg"); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fUnregisterThreadStarted = true; + rc = RTThreadUserWait(hUnregisterThread, RT_MS_5SEC); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + /* + * Unregister removes the transfer from lookup before waiting for the + * active request. Wait for that point so the thread timeout below + * proves the request drain, rather than merely a scheduling delay. + */ + for (uint32_t i = 0; i < 1000 && ShClTransferHttpServerGetTransferCount(&HttpSrv) != 0; ++i) + RTThreadSleep(1); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 0); + + int rcUnregisterThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rcWait = RTThreadWait(hUnregisterThread, 100 /* msTimeout */, &rcUnregisterThread); + RTTEST_CHECK_RC(hTest, rcWait, VERR_TIMEOUT); + } + + if (fProviderInitialized) + RTTEST_CHECK_RC_OK(hTest, RTSemEventMultiSignal(ProviderCtx.hReadContinue)); + if (fGetThreadStarted) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int rcWait = RTThreadWait(hGetThread, RT_MS_10SEC, &rcThread); + RTTEST_CHECK_RC_OK(hTest, rcWait); + if (RT_FAILURE(rcWait)) + rcWait = RTThreadWait(hGetThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rcWait)) + { + RTTEST_CHECK_RC_OK(hTest, rcThread); + RTTEST_CHECK_RC_OK(hTest, GetCtx.rc); + if (RT_SUCCESS(GetCtx.rc)) + RTTEST_CHECK_RC_OK(hTest, RTFileCompare(szSrcFile, szDstFile)); + } + } + if (fUnregisterThreadStarted) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int rcWait = RTThreadWait(hUnregisterThread, RT_MS_10SEC, &rcThread); + RTTEST_CHECK_RC_OK(hTest, rcWait); + if (RT_FAILURE(rcWait)) + rcWait = RTThreadWait(hUnregisterThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rcWait)) + { + fUnregisterThreadJoined = true; + RTTEST_CHECK_RC_OK(hTest, rcThread); + RTTEST_CHECK_RC_OK(hTest, UnregisterCtx.rc); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 0); + RTTEST_CHECK(hTest, !ShClTransferHttpServerGetTransfer(&HttpSrv, idTransfer)); + } + } + + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjOpens) == 1); + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjCloses) == 1); + + RTStrFree(pszUrl); + if (fUnregisterThreadStarted && !fUnregisterThreadJoined) + { + int rcThread; + RTTEST_CHECK_RC_OK(hTest, RTThreadWait(hUnregisterThread, RT_INDEFINITE_WAIT, &rcThread)); + } + if (pTransfer) + { + if (fServerInitialized && ShClTransferHttpServerGetTransfer(&HttpSrv, idTransfer)) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pTransfer)); + if (fCtxInitialized && ShClTransferCtxGetTransferById(&TransferCtx, idTransfer) == pTransfer) + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxUnregisterById(&TransferCtx, idTransfer)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferDestroy(pTransfer)); + } + if (fCtxInitialized) + ShClTransferCtxDestroy(&TransferCtx); + if (fServerInitialized) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerDestroy(&HttpSrv)); + if (fProviderInitialized) + tstHttpProviderTerm(&ProviderCtx); + if (RTFileExists(szDstFile)) + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szDstFile)); + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szSrcFile)); +} + /** * Run a manual (i.e. interacive) test. * @@ -504,9 +1278,11 @@ int main(int argc, char *argv[]) SHCLTRANSFERCTX TxCtx; RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxInit(&TxCtx)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxBeginSession(&TxCtx, 1)); /* Query the local transfer provider. */ SHCLTXPROVIDER Provider; + RT_ZERO(Provider); RTTESTI_CHECK(ShClTransferProviderLocalQueryInterface(&Provider) != NULL); /* Parse options again, but this time we only fetch all files we want to serve. @@ -534,6 +1310,13 @@ int main(int argc, char *argv[]) RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardHttpServer-XXXXXX")); RTTEST_CHECK_RC_OK(hTest, RTDirCreateTemp(szTempDir, 0700)); + if (!g_fManual) + { + tstRepeatedRequestHandles(hTest, szTempDir); + tstStaleKeyUnregister(hTest, szTempDir); + tstUnregisterDuringRequest(hTest, szTempDir); + } + tstDuplicateTransferRegistration(hTest, &TxCtx, &HttpSrv, szTempDir, &Provider); if (!g_fManual) From 5ccc196ad1b370b10250d330ca48498bc153901f Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 11 Aug 2026 14:34:45 +0000 Subject: [PATCH 093/176] Shared Clipboard/Transfers: More code for transfer registration and server lifetime; give each HTTP request its own object handle, retain registrations while requests are active, drain callbacks before destroying transfers, and use the complete session/transfer/generation identity for lookups [build fix]. bugref:4697 svn:sync-xref-src-repo-rev: r174831 --- .../GuestHost/SharedClipboard/clipboard-transfers-http.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp index 26c978b4ab61..ec2cd47bceb3 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-http.cpp 114989 2026-08-11 14:13:05Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-http.cpp 114990 2026-08-11 14:34:45Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts. */ @@ -558,6 +558,7 @@ static DECLCALLBACK(int) shClTransferHttpBegin(PRTHTTPCALLBACKDATA pData, PRTHTT /** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestEnd */ static DECLCALLBACK(int) shClTransferHttpEnd(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq) { + RT_NOREF(pData); Assert(pData->cbUser == sizeof(SHCLHTTPSERVER)); LogRel2(("Shared Clipboard: HTTP request end\n")); From 245401d969332a24a87c07bd0eff23b0845a4ea8 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 11 Aug 2026 14:46:01 +0000 Subject: [PATCH 094/176] Shared Clipboard/HostService: Removed lots of files not needed in the service binary anymore, as those now all live in Main. This also drops the 'ApplicationServices' framework linking depedency on macOS, as there is no pasteboard access from this service anymore. bugref:4697 svn:sync-xref-src-repo-rev: r174834 --- .../HostServices/SharedClipboard/Makefile.kmk | 38 +------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/src/VBox/HostServices/SharedClipboard/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/Makefile.kmk index 958df11c2287..d1faef100249 100644 --- a/src/VBox/HostServices/SharedClipboard/Makefile.kmk +++ b/src/VBox/HostServices/SharedClipboard/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114974 2026-08-10 17:47:03Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114993 2026-08-11 14:46:01Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the Shared Clipboard Host Service. # @@ -48,55 +48,21 @@ VBoxSharedClipboard_SOURCES = \ VBoxSharedClipboardSvc-backend.cpp \ VBoxSharedClipboardSvc-client.cpp \ VBoxSharedClipboardSvc-host.cpp \ - $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp VBoxSharedClipboard_SOURCES.win = \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ VBoxSharedClipboardSvc.rc ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - VBoxSharedClipboard_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - VBoxSharedClipboard_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp - endif VBoxSharedClipboard_SOURCES += \ VBoxSharedClipboardSvc-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp - VBoxSharedClipboard_SOURCES.win += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp endif VBoxSharedClipboard_LIBS = \ $(LIB_RUNTIME) VBoxSharedClipboard_LDFLAGS.darwin = \ - -framework ApplicationServices -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedClipboard.dylib - -if 0 ## Disabled for now; needs to be adapted to the new protocol first. - if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) - if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) - # - # Set this in LocalConfig.kmk if you are working on the X11 clipboard service - # to automatically run the unit test at build time. - # OTHERS += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run - PROGRAMS += tstClipboardX11-2 - TESTING += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run - tstClipboardX11-2_TEMPLATE = VBoxR3TstExe - tstClipboardX11-2_DEFS = VBOX_WITH_HGCM TESTCASE - tstClipboardX11-2_SOURCES = VBoxSharedClipboardSvc-x11.cpp - tstClipboardX11-2_LIBS = $(LIB_RUNTIME) - tstClipboardX11-2_CLEAN = $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run - - $$(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run: $$(tstClipboardX11-2_1_STAGE_TARGET) - export VBOX_LOG_DEST=nofile; $(tstClipboardX11-2_1_STAGE_TARGET) quiet - $(QUIET)$(APPEND) -t "$@" "done" - endif # 1of ($(KBUILD_TARGET),freebsd linux netbsd openbsd solaris) - endif -endif + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedClipboard.dylib include $(FILE_KBUILD_SUB_FOOTER) From 3fc0e604f72a7afc9a6d35f24786f45b1d2db0fb Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Tue, 11 Aug 2026 15:32:50 +0000 Subject: [PATCH 095/176] WDDM: paging transfers sysmem/resource. bugref:10934. svn:sync-xref-src-repo-rev: r174835 --- .../win/Graphics/Video/mp/wddm/VBoxMPTypes.h | 8 +- .../Video/mp/wddm/gallium/VBoxMPDX.cpp | 347 +++++++++++++++++- 2 files changed, 353 insertions(+), 2 deletions(-) diff --git a/src/VBox/Additions/win/Graphics/Video/mp/wddm/VBoxMPTypes.h b/src/VBox/Additions/win/Graphics/Video/mp/wddm/VBoxMPTypes.h index cf577784810e..3f0a476ce9be 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/wddm/VBoxMPTypes.h +++ b/src/VBox/Additions/win/Graphics/Video/mp/wddm/VBoxMPTypes.h @@ -1,4 +1,4 @@ -/* $Id: VBoxMPTypes.h 114359 2026-06-15 14:38:33Z vitali.pelenjow@oracle.com $ */ +/* $Id: VBoxMPTypes.h 114994 2026-08-11 15:32:50Z vitali.pelenjow@oracle.com $ */ /** @file * VBox WDDM Miniport driver */ @@ -217,6 +217,12 @@ typedef struct VBOXWDDM_ALLOCATION uint32_t sid; /* For surfaces. */ uint32_t SegmentId; /* Segment of the allocation. */ AVLU32TREE treeInstances; /* DX_ALLOCATION_INSTANCE */ + struct + { + uint32_t fReadbackCompleted : 1; + uint32_t fReserved : 31; + } flags; + uint64_t u64LastReferencedCommandFence; } dx; #endif /* VBOX_WITH_VMSVGA3D_DX */ } VBOXWDDM_ALLOCATION, *PVBOXWDDM_ALLOCATION; diff --git a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPDX.cpp b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPDX.cpp index b4e700456794..e578104076a0 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPDX.cpp +++ b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPDX.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxMPDX.cpp 114469 2026-06-22 09:48:44Z vitali.pelenjow@oracle.com $ */ +/* $Id: VBoxMPDX.cpp 114994 2026-08-11 15:32:50Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox Windows Guest Graphics Driver - Direct3D (DX) driver function. */ @@ -757,6 +757,330 @@ static NTSTATUS svgaPTVRAM2SysMem(PVBOXMP_DEVEXT pDevExt, PVBOXWDDM_ALLOCATION p } +NTSTATUS SvgaCommandFence(VBOXWDDM_EXT_VMSVGA *pSvga, + uint64_t u64FenceValue, + void *pvCmd, + uint32_t cbReserved, + uint32_t *pcbCmd) +{ + uint32_t cbRequired = sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdDXMobFence64); + + *pcbCmd += cbRequired; + if (cbReserved < cbRequired) + return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + + /* Generate commands. */ + uint8_t *pu8Cmd = (uint8_t *)pvCmd; + SVGA3dCmdHeader *pHdr; + + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_DX_MOB_FENCE_64; + pHdr->size = sizeof(SVGA3dCmdDXMobFence64); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdDXMobFence64 *pCmd = (SVGA3dCmdDXMobFence64 *)pu8Cmd; + pCmd->value = u64FenceValue; + pCmd->mobId = pSvga->mobidMiniport; + pCmd->mobOffset = RT_OFFSETOF(VMSVGAMINIPORTMOB, u64MobFence); + pu8Cmd += sizeof(*pCmd); + } + + Assert((uintptr_t)pu8Cmd - (uintptr_t)pvCmd == cbRequired); + + return STATUS_SUCCESS; +} + + +DECLINLINE(int) SvgaFenceCmp64(uint64_t u64FenceA, uint64_t u64FenceB) +{ + if ( u64FenceA < u64FenceB + || u64FenceA - u64FenceB > UINT64_MAX / 2) + return -1; /* FenceA is newer than FenceB. */ + + return u64FenceA > u64FenceB; +} + + +static NTSTATUS svgaPTHost2SysMem(PVBOXMP_DEVEXT pDevExt, PVBOXWDDM_ALLOCATION pAllocation, + DXGKARG_BUILDPAGINGBUFFER *pBuildPagingBuffer, uint32_t *pcbCommands) +{ + GALOG(("PTHost2SysMem: MDL: offset %u, size %u, 0x%p/%u, stage %u\n", + pBuildPagingBuffer->Transfer.Destination.pMdl->ByteOffset, + pBuildPagingBuffer->Transfer.Destination.pMdl->ByteCount, + pBuildPagingBuffer->Transfer.Destination.pMdl, + pBuildPagingBuffer->Transfer.MdlOffset, + pBuildPagingBuffer->MultipassOffset)); + + /* + * Steps that take partial transfers into account. + * If MultipassOffset is 1, then wait for the MOB fence that was submitted for deleting of MOB. + * - if surface was not yet read back, that is mobid != SVGA3D_INVALID_ID: + * - readback surface; + * - unbind and delete current mob (pGbo still exists in the guest memory); + * - set mobid to SVGA3D_INVALID_ID; + * - set MultipassOffset to 1 and return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER. + * + * - memcpy from dx.pGbo to the MDL. + */ + + VBOXWDDM_EXT_VMSVGA *pSvga = pDevExt->pGa->hw.pSvga; + + /* Segment 3 allocations have one instance. */ + DX_ALLOCATION_INSTANCE *pInstance = svgaGetAllocationInstance(pAllocation, 0); + AssertReturn(pAllocation->dx.SegmentId == 3 && pInstance, STATUS_INVALID_PARAMETER); + + if (!pAllocation->dx.flags.fReadbackCompleted) + { + if (pBuildPagingBuffer->MultipassOffset == 0) + { + /* Start paging transfer for this surface by reading back its content. */ + /* MOB should be bound at this point. */ + Assert(pInstance->mobid != SVGA3D_INVALID_ID); + + /* Read back, unbind and destroy mob. */ + + /* + * How many bytes are required in the DMA buffer + */ + uint32_t cbRequired = 0; + SvgaMobDestroy(pSvga, SVGA3D_INVALID_ID, NULL, 0, &cbRequired); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdReadbackGBSurface); + SvgaCommandFence(pSvga, 0, NULL, 0, &cbRequired); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdBindGBSurface); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdInvalidateGBSurface); + + if (pBuildPagingBuffer->DmaSize < cbRequired) + return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + + uint8_t *pu8Cmd = (uint8_t *)pBuildPagingBuffer->pDmaBuffer; + SVGA3dCmdHeader *pHdr; + + /* + * Readback. + */ + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_READBACK_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdReadbackGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdReadbackGBSurface *pCmd = (SVGA3dCmdReadbackGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pu8Cmd += sizeof(*pCmd); + } + + /* + * Fence that indicates that readback has been completed. + */ + pAllocation->dx.u64LastReferencedCommandFence = ASMAtomicIncU64(&pSvga->u64MobFence); + + uint32_t cbCmd = 0; + NTSTATUS Status = SvgaCommandFence(pSvga, pAllocation->dx.u64LastReferencedCommandFence, pu8Cmd, + cbRequired - ((uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer), + &cbCmd); + AssertReturn(NT_SUCCESS(Status), Status); + pu8Cmd += cbCmd; + + /* + * Unbind the mob. + */ + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_BIND_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdBindGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdBindGBSurface *pCmd = (SVGA3dCmdBindGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pCmd->mobid = SVGA3D_INVALID_ID; + pu8Cmd += sizeof(*pCmd); + } + + /* + * Destroy the mob. + */ + cbCmd = 0; + Status = SvgaMobDestroy(pSvga, pInstance->mobid, pu8Cmd, + cbRequired - ((uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer), + &cbCmd); + AssertReturn(NT_SUCCESS(Status), Status); + pu8Cmd += cbCmd; + + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_INVALIDATE_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdInvalidateGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdInvalidateGBSurface *pCmd = (SVGA3dCmdInvalidateGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pu8Cmd += sizeof(*pCmd); + } + + *pcbCommands = (uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer; + Assert(*pcbCommands == cbRequired); + + pInstance->mobid = SVGA3D_INVALID_ID; + + /* Continue with the next stage. */ + pBuildPagingBuffer->MultipassOffset = 1; + + /* Return this to submit the current commands and get to the next stage of this transfer. */ + return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + } + + if (pBuildPagingBuffer->MultipassOffset == 1) + { + /* Wait until submitted commands are processed by waiting for the fence. */ + uint64_t const u64CurrentCommandFence = ASMAtomicReadU64(&pSvga->pMiniportMobData->u64MobFence); + if (SvgaFenceCmp64(pAllocation->dx.u64LastReferencedCommandFence, u64CurrentCommandFence) > 0) + return STATUS_GRAPHICS_ALLOCATION_BUSY; + + pAllocation->dx.flags.fReadbackCompleted = true; + } + } + + /* Surface has been already read back to its GBO. */ + Assert(pAllocation->dx.flags.fReadbackCompleted); + Assert(pInstance->mobid == SVGA3D_INVALID_ID); + + /* Copy data from GBO to MDL. */ + Assert(!pInstance->pGbo->flags.fMdl); /* The GBO must be allocated by the driver. */ + + /* Check that transfer is within GBO memory. */ + AssertReturn( pBuildPagingBuffer->Transfer.TransferOffset < pInstance->pGbo->cbGbo + && pBuildPagingBuffer->Transfer.TransferSize <= pInstance->pGbo->cbGbo - pBuildPagingBuffer->Transfer.TransferOffset, + STATUS_INVALID_PARAMETER); + + /* Src is the GBO. */ + uint8_t const *pu8Src = (uint8_t *)RTR0MemObjAddress(pInstance->pGbo->hMemObj); + AssertReturn(pu8Src, STATUS_UNSUCCESSFUL); + pu8Src += pBuildPagingBuffer->Transfer.TransferOffset; + + /* Dst is the MDL. */ + uint8_t *pu8Dst = (uint8_t *)MmGetSystemAddressForMdlSafe( + pBuildPagingBuffer->Transfer.Destination.pMdl, NormalPagePriority); + AssertReturn(pu8Dst, STATUS_UNSUCCESSFUL); + pu8Dst += pBuildPagingBuffer->Transfer.MdlOffset * PAGE_SIZE; + + memcpy(pu8Dst, pu8Src, pBuildPagingBuffer->Transfer.TransferSize); + + /** @todo 'pInstance->pGbo' can be deallocated on 'pBuildPagingBuffer->Transfer.Flags.TransferEnd' + * and reallocated in svgaPTSysMem2Host. + */ + + return STATUS_SUCCESS; +} + + +static NTSTATUS svgaPTSysMem2Host(PVBOXMP_DEVEXT pDevExt, PVBOXWDDM_ALLOCATION pAllocation, + DXGKARG_BUILDPAGINGBUFFER *pBuildPagingBuffer, uint32_t *pcbCommands) +{ + GALOG(("PTSysMem2Host: MDL: offset %u, size %u, 0x%p/%u, stage %u\n", + pBuildPagingBuffer->Transfer.Source.pMdl->ByteOffset, + pBuildPagingBuffer->Transfer.Source.pMdl->ByteCount, + pBuildPagingBuffer->Transfer.Source.pMdl, + pBuildPagingBuffer->Transfer.MdlOffset, + pBuildPagingBuffer->MultipassOffset)); + + /* Copy data from MDL to the GBO until end of transfer (pBuildPagingBuffer->Transfer.Flags.TransferEnd). + * Create a mob, bind the surface and update it on TransferEnd. + */ + + VBOXWDDM_EXT_VMSVGA *pSvga = pDevExt->pGa->hw.pSvga; + + /* Segment 3 allocations have one instance. */ + DX_ALLOCATION_INSTANCE *pInstance = svgaGetAllocationInstance(pAllocation, 0); + AssertReturn(pAllocation->dx.SegmentId == 3 && pInstance, STATUS_INVALID_PARAMETER); + + Assert(pAllocation->dx.flags.fReadbackCompleted); + Assert(pInstance->mobid == SVGA3D_INVALID_ID); + + /* Copy data from MDL to GBO. */ + Assert(!pInstance->pGbo->flags.fMdl); /* The GBO must be allocated by the driver. */ + + /* Check that transfer is within GBO memory. */ + AssertReturn( pBuildPagingBuffer->Transfer.TransferOffset < pInstance->pGbo->cbGbo + && pBuildPagingBuffer->Transfer.TransferSize <= pInstance->pGbo->cbGbo - pBuildPagingBuffer->Transfer.TransferOffset, + STATUS_INVALID_PARAMETER); + + /* Src is the MDL. */ + uint8_t const *pu8Src = (uint8_t *)MmGetSystemAddressForMdlSafe( + pBuildPagingBuffer->Transfer.Source.pMdl, NormalPagePriority); + AssertReturn(pu8Src, STATUS_UNSUCCESSFUL); + pu8Src += pBuildPagingBuffer->Transfer.MdlOffset * PAGE_SIZE; + + /* Dst is the GBO. */ + uint8_t *pu8Dst = (uint8_t *)RTR0MemObjAddress(pInstance->pGbo->hMemObj); + AssertReturn(pu8Dst, STATUS_UNSUCCESSFUL); + pu8Dst += pBuildPagingBuffer->Transfer.TransferOffset; + + memcpy(pu8Dst, pu8Src, pBuildPagingBuffer->Transfer.TransferSize); + + if (pBuildPagingBuffer->Transfer.Flags.TransferEnd) + { + /* + * Create a mob, bind the surface and update it. + */ + uint32_t cbRequired = 0; + SvgaMobDefine(pSvga, SVGA3D_INVALID_ID, NULL, 0, &cbRequired); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdBindGBSurface); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdUpdateGBSurface); + + if (pBuildPagingBuffer->DmaSize < cbRequired) + return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + + uint8_t *pu8Cmd = (uint8_t *)pBuildPagingBuffer->pDmaBuffer; + SVGA3dCmdHeader *pHdr; + + /* Create a new mob */ + /// @todo svgaDefineMobForAllocation? + NTSTATUS Status = SvgaMobAlloc(pSvga, &pInstance->mobid, pInstance->pGbo); + AssertReturn(NT_SUCCESS(Status), Status); + + uint32_t cbCmd = 0; + Status = SvgaMobDefine(pSvga, pInstance->mobid, pu8Cmd, + cbRequired - ((uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer), + &cbCmd); + AssertReturnStmt(NT_SUCCESS(Status), SvgaMobFree(pSvga, &pInstance->mobid), Status); + pu8Cmd += cbCmd; + + /* Bind the surface */ + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_BIND_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdBindGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdBindGBSurface *pCmd = (SVGA3dCmdBindGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pCmd->mobid = pInstance->mobid; + pu8Cmd += sizeof(*pCmd); + } + + /* Update the surface */ + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_UPDATE_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdUpdateGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdUpdateGBSurface *pCmd = (SVGA3dCmdUpdateGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pu8Cmd += sizeof(*pCmd); + } + + *pcbCommands = (uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer; + Assert(*pcbCommands == cbRequired); + + pAllocation->dx.flags.fReadbackCompleted = false; + } + + return STATUS_SUCCESS; +} + + static NTSTATUS svgaPagingTransfer(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGINGBUFFER *pBuildPagingBuffer, uint32_t *pcbCommands) { VBOXWDDM_EXT_VMSVGA *pSvga = pDevExt->pGa->hw.pSvga; @@ -773,9 +1097,19 @@ static NTSTATUS svgaPagingTransfer(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGINGBU && pBuildPagingBuffer->Transfer.TransferSize <= cbAllocation - pBuildPagingBuffer->Transfer.TransferOffset, STATUS_INVALID_PARAMETER); + GALOG(("%u -> %u, offset %u, size %u, cbAllocation %u\n", + pBuildPagingBuffer->Transfer.Source.SegmentId, + pBuildPagingBuffer->Transfer.Destination.SegmentId, + pBuildPagingBuffer->Transfer.TransferOffset, + pBuildPagingBuffer->Transfer.TransferSize, + cbAllocation)); + if (pBuildPagingBuffer->Transfer.TransferSize == 0) return STATUS_SUCCESS; + if (!pBuildPagingBuffer->Transfer.Flags.AllocationIsIdle) + return STATUS_GRAPHICS_ALLOCATION_BUSY; + NTSTATUS Status = STATUS_SUCCESS; RT_NOREF(pcbCommands); @@ -795,6 +1129,8 @@ static NTSTATUS svgaPagingTransfer(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGINGBU Status = svgaPTSysMem2VRAM(pDevExt, pAllocation, pBuildPagingBuffer); } } + else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 3) /* To Host */ + Status = svgaPTSysMem2Host(pDevExt, pAllocation, pBuildPagingBuffer, pcbCommands); else DEBUG_BREAKPOINT_TEST(); break; @@ -817,6 +1153,15 @@ static NTSTATUS svgaPagingTransfer(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGINGBU break; } + case 3: /* From a host resource. */ + { + if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) /* To system memory */ + Status = svgaPTHost2SysMem(pDevExt, pAllocation, pBuildPagingBuffer, pcbCommands); + else + DEBUG_BREAKPOINT_TEST(); + break; + } + default: DEBUG_BREAKPOINT_TEST(); } From dbda717833f0851041f27fb77edcadd4f605d522 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Wed, 12 Aug 2026 09:59:45 +0000 Subject: [PATCH 096/176] NetworkServices/tstVBoxNetDhcpd: Try to fix testcases on testboxes under load, added more diagnostics. svn:sync-xref-src-repo-rev: r174836 --- .../Dhcpd/testcase/tstVBoxNetDhcpd.cpp | 1284 +++++++++++++++++ 1 file changed, 1284 insertions(+) create mode 100644 src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp new file mode 100644 index 000000000000..665e070a30e9 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp @@ -0,0 +1,1284 @@ +/* $Id: tstVBoxNetDhcpd.cpp 114995 2026-08-12 09:59:45Z andreas.loeffler@oracle.com $ */ +/** @file + * VBoxNetDHCP in-process testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../Config.h" +#include "../../NetLib/IntNetIf.h" + +extern "C" int VBoxNetDhcpdTestStart(int argc, char **argv, void **ppvHandle); +extern "C" int VBoxNetDhcpdTestStop(void *pvHandle); +extern "C" bool VBoxNetDhcpdTestIsRunning(void *pvHandle); +extern "C" int VBoxIntNetSwitchTestStart(void **ppvHandle); +extern "C" int VBoxIntNetSwitchTestStop(void *pvHandle); + +static RTTEST g_hTest = NIL_RTTEST; + +#define TST_MAX_FRAME 2048 +#define TST_MAX_RX_FRAMES 64 +#define TST_DHCP_TIMEOUT_MS 3000 +#define TST_ANY_XID UINT32_MAX + +#define DHCP4_DISCOVER 1 +#define DHCP4_OFFER 2 +#define DHCP4_REQUEST 3 +#define DHCP4_ACK 5 + +#define TST_CHECK(a_Expr) \ + do { if (!(a_Expr)) RTTestFailed(g_hTest, "%s:%u: %s", __FILE__, __LINE__, #a_Expr); } while (0) + +#define TST_CHECK_RC_OK(a_rc) \ + do { int rc__ = (a_rc); if (RT_FAILURE(rc__)) RTTestFailed(g_hTest, "%s:%u: %s -> %Rrc", __FILE__, __LINE__, #a_rc, rc__); } while (0) + +static uint16_t tstH2N16(uint16_t u) { return RT_H2N_U16(u); } +static uint32_t tstH2N32(uint32_t u) { return RT_H2N_U32(u); } + +static uint32_t tstIPv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d) +{ + return tstH2N32(((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d); +} + +static int tstMakeUuidName(const char *pszPrefix, char *pszOut, size_t cbOut) +{ + RTUUID Uuid; + char szUuid[RTUUID_STR_LENGTH]; + + int rc = RTUuidCreate(&Uuid); + if (RT_FAILURE(rc)) + return rc; + + RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + + ssize_t cch = RTStrPrintf2(pszOut, cbOut, "%s-%s", pszPrefix, szUuid); + return cch > 0 && (size_t)cch < cbOut ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW; +} + +static int tstMakeTempPath(const char *pszPrefix, const char *pszSuffix, char *pszOut, size_t cbOut) +{ + char szTmp[RTPATH_MAX]; + RTUUID Uuid; + char szUuid[RTUUID_STR_LENGTH]; + + int rc = RTPathTemp(szTmp, sizeof(szTmp)); + if (RT_FAILURE(rc)) + return rc; + + rc = RTUuidCreate(&Uuid); + if (RT_FAILURE(rc)) + return rc; + + RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + + ssize_t cch = RTStrPrintf2(pszOut, cbOut, "%s%c%s-%s%s", + szTmp, RTPATH_DELIMITER, pszPrefix, szUuid, pszSuffix); + return cch > 0 && (size_t)cch < cbOut ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW; +} + +static int tstWriteAll(const char *pszFilename, const void *pvBuf, size_t cbBuf) +{ + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpen(&hFile, + pszFilename, + RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return rc; + + rc = RTFileWrite(hFile, pvBuf, cbBuf, NULL); + + int rcClose = RTFileClose(hFile); + if (RT_SUCCESS(rc) && RT_FAILURE(rcClose)) + rc = rcClose; + + return rc; +} + +static int tstWriteConfigFile(const char *pszNetwork, + const char *pszServerIp, + const char *pszMask, + const char *pszLower, + const char *pszUpper, + char *pszCfgFile, + size_t cbCfgFile) +{ + char szLeases[RTPATH_MAX]; + char szXml[8192]; + + int rc = tstMakeTempPath("VBoxNetDhcpd", ".xml", pszCfgFile, cbCfgFile); + if (RT_FAILURE(rc)) + return rc; + + rc = tstMakeTempPath("VBoxNetDhcpd", ".leases", szLeases, sizeof(szLeases)); + if (RT_FAILURE(rc)) + return rc; + + ssize_t cch = RTStrPrintf2(szXml, sizeof(szXml), + "\n" + "\n" + " \n" + " \n" + "\n", + pszNetwork, + pszServerIp, + pszMask, + pszLower, + pszUpper, + szLeases, + pszMask, + pszServerIp, + pszServerIp); + + if (cch <= 0 || (size_t)cch >= sizeof(szXml)) + return VERR_BUFFER_OVERFLOW; + + return tstWriteAll(pszCfgFile, szXml, (size_t)cch); +} + +static bool tstConfigCreateFromFile(const char *pszCfgFile) +{ + char *argv[3]; + argv[0] = (char *)"tstVBoxNetDhcpd-config"; + argv[1] = (char *)"--config"; + argv[2] = (char *)pszCfgFile; + + Config *pConfig = Config::create(3, argv); + if (pConfig != NULL) + { + delete pConfig; + return true; + } + + return false; +} + +static void tstConfigValidation(void) +{ + RTTestSub(g_hTest, "configuration validation"); + + char szNetwork[128]; + char szCfg[RTPATH_MAX]; + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdCfg", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "10.37.0.1", "255.255.255.0", "10.37.0.10", "10.37.0.12", + szCfg, sizeof(szCfg))); + TST_CHECK(tstConfigCreateFromFile(szCfg)); + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdBadOutside", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "10.37.0.1", "255.255.255.0", "10.38.0.10", "10.38.0.12", + szCfg, sizeof(szCfg))); + TST_CHECK(!tstConfigCreateFromFile(szCfg)); + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdBadReversed", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "10.37.0.1", "255.255.255.0", "10.37.0.12", "10.37.0.10", + szCfg, sizeof(szCfg))); + TST_CHECK(!tstConfigCreateFromFile(szCfg)); + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdBadMask", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "10.37.0.1", "255.255.0.255", "10.37.0.10", "10.37.0.12", + szCfg, sizeof(szCfg))); + TST_CHECK(!tstConfigCreateFromFile(szCfg)); + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdIPv6Unsupported", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "fd00:1::1", "ffff:ffff:ffff:ffff::", + "fd00:1::100", "fd00:1::1ff", szCfg, sizeof(szCfg))); + TST_CHECK(!tstConfigCreateFromFile(szCfg)); + + { + char *argv[1]; + argv[0] = (char *)"tstVBoxNetDhcpd-no-config"; + Config *pConfig = Config::create(1, argv); + TST_CHECK(pConfig == NULL); + if (pConfig) + delete pConfig; + } +} + +typedef struct TSTPKT +{ + uint32_t cb; + uint8_t ab[TST_MAX_FRAME]; +} TSTPKT; +typedef TSTPKT *PTSTPKT; +typedef const TSTPKT *PCTSTPKT; + +typedef struct TSTRXFRAME +{ + uint32_t cb; + uint8_t ab[TST_MAX_FRAME]; +} TSTRXFRAME; +typedef TSTRXFRAME *PTSTRXFRAME; + +typedef struct TSTCLIENT +{ + INTNETIFCTX hIf; + RTTHREAD hThread; + /** Receive thread status, VINF_SUCCESS while it is running normally. */ + int32_t volatile rcThread; + RTCRITSECT CritSect; + RTSEMEVENT hEvtRx; + uint32_t iRead; + uint32_t iWrite; + uint32_t cFrames; + TSTRXFRAME aFrames[TST_MAX_RX_FRAMES]; +} TSTCLIENT; +typedef TSTCLIENT *PTSTCLIENT; + +#pragma pack(1) +typedef struct TSTETHHDR +{ + uint8_t abDst[6]; + uint8_t abSrc[6]; + uint16_t uType; +} TSTETHHDR; + +typedef struct TSTIP4HDR +{ + uint8_t uVerIhl; + uint8_t uTos; + uint16_t uLen; + uint16_t uId; + uint16_t uFragOff; + uint8_t uTtl; + uint8_t uProto; + uint16_t uChecksum; + uint32_t uSrc; + uint32_t uDst; +} TSTIP4HDR; + +typedef struct TSTIP6HDR +{ + uint32_t uVerTcFl; + uint16_t uPayloadLen; + uint8_t uNextHeader; + uint8_t uHopLimit; + uint8_t abSrc[16]; + uint8_t abDst[16]; +} TSTIP6HDR; + +typedef struct TSTUDPHDR +{ + uint16_t uSrcPort; + uint16_t uDstPort; + uint16_t uLen; + uint16_t uChecksum; +} TSTUDPHDR; + +typedef struct TSTDHCP4HDR +{ + uint8_t uOp; + uint8_t uHType; + uint8_t uHLen; + uint8_t uHops; + uint32_t uXid; + uint16_t uSecs; + uint16_t uFlags; + uint32_t uCiAddr; + uint32_t uYiAddr; + uint32_t uSiAddr; + uint32_t uGiAddr; + uint8_t abChAddr[16]; + uint8_t abSName[64]; + uint8_t abFile[128]; + uint32_t uCookie; +} TSTDHCP4HDR; +#pragma pack() + +typedef struct TSTDHCP4REPLY +{ + uint8_t uMsgType; + uint32_t uYiAddrBe; + uint32_t uServerIdBe; + uint32_t uXid; +} TSTDHCP4REPLY; +typedef TSTDHCP4REPLY *PTSTDHCP4REPLY; + +static uint32_t tstChecksumAdd(uint32_t uSum, const void *pv, size_t cb) +{ + const uint8_t *pb = (const uint8_t *)pv; + + while (cb > 1) + { + uSum += ((uint16_t)pb[0] << 8) | pb[1]; + pb += 2; + cb -= 2; + } + + if (cb) + uSum += ((uint16_t)pb[0] << 8); + + return uSum; +} + +static uint16_t tstChecksumFinish(uint32_t uSum) +{ + while (uSum >> 16) + uSum = (uSum & 0xffff) + (uSum >> 16); + return (uint16_t)~uSum; +} + +static int tstPktAppend(PTSTPKT pPkt, const void *pv, uint32_t cb) +{ + if (pPkt->cb + cb > sizeof(pPkt->ab)) + return VERR_BUFFER_OVERFLOW; + + memcpy(&pPkt->ab[pPkt->cb], pv, cb); + pPkt->cb += cb; + return VINF_SUCCESS; +} + +static int tstDhcp4AppendOpt(PTSTPKT pPkt, uint8_t uOpt, const void *pv, uint8_t cb) +{ + uint8_t abHdr[2]; + abHdr[0] = uOpt; + abHdr[1] = cb; + + int rc = tstPktAppend(pPkt, abHdr, sizeof(abHdr)); + if (RT_SUCCESS(rc)) + rc = tstPktAppend(pPkt, pv, cb); + return rc; +} + +static int tstDhcp4AppendOptU32(PTSTPKT pPkt, uint8_t uOpt, uint32_t uValueBe) +{ + return tstDhcp4AppendOpt(pPkt, uOpt, &uValueBe, sizeof(uValueBe)); +} + +static int tstBuildDhcp4(PTSTPKT pDhcp, + uint8_t uMsgType, + uint32_t uXid, + const uint8_t abMac[6], + uint32_t uRequestedIpBe, + uint32_t uServerIdBe, + bool fBadCookie) +{ + RT_ZERO(*pDhcp); + pDhcp->cb = sizeof(TSTDHCP4HDR); + + TSTDHCP4HDR *pHdr = (TSTDHCP4HDR *)&pDhcp->ab[0]; + + pHdr->uOp = 1; /* BOOTREQUEST */ + pHdr->uHType = 1; /* Ethernet */ + pHdr->uHLen = 6; + pHdr->uXid = tstH2N32(uXid); + pHdr->uFlags = tstH2N16(0x8000); + pHdr->uCookie = fBadCookie ? tstH2N32(0xdeadbeef) : tstH2N32(0x63825363); + memcpy(pHdr->abChAddr, abMac, 6); + + uint8_t b = uMsgType; + int rc = tstDhcp4AppendOpt(pDhcp, 53, &b, sizeof(b)); + if (RT_FAILURE(rc)) + return rc; + + uint8_t abClientId[7]; + abClientId[0] = 1; + memcpy(&abClientId[1], abMac, 6); + rc = tstDhcp4AppendOpt(pDhcp, 61, abClientId, sizeof(abClientId)); + if (RT_FAILURE(rc)) + return rc; + + { + uint8_t abPrl[] = { 1, 3, 6, 51, 54 }; + rc = tstDhcp4AppendOpt(pDhcp, 55, abPrl, sizeof(abPrl)); + if (RT_FAILURE(rc)) + return rc; + } + + if (uRequestedIpBe) + { + rc = tstDhcp4AppendOptU32(pDhcp, 50, uRequestedIpBe); + if (RT_FAILURE(rc)) + return rc; + } + + if (uServerIdBe) + { + rc = tstDhcp4AppendOptU32(pDhcp, 54, uServerIdBe); + if (RT_FAILURE(rc)) + return rc; + } + + b = 255; + rc = tstPktAppend(pDhcp, &b, sizeof(b)); + if (RT_FAILURE(rc)) + return rc; + + while (pDhcp->cb < 300) + { + b = 0; + rc = tstPktAppend(pDhcp, &b, sizeof(b)); + if (RT_FAILURE(rc)) + return rc; + } + + return VINF_SUCCESS; +} + +static int tstBuildUdp4Frame(PTSTPKT pFrame, + const uint8_t abSrcMac[6], + uint32_t uSrcIpBe, + uint32_t uDstIpBe, + uint16_t uSrcPort, + uint16_t uDstPort, + PCTSTPKT pPayload) +{ + static const uint8_t s_abBcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + RT_ZERO(*pFrame); + + TSTETHHDR Eth; + memcpy(Eth.abDst, s_abBcast, sizeof(Eth.abDst)); + memcpy(Eth.abSrc, abSrcMac, sizeof(Eth.abSrc)); + Eth.uType = tstH2N16(0x0800); + + int rc = tstPktAppend(pFrame, &Eth, sizeof(Eth)); + if (RT_FAILURE(rc)) + return rc; + + TSTIP4HDR Ip; + RT_ZERO(Ip); + Ip.uVerIhl = 0x45; + Ip.uLen = tstH2N16((uint16_t)(sizeof(TSTIP4HDR) + sizeof(TSTUDPHDR) + pPayload->cb)); + Ip.uId = tstH2N16((uint16_t)RTRandU32()); + Ip.uTtl = 64; + Ip.uProto = 17; + Ip.uSrc = uSrcIpBe; + Ip.uDst = uDstIpBe; + Ip.uChecksum = tstH2N16(tstChecksumFinish(tstChecksumAdd(0, &Ip, sizeof(Ip)))); + + rc = tstPktAppend(pFrame, &Ip, sizeof(Ip)); + if (RT_FAILURE(rc)) + return rc; + + TSTUDPHDR Udp; + RT_ZERO(Udp); + Udp.uSrcPort = tstH2N16(uSrcPort); + Udp.uDstPort = tstH2N16(uDstPort); + Udp.uLen = tstH2N16((uint16_t)(sizeof(TSTUDPHDR) + pPayload->cb)); + Udp.uChecksum = 0; /* legal for IPv4 */ + + rc = tstPktAppend(pFrame, &Udp, sizeof(Udp)); + if (RT_FAILURE(rc)) + return rc; + + return tstPktAppend(pFrame, pPayload->ab, pPayload->cb); +} + +static int tstBuildDhcp6SolicitFrame(PTSTPKT pFrame, const uint8_t abSrcMac[6]) +{ + static const uint8_t s_abDstMac[6] = { 0x33, 0x33, 0x00, 0x01, 0x00, 0x02 }; + static const uint8_t s_abSrcIp[16] = { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, + 0x02, 0x00, 0x27, 0xff, 0xfe, 0x12, 0x34, 0x56 }; + static const uint8_t s_abDstIp[16] = { 0xff, 0x02, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0x01, 0x02 }; + + RT_ZERO(*pFrame); + + TSTETHHDR Eth; + memcpy(Eth.abDst, s_abDstMac, sizeof(Eth.abDst)); + memcpy(Eth.abSrc, abSrcMac, sizeof(Eth.abSrc)); + Eth.uType = tstH2N16(0x86dd); + + int rc = tstPktAppend(pFrame, &Eth, sizeof(Eth)); + if (RT_FAILURE(rc)) + return rc; + + TSTIP6HDR Ip6; + RT_ZERO(Ip6); + Ip6.uVerTcFl = tstH2N32(6U << 28); + Ip6.uPayloadLen = tstH2N16((uint16_t)(sizeof(TSTUDPHDR) + 4)); + Ip6.uNextHeader = 17; + Ip6.uHopLimit = 1; + memcpy(Ip6.abSrc, s_abSrcIp, sizeof(Ip6.abSrc)); + memcpy(Ip6.abDst, s_abDstIp, sizeof(Ip6.abDst)); + + rc = tstPktAppend(pFrame, &Ip6, sizeof(Ip6)); + if (RT_FAILURE(rc)) + return rc; + + TSTUDPHDR Udp; + RT_ZERO(Udp); + Udp.uSrcPort = tstH2N16(546); + Udp.uDstPort = tstH2N16(547); + Udp.uLen = tstH2N16((uint16_t)(sizeof(TSTUDPHDR) + 4)); + + uint8_t abDhcp6[4]; + abDhcp6[0] = 1; /* SOLICIT */ + abDhcp6[1] = 0x12; + abDhcp6[2] = 0x34; + abDhcp6[3] = 0x56; + + uint32_t uSum = 0; + uint32_t uLenBe = tstH2N32(sizeof(TSTUDPHDR) + sizeof(abDhcp6)); + uint8_t abNextHdr[4] = { 0, 0, 0, 17 }; + + uSum = tstChecksumAdd(uSum, s_abSrcIp, sizeof(s_abSrcIp)); + uSum = tstChecksumAdd(uSum, s_abDstIp, sizeof(s_abDstIp)); + uSum = tstChecksumAdd(uSum, &uLenBe, sizeof(uLenBe)); + uSum = tstChecksumAdd(uSum, abNextHdr, sizeof(abNextHdr)); + uSum = tstChecksumAdd(uSum, &Udp, sizeof(Udp)); + uSum = tstChecksumAdd(uSum, abDhcp6, sizeof(abDhcp6)); + + uint16_t uCsum = tstChecksumFinish(uSum); + if (uCsum == 0) + uCsum = 0xffff; + Udp.uChecksum = tstH2N16(uCsum); + + rc = tstPktAppend(pFrame, &Udp, sizeof(Udp)); + if (RT_FAILURE(rc)) + return rc; + + return tstPktAppend(pFrame, abDhcp6, sizeof(abDhcp6)); +} + +static DECLCALLBACK(void) tstClientRx(void *pvUser, void *pvFrame, uint32_t cbFrame) +{ + PTSTCLIENT pThis = (PTSTCLIENT)pvUser; + + if (cbFrame > TST_MAX_FRAME) + return; + + RTCritSectEnter(&pThis->CritSect); + + if (pThis->cFrames == TST_MAX_RX_FRAMES) + { + pThis->iRead = (pThis->iRead + 1) % TST_MAX_RX_FRAMES; + pThis->cFrames--; + } + + PTSTRXFRAME pDst = &pThis->aFrames[pThis->iWrite]; + memcpy(pDst->ab, pvFrame, cbFrame); + pDst->cb = cbFrame; + + pThis->iWrite = (pThis->iWrite + 1) % TST_MAX_RX_FRAMES; + pThis->cFrames++; + + RTCritSectLeave(&pThis->CritSect); + RTSemEventSignal(pThis->hEvtRx); +} + +static DECLCALLBACK(int) tstClientThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PTSTCLIENT pThis = (PTSTCLIENT)pvUser; + int const rc = IntNetR3IfPumpPkts(pThis->hIf, tstClientRx, pThis, NULL, NULL); + ASMAtomicWriteS32(&pThis->rcThread, rc); + RTSemEventSignal(pThis->hEvtRx); + return rc; +} + +static void tstClientDestroy(PTSTCLIENT pThis); + +static int tstClientInit(PTSTCLIENT pThis, const char *pszNetwork) +{ + RT_ZERO(*pThis); + pThis->hThread = NIL_RTTHREAD; + pThis->hEvtRx = NIL_RTSEMEVENT; + pThis->rcThread = VINF_SUCCESS; + + int rc = RTCritSectInit(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + rc = RTSemEventCreate(&pThis->hEvtRx); + if (RT_FAILURE(rc)) + { + tstClientDestroy(pThis); + return rc; + } + + rc = IntNetR3IfCreateEx(&pThis->hIf, pszNetwork, kIntNetTrunkType_WhateverNone, "", _256K, _256K, 0); + if (RT_FAILURE(rc)) + { + tstClientDestroy(pThis); + return rc; + } + + rc = IntNetR3IfSetActive(pThis->hIf, true); + if (RT_FAILURE(rc)) + { + tstClientDestroy(pThis); + return rc; + } + + rc = RTThreadCreate(&pThis->hThread, + tstClientThread, + pThis, + 0, + RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, + "DhcpCli"); + if (RT_FAILURE(rc)) + tstClientDestroy(pThis); + return rc; +} + +static void tstClientDestroy(PTSTCLIENT pThis) +{ + if (pThis->hIf != NULL) + { + int const rc = IntNetR3IfWaitAbort(pThis->hIf); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Aborting the test client receive wait failed: %Rrc", rc); + } + + if (pThis->hThread != NIL_RTTHREAD) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rc = RTThreadWait(pThis->hThread, 30000, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Joining the test client receive thread failed: %Rrc", rc); + return; + } + pThis->hThread = NIL_RTTHREAD; + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Test client receive thread returned %Rrc, expected %Rrc", + rcThread, VERR_SEM_DESTROYED); + } + + if (pThis->hIf != NULL) + { + IntNetR3IfSetActive(pThis->hIf, false); + IntNetR3IfDestroy(pThis->hIf); + pThis->hIf = NULL; + } + + if (pThis->hEvtRx != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pThis->hEvtRx); + pThis->hEvtRx = NIL_RTSEMEVENT; + } + + if (pThis->CritSect.u32Magic == RTCRITSECT_MAGIC) + RTCritSectDelete(&pThis->CritSect); +} + +static bool tstClientPopFrame(PTSTCLIENT pThis, PTSTRXFRAME pFrame) +{ + bool fHave = false; + + RTCritSectEnter(&pThis->CritSect); + + if (pThis->cFrames > 0) + { + *pFrame = pThis->aFrames[pThis->iRead]; + pThis->iRead = (pThis->iRead + 1) % TST_MAX_RX_FRAMES; + pThis->cFrames--; + fHave = true; + } + + RTCritSectLeave(&pThis->CritSect); + return fHave; +} + +static uint32_t tstClientQueuedFrameCount(PTSTCLIENT pThis) +{ + RTCritSectEnter(&pThis->CritSect); + uint32_t const cFrames = pThis->cFrames; + RTCritSectLeave(&pThis->CritSect); + return cFrames; +} + +static int tstClientSendFrame(PTSTCLIENT pThis, PCTSTPKT pFrame) +{ + INTNETFRAME Frame; + int rc = IntNetR3IfQueryOutputFrame(pThis->hIf, pFrame->cb, &Frame); + if (RT_FAILURE(rc)) + return rc; + + memcpy(Frame.pvFrame, pFrame->ab, pFrame->cb); + return IntNetR3IfOutputFrameCommit(pThis->hIf, &Frame); +} + +static bool tstParseDhcp4Reply(const uint8_t *pbFrame, uint32_t cbFrame, uint32_t uWantXid, PTSTDHCP4REPLY pReply) +{ + if (cbFrame < sizeof(TSTETHHDR) + sizeof(TSTIP4HDR) + sizeof(TSTUDPHDR) + sizeof(TSTDHCP4HDR)) + return false; + + const TSTETHHDR *pEth = (const TSTETHHDR *)pbFrame; + if (RT_N2H_U16(pEth->uType) != 0x0800) + return false; + + const TSTIP4HDR *pIp = (const TSTIP4HDR *)(pbFrame + sizeof(TSTETHHDR)); + if ((pIp->uVerIhl >> 4) != 4 || pIp->uProto != 17) + return false; + + uint32_t cbIp = (pIp->uVerIhl & 0x0f) * 4; + if (cbIp < sizeof(TSTIP4HDR)) + return false; + + if (cbFrame < sizeof(TSTETHHDR) + cbIp + sizeof(TSTUDPHDR) + sizeof(TSTDHCP4HDR)) + return false; + + const TSTUDPHDR *pUdp = (const TSTUDPHDR *)(pbFrame + sizeof(TSTETHHDR) + cbIp); + if (RT_N2H_U16(pUdp->uSrcPort) != 67 || RT_N2H_U16(pUdp->uDstPort) != 68) + return false; + + const TSTDHCP4HDR *pDhcp = (const TSTDHCP4HDR *)(pbFrame + sizeof(TSTETHHDR) + cbIp + sizeof(TSTUDPHDR)); + + if (pDhcp->uOp != 2) + return false; + + uint32_t uXid = RT_N2H_U32(pDhcp->uXid); + if (uWantXid != TST_ANY_XID && uXid != uWantXid) + return false; + + if (RT_N2H_U32(pDhcp->uCookie) != 0x63825363) + return false; + + uint8_t uMsgType = 0; + uint32_t uServerIdBe = 0; + + const uint8_t *pbOpt = (const uint8_t *)(pDhcp + 1); + const uint8_t *pbEnd = pbFrame + cbFrame; + + while (pbOpt < pbEnd) + { + uint8_t uOpt = *pbOpt++; + + if (uOpt == 255) + break; + if (uOpt == 0) + continue; + + if (pbOpt >= pbEnd) + return false; + + uint8_t cbOpt = *pbOpt++; + if (pbOpt + cbOpt > pbEnd) + return false; + + if (uOpt == 53 && cbOpt == 1) + uMsgType = pbOpt[0]; + else if (uOpt == 54 && cbOpt == 4) + memcpy(&uServerIdBe, pbOpt, sizeof(uServerIdBe)); + + pbOpt += cbOpt; + } + + if (uMsgType == 0) + return false; + + pReply->uMsgType = uMsgType; + pReply->uYiAddrBe = pDhcp->uYiAddr; + pReply->uServerIdBe = uServerIdBe; + pReply->uXid = uXid; + return true; +} + +static bool tstFrameIsDhcp6Reply(const uint8_t *pbFrame, uint32_t cbFrame) +{ + if (cbFrame < sizeof(TSTETHHDR) + sizeof(TSTIP6HDR) + sizeof(TSTUDPHDR) + 4) + return false; + + const TSTETHHDR *pEth = (const TSTETHHDR *)pbFrame; + if (RT_N2H_U16(pEth->uType) != 0x86dd) + return false; + + const TSTIP6HDR *pIp6 = (const TSTIP6HDR *)(pbFrame + sizeof(TSTETHHDR)); + if ((RT_N2H_U32(pIp6->uVerTcFl) >> 28) != 6 || pIp6->uNextHeader != 17) + return false; + + const TSTUDPHDR *pUdp = (const TSTUDPHDR *)(pbFrame + sizeof(TSTETHHDR) + sizeof(TSTIP6HDR)); + return RT_N2H_U16(pUdp->uSrcPort) == 547 && RT_N2H_U16(pUdp->uDstPort) == 546; +} + +static bool tstClientWaitForDhcp4(PTSTCLIENT pClient, + uint32_t uXid, + uint8_t uMsgType, + PTSTDHCP4REPLY pReply, + uint32_t cMs) +{ + uint64_t uDeadline = RTTimeMilliTS() + cMs; + + while (RTTimeMilliTS() < uDeadline) + { + TSTRXFRAME Frame; + if (tstClientPopFrame(pClient, &Frame)) + { + TSTDHCP4REPLY Reply; + if (tstParseDhcp4Reply(Frame.ab, Frame.cb, uXid, &Reply) && Reply.uMsgType == uMsgType) + { + if (pReply) + *pReply = Reply; + return true; + } + } + else + RTSemEventWait(pClient->hEvtRx, 50); + } + + return false; +} + +static bool tstClientWaitForDhcp6Reply(PTSTCLIENT pClient, uint32_t cMs) +{ + uint64_t uDeadline = RTTimeMilliTS() + cMs; + + while (RTTimeMilliTS() < uDeadline) + { + TSTRXFRAME Frame; + if (tstClientPopFrame(pClient, &Frame)) + { + if (tstFrameIsDhcp6Reply(Frame.ab, Frame.cb)) + return true; + } + else + RTSemEventWait(pClient->hEvtRx, 50); + } + + return false; +} + +static int tstDhcp4DiscoverOnce(PTSTCLIENT pClient, + const uint8_t abMac[6], + uint32_t uXid, + PTSTDHCP4REPLY pOffer, + uint32_t cMs) +{ + TSTPKT Dhcp; + TSTPKT Frame; + + int rc = tstBuildDhcp4(&Dhcp, DHCP4_DISCOVER, uXid, abMac, 0, 0, false); + if (RT_FAILURE(rc)) + return rc; + + rc = tstBuildUdp4Frame(&Frame, abMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); + if (RT_FAILURE(rc)) + return rc; + + rc = tstClientSendFrame(pClient, &Frame); + if (RT_FAILURE(rc)) + return rc; + + return tstClientWaitForDhcp4(pClient, uXid, DHCP4_OFFER, pOffer, cMs) ? VINF_SUCCESS : VERR_TIMEOUT; +} + +static bool tstDhcp4Discover(PTSTCLIENT pClient, const uint8_t abMac[6], PTSTDHCP4REPLY pOffer) +{ + /* Retransmissions belong to one DHCP transaction, so delayed replies from + an earlier attempt must remain acceptable on a loaded testbox. */ + uint32_t const uXid = RTRandU32(); + for (unsigned i = 0; i < 8; i++) + { + int const rc = tstDhcp4DiscoverOnce(pClient, abMac, uXid, pOffer, 600); + if (RT_SUCCESS(rc)) + return true; + if (rc != VERR_TIMEOUT) + { + RTTestFailed(g_hTest, "Sending DHCPDISCOVER for %02x:%02x:%02x:%02x:%02x:%02x (xid %#RX32) failed: %Rrc", + abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], uXid, rc); + return false; + } + RTThreadSleep(100); + } + RTTestFailed(g_hTest, "No DHCPOFFER for %02x:%02x:%02x:%02x:%02x:%02x (xid %#RX32) after 8 attempts", + abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], uXid); + RTTestFailureDetails(g_hTest, "client receive status: %Rrc; queued frames: %RU32\n", + ASMAtomicReadS32(&pClient->rcThread), tstClientQueuedFrameCount(pClient)); + return false; +} + +static bool tstDhcp4Request(PTSTCLIENT pClient, + const uint8_t abMac[6], + const TSTDHCP4REPLY *pOffer, + PTSTDHCP4REPLY pAck) +{ + uint32_t uXid = RTRandU32(); + TSTPKT Dhcp; + TSTPKT Frame; + + int rc = tstBuildDhcp4(&Dhcp, + DHCP4_REQUEST, + uXid, + abMac, + pOffer->uYiAddrBe, + pOffer->uServerIdBe, + false); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building DHCPREQUEST for %02x:%02x:%02x:%02x:%02x:%02x failed: %Rrc", + abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], rc); + return false; + } + + rc = tstBuildUdp4Frame(&Frame, abMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building DHCPREQUEST frame for %02x:%02x:%02x:%02x:%02x:%02x failed: %Rrc", + abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], rc); + return false; + } + + rc = tstClientSendFrame(pClient, &Frame); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Sending DHCPREQUEST for %02x:%02x:%02x:%02x:%02x:%02x (xid %#RX32) failed: %Rrc", + abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], uXid, rc); + return false; + } + + if (tstClientWaitForDhcp4(pClient, uXid, DHCP4_ACK, pAck, TST_DHCP_TIMEOUT_MS)) + return true; + + RTTestFailed(g_hTest, "No DHCPACK for %02x:%02x:%02x:%02x:%02x:%02x (xid %#RX32, requested %RTnaipv4)", + abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], uXid, pOffer->uYiAddrBe); + RTTestFailureDetails(g_hTest, "client receive status: %Rrc; queued frames: %RU32\n", + ASMAtomicReadS32(&pClient->rcThread), tstClientQueuedFrameCount(pClient)); + return false; +} + +static bool tstIPv4InPool(uint32_t uIpBe, uint8_t uFirst, uint8_t uLast) +{ + uint32_t uIp = RT_N2H_U32(uIpBe); + uint32_t uLo = RT_N2H_U32(tstIPv4(10,37,0,uFirst)); + uint32_t uHi = RT_N2H_U32(tstIPv4(10,37,0,uLast)); + + return uIp >= uLo && uIp <= uHi; +} + +static bool tstSendBadCookieNoReply(PTSTCLIENT pClient) +{ + static const uint8_t s_abMac[6] = { 0x08, 0x00, 0x27, 0xde, 0xad, 0x01 }; + + uint32_t uXid = RTRandU32(); + TSTPKT Dhcp; + TSTPKT Frame; + + int rc = tstBuildDhcp4(&Dhcp, DHCP4_DISCOVER, uXid, s_abMac, 0, 0, true); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building bad-cookie DHCPDISCOVER failed: %Rrc", rc); + return false; + } + + rc = tstBuildUdp4Frame(&Frame, s_abMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building bad-cookie DHCPDISCOVER frame failed: %Rrc", rc); + return false; + } + + rc = tstClientSendFrame(pClient, &Frame); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Sending bad-cookie DHCPDISCOVER failed: %Rrc", rc); + return false; + } + + if (tstClientWaitForDhcp4(pClient, uXid, DHCP4_OFFER, NULL, 750)) + { + RTTestFailed(g_hTest, "Server replied to bad-cookie DHCPDISCOVER (xid %#RX32)", uXid); + return false; + } + + int const rcThread = ASMAtomicReadS32(&pClient->rcThread); + if (RT_FAILURE(rcThread)) + { + RTTestFailed(g_hTest, "Client receive thread failed during bad-cookie test: %Rrc", rcThread); + return false; + } + return true; +} + +static bool tstSendDhcp6SolicitNoReply(PTSTCLIENT pClient) +{ + static const uint8_t s_abMac[6] = { 0x08, 0x00, 0x27, 0x66, 0x66, 0x66 }; + + TSTPKT Frame; + int rc = tstBuildDhcp6SolicitFrame(&Frame, s_abMac); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building DHCPv6 SOLICIT frame failed: %Rrc", rc); + return false; + } + + rc = tstClientSendFrame(pClient, &Frame); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Sending DHCPv6 SOLICIT failed: %Rrc", rc); + return false; + } + + if (tstClientWaitForDhcp6Reply(pClient, 1000)) + { + RTTestFailed(g_hTest, "IPv4-only server unexpectedly replied to DHCPv6 SOLICIT"); + return false; + } + + int const rcThread = ASMAtomicReadS32(&pClient->rcThread); + if (RT_FAILURE(rcThread)) + { + RTTestFailed(g_hTest, "Client receive thread failed during DHCPv6 test: %Rrc", rcThread); + return false; + } + return true; +} + +static bool tstLeaseClient(PTSTCLIENT pClient, + const uint8_t abMac[6], + PTSTDHCP4REPLY pAck) +{ + TSTDHCP4REPLY Offer; + if (!tstDhcp4Discover(pClient, abMac, &Offer)) + return false; + if (!tstIPv4InPool(Offer.uYiAddrBe, 10, 12)) + { + RTTestFailed(g_hTest, "DHCPOFFER for %02x:%02x:%02x:%02x:%02x:%02x contains out-of-pool address %RTnaipv4", + abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], Offer.uYiAddrBe); + return false; + } + if (Offer.uServerIdBe != tstIPv4(10,37,0,1)) + { + RTTestFailed(g_hTest, "DHCPOFFER for %02x:%02x:%02x:%02x:%02x:%02x has server ID %RTnaipv4, expected 10.37.0.1", + abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], Offer.uServerIdBe); + return false; + } + if (!tstDhcp4Request(pClient, abMac, &Offer, pAck)) + return false; + if (pAck->uYiAddrBe != Offer.uYiAddrBe) + { + RTTestFailed(g_hTest, "DHCPACK for %02x:%02x:%02x:%02x:%02x:%02x assigns %RTnaipv4, offered %RTnaipv4", + abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], + pAck->uYiAddrBe, Offer.uYiAddrBe); + return false; + } + return true; +} + +static bool tstStartInProcessServer(char *pszNetwork, size_t cbNetwork, PTSTCLIENT pClient, void **ppvDhcpd) +{ + char szCfg[RTPATH_MAX]; + + int rc = tstMakeUuidName("VBoxNetDhcpdWire", pszNetwork, cbNetwork); + if (RT_FAILURE(rc)) + return false; + + rc = tstWriteConfigFile(pszNetwork, + "10.37.0.1", + "255.255.255.0", + "10.37.0.10", + "10.37.0.12", + szCfg, + sizeof(szCfg)); + if (RT_FAILURE(rc)) + return false; + + char *argv[5]; + argv[0] = (char *)"VBoxNetDHCP-inproc"; + argv[1] = (char *)"--config"; + argv[2] = szCfg; + argv[3] = (char *)"--comment"; + argv[4] = pszNetwork; + + rc = VBoxNetDhcpdTestStart(5, argv, ppvDhcpd); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "VBoxNetDhcpdTestStart failed: %Rrc", rc); + return false; + } + + rc = tstClientInit(pClient, pszNetwork); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "test client IntNet attach failed: %Rrc", rc); + tstClientDestroy(pClient); + return false; + } + + return true; +} + +static void tstWireDhcp(void) +{ + RTTestSub(g_hTest, "in-process daemon setup"); + + char szNetwork[128]; + TSTCLIENT Client; + void *pvDhcpd = NULL; + + RT_ZERO(Client); + + if (!tstStartInProcessServer(szNetwork, sizeof(szNetwork), &Client, &pvDhcpd)) + { + if (pvDhcpd) + VBoxNetDhcpdTestStop(pvDhcpd); + return; + } + + if (!VBoxNetDhcpdTestIsRunning(pvDhcpd)) + RTTestFailed(g_hTest, "VBoxNetDHCP stopped after reporting successful startup"); + + static const uint8_t s_abMac1[6] = { 0x08, 0x00, 0x27, 0x12, 0x34, 0x56 }; + static const uint8_t s_abMac2[6] = { 0x08, 0x00, 0x27, 0x00, 0x00, 0x02 }; + static const uint8_t s_abMac3[6] = { 0x08, 0x00, 0x27, 0x00, 0x00, 0x03 }; + static const uint8_t s_abMac4[6] = { 0x08, 0x00, 0x27, 0x00, 0x00, 0x04 }; + + TSTDHCP4REPLY Ack1; + TSTDHCP4REPLY Ack1Again; + TSTDHCP4REPLY Ack2; + TSTDHCP4REPLY Ack3; + TSTDHCP4REPLY Offer4; + + RT_ZERO(Ack1); + RT_ZERO(Ack1Again); + RT_ZERO(Ack2); + RT_ZERO(Ack3); + RT_ZERO(Offer4); + + RTTestSub(g_hTest, "first client lease"); + bool const fAck1 = tstLeaseClient(&Client, s_abMac1, &Ack1); + + RTTestSub(g_hTest, "first client lease reuse"); + bool fAck1Again = false; + if (fAck1) + { + fAck1Again = tstLeaseClient(&Client, s_abMac1, &Ack1Again); + if (fAck1Again && Ack1Again.uYiAddrBe != Ack1.uYiAddrBe) + RTTestFailed(g_hTest, "Repeated lease changed from %RTnaipv4 to %RTnaipv4", + Ack1.uYiAddrBe, Ack1Again.uYiAddrBe); + } + else + RTTestSkipped(g_hTest, "Initial lease failed"); + + RTTestSub(g_hTest, "invalid DHCP cookie rejection"); + tstSendBadCookieNoReply(&Client); + + RTTestSub(g_hTest, "DHCPv6 packet rejection"); + tstSendDhcp6SolicitNoReply(&Client); + + RTTestSub(g_hTest, "second client lease"); + bool const fAck2 = tstLeaseClient(&Client, s_abMac2, &Ack2); + + RTTestSub(g_hTest, "third client lease"); + bool const fAck3 = tstLeaseClient(&Client, s_abMac3, &Ack3); + + RTTestSub(g_hTest, "unique client leases"); + if (fAck1 && fAck2 && fAck3) + { + if (Ack1.uYiAddrBe == Ack2.uYiAddrBe) + RTTestFailed(g_hTest, "First and second clients both received %RTnaipv4", Ack1.uYiAddrBe); + if (Ack1.uYiAddrBe == Ack3.uYiAddrBe) + RTTestFailed(g_hTest, "First and third clients both received %RTnaipv4", Ack1.uYiAddrBe); + if (Ack2.uYiAddrBe == Ack3.uYiAddrBe) + RTTestFailed(g_hTest, "Second and third clients both received %RTnaipv4", Ack2.uYiAddrBe); + } + else + RTTestSkipped(g_hTest, "One or more prerequisite lease exchanges failed"); + + RTTestSub(g_hTest, "exhausted address pool"); + if (fAck1 && fAck2 && fAck3) + { + uint32_t const uXid = RTRandU32(); + int const rc = tstDhcp4DiscoverOnce(&Client, s_abMac4, uXid, &Offer4, 1000); + if (RT_SUCCESS(rc)) + RTTestFailed(g_hTest, "Exhausted pool offered %RTnaipv4 to fourth client (xid %#RX32)", + Offer4.uYiAddrBe, uXid); + else if (rc != VERR_TIMEOUT) + RTTestFailed(g_hTest, "Fourth-client DHCPDISCOVER failed with %Rrc instead of timing out", rc); + else + { + int const rcThread = ASMAtomicReadS32(&Client.rcThread); + if (RT_FAILURE(rcThread)) + RTTestFailed(g_hTest, "Client receive thread failed while checking pool exhaustion: %Rrc", rcThread); + } + } + else + RTTestSkipped(g_hTest, "Address pool was not successfully filled"); + + RTTestSub(g_hTest, "in-process daemon teardown"); + int const rcClientBeforeTeardown = ASMAtomicReadS32(&Client.rcThread); + tstClientDestroy(&Client); + + int rc = VBoxNetDhcpdTestStop(pvDhcpd); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "VBoxNetDhcpdTestStop failed: %Rrc", rc); + RTTestFailureDetails(g_hTest, "client receive status before teardown: %Rrc; after teardown: %Rrc\n", + rcClientBeforeTeardown, ASMAtomicReadS32(&Client.rcThread)); + } +} + +int main(int argc, char **argv) +{ + /* The embedded-switch build forces the driverless R3 path, so SUPLib must + not be initialized by the testcase process. */ + int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + RTTestInitAndCreate("tstVBoxNetDhcpd", &g_hTest); + if (g_hTest == NIL_RTTEST) + return 1; + + RTTestBanner(g_hTest); + + tstConfigValidation(); + + void *pvSwitch = NULL; + rc = VBoxIntNetSwitchTestStart(&pvSwitch); + if (RT_SUCCESS(rc)) + { + tstWireDhcp(); + rc = VBoxIntNetSwitchTestStop(pvSwitch); + TST_CHECK_RC_OK(rc); + } + else + RTTestFailed(g_hTest, "Embedded IntNet switch startup failed: %Rrc", rc); + + return RTTestSummaryAndDestroy(g_hTest); +} From 53cc4b104eb7cf4af291515023dee2492ed7fc23 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Wed, 12 Aug 2026 10:42:45 +0000 Subject: [PATCH 097/176] IPRT/tstRTNoCrt-2: Try to fix some testcases failing on various testboxes. Needs review. svn:sync-xref-src-repo-rev: r174837 --- src/VBox/Runtime/testcase/tstRTNoCrt-2.cpp | 92 +++++++++++----------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/src/VBox/Runtime/testcase/tstRTNoCrt-2.cpp b/src/VBox/Runtime/testcase/tstRTNoCrt-2.cpp index 71c2ab292a8b..2c0ef4f6c591 100644 --- a/src/VBox/Runtime/testcase/tstRTNoCrt-2.cpp +++ b/src/VBox/Runtime/testcase/tstRTNoCrt-2.cpp @@ -1,4 +1,4 @@ -/* $Id: tstRTNoCrt-2.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: tstRTNoCrt-2.cpp 114996 2026-08-12 10:42:45Z andreas.loeffler@oracle.com $ */ /** @file * IPRT Testcase - Testcase for the No-CRT math bits. */ @@ -2931,33 +2931,35 @@ void testFma() { RTTestSub(g_hTest, "fma[f]"); - CHECK_DBL(RT_NOCRT(fma)(1.0, 1.0, 1.0), 2.0); - CHECK_DBL(RT_NOCRT(fma)(4.0, 2.0, 1.0), 9.0); - CHECK_DBL(RT_NOCRT(fma)(4.0, 2.0, -1.0), 7.0); - CHECK_DBL_SAME(fma, (0.0, 0.0, 0.0)); - CHECK_DBL_SAME(fma, (999999.0, 33334.0, 29345.0)); - CHECK_DBL_SAME(fma, (39560.32334, 9605.5546, -59079.345069)); - CHECK_DBL_SAME(fma, (39560.32334, -59079.345069, 9605.5546)); - CHECK_DBL_SAME(fma, (-59079.345069, 39560.32334, 9605.5546)); - CHECK_DBL_SAME(fma, (+INFINITY, +INFINITY, -INFINITY)); - CHECK_DBL_SAME(fma, (4.0, +INFINITY, 2.0)); - CHECK_DBL_SAME(fma, (4.0, 4.0, +INFINITY)); - CHECK_DBL_SAME(fma, (-INFINITY, 4.0, 4.0)); - CHECK_DBL_SAME(fma, (2.34960584706e100, 7.6050698459e-13, 9.99996777e77)); - - CHECK_FLT(RT_NOCRT(fmaf)(1.0f, 1.0f, 1.0), 2.0); - CHECK_FLT(RT_NOCRT(fmaf)(4.0f, 2.0f, 1.0), 9.0); - CHECK_FLT(RT_NOCRT(fmaf)(4.0f, 2.0f, -1.0), 7.0); - CHECK_FLT_SAME(fmaf, (0.0f, 0.0f, 0.0f)); - CHECK_FLT_SAME(fmaf, (999999.0f, 33334.0f, 29345.0f)); - CHECK_FLT_SAME(fmaf, (39560.32334f, 9605.5546f, -59079.345069f)); - CHECK_FLT_SAME(fmaf, (39560.32334f, -59079.345069f, 9605.5546f)); - CHECK_FLT_SAME(fmaf, (-59079.345069f, 39560.32334f, 9605.5546f)); - CHECK_FLT_SAME(fmaf, (+INFINITY, +INFINITY, -INFINITY)); - CHECK_FLT_SAME(fmaf, (4.0f, +INFINITY, 2.0f)); - CHECK_FLT_SAME(fmaf, (4.0f, 4.0f, +INFINITY)); - CHECK_FLT_SAME(fmaf, (-INFINITY, 4.0f, 4.0f)); - CHECK_FLT_SAME(fmaf, (2.34960584706e22f, 7.6050698459e-13f, 9.99996777e27f)); + CHECK_DBL( RT_NOCRT(fma)(1.0, 1.0, 1.0), 2.0); + CHECK_DBL( RT_NOCRT(fma)(4.0, 2.0, 1.0), 9.0); + CHECK_DBL( RT_NOCRT(fma)(4.0, 2.0, -1.0), 7.0); + CHECK_DBL_SAME( fma, (0.0, 0.0, 0.0)); + CHECK_DBL_SAME( fma, (999999.0, 33334.0, 29345.0)); + CHECK_DBL_SAME( fma, (39560.32334, 9605.5546, -59079.345069)); + CHECK_DBL_SAME( fma, (39560.32334, -59079.345069, 9605.5546)); + CHECK_DBL_SAME( fma, (-59079.345069, 39560.32334, 9605.5546)); + /* The NaN sign resulting from this invalid operation is not specified. */ + CHECK_DBL_SAME_RELAXED_NAN(fma, (+INFINITY, +INFINITY, -INFINITY)); + CHECK_DBL_SAME( fma, (4.0, +INFINITY, 2.0)); + CHECK_DBL_SAME( fma, (4.0, 4.0, +INFINITY)); + CHECK_DBL_SAME( fma, (-INFINITY, 4.0, 4.0)); + CHECK_DBL_SAME( fma, (2.34960584706e100, 7.6050698459e-13, 9.99996777e77)); + + CHECK_FLT( RT_NOCRT(fmaf)(1.0f, 1.0f, 1.0), 2.0); + CHECK_FLT( RT_NOCRT(fmaf)(4.0f, 2.0f, 1.0), 9.0); + CHECK_FLT( RT_NOCRT(fmaf)(4.0f, 2.0f, -1.0), 7.0); + CHECK_FLT_SAME( fmaf, (0.0f, 0.0f, 0.0f)); + CHECK_FLT_SAME( fmaf, (999999.0f, 33334.0f, 29345.0f)); + CHECK_FLT_SAME( fmaf, (39560.32334f, 9605.5546f, -59079.345069f)); + CHECK_FLT_SAME( fmaf, (39560.32334f, -59079.345069f, 9605.5546f)); + CHECK_FLT_SAME( fmaf, (-59079.345069f, 39560.32334f, 9605.5546f)); + /* The NaN sign resulting from this invalid operation is not specified. */ + CHECK_FLT_SAME_RELAXED_NAN(fmaf, (+INFINITY, +INFINITY, -INFINITY)); + CHECK_FLT_SAME( fmaf, (4.0f, +INFINITY, 2.0f)); + CHECK_FLT_SAME( fmaf, (4.0f, 4.0f, +INFINITY)); + CHECK_FLT_SAME( fmaf, (-INFINITY, 4.0f, 4.0f)); + CHECK_FLT_SAME( fmaf, (2.34960584706e22f, 7.6050698459e-13f, 9.99996777e27f)); } @@ -3543,24 +3545,25 @@ void testCos() CHECK_DBL( RT_NOCRT(cos)( RTStrNanDouble("123s", false)), RTStrNanDouble("123s", false)); CHECK_DBL( RT_NOCRT(cos)( RTStrNanDouble("9991s", true)), RTStrNanDouble("9991s", true)); - CHECK_DBL_SAME( cos,( 1.0)); - CHECK_DBL_SAME( cos,( 1.5)); - CHECK_DBL_SAME( cos,( +0.0)); - CHECK_DBL_SAME( cos,( +0.0)); - CHECK_DBL_SAME( cos,( -0.0)); - CHECK_DBL_SAME( cos,( -0.0)); - CHECK_DBL_SAME( cos,( 238.6634566)); - CHECK_DBL_SAME( cos,( -49.4578999)); - CHECK_DBL_SAME( cos,( +M_PI)); - CHECK_DBL_SAME( cos,( -M_PI)); + /* FCOS and the CRT may differ slightly in the last few bits. */ + CHECK_DBL_APPROX_SAME(cos,( 1.0), 1); + CHECK_DBL_SAME( cos,( 1.5)); + CHECK_DBL_SAME( cos,( +0.0)); + CHECK_DBL_SAME( cos,( +0.0)); + CHECK_DBL_SAME( cos,( -0.0)); + CHECK_DBL_SAME( cos,( -0.0)); + CHECK_DBL_SAME( cos,( 238.6634566)); + CHECK_DBL_SAME( cos,( -49.4578999)); + CHECK_DBL_SAME( cos,( +M_PI)); + CHECK_DBL_SAME( cos,( -M_PI)); #if 0 /* UCRT does not produce 0.0 here, but some 2**-54 value */ - CHECK_DBL_SAME( cos,( +M_PI_2)); - CHECK_DBL_SAME( cos,( -M_PI_2)); + CHECK_DBL_SAME( cos,( +M_PI_2)); + CHECK_DBL_SAME( cos,( -M_PI_2)); #endif - CHECK_DBL_SAME( cos,( +INFINITY)); - CHECK_DBL_SAME( cos,( -INFINITY)); - CHECK_DBL_SAME( cos,(RTStrNanDouble(NULL, false))); - CHECK_DBL_SAME( cos,(RTStrNanDouble(NULL, true))); + CHECK_DBL_SAME( cos,( +INFINITY)); + CHECK_DBL_SAME( cos,( -INFINITY)); + CHECK_DBL_SAME( cos,(RTStrNanDouble(NULL, false))); + CHECK_DBL_SAME( cos,(RTStrNanDouble(NULL, true))); CHECK_FLT( RT_NOCRT(cosf)( +0.0f), +1.0f); @@ -3845,4 +3848,3 @@ int main() return RTTestSummaryAndDestroy(g_hTest); } - From 9264d013cd523bb7faf4521b98fb79600468567b Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Wed, 12 Aug 2026 15:54:46 +0000 Subject: [PATCH 098/176] Devices/Graphics: state tracker for vertex/index buffers. bugref:10934 svn:sync-xref-src-repo-rev: r174838 --- .../Graphics/DevVGA-SVGA3d-dx-dx11.cpp | 194 +++++++++++++++++- .../Devices/Graphics/DevVGA-SVGA3d-dx.cpp | 45 +++- .../Devices/Graphics/DevVGA-SVGA3d-internal.h | 15 +- 3 files changed, 245 insertions(+), 9 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp index ed0198dfe076..c4b5c9c76098 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114997 2026-08-12 15:54:46Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -313,15 +313,16 @@ typedef struct DXSTREAMOUTPUT typedef struct DXBOUNDVERTEXBUFFER { ID3D11Buffer *pBuffer; - uint32_t stride; - uint32_t offset; + PVMSVGA3DSURFACE pSurface; + //uint32_t stride; + //uint32_t offset; } DXBOUNDVERTEXBUFFER; typedef struct DXBOUNDINDEXBUFFER { ID3D11Buffer *pBuffer; - DXGI_FORMAT indexBufferFormat; - uint32_t indexBufferOffset; + //DXGI_FORMAT indexBufferFormat; + //uint32_t indexBufferOffset; } DXBOUNDINDEXBUFFER; /** @todo Temporary development define. */ @@ -352,6 +353,11 @@ typedef struct DXCONSTANTBUFFERSTATE typedef struct DXBOUNDRESOURCES /* Currently bound resources. Mirror SVGADXContextMobFormat structure. */ { + struct + { + DXBOUNDVERTEXBUFFER vertexBuffers[SVGA3D_DX_MAX_VERTEXBUFFERS]; + DXBOUNDINDEXBUFFER indexBuffer; + } inputAssembly; struct { #ifndef DX_CB @@ -427,9 +433,11 @@ typedef struct VMSVGA3DBACKEND UINT VendorId; UINT DeviceId; +#ifndef DX_STATE_TRACKER SVGADXContextMobFormat svgaDXContext; /* Current state of pipeline. */ DXBOUNDRESOURCES resources; /* What is currently applied to the pipeline. */ +#endif struct { @@ -3410,7 +3418,9 @@ static DECLCALLBACK(int) vmsvga3dBackInit(PPDMDEVINS pDevIns, PVGASTATE pThis, P } #endif +#ifndef DX_STATE_TRACKER vmsvga3dDXInitContextMobData(&pBackend->svgaDXContext); +#endif //DEBUG_BREAKPOINT_TEST(); return rc; } @@ -6440,6 +6450,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXSwitchContext(PVGASTATECC pThisCC, PVMSVG pCb->StartSlot = 0; pCb->NumBuffers = D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; } + + /* Reset vertex buffers. */ + uint32_t const cBoundVB = pDXContextFrom + ? pDXContextFrom->state.ia.vb.cMaxBound + : SVGA3D_DX_MAX_VERTEXBUFFERS; + AssertCompile(SVGA3D_DX_MAX_VERTEXBUFFERS == 32); + pDXContext->state.ia.vb.au32Modified[0] = (UINT32_C(0xFFFFFFFF) >> (32 - cBoundVB)); #endif return VINF_SUCCESS; } @@ -7158,6 +7175,8 @@ static void dxSetConstantBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXCont #endif } + +#ifndef DX_STATE_TRACKER static void dxSetVertexBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) { PVMSVGA3DBACKEND pBackend = pThisCC->svga.p3dState->pBackend; @@ -7237,7 +7256,70 @@ static void dxSetVertexBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContex pDXDevice->pImmediateContext->IASetVertexBuffers(idxMinSlot, (idxMaxSlot - idxMinSlot) + 1, &paResources[idxMinSlot], &paStride[idxMinSlot], &paOffset[idxMinSlot]); } +#else +static void dxSetVertexBuffers(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) +{ + ID3D11Buffer *paResources[SVGA3D_DX_MAX_VERTEXBUFFERS]; + UINT paStride[SVGA3D_DX_MAX_VERTEXBUFFERS]; + UINT paOffset[SVGA3D_DX_MAX_VERTEXBUFFERS]; + + UINT StartSlot = 0; + UINT NumBuffers = 0; + for (UINT i = 0; i < pDXContext->state.ia.vb.cMaxBound; ++i) + { + if (ASMBitTest(pDXContext->state.ia.vb.au32Modified, i)) + { + SVGA3dBufferBinding const *pBufferBinding = &pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i]; + if (pBufferBinding->bufferId != SVGA3D_INVALID_ID) + { + DXBOUNDVERTEXBUFFER const *p = &pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i]; + + /* pBuffer is created in ensureResourcesAndViews */ + PVMSVGA3DSURFACE pSurface = p->pSurface; + paResources[i] = p->pBuffer; + + /* DX11 supports stride up to 2048. Ignore large values (> 40000) that Ubuntu guest might send. */ + paStride[i] = pBufferBinding->stride <= 2048 ? pBufferBinding->stride : 0; + + if ( paStride[i] <= pSurface->paMipmapLevels[0].cbSurface + && pBufferBinding->offset <= pSurface->paMipmapLevels[0].cbSurface - paStride[i]) + paOffset[i] = pBufferBinding->offset; + else + { + paStride[i] = 0; + paOffset[i] = 0; + } + } + else + { + paResources[i] = NULL; + paStride[i] = 0; + paOffset[i] = 0; + } + + if (NumBuffers == 0) + StartSlot = i; + ++NumBuffers; + } + else if (NumBuffers > 0) + { + pDXDevice->pImmediateContext->IASetVertexBuffers(StartSlot, NumBuffers, + &paResources[StartSlot], + &paStride[StartSlot], + &paOffset[StartSlot]); + NumBuffers = 0; + } + } + + if (NumBuffers > 0) + pDXDevice->pImmediateContext->IASetVertexBuffers(StartSlot, NumBuffers, + &paResources[StartSlot], + &paStride[StartSlot], + &paOffset[StartSlot]); +} +#endif +#ifndef DX_STATE_TRACKER static void dxSetIndexBuffer(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) { PVMSVGA3DBACKEND pBackend = pThisCC->svga.p3dState->pBackend; @@ -7295,6 +7377,39 @@ static void dxSetIndexBuffer(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); pDXDevice->pImmediateContext->IASetIndexBuffer(pBuffer, enmDxgiFormat, Offset); } +#else +static void dxSetIndexBuffer(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) +{ + ID3D11Buffer *pBuffer; + DXGI_FORMAT enmDxgiFormat; + UINT Offset; + + if (pDXContext->svgaDXContext.inputAssembly.indexBufferSid != SVGA3D_INVALID_ID) + { + /* pBuffer is created in ensureResourcesAndViews */ + enmDxgiFormat = vmsvgaDXSurfaceFormat2Dxgi((SVGA3dSurfaceFormat)pDXContext->svgaDXContext.inputAssembly.indexBufferFormat); + if (enmDxgiFormat == DXGI_FORMAT_R16_UINT || enmDxgiFormat == DXGI_FORMAT_R32_UINT) + { + pBuffer = pDXContext->pBackendDXContext->resources.inputAssembly.indexBuffer.pBuffer; + Offset = pDXContext->svgaDXContext.inputAssembly.indexBufferOffset; + } + else + { + pBuffer = NULL; + enmDxgiFormat = DXGI_FORMAT_UNKNOWN; + Offset = 0; + } + } + else + { + pBuffer = NULL; + enmDxgiFormat = DXGI_FORMAT_UNKNOWN; + Offset = 0; + } + + pDXDevice->pImmediateContext->IASetIndexBuffer(pBuffer, enmDxgiFormat, Offset); +} +#endif #ifdef LOG_ENABLED static void dxDbgLogVertexElement(DXGI_FORMAT Format, void const *pvElementData) @@ -7851,7 +7966,7 @@ static int dxEnsureUnorderedAccessView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT p #ifdef DX_STATE_TRACKER -static void dxEnsureViews(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) +static void dxEnsureResourcesAndViews(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) { /* * Ensure that all views exist. @@ -7980,6 +8095,57 @@ static void dxEnsureViews(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) } } } + + /* Vertex buffers. */ + for (uint32_t i = 0; i < pDXContext->state.ia.vb.cMaxBound; ++i) + { + if (pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].bufferId != SVGA3D_INVALID_ID) + { + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + rc = dxEnsureResource(pThisCC, pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].bufferId, &pSurface, &pResource); + AssertContinue(RT_SUCCESS(rc)); + + if (pSurface->pBackendSurface->enmResType == VMSVGA3D_RESTYPE_BUFFER) + { + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pBuffer = (ID3D11Buffer *)pResource; + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pSurface = pSurface; + } + else + { + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pBuffer = NULL; + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pSurface = NULL; + } + + LogFunc(("vb[%u]: sid = %u, stride %u, offset %u%s\n", i, + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].bufferId, + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].stride, + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].offset, + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pBuffer ? "" : " NULL")); + } + } + + /* Index buffer. */ + if (pDXContext->svgaDXContext.inputAssembly.indexBufferSid != SVGA3D_INVALID_ID) + { + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + rc = dxEnsureResource(pThisCC, pDXContext->svgaDXContext.inputAssembly.indexBufferSid, &pSurface, &pResource); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (pSurface->pBackendSurface->enmResType == VMSVGA3D_RESTYPE_BUFFER) + pDXContext->pBackendDXContext->resources.inputAssembly.indexBuffer.pBuffer = (ID3D11Buffer *)pResource; + else + pDXContext->pBackendDXContext->resources.inputAssembly.indexBuffer.pBuffer = NULL; + + LogFunc(("ib: sid = %u, offset %u, fmt %u%s\n", + pDXContext->svgaDXContext.inputAssembly.indexBufferSid, + pDXContext->svgaDXContext.inputAssembly.indexBufferOffset, + pDXContext->svgaDXContext.inputAssembly.indexBufferFormat, + pDXContext->pBackendDXContext->resources.inputAssembly.indexBuffer.pBuffer ? "" : " NULL")); + } + } } @@ -8558,7 +8724,7 @@ static void dxSetupPipeline(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturnVoid(pDXDevice->pDevice); - dxEnsureViews(pThisCC, pDXContext); + dxEnsureResourcesAndViews(pThisCC, pDXContext); /* * State objects @@ -8656,8 +8822,22 @@ static void dxSetupPipeline(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) #endif dxSetConstantBuffers(pThisCC, pDXContext); +#ifndef DX_STATE_TRACKER dxSetVertexBuffers(pThisCC, pDXContext); dxSetIndexBuffer(pThisCC, pDXContext); +#else + if (pDXContext->u64ContextFlags & DX_CTX_F_STATE_VERTEXBUFFER) + { + pDXContext->u64ContextFlags &= ~DX_CTX_F_STATE_VERTEXBUFFER; + dxSetVertexBuffers(pDXDevice, pDXContext); + } + + if (pDXContext->u64ContextFlags & DX_CTX_F_STATE_INDEXBUFFER) + { + pDXContext->u64ContextFlags &= ~DX_CTX_F_STATE_INDEXBUFFER; + dxSetIndexBuffer(pDXDevice, pDXContext); + } +#endif /* * Shader resources diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp index 8c029c1a9c96..19175de2ba48 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx.cpp 114712 2026-07-15 17:24:33Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx.cpp 114997 2026-08-12 15:54:46Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - Common code for DX backend interface. */ @@ -136,6 +136,7 @@ void vmsvga3dDXInitContextMobData(SVGADXContextMobFormat *p) DECLINLINE(void) dxPostDraw(PVMSVGA3DDXCONTEXT pDXContext) { + RT_ZERO(pDXContext->state.ia.vb.au32Modified); RT_ZERO(pDXContext->state.shader[0].shaderResources.au64Modified); RT_ZERO(pDXContext->state.shader[1].shaderResources.au64Modified); RT_ZERO(pDXContext->state.shader[2].shaderResources.au64Modified); @@ -871,6 +872,7 @@ int vmsvga3dDXSetVertexBuffers(PVGASTATECC pThisCC, uint32_t idDXContext, uint32 ASSERT_GUEST_RETURN(cVertexBuffer <= SVGA3D_DX_MAX_VERTEXBUFFERS - startBuffer, VERR_INVALID_PARAMETER); RT_UNTRUSTED_VALIDATED_FENCE(); +#ifndef DX_STATE_TRACKER for (uint32_t i = 0; i < cVertexBuffer; ++i) { uint32_t const idxVertexBuffer = startBuffer + i; @@ -879,6 +881,35 @@ int vmsvga3dDXSetVertexBuffers(PVGASTATECC pThisCC, uint32_t idDXContext, uint32 pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].stride = paVertexBuffer[i].stride; pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].offset = paVertexBuffer[i].offset; } +#else + bool fModified = false; + uint32_t cMaxBound = 0; + for (uint32_t i = 0; i < cVertexBuffer; ++i) + { + uint32_t const idxVertexBuffer = startBuffer + i; + + if ( pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].bufferId != paVertexBuffer[i].sid + || pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].stride != paVertexBuffer[i].stride + || pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].offset != paVertexBuffer[i].offset) + { + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].bufferId = paVertexBuffer[i].sid; + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].stride = paVertexBuffer[i].stride; + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].offset = paVertexBuffer[i].offset; + fModified = true; + ASMBitSet(pDXContext->state.ia.vb.au32Modified, idxVertexBuffer); + } + + if (paVertexBuffer[i].sid != SVGA3D_INVALID_ID) + cMaxBound = idxVertexBuffer + 1; + } + + if (fModified) + pDXContext->u64ContextFlags |= DX_CTX_F_STATE_VERTEXBUFFER; + + /* Remember how many slots the context actually uses. */ + if (pDXContext->state.ia.vb.cMaxBound < cMaxBound) + pDXContext->state.ia.vb.cMaxBound = cMaxBound; +#endif rc = pSvgaR3State->pFuncsDX->pfnDXSetVertexBuffers(pThisCC, pDXContext, startBuffer, cVertexBuffer, paVertexBuffer); return rc; @@ -897,9 +928,21 @@ int vmsvga3dDXSetIndexBuffer(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCm rc = vmsvga3dDXContextFromCid(p3dState, idDXContext, &pDXContext); AssertRCReturn(rc, rc); +#ifndef DX_STATE_TRACKER pDXContext->svgaDXContext.inputAssembly.indexBufferSid = pCmd->sid; pDXContext->svgaDXContext.inputAssembly.indexBufferOffset = pCmd->offset; pDXContext->svgaDXContext.inputAssembly.indexBufferFormat = pCmd->format; +#else + if ( pDXContext->svgaDXContext.inputAssembly.indexBufferSid != pCmd->sid + || pDXContext->svgaDXContext.inputAssembly.indexBufferOffset != pCmd->offset + || pDXContext->svgaDXContext.inputAssembly.indexBufferFormat != (uint32)pCmd->format) + { + pDXContext->svgaDXContext.inputAssembly.indexBufferSid = pCmd->sid; + pDXContext->svgaDXContext.inputAssembly.indexBufferOffset = pCmd->offset; + pDXContext->svgaDXContext.inputAssembly.indexBufferFormat = pCmd->format; + pDXContext->u64ContextFlags |= DX_CTX_F_STATE_INDEXBUFFER; + } +#endif rc = pSvgaR3State->pFuncsDX->pfnDXSetIndexBuffer(pThisCC, pDXContext, pCmd->sid, pCmd->format, pCmd->offset); return rc; diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h index 45d60c575917..c8ae90a657e7 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-internal.h 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-internal.h 114997 2026-08-12 15:54:46Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device - 3D part, internal header. */ @@ -1001,6 +1001,8 @@ static SSMFIELD const g_aVMSVGA3DCONTEXTFields[] = #define DX_CTX_F_STATE_VIEWPORT 0x00000400 #define DX_CTX_F_STATE_SCISSORRECT 0x00000800 #define DX_CTX_F_STATE_RASTERIZERSTATE 0x00001000 +#define DX_CTX_F_STATE_INDEXBUFFER 0x00002000 +#define DX_CTX_F_STATE_VERTEXBUFFER 0x00004000 #define DX_CTX_F_STATE_SAMPLER_VS 0x00010000 /* Sampler bits must be in this order without gaps for '<<'. */ #define DX_CTX_F_STATE_SAMPLER_PS 0x00020000 #define DX_CTX_F_STATE_SAMPLER_GS 0x00040000 @@ -1023,6 +1025,8 @@ static SSMFIELD const g_aVMSVGA3DCONTEXTFields[] = | DX_CTX_F_STATE_VIEWPORT \ | DX_CTX_F_STATE_SCISSORRECT \ | DX_CTX_F_STATE_RASTERIZERSTATE \ + | DX_CTX_F_STATE_INDEXBUFFER \ + | DX_CTX_F_STATE_VERTEXBUFFER \ | DX_CTX_F_STATE_SAMPLER_VS \ | DX_CTX_F_STATE_SAMPLER_PS \ | DX_CTX_F_STATE_SAMPLER_GS \ @@ -1097,6 +1101,15 @@ typedef struct VMSVGA3DDXCONTEXT } cot; struct { + struct + { + struct + { + uint32_t cMaxBound; + AssertCompile(SVGA3D_DX_MAX_VERTEXBUFFERS == 32); + uint32_t au32Modified[(SVGA3D_DX_MAX_VERTEXBUFFERS + 31) / 32]; + } vb; + } ia; struct { struct From 40f2b623b5a80c24956880e0d90d97a9760da890 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:06:36 +0000 Subject: [PATCH 099/176] IPRT/Makefile.kmk: Some more NASM adjustments. github:gh-520 svn:sync-xref-src-repo-rev: r174839 --- src/VBox/Runtime/Makefile.kmk | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/VBox/Runtime/Makefile.kmk b/src/VBox/Runtime/Makefile.kmk index 361ba9527d75..bb037bb94d64 100644 --- a/src/VBox/Runtime/Makefile.kmk +++ b/src/VBox/Runtime/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114131 2026-05-14 12:27:45Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114998 2026-08-12 23:06:36Z knut.osmundsen@oracle.com $ ## @file # Sub-Makefile for the IPRT. # @@ -2891,6 +2891,7 @@ RuntimeGuestR3_SOURCES = $(filter-out \ ifndef VBOX_WITH_NOCRT_STATIC RuntimeGuestR3_DEFS.win.x86 = $(RuntimeR3_DEFS.win.x86) \ VCC_FAKES_TARGET_$(VBOX_VCC_TOOL_STEM) VCC_FAKES_TARGET=$(substr $(VBOX_VCC_TOOL_STEM),-3) + RuntimeGuestR3_ASINCS.win.x86 = $(RuntimeR3_ASINCS.win.x86) r3/win/ # NASM crap RuntimeGuestR3_SOURCES.win.x86 = $(RuntimeR3_SOURCES.win.x86) \ r3/win/vcc-fakes-kernel32.cpp \ r3/win/vcc-fakes-kernel32-A.asm \ @@ -3640,6 +3641,7 @@ if ($(VBOX_SOLARIS_11_UPDATE_VERSION) > 2 \ endif RuntimeR0Drv_INCS := $(PATH_SUB_CURRENT) include +RuntimeR0Drv_ASINCS.win.x86 = common/compiler/vcc/ # NASM crap RuntimeR0Drv_INCS.freebsd = \ $(PATH_STAGE)/gen-sys-hdrs RuntimeR0Drv_INCS.solaris = \ @@ -4637,6 +4639,7 @@ endif ifndef VBOX_WITH_NOCRT_STATIC RuntimeR3VccTricks_TEMPLATE = VBoxR3Static RuntimeR3VccTricks_DEFS = VCC_FAKES_TARGET_$(VBOX_VCC_TOOL_STEM) VCC_FAKES_TARGET=$(substr $(VBOX_VCC_TOOL_STEM),-3) + RuntimeR3VccTricks_ASINCS = r3/win/ # NASM crap RuntimeR3VccTricks_SOURCES = \ r3/win/vcc-fakes-kernel32.cpp \ r3/win/vcc-fakes-kernel32-A.asm \ From 9d7d0e0c572a3b72d3cd5e45f4925673df87ee15 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:09:20 +0000 Subject: [PATCH 100/176] iprt/asmdefs.mac: Modified PE 'export' directives as it turns out YASM and NASM does different things (YASM is buggy). github:gh-520 svn:sync-xref-src-repo-rev: r174840 --- include/iprt/asmdefs.mac | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/iprt/asmdefs.mac b/include/iprt/asmdefs.mac index 3785ba4feb11..cbe4c06b02a6 100644 --- a/include/iprt/asmdefs.mac +++ b/include/iprt/asmdefs.mac @@ -638,7 +638,8 @@ SAFE_LABEL NAME(%1) ; Global exported marker which is DECLASM() compatible. %macro EXPORTEDNAME 1 %ifdef ASM_FORMAT_PE - export %1=NAME(%1) + ; HACK ALERT! export difference: nasm: passthru; yasm: word after '=', ignore the rest. (was: export %1=NAME(%1)) + export NAME(%1) %endif %ifdef __NASM__ %ifdef ASM_FORMAT_OMF @@ -739,7 +740,8 @@ GLOBALNAME_RAW NAME(%1), %2, %3, %4 ; %macro EXPORTEDNAME_RAW 3-4 %ifdef ASM_FORMAT_PE - export %2=%1 + ; HACK ALERT! export difference: nasm: passthru; yasm: word after '=', ignore the rest. (was: export %2=%1) + export %1 %endif %ifdef __NASM__ %ifdef ASM_FORMAT_OMF @@ -812,7 +814,8 @@ BEGINPROC_RAW NAME(%1), %2, CALC_PROC_SIZE(%1) %endif %ifdef RT_ASM_WITH_SEH64 %ifdef ASM_FORMAT_PE -export %2=%1 + ; HACK ALERT! export difference: nasm: passthru; yasm: word after '=', ignore the rest. (was: export %2=%1) +export %1 %endif global %1:function proc_frame %1 From efd966bffa75487d42b75fa6f75c14da9d94a1fe Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:10:34 +0000 Subject: [PATCH 101/176] IPRT/DefToAsmExternsVcc32.sed: Fixes to make it work with NASM. github:gh-520 svn:sync-xref-src-repo-rev: r174841 --- src/VBox/Runtime/VBox/DefToAsmExternsVcc32.sed | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/VBox/Runtime/VBox/DefToAsmExternsVcc32.sed b/src/VBox/Runtime/VBox/DefToAsmExternsVcc32.sed index df12c51a4d86..69627aedc13f 100644 --- a/src/VBox/Runtime/VBox/DefToAsmExternsVcc32.sed +++ b/src/VBox/Runtime/VBox/DefToAsmExternsVcc32.sed @@ -1,4 +1,4 @@ -# $Id: DefToAsmExternsVcc32.sed 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: DefToAsmExternsVcc32.sed 115000 2026-08-12 23:10:34Z knut.osmundsen@oracle.com $ ## @file # SED script for generating assembly externs from a VBoxRT windows .def file. # @@ -39,6 +39,11 @@ # /not-some-systems/d +# +# Check the external side of function aliases. +# +s/=[^ ;]*// + # # Remove comments and space. Skip empty lines. # @@ -54,7 +59,8 @@ s/[[:space:]][[:space:]]*$//g s/^EXPORTS$// /^$/b end -/^?/b cpp_export +/^?.*[[:space:]]DATA$/b cpp_data_export +/^?.*/b cpp_export /[[:space:]]DATA$/b data # @@ -72,12 +78,16 @@ s/^\(.*\)[[:space:]]*DATA$/EXTERN_IMP2 \1/ b end # -# Mangled C++ . +# Mangled C++ code and data. # :cpp_export s/^\(.*\)$/extern __imp_\1/ b end +:cpp_data_export +s/^\(.*\)[[:space:]]DATA$/extern __imp_\1/ +b end + } d b end From 15b8fc544cb1c4be7230f734d037e2810ecc6184 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:13:51 +0000 Subject: [PATCH 102/176] iprt/asmdefs.mac: Added MARK_OBJECT_RETPOLINE_SAFE for marking assembly code as retpoline safe on windows 64. bugref:11138 svn:sync-xref-src-repo-rev: r174842 --- include/iprt/asmdefs.mac | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/include/iprt/asmdefs.mac b/include/iprt/asmdefs.mac index cbe4c06b02a6..49b0b1d69f0e 100644 --- a/include/iprt/asmdefs.mac +++ b/include/iprt/asmdefs.mac @@ -1409,6 +1409,33 @@ BEGINPROC_EXPORTED RT_NOCRT(%1), 0 %endmacro ; RT_NOCRT_BEGINPROC +;; @def MARK_OBJECT_RETPOLINE_SAFE +; Marks the object as retpoline safe for the Visual C++ linker. +; +; Repoline support in the Visual C++ toolchain is undocumented, but compiling +; with /guardretpoline or /d2guardretpoline and linking /guard:retpoline will +; add tables annotating the indirect jump and call sites. +; +; The .retplne section starts with a magic 'RetpolineV1\0' and is followed by +; zero or more variable length markup tables for code sections containing +; indirect jumps & calls. The first two dword are the table length and section +; number. This is then followed by dword pairs of type and offset. +; Type 9: Control-flow guarded indirect call. offset of the call instruction. +; +; For the time being we only emit and empty table, as getting the section +; number is probably not supported by the assemblers yet. +; +%macro MARK_OBJECT_RETPOLINE_SAFE 0 +%ifdef RT_ARCH_AMD64 + %ifdef ASM_FORMAT_PE + %ifdef IN_RING0 +section .retplne info align=1 ; flags=100200 + db 'RetpolineV1', 0 + %endif + %endif +%endif +%endmacro ; MARK_OBJECT_RETPOLINE_SAFE + ;; @def xCB ; The stack unit size / The register unit size. From c4a3d1fcfc34212aefb39708d4e264986529aa21 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:15:10 +0000 Subject: [PATCH 103/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 1. bugref:11138 svn:sync-xref-src-repo-rev: r174843 --- src/VBox/Runtime/win/amd64/ASMAtomicBitClear.asm | 1 + src/VBox/Runtime/win/amd64/ASMAtomicBitTestAndToggle.asm | 1 + src/VBox/Runtime/win/amd64/ASMAtomicBitToggle.asm | 1 + src/VBox/Runtime/win/amd64/ASMAtomicReadU64.asm | 1 + src/VBox/Runtime/win/amd64/ASMAtomicXchgU8.asm | 1 + src/VBox/Runtime/win/amd64/ASMGetCS.asm | 1 + src/VBox/Runtime/win/amd64/ASMGetDR0.asm | 2 ++ src/VBox/Runtime/win/amd64/ASMGetDR1.asm | 2 ++ src/VBox/Runtime/win/amd64/ASMGetDR2.asm | 2 ++ src/VBox/Runtime/win/amd64/ASMGetDR3.asm | 2 ++ src/VBox/Runtime/win/amd64/ASMGetDR6.asm | 2 ++ src/VBox/Runtime/win/amd64/ASMGetDR7.asm | 2 ++ src/VBox/Runtime/win/amd64/ASMGetDS.asm | 1 + src/VBox/Runtime/win/amd64/ASMGetES.asm | 1 + src/VBox/Runtime/win/amd64/ASMGetFS.asm | 1 + src/VBox/Runtime/win/amd64/ASMGetGS.asm | 1 + src/VBox/Runtime/win/amd64/ASMGetSS.asm | 1 + src/VBox/Runtime/win/amd64/ASMProbeReadByte.asm | 1 + 18 files changed, 24 insertions(+) diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicBitClear.asm b/src/VBox/Runtime/win/amd64/ASMAtomicBitClear.asm index d440aaac29b0..23ed7d4756cf 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicBitClear.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicBitClear.asm @@ -52,3 +52,4 @@ RT_BEGINPROC ASMAtomicBitClear ret ENDPROC ASMAtomicBitClear +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicBitTestAndToggle.asm b/src/VBox/Runtime/win/amd64/ASMAtomicBitTestAndToggle.asm index f716cd479ef8..82d28a83ee8f 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicBitTestAndToggle.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicBitTestAndToggle.asm @@ -54,3 +54,4 @@ RT_BEGINPROC ASMAtomicBitTestAndToggle ret ENDPROC ASMAtomicBitTestAndToggle +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicBitToggle.asm b/src/VBox/Runtime/win/amd64/ASMAtomicBitToggle.asm index 288c850d822d..b77ba37ba377 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicBitToggle.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicBitToggle.asm @@ -51,3 +51,4 @@ RT_BEGINPROC ASMAtomicBitToggle ret ENDPROC ASMAtomicBitToggle +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicReadU64.asm b/src/VBox/Runtime/win/amd64/ASMAtomicReadU64.asm index df43ba2d92b1..289dd814d531 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicReadU64.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicReadU64.asm @@ -53,3 +53,4 @@ RT_BEGINPROC ASMAtomicReadU64 ret ENDPROC ASMAtomicReadU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicXchgU8.asm b/src/VBox/Runtime/win/amd64/ASMAtomicXchgU8.asm index 3c495f418864..721db58ddd02 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicXchgU8.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicXchgU8.asm @@ -50,3 +50,4 @@ RT_BEGINPROC ASMAtomicXchgU8 ret ENDPROC ASMAtomicXchgU8 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetCS.asm b/src/VBox/Runtime/win/amd64/ASMGetCS.asm index f79f9b6c785e..720af298d4fb 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetCS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetCS.asm @@ -50,3 +50,4 @@ RT_BEGINPROC ASMGetCS ret ENDPROC ASMGetCS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR0.asm b/src/VBox/Runtime/win/amd64/ASMGetDR0.asm index 45a1e54b1584..285f046ee7b5 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR0.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR0.asm @@ -55,3 +55,5 @@ RT_BEGINPROC ASMSetDR0 mov dr0, rcx ret ENDPROC ASMSetDR0 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR1.asm b/src/VBox/Runtime/win/amd64/ASMGetDR1.asm index 0b3277772617..ce6616face87 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR1.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR1.asm @@ -54,3 +54,5 @@ RT_BEGINPROC ASMSetDR1 mov dr1, rcx ret ENDPROC ASMSetDR1 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR2.asm b/src/VBox/Runtime/win/amd64/ASMGetDR2.asm index fb846ba7e13c..b04e953761ce 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR2.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR2.asm @@ -55,3 +55,5 @@ RT_BEGINPROC ASMSetDR2 mov dr2, rcx ret ENDPROC ASMSetDR2 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR3.asm b/src/VBox/Runtime/win/amd64/ASMGetDR3.asm index 5630cb90c288..bf1c0f65025a 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR3.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR3.asm @@ -55,3 +55,5 @@ RT_BEGINPROC ASMSetDR3 mov dr3, rcx ret ENDPROC ASMSetDR3 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR6.asm b/src/VBox/Runtime/win/amd64/ASMGetDR6.asm index b677b912ef02..170f3fb6ee49 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR6.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR6.asm @@ -54,3 +54,5 @@ RT_BEGINPROC ASMSetDR6 mov dr6, rcx ret ENDPROC ASMSetDR6 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR7.asm b/src/VBox/Runtime/win/amd64/ASMGetDR7.asm index ab7a17b0f6da..46d9bd91873a 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR7.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR7.asm @@ -53,3 +53,5 @@ RT_BEGINPROC ASMSetDR7 mov dr7, rcx ret ENDPROC ASMSetDR7 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDS.asm b/src/VBox/Runtime/win/amd64/ASMGetDS.asm index 23fbf4c19ebc..850d6e7d6a9c 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDS.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetDS ret ENDPROC ASMGetDS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetES.asm b/src/VBox/Runtime/win/amd64/ASMGetES.asm index 5350c360bf68..56b56af08aae 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetES.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetES.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetES ret ENDPROC ASMGetES +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetFS.asm b/src/VBox/Runtime/win/amd64/ASMGetFS.asm index 0d6a5e1bb751..000cb2820fe0 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetFS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetFS.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetFS ret ENDPROC ASMGetFS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetGS.asm b/src/VBox/Runtime/win/amd64/ASMGetGS.asm index 86f8e9508c3c..224a416bdd33 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetGS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetGS.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetGS ret ENDPROC ASMGetGS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetSS.asm b/src/VBox/Runtime/win/amd64/ASMGetSS.asm index 766193211295..9f6d1a8364b2 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetSS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetSS.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetSS ret ENDPROC ASMGetSS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMProbeReadByte.asm b/src/VBox/Runtime/win/amd64/ASMProbeReadByte.asm index 6875db9098fd..0445bf4952dc 100644 --- a/src/VBox/Runtime/win/amd64/ASMProbeReadByte.asm +++ b/src/VBox/Runtime/win/amd64/ASMProbeReadByte.asm @@ -56,3 +56,4 @@ RT_BEGINPROC ASMProbeReadByte ret ENDPROC ASMProbeReadByte +MARK_OBJECT_RETPOLINE_SAFE From 4a40f58f17b00c5ad0d995fef608b11462497144 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:16:08 +0000 Subject: [PATCH 104/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 2. Also some NASM adjustments. bugref:11138 github:gh-520 svn:sync-xref-src-repo-rev: r174844 --- .../Runtime/r0drv/nt/alloca-x86-r0drv-nt.asm | 3 +- .../Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm | 28 ++++++++++++++++--- .../Runtime/r0drv/nt/security-cookie-vcc.asm | 3 +- .../r0drv/nt/toxic-chkstk-r0drv-nt.asm | 3 +- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/VBox/Runtime/r0drv/nt/alloca-x86-r0drv-nt.asm b/src/VBox/Runtime/r0drv/nt/alloca-x86-r0drv-nt.asm index 41c523891890..1955fcda2b9e 100644 --- a/src/VBox/Runtime/r0drv/nt/alloca-x86-r0drv-nt.asm +++ b/src/VBox/Runtime/r0drv/nt/alloca-x86-r0drv-nt.asm @@ -1,4 +1,4 @@ -; $Id: alloca-x86-r0drv-nt.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: alloca-x86-r0drv-nt.asm 115003 2026-08-12 23:16:08Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ __alloca__probe_16. ; @@ -67,3 +67,4 @@ BEGINPROC _alloca_probe_16 jmp [eax] ENDPROC _alloca_probe_16 +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: _alloc_probe_16 is doing an indirect call... diff --git a/src/VBox/Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm b/src/VBox/Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm index 9a0a6dcec204..bddfc3e861d0 100644 --- a/src/VBox/Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm +++ b/src/VBox/Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm @@ -1,4 +1,4 @@ -; $Id: nt3fakesA-r0drv-nt.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: nt3fakesA-r0drv-nt.asm 115003 2026-08-12 23:16:08Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Companion to nt3fakes-r0drv-nt.cpp that provides import stuff to satisfy the linker. ; @@ -54,11 +54,15 @@ BEGINPROC _rtNt3InitSymbolsAssembly ; @param 1 The fastcall name. ; @param 2 Byte size of arguments. %macro DefineImportDataAndInitCode 3 +%ifdef __NASM__ +extern %1 %+ Nt3Fb_ %+ %2 %+ @ %+ %3 +%else extern $%1 %+ Nt3Fb_ %+ %2 %+ @ %+ %3 +%endif BEGINDATA extern _g_pfnrt %+ %2 GLOBALNAME __imp_ %+ %1 %+ %2 %+ @ %+ %3 - dd $%1 %+ Nt3Fb_ %+ %2 %+ @ %+ %3 + dd %1 %+ Nt3Fb_ %+ %2 %+ @ %+ %3 BEGINCODE mov eax, [_g_pfnrt %+ %2] test eax, eax @@ -93,7 +97,11 @@ ENDPROC _rtNt3InitSymbolsAssembly BEGINCODE extern _g_pfnrt %+ %1 extern _g_pfnrt %+ %2 +%ifdef __NASM__ +BEGINPROC_EXPORTED @ %+ %1 %+ @ %+ %3 +%else BEGINPROC_EXPORTED $@ %+ %1 %+ @ %+ %3 +%endif mov eax, [_g_pfnrt %+ %1] cmp eax, 0 jnz .got_fast_call @@ -116,15 +124,20 @@ BEGINPROC_EXPORTED $@ %+ %1 %+ @ %+ %3 %endif leave ret + int3 .got_fast_call: mov [__imp_@ %+ %1 %+ @ %+ %3], eax jmp eax +%ifdef __NASM__ +ENDPROC @ %+ %1 %+ @ %+ %3 +%else ENDPROC $@ %+ %1 %+ @ %+ %3 +%endif BEGINDATA GLOBALNAME __imp_@ %+ %1 %+ @ %+ %3 - dd $@ %+ %1 %+ @ %+ %3 + dd @ %+ %1 %+ @ %+ %3 %endmacro FastOrStdCallWrapper IofCompleteRequest, IoCompleteRequest, 8, 0 @@ -140,14 +153,19 @@ FastOrStdCallWrapper KefReleaseSpinLockFromDpcLevel,KeReleaseSpinLockFromDpcLeve BEGINCODE ; LONG FASTCALL InterlockedExchange(LONG volatile *,LONG ); +%ifdef __NASM__ +BEGINPROC_EXPORTED @InterlockedExchange@8 +%else BEGINPROC_EXPORTED $@InterlockedExchange@8 +%endif mov eax, edx xchg [ecx], eax ret + int3 BEGINDATA GLOBALNAME __imp_@InterlockedExchange@8 - dd $@InterlockedExchange@8 + dd @InterlockedExchange@8 BEGINDATA @@ -155,3 +173,5 @@ GLOBALNAME __imp__KeTickCount GLOBALNAME _KeTickCount dd 0 + +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: we're doing indirect calls here of course. diff --git a/src/VBox/Runtime/r0drv/nt/security-cookie-vcc.asm b/src/VBox/Runtime/r0drv/nt/security-cookie-vcc.asm index 38f2709e97b3..c0a0efc48f2e 100644 --- a/src/VBox/Runtime/r0drv/nt/security-cookie-vcc.asm +++ b/src/VBox/Runtime/r0drv/nt/security-cookie-vcc.asm @@ -1,4 +1,4 @@ -; $Id: security-cookie-vcc.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: security-cookie-vcc.asm 115003 2026-08-12 23:16:08Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Stack related Visual C++ support routines, ring-0. ; @@ -138,3 +138,4 @@ BEGINPROC __security_init_cookie ret ENDPROC __security_init_cookie +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r0drv/nt/toxic-chkstk-r0drv-nt.asm b/src/VBox/Runtime/r0drv/nt/toxic-chkstk-r0drv-nt.asm index c283591823d0..b39f82df034c 100644 --- a/src/VBox/Runtime/r0drv/nt/toxic-chkstk-r0drv-nt.asm +++ b/src/VBox/Runtime/r0drv/nt/toxic-chkstk-r0drv-nt.asm @@ -1,4 +1,4 @@ -; $Id: toxic-chkstk-r0drv-nt.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: toxic-chkstk-r0drv-nt.asm 115003 2026-08-12 23:16:08Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Toxic _chkstk symbol. ; @@ -50,3 +50,4 @@ BEGINPROC _chkstk jmp MY_SYM ENDPROC _chkstk +MARK_OBJECT_RETPOLINE_SAFE From d8bd1e8b3fa1f8d18a8d6a2922e3211d1da80e3a Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:33:28 +0000 Subject: [PATCH 105/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 3. bugref:11138 svn:sync-xref-src-repo-rev: r174845 --- src/VBox/Runtime/common/misc/setjmp.asm | 7 ++++++- src/VBox/Runtime/common/misc/zero.asm | 3 ++- src/VBox/Runtime/common/time/timesupA.asm | 7 ++++--- src/VBox/Runtime/common/time/timesupA.mac | 17 +++++++++++++---- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/VBox/Runtime/common/misc/setjmp.asm b/src/VBox/Runtime/common/misc/setjmp.asm index 7f21ba488355..293a01156012 100644 --- a/src/VBox/Runtime/common/misc/setjmp.asm +++ b/src/VBox/Runtime/common/misc/setjmp.asm @@ -1,4 +1,4 @@ -; $Id: setjmp.asm 113142 2026-02-24 12:23:15Z knut.osmundsen@oracle.com $ +; $Id: setjmp.asm 115004 2026-08-12 23:33:28Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT setjmp & longjmp - AMD64 & X86. ; @@ -288,6 +288,7 @@ RT_NOCRT_BEGINPROC setjmp .strict_zero_args: %endif ret + int3 .have_xcpt_reg_rec: ; Get the parameter count. @@ -307,6 +308,7 @@ RT_NOCRT_BEGINPROC setjmp dec eax jnz .copy_unwind_data ret + int3 .set_try_level_from_xcpt_reg_rec_reload_ptr_first: mov edx, [ecx + RTJMPBUF.pXcptRegRec] @@ -314,6 +316,7 @@ RT_NOCRT_BEGINPROC setjmp mov edx, [edx + 12] ; Something following the EXCEPTION_REGISTRATION_RECORD... mov [ecx + RTJMPBUF.uTryLevel], edx ret + int3 ; Copy unwind data. .copy_unwind_data: @@ -519,6 +522,7 @@ RT_NOCRT_BEGINPROC longjmp int3 %endif jmp .nt_init_xcpt_rec + int3 %else ; RT_ARCH_X86 push 0 ; zero ('return value') lea eax, [ADDR_EXPR_XCPT_REC] @@ -651,3 +655,4 @@ ENDPROC longjmp %endif ; !RT_WITHOUT_NOCRT_WRAPPERS %endif ; RT_OS_WINDOWS +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: there are indirect calls here! diff --git a/src/VBox/Runtime/common/misc/zero.asm b/src/VBox/Runtime/common/misc/zero.asm index d36cc0d34f13..c8ac861b9e62 100644 --- a/src/VBox/Runtime/common/misc/zero.asm +++ b/src/VBox/Runtime/common/misc/zero.asm @@ -1,4 +1,4 @@ -; $Id: zero.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: zero.asm 115004 2026-08-12 23:33:28Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Zero Memory. ; @@ -73,3 +73,4 @@ EXPORTEDNAME_EX g_abRTZero64K, object, _64K times 0x10000/(16*4) dd 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 %endif +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/time/timesupA.asm b/src/VBox/Runtime/common/time/timesupA.asm index ccb9953a696a..05f630cea893 100644 --- a/src/VBox/Runtime/common/time/timesupA.asm +++ b/src/VBox/Runtime/common/time/timesupA.asm @@ -1,4 +1,4 @@ -; $Id: timesupA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: timesupA.asm 115004 2026-08-12 23:33:28Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Time using SUPLib, the Assembly Implementation. ; @@ -34,9 +34,8 @@ ; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 ; -%ifndef IN_GUEST - %include "iprt/asmdefs.mac" +%ifndef IN_GUEST ; rest of the file %include "VBox/sup.mac" ; @@ -159,3 +158,5 @@ BEGINCODE %endif ; !IN_GUEST + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/time/timesupA.mac b/src/VBox/Runtime/common/time/timesupA.mac index 2807bb207da3..5b282336dfee 100644 --- a/src/VBox/Runtime/common/time/timesupA.mac +++ b/src/VBox/Runtime/common/time/timesupA.mac @@ -1,4 +1,4 @@ -; $Id: timesupA.mac 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: timesupA.mac 115004 2026-08-12 23:33:28Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Time using SUPLib, the Assembly Code Template. ; @@ -308,6 +308,7 @@ BEGINPROC rtTimeNanoTSInternalAsm inc dword [esi + RTTIMENANOTSDATA.cExpired] mov eax, u32UpdateIntervalTSC jmp .ContinueCalcs + int3 ;; @@ -352,6 +353,7 @@ BEGINPROC rtTimeNanoTSInternalAsm add eax, 1 adc edx, 0 jmp .Update + int3 .DeltaPrevNotInRecentPast: ; else if (!u64PrevNanoTS) /* We're resuming (see TMVirtualResume). */ @@ -361,6 +363,7 @@ BEGINPROC rtTimeNanoTSInternalAsm cmp dword u64PrevNanoTS_Hi, 0 jne .DeltaPrevNotZero jmp .Update + int3 .DeltaPrevNotZero: ; else @@ -389,6 +392,7 @@ BEGINPROC rtTimeNanoTSInternalAsm mov eax, u64RetNanoTS mov edx, u64RetNanoTS_Hi jmp .Update + int3 ;; @@ -422,6 +426,7 @@ BEGINPROC rtTimeNanoTSInternalAsm dec edi jnz .UpdateLoop jmp .Updated + int3 ;; @@ -433,6 +438,7 @@ BEGINPROC rtTimeNanoTSInternalAsm call [eax + RTTIMENANOTSDATA.pfnRediscover] add esp, 4h jmp .Done + int3 %ifdef WITH_TSC_DELTA @@ -443,6 +449,7 @@ BEGINPROC rtTimeNanoTSInternalAsm cmp dword [edi + SUPGIPCPU.i64TSCDelta + 4], 0x7fffffff jne .TscDeltaValid jmp .TscDeltaNotValid + int3 %endif ; @@ -758,7 +765,7 @@ ALIGNCODE(16) inc dword [pData + RTTIMENANOTSDATA.cExpired] mov eax, u32UpdateIntervalTSC jmp .ContinueCalcs - + int3 ;; ;; u64DeltaPrev >= 24h @@ -789,6 +796,7 @@ ALIGNCODE(16) inc dword [pData + RTTIMENANOTSDATA.c1nsSteps] lea rax, [u64PrevNanoTS + 1] jmp .Update + int3 ; else if (!u64PrevNanoTS) /* We're resuming (see TMVirtualResume) / first call. */ ; /* do nothing */; @@ -822,7 +830,7 @@ ALIGNCODE(16) mov rax, TmpVar mov pData, TmpVar2 jmp .Update - + int3 ;; ;; Attempt updating the previous value, provided we're still ahead of it. @@ -851,7 +859,7 @@ ALIGNCODE(16) dec edx jnz .UpdateLoop jmp .Updated - + int3 ;; ;; The GIP is seemingly invalid, redo the discovery. @@ -864,6 +872,7 @@ ALIGNCODE(16) %endif call [pData + RTTIMENANOTSDATA.pfnRediscover] jmp .Done + int3 ; From a55b80a925f93ffeaceb72b142f201f5b08e30d5 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:33:52 +0000 Subject: [PATCH 106/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 4. bugref:11138 svn:sync-xref-src-repo-rev: r174846 --- src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm | 4 +++- src/VBox/Runtime/r3/win/nocrt-atexit-win.asm | 3 ++- src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm | 4 +++- src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm | 4 +++- src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm | 4 +++- src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm | 4 +++- src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm | 4 +++- 7 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm b/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm index e06321f5ba50..2703033c1487 100644 --- a/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm +++ b/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm @@ -1,4 +1,4 @@ -; $Id: nocrt-WinMainCRTStartup-win.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: nocrt-WinMainCRTStartup-win.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Alias WinMainCRTStartup to CustomMainEntrypoint in nocrt-startup-exe-win.cpp. ; @@ -42,3 +42,5 @@ BEGINPROC WinMainCRTStartup jmp NAME(CustomMainEntrypoint) ENDPROC WinMainCRTStartup + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm b/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm index b3a1dac56542..c3d13334c7be 100644 --- a/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm +++ b/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm @@ -1,4 +1,4 @@ -; $Id: nocrt-atexit-win.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: nocrt-atexit-win.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Alias atexit to rtnocrt_atexit in nocrt-startup-exe-win.cpp. ; @@ -42,3 +42,4 @@ BEGINPROC atexit jmp NAME(nocrt_atexit) ENDPROC atexit +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm b/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm index bceb44133821..98c91fd3413c 100644 --- a/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm +++ b/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm @@ -1,4 +1,4 @@ -; $Id: nocrt-mainCRTStartup-win.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: nocrt-mainCRTStartup-win.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Alias mainCRTStartup to CustomMainEntrypoint in nocrt-startup-exe-win.cpp. ; @@ -42,3 +42,5 @@ BEGINPROC mainCRTStartup jmp NAME(CustomMainEntrypoint) ENDPROC mainCRTStartup + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm index db466a30ce86..c5ab2c7dc7b3 100644 --- a/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm +++ b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm @@ -1,4 +1,4 @@ -; $Id: vcc-fakes-kernel32-A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: vcc-fakes-kernel32-A.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Wrappers for kernel32 APIs missing in NT4 and earlier. ; @@ -56,3 +56,5 @@ GLOBALNAME vcc100_kernel32_fakes_asm %error "PORT ME!" %endif + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm index fb1c905f86d1..e2ab4b2b67b8 100644 --- a/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm +++ b/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm @@ -1,4 +1,4 @@ -; $Id: vcc-fakes-ntdll-A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: vcc-fakes-ntdll-A.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Wrappers for ntdll APIs misisng NT4. ; @@ -55,3 +55,5 @@ GLOBALNAME vcc100_ntdll_fakes_asm MAKE_IMPORT_ENTRY RtlGetLastWin32Error, 0 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm index c37afbae53f9..ef79bb1c5ec4 100644 --- a/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm +++ b/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm @@ -1,4 +1,4 @@ -; $Id: vcc-fakes-shell32-A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: vcc-fakes-shell32-A.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Wrappers for shell32 APIs missing in NT 4 and earlier. ; @@ -57,3 +57,5 @@ GLOBALNAME vcc100_shell32_fakes_asm ; NT 3.1 MAKE_IMPORT_ENTRY CommandLineToArgvW, 8 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm index 28251acc95b5..e315a5abe5a9 100644 --- a/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm +++ b/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm @@ -1,4 +1,4 @@ -; $Id: vcc-fakes-ws2_32-A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: vcc-fakes-ws2_32-A.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Wrappers for ws2_32 APIs misisng NT4. ; @@ -56,3 +56,5 @@ GLOBALNAME vcc100_ws2_32_fakes_asm MAKE_IMPORT_ENTRY getaddrinfo, 16 MAKE_IMPORT_ENTRY freeaddrinfo, 4 + +MARK_OBJECT_RETPOLINE_SAFE From 861061031cda6fe4e71c88fa91af8733f236cfa6 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:34:22 +0000 Subject: [PATCH 107/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 5. bugref:11138 svn:sync-xref-src-repo-rev: r174847 --- src/VBox/Runtime/common/string/RTStrEnd.asm | 4 +++- src/VBox/Runtime/common/string/RTStrMemFind32.asm | 4 +++- src/VBox/Runtime/common/string/bzero.asm | 3 ++- src/VBox/Runtime/common/string/memchr.asm | 4 +++- src/VBox/Runtime/common/string/memcmp.asm | 7 ++++++- src/VBox/Runtime/common/string/memcpy.asm | 3 ++- src/VBox/Runtime/common/string/memmove.asm | 5 ++++- src/VBox/Runtime/common/string/mempcpy.asm | 3 ++- src/VBox/Runtime/common/string/memrchr.asm | 4 +++- src/VBox/Runtime/common/string/memset.asm | 3 ++- src/VBox/Runtime/common/string/strchr.asm | 6 +++++- src/VBox/Runtime/common/string/strcmp.asm | 5 ++++- src/VBox/Runtime/common/string/strcpy.asm | 4 +++- src/VBox/Runtime/common/string/strlen.asm | 3 ++- src/VBox/Runtime/common/string/strncmp.asm | 5 ++++- src/VBox/Runtime/common/string/strncpy.asm | 5 ++++- src/VBox/Runtime/common/string/wcslen.asm | 3 ++- 17 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/VBox/Runtime/common/string/RTStrEnd.asm b/src/VBox/Runtime/common/string/RTStrEnd.asm index 5645d00b7de4..a80a3dd55a06 100644 --- a/src/VBox/Runtime/common/string/RTStrEnd.asm +++ b/src/VBox/Runtime/common/string/RTStrEnd.asm @@ -1,4 +1,4 @@ -; $Id: RTStrEnd.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTStrEnd.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTStrEnd - AMD64 & X86. ; @@ -91,6 +91,7 @@ RT_BEGINPROC RTStrEnd %endif %endif ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -112,3 +113,4 @@ RT_BEGINPROC RTStrEnd ret ENDPROC RTStrEnd +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/RTStrMemFind32.asm b/src/VBox/Runtime/common/string/RTStrMemFind32.asm index 131d6aaf25f0..c2ead4339249 100644 --- a/src/VBox/Runtime/common/string/RTStrMemFind32.asm +++ b/src/VBox/Runtime/common/string/RTStrMemFind32.asm @@ -1,4 +1,4 @@ -; $Id: RTStrMemFind32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTStrMemFind32.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTStrMemFind32 - AMD64 & X86. ; @@ -92,6 +92,7 @@ RT_BEGINPROC RTStrMemFind32 %endif %endif ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -109,3 +110,4 @@ RT_BEGINPROC RTStrMemFind32 ret ENDPROC RTStrMemFind32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/bzero.asm b/src/VBox/Runtime/common/string/bzero.asm index 355c3c4fa5d4..67cba1e6b706 100644 --- a/src/VBox/Runtime/common/string/bzero.asm +++ b/src/VBox/Runtime/common/string/bzero.asm @@ -1,4 +1,4 @@ -; $Id: bzero.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: bzero.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT bzero - AMD64 & X86. ; @@ -143,3 +143,4 @@ GLOBALNAME __bzero ret ENDPROC RT_NOCRT(bzero) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memchr.asm b/src/VBox/Runtime/common/string/memchr.asm index a33d6b8530d2..b5bcc6e7f34b 100644 --- a/src/VBox/Runtime/common/string/memchr.asm +++ b/src/VBox/Runtime/common/string/memchr.asm @@ -1,4 +1,4 @@ -; $Id: memchr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memchr.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memchr - AMD64 & X86. ; @@ -95,6 +95,7 @@ RT_NOCRT_BEGINPROC memchr %endif %endif ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -116,3 +117,4 @@ RT_NOCRT_BEGINPROC memchr ret ENDPROC RT_NOCRT(memchr) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memcmp.asm b/src/VBox/Runtime/common/string/memcmp.asm index 167ee8f86b50..013fbf667955 100644 --- a/src/VBox/Runtime/common/string/memcmp.asm +++ b/src/VBox/Runtime/common/string/memcmp.asm @@ -1,4 +1,4 @@ -; $Id: memcmp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memcmp.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memcmp - AMD64 & X86. ; @@ -121,6 +121,7 @@ RT_NOCRT_BEGINPROC memcmp pop edi %endif ret + int3 ; ; Mismatches. @@ -136,6 +137,7 @@ RT_NOCRT_BEGINPROC memcmp movzx ecx, byte [xSI-1] sub eax, ecx jmp .done + int3 %endif .not_equal_dword: @@ -145,12 +147,14 @@ RT_NOCRT_BEGINPROC memcmp repe cmpsb %ifdef RT_ARCH_AMD64 jmp .not_equal_byte + int3 %else .not_equal_byte: mov al, [xDI-1] movzx ecx, byte [xSI-1] sub eax, ecx jmp .done + int3 %endif .not_equal_word: @@ -161,3 +165,4 @@ RT_NOCRT_BEGINPROC memcmp jmp .not_equal_byte ENDPROC RT_NOCRT(memcmp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memcpy.asm b/src/VBox/Runtime/common/string/memcpy.asm index 388c4080dff0..9302189bf3df 100644 --- a/src/VBox/Runtime/common/string/memcpy.asm +++ b/src/VBox/Runtime/common/string/memcpy.asm @@ -1,4 +1,4 @@ -; $Id: memcpy.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memcpy.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memcpy - AMD64 & X86. ; @@ -128,3 +128,4 @@ RT_NOCRT_BEGINPROC memcpy ret ENDPROC RT_NOCRT(memcpy) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memmove.asm b/src/VBox/Runtime/common/string/memmove.asm index 4b5276f4ddaa..a3afb1526b97 100644 --- a/src/VBox/Runtime/common/string/memmove.asm +++ b/src/VBox/Runtime/common/string/memmove.asm @@ -1,4 +1,4 @@ -; $Id: memmove.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memmove.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memmove - AMD64 & X86. ; @@ -96,6 +96,7 @@ RT_NOCRT_BEGINPROC memmove cld rep movsb jmp .epilog + int3 %else ; disabled - it seems to work, but play safe for now. ;sub xAX, xSI @@ -152,6 +153,7 @@ RT_NOCRT_BEGINPROC memmove pop edi %endif ret + int3 ; ; Slow/simple backward copy. @@ -167,3 +169,4 @@ ALIGNCODE(16) jmp .epilog ENDPROC RT_NOCRT(memmove) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/mempcpy.asm b/src/VBox/Runtime/common/string/mempcpy.asm index 3c029ca93540..73fe7429c75f 100644 --- a/src/VBox/Runtime/common/string/mempcpy.asm +++ b/src/VBox/Runtime/common/string/mempcpy.asm @@ -1,4 +1,4 @@ -; $Id: mempcpy.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: mempcpy.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT mempcpy - AMD64 & X86. ; @@ -116,3 +116,4 @@ RT_NOCRT_BEGINPROC mempcpy ret ENDPROC RT_NOCRT(mempcpy) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memrchr.asm b/src/VBox/Runtime/common/string/memrchr.asm index 22523cab5847..81a7b0a82588 100644 --- a/src/VBox/Runtime/common/string/memrchr.asm +++ b/src/VBox/Runtime/common/string/memrchr.asm @@ -1,4 +1,4 @@ -; $Id: memrchr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memrchr.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memrchr - AMD64 & X86. ; @@ -98,6 +98,7 @@ RT_NOCRT_BEGINPROC memrchr %endif cld ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -120,3 +121,4 @@ RT_NOCRT_BEGINPROC memrchr ret ENDPROC RT_NOCRT(memrchr) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memset.asm b/src/VBox/Runtime/common/string/memset.asm index a622b50e42a2..f5e3eb256ee1 100644 --- a/src/VBox/Runtime/common/string/memset.asm +++ b/src/VBox/Runtime/common/string/memset.asm @@ -1,4 +1,4 @@ -; $Id: memset.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memset.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memset - AMD64 & X86. ; @@ -145,3 +145,4 @@ RT_NOCRT_BEGINPROC memset ret ENDPROC RT_NOCRT(memset) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strchr.asm b/src/VBox/Runtime/common/string/strchr.asm index 790c72ca72d5..d9ce339d879a 100644 --- a/src/VBox/Runtime/common/string/strchr.asm +++ b/src/VBox/Runtime/common/string/strchr.asm @@ -1,4 +1,4 @@ -; $Id: strchr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strchr.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strchr - AMD64 & X86. ; @@ -98,6 +98,7 @@ RT_NOCRT_BEGINPROC strchr test al, al jz .not_found jmp .next + int3 .found: lea xAX, [xSI - 1] @@ -112,6 +113,7 @@ RT_NOCRT_BEGINPROC strchr %endif %endif ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -126,6 +128,7 @@ RT_NOCRT_BEGINPROC strchr %endif xor eax, eax ret + int3 ; ; Special case: strchr(str, '\0'); @@ -166,3 +169,4 @@ align 16 ret ENDPROC RT_NOCRT(strchr) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strcmp.asm b/src/VBox/Runtime/common/string/strcmp.asm index 5ac9c516f7f3..9a82d4b1bed6 100644 --- a/src/VBox/Runtime/common/string/strcmp.asm +++ b/src/VBox/Runtime/common/string/strcmp.asm @@ -1,4 +1,4 @@ -; $Id: strcmp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strcmp.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strcmp - AMD64 & X86. ; @@ -99,6 +99,7 @@ RT_NOCRT_BEGINPROC strcmp add psz1, 4 add psz2, 4 jmp .next + int3 .equal: %ifdef RT_ARCH_X86 @@ -109,6 +110,7 @@ RT_NOCRT_BEGINPROC strcmp %endif xor eax, eax ret + int3 .not_equal: movzx ecx, ah @@ -123,3 +125,4 @@ RT_NOCRT_BEGINPROC strcmp ret ENDPROC RT_NOCRT(strcmp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strcpy.asm b/src/VBox/Runtime/common/string/strcpy.asm index 1a2abd50c98d..5b370e0ce8e9 100644 --- a/src/VBox/Runtime/common/string/strcpy.asm +++ b/src/VBox/Runtime/common/string/strcpy.asm @@ -1,4 +1,4 @@ -; $Id: strcpy.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strcpy.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strcpy - AMD64 & X86. ; @@ -91,6 +91,7 @@ RT_NOCRT_BEGINPROC strcpy add pszDst, 4 add pszSrc, 4 jmp .next + int3 .done: %ifdef RT_ARCH_AMD64 @@ -101,3 +102,4 @@ RT_NOCRT_BEGINPROC strcpy ret ENDPROC RT_NOCRT(strcpy) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strlen.asm b/src/VBox/Runtime/common/string/strlen.asm index 0b6f33fc49e0..cc77cb51adc4 100644 --- a/src/VBox/Runtime/common/string/strlen.asm +++ b/src/VBox/Runtime/common/string/strlen.asm @@ -1,4 +1,4 @@ -; $Id: strlen.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strlen.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strlen - AMD64 & X86. ; @@ -73,3 +73,4 @@ RT_NOCRT_BEGINPROC strlen ret ENDPROC RT_NOCRT(strlen) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strncmp.asm b/src/VBox/Runtime/common/string/strncmp.asm index d2c24bfd215e..57f7bc513bc9 100644 --- a/src/VBox/Runtime/common/string/strncmp.asm +++ b/src/VBox/Runtime/common/string/strncmp.asm @@ -1,4 +1,4 @@ -; $Id: strncmp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strncmp.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strncmp - AMD64 & X86. ; @@ -117,6 +117,7 @@ RT_NOCRT_BEGINPROC strncmp add psz1, 4 add psz2, 4 jmp .next + int3 .equal: xor eax, eax @@ -126,6 +127,7 @@ RT_NOCRT_BEGINPROC strncmp %endif %endif ret + int3 .not_equal: movzx ecx, ah @@ -139,3 +141,4 @@ RT_NOCRT_BEGINPROC strncmp ret ENDPROC RT_NOCRT(strncmp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strncpy.asm b/src/VBox/Runtime/common/string/strncpy.asm index 3d77725e18fd..a91e345f4d94 100644 --- a/src/VBox/Runtime/common/string/strncpy.asm +++ b/src/VBox/Runtime/common/string/strncpy.asm @@ -1,4 +1,4 @@ -; $Id: strncpy.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strncpy.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strncpy - AMD64 & X86. ; @@ -104,6 +104,7 @@ RT_NOCRT_BEGINPROC strncpy add pszSrc, 4 sub cbMax, 4 jmp .next + int3 ; ; Char by char. @@ -124,6 +125,7 @@ RT_NOCRT_BEGINPROC strncpy inc pszSrc inc pszDst jmp .simple_next + int3 .done: %ifdef RT_ARCH_AMD64 @@ -137,3 +139,4 @@ RT_NOCRT_BEGINPROC strncpy ret ENDPROC RT_NOCRT(strncpy) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/wcslen.asm b/src/VBox/Runtime/common/string/wcslen.asm index 4b81a8eb6a9b..9302862abc12 100644 --- a/src/VBox/Runtime/common/string/wcslen.asm +++ b/src/VBox/Runtime/common/string/wcslen.asm @@ -1,4 +1,4 @@ -; $Id: wcslen.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: wcslen.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strlen - AMD64 & X86. ; @@ -80,3 +80,4 @@ RT_NOCRT_BEGINPROC wcslen ret ENDPROC RT_NOCRT(wcslen) +MARK_OBJECT_RETPOLINE_SAFE From c1eaf7a07dd2306fd9e28c193ee3dc88b78cc497 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:35:12 +0000 Subject: [PATCH 108/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 6. bugref:11138 svn:sync-xref-src-repo-rev: r174848 --- src/VBox/Runtime/common/math/fabs.asm | 3 ++- src/VBox/Runtime/common/math/fabsf.asm | 3 ++- src/VBox/Runtime/common/math/fabsl.asm | 3 ++- src/VBox/Runtime/common/math/feclearexcept.asm | 4 +++- src/VBox/Runtime/common/math/fedisableexcept.asm | 4 +++- src/VBox/Runtime/common/math/feenableexcept.asm | 4 +++- src/VBox/Runtime/common/math/fegetenv.asm | 3 ++- src/VBox/Runtime/common/math/fegetexcept.asm | 3 ++- src/VBox/Runtime/common/math/fegetexceptflag.asm | 4 +++- src/VBox/Runtime/common/math/fegetround.asm | 3 ++- src/VBox/Runtime/common/math/fegetx87precision.asm | 3 ++- src/VBox/Runtime/common/math/feholdexcept.asm | 3 ++- src/VBox/Runtime/common/math/feraiseexcept.asm | 3 ++- src/VBox/Runtime/common/math/fesetenv.asm | 6 +++++- src/VBox/Runtime/common/math/fesetexceptflag.asm | 3 ++- src/VBox/Runtime/common/math/fesetround.asm | 3 ++- src/VBox/Runtime/common/math/fesetx87precision.asm | 3 ++- src/VBox/Runtime/common/math/fetestexcept.asm | 3 ++- src/VBox/Runtime/common/math/feupdateenv.asm | 3 ++- src/VBox/Runtime/common/math/floor.asm | 3 ++- src/VBox/Runtime/common/math/floorf.asm | 3 ++- src/VBox/Runtime/common/math/floorl.asm | 3 ++- src/VBox/Runtime/common/math/fma-asm.asm | 3 ++- src/VBox/Runtime/common/math/fmaf-asm.asm | 3 ++- 24 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/VBox/Runtime/common/math/fabs.asm b/src/VBox/Runtime/common/math/fabs.asm index cefb18855179..ede73f2cda9a 100644 --- a/src/VBox/Runtime/common/math/fabs.asm +++ b/src/VBox/Runtime/common/math/fabs.asm @@ -1,4 +1,4 @@ -; $Id: fabs.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: fabs.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fabs - AMD64 & X86. ; @@ -71,3 +71,4 @@ g_r64ClearSignMask: dd 0ffffffffh dd 07fffffffh +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fabsf.asm b/src/VBox/Runtime/common/math/fabsf.asm index b6b21491a855..7eefdd308b17 100644 --- a/src/VBox/Runtime/common/math/fabsf.asm +++ b/src/VBox/Runtime/common/math/fabsf.asm @@ -1,4 +1,4 @@ -; $Id: fabsf.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: fabsf.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fabsf - AMD64 & X86. ; @@ -70,3 +70,4 @@ g_r32ClearSignMask: dd 07fffffffh dd 07fffffffh +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fabsl.asm b/src/VBox/Runtime/common/math/fabsl.asm index bd7864c0dfdd..c37d97e7493b 100644 --- a/src/VBox/Runtime/common/math/fabsl.asm +++ b/src/VBox/Runtime/common/math/fabsl.asm @@ -1,4 +1,4 @@ -; $Id: fabsl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fabsl.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fabsl - AMD64 & X86. ; @@ -59,3 +59,4 @@ RT_NOCRT_BEGINPROC fabsl ret ENDPROC RT_NOCRT(fabsl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feclearexcept.asm b/src/VBox/Runtime/common/math/feclearexcept.asm index 2e0f91a61728..9c56e104f6e0 100644 --- a/src/VBox/Runtime/common/math/feclearexcept.asm +++ b/src/VBox/Runtime/common/math/feclearexcept.asm @@ -1,4 +1,4 @@ -; $Id: feclearexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: feclearexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feclearexcept - AMD64 & X86. ; @@ -91,6 +91,7 @@ RT_NOCRT_BEGINPROC feclearexcept jne .partial_mask fnclex jmp .do_sse + int3 .partial_mask: fnstenv [xBP - 20h] @@ -119,3 +120,4 @@ RT_NOCRT_BEGINPROC feclearexcept ret ENDPROC RT_NOCRT(feclearexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fedisableexcept.asm b/src/VBox/Runtime/common/math/fedisableexcept.asm index 6f0f33a47d61..7ef4616a1821 100644 --- a/src/VBox/Runtime/common/math/fedisableexcept.asm +++ b/src/VBox/Runtime/common/math/fedisableexcept.asm @@ -1,4 +1,4 @@ -; $Id: fedisableexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fedisableexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fedisableexcept - AMD64 & X86. ; @@ -74,6 +74,7 @@ RT_NOCRT_BEGINPROC fedisableexcept jz .input_ok int3 jmp .return + int3 .input_ok: %endif @@ -115,3 +116,4 @@ RT_NOCRT_BEGINPROC fedisableexcept ret ENDPROC RT_NOCRT(fedisableexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feenableexcept.asm b/src/VBox/Runtime/common/math/feenableexcept.asm index a010d002f07a..822d7ab6a30a 100644 --- a/src/VBox/Runtime/common/math/feenableexcept.asm +++ b/src/VBox/Runtime/common/math/feenableexcept.asm @@ -1,4 +1,4 @@ -; $Id: feenableexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: feenableexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feenableexcept - AMD64 & X86. ; @@ -74,6 +74,7 @@ RT_NOCRT_BEGINPROC feenableexcept jz .input_ok int3 jmp .return + int3 .input_ok: %endif @@ -119,3 +120,4 @@ RT_NOCRT_BEGINPROC feenableexcept ret ENDPROC RT_NOCRT(feenableexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetenv.asm b/src/VBox/Runtime/common/math/fegetenv.asm index 955b58248c44..e5b8eb9587b9 100644 --- a/src/VBox/Runtime/common/math/fegetenv.asm +++ b/src/VBox/Runtime/common/math/fegetenv.asm @@ -1,4 +1,4 @@ -; $Id: fegetenv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetenv.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetenv - AMD64 & X86. ; @@ -88,3 +88,4 @@ RT_NOCRT_BEGINPROC fegetenv ret ENDPROC RT_NOCRT(fegetenv) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetexcept.asm b/src/VBox/Runtime/common/math/fegetexcept.asm index 861c8c66440e..ecd7d1dc816c 100644 --- a/src/VBox/Runtime/common/math/fegetexcept.asm +++ b/src/VBox/Runtime/common/math/fegetexcept.asm @@ -1,4 +1,4 @@ -; $Id: fegetexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetexcept - AMD64 & X86. ; @@ -80,3 +80,4 @@ RT_NOCRT_BEGINPROC fegetexcept ret ENDPROC RT_NOCRT(fegetexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetexceptflag.asm b/src/VBox/Runtime/common/math/fegetexceptflag.asm index cccb282f5a85..3ee2b002224f 100644 --- a/src/VBox/Runtime/common/math/fegetexceptflag.asm +++ b/src/VBox/Runtime/common/math/fegetexceptflag.asm @@ -1,4 +1,4 @@ -; $Id: fegetexceptflag.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetexceptflag.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetexceptflag - AMD64 & X86. ; @@ -80,6 +80,7 @@ RT_NOCRT_BEGINPROC fegetexceptflag jz .input_ok int3 jmp .return + int3 .input_ok: %endif %endif @@ -115,3 +116,4 @@ RT_NOCRT_BEGINPROC fegetexceptflag ret ENDPROC RT_NOCRT(fegetexceptflag) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetround.asm b/src/VBox/Runtime/common/math/fegetround.asm index 9dc088bd984c..3e9ff2bd23ac 100644 --- a/src/VBox/Runtime/common/math/fegetround.asm +++ b/src/VBox/Runtime/common/math/fegetround.asm @@ -1,4 +1,4 @@ -; $Id: fegetround.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetround.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetround - AMD64 & X86. ; @@ -77,3 +77,4 @@ RT_NOCRT_BEGINPROC fegetround ret ENDPROC RT_NOCRT(fegetround) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetx87precision.asm b/src/VBox/Runtime/common/math/fegetx87precision.asm index 4dd5800f1243..51f088256d7d 100644 --- a/src/VBox/Runtime/common/math/fegetx87precision.asm +++ b/src/VBox/Runtime/common/math/fegetx87precision.asm @@ -1,4 +1,4 @@ -; $Id: fegetx87precision.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetx87precision.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetx87precision - AMD64 & X86. ; @@ -68,3 +68,4 @@ RT_NOCRT_BEGINPROC fegetx87precision ret ENDPROC RT_NOCRT(fegetx87precision) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feholdexcept.asm b/src/VBox/Runtime/common/math/feholdexcept.asm index 3777dbcb7c07..0dea2cc28e27 100644 --- a/src/VBox/Runtime/common/math/feholdexcept.asm +++ b/src/VBox/Runtime/common/math/feholdexcept.asm @@ -1,4 +1,4 @@ -; $Id: feholdexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: feholdexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feholdexcept - AMD64 & X86. ; @@ -97,3 +97,4 @@ RT_NOCRT_BEGINPROC feholdexcept ret ENDPROC RT_NOCRT(feholdexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feraiseexcept.asm b/src/VBox/Runtime/common/math/feraiseexcept.asm index 8c3d1c14c3f3..aab030dffca1 100644 --- a/src/VBox/Runtime/common/math/feraiseexcept.asm +++ b/src/VBox/Runtime/common/math/feraiseexcept.asm @@ -1,4 +1,4 @@ -; $Id: feraiseexcept.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: feraiseexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feraiseexcept - AMD64 & X86. ; @@ -186,3 +186,4 @@ g_r32Tiny: dd 1.0e-37 %endif +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fesetenv.asm b/src/VBox/Runtime/common/math/fesetenv.asm index 22a1e6ab8f73..9f601b0d7028 100644 --- a/src/VBox/Runtime/common/math/fesetenv.asm +++ b/src/VBox/Runtime/common/math/fesetenv.asm @@ -1,4 +1,4 @@ -; $Id: fesetenv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fesetenv.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fesetenv - AMD64 & X86. ; @@ -89,6 +89,7 @@ RT_NOCRT_BEGINPROC fesetenv int3 %endif jmp .return + int3 ; ; Special x87 state. Clear all pending exceptions. @@ -122,6 +123,7 @@ RT_NOCRT_BEGINPROC fesetenv .x87_special_done: mov [xBP - 20h + X86FSTENV32P.FCW], ax jmp .x87_common + int3 ; ; Merge input and current. @@ -169,6 +171,7 @@ RT_NOCRT_BEGINPROC fesetenv jb .sse_special_env ldmxcsr [xCX + 28] jmp .return_okay + int3 .sse_special_env: stmxcsr [xBP - 10h] @@ -192,3 +195,4 @@ RT_NOCRT_BEGINPROC fesetenv ret ENDPROC RT_NOCRT(fesetenv) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fesetexceptflag.asm b/src/VBox/Runtime/common/math/fesetexceptflag.asm index ba75c4607007..10f8b0159553 100644 --- a/src/VBox/Runtime/common/math/fesetexceptflag.asm +++ b/src/VBox/Runtime/common/math/fesetexceptflag.asm @@ -1,4 +1,4 @@ -; $Id: fesetexceptflag.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fesetexceptflag.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fesetexceptflag - AMD64 & X86. ; @@ -125,3 +125,4 @@ RT_NOCRT_BEGINPROC fesetexceptflag ret ENDPROC RT_NOCRT(fesetexceptflag) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fesetround.asm b/src/VBox/Runtime/common/math/fesetround.asm index f61963b91da2..c8e7288546d2 100644 --- a/src/VBox/Runtime/common/math/fesetround.asm +++ b/src/VBox/Runtime/common/math/fesetround.asm @@ -1,4 +1,4 @@ -; $Id: fesetround.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fesetround.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fesetround - AMD64 & X86. ; @@ -105,3 +105,4 @@ RT_NOCRT_BEGINPROC fesetround ret ENDPROC RT_NOCRT(fesetround) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fesetx87precision.asm b/src/VBox/Runtime/common/math/fesetx87precision.asm index 4e9a4ef4c751..e76790f07d71 100644 --- a/src/VBox/Runtime/common/math/fesetx87precision.asm +++ b/src/VBox/Runtime/common/math/fesetx87precision.asm @@ -1,4 +1,4 @@ -; $Id: fesetx87precision.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fesetx87precision.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fesetx87precision - AMD64 & X86. ; @@ -86,3 +86,4 @@ RT_NOCRT_BEGINPROC fesetx87precision ret ENDPROC RT_NOCRT(fesetx87precision) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fetestexcept.asm b/src/VBox/Runtime/common/math/fetestexcept.asm index 4fa53bce7aba..8527b4747796 100644 --- a/src/VBox/Runtime/common/math/fetestexcept.asm +++ b/src/VBox/Runtime/common/math/fetestexcept.asm @@ -1,4 +1,4 @@ -; $Id: fetestexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fetestexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fetestexcept - AMD64 & X86. ; @@ -105,3 +105,4 @@ RT_NOCRT_BEGINPROC fetestexcept ret ENDPROC RT_NOCRT(fetestexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feupdateenv.asm b/src/VBox/Runtime/common/math/feupdateenv.asm index 4ef0533e1a2f..2adb4e2e7b51 100644 --- a/src/VBox/Runtime/common/math/feupdateenv.asm +++ b/src/VBox/Runtime/common/math/feupdateenv.asm @@ -1,4 +1,4 @@ -; $Id: feupdateenv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: feupdateenv.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feupdateenv - AMD64 & X86. ; @@ -126,3 +126,4 @@ RT_NOCRT_BEGINPROC feupdateenv ret ENDPROC RT_NOCRT(feupdateenv) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/floor.asm b/src/VBox/Runtime/common/math/floor.asm index b71f5711f9a0..d5ede0e490ec 100644 --- a/src/VBox/Runtime/common/math/floor.asm +++ b/src/VBox/Runtime/common/math/floor.asm @@ -1,4 +1,4 @@ -; $Id: floor.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: floor.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT floor - AMD64 & X86. ; @@ -76,3 +76,4 @@ RT_NOCRT_BEGINPROC floor ret ENDPROC RT_NOCRT(floor) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/floorf.asm b/src/VBox/Runtime/common/math/floorf.asm index f749483a8cc2..342cd8ea60cd 100644 --- a/src/VBox/Runtime/common/math/floorf.asm +++ b/src/VBox/Runtime/common/math/floorf.asm @@ -1,4 +1,4 @@ -; $Id: floorf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: floorf.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT floorf - AMD64 & X86. ; @@ -76,3 +76,4 @@ RT_NOCRT_BEGINPROC floorf ret ENDPROC RT_NOCRT(floorf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/floorl.asm b/src/VBox/Runtime/common/math/floorl.asm index 61033eb97032..7445500d4dfb 100644 --- a/src/VBox/Runtime/common/math/floorl.asm +++ b/src/VBox/Runtime/common/math/floorl.asm @@ -1,4 +1,4 @@ -; $Id: floorl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: floorl.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT floorl - AMD64 & X86. ; @@ -67,3 +67,4 @@ RT_NOCRT_BEGINPROC floorl ret ENDPROC RT_NOCRT(floorl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fma-asm.asm b/src/VBox/Runtime/common/math/fma-asm.asm index 60d0a223f1d3..733ed8d80fa0 100644 --- a/src/VBox/Runtime/common/math/fma-asm.asm +++ b/src/VBox/Runtime/common/math/fma-asm.asm @@ -1,4 +1,4 @@ -; $Id: fma-asm.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fma-asm.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fma alternatives - AMD64 & X86. ; @@ -102,3 +102,4 @@ BEGINPROC rtNoCrtMathFma4 ret ENDPROC rtNoCrtMathFma4 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fmaf-asm.asm b/src/VBox/Runtime/common/math/fmaf-asm.asm index 06271d0d28eb..62a9f5863c3c 100644 --- a/src/VBox/Runtime/common/math/fmaf-asm.asm +++ b/src/VBox/Runtime/common/math/fmaf-asm.asm @@ -1,4 +1,4 @@ -; $Id: fmaf-asm.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fmaf-asm.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fmaf alternatives - AMD64 & X86. ; @@ -102,3 +102,4 @@ BEGINPROC rtNoCrtMathFma4f ret ENDPROC rtNoCrtMathFma4f +MARK_OBJECT_RETPOLINE_SAFE From ba17f8424a41c6fbaa246345acf01e40c32ebf17 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:35:24 +0000 Subject: [PATCH 109/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 7. bugref:11138 svn:sync-xref-src-repo-rev: r174849 --- .../Runtime/common/math/RTUInt128MulByU64.asm | 3 +- .../common/math/RTUInt128MulByU64Ex.asm | 3 +- src/VBox/Runtime/common/math/atan.asm | 3 +- src/VBox/Runtime/common/math/atan2.asm | 3 +- src/VBox/Runtime/common/math/atan2f.asm | 3 +- src/VBox/Runtime/common/math/atanf.asm | 3 +- .../Runtime/common/math/bignum-amd64-x86.asm | 14 +++++++++- src/VBox/Runtime/common/math/ceil.asm | 3 +- src/VBox/Runtime/common/math/ceilf.asm | 3 +- src/VBox/Runtime/common/math/ceill.asm | 3 +- src/VBox/Runtime/common/math/cos.asm | 8 +++++- src/VBox/Runtime/common/math/cosf.asm | 8 +++++- src/VBox/Runtime/common/math/cosl.asm | 3 +- src/VBox/Runtime/common/math/exp.asm | 7 ++++- src/VBox/Runtime/common/math/exp2.asm | 4 ++- src/VBox/Runtime/common/math/exp2f.asm | 4 ++- src/VBox/Runtime/common/math/expf.asm | 7 ++++- src/VBox/Runtime/common/math/ldexp.asm | 3 +- src/VBox/Runtime/common/math/ldexpf.asm | 3 +- src/VBox/Runtime/common/math/ldexpl.asm | 3 +- src/VBox/Runtime/common/math/llrint.asm | 3 +- src/VBox/Runtime/common/math/llrintf.asm | 3 +- src/VBox/Runtime/common/math/llrintl.asm | 3 +- src/VBox/Runtime/common/math/log.asm | 5 +++- src/VBox/Runtime/common/math/log2.asm | 10 ++++++- src/VBox/Runtime/common/math/log2f.asm | 10 ++++++- src/VBox/Runtime/common/math/logf.asm | 4 ++- src/VBox/Runtime/common/math/logl.asm | 4 ++- src/VBox/Runtime/common/math/lrint.asm | 3 +- src/VBox/Runtime/common/math/lrintf.asm | 3 +- src/VBox/Runtime/common/math/lrintl.asm | 3 +- src/VBox/Runtime/common/math/pow.asm | 5 +++- src/VBox/Runtime/common/math/powcore.asm | 28 ++++++++++++++++++- src/VBox/Runtime/common/math/powf.asm | 5 +++- src/VBox/Runtime/common/math/remainder.asm | 3 +- src/VBox/Runtime/common/math/remainderf.asm | 3 +- src/VBox/Runtime/common/math/remainderl.asm | 3 +- src/VBox/Runtime/common/math/rint.asm | 4 ++- src/VBox/Runtime/common/math/rintf.asm | 4 ++- .../Runtime/common/math/rtNoCrtHasSse.asm | 4 ++- src/VBox/Runtime/common/math/sin.asm | 6 +++- src/VBox/Runtime/common/math/sincore.asm | 12 +++++++- src/VBox/Runtime/common/math/sinf.asm | 6 +++- src/VBox/Runtime/common/math/sinl.asm | 3 +- src/VBox/Runtime/common/math/sqrt.asm | 3 +- src/VBox/Runtime/common/math/sqrtf.asm | 3 +- src/VBox/Runtime/common/math/tan.asm | 3 +- src/VBox/Runtime/common/math/tanf.asm | 3 +- src/VBox/Runtime/common/math/tanl.asm | 3 +- src/VBox/Runtime/common/math/trunc.asm | 4 ++- src/VBox/Runtime/common/math/truncf.asm | 4 ++- src/VBox/Runtime/common/math/truncl.asm | 3 +- 52 files changed, 202 insertions(+), 52 deletions(-) diff --git a/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm b/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm index fffafc169b4b..965bbd0bc48a 100644 --- a/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm +++ b/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm @@ -1,4 +1,4 @@ -; $Id: RTUInt128MulByU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTUInt128MulByU64.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTUInt128MulByU64 - AMD64 implementation. ; @@ -89,3 +89,4 @@ SEH64_END_PROLOGUE ret ENDPROC RTUInt128MulByU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm b/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm index 02286e5ac577..f899b0812e00 100644 --- a/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm +++ b/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm @@ -1,4 +1,4 @@ -; $Id: RTUInt128MulByU64Ex.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTUInt128MulByU64Ex.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTUInt128MulByU64 - AMD64 implementation. ; @@ -93,3 +93,4 @@ SEH64_END_PROLOGUE ret ENDPROC RTUInt128MulByU64Ex +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/atan.asm b/src/VBox/Runtime/common/math/atan.asm index fe19d497fa07..4ae655cdcbce 100644 --- a/src/VBox/Runtime/common/math/atan.asm +++ b/src/VBox/Runtime/common/math/atan.asm @@ -1,4 +1,4 @@ -; $Id: atan.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: atan.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT atan - AMD64 & X86. ; @@ -75,3 +75,4 @@ RT_NOCRT_BEGINPROC atan ret ENDPROC RT_NOCRT(atan) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/atan2.asm b/src/VBox/Runtime/common/math/atan2.asm index aa8a28d60744..0a867941e441 100644 --- a/src/VBox/Runtime/common/math/atan2.asm +++ b/src/VBox/Runtime/common/math/atan2.asm @@ -1,4 +1,4 @@ -; $Id: atan2.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: atan2.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT atan2 - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC atan2 ret ENDPROC RT_NOCRT(atan2) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/atan2f.asm b/src/VBox/Runtime/common/math/atan2f.asm index 71d934b59bef..20d29ab5da66 100644 --- a/src/VBox/Runtime/common/math/atan2f.asm +++ b/src/VBox/Runtime/common/math/atan2f.asm @@ -1,4 +1,4 @@ -; $Id: atan2f.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: atan2f.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT atan2f - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC atan2f ret ENDPROC RT_NOCRT(atan2f) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/atanf.asm b/src/VBox/Runtime/common/math/atanf.asm index 4817c734d697..9ca7a9232740 100644 --- a/src/VBox/Runtime/common/math/atanf.asm +++ b/src/VBox/Runtime/common/math/atanf.asm @@ -1,4 +1,4 @@ -; $Id: atanf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: atanf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT atanf - AMD64 & X86. ; @@ -75,3 +75,4 @@ RT_NOCRT_BEGINPROC atanf ret ENDPROC RT_NOCRT(atanf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/bignum-amd64-x86.asm b/src/VBox/Runtime/common/math/bignum-amd64-x86.asm index b6c347ddfd67..d0a873c27b4b 100644 --- a/src/VBox/Runtime/common/math/bignum-amd64-x86.asm +++ b/src/VBox/Runtime/common/math/bignum-amd64-x86.asm @@ -1,4 +1,4 @@ -; $Id: bignum-amd64-x86.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: bignum-amd64-x86.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Big Integer Numbers, AMD64 and X86 Assembly Workers ; @@ -137,12 +137,14 @@ SEH64_END_PROLOGUE jz .done sahf ; Restore CF. jmp .small_loop ; Skip CF=1 (clc). + int3 %else jnc .no_carry and cUsed, 7 ; Up to seven odd rounds. jz .done stc jmp .small_loop ; Skip CF=1 (clc). + int3 .no_carry: and cUsed, 7 ; Up to seven odd rounds. jz .done @@ -218,6 +220,7 @@ SEH64_END_PROLOGUE jz .done sahf ; Restore CF. jmp .small_loop ; Skip CF=1 (clc). + int3 .small_job: clc @@ -319,12 +322,14 @@ SEH64_END_PROLOGUE jz .done sahf ; Restore CF. jmp .small_loop ; Skip CF=1 (clc). + int3 %else jnc .no_carry and cUsed, 7 ; Up to seven odd rounds. jz .done stc jmp .small_loop ; Skip CF=1 (clc). + int3 .no_carry: and cUsed, 7 ; Up to seven odd rounds. jz .done @@ -388,6 +393,7 @@ SEH64_END_PROLOGUE jz .done sahf ; Restore CF. jmp .small_loop ; Skip CF=1 (clc). + int3 .small_job: clc @@ -460,6 +466,7 @@ SEH64_END_PROLOGUE test cUsed, cUsed jz .no_elements jmp .small_loop_init + int3 ; Big loop - 8 unrolled loop iterations. .big_loop_init: @@ -506,9 +513,11 @@ SEH64_END_PROLOGUE jz .restore_cf_and_return ; Jump if we're good and done. popf ; Restore CF. jmp .small_loop ; Deal with the odd rounds. + int3 .restore_cf_and_return: popf jmp .carry_to_eax + int3 ; Small loop - One round at the time. .small_loop_init: @@ -534,6 +543,7 @@ SEH64_END_PROLOGUE .return: leave ret + int3 .no_elements: mov eax, uCarry @@ -754,6 +764,7 @@ SEH64_END_PROLOGUE add pauMultiplier, RTBIGNUM_ELEMENT_SIZE add pauResult, RTBIGNUM_ELEMENT_SIZE jmp .multiplier_loop + int3 .done: @@ -889,3 +900,4 @@ SEH64_END_PROLOGUE ret ENDPROC rtBigNumKnuthD4_MulSub +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ceil.asm b/src/VBox/Runtime/common/math/ceil.asm index 84103c3e9d6b..bda72d5d4699 100644 --- a/src/VBox/Runtime/common/math/ceil.asm +++ b/src/VBox/Runtime/common/math/ceil.asm @@ -1,4 +1,4 @@ -; $Id: ceil.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ceil.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ceil - AMD64 & X86. ; @@ -77,3 +77,4 @@ RT_NOCRT_BEGINPROC ceil ret ENDPROC RT_NOCRT(ceil) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ceilf.asm b/src/VBox/Runtime/common/math/ceilf.asm index 78ca6a0b8058..595a5526393d 100644 --- a/src/VBox/Runtime/common/math/ceilf.asm +++ b/src/VBox/Runtime/common/math/ceilf.asm @@ -1,4 +1,4 @@ -; $Id: ceilf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ceilf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ceilf - AMD64 & X86. ; @@ -77,3 +77,4 @@ RT_NOCRT_BEGINPROC ceilf ret ENDPROC RT_NOCRT(ceilf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ceill.asm b/src/VBox/Runtime/common/math/ceill.asm index 0d0b3e41cae7..9f417b148243 100644 --- a/src/VBox/Runtime/common/math/ceill.asm +++ b/src/VBox/Runtime/common/math/ceill.asm @@ -1,4 +1,4 @@ -; $Id: ceill.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ceill.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ceill - AMD64 & X86. ; @@ -68,3 +68,4 @@ RT_NOCRT_BEGINPROC ceill ret ENDPROC RT_NOCRT(ceill) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/cos.asm b/src/VBox/Runtime/common/math/cos.asm index 10345aa32565..0fb31f08a5d2 100644 --- a/src/VBox/Runtime/common/math/cos.asm +++ b/src/VBox/Runtime/common/math/cos.asm @@ -1,4 +1,4 @@ -; $Id: cos.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: cos.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT cos - AMD64 & X86. ; @@ -106,6 +106,7 @@ RT_NOCRT_BEGINPROC cos .do_fcos: fcos jmp .return_val + int3 ; ; Finite number. @@ -143,6 +144,7 @@ RT_NOCRT_BEGINPROC cos fsubp st1, st0 ; st0=3pi/2 fchs ; st0=-3pi/2 jmp .make_sine_adjustment + int3 .adjust_negative_to_sine: ; Calc +pi/2. @@ -174,6 +176,7 @@ RT_NOCRT_BEGINPROC cos .return: leave ret + int3 ; ; cos(+/-0) = +1.0 @@ -184,6 +187,7 @@ RT_NOCRT_BEGINPROC cos ffreep st0 fld1 jmp .return_val + int3 ; ; Input is NaN, output it unmodified as far as we can (FLD changes SNaN @@ -194,6 +198,7 @@ RT_NOCRT_BEGINPROC cos ffreep st0 %endif jmp .return + int3 ; ; Local constants. @@ -211,3 +216,4 @@ ALIGNCODE(8) dq 2.0 ENDPROC RT_NOCRT(cos) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/cosf.asm b/src/VBox/Runtime/common/math/cosf.asm index a14dabd80238..55783a6e5eaa 100644 --- a/src/VBox/Runtime/common/math/cosf.asm +++ b/src/VBox/Runtime/common/math/cosf.asm @@ -1,4 +1,4 @@ -; $Id: cosf.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: cosf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT cosf - AMD64 & X86. ; @@ -106,6 +106,7 @@ RT_NOCRT_BEGINPROC cosf .do_fcos: fcos jmp .return_val + int3 ; ; Finite number. @@ -143,6 +144,7 @@ RT_NOCRT_BEGINPROC cosf fsubp st1, st0 ; st0=3pi/2 fchs ; st0=-3pi/2 jmp .make_sine_adjustment + int3 .adjust_negative_to_sine: ; Calc +pi/2. @@ -174,6 +176,7 @@ RT_NOCRT_BEGINPROC cosf .return: leave ret + int3 ; ; cosf(+/-0) = +1.0 @@ -184,6 +187,7 @@ RT_NOCRT_BEGINPROC cosf ffreep st0 fld1 jmp .return_val + int3 ; ; Input is NaN, output it unmodified as far as we can (FLD changes SNaN @@ -194,6 +198,7 @@ RT_NOCRT_BEGINPROC cosf ffreep st0 %endif jmp .return + int3 ; ; Local constants. @@ -211,3 +216,4 @@ ALIGNCODE(8) dq 2.0 ENDPROC RT_NOCRT(cosf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/cosl.asm b/src/VBox/Runtime/common/math/cosl.asm index ffeff343bc9d..41809b181ca3 100644 --- a/src/VBox/Runtime/common/math/cosl.asm +++ b/src/VBox/Runtime/common/math/cosl.asm @@ -1,4 +1,4 @@ -; $Id: cosl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: cosl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT cosl - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC cosl ret ENDPROC RT_NOCRT(cosl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/exp.asm b/src/VBox/Runtime/common/math/exp.asm index 91e32aef4ee2..cfb781006ad5 100644 --- a/src/VBox/Runtime/common/math/exp.asm +++ b/src/VBox/Runtime/common/math/exp.asm @@ -1,4 +1,4 @@ -; $Id: exp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: exp.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT exp - AMD64 & X86. ; @@ -83,6 +83,7 @@ RT_NOCRT_BEGINPROC exp cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .inf jmp .nan + int3 .finite: ; @@ -119,6 +120,7 @@ RT_NOCRT_BEGINPROC exp .return: leave ret + int3 ; ; +/-0.0: Return +1.0 @@ -127,6 +129,7 @@ RT_NOCRT_BEGINPROC exp ffreep st0 fld1 jmp .return_val + int3 ; ; -Inf: Return +0.0. @@ -138,6 +141,7 @@ RT_NOCRT_BEGINPROC exp ffreep st0 fldz jmp .return_val + int3 ; ; NaN: Return the input NaN value as is, if we can. @@ -149,3 +153,4 @@ RT_NOCRT_BEGINPROC exp jmp .return ENDPROC RT_NOCRT(exp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/exp2.asm b/src/VBox/Runtime/common/math/exp2.asm index ea609fd9cae6..e8e57b33169f 100644 --- a/src/VBox/Runtime/common/math/exp2.asm +++ b/src/VBox/Runtime/common/math/exp2.asm @@ -1,4 +1,4 @@ -; $Id: exp2.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: exp2.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT exp2 - AMD64 & X86. ; @@ -86,6 +86,7 @@ RT_NOCRT_BEGINPROC exp2 %endif fldz ; Signed, so return zero as that's a good approximation for 2**-Inf. jmp .return_val + int3 .input_ok: ; @@ -115,3 +116,4 @@ RT_NOCRT_BEGINPROC exp2 ret ENDPROC RT_NOCRT(exp2) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/exp2f.asm b/src/VBox/Runtime/common/math/exp2f.asm index 51357883047f..a075514fc0a7 100644 --- a/src/VBox/Runtime/common/math/exp2f.asm +++ b/src/VBox/Runtime/common/math/exp2f.asm @@ -1,4 +1,4 @@ -; $Id: exp2f.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: exp2f.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT exp2f - AMD64 & X86. ; @@ -86,6 +86,7 @@ RT_NOCRT_BEGINPROC exp2f %endif fldz ; Signed, so return zero as that's a good approximation for 2**-Inf. jmp .return_val + int3 .input_ok: ; @@ -115,3 +116,4 @@ RT_NOCRT_BEGINPROC exp2f ret ENDPROC RT_NOCRT(exp2f) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/expf.asm b/src/VBox/Runtime/common/math/expf.asm index 1362265a3ada..aeba05e14b07 100644 --- a/src/VBox/Runtime/common/math/expf.asm +++ b/src/VBox/Runtime/common/math/expf.asm @@ -1,4 +1,4 @@ -; $Id: expf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: expf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT expf - AMD64 & X86. ; @@ -83,6 +83,7 @@ RT_NOCRT_BEGINPROC expf cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .inf jmp .nan + int3 .finite: ; @@ -119,6 +120,7 @@ RT_NOCRT_BEGINPROC expf .return: leave ret + int3 ; ; +/-0.0: Return +1.0 @@ -127,6 +129,7 @@ RT_NOCRT_BEGINPROC expf ffreep st0 fld1 jmp .return_val + int3 ; ; -Inf: Return +0.0. @@ -138,6 +141,7 @@ RT_NOCRT_BEGINPROC expf ffreep st0 fldz jmp .return_val + int3 ; ; NaN: Return the input NaN value as is, if we can. @@ -149,3 +153,4 @@ RT_NOCRT_BEGINPROC expf jmp .return ENDPROC RT_NOCRT(expf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ldexp.asm b/src/VBox/Runtime/common/math/ldexp.asm index 49e2ed63dabc..ba1e7bbef953 100644 --- a/src/VBox/Runtime/common/math/ldexp.asm +++ b/src/VBox/Runtime/common/math/ldexp.asm @@ -1,4 +1,4 @@ -; $Id: ldexp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ldexp.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ldexp - AMD64 & X86. ; @@ -87,3 +87,4 @@ RT_NOCRT_BEGINPROC ldexp ret ENDPROC RT_NOCRT(ldexp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ldexpf.asm b/src/VBox/Runtime/common/math/ldexpf.asm index d7482eb3614a..4fc2a94ef764 100644 --- a/src/VBox/Runtime/common/math/ldexpf.asm +++ b/src/VBox/Runtime/common/math/ldexpf.asm @@ -1,4 +1,4 @@ -; $Id: ldexpf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ldexpf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ldexpf - AMD64 & X86. ; @@ -87,3 +87,4 @@ RT_NOCRT_BEGINPROC ldexpf ret ENDPROC RT_NOCRT(ldexpf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ldexpl.asm b/src/VBox/Runtime/common/math/ldexpl.asm index 75f983a818cc..1a9a7c18d2e0 100644 --- a/src/VBox/Runtime/common/math/ldexpl.asm +++ b/src/VBox/Runtime/common/math/ldexpl.asm @@ -1,4 +1,4 @@ -; $Id: ldexpl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ldexpl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ldexpl - AMD64 & X86. ; @@ -78,3 +78,4 @@ RT_NOCRT_BEGINPROC ldexpl ret ENDPROC RT_NOCRT(ldexpl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/llrint.asm b/src/VBox/Runtime/common/math/llrint.asm index 2eae844df4f1..14dccc59294c 100644 --- a/src/VBox/Runtime/common/math/llrint.asm +++ b/src/VBox/Runtime/common/math/llrint.asm @@ -1,4 +1,4 @@ -; $Id: llrint.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: llrint.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT llrint - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC llrint ret ENDPROC RT_NOCRT(llrint) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/llrintf.asm b/src/VBox/Runtime/common/math/llrintf.asm index 2673b8c9da4c..f1f4ea13d3db 100644 --- a/src/VBox/Runtime/common/math/llrintf.asm +++ b/src/VBox/Runtime/common/math/llrintf.asm @@ -1,4 +1,4 @@ -; $Id: llrintf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: llrintf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT llrintf - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC llrintf ret ENDPROC RT_NOCRT(llrintf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/llrintl.asm b/src/VBox/Runtime/common/math/llrintl.asm index 1104ccb78b09..3b6dcd559b81 100644 --- a/src/VBox/Runtime/common/math/llrintl.asm +++ b/src/VBox/Runtime/common/math/llrintl.asm @@ -1,4 +1,4 @@ -; $Id: llrintl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: llrintl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT llrintl - AMD64 & X86. ; @@ -68,3 +68,4 @@ RT_NOCRT_BEGINPROC llrintl ret ENDPROC RT_NOCRT(llrintl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/log.asm b/src/VBox/Runtime/common/math/log.asm index 50f7d873a971..d06f93efdc7d 100644 --- a/src/VBox/Runtime/common/math/log.asm +++ b/src/VBox/Runtime/common/math/log.asm @@ -1,4 +1,4 @@ -; $Id: log.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: log.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT log - AMD64 & X86. ; @@ -77,6 +77,7 @@ RT_NOCRT_BEGINPROC log fstp st0 ; st1=log(2) st0=lrd fyl2x ; log(lrd) jmp .done + int3 .use_st1: fstp st1 ; st1=log(2) st0=lrd-1.0 @@ -89,9 +90,11 @@ RT_NOCRT_BEGINPROC log %endif leave ret + int3 ALIGNCODE(8) .one: dq 1.0 .limit: dq 0.29 ENDPROC RT_NOCRT(log) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/log2.asm b/src/VBox/Runtime/common/math/log2.asm index 42c845480ccc..2e9d5724b379 100644 --- a/src/VBox/Runtime/common/math/log2.asm +++ b/src/VBox/Runtime/common/math/log2.asm @@ -1,4 +1,4 @@ -; $Id: log2.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: log2.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT log2 - AMD64 & X86. ; @@ -83,6 +83,7 @@ RT_NOCRT_BEGINPROC log2 cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .inf jmp .nan + int3 .finite: ; Negative number? @@ -118,6 +119,7 @@ RT_NOCRT_BEGINPROC log2 fsub st0, st1 ; -> st0=input-1; st1=1.0 fyl2xp1 ; -> st0=1.0*log2(st0+1.0) jmp .return_val + int3 .cannot_use_fyl2xp1: fyl2x ; -> st0=1.0*log2(st0) @@ -133,6 +135,7 @@ RT_NOCRT_BEGINPROC log2 .return: leave ret + int3 ; @@ -142,6 +145,7 @@ RT_NOCRT_BEGINPROC log2 ffreep st0 fldz jmp .return_val + int3 ; ; Negative numbers: Return NaN and raise invalid operation. @@ -167,6 +171,7 @@ RT_NOCRT_BEGINPROC log2 fld qword [RT_WRT_RIP(.s_r64NaN)] %endif jmp .return + int3 ; ; +/-0.0: Return inf and raise divide by zero error. @@ -193,6 +198,7 @@ RT_NOCRT_BEGINPROC log2 fld qword [RT_WRT_RIP(.s_r64MinusInf)] %endif jmp .return + int3 ; ; -Inf: Same as other negative numbers @@ -210,6 +216,7 @@ RT_NOCRT_BEGINPROC log2 ffreep st0 %endif jmp .return + int3 ALIGNCODE(8) ;; The fyl2xp1 instruction only works between +/-1(1-sqrt(0.5)). @@ -227,3 +234,4 @@ ALIGNCODE(8) dq RTFLOAT64U_QNAN_MINUS ENDPROC RT_NOCRT(log2) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/log2f.asm b/src/VBox/Runtime/common/math/log2f.asm index 55088e1b7a55..c255705facb2 100644 --- a/src/VBox/Runtime/common/math/log2f.asm +++ b/src/VBox/Runtime/common/math/log2f.asm @@ -1,4 +1,4 @@ -; $Id: log2f.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: log2f.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT log2f - AMD64 & X86. ; @@ -83,6 +83,7 @@ RT_NOCRT_BEGINPROC log2f cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .inf jmp .nan + int3 .finite: ; Negative number? @@ -118,6 +119,7 @@ RT_NOCRT_BEGINPROC log2f fsub st0, st1 ; -> st0=input-1; st1=1.0 fyl2xp1 ; -> st0=1.0*log2(st0+1.0) jmp .return_val + int3 .cannot_use_fyl2xp1: fyl2x ; -> st0=1.0*log2(st0) @@ -133,6 +135,7 @@ RT_NOCRT_BEGINPROC log2f .return: leave ret + int3 ; @@ -142,6 +145,7 @@ RT_NOCRT_BEGINPROC log2f ffreep st0 fldz jmp .return_val + int3 ; ; Negative numbers: Return NaN and raise invalid operation. @@ -167,6 +171,7 @@ RT_NOCRT_BEGINPROC log2f fld dword [RT_WRT_RIP(.s_r32NaN)] %endif jmp .return + int3 ; ; +/-0.0: Return inf and raise divide by zero error. @@ -193,6 +198,7 @@ RT_NOCRT_BEGINPROC log2f fld dword [RT_WRT_RIP(.s_r32MinusInf)] %endif jmp .return + int3 ; ; -Inf: Same as other negative numbers @@ -210,6 +216,7 @@ RT_NOCRT_BEGINPROC log2f ffreep st0 %endif jmp .return + int3 ALIGNCODE(8) ;; The fyl2xp1 instruction only works between +/-1(1-sqrt(0.5)). @@ -225,3 +232,4 @@ ALIGNCODE(8) dd RTFLOAT32U_QNAN_MINUS ENDPROC RT_NOCRT(log2f) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/logf.asm b/src/VBox/Runtime/common/math/logf.asm index d185f27a289d..5f47ff7c6f39 100644 --- a/src/VBox/Runtime/common/math/logf.asm +++ b/src/VBox/Runtime/common/math/logf.asm @@ -1,4 +1,4 @@ -; $Id: logf.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: logf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT logf - AMD64 & X86. ; @@ -77,6 +77,7 @@ RT_NOCRT_BEGINPROC logf fstp st0 ; st1=log(2) st0=lrd fyl2x ; log(lrd) jmp .done + int3 .use_st1: fstp st1 ; st1=log(2) st0=lrd-1.0 @@ -95,3 +96,4 @@ ALIGNCODE(8) .limit: dq 0.29 ENDPROC RT_NOCRT(logf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/logl.asm b/src/VBox/Runtime/common/math/logl.asm index 2cd7f8ccb6b3..58a11a7179d1 100644 --- a/src/VBox/Runtime/common/math/logl.asm +++ b/src/VBox/Runtime/common/math/logl.asm @@ -1,4 +1,4 @@ -; $Id: logl.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: logl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT logl - AMD64 & X86. ; @@ -68,6 +68,7 @@ RT_NOCRT_BEGINPROC logl fstp st0 ; st1=log(2) st0=lrd fyl2x ; log(lrd) jmp .done + int3 .use_st1: fstp st1 ; st1=log(2) st0=lrd-1.0 @@ -82,3 +83,4 @@ ALIGNCODE(8) .limit: dq 0.29 ENDPROC RT_NOCRT(logl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/lrint.asm b/src/VBox/Runtime/common/math/lrint.asm index 5e4fbd1dfcc3..2bdd262889ba 100644 --- a/src/VBox/Runtime/common/math/lrint.asm +++ b/src/VBox/Runtime/common/math/lrint.asm @@ -1,4 +1,4 @@ -; $Id: lrint.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: lrint.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT lrint - AMD64 & X86. ; @@ -73,3 +73,4 @@ RT_NOCRT_BEGINPROC lrint ret ENDPROC RT_NOCRT(lrint) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/lrintf.asm b/src/VBox/Runtime/common/math/lrintf.asm index 0bf2bafd7be6..085c634d40cc 100644 --- a/src/VBox/Runtime/common/math/lrintf.asm +++ b/src/VBox/Runtime/common/math/lrintf.asm @@ -1,4 +1,4 @@ -; $Id: lrintf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: lrintf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT lrintf - AMD64 & X86. ; @@ -72,3 +72,4 @@ RT_NOCRT_BEGINPROC lrintf ret ENDPROC RT_NOCRT(lrintf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/lrintl.asm b/src/VBox/Runtime/common/math/lrintl.asm index 06a3bf055218..abfd5bd4232e 100644 --- a/src/VBox/Runtime/common/math/lrintl.asm +++ b/src/VBox/Runtime/common/math/lrintl.asm @@ -1,4 +1,4 @@ -; $Id: lrintl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: lrintl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT lrintl - AMD64 & X86. ; @@ -75,3 +75,4 @@ RT_NOCRT_BEGINPROC lrintl ret ENDPROC RT_NOCRT(lrintl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/pow.asm b/src/VBox/Runtime/common/math/pow.asm index aaca6627e63d..632c30124b00 100644 --- a/src/VBox/Runtime/common/math/pow.asm +++ b/src/VBox/Runtime/common/math/pow.asm @@ -1,4 +1,4 @@ -; $Id: pow.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: pow.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT pow - AMD64 & X86. ; @@ -103,6 +103,7 @@ RT_NOCRT_BEGINPROC pow pop xBX leave ret + int3 ; ; But sometimes, like if we have NaN or other special inputs, we should @@ -119,9 +120,11 @@ RT_NOCRT_BEGINPROC pow %endif .return_base: jmp .return + int3 .return_exp: movsd xmm0, xmm1 jmp .return ENDPROC RT_NOCRT(pow) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/powcore.asm b/src/VBox/Runtime/common/math/powcore.asm index 591df78155b1..699f68cba14e 100644 --- a/src/VBox/Runtime/common/math/powcore.asm +++ b/src/VBox/Runtime/common/math/powcore.asm @@ -1,4 +1,4 @@ -; $Id: powcore.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: powcore.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT common pow code - AMD64 & X86. ; @@ -102,6 +102,7 @@ BEGINPROC rtNoCrtMathPowCore cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .exp_inf jmp .exp_nan + int3 .exp_finite: ; @@ -118,6 +119,7 @@ BEGINPROC rtNoCrtMathPowCore cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .base_inf jmp .base_nan + int3 .base_finite: ; @@ -209,10 +211,12 @@ BEGINPROC rtNoCrtMathPowCore ; Calculate the factor for the next bit. fmul st0, st0 jmp .integer_exp_loop + int3 .integer_exp_return: ffreep st0 ; drop the factor -> st0=result; no st1. jmp .return_val + int3 ; @@ -258,6 +262,7 @@ BEGINPROC rtNoCrtMathPowCore fsub st0, st1 ; -> st0=base-1; st1=1.0; st2=exponent fyl2xp1 ; -> st0=1.0*log2(base-1.0+1.0); st1=exponent jmp .done_log2 + int3 .cannot_use_fyl2xp1: fyl2x ; -> st0=1.0*log2(base); st1=exponent @@ -295,6 +300,7 @@ BEGINPROC rtNoCrtMathPowCore .return: leave ret + int3 ; @@ -323,6 +329,7 @@ BEGINPROC rtNoCrtMathPowCore .base_negative_non_integer_exp: CALL_feraiseexcept_WITH X86_FSW_IE jmp .return_nan + int3 ; ; 7. Exponent = +/-0.0, any base value including NaN: return +1.0 @@ -332,6 +339,7 @@ BEGINPROC rtNoCrtMathPowCore .return_plus_one: fld1 jmp .return_pop_pop_val + int3 ; ; 6. Exponent = whatever and base = 1: Return 1.0 @@ -363,11 +371,13 @@ BEGINPROC rtNoCrtMathPowCore test cx, X86_FSW_C1 ; cx=faxm(exponent); C1=sign jz .return_plus_inf ; Matches rule 14 (exponent is +Inf). jmp .return_plus_zero ; Matches rule 12 (exponent is -Inf). + int3 .exp_inf_base_smaller_than_one: test cx, X86_FSW_C1 ; cx=faxm(exponent); C1=sign jnz .return_plus_inf ; Matches rule 11 (exponent is -Inf). jmp .return_plus_zero ; Matches rule 13 (exponent is +Inf). + int3 ; ; 6. Exponent = whatever and base = 1: Return 1.0 @@ -381,6 +391,7 @@ BEGINPROC rtNoCrtMathPowCore fcomip st0, st2 jne .return_exp_nan jmp .return_plus_one + int3 ; ; 4a. base == +/-0.0 and exp < 0 and exp is odd integer: Return +/-Inf, raise div/0. @@ -407,14 +418,17 @@ BEGINPROC rtNoCrtMathPowCore .raise_de_and_return_minus_inf: CALL_feraiseexcept_WITH X86_FSW_DE jmp .return_minus_inf + int3 .raise_de_and_return_plus_inf: CALL_feraiseexcept_WITH X86_FSW_DE jmp .return_plus_inf + int3 ; Matching 4b. .base_zero_minus_exp_not_odd_int: CALL_feraiseexcept_WITH X86_FSW_DE jmp .return_plus_inf + int3 .base_zero_plus_exp: call .is_exp_odd_integer @@ -423,6 +437,7 @@ BEGINPROC rtNoCrtMathPowCore .return_plus_zero: ; Matching 9 fldz jmp .return_pop_pop_val + int3 ; ; 15. base == -Inf and exp < 0 and exp is odd integer: Return -0 @@ -449,6 +464,7 @@ BEGINPROC rtNoCrtMathPowCore fldz fchs jmp .return_pop_pop_val + int3 .base_inf_plus_exp: test dx, X86_FSW_C1 @@ -458,6 +474,7 @@ BEGINPROC rtNoCrtMathPowCore or eax, eax jnz .return_minus_inf ; Matches 17 (exp is odd and > 0, base == +Inf) jmp .return_plus_inf ; Matches 18 (exp not odd and > 0, base == +Inf) + int3 ; ; Return the exponent NaN (or whatever) value. @@ -466,6 +483,7 @@ BEGINPROC rtNoCrtMathPowCore fld st0 mov eax, 2 ; return param 2 jmp .return_pop_pop_val_with_eax + int3 ; ; Return the base NaN (or whatever) value. @@ -476,6 +494,7 @@ BEGINPROC rtNoCrtMathPowCore fld st1 mov eax, 1 ; return param 1 jmp .return_pop_pop_val_with_eax + int3 ; ; Pops the two values off the FPU stack and returns NaN. @@ -483,6 +502,7 @@ BEGINPROC rtNoCrtMathPowCore .return_nan: fld qword [RT_WRT_RIP(.s_r64QNan)] jmp .return_pop_pop_val + int3 ; ; Pops the two values off the FPU stack and returns +Inf. @@ -490,6 +510,7 @@ BEGINPROC rtNoCrtMathPowCore .return_plus_inf: fld qword [RT_WRT_RIP(.s_r64PlusInf)] jmp .return_pop_pop_val + int3 ; ; Pops the two values off the FPU stack and returns -Inf. @@ -497,6 +518,7 @@ BEGINPROC rtNoCrtMathPowCore .return_minus_inf: fld qword [RT_WRT_RIP(.s_r64MinusInf)] jmp .return_pop_pop_val + int3 ; ; Return st0, remove st1 and st2. @@ -507,6 +529,7 @@ BEGINPROC rtNoCrtMathPowCore fstp st2 ffreep st0 jmp .return + int3 ALIGNCODE(8) @@ -601,6 +624,7 @@ ALIGNCODE(8) jnz .is_exp_odd_integer__high_dword_is_zero lea eax, [edx + 20h] jmp .is_exp_odd_integer__first_bit_in_eax + int3 .is_exp_odd_integer__high_dword_is_zero: bsr eax, eax .is_exp_odd_integer__first_bit_in_eax: @@ -622,6 +646,7 @@ ALIGNCODE(8) ; Return. .is_exp_odd_integer__return_true: jmp .is_exp_odd_integer__return + int3 .is_exp_odd_integer__return_false: xor eax, eax .is_exp_odd_integer__return: @@ -631,3 +656,4 @@ ALIGNCODE(8) ENDPROC rtNoCrtMathPowCore +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/powf.asm b/src/VBox/Runtime/common/math/powf.asm index 38cb05015683..cf97e565e667 100644 --- a/src/VBox/Runtime/common/math/powf.asm +++ b/src/VBox/Runtime/common/math/powf.asm @@ -1,4 +1,4 @@ -; $Id: powf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: powf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT powf - AMD64 & X86. ; @@ -103,6 +103,7 @@ RT_NOCRT_BEGINPROC powf pop xBX leave ret + int3 ; ; But sometimes, like if we have NaN or other special inputs, we should @@ -119,9 +120,11 @@ RT_NOCRT_BEGINPROC powf %endif .return_base: jmp .return + int3 .return_exp: movss xmm0, xmm1 jmp .return ENDPROC RT_NOCRT(powf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/remainder.asm b/src/VBox/Runtime/common/math/remainder.asm index deb7b4e50aed..9500ab4d2b18 100644 --- a/src/VBox/Runtime/common/math/remainder.asm +++ b/src/VBox/Runtime/common/math/remainder.asm @@ -1,4 +1,4 @@ -; $Id: remainder.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: remainder.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT remainder - AMD64 & X86. ; @@ -102,3 +102,4 @@ RT_NOCRT_BEGINPROC remainder ret ENDPROC RT_NOCRT(remainder) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/remainderf.asm b/src/VBox/Runtime/common/math/remainderf.asm index 7c8991443c6c..519c657408c1 100644 --- a/src/VBox/Runtime/common/math/remainderf.asm +++ b/src/VBox/Runtime/common/math/remainderf.asm @@ -1,4 +1,4 @@ -; $Id: remainderf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: remainderf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT remainderf - AMD64 & X86. ; @@ -102,3 +102,4 @@ RT_NOCRT_BEGINPROC remainderf ret ENDPROC RT_NOCRT(remainderf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/remainderl.asm b/src/VBox/Runtime/common/math/remainderl.asm index 296e02f36164..0d5ed57b2f0e 100644 --- a/src/VBox/Runtime/common/math/remainderl.asm +++ b/src/VBox/Runtime/common/math/remainderl.asm @@ -1,4 +1,4 @@ -; $Id: remainderl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: remainderl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT remainderl - AMD64 & X86. ; @@ -86,3 +86,4 @@ RT_NOCRT_BEGINPROC remainderl ret ENDPROC RT_NOCRT(remainderl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/rint.asm b/src/VBox/Runtime/common/math/rint.asm index ecf90da3ec85..9b0728dcb4f1 100644 --- a/src/VBox/Runtime/common/math/rint.asm +++ b/src/VBox/Runtime/common/math/rint.asm @@ -1,4 +1,4 @@ -; $Id: rint.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: rint.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT rint - AMD64 & X86. ; @@ -81,6 +81,7 @@ RT_NOCRT_BEGINPROC rint ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN. %endif jmp .return + int3 .input_ok: ; @@ -97,3 +98,4 @@ RT_NOCRT_BEGINPROC rint ret ENDPROC RT_NOCRT(rint) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/rintf.asm b/src/VBox/Runtime/common/math/rintf.asm index 1fd1b7f8cac4..63e9eb3ac555 100644 --- a/src/VBox/Runtime/common/math/rintf.asm +++ b/src/VBox/Runtime/common/math/rintf.asm @@ -1,4 +1,4 @@ -; $Id: rintf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: rintf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT rintf - AMD64 & X86. ; @@ -81,6 +81,7 @@ RT_NOCRT_BEGINPROC rintf ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN. %endif jmp .return + int3 .input_ok: ; @@ -97,3 +98,4 @@ RT_NOCRT_BEGINPROC rintf ret ENDPROC RT_NOCRT(rintf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm b/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm index 7dc7710413c5..0fa044cd3b0b 100644 --- a/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm +++ b/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm @@ -1,4 +1,4 @@ -; $Id: rtNoCrtHasSse.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: rtNoCrtHasSse.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT rtNoCrtHasSse - X86. ; @@ -55,6 +55,7 @@ BEGINPROC rtNoCrtHasSse test al, 0x80 jnz .detect_sse ret + int3 .detect_sse: push ebx @@ -76,3 +77,4 @@ BEGINPROC rtNoCrtHasSse ret ENDPROC rtNoCrtHasSse +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sin.asm b/src/VBox/Runtime/common/math/sin.asm index 2505f6a6ca7a..c0ef4187617a 100644 --- a/src/VBox/Runtime/common/math/sin.asm +++ b/src/VBox/Runtime/common/math/sin.asm @@ -1,4 +1,4 @@ -; $Id: sin.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: sin.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sin - AMD64 & X86. ; @@ -98,6 +98,7 @@ RT_NOCRT_BEGINPROC sin .do_sin: fsin jmp .return_val + int3 ; ; Finite number. @@ -142,6 +143,7 @@ RT_NOCRT_BEGINPROC sin .return: leave ret + int3 ; ; As explained already, we can return tiny numbers directly too as the @@ -167,6 +169,7 @@ RT_NOCRT_BEGINPROC sin ffreep st0 %endif jmp .return + int3 ALIGNCODE(8) ; Ca. 2**-17, absolute value. Inputs closer to zero than this can be @@ -183,3 +186,4 @@ ALIGNCODE(8) ENDPROC RT_NOCRT(sin) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sincore.asm b/src/VBox/Runtime/common/math/sincore.asm index 4848cf69f2b9..58914c828eb8 100644 --- a/src/VBox/Runtime/common/math/sincore.asm +++ b/src/VBox/Runtime/common/math/sincore.asm @@ -1,4 +1,4 @@ -; $Id: sincore.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: sincore.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT common sin & cos - AMD64 & X86. ; @@ -143,6 +143,7 @@ BEGINPROC rtNoCrtMathSinCore ; Ok, calculate sine. fsin jmp .return + int3 ; ; The value is in the range ]pi/2,pi[ @@ -176,6 +177,7 @@ BEGINPROC rtNoCrtMathSinCore fsin fchs jmp .return + int3 ; ; input in the range ]pi,2pi[ @@ -218,6 +220,7 @@ BEGINPROC rtNoCrtMathSinCore fsin fchs jmp .return + int3 ; ; The value is in the last pi/2 of the range: ]3pi/2,2pi[ @@ -249,6 +252,7 @@ BEGINPROC rtNoCrtMathSinCore fsubp st1, st0 fsin jmp .return + int3 ; ; sin(0) = 0 @@ -263,6 +267,7 @@ BEGINPROC rtNoCrtMathSinCore ffreep st0 fldz jmp .return + int3 ; ; sin(pi/2) = 1 @@ -272,6 +277,7 @@ BEGINPROC rtNoCrtMathSinCore ffreep st0 fld1 jmp .return + int3 ; ; sin(3*pi/2) = -1 @@ -282,6 +288,7 @@ BEGINPROC rtNoCrtMathSinCore fld1 fchs jmp .return + int3 ; ; Return. @@ -289,6 +296,7 @@ BEGINPROC rtNoCrtMathSinCore .return: leave ret + int3 ; ; Reduce st0 by reminder division by PI*2. The result should be positive here. @@ -325,6 +333,7 @@ BEGINPROC rtNoCrtMathSinCore .reduced_to_positive: fstp st1 ; Get rid of the 2pi value. jmp .in_range + int3 ALIGNCODE(8) .s_r64Max: @@ -350,3 +359,4 @@ ALIGNCODE(8) dq (-52 + 1023) << 52 ; long double / 80-bit / extended precision input ENDPROC rtNoCrtMathSinCore +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sinf.asm b/src/VBox/Runtime/common/math/sinf.asm index 897218a46db8..bf00fbd54130 100644 --- a/src/VBox/Runtime/common/math/sinf.asm +++ b/src/VBox/Runtime/common/math/sinf.asm @@ -1,4 +1,4 @@ -; $Id: sinf.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: sinf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sinf - AMD64 & X86. ; @@ -98,6 +98,7 @@ RT_NOCRT_BEGINPROC sinf .do_sin: fsin jmp .return_val + int3 ; ; Finite number. @@ -142,6 +143,7 @@ RT_NOCRT_BEGINPROC sinf .return: leave ret + int3 ; ; As explained already, we can return tiny numbers directly too as the @@ -167,6 +169,7 @@ RT_NOCRT_BEGINPROC sinf ffreep st0 %endif jmp .return + int3 ALIGNCODE(8) ; Ca. 2**-26, absolute value. Inputs closer to zero than this can be @@ -183,3 +186,4 @@ ALIGNCODE(8) ENDPROC RT_NOCRT(sinf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sinl.asm b/src/VBox/Runtime/common/math/sinl.asm index 2a958f741ed2..493d3ad84c81 100644 --- a/src/VBox/Runtime/common/math/sinl.asm +++ b/src/VBox/Runtime/common/math/sinl.asm @@ -1,4 +1,4 @@ -; $Id: sinl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: sinl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sinl - AMD64 & X86. ; @@ -69,3 +69,4 @@ RT_NOCRT_BEGINPROC sinl ret ENDPROC RT_NOCRT(sinl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sqrt.asm b/src/VBox/Runtime/common/math/sqrt.asm index 464e7de47899..fc22f4f247de 100644 --- a/src/VBox/Runtime/common/math/sqrt.asm +++ b/src/VBox/Runtime/common/math/sqrt.asm @@ -1,4 +1,4 @@ -; $Id: sqrt.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: sqrt.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sqrt - AMD64 & X86. ; @@ -63,3 +63,4 @@ RT_NOCRT_BEGINPROC sqrt ret ENDPROC RT_NOCRT(sqrt) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sqrtf.asm b/src/VBox/Runtime/common/math/sqrtf.asm index aaabdfbdcf71..e4e6e3f4daae 100644 --- a/src/VBox/Runtime/common/math/sqrtf.asm +++ b/src/VBox/Runtime/common/math/sqrtf.asm @@ -1,4 +1,4 @@ -; $Id: sqrtf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: sqrtf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sqrtf - AMD64 & X86. ; @@ -63,3 +63,4 @@ RT_NOCRT_BEGINPROC sqrtf ret ENDPROC RT_NOCRT(sqrtf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/tan.asm b/src/VBox/Runtime/common/math/tan.asm index 220f83ce8fbb..f8a21e023cc0 100644 --- a/src/VBox/Runtime/common/math/tan.asm +++ b/src/VBox/Runtime/common/math/tan.asm @@ -1,4 +1,4 @@ -; $Id: tan.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: tan.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT tan - AMD64 & X86. ; @@ -117,3 +117,4 @@ RT_NOCRT_BEGINPROC tan ret ENDPROC RT_NOCRT(tan) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/tanf.asm b/src/VBox/Runtime/common/math/tanf.asm index 4b75e9732c96..d4f0b299f001 100644 --- a/src/VBox/Runtime/common/math/tanf.asm +++ b/src/VBox/Runtime/common/math/tanf.asm @@ -1,4 +1,4 @@ -; $Id: tanf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: tanf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT tanf - AMD64 & X86. ; @@ -117,3 +117,4 @@ RT_NOCRT_BEGINPROC tanf ret ENDPROC RT_NOCRT(tanf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/tanl.asm b/src/VBox/Runtime/common/math/tanl.asm index 3ac0066d0c1e..f34fa9a70de1 100644 --- a/src/VBox/Runtime/common/math/tanl.asm +++ b/src/VBox/Runtime/common/math/tanl.asm @@ -1,4 +1,4 @@ -; $Id: tanl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: tanl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT tanl - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC tanl ret ENDPROC RT_NOCRT(tanl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/trunc.asm b/src/VBox/Runtime/common/math/trunc.asm index 4b00e2a6dc17..7169d55c01db 100644 --- a/src/VBox/Runtime/common/math/trunc.asm +++ b/src/VBox/Runtime/common/math/trunc.asm @@ -1,4 +1,4 @@ -; $Id: trunc.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: trunc.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT trunc - AMD64 & X86. ; @@ -76,6 +76,7 @@ RT_NOCRT_BEGINPROC trunc ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN. %endif jmp .return_val + int3 .input_ok: ; @@ -106,3 +107,4 @@ RT_NOCRT_BEGINPROC trunc ret ENDPROC RT_NOCRT(trunc) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/truncf.asm b/src/VBox/Runtime/common/math/truncf.asm index 4f4b6403bc81..31f6770e7994 100644 --- a/src/VBox/Runtime/common/math/truncf.asm +++ b/src/VBox/Runtime/common/math/truncf.asm @@ -1,4 +1,4 @@ -; $Id: truncf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: truncf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT truncf - AMD64 & X86. ; @@ -76,6 +76,7 @@ RT_NOCRT_BEGINPROC truncf ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN. %endif jmp .return_val + int3 .input_ok: ; @@ -106,3 +107,4 @@ RT_NOCRT_BEGINPROC truncf ret ENDPROC RT_NOCRT(truncf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/truncl.asm b/src/VBox/Runtime/common/math/truncl.asm index 61aefdc8489e..15241beb6fbb 100644 --- a/src/VBox/Runtime/common/math/truncl.asm +++ b/src/VBox/Runtime/common/math/truncl.asm @@ -1,4 +1,4 @@ -; $Id: truncl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: truncl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT truncl - AMD64 & X86. ; @@ -74,3 +74,4 @@ RT_NOCRT_BEGINPROC truncl ret ENDPROC RT_NOCRT(truncl) +MARK_OBJECT_RETPOLINE_SAFE From dfc115884fe0b6a422ba4995d84a1cf0773d53f6 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:36:02 +0000 Subject: [PATCH 110/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 8. bugref:11138 svn:sync-xref-src-repo-rev: r174850 --- .../Runtime/common/compiler/vcc/except-x86-vcc-asm.asm | 5 ++++- src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm | 8 +++++++- src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm | 3 ++- src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm | 6 +++++- src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm | 4 +++- src/VBox/Runtime/common/compiler/vcc/x86-alldiv.asm | 6 +++++- src/VBox/Runtime/common/compiler/vcc/x86-alldvrm.asm | 6 +++++- src/VBox/Runtime/common/compiler/vcc/x86-allmul.asm | 4 +++- src/VBox/Runtime/common/compiler/vcc/x86-allrem.asm | 6 +++++- src/VBox/Runtime/common/compiler/vcc/x86-allshl.asm | 5 ++++- src/VBox/Runtime/common/compiler/vcc/x86-allshr.asm | 5 ++++- src/VBox/Runtime/common/compiler/vcc/x86-aulldiv.asm | 3 ++- .../Runtime/common/compiler/vcc/x86-aulldvrm-core.mac | 6 +++++- src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm.asm | 3 ++- src/VBox/Runtime/common/compiler/vcc/x86-aullrem.asm | 3 ++- src/VBox/Runtime/common/compiler/vcc/x86-aullshr.asm | 5 ++++- 16 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm b/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm index 73b652d3c127..864e5bb7d845 100644 --- a/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm +++ b/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm @@ -1,4 +1,4 @@ -; $Id: except-x86-vcc-asm.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: except-x86-vcc-asm.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - x86 Exception Handler Support Code. ; @@ -181,6 +181,7 @@ __NLG_Dispatch: pop ecx pop eax ret + int3 ;; ; NLG call + return2. @@ -195,6 +196,7 @@ GLOBALNAME_RAW __NLG_Call, function, hidden global __NLG_Return2 __NLG_Return2: ret + int3 %endif @@ -356,3 +358,4 @@ BEGINPROC rtVccEh4DoGlobalUnwind ret ENDPROC rtVccEh4DoGlobalUnwind +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: file contains indirect jumps diff --git a/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm index dc3d8a12f6e1..2f87d087c91b 100644 --- a/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm +++ b/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm @@ -1,4 +1,4 @@ -; $Id: ftol2-vcc.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: ftol2-vcc.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Floating Point to Integer related Visual C++ support routines. ; @@ -121,6 +121,7 @@ BEGINPROC_RAW __ftoui2 .return: leave ret + int3 ; ; Negative value. @@ -136,6 +137,7 @@ BEGINPROC_RAW __ftoui2 fisttp dword [esp] ; Raise exceptions as appropriate, pop ST0. xor eax, eax jmp .return + int3 ; Return MAX after maybe raising an exception. .unordered: @@ -183,6 +185,7 @@ BEGINPROC_RAW __ftoul2 .return: leave ret + int3 ; ; We've got a value that so large that fisttp can't handle it, however @@ -223,6 +226,7 @@ BEGINPROC_RAW __ftoul2 frndint ; Clear C1 & raising exceptions as appropriate. ffreep st0 jmp .return + int3 ; ; Negative value. @@ -239,6 +243,7 @@ BEGINPROC_RAW __ftoul2 xor edx, edx xor eax, eax jmp .return + int3 ; ; Unordered or a value in the (-1.0, 0) range. @@ -288,3 +293,4 @@ g_r32TwoToThePowerOf63: g_r32QNaN: dd 0xffc00000 ; Quite negative NaN (RTFLOAT32U_INIT_QNAN) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm index c93ffd37c27a..064545ea5c51 100644 --- a/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm +++ b/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm @@ -1,4 +1,4 @@ -; $Id: guard-vcc.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: guard-vcc.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Control Flow Guard related Visual C++ support routines. ; @@ -106,3 +106,4 @@ BEGINPROC __guard_xfg_dispatch_icall_nop ENDPROC __guard_xfg_dispatch_icall_nop %endif +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm index 3abcd81f573b..b6d32d52b66e 100644 --- a/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm +++ b/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm @@ -1,4 +1,4 @@ -; $Id: stack-probe-vcc.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: stack-probe-vcc.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Stack related Visual C++ support routines. ; @@ -89,6 +89,7 @@ BEGINPROC_RAW __chkstk leave %ifndef RT_ARCH_X86 ret + int3 %else ; ; Do the stack space allocation and jump to the return location. @@ -96,6 +97,7 @@ BEGINPROC_RAW __chkstk sub esp, eax add esp, 4 jmp dword [esp + eax - 4] + int3 %endif ; @@ -141,6 +143,7 @@ BEGINPROC_RAW __alloca_probe_ %+ %1 pop ecx jmp __alloca_probe + int3 .bad_alloc_size: %ifdef RT_STRICT @@ -155,3 +158,4 @@ __alloc_probe_xxx 16 __alloc_probe_xxx 8 %endif ; RT_ARCH_X86 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm index 18b7377ec7b8..128a07826555 100644 --- a/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm +++ b/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm @@ -1,4 +1,4 @@ -; $Id: stack-vcc.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: stack-vcc.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Stack related Visual C++ support routines. ; @@ -266,6 +266,7 @@ ALIGNCODE(16) BEGINPROC _RTC_CheckEsp jne .unexpected_esp ret + int3 .unexpected_esp: push xBP @@ -637,3 +638,4 @@ BEGINPROC rtVccCaptureContext ret ENDPROC rtVccCaptureContext +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-alldiv.asm b/src/VBox/Runtime/common/compiler/vcc/x86-alldiv.asm index 1dddafb0b3bf..2a27903c110f 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-alldiv.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-alldiv.asm @@ -1,4 +1,4 @@ -; $Id: x86-alldiv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-alldiv.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - signed 64-bit division support, x86. ; @@ -74,6 +74,7 @@ BEGINPROC_RAW __alldiv ; Both positive, so same as unsigned division. jmp __aulldiv + int3 ; @@ -106,6 +107,7 @@ BEGINPROC_RAW __alldiv leave ret 10h + int3 .negative_dividend: push ebp @@ -137,6 +139,7 @@ BEGINPROC_RAW __alldiv leave ret 10h + int3 .negative_dividend_negative_divisor: ; negate both dividend (above) and divisor, do unsigned division(, and negate the remainder). @@ -154,3 +157,4 @@ BEGINPROC_RAW __alldiv ret 10h ENDPROC_RAW __alldiv +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-alldvrm.asm b/src/VBox/Runtime/common/compiler/vcc/x86-alldvrm.asm index 0536a9634f12..03fe829cde86 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-alldvrm.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-alldvrm.asm @@ -1,4 +1,4 @@ -; $Id: x86-alldvrm.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-alldvrm.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - signed 64-bit division support, x86. ; @@ -74,6 +74,7 @@ BEGINPROC_RAW __alldvrm ; Both positive, so same as unsigned division. jmp __aulldvrm + int3 ; @@ -106,6 +107,7 @@ BEGINPROC_RAW __alldvrm leave ret 10h + int3 .negative_dividend: push ebp @@ -142,6 +144,7 @@ BEGINPROC_RAW __alldvrm leave ret 10h + int3 .negative_dividend_negative_divisor: ; negate both dividend (above) and divisor, do unsigned division, and negate the remainder. @@ -158,3 +161,4 @@ BEGINPROC_RAW __alldvrm jmp .return_negated_remainder ENDPROC_RAW __alldvrm +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-allmul.asm b/src/VBox/Runtime/common/compiler/vcc/x86-allmul.asm index becfe7292535..c10942d82857 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-allmul.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-allmul.asm @@ -1,4 +1,4 @@ -; $Id: x86-allmul.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-allmul.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - 64-bit multiplication support, x86. ; @@ -64,6 +64,7 @@ BEGINPROC_RAW __allmul mul dword [esp + 0ch] ret 10h + int3 ; ; Complicated. @@ -88,3 +89,4 @@ BEGINPROC_RAW __allmul ret 10h ENDPROC_RAW __allmul +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-allrem.asm b/src/VBox/Runtime/common/compiler/vcc/x86-allrem.asm index 18e1ad23fe01..0d049b8b3785 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-allrem.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-allrem.asm @@ -1,4 +1,4 @@ -; $Id: x86-allrem.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-allrem.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - signed 64-bit division support, x86. ; @@ -74,6 +74,7 @@ BEGINPROC_RAW __allrem ; Both positive, so same as unsigned division. jmp __aullrem + int3 .negative_divisor_positive_dividend: @@ -84,6 +85,7 @@ BEGINPROC_RAW __allrem mov [esp + 0ch+4], ecx jmp __aullrem + int3 ; @@ -122,6 +124,7 @@ BEGINPROC_RAW __allrem leave ret 10h + int3 .negative_dividend_negative_divisor: ; negate both dividend (above) and divisor, do unsigned division, and negate the remainder. @@ -132,3 +135,4 @@ BEGINPROC_RAW __allrem jmp .negative_dividend_positive_divisor ENDPROC_RAW __allrem +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-allshl.asm b/src/VBox/Runtime/common/compiler/vcc/x86-allshl.asm index 995955f804b0..9a6c459585f4 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-allshl.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-allshl.asm @@ -1,4 +1,4 @@ -; $Id: x86-allshl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-allshl.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - 64-bit left shift support, x86. ; @@ -56,6 +56,7 @@ BEGINPROC_RAW __allshl shld edx, eax, cl shl eax, cl ret + int3 .shift_32_or_more: test cl, ~63 @@ -67,9 +68,11 @@ BEGINPROC_RAW __allshl .return_zero_eax: xor eax, eax ret + int3 .shift_64_or_more: xor edx, edx jmp .return_zero_eax ENDPROC_RAW __allshl +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-allshr.asm b/src/VBox/Runtime/common/compiler/vcc/x86-allshr.asm index 5a5874ff7d67..1ce3874d4ba3 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-allshr.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-allshr.asm @@ -1,4 +1,4 @@ -; $Id: x86-allshr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-allshr.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - signed 64-bit right shift support, x86. ; @@ -56,6 +56,7 @@ BEGINPROC_RAW __allshr shrd eax, edx, cl sar edx, cl ret + int3 .shift_32_or_more: mov eax, edx @@ -67,9 +68,11 @@ BEGINPROC_RAW __allshr and cl, 31 sar eax, cl ret + int3 .shift_64_or_more: mov eax, edx ret ENDPROC_RAW __allshr +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aulldiv.asm b/src/VBox/Runtime/common/compiler/vcc/x86-aulldiv.asm index 9b08a492c3a9..0e6e48ae4ffe 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aulldiv.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aulldiv.asm @@ -1,4 +1,4 @@ -; $Id: x86-aulldiv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aulldiv.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit division support, x86. ; @@ -44,3 +44,4 @@ rtVccUnsignedDivision __aulldiv, 1 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm-core.mac b/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm-core.mac index 4809e332962b..016835641f38 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm-core.mac +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm-core.mac @@ -1,4 +1,4 @@ -; $Id: x86-aulldvrm-core.mac 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aulldvrm-core.mac 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit division support, x86. ; @@ -110,6 +110,7 @@ BEGINPROC_RAW %1 %endif leave ret 10h + int3 ; ; The divisor is larger than 32 bits. @@ -140,6 +141,7 @@ BEGINPROC_RAW %1 shrd eax, edx, cl shr edx, cl jmp .shifted + int3 .shift_32: ; simplified version. mov edi, ebx @@ -185,6 +187,7 @@ BEGINPROC_RAW %1 %endif leave ret 10h + int3 .quotient_is_one_above_and_calc_remainder: %if %2 != 1 @@ -200,6 +203,7 @@ BEGINPROC_RAW %1 dec edi %endif jmp .done + int3 %else ; diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm.asm b/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm.asm index 261edfb1f8ce..a2708c3b5a15 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm.asm @@ -1,4 +1,4 @@ -; $Id: x86-aulldvrm.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aulldvrm.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit division support, x86. ; @@ -44,3 +44,4 @@ rtVccUnsignedDivision __aulldvrm, 0 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aullrem.asm b/src/VBox/Runtime/common/compiler/vcc/x86-aullrem.asm index 84e2c20f274a..9e3a4172ce21 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aullrem.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aullrem.asm @@ -1,4 +1,4 @@ -; $Id: x86-aullrem.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aullrem.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit division support, x86. ; @@ -43,3 +43,4 @@ rtVccUnsignedDivision __aullrem, 2 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aullshr.asm b/src/VBox/Runtime/common/compiler/vcc/x86-aullshr.asm index 3f7ac32781b1..334fa47659e6 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aullshr.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aullshr.asm @@ -1,4 +1,4 @@ -; $Id: x86-aullshr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aullshr.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit right shift support, x86. ; @@ -56,6 +56,7 @@ BEGINPROC_RAW __aullshr shrd eax, edx, cl shr edx, cl ret + int3 .shift_32_or_more: test cl, ~63 @@ -67,9 +68,11 @@ BEGINPROC_RAW __aullshr .return_zero_edx: xor edx, edx ret + int3 .shift_64_or_more: xor eax, eax jmp .return_zero_edx ENDPROC_RAW __aullshr +MARK_OBJECT_RETPOLINE_SAFE From f197cc458df36203c275d0bb109f4ea88945afb1 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:36:50 +0000 Subject: [PATCH 111/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 9. bugref:11138 svn:sync-xref-src-repo-rev: r174851 --- src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm | 4 +++- src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm | 4 +++- src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm | 3 ++- src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm | 3 ++- 15 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm index 24a0ae38970e..42ae5a00fc6a 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicCmpXchgExU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicCmpXchgExU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicCmpXchgExU64(). ; @@ -92,3 +92,4 @@ RT_BEGINPROC ASMAtomicCmpXchgExU64 %endif ENDPROC ASMAtomicCmpXchgExU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm index 5fabb9204c3b..76ae4b48a0d8 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicCmpXchgU16.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicCmpXchgU16.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicCmpXchgU16(). ; @@ -71,3 +71,4 @@ RT_BEGINPROC ASMAtomicCmpXchgU16 ret ENDPROC ASMAtomicCmpXchgU16 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm index 6ef61a2c902e..f75c68d982ac 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicCmpXchgU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicCmpXchgU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicCmpXchgU64(). ; @@ -86,3 +86,4 @@ RT_BEGINPROC ASMAtomicCmpXchgU64 %endif ENDPROC ASMAtomicCmpXchgU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm index 1b4bf81233f5..81e8a1df41e0 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicCmpXchgU8.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicCmpXchgU8.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicCmpXchgU8(). ; @@ -71,3 +71,4 @@ RT_BEGINPROC ASMAtomicCmpXchgU8 ret ENDPROC ASMAtomicCmpXchgU8 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm index 2ed9436ed6e1..1b7a1366fcff 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicReadU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicReadU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicReadU64(). ; @@ -79,3 +79,4 @@ RT_BEGINPROC ASMAtomicReadU64 %endif ENDPROC ASMAtomicReadU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm index d86a951f1580..e6a3d0a5fb2b 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoAndU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoAndU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoAndU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoAndU32 ret ENDPROC ASMAtomicUoAndU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm index 9b6d52296d90..6c908c7e8123 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoAndU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoAndU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoAndU64(). ; @@ -75,6 +75,7 @@ RT_BEGINPROC ASMAtomicUoAndU64 mov ecx, edx and ecx, [ebp + 0ch + 4] jmp .try_again + int3 .done: pop edi @@ -84,3 +85,4 @@ RT_BEGINPROC ASMAtomicUoAndU64 ret ENDPROC ASMAtomicUoAndU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm index 13465e2a7e1c..42436d8c2a3f 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoDecU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoDecU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoDecU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoDecU32 ret ENDPROC ASMAtomicUoDecU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm index 186163b7af63..f0e1404a0037 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoIncU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoIncU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoIncU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoIncU32 ret ENDPROC ASMAtomicUoIncU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm index 16f47030516d..aec58e2cc05c 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoOrU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoOrU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoOrU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoOrU32 ret ENDPROC ASMAtomicUoOrU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm index d43ca1cd3302..77efb8361d6f 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoOrU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoOrU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoOrU64(). ; @@ -75,6 +75,7 @@ RT_BEGINPROC ASMAtomicUoOrU64 mov ecx, edx or ecx, [ebp + 0ch + 4] jmp .try_again + int3 .done: pop edi @@ -84,3 +85,4 @@ RT_BEGINPROC ASMAtomicUoOrU64 ret ENDPROC ASMAtomicUoOrU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm index e86b29d004f9..8adea3e0f072 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoReadU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoReadU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoReadU64(). ; @@ -78,3 +78,4 @@ RT_BEGINPROC ASMAtomicUoReadU64 %endif ENDPROC ASMAtomicUoReadU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm index e82a8e580d58..73e51da64d18 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoXorU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoXorU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoXorU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoXorU32 ret ENDPROC ASMAtomicUoXorU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm b/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm index 5b81a233ae99..3feefba7b914 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicXchgU16.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicXchgU16.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicXchgU16(). ; @@ -68,3 +68,4 @@ RT_BEGINPROC ASMAtomicXchgU16 ret ENDPROC ASMAtomicXchgU16 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm index 29b69060814b..4a9800afa481 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicXchgU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicXchgU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicXchgU64(). ; @@ -78,3 +78,4 @@ RT_BEGINPROC ASMAtomicXchgU64 %endif ENDPROC ASMAtomicXchgU64 +MARK_OBJECT_RETPOLINE_SAFE From fc7f4ef44aa2ac45abd5c00ed541f7b868e2c846 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:37:22 +0000 Subject: [PATCH 112/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 10. bugref:11138 svn:sync-xref-src-repo-rev: r174852 --- src/VBox/Runtime/common/asm/ASMAddFlags.asm | 3 ++- src/VBox/Runtime/common/asm/ASMBitFirstClear.asm | 4 +++- src/VBox/Runtime/common/asm/ASMBitFirstSet.asm | 4 +++- src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm | 5 ++++- src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm | 5 ++++- src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm | 6 +++++- src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm | 5 ++++- src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm | 5 ++++- src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm | 8 +++++++- src/VBox/Runtime/common/asm/ASMBitNextClear.asm | 5 ++++- src/VBox/Runtime/common/asm/ASMBitNextSet.asm | 5 ++++- src/VBox/Runtime/common/asm/ASMCpuId.asm | 3 ++- src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm | 3 ++- src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm | 3 ++- src/VBox/Runtime/common/asm/ASMFxRstor.asm | 3 ++- src/VBox/Runtime/common/asm/ASMFxSave.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetFSBase.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetFlags.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetGDTR.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetGSBase.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetIDTR.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetLDTR.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetSegAttr.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetTR.asm | 3 ++- src/VBox/Runtime/common/asm/ASMGetXcr0.asm | 3 ++- src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm | 8 +++++++- src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm | 4 +++- src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm | 3 ++- src/VBox/Runtime/common/asm/ASMNopPause.asm | 3 ++- src/VBox/Runtime/common/asm/ASMRdMsrEx.asm | 3 ++- .../Runtime/common/asm/ASMSerializeInstruction-cpuid.asm | 3 ++- .../Runtime/common/asm/ASMSerializeInstruction-iret.asm | 3 ++- .../Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm | 3 ++- src/VBox/Runtime/common/asm/ASMSetFSBase.asm | 3 ++- src/VBox/Runtime/common/asm/ASMSetFlags.asm | 3 ++- src/VBox/Runtime/common/asm/ASMSetGDTR.asm | 3 ++- src/VBox/Runtime/common/asm/ASMSetGSBase.asm | 3 ++- src/VBox/Runtime/common/asm/ASMSetIDTR.asm | 3 ++- src/VBox/Runtime/common/asm/ASMSetXcr0.asm | 3 ++- src/VBox/Runtime/common/asm/ASMWrMsr.asm | 3 ++- src/VBox/Runtime/common/asm/ASMWrMsrEx.asm | 3 ++- src/VBox/Runtime/common/asm/ASMXRstor.asm | 3 ++- src/VBox/Runtime/common/asm/ASMXSave.asm | 3 ++- 44 files changed, 116 insertions(+), 44 deletions(-) diff --git a/src/VBox/Runtime/common/asm/ASMAddFlags.asm b/src/VBox/Runtime/common/asm/ASMAddFlags.asm index 2052bb004387..d44ad0f05e78 100644 --- a/src/VBox/Runtime/common/asm/ASMAddFlags.asm +++ b/src/VBox/Runtime/common/asm/ASMAddFlags.asm @@ -1,4 +1,4 @@ -; $Id: ASMAddFlags.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAddFlags.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetFlags(). ; @@ -78,3 +78,4 @@ RT_BEGINPROC ASMAddFlags ret ENDPROC ASMAddFlags +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm b/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm index f2a7a02ea22c..df4630a29ee2 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstClear.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstClear.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstClear(). ; @@ -118,6 +118,7 @@ RT_BEGINPROC ASMBitFirstClear leave %endif ret + int3 ; failure ;} @@ -135,3 +136,4 @@ RT_BEGINPROC ASMBitFirstClear ret ENDPROC ASMBitFirstClear +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm index ee65fb0ea2aa..e4476fb06240 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstSet.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstSet.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstSet(). ; @@ -118,6 +118,7 @@ RT_BEGINPROC ASMBitFirstSet leave %endif ret + int3 ; failure ;} @@ -135,3 +136,4 @@ RT_BEGINPROC ASMBitFirstSet ret ENDPROC ASMBitFirstSet +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm index 8835ea1086b4..b20a53c09ad6 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstSetU16.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstSetU16.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstSetU16(). ; @@ -71,6 +71,7 @@ RT_BEGINPROC ASMBitFirstSetU16 jc .return inc ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -95,9 +96,11 @@ RT_BEGINPROC ASMBitFirstSetU16 inc eax .return: ret + int3 .return_zero: xor eax, eax ret %endif ENDPROC ASMBitFirstSetU16 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm index 6708ff92d333..558de82489f3 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstSetU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstSetU32.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstSetU32(). ; @@ -76,6 +76,7 @@ RT_BEGINPROC ASMBitFirstSetU32 jc .return inc ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -99,9 +100,11 @@ RT_BEGINPROC ASMBitFirstSetU32 inc eax .return: ret + int3 .return_zero: xor eax, eax ret %endif ENDPROC ASMBitFirstSetU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm index aa99e81952d5..0288db5da96c 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstSetU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstSetU64.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstSetU64(). ; @@ -86,6 +86,7 @@ RT_BEGINPROC ASMBitFirstSetU64 jc .return inc ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -103,6 +104,7 @@ RT_BEGINPROC ASMBitFirstSetU64 jz .return_zero inc eax ret + int3 %elif ARCH_BITS == 32 ; Check the first dword then the 2nd one. @@ -110,6 +112,7 @@ RT_BEGINPROC ASMBitFirstSetU64 jnz .check_2nd_dword inc eax ret + int3 .check_2nd_dword: bsf eax, dword [esp + 4 + 4] jz .return_zero @@ -124,3 +127,4 @@ RT_BEGINPROC ASMBitFirstSetU64 %endif ENDPROC ASMBitFirstSetU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm b/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm index 2ccc3e881f91..3f97bae64a63 100644 --- a/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm +++ b/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitLastSetU16.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitLastSetU16.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitLastSetU16(). ; @@ -69,6 +69,7 @@ RT_BEGINPROC ASMBitLastSetU16 jc .return dec ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -93,9 +94,11 @@ RT_BEGINPROC ASMBitLastSetU16 inc eax .return: ret + int3 .return_zero: xor eax, eax ret %endif ENDPROC ASMBitLastSetU16 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm b/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm index ca60ec5c6b57..b33ac29b6628 100644 --- a/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm +++ b/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitLastSetU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitLastSetU32.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitLastSetU32(). ; @@ -76,6 +76,7 @@ RT_BEGINPROC ASMBitLastSetU32 jc .return dec ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -99,9 +100,11 @@ RT_BEGINPROC ASMBitLastSetU32 inc eax .return: ret + int3 .return_zero: xor eax, eax ret %endif ENDPROC ASMBitLastSetU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm b/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm index 8e35542aae3d..28f382bc49b0 100644 --- a/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm +++ b/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitLastSetU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitLastSetU64.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitLastSetU64(). ; @@ -86,12 +86,14 @@ RT_BEGINPROC ASMBitLastSetU64 jc .return dec ax jmp .next_bit + int3 .return_zero: xor ax, ax .return: pop bp ret + int3 %else %if ARCH_BITS == 64 @@ -104,6 +106,7 @@ RT_BEGINPROC ASMBitLastSetU64 inc eax .return: ret + int3 %elif ARCH_BITS == 32 ; Check the 2nd dword then the first one. @@ -111,12 +114,14 @@ RT_BEGINPROC ASMBitLastSetU64 jz .check_1st_dword add eax, 32 ret + int3 .check_1st_dword: bsr eax, dword [esp + 4 + 0] jz .return_zero inc eax ret + int3 %else %error "Missing or invalid ARCH_BITS." @@ -128,3 +133,4 @@ RT_BEGINPROC ASMBitLastSetU64 %endif ENDPROC ASMBitLastSetU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitNextClear.asm b/src/VBox/Runtime/common/asm/ASMBitNextClear.asm index c3a58e93db29..33fa906dde1a 100644 --- a/src/VBox/Runtime/common/asm/ASMBitNextClear.asm +++ b/src/VBox/Runtime/common/asm/ASMBitNextClear.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitNextClear.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitNextClear.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitNextClear(). ; @@ -115,6 +115,7 @@ RT_BEGINPROC ASMBitNextClear leave %endif ret + int3 ; ; Do dword scan. @@ -175,9 +176,11 @@ RT_BEGINPROC ASMBitNextClear shl edi, 3 ; edi=bit offset of current dword. add eax, edi jmp .return + int3 .return_failure: mov eax, 0ffffffffh jmp .return ENDPROC ASMBitNextClear +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitNextSet.asm b/src/VBox/Runtime/common/asm/ASMBitNextSet.asm index fc40e24d9111..60d20fe64c23 100644 --- a/src/VBox/Runtime/common/asm/ASMBitNextSet.asm +++ b/src/VBox/Runtime/common/asm/ASMBitNextSet.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitNextSet.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitNextSet.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitNextSet(). ; @@ -116,6 +116,7 @@ RT_BEGINPROC ASMBitNextSet leave %endif ret + int3 ; ; Do dword scan. @@ -175,9 +176,11 @@ RT_BEGINPROC ASMBitNextSet shl edi, 3 ; edi=bit offset of current dword. add eax, edi jmp .return + int3 .return_failure: mov eax, 0ffffffffh jmp .return ENDPROC ASMBitNextSet +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMCpuId.asm b/src/VBox/Runtime/common/asm/ASMCpuId.asm index b889072d9b41..1cbfb3db3fb6 100644 --- a/src/VBox/Runtime/common/asm/ASMCpuId.asm +++ b/src/VBox/Runtime/common/asm/ASMCpuId.asm @@ -1,4 +1,4 @@ -; $Id: ASMCpuId.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMCpuId.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMCpuIdExSlow(). ; @@ -119,3 +119,4 @@ RT_BEGINPROC ASMCpuId ret ENDPROC ASMCpuId +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm b/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm index 7a8ecfde0dca..93da869fd8e9 100644 --- a/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm +++ b/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm @@ -1,4 +1,4 @@ -; $Id: ASMCpuIdExSlow.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMCpuIdExSlow.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMCpuIdExSlow(). ; @@ -179,3 +179,4 @@ RT_BEGINPROC ASMCpuIdExSlow ret ENDPROC ASMCpuIdExSlow +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm b/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm index 47a93e3520d1..59a8dad16b4a 100644 --- a/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm +++ b/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm @@ -1,4 +1,4 @@ -; $Id: ASMCpuId_Idx_ECX.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMCpuId_Idx_ECX.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMCpuId_Idx_ECX(). ; @@ -124,3 +124,4 @@ RT_BEGINPROC ASMCpuId_Idx_ECX %endif ENDPROC ASMCpuId_Idx_ECX +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMFxRstor.asm b/src/VBox/Runtime/common/asm/ASMFxRstor.asm index c0153fad1cec..3afeb18b2859 100644 --- a/src/VBox/Runtime/common/asm/ASMFxRstor.asm +++ b/src/VBox/Runtime/common/asm/ASMFxRstor.asm @@ -1,4 +1,4 @@ -; $Id: ASMFxRstor.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMFxRstor.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMFxRstor(). ; @@ -72,3 +72,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMFxRstor +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMFxSave.asm b/src/VBox/Runtime/common/asm/ASMFxSave.asm index c3bed12dfb13..139db7cc9f87 100644 --- a/src/VBox/Runtime/common/asm/ASMFxSave.asm +++ b/src/VBox/Runtime/common/asm/ASMFxSave.asm @@ -1,4 +1,4 @@ -; $Id: ASMFxSave.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMFxSave.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMFxSave(). ; @@ -72,3 +72,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMFxSave +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetFSBase.asm b/src/VBox/Runtime/common/asm/ASMGetFSBase.asm index ce3119884ae7..b92e73b31263 100644 --- a/src/VBox/Runtime/common/asm/ASMGetFSBase.asm +++ b/src/VBox/Runtime/common/asm/ASMGetFSBase.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetFSBase.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetFSBase.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetFSBase(). ; @@ -52,3 +52,4 @@ RT_BEGINPROC ASMGetFSBase ret ENDPROC ASMGetFSBase +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetFlags.asm b/src/VBox/Runtime/common/asm/ASMGetFlags.asm index 30d0af222d55..ca802b6dd66e 100644 --- a/src/VBox/Runtime/common/asm/ASMGetFlags.asm +++ b/src/VBox/Runtime/common/asm/ASMGetFlags.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetFlags.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetFlags.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetFlags(). ; @@ -51,3 +51,4 @@ RT_BEGINPROC ASMGetFlags ret ENDPROC ASMGetFlags +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetGDTR.asm b/src/VBox/Runtime/common/asm/ASMGetGDTR.asm index 56e657db3502..c1b42a35531e 100644 --- a/src/VBox/Runtime/common/asm/ASMGetGDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMGetGDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetGDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetGDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetGDTR(). ; @@ -60,3 +60,4 @@ RT_BEGINPROC ASMGetGDTR ret ENDPROC ASMGetGDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetGSBase.asm b/src/VBox/Runtime/common/asm/ASMGetGSBase.asm index a2c0250864f3..eca9ca7e1a4c 100644 --- a/src/VBox/Runtime/common/asm/ASMGetGSBase.asm +++ b/src/VBox/Runtime/common/asm/ASMGetGSBase.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetGSBase.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetGSBase.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetGSBase(). ; @@ -52,3 +52,4 @@ RT_BEGINPROC ASMGetGSBase ret ENDPROC ASMGetGSBase +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetIDTR.asm b/src/VBox/Runtime/common/asm/ASMGetIDTR.asm index e41b34804a1c..78f8ebbaa66e 100644 --- a/src/VBox/Runtime/common/asm/ASMGetIDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMGetIDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetIDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetIDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetIDTR(). ; @@ -60,3 +60,4 @@ RT_BEGINPROC ASMGetIDTR ret ENDPROC ASMGetIDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm b/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm index 06f5f50dc730..79760313d2a1 100644 --- a/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm +++ b/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetIdtrLimit.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetIdtrLimit.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetIdtrLimit(). ; @@ -56,3 +56,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMGetIdtrLimit +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetLDTR.asm b/src/VBox/Runtime/common/asm/ASMGetLDTR.asm index c5f00a5e9b6e..6c4046b0079f 100644 --- a/src/VBox/Runtime/common/asm/ASMGetLDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMGetLDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetLDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetLDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetLDTR(). ; @@ -51,3 +51,4 @@ RT_BEGINPROC ASMGetLDTR ret ENDPROC ASMGetLDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm b/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm index de76d2e04e6a..2f8e0d21ff9f 100644 --- a/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm +++ b/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetSegAttr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetSegAttr.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetSegAttr(). ; @@ -69,3 +69,4 @@ RT_BEGINPROC ASMGetSegAttr ret ENDPROC ASMGetSegAttr +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetTR.asm b/src/VBox/Runtime/common/asm/ASMGetTR.asm index 8da48479edf9..d7505f547b68 100644 --- a/src/VBox/Runtime/common/asm/ASMGetTR.asm +++ b/src/VBox/Runtime/common/asm/ASMGetTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetTR(). ; @@ -51,3 +51,4 @@ RT_BEGINPROC ASMGetTR ret ENDPROC ASMGetTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetXcr0.asm b/src/VBox/Runtime/common/asm/ASMGetXcr0.asm index 436c0b667f26..eb64466836a9 100644 --- a/src/VBox/Runtime/common/asm/ASMGetXcr0.asm +++ b/src/VBox/Runtime/common/asm/ASMGetXcr0.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetXcr0.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetXcr0.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetXcr0(). ; @@ -64,3 +64,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMGetXcr0 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm b/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm index 5a30cd6f1adc..6b91f49e854c 100644 --- a/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm +++ b/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm @@ -1,4 +1,4 @@ -; $Id: ASMMemFirstMismatchingU8.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMMemFirstMismatchingU8.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMMemFirstMismatchingU8(). ; @@ -204,6 +204,7 @@ SEH64_END_PROLOGUE leave %endif ret + int3 ; Return after byte scan mismatch. .return_xDI: @@ -215,6 +216,7 @@ SEH64_END_PROLOGUE leave %endif ret + int3 ; ; Multibyte mismatch. We rewind and do a byte scan of the remainder. @@ -225,6 +227,7 @@ SEH64_END_PROLOGUE lea xCX, [xCX * xCB + xCB] or ecx, edx jmp .byte_by_byte + int3 ; ; Unaligned pointer. If it's worth it, align the pointer, but if the @@ -277,6 +280,7 @@ SEH64_END_PROLOGUE scasb jne .return_xDI jmp .aligned_pv + int3 %else ; ARCH_BITS == 16 @@ -330,6 +334,7 @@ CPU 8086 pop di pop bp ret + int3 .word_mismatch: ; back up a word. @@ -353,3 +358,4 @@ CPU 8086 %endif ; ARCH_BITS == 16 ENDPROC ASMMemFirstMismatchingU8 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm b/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm index 28e753d7bd19..fe9fe2c9a489 100644 --- a/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm +++ b/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMMultU32ByU32DivByU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMMultU32ByU32DivByU32.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Assembly Functions, ASMMultU32ByU32DivByU32. ; @@ -72,3 +72,5 @@ RT_BEGINPROC ASMMultU32ByU32DivByU32 ret ENDPROC ASMMultU32ByU32DivByU32 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm b/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm index 84597f58f092..62d04cb0613c 100644 --- a/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm +++ b/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMMultU64ByU32DivByU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMMultU64ByU32DivByU32.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Assembly Functions, ASMMultU64ByU32DivByU32. ; @@ -139,3 +139,4 @@ RT_BEGINPROC ASMMultU64ByU32DivByU32 ret ENDPROC ASMMultU64ByU32DivByU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMNopPause.asm b/src/VBox/Runtime/common/asm/ASMNopPause.asm index fdf22913617d..1405a1210348 100644 --- a/src/VBox/Runtime/common/asm/ASMNopPause.asm +++ b/src/VBox/Runtime/common/asm/ASMNopPause.asm @@ -1,4 +1,4 @@ -; $Id: ASMNopPause.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMNopPause.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMNopPause(). ; @@ -49,3 +49,4 @@ RT_BEGINPROC ASMNopPause ret ENDPROC ASMNopPause +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm b/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm index 5fe9c26464d1..8eec3f81f4f4 100644 --- a/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm +++ b/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm @@ -1,4 +1,4 @@ -; $Id: ASMRdMsrEx.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: ASMRdMsrEx.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMRdMsrEx(). ; @@ -91,3 +91,4 @@ SEH64_END_PROLOGUE %endif ENDPROC ASMRdMsrEx +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm index ea0467c2d845..0106a141446d 100644 --- a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm +++ b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm @@ -1,4 +1,4 @@ -; $Id: ASMSerializeInstruction-cpuid.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSerializeInstruction-cpuid.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSerializeInstruction() using cpuid. ; @@ -57,3 +57,4 @@ RT_BEGINPROC ASMSerializeInstructionCpuId ret ENDPROC ASMSerializeInstructionCpuId +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm index 89eb377e6765..cad369cd8461 100644 --- a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm +++ b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm @@ -1,4 +1,4 @@ -; $Id: ASMSerializeInstruction-iret.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSerializeInstruction-iret.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSerializeInstruction() using iret. ; @@ -69,3 +69,4 @@ RT_BEGINPROC ASMSerializeInstructionIRet %endif ENDPROC ASMSerializeInstructionIRet +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm index 4adc645e378c..72e94facb6ee 100644 --- a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm +++ b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm @@ -1,4 +1,4 @@ -; $Id: ASMSerializeInstruction-rdtscp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSerializeInstruction-rdtscp.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSerializeInstruction() using rdtscp. ; @@ -54,3 +54,4 @@ RT_BEGINPROC ASMSerializeInstructionRdTscp ret ENDPROC ASMSerializeInstructionRdTscp +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetFSBase.asm b/src/VBox/Runtime/common/asm/ASMSetFSBase.asm index 0b4bd688e1a1..10992f316cb2 100644 --- a/src/VBox/Runtime/common/asm/ASMSetFSBase.asm +++ b/src/VBox/Runtime/common/asm/ASMSetFSBase.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetFSBase.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetFSBase.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetFSBase(). ; @@ -56,3 +56,4 @@ RT_BEGINPROC ASMSetFSBase ret ENDPROC ASMSetFSBase +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetFlags.asm b/src/VBox/Runtime/common/asm/ASMSetFlags.asm index c8fc77873fbb..4e107bd225ff 100644 --- a/src/VBox/Runtime/common/asm/ASMSetFlags.asm +++ b/src/VBox/Runtime/common/asm/ASMSetFlags.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetFlags.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetFlags.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetFlags(). ; @@ -67,3 +67,4 @@ RT_BEGINPROC ASMSetFlags ret ENDPROC ASMSetFlags +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetGDTR.asm b/src/VBox/Runtime/common/asm/ASMSetGDTR.asm index e4d8c061db30..3f0ab80bf467 100644 --- a/src/VBox/Runtime/common/asm/ASMSetGDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMSetGDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetGDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetGDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetGDTR(). ; @@ -60,3 +60,4 @@ RT_BEGINPROC ASMSetGDTR ret ENDPROC ASMSetGDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetGSBase.asm b/src/VBox/Runtime/common/asm/ASMSetGSBase.asm index 0f1209105e5e..e0b3fcffd18b 100644 --- a/src/VBox/Runtime/common/asm/ASMSetGSBase.asm +++ b/src/VBox/Runtime/common/asm/ASMSetGSBase.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetGSBase.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetGSBase.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetGSBase(). ; @@ -56,3 +56,4 @@ RT_BEGINPROC ASMSetGSBase ret ENDPROC ASMSetGSBase +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetIDTR.asm b/src/VBox/Runtime/common/asm/ASMSetIDTR.asm index fdf2290455f6..60c13a223d17 100644 --- a/src/VBox/Runtime/common/asm/ASMSetIDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMSetIDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetIDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetIDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetIDTR(). ; @@ -60,3 +60,4 @@ RT_BEGINPROC ASMSetIDTR ret ENDPROC ASMSetIDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetXcr0.asm b/src/VBox/Runtime/common/asm/ASMSetXcr0.asm index 86c02ac7599c..72a28abd05f8 100644 --- a/src/VBox/Runtime/common/asm/ASMSetXcr0.asm +++ b/src/VBox/Runtime/common/asm/ASMSetXcr0.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetXcr0.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetXcr0.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetXcr0(). ; @@ -78,3 +78,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMSetXcr0 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMWrMsr.asm b/src/VBox/Runtime/common/asm/ASMWrMsr.asm index e04849f59482..b49672696ff0 100644 --- a/src/VBox/Runtime/common/asm/ASMWrMsr.asm +++ b/src/VBox/Runtime/common/asm/ASMWrMsr.asm @@ -1,4 +1,4 @@ -; $Id: ASMWrMsr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMWrMsr.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMWrMsr(). ; @@ -97,3 +97,4 @@ RT_BEGINPROC ASMWrMsr %endif ENDPROC ASMWrMsr +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm b/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm index 039880958995..db0267af1a53 100644 --- a/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm +++ b/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm @@ -1,4 +1,4 @@ -; $Id: ASMWrMsrEx.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: ASMWrMsrEx.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMWrMsrEx(). ; @@ -86,3 +86,4 @@ SEH64_END_PROLOGUE %endif ENDPROC ASMWrMsrEx +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMXRstor.asm b/src/VBox/Runtime/common/asm/ASMXRstor.asm index f3d482a8e2ba..9b5e6d209baa 100644 --- a/src/VBox/Runtime/common/asm/ASMXRstor.asm +++ b/src/VBox/Runtime/common/asm/ASMXRstor.asm @@ -1,4 +1,4 @@ -; $Id: ASMXRstor.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMXRstor.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMXRstor(). ; @@ -71,3 +71,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMXRstor +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMXSave.asm b/src/VBox/Runtime/common/asm/ASMXSave.asm index e3ce807f8e2e..5d2aecd2c643 100644 --- a/src/VBox/Runtime/common/asm/ASMXSave.asm +++ b/src/VBox/Runtime/common/asm/ASMXSave.asm @@ -1,4 +1,4 @@ -; $Id: ASMXSave.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMXSave.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMXSave(). ; @@ -71,3 +71,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMXSave +MARK_OBJECT_RETPOLINE_SAFE From 668d5d8c3361ddc6e55085caf64dc19206d94600 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:37:41 +0000 Subject: [PATCH 113/176] IPRT: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 11. bugref:11138 svn:sync-xref-src-repo-rev: r174853 --- src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.asm | 3 ++- src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.asm b/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.asm index 6c5528d99cdd..dfde77cd8496 100644 --- a/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.asm +++ b/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.asm @@ -1,4 +1,4 @@ -; $Id: RTLogWriteVmm-amd64-x86.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTLogWriteVmm-amd64-x86.asm 115012 2026-08-12 23:37:41Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTLogWriteVmm - AMD64 & X86 for VBox. ; @@ -95,3 +95,4 @@ RT_BEGINPROC RTLogWriteVmm ret ENDPROC RTLogWriteVmm +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm b/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm index e6354558411f..5ef675fbd19f 100644 --- a/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm +++ b/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm @@ -1,4 +1,4 @@ -; $Id: dbgstackdumpself-amd64-x86.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: dbgstackdumpself-amd64-x86.asm 115012 2026-08-12 23:37:41Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTDbgStackDumpSelf assembly wrapper calling rtDbgStackDumpSelfWorker. ; @@ -155,3 +155,4 @@ SEH64_END_PROLOGUE ret ENDPROC RTDbgStackDumpSelf +MARK_OBJECT_RETPOLINE_SAFE From cdd07ec4c09d2171eebb56f6eccfdadc050399e2 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:38:23 +0000 Subject: [PATCH 114/176] SUP: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp. bugref:11138 svn:sync-xref-src-repo-rev: r174854 --- src/VBox/HostDrivers/Support/SUPDrvA.asm | 3 ++- src/VBox/HostDrivers/Support/SUPDrvTracerA.asm | 3 ++- src/VBox/HostDrivers/Support/SUPLibTracerA.asm | 3 ++- src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm | 5 ++++- .../HostDrivers/Support/win/SUPR3HardenedMainA-win.asm | 7 ++++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/VBox/HostDrivers/Support/SUPDrvA.asm b/src/VBox/HostDrivers/Support/SUPDrvA.asm index 5cc107d0fbbe..7e1f4bc0decd 100644 --- a/src/VBox/HostDrivers/Support/SUPDrvA.asm +++ b/src/VBox/HostDrivers/Support/SUPDrvA.asm @@ -1,4 +1,4 @@ -; $Id: SUPDrvA.asm 114843 2026-08-03 12:18:40Z knut.osmundsen@oracle.com $ +; $Id: SUPDrvA.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Driver - Assembly bits. ; @@ -53,3 +53,4 @@ BEGINPROC_EXPORTED SUPR0DispatchHostNmi ret ENDPROC SUPR0DispatchHostNmi +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm b/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm index 5e070e476a39..680f384b67bd 100644 --- a/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm +++ b/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm @@ -1,4 +1,4 @@ -; $Id: SUPDrvTracerA.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: SUPDrvTracerA.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Driver - Tracer Interface, Assembly bits. ; @@ -58,3 +58,4 @@ BEGINPROC_EXPORTED SUPR0TracerFireProbe jmp xAX ENDPROC SUPR0TracerFireProbe +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: SUPR0TracerFireProbe makes an indirect call. diff --git a/src/VBox/HostDrivers/Support/SUPLibTracerA.asm b/src/VBox/HostDrivers/Support/SUPLibTracerA.asm index e495409b57d2..ca100f8c256e 100644 --- a/src/VBox/HostDrivers/Support/SUPLibTracerA.asm +++ b/src/VBox/HostDrivers/Support/SUPLibTracerA.asm @@ -1,4 +1,4 @@ -; $Id: SUPLibTracerA.asm 114517 2026-06-25 07:52:48Z andreas.loeffler@oracle.com $ +; $Id: SUPLibTracerA.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Library - Tracer Interface, Assembly bits. ; @@ -230,3 +230,4 @@ BEGINPROC_EXPORTED SUPTracerFireProbe ret ENDPROC SUPTracerFireProbe +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm index c7a3fa2a7d5a..10c4033f9d25 100644 --- a/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm +++ b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm @@ -1,4 +1,4 @@ -; $Id: SUPDrvA-win.asm 114348 2026-06-12 15:03:49Z knut.osmundsen@oracle.com $ +; $Id: SUPDrvA-win.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Driver - Windows NT specific assembly parts. ; @@ -55,6 +55,7 @@ BEGINPROC supdrvNtQueryVirtualMemory_Xxx GLOBALNAME supdrvNtQueryVirtualMemory_ %+ %1 mov eax, %1 jmp supdrvNtQueryVirtualMemory_Jump + int3 %endm NtQueryVirtualMemorySyscall 0xAF NtQueryVirtualMemorySyscall 0xB0 @@ -93,6 +94,7 @@ BEGINPROC supdrvNtQueryVirtualMemory_Xxx GLOBALNAME supdrvNtQueryVirtualMemory_ %+ %1 mov eax, %1 jmp supdrvNtQueryVirtualMemory_Jump + int3 %endm NtQueryVirtualMemorySyscall 0x1F @@ -117,3 +119,4 @@ ENDPROC supdrvNtQueryVirtualMemory_Xxx %endif ; VBOX_WITH_HARDENING +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: there indirect calls here... diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm index 99c2ca1c77b9..4bf1a4ee86de 100644 --- a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm @@ -1,4 +1,4 @@ -; $Id: SUPR3HardenedMainA-win.asm 114350 2026-06-12 17:02:31Z knut.osmundsen@oracle.com $ +; $Id: SUPR3HardenedMainA-win.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Library - Hardened main(), Windows assembly bits. ; @@ -386,6 +386,7 @@ BEGINPROC HardenedSyscallHashStack ; Cleanup the call and return. add rsp, MY_STACK_FRAME ret + int3 .alt_stack: ; Check up to the next page. @@ -494,6 +495,7 @@ BEGINPROC HardenedSyscallHashStackPostCheck mov r13, [rsp + 24 + 8] mov r14, [rsp + 32 + 8] ret + int3 .stack_check_failed: mov rbp, 99h @@ -535,6 +537,7 @@ BEGINCODE global SUPHNTIMP_STDCALL_NAME(%1, %2) SUPHNTIMP_STDCALL_NAME(%1, %2): jmp RTCCPTR_PRE [RT_WRT_RIP(NAME(g_pfn %+ %1))] + int3 %if %3 ; @@ -569,6 +572,7 @@ BEGINPROC %1 %+ _SyscallType2 ; Introduced with build 10525 call NAME(HardenedSyscallHashStackPostCheck) %endif ret + int3 .int_alternative: int 2eh %if %5 @@ -632,3 +636,4 @@ section .rwxpg bss execute read write align=4096 GLOBALNAME g_abSupHardReadWriteExecPage resb 4096 +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: There are of course indirect calls/jmps here. From 3ea0728501fd3c5116b068493c478260b9c31130 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:38:48 +0000 Subject: [PATCH 115/176] VBoxDTrace: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp. bugref:11138 svn:sync-xref-src-repo-rev: r174855 --- src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceR0A.asm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceR0A.asm b/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceR0A.asm index a82798bdca72..a6283afd340a 100644 --- a/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceR0A.asm +++ b/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceR0A.asm @@ -1,4 +1,4 @@ -; $Id: VBoxDTraceR0A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VBoxDTraceR0A.asm 115014 2026-08-12 23:38:48Z knut.osmundsen@oracle.com $ ;; @file ; VBoxDTraceR0 - Assembly Hacks. ; @@ -30,3 +30,4 @@ extern NAME(dtrace_probe) GLOBALNAME dtrace_probe6 jmp NAME(dtrace_probe) +MARK_OBJECT_RETPOLINE_SAFE From ebdc2f6690485444ae80abae41bb9275476ccdcf Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:40:14 +0000 Subject: [PATCH 116/176] VMM: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 1. bugref:11138 svn:sync-xref-src-repo-rev: r174856 --- src/VBox/VMM/VMMAll/IEMAllN8veHlpA.asm | 4 +++- src/VBox/VMM/VMMAll/VMMAllA.asm | 3 ++- src/VBox/VMM/VMMR0/CPUMR0A.asm | 4 +++- src/VBox/VMM/VMMR0/VMMR0JmpA-amd64.asm | 5 ++++- src/VBox/VMM/VMMR0/VMMR0TripleFaultHackA.asm | 5 ++++- src/VBox/VMM/VMMRZ/CPUMRZA.asm | 3 ++- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/VBox/VMM/VMMAll/IEMAllN8veHlpA.asm b/src/VBox/VMM/VMMAll/IEMAllN8veHlpA.asm index b84f298ef25a..5ed638f030c6 100644 --- a/src/VBox/VMM/VMMAll/IEMAllN8veHlpA.asm +++ b/src/VBox/VMM/VMMAll/IEMAllN8veHlpA.asm @@ -1,4 +1,4 @@ -; $Id: IEMAllN8veHlpA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: IEMAllN8veHlpA.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; IEM - Native Recompiler Assembly Helpers. ; @@ -303,3 +303,5 @@ SEH64_END_PROLOGUE ret ENDPROC iemNativeFpCtrlRegRestore + +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: iemNativeTbEntry does an indirect jmp (probably not something we want to touch). diff --git a/src/VBox/VMM/VMMAll/VMMAllA.asm b/src/VBox/VMM/VMMAll/VMMAllA.asm index 7745c91389b6..4b325a82d43a 100644 --- a/src/VBox/VMM/VMMAll/VMMAllA.asm +++ b/src/VBox/VMM/VMMAll/VMMAllA.asm @@ -1,4 +1,4 @@ -; $Id: VMMAllA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VMMAllA.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; VMM - All Contexts Assembly Routines. ; @@ -91,3 +91,4 @@ BEGINPROC VMMTrashVolatileXMMRegs ret ENDPROC VMMTrashVolatileXMMRegs +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR0/CPUMR0A.asm b/src/VBox/VMM/VMMR0/CPUMR0A.asm index 16c6d3f8af55..cb273fe2339c 100644 --- a/src/VBox/VMM/VMMR0/CPUMR0A.asm +++ b/src/VBox/VMM/VMMR0/CPUMR0A.asm @@ -1,4 +1,4 @@ - ; $Id: CPUMR0A.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ + ; $Id: CPUMR0A.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; CPUM - Ring-0 Assembly Routines (supporting HM and IEM). ; @@ -149,6 +149,7 @@ SEH64_END_PROLOGUE %ifdef VBOX_WITH_KERNEL_USING_XMM jmp .load_guest + int3 %endif .already_saved_host: %ifdef VBOX_WITH_KERNEL_USING_XMM @@ -310,3 +311,4 @@ SEH64_END_PROLOGUE %undef pXState ENDPROC cpumR0SaveGuestRestoreHostFPUState +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR0/VMMR0JmpA-amd64.asm b/src/VBox/VMM/VMMR0/VMMR0JmpA-amd64.asm index 17f88d2345c8..f1c37fb7bbb5 100644 --- a/src/VBox/VMM/VMMR0/VMMR0JmpA-amd64.asm +++ b/src/VBox/VMM/VMMR0/VMMR0JmpA-amd64.asm @@ -1,4 +1,4 @@ -; $Id: VMMR0JmpA-amd64.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: VMMR0JmpA-amd64.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; VMM - R0 SetJmp / LongJmp routines for AMD64. ; @@ -204,6 +204,7 @@ SEH64_END_PROLOGUE jbe .do_stack_buffer_big_enough mov ecx, ebx ; too much to copy, limit to ebx jmp .do_stack_copying + int3 .do_stack_buffer_big_enough: mov ebx, ecx ; ecx is smaller, update ebx for cbStackValid @@ -250,6 +251,7 @@ SEH64_END_PROLOGUE .unexpected_return_loop: int3 jmp .unexpected_return_loop + int3 ; ; Failure @@ -279,3 +281,4 @@ SEH64_END_PROLOGUE ret ENDPROC vmmR0CallRing3LongJmp +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR0/VMMR0TripleFaultHackA.asm b/src/VBox/VMM/VMMR0/VMMR0TripleFaultHackA.asm index 925ea045f9c9..13229c651d64 100644 --- a/src/VBox/VMM/VMMR0/VMMR0TripleFaultHackA.asm +++ b/src/VBox/VMM/VMMR0/VMMR0TripleFaultHackA.asm @@ -1,4 +1,4 @@ -; $Id: VMMR0TripleFaultHackA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VMMR0TripleFaultHackA.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; VMM - Host Context Ring 0, Assembly Code for The Triple Fault Debugging Hack. ; @@ -57,6 +57,7 @@ BEGINPROC vmmR0TripleFaultHack .forever: hlt jmp .forever + int3 .s_szHello: db 'Hello post-reset world', 0ah, 0dh, 0 @@ -188,6 +189,7 @@ BEGINPROC vmmR0TripleFaultHackKbdWait pop xAX ret + int3 .read_data_and_status: in al, 60h @@ -272,3 +274,4 @@ BEGINPROC vmmR0TripleFaultHackTripleFault ret ENDPROC vmmR0TripleFaultHackTripleFault +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMRZ/CPUMRZA.asm b/src/VBox/VMM/VMMRZ/CPUMRZA.asm index d7a0f304f51d..106e650acf68 100644 --- a/src/VBox/VMM/VMMRZ/CPUMRZA.asm +++ b/src/VBox/VMM/VMMRZ/CPUMRZA.asm @@ -1,4 +1,4 @@ - ; $Id: CPUMRZA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ + ; $Id: CPUMRZA.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; CPUM - Raw-mode and Ring-0 Context Assembly Routines. ; @@ -348,3 +348,4 @@ SEH64_END_PROLOGUE ret ENDPROC cpumRZSaveGuestAvxRegisters +MARK_OBJECT_RETPOLINE_SAFE From 36249d6c2a873f03d744faffc43d10c8da74a123 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:40:45 +0000 Subject: [PATCH 117/176] VMM: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 2. bugref:11138 svn:sync-xref-src-repo-rev: r174857 --- src/VBox/VMM/VMMR0/target-x86/HMR0UtilA-x86.asm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0UtilA-x86.asm b/src/VBox/VMM/VMMR0/target-x86/HMR0UtilA-x86.asm index 26f8bb43b9bb..9af6a15cecd3 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0UtilA-x86.asm +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0UtilA-x86.asm @@ -1,4 +1,4 @@ -; $Id: HMR0UtilA-x86.asm 114485 2026-06-22 14:00:21Z klaus.espenlaub@oracle.com $ +; $Id: HMR0UtilA-x86.asm 115016 2026-08-12 23:40:45Z knut.osmundsen@oracle.com $ ;; @file ; HM - Ring-0 VMX & SVM Helpers. ; @@ -59,6 +59,7 @@ BEGINPROC VMXWriteVmcs64 jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_VMX_INVALID_VMCS_FIELD @@ -90,6 +91,7 @@ BEGINPROC VMXReadVmcs64 jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_VMX_INVALID_VMCS_FIELD @@ -123,6 +125,7 @@ BEGINPROC VMXReadVmcs32 jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_VMX_INVALID_VMCS_FIELD @@ -156,6 +159,7 @@ BEGINPROC VMXWriteVmcs32 jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_VMX_INVALID_VMCS_FIELD @@ -183,6 +187,7 @@ BEGINPROC VMXEnable jnc .good mov eax, VERR_VMX_INVALID_VMXON_PTR jmp .the_end + int3 .good: jnz .the_end @@ -301,6 +306,7 @@ BEGINPROC VMXR0InvEPT jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_INVALID_PARAMETER @@ -332,6 +338,7 @@ BEGINPROC VMXR0InvVPID jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_INVALID_PARAMETER @@ -364,3 +371,4 @@ BEGINPROC SVMR0InvlpgA ret ENDPROC SVMR0InvlpgA +MARK_OBJECT_RETPOLINE_SAFE From 542222259b7e4fdcd1f518d56409e3c7f5261dfc Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:41:03 +0000 Subject: [PATCH 118/176] VMM: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 3. bugref:11138 svn:sync-xref-src-repo-rev: r174858 --- src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm index 1094c028c627..6ef3c58f757c 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm @@ -1,4 +1,4 @@ -; $Id: HMR0A-x86.asm 114828 2026-07-31 09:19:37Z alexander.eichner@oracle.com $ +; $Id: HMR0A-x86.asm 115017 2026-08-12 23:41:03Z knut.osmundsen@oracle.com $ ;; @file ; HM - Ring-0 VMX, SVM world-switch and helper routines. ; @@ -308,6 +308,7 @@ BEGINPROC hmR0VmxExportHostSegmentRegsAsmHlp mov [pRestoreHost + VMXRESTOREHOST.uHostSelDS], ax ret + int3 ALIGNCODE(16) .use_rdmsr_for_fs_and_gs_base: @@ -435,6 +436,7 @@ BEGINPROC VMXRestoreHostState mov rsi, r11 %endif ret + int3 ALIGNCODE(8) .gdt_readonly_or_need_writable: @@ -450,6 +452,7 @@ ALIGNCODE(8) ltr dx mov cr0, r9 jmp .restore_fs + int3 ALIGNCODE(8) .gdt_readonly_need_writable: @@ -459,6 +462,7 @@ ALIGNCODE(8) ltr dx lgdt [rsi + VMXRESTOREHOST.HostGdtr] ; load the original GDT jmp .restore_fs + int3 ALIGNCODE(8) .restore_fs_using_wrmsr: @@ -664,6 +668,7 @@ BEGINPROC RT_CONCAT(hmR0VmxStartVm,%1) %endif je RT_CONCAT3(hmR0VmxStartVm,%1,_SseManual) jmp RT_CONCAT3(hmR0VmxStartVm,%1,_SseXSave) + int3 .save_xmm_no_need: %endif %endif @@ -897,12 +902,14 @@ BEGINPROC RT_CONCAT(hmR0VmxStartVm,%1) jc NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmxstart64_invalid_vmcs_ptr) jz NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmxstart64_start_failed) jmp NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1)) ; here if vmresume detected a failure + int3 .vmlaunch64_launch: vmlaunch jc NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmxstart64_invalid_vmcs_ptr) jz NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmxstart64_start_failed) jmp NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1)) ; here if vmlaunch detected a failure + int3 ; Put these two outside the normal code path as they should rarely change. @@ -918,6 +925,7 @@ ALIGNCODE(8) jna NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmwrite_failed) %endif jmp .wrote_host_rip + int3 ALIGNCODE(8) .write_host_rsp: @@ -931,6 +939,7 @@ ALIGNCODE(8) jna NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmwrite_failed) %endif jmp .wrote_host_rsp + int3 ALIGNCODE(64) GLOBALNAME_EX RT_CONCAT(hmR0VmxStartVmHostRIP,%1), notype, hidden, (NAME(hmR0VmxStartVmHostRIP %+ %1) - NAME(hmR0VmxStartVm %+ %1 %+ _EndProc)) @@ -994,6 +1003,7 @@ GLOBALNAME_EX RT_CONCAT(hmR0VmxStartVmHostRIP,%1), notype, hidden, (NAME(hmR0Vmx popf leave ret + int3 ; ; Error returns. @@ -1004,16 +1014,19 @@ GLOBALNAME_EX RT_CONCAT(hmR0VmxStartVmHostRIP,%1), notype, hidden, (NAME(hmR0Vmx jz .return_after_vmwrite_error mov dword [rsp + cbFrame + frm_rcError], VERR_VMX_INVALID_VMCS_PTR jmp .return_after_vmwrite_error + int3 %endif .vmxstart64_invalid_vmcs_ptr: mov dword [rsp + cbFrame + frm_rcError], VERR_VMX_INVALID_VMCS_PTR_TO_START_VM jmp .vmstart64_error_return + int3 .vmxstart64_start_failed: mov dword [rsp + cbFrame + frm_rcError], VERR_VMX_UNABLE_TO_START_VM .vmstart64_error_return: RESTORE_STATE_VMX 1, %2, %3, %4 mov eax, [rbp + frm_rcError] jmp .vmstart64_end + int3 %ifdef VBOX_STRICT ; Precondition checks failed. @@ -1023,6 +1036,7 @@ GLOBALNAME_EX RT_CONCAT(hmR0VmxStartVmHostRIP,%1), notype, hidden, (NAME(hmR0Vmx %error Bad frame size value: cbFrame, expected cbBaseFrame %endif jmp .return_with_restored_preserved_registers + int3 %endif %undef frm_fRFlags @@ -1129,6 +1143,7 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) %endif je RT_CONCAT3(hmR0SvmVmRun,%1,_SseManual) jmp RT_CONCAT3(hmR0SvmVmRun,%1,_SseXSave) + int3 .save_xmm_no_need: %endif %endif @@ -1486,6 +1501,7 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) popf leave ret + int3 %ifdef VBOX_STRICT ; Precondition checks failed. @@ -1564,3 +1580,5 @@ hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 %endif + +MARK_OBJECT_RETPOLINE_SAFE From f7f1f0053b8790f21c95c69873707c20728200ec Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:41:47 +0000 Subject: [PATCH 119/176] VMM: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 4. bugref:11138 svn:sync-xref-src-repo-rev: r174859 --- .../target-x86/IEMAllAImpl-x86-amd64.asm | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/VBox/VMM/VMMAll/target-x86/IEMAllAImpl-x86-amd64.asm b/src/VBox/VMM/VMMAll/target-x86/IEMAllAImpl-x86-amd64.asm index a8355093a4a2..ee25dff2194a 100644 --- a/src/VBox/VMM/VMMAll/target-x86/IEMAllAImpl-x86-amd64.asm +++ b/src/VBox/VMM/VMMAll/target-x86/IEMAllAImpl-x86-amd64.asm @@ -1,4 +1,4 @@ -; $Id: IEMAllAImpl-x86-amd64.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: IEMAllAImpl-x86-amd64.asm 115018 2026-08-12 23:41:47Z knut.osmundsen@oracle.com $ ;; @file ; IEM - Instruction Implementation in Assembly, x86 target, amd64 host. ; @@ -93,36 +93,44 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME %endmacro %macro EPILOGUE_1_ARGS 0 ret + int3 %endmacro %macro EPILOGUE_1_ARGS_EX 0 ret + int3 %endmacro %macro PROLOGUE_2_ARGS 0 %endmacro %macro EPILOGUE_2_ARGS 0 ret + int3 %endmacro %macro EPILOGUE_2_ARGS_EX 1 ret + int3 %endmacro %macro PROLOGUE_3_ARGS 0 %endmacro %macro EPILOGUE_3_ARGS 0 ret + int3 %endmacro %macro EPILOGUE_3_ARGS_EX 1 ret + int3 %endmacro %macro PROLOGUE_4_ARGS 0 %endmacro %macro EPILOGUE_4_ARGS 0 ret + int3 %endmacro %macro EPILOGUE_4_ARGS_EX 1 ret + int3 %endmacro %ifdef ASM_CALL64_GCC @@ -201,10 +209,12 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME %macro EPILOGUE_1_ARGS 0 pop edi ret 0 + int3 %endmacro %macro EPILOGUE_1_ARGS_EX 1 pop edi ret %1 + int3 %endmacro %macro PROLOGUE_2_ARGS 0 @@ -213,10 +223,12 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME %macro EPILOGUE_2_ARGS 0 pop edi ret 0 + int3 %endmacro %macro EPILOGUE_2_ARGS_EX 1 pop edi ret %1 + int3 %endmacro %macro PROLOGUE_3_ARGS 0 @@ -231,6 +243,7 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME pop edi pop ebx ret %1 + int3 %endmacro %macro EPILOGUE_3_ARGS 0 EPILOGUE_3_ARGS_EX 4 @@ -251,6 +264,7 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME pop edi pop ebx ret %1 + int3 %endmacro %macro EPILOGUE_4_ARGS 0 EPILOGUE_4_ARGS_EX 8 @@ -2198,6 +2212,7 @@ BEGINPROC_FASTCALL iemAImpl_cmpxchg_u64 %+ %2, 16 pop edi pop esi ret 8 + int3 .cmpxchg8b_not_equal: cmp [esi + 4], edx ;; @todo FIXME - verify 64-bit compare implementation @@ -2736,6 +2751,7 @@ BEGINPROC_FASTCALL iemAImpl_ %+ %1 %+ _u8 %+ %5, 12 cmp T0_8, A1_8 jae .div_overflow jmp .div_no_overflow + int3 .divisor_negative: neg A1_8 @@ -2801,6 +2817,7 @@ BEGINPROC_FASTCALL iemAImpl_ %+ %1 %+ _u16 %+ %5, 16 cmp T0_16, T1_16 jae .div_overflow jmp .div_no_overflow + int3 .divisor_negative: neg T1_16 @@ -2881,6 +2898,7 @@ BEGINPROC_FASTCALL iemAImpl_ %+ %1 %+ _u32 %+ %5, 16 cmp T0_32, A2_32 jae .div_overflow jmp .div_no_overflow + int3 .divisor_negative: neg A2_32 @@ -2968,6 +2986,7 @@ BEGINPROC_FASTCALL iemAImpl_ %+ %1 %+ _u64 %+ %5, 20 cmp T0, A2 jae .div_overflow jmp .div_no_overflow + int3 .divisor_negative: neg A2 @@ -7517,3 +7536,5 @@ IEMIMPL_ADX_64 adcx, X86_EFL_CF IEMIMPL_ADX_32 adox, X86_EFL_OF IEMIMPL_ADX_64 adox, X86_EFL_OF + +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: lots of indirect jmps/calls here for instructions with behavrioual immediates. Buch of missing int3 after ret too. From c179ca0ae1a5314b26e07c9943f85eaba9cbfc38 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:42:04 +0000 Subject: [PATCH 120/176] VMM: Applying MARK_OBJECT_RETPOLINE_SAFE and adding int3 after ret & jmp - part 5. bugref:11138 svn:sync-xref-src-repo-rev: r174860 --- src/VBox/VMM/VMMR3/PGMR3DbgA.asm | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/VBox/VMM/VMMR3/PGMR3DbgA.asm b/src/VBox/VMM/VMMR3/PGMR3DbgA.asm index 26a7fc7a32e2..b1de56b2df6f 100644 --- a/src/VBox/VMM/VMMR3/PGMR3DbgA.asm +++ b/src/VBox/VMM/VMMR3/PGMR3DbgA.asm @@ -1,4 +1,4 @@ -; $Id: PGMR3DbgA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: PGMR3DbgA.asm 115019 2026-08-12 23:42:04Z knut.osmundsen@oracle.com $ ;; @file ; PGM - Page Manager and Monitor - Debugger & Debugging API Optimizations. ; @@ -95,6 +95,7 @@ SEH64_END_PROLOGUE jne .continue inc r11d jmp .needle_check + int3 .return_edi: lea xAX, [xDI - 8] @@ -104,6 +105,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -154,6 +156,7 @@ SEH64_END_PROLOGUE jne .continue inc r11d jmp .needle_check + int3 .return_edi: lea xAX, [xDI - 4] @@ -162,6 +165,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -212,6 +216,7 @@ SEH64_END_PROLOGUE jne .continue inc r11d jmp .needle_check + int3 .return_edi: lea xAX, [xDI - 2] @@ -220,6 +225,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -267,6 +273,7 @@ SEH64_END_PROLOGUE jne .continue inc r11d jmp .needle_check + int3 .return_edi: lea xAX, [xDI - 1] @@ -275,6 +282,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -324,6 +332,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -367,6 +376,7 @@ SEH64_END_PROLOGUE cmp rax, [xDI - 1] jne .continue jmp .return_edi + int3 .check_smaller: cmp ecx, 3 jb .return_null @@ -380,6 +390,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -389,3 +400,4 @@ SEH64_END_PROLOGUE ret ENDPROC pgmR3DbgFixedMemScan8Wide1Step +MARK_OBJECT_RETPOLINE_SAFE From af118cb9e89149992013f3e727b1f59509cb0ffa Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:43:12 +0000 Subject: [PATCH 121/176] VBoxTpG: Applying MARK_OBJECT_RETPOLINE_SAFE, adding int3 after ret & jmp, and generating win64 unwind info. bugref:11138 svn:sync-xref-src-repo-rev: r174861 --- src/bldprogs/VBoxTpG.cpp | 49 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/bldprogs/VBoxTpG.cpp b/src/bldprogs/VBoxTpG.cpp index 39f045a2e9f5..854164af37aa 100644 --- a/src/bldprogs/VBoxTpG.cpp +++ b/src/bldprogs/VBoxTpG.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxTpG.cpp 114139 2026-05-14 22:38:18Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxTpG.cpp 115020 2026-08-12 23:43:12Z knut.osmundsen@oracle.com $ */ /** @file * VBox Build Tool - VBox Tracepoint Generator. */ @@ -454,7 +454,7 @@ static RTEXITCODE generateAssembly(PSCMSTREAM pStrm) * Write the file header. */ ScmStreamPrintf(pStrm, - "; $Id: VBoxTpG.cpp 114139 2026-05-14 22:38:18Z knut.osmundsen@oracle.com $ \n" + "; $Id: VBoxTpG.cpp 115020 2026-08-12 23:43:12Z knut.osmundsen@oracle.com $ \n" ";; @file\n" "; Automatically generated from %s. Do NOT edit!\n" ";\n" @@ -909,13 +909,52 @@ static RTEXITCODE generateAssembly(PSCMSTREAM pStrm) , g_pszProbeFnName); ScmStreamPrintf(pStrm, + " int3\n" ".return:\n" " ret ; The probe was disabled, return\n" ".end_proc:\n" + " int3\n" "\n"); } } + /* + * Emit unwind info for the stubs. + */ + if (fWin64) + { + ScmStreamPrintf(pStrm, + "\n" + "section .pdata rdata align=4\n"); + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "dd NAME(VTGProbeStub_%s_%s) wrt ..imagebase\n" + "dd NAME(VTGProbeStub_%s_%s.end_proc) wrt ..imagebase\n" + "dd vtg_unwind_info wrt ..imagebase\n" + , pProvider->pszName, pProbe->pszMangledName, pProvider->pszName, pProbe->pszMangledName); + } + } + + ScmStreamPrintf(pStrm, + "\n" + "section .xdata rdata align=4\n" + "align 4, db 0\n" + "vtg_unwind_info:\n" + " db 1 ; version 1 (3 bit), no flags (5 bits)\n" + " db 0 ; prolog size (0)\n" + " db 0 ; info array length (0)\n" + " db 0 ; frame register and offset.\n" + "\n" + "@feat.00 equ 1\n"); + } + + ScmStreamPrintf(pStrm, + "\n" + "MARK_OBJECT_RETPOLINE_SAFE"); /** @todo retpoline: there are plenty indirect jmps above... */ + return RTEXITCODE_SUCCESS; } @@ -1009,7 +1048,7 @@ static RTEXITCODE generateHeader(PSCMSTREAM pStrm) } ScmStreamPrintf(pStrm, - "/* $Id: VBoxTpG.cpp 114139 2026-05-14 22:38:18Z knut.osmundsen@oracle.com $ */\n" + "/* $Id: VBoxTpG.cpp 115020 2026-08-12 23:43:12Z knut.osmundsen@oracle.com $ */\n" "/** @file\n" " * Automatically generated from %s. Do NOT edit!\n" " */\n" @@ -1215,7 +1254,7 @@ static RTEXITCODE generateWrapperHeader(PSCMSTREAM pStrm) } ScmStreamPrintf(pStrm, - "/* $Id: VBoxTpG.cpp 114139 2026-05-14 22:38:18Z knut.osmundsen@oracle.com $ */\n" + "/* $Id: VBoxTpG.cpp 115020 2026-08-12 23:43:12Z knut.osmundsen@oracle.com $ */\n" "/** @file\n" " * Automatically generated from %s. Do NOT edit!\n" " */\n" @@ -2543,7 +2582,7 @@ static RTEXITCODE parseArguments(int argc, char **argv) case 'V': { /* The following is assuming that svn does it's job here. */ - static const char s_szRev[] = "$Revision: 114139 $"; + static const char s_szRev[] = "$Revision: 115020 $"; const char *psz = RTStrStripL(strchr(s_szRev, ' ')); RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz); return RTEXITCODE_SUCCESS; From e14cfea8be2ba3c4602f3a48b172450232dcfa7f Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Wed, 12 Aug 2026 23:45:19 +0000 Subject: [PATCH 122/176] Config.kmk,*.kmk: Some more hardening of ring-0 code on windows. bugref:11138 svn:sync-xref-src-repo-rev: r174862 --- Config.kmk | 35 ++++++++++++++++--- .../win/Graphics/Video/mp/Makefile.kmk | 4 +-- .../win/SharedFolders/driver/Makefile.kmk | 4 +-- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Config.kmk b/Config.kmk index 53d48b1beec2..e1333d4af226 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114986 2026-08-11 13:26:17Z knut.osmundsen@oracle.com $ +# $Id: Config.kmk 115021 2026-08-12 23:45:19Z knut.osmundsen@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -3871,6 +3871,9 @@ if1of (win, $(KBUILD_TARGET) $(KBUILD_HOST)) VBOX_VCC_LD_GUARD_CF := VBOX_VCC_CC_GUARD_EHCONT := VBOX_VCC_LD_GUARD_EHCONT := + VBOX_VCC_LD_GUARD_RETPOLINE := + VBOX_VCC_LD_GUARD_RETPOLINE_IGNORE := + VBOX_VCC_LD_GUARD_RETPOLINE_WITH_IGNORE := VBOX_VCC_LD_HIGH_ENTRYOPY_VA := VBOX_VCC_LD_CET_COMPAT := @@ -3885,6 +3888,11 @@ if1of (win, $(KBUILD_TARGET) $(KBUILD_HOST)) VBOX_VCC_CC_GUARD_EHCONT := -guard:ehcont VBOX_VCC_LD_GUARD_EHCONT := -Guard:ehcont endif + if $(VBOX_VCC_TOOL_STEM) >= VCC142 # Since when? (Also present in older versions.) + VBOX_VCC_LD_GUARD_RETPOLINE := -Guard:retpoline + VBOX_VCC_LD_GUARD_RETPOLINE_IGNORE := -Ignore:4287 + VBOX_VCC_LD_GUARD_RETPOLINE_WITH_IGNORE := -Guard:retpoline -Ignore:4287 + endif endif endif @@ -5687,8 +5695,12 @@ ifeq ($(VBOX_LDR_FMT),pe) if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Don't waste space on x86/amd64-on-arm emulation optimizations. TEMPLATE_VBoxR0_CXXFLAGS += /volatileMetadata- endif - if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Prevent speculative execution past indirect jumps. - TEMPLATE_VBoxR0_CXXFLAGS += -Qspectre-jmp + if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Speculative execution hardening. + TEMPLATE_VBoxR0_CXXFLAGS += -Qspectre + if1of ($(KBUILD_TARGET_ARCH), amd64 x86) + TEMPLATE_VBoxR0_CXXFLAGS += -Qspectre-jmp + #TEMPLATE_VBoxR0_CXXFLAGS += -d2guardretpoline - requires RTLdr support or the VM fails to start due to mismatches. + endif endif ifdef VBOX_WITH_MSC_ANALYZE_THIS TEMPLATE_VBoxR0_CXXFLAGS += /analyze @@ -5716,6 +5728,9 @@ ifeq ($(VBOX_LDR_FMT),pe) TEMPLATE_VBoxR0_LDFLAGS.win.x86 += -Include:___security_init_cookie -Export:ModuleInitSecurityCookie=__security_init_cookie TEMPLATE_VBoxR0_LDFLAGS.win.arm64 += -Include:__security_init_cookie -Export:ModuleInitSecurityCookie=__security_init_cookie endif + if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Speculative execution hardening. + #TEMPLATE_VBoxR0_LDFLAGS.win.amd64 += $(VBOX_VCC_LD_GUARD_RETPOLINE) - requires RTLdr support or the VM fails to start due to mismatches. + endif TEMPLATE_VBoxR0_LATE_LIBS = \ $(PATH_STAGE_LIB)/RuntimeR0Stub$(VBOX_SUFF_LIB) ifneq ($(VBOX_VCC_CC_GUARD_CF),) @@ -5922,10 +5937,17 @@ ifeq ($(KBUILD_TARGET),win) if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Don't waste space on x86/amd64-on-arm emulation optimizations. TEMPLATE_VBoxR0Drv_CXXFLAGS += /volatileMetadata- endif + if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Speculative execution hardening. + TEMPLATE_VBoxR0Drv_CXXFLAGS += -Qspectre + TEMPLATE_VBoxR0Drv_CXXFLAGS.win.x86 += -Qspectre-jmp + TEMPLATE_VBoxR0Drv_CXXFLAGS.win.amd64 += -Qspectre-jmp -d2guardretpoline + endif ifdef VBOX_WITH_MSC_ANALYZE_THIS TEMPLATE_VBoxR0Drv_CXXFLAGS += /analyze endif TEMPLATE_VBoxR0Drv_CFLAGS = $(TEMPLATE_VBoxR0Drv_CXXFLAGS) + TEMPLATE_VBoxR0Drv_CFLAGS.win.x86 = $(TEMPLATE_VBoxR0Drv_CXXFLAGS.win.x86) + TEMPLATE_VBoxR0Drv_CFLAGS.win.amd64 = $(TEMPLATE_VBoxR0Drv_CXXFLAGS.win.amd64) TEMPLATE_VBoxR0Drv_LDFLAGS = -WX -Ignore:4197 \ -Driver -Subsystem:NATIVE -Incremental:NO -Align:4096 -MapInfo:Exports -NoD -Release -Debug -Opt:Ref -Opt:Icf \ $(VBOX_VCC_LD_GUARD_CF) \ @@ -5943,6 +5965,9 @@ ifeq ($(KBUILD_TARGET),win) TEMPLATE_VBoxR0Drv_LDFLAGS.win.amd64 = $(VBOX_VCC_LD_HIGH_ENTRYOPY_VA) $(VBOX_VCC_LD_GUARD_EHCONT) $(VBOX_VCC_LD_CET_COMPAT) TEMPLATE_VBoxR0Drv_LDFLAGS.win.arm64 = $(VBOX_VCC_LD_HIGH_ENTRYOPY_VA) $(VBOX_VCC_LD_GUARD_EHCONT) TEMPLATE_VBoxR0Drv_LDFLAGS.win.x86 = $(VBOX_VCC_LD_CET_COMPAT) + if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Speculative execution hardening. + TEMPLATE_VBoxR0Drv_LDFLAGS.win.amd64 += $(VBOX_VCC_LD_GUARD_RETPOLINE) + endif TEMPLATE_VBoxR0Drv_ORDERDEPS = $(VBOX_SIGN_DRIVER_ORDERDEPS) TEMPLATE_VBoxR0Drv_POST_CMDS = $(VBOX_SIGN_DRIVER_CMDS) endif @@ -9612,7 +9637,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114986 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 115021 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9626,7 +9651,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114986 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 115021 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif diff --git a/src/VBox/Additions/win/Graphics/Video/mp/Makefile.kmk b/src/VBox/Additions/win/Graphics/Video/mp/Makefile.kmk index 26e100e45fa4..b8552d2e36f5 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/Makefile.kmk +++ b/src/VBox/Additions/win/Graphics/Video/mp/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 113305 2026-03-10 15:20:18Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 115021 2026-08-12 23:45:19Z knut.osmundsen@oracle.com $ ## @file # Makefile for the Windows guest miniport driver. # @@ -106,7 +106,7 @@ ifdef VBOX_WITH_WDDM $(VBOX_GRAPHICS_INCS) VBoxWddm_LDFLAGS.x86 += /Entry:DriverEntry@8 - VBoxWddm_LDFLAGS.amd64 += /Entry:DriverEntry + VBoxWddm_LDFLAGS.amd64 += /Entry:DriverEntry $(VBOX_VCC_LD_GUARD_RETPOLINE_IGNORE) VBoxWddm_LDFLAGS.arm64 += /Entry:DriverEntry VBoxWddm_SOURCES = \ diff --git a/src/VBox/Additions/win/SharedFolders/driver/Makefile.kmk b/src/VBox/Additions/win/SharedFolders/driver/Makefile.kmk index 8cec9bb68463..50a9e5b27dac 100644 --- a/src/VBox/Additions/win/SharedFolders/driver/Makefile.kmk +++ b/src/VBox/Additions/win/SharedFolders/driver/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 113299 2026-03-10 08:08:39Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 115021 2026-08-12 23:45:19Z knut.osmundsen@oracle.com $ ## @file # Sub-Makefile for the VirtualBox Windows Guest Shared Folders FSD. # @@ -97,7 +97,7 @@ endif # Hack: The rdbsslib.lib we're using wasn't compiled -guard:ehcont. Unfortunately # we cannot individually disable the -guard options, so we have to disable # everything and re-enable all but ehcont. -VBoxSF_LDFLAGS.win.amd64 = -Guard:No $(VBOX_VCC_LD_GUARD_CF) +VBoxSF_LDFLAGS.win.amd64 = -Guard:No $(VBOX_VCC_LD_GUARD_RETPOLINE_WITH_IGNORE) $(VBOX_VCC_LD_GUARD_CF) VBoxSF_USES.win += vboximportchecker VBoxSF_VBOX_IMPORT_CHECKER.win.x86 = w8/r0 From 19f057aa7001bd8e5c4db3b757c46ce12f7acd18 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Thu, 13 Aug 2026 02:02:28 +0000 Subject: [PATCH 123/176] IPRT/setjmp.asm: Corrected handcoded instruction. svn:sync-xref-src-repo-rev: r174865 --- src/VBox/Runtime/common/misc/setjmp.asm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/VBox/Runtime/common/misc/setjmp.asm b/src/VBox/Runtime/common/misc/setjmp.asm index 293a01156012..fce7b9bc1a91 100644 --- a/src/VBox/Runtime/common/misc/setjmp.asm +++ b/src/VBox/Runtime/common/misc/setjmp.asm @@ -1,4 +1,4 @@ -; $Id: setjmp.asm 115004 2026-08-12 23:33:28Z knut.osmundsen@oracle.com $ +; $Id: setjmp.asm 115024 2026-08-13 02:02:28Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT setjmp & longjmp - AMD64 & X86. ; @@ -478,7 +478,11 @@ RT_NOCRT_BEGINPROC longjmp cmp qword [rcx + RTJMPBUF.uFrame], byte 0 jnz .nt_restore - db 0xfe, 0x48, 0x0f, 0x1e, 0xca ; rdsspq rdx - a NOP unless CET is supported & enabled. + %ifdef __NASM__ + rdsspq rdx + %else + db 0xf3, 0x48, 0x0f, 0x1e, 0xca ; rdsspq rdx - a NOP unless CET is supported & enabled. + %endif test rdx, rdx jz .regular_restore %ifdef RT_STRICT From 3f0dd62517a8fb8cad0f4d8acb598a07375a60ea Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Thu, 13 Aug 2026 02:15:03 +0000 Subject: [PATCH 124/176] VMM/HM,CPUM: Enhanced the IBPB flushing for AMD. bugref:11138 svn:sync-xref-src-repo-rev: r174867 --- include/VBox/vmm/cpum.h | 4 +- src/VBox/VMM/VMMAll/CPUMAllCpuId.cpp | 7 +- src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp | 8 +- src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm | 239 ++++++++++++++---- src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp | 60 +++-- src/VBox/VMM/include/HMInternal.h | 60 +++-- src/VBox/VMM/include/HMInternal.mac | 9 +- 7 files changed, 287 insertions(+), 100 deletions(-) diff --git a/include/VBox/vmm/cpum.h b/include/VBox/vmm/cpum.h index 923e8d05c8f4..0c72c9d31bf9 100644 --- a/include/VBox/vmm/cpum.h +++ b/include/VBox/vmm/cpum.h @@ -486,6 +486,8 @@ typedef struct CPUMFEATURESX86 uint32_t fClFlushOpt : 1; /** Supports IA32_PRED_CMD.IBPB. */ uint32_t fIbpb : 1; + /** IA32_PRED_CMD.IBPB doest not clear return target predictions. */ + uint32_t fIbpbNoRet : 1; /** Supports the IA32_SPEC_CTRL MSR (summary of the next). */ uint32_t fSpecCtrlMsr : 1; /** Supports IA32_SPEC_CTRL.IBRS. */ @@ -662,7 +664,7 @@ typedef struct CPUMFEATURESX86 /** @} */ /** Alignment padding / reserved for future use. */ - uint32_t fPadding0 : 17; + uint32_t fPadding0 : 16; uint32_t auPadding[3]; /** @name SVM diff --git a/src/VBox/VMM/VMMAll/CPUMAllCpuId.cpp b/src/VBox/VMM/VMMAll/CPUMAllCpuId.cpp index 8a2b54e6a443..b91fbe814997 100644 --- a/src/VBox/VMM/VMMAll/CPUMAllCpuId.cpp +++ b/src/VBox/VMM/VMMAll/CPUMAllCpuId.cpp @@ -1,4 +1,4 @@ -/* $Id: CPUMAllCpuId.cpp 112779 2026-02-01 19:19:54Z knut.osmundsen@oracle.com $ */ +/* $Id: CPUMAllCpuId.cpp 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ */ /** @file * CPUM - CPU ID part, common bits. */ @@ -1796,6 +1796,10 @@ VMMDECL(int) CPUMCpuIdExplodeFeaturesX86(PCCPUMCPUIDLEAF paLeaves, uint32_t cLea pFeatures->fArchCap = RT_BOOL(pSxfLeaf0->uEdx & X86_CPUID_STEXT_FEATURE_EDX_ARCHCAP); pFeatures->fCoreCap = RT_BOOL(pSxfLeaf0->uEdx & X86_CPUID_STEXT_FEATURE_EDX_CORECAP); pFeatures->fMdsClear = RT_BOOL(pSxfLeaf0->uEdx & X86_CPUID_STEXT_FEATURE_EDX_MD_CLEAR); + + pFeatures->fIbpbNoRet = pFeatures->fIbpb + && ( pFeatures->enmCpuVendor == CPUMCPUVENDOR_AMD + || pFeatures->enmCpuVendor == CPUMCPUVENDOR_HYGON); } PCCPUMCPUIDLEAF const pSxfLeaf2 = cpumCpuIdFindLeafEx(paLeaves, cLeaves, 7, 2); if (pSxfLeaf2) @@ -1863,6 +1867,7 @@ VMMDECL(int) CPUMCpuIdExplodeFeaturesX86(PCCPUMCPUIDLEAF paLeaves, uint32_t cLea pFeatures->fStibp |= RT_BOOL(pExtLeaf8->uEbx & X86_CPUID_AMD_EFEID_EBX_STIBP); pFeatures->fSsbd |= RT_BOOL(pExtLeaf8->uEbx & X86_CPUID_AMD_EFEID_EBX_SPEC_CTRL_SSBD); pFeatures->fPsfd |= RT_BOOL(pExtLeaf8->uEbx & X86_CPUID_AMD_EFEID_EBX_PSFD); + pFeatures->fIbpbNoRet = pFeatures->fIbpb && !RT_BOOL(pExtLeaf8->uEbx & X86_CPUID_AMD_EFEID_EBX_IBPB_RET); } PCCPUMCPUIDLEAF pExtLeaf21 = cpumCpuIdFindLeaf(paLeaves, cLeaves, 0x80000021); diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp index 7ab41223acf1..39ab69414aec 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0-x86.cpp 114490 2026-06-22 17:29:00Z knut.osmundsen@oracle.com $ */ +/* $Id: HMR0-x86.cpp 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ */ /** @file * Hardware Assisted Virtualization Manager (HM) - Host Context Ring-0. */ @@ -1363,6 +1363,12 @@ VMMR0_INT_DECL(int) HMR0InitVM(PVMCC pVM) fWorldSwitcher |= HM_WSF_IBPB_EXIT; if (pVM->hm.s.fIbpbOnVmEntry) fWorldSwitcher |= HM_WSF_IBPB_ENTRY; + + /* If IBPB doesn't clear the RSBs, do so manually unless shadow stack is enabled. */ + if ( (fWorldSwitcher & (HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT)) + && g_CpumHostFeatures.s.fIbpbNoRet + && !hmR0IsShadowStackEnabled()) + fWorldSwitcher |= HM_WSF_IBPB_MAN_RET; } if (g_CpumHostFeatures.s.fFlushCmd) { diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm index 6ef3c58f757c..df71b4c890a0 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm @@ -1,4 +1,4 @@ -; $Id: HMR0A-x86.asm 115017 2026-08-12 23:41:03Z knut.osmundsen@oracle.com $ +; $Id: HMR0A-x86.asm 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ ;; @file ; HM - Ring-0 VMX, SVM world-switch and helper routines. ; @@ -501,6 +501,80 @@ BEGINPROC hmR0MdsClear ENDPROC hmR0MdsClear +;; +; Helper for checking whether shadow stacks are enabled. +; +BEGINPROC hmR0IsShadowStackEnabled + SEH64_END_PROLOGUE + + ; + ; The rdsspq instruction is a NOP unless CET is supported & enabled. + ; So, we do 'rdsspq rax' on a zero rax register and check if the result is non-zero. + ; + xor eax, eax +%ifdef __NASM__ + rdsspq rax +%else + db 0xf3, 0x48, 0x0f, 0x1e, 0xc8 ; rdsspq rdx - a NOP unless CET is supported & enabled. +%endif + test rax, rax + setnz al + movzx eax, al + ret +ENDPROC hmR0IsShadowStackEnabled + + +; +; Stuffing the return stack buffers (RSB), aka. return target buffers (RTB). +; +; The assumption here is that the RSB operates as a stack and has 32 or +; fewer entries. So, to stuff all of them, we do 32 calls without any returns. +; + +;; Do one (changes RSP). +%macro STUFF_ONE_RET_BUFFER 0 + call %%call_target + int3 +%%call_target: +%endmacro + +;; Do eight and adjust RSP afterwards. +%macro STUFF_EIGHT_RET_BUFFERS 0 + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + add xSP, xCB * 8 +%endmacro + +;; Do all 32. +%macro STUFF_ALL_RET_BUFFERS 0 + STUFF_EIGHT_RET_BUFFERS + STUFF_EIGHT_RET_BUFFERS + STUFF_EIGHT_RET_BUFFERS + STUFF_EIGHT_RET_BUFFERS +%endmacro + + +;; +; Stuffs the return target prediction buffers. +; +; @clobbers Nothing (only tramples the stack). +; @note This is not safe to call with shadow stack enabled! +; +ALIGNCODE(64) +BEGINPROC hmR0StuffReturnTargetPredictionBuffers + SEH64_END_PROLOGUE + STUFF_ALL_RET_BUFFERS + ret +ENDPROC hmR0StuffReturnTargetPredictionBuffers + + + ;; ; Common restore logic for success and error paths. We duplicate this because we ; don't want to waste writing the VINF_SUCCESS return value to the stack in the @@ -1102,7 +1176,7 @@ hmR0VmxStartVmSseTemplate 2,_SseXSave ; ; @param 1 The suffix of the variation. ; @param 2 fLoadSaveGuestXcr0 value -; @param 3 The HM_WSF_IBPB_ENTRY + HM_WSF_IBPB_EXIT + HM_WSF_SPEC_CTRL value. +; @param 3 The HM_WSF_IBPB_ENTRY + HM_WSF_IBPB_EXIT + HM_WSF_SPEC_CTRL + HM_WSF_IBPB_MAN_RET value. ; @param 4 The SSE saving/restoring: 0 to do nothing, 1 to do it manually, 2 to use xsave/xrstor. ; Drivers shouldn't use AVX registers without saving+loading: ; https://msdn.microsoft.com/en-us/library/windows/hardware/ff545910%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 @@ -1213,7 +1287,7 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) jne .failure_return mov eax, [rsi + GVMCPU.hmr0 + HMR0PERVCPU.fWorldSwitcher] - and eax, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_SPEC_CTRL + and eax, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_MAN_RET cmp eax, (%3) mov eax, VERR_SVM_VMRUN_PRECOND_1 jne .failure_return @@ -1323,6 +1397,9 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) %if (%3) & HM_WSF_IBPB_ENTRY ; Fight spectre (trashes rax, rdx and rcx). + %if (%3) & HM_WSF_IBPB_ENTRY + call NAME(hmR0StuffReturnTargetPredictionBuffers) + %endif mov ecx, MSR_IA32_PRED_CMD mov eax, MSR_IA32_PRED_CMD_F_IBPB xor edx, edx @@ -1423,6 +1500,13 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) mov eax, MSR_IA32_PRED_CMD_F_IBPB xor edx, edx wrmsr + %if (%3) & HM_WSF_IBPB_ENTRY + %if 0 + call NAME(hmR0StuffReturnTargetPredictionBuffers) + %else + STUFF_EIGHT_RET_BUFFERS + %endif + %endif %endif %if %2 != 0 @@ -1528,57 +1612,108 @@ ENDPROC RT_CONCAT(hmR0SvmVmRun,%1) ; ; Instantiate the hmR0SvmVmRun various variations. ; -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl, 0, 0, 0 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl, 1, 0, 0 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl, 0, HM_WSF_IBPB_ENTRY, 0 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl, 1, HM_WSF_IBPB_ENTRY, 0 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl, 0, HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl, 1, HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl, 0, HM_WSF_SPEC_CTRL, 0 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl, 1, HM_WSF_SPEC_CTRL, 0 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet, 0, 0, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet, 1, 0, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet, 0, HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet, 1, HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet, 0, HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet, 1, HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet, 0, HM_WSF_SPEC_CTRL, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet, 1, HM_WSF_SPEC_CTRL, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 + +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 %ifdef VBOX_WITH_KERNEL_USING_XMM -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SseManual, 0, 0, 1 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SseManual, 1, 0, 1 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SseManual, 0, HM_WSF_IBPB_ENTRY, 1 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SseManual, 1, HM_WSF_IBPB_ENTRY, 1 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SseManual, 0, HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SseManual, 1, HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SseManual, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SseManual, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SseManual, 0, HM_WSF_SPEC_CTRL, 1 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SseManual, 1, HM_WSF_SPEC_CTRL, 1 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 - -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SseXSave, 0, 0, 2 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SseXSave, 1, 0, 2 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SseXSave, 0, HM_WSF_IBPB_ENTRY, 2 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SseXSave, 1, HM_WSF_IBPB_ENTRY, 2 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SseXSave, 0, HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SseXSave, 1, HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SseXSave, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SseXSave, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SseXSave, 0, HM_WSF_SPEC_CTRL, 2 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SseXSave, 1, HM_WSF_SPEC_CTRL, 2 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseManual, 0, 0, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseManual, 1, 0, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseManual, 0, HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseManual, 1, HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseManual, 0, HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseManual, 1, HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseManual, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseManual, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseManual, 0, HM_WSF_SPEC_CTRL, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseManual, 1, HM_WSF_SPEC_CTRL, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 + +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 + +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 0, 0, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 1, 0, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_SPEC_CTRL, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_SPEC_CTRL, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 + +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 %endif MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp index 8b546b6bb965..80c250c15fe7 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0SVM-x86.cpp 114522 2026-06-25 09:32:47Z alexander.eichner@oracle.com $ */ +/* $Id: HMR0SVM-x86.cpp 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ */ /** @file * HM SVM (AMD-V) - Host Context Ring-0. */ @@ -714,27 +714,45 @@ static void hmR0SvmUpdateVmRunFunction(PVMCPUCC pVCpu) { static const struct CLANGWORKAROUND { PFNHMSVMVMRUN pfn; } s_aHmR0SvmVmRunFunctions[] = { - { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet }, + + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet }, }; - uintptr_t const idx = (pVCpu->hmr0.s.fLoadSaveGuestXcr0 ? 1 : 0) - | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_ENTRY ? 2 : 0) - | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_EXIT ? 4 : 0) - | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_SPEC_CTRL ? 8 : 0); + uintptr_t const idx = (pVCpu->hmr0.s.fLoadSaveGuestXcr0 ? 1 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_ENTRY ? 2 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_EXIT ? 4 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_SPEC_CTRL ? 8 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_MAN_RET ? 16 : 0); PFNHMSVMVMRUN const pfnVMRun = s_aHmR0SvmVmRunFunctions[idx].pfn; if (pVCpu->hmr0.s.svm.pfnVMRun != pfnVMRun) pVCpu->hmr0.s.svm.pfnVMRun = pfnVMRun; diff --git a/src/VBox/VMM/include/HMInternal.h b/src/VBox/VMM/include/HMInternal.h index f5d40a6aab74..8dcdc50f53d0 100644 --- a/src/VBox/VMM/include/HMInternal.h +++ b/src/VBox/VMM/include/HMInternal.h @@ -1,4 +1,4 @@ -/* $Id: HMInternal.h 114490 2026-06-22 17:29:00Z knut.osmundsen@oracle.com $ */ +/* $Id: HMInternal.h 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ */ /** @file * HM - Internal header file. */ @@ -1182,15 +1182,17 @@ AssertCompileMemberAlignment(HMR0PERVCPU, vmx.RestoreHost, 8); #define HM_WSF_IBPB_EXIT RT_BIT_32(0) /** Touch IA32_PRED_CMD.IBPB on VM entry. */ #define HM_WSF_IBPB_ENTRY RT_BIT_32(1) +/** AMD: IA32_PRED_CMD.IBPB require manual return target prediction clearing. */ +#define HM_WSF_IBPB_MAN_RET RT_BIT_32(2) /** Touch IA32_FLUSH_CMD.L1D on VM entry. */ -#define HM_WSF_L1D_ENTRY RT_BIT_32(2) +#define HM_WSF_L1D_ENTRY RT_BIT_32(3) /** Flush MDS buffers on VM entry. */ -#define HM_WSF_MDS_ENTRY RT_BIT_32(3) +#define HM_WSF_MDS_ENTRY RT_BIT_32(4) /** MSR_IA32_SPEC_CTRL needs to be replaced upon entry and exit. * Save host value on entry, load guest value, run guest, save guest value on * exit and restore the host value. * @todo may not reliable for VT-x/Intel. */ -#define HM_WSF_SPEC_CTRL RT_BIT_32(4) +#define HM_WSF_SPEC_CTRL RT_BIT_32(5) /** Touch IA32_FLUSH_CMD.L1D on VM scheduling. */ #define HM_WSF_L1D_SCHED RT_BIT_32(16) @@ -1223,6 +1225,7 @@ VMMR0_INT_DECL(void) hmR0DumpDescriptor(PCX86DESCHC pDesc, RTSEL Sel, con # endif DECLASM(void) hmR0MdsClear(void); +DECLASM(bool) hmR0IsShadowStackEnabled(void); #endif /* IN_RING0 */ @@ -1246,22 +1249,39 @@ VMM_INT_DECL(int) hmEmulateSvmMovTpr(PVMCC pVM, PVMCPUCC pVCpu); * * @{ */ -DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); + +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); /** @} */ /** @} */ diff --git a/src/VBox/VMM/include/HMInternal.mac b/src/VBox/VMM/include/HMInternal.mac index a651083a5ac0..0078463d0cf0 100644 --- a/src/VBox/VMM/include/HMInternal.mac +++ b/src/VBox/VMM/include/HMInternal.mac @@ -1,4 +1,4 @@ -;$Id: HMInternal.mac 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +;$Id: HMInternal.mac 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ ;; @file ; HM - Internal header file. ; @@ -273,7 +273,8 @@ endstruc %define HM_WSF_IBPB_EXIT RT_BIT_32(0) %define HM_WSF_IBPB_ENTRY RT_BIT_32(1) -%define HM_WSF_L1D_ENTRY RT_BIT_32(2) -%define HM_WSF_MDS_ENTRY RT_BIT_32(3) -%define HM_WSF_SPEC_CTRL RT_BIT_32(4) +%define HM_WSF_IBPB_MAN_RET RT_BIT_32(2) +%define HM_WSF_L1D_ENTRY RT_BIT_32(3) +%define HM_WSF_MDS_ENTRY RT_BIT_32(4) +%define HM_WSF_SPEC_CTRL RT_BIT_32(5) From 925aa2f863482ddd2b8567834a2130ed60a0018d Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Thu, 13 Aug 2026 02:23:03 +0000 Subject: [PATCH 125/176] VMM/HM,CPUM: Enhanced the IBPB flushing for AMD. [fix] bugref:11138 svn:sync-xref-src-repo-rev: r174869 --- src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm index df71b4c890a0..3687d52118bd 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm @@ -1,4 +1,4 @@ -; $Id: HMR0A-x86.asm 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ +; $Id: HMR0A-x86.asm 115028 2026-08-13 02:23:03Z knut.osmundsen@oracle.com $ ;; @file ; HM - Ring-0 VMX, SVM world-switch and helper routines. ; @@ -1397,7 +1397,7 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) %if (%3) & HM_WSF_IBPB_ENTRY ; Fight spectre (trashes rax, rdx and rcx). - %if (%3) & HM_WSF_IBPB_ENTRY + %if (%3) & HM_WSF_IBPB_MAN_RET call NAME(hmR0StuffReturnTargetPredictionBuffers) %endif mov ecx, MSR_IA32_PRED_CMD @@ -1500,7 +1500,7 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) mov eax, MSR_IA32_PRED_CMD_F_IBPB xor edx, edx wrmsr - %if (%3) & HM_WSF_IBPB_ENTRY + %if (%3) & HM_WSF_IBPB_MAN_RET %if 0 call NAME(hmR0StuffReturnTargetPredictionBuffers) %else From bfdbe720b57263fef069ac1bcccc4ae971415b5a Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Thu, 13 Aug 2026 02:46:39 +0000 Subject: [PATCH 126/176] VMM/HM,CPUM: Enhanced the IBPB flushing for Intel. bugref:11138 svn:sync-xref-src-repo-rev: r174871 --- src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp | 13 +- src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm | 113 ++++++++++++------ src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp | 101 ++++++++++------ src/VBox/VMM/include/HMInternal.h | 106 ++++++++++------ src/VBox/VMM/include/HMInternal.mac | 9 +- 5 files changed, 227 insertions(+), 115 deletions(-) diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp index 39ab69414aec..76ac8981ad13 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0-x86.cpp 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ */ +/* $Id: HMR0-x86.cpp 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ */ /** @file * Hardware Assisted Virtualization Manager (HM) - Host Context Ring-0. */ @@ -1365,10 +1365,13 @@ VMMR0_INT_DECL(int) HMR0InitVM(PVMCC pVM) fWorldSwitcher |= HM_WSF_IBPB_ENTRY; /* If IBPB doesn't clear the RSBs, do so manually unless shadow stack is enabled. */ - if ( (fWorldSwitcher & (HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT)) - && g_CpumHostFeatures.s.fIbpbNoRet - && !hmR0IsShadowStackEnabled()) - fWorldSwitcher |= HM_WSF_IBPB_MAN_RET; + if (fWorldSwitcher & (HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT)) + { + if (g_CpumHostFeatures.s.fIbpbNoRet && !hmR0IsShadowStackEnabled()) + fWorldSwitcher |= HM_WSF_IBPB_MAN_RET; + if (!g_CpumHostFeatures.s.fArchPbrsbNo && !hmR0IsShadowStackEnabled()) + fWorldSwitcher |= HM_WSF_IBPB_PBRSB; + } } if (g_CpumHostFeatures.s.fFlushCmd) { diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm index 3687d52118bd..1fb8fd545313 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm @@ -1,4 +1,4 @@ -; $Id: HMR0A-x86.asm 115028 2026-08-13 02:23:03Z knut.osmundsen@oracle.com $ +; $Id: HMR0A-x86.asm 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ ;; @file ; HM - Ring-0 VMX, SVM world-switch and helper routines. ; @@ -582,7 +582,7 @@ ENDPROC hmR0StuffReturnTargetPredictionBuffers ; ; @param 1 Zero if regular return, non-zero if error return. Controls label emission. ; @param 2 fLoadSaveGuestXcr0 value -; @param 3 The (HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY) + HM_WSF_IBPB_EXIT value. +; @param 3 The (HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY) + HM_WSF_IBPB_EXIT + HM_WSF_IBPB_PBRSB value. ; The entry values are either all set or not at all, as we're too lazy to flesh out all the variants. ; @param 4 The SSE saving/restoring: 0 to do nothing, 1 to do it manually, 2 to use xsave/xrstor. ; @@ -659,6 +659,10 @@ ENDPROC hmR0StuffReturnTargetPredictionBuffers mov eax, MSR_IA32_PRED_CMD_F_IBPB xor edx, edx wrmsr + %if (%3) & HM_WSF_IBPB_PBRSB + STUFF_ONE_RET_BUFFER + add rsp, xCB + %endif %endif %endif @@ -702,7 +706,7 @@ ENDPROC hmR0StuffReturnTargetPredictionBuffers ; ; @param 1 The suffix of the variation. ; @param 2 fLoadSaveGuestXcr0 value -; @param 3 The HM_WSF_IBPB_ENTRY + HM_WSF_IBPB_EXIT value. +; @param 3 The HM_WSF_IBPB_ENTRY + HM_WSF_IBPB_EXIT + HM_WSF_IBPB_PBRSB value. ; @param 4 The SSE saving/restoring: 0 to do nothing, 1 to do it manually, 2 to use xsave/xrstor. ; Drivers shouldn't use AVX registers without saving+loading: ; https://msdn.microsoft.com/en-us/library/windows/hardware/ff545910%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 @@ -811,7 +815,7 @@ BEGINPROC RT_CONCAT(hmR0VmxStartVm,%1) jne NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).precond_failure_return) mov eax, [rsi + GVMCPU.hmr0 + HMR0PERVCPU.fWorldSwitcher] - and eax, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT ; | HM_WSF_SPEC_CTRL + and eax, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB ; | HM_WSF_SPEC_CTRL cmp eax, (%3) mov eax, VERR_VMX_STARTVM_PRECOND_1 jne NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).precond_failure_return) @@ -941,6 +945,10 @@ BEGINPROC RT_CONCAT(hmR0VmxStartVm,%1) %if (%3) & HM_WSF_IBPB_ENTRY ; Indirect branch barrier. mov ecx, MSR_IA32_PRED_CMD wrmsr + %if (%3) & HM_WSF_IBPB_PBRSB + STUFF_ONE_RET_BUFFER + add rsp, xCB + %endif %endif %if (%3) & HM_WSF_L1D_ENTRY ; Level 1 data cache flush. mov ecx, MSR_IA32_FLUSH_CMD @@ -1130,38 +1138,71 @@ ENDPROC RT_CONCAT(hmR0VmxStartVm,%1) %endmacro ; hmR0VmxStartVmTemplate %macro hmR0VmxStartVmSseTemplate 1-2 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 0, 0 | 0 | 0 | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 1, 0 | 0 | 0 | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 0, 0 | 0 | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 1, 0 | 0 | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, 0 | 0 | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, 0 | 0 | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, 0 | 0 | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, 0 | 0 | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 + +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, 0 | 0 | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, 0 | 0 | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, 0 | 0 | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, 0 | 0 | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 %endmacro hmR0VmxStartVmSseTemplate 0 diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp index a56faa6e6b63..9ffed9c228d8 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0VMX-x86.cpp 114828 2026-07-31 09:19:37Z alexander.eichner@oracle.com $ */ +/* $Id: HMR0VMX-x86.cpp 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ */ /** @file * HM VMX (Intel VT-x) - Host Context Ring-0. */ @@ -499,44 +499,77 @@ static void hmR0VmxUpdateStartVmFunction(PVMCPUCC pVCpu) { static const struct CLANGWORKAROUND { PFNHMVMXSTARTVM pfn; } s_aHmR0VmxStartVmFunctions[] = { - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, }; uintptr_t const idx = (pVCpu->hmr0.s.fLoadSaveGuestXcr0 ? 1 : 0) | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_ENTRY ? 2 : 0) | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_L1D_ENTRY ? 4 : 0) | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_MDS_ENTRY ? 8 : 0) - | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_EXIT ? 16 : 0); + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_EXIT ? 16 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_PBRSB ? 32 : 0); PFNHMVMXSTARTVM const pfnStartVm = s_aHmR0VmxStartVmFunctions[idx].pfn; if (pVCpu->hmr0.s.vmx.pfnStartVm != pfnStartVm) pVCpu->hmr0.s.vmx.pfnStartVm = pfnStartVm; diff --git a/src/VBox/VMM/include/HMInternal.h b/src/VBox/VMM/include/HMInternal.h index 8dcdc50f53d0..2cc2712dbdbc 100644 --- a/src/VBox/VMM/include/HMInternal.h +++ b/src/VBox/VMM/include/HMInternal.h @@ -1,4 +1,4 @@ -/* $Id: HMInternal.h 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ */ +/* $Id: HMInternal.h 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ */ /** @file * HM - Internal header file. */ @@ -1184,15 +1184,17 @@ AssertCompileMemberAlignment(HMR0PERVCPU, vmx.RestoreHost, 8); #define HM_WSF_IBPB_ENTRY RT_BIT_32(1) /** AMD: IA32_PRED_CMD.IBPB require manual return target prediction clearing. */ #define HM_WSF_IBPB_MAN_RET RT_BIT_32(2) +/** CPU suffers from the PBRSB problem. */ +#define HM_WSF_IBPB_PBRSB RT_BIT_32(3) /** Touch IA32_FLUSH_CMD.L1D on VM entry. */ -#define HM_WSF_L1D_ENTRY RT_BIT_32(3) +#define HM_WSF_L1D_ENTRY RT_BIT_32(4) /** Flush MDS buffers on VM entry. */ -#define HM_WSF_MDS_ENTRY RT_BIT_32(4) +#define HM_WSF_MDS_ENTRY RT_BIT_32(5) /** MSR_IA32_SPEC_CTRL needs to be replaced upon entry and exit. * Save host value on entry, load guest value, run guest, save guest value on * exit and restore the host value. * @todo may not reliable for VT-x/Intel. */ -#define HM_WSF_SPEC_CTRL RT_BIT_32(5) +#define HM_WSF_SPEC_CTRL RT_BIT_32(6) /** Touch IA32_FLUSH_CMD.L1D on VM scheduling. */ #define HM_WSF_L1D_SCHED RT_BIT_32(16) @@ -1324,38 +1326,70 @@ DECLASM(int) VMXRestoreHostState(uint32_t fRestoreHostFlags, PVMX * * @{ */ -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); /** @} */ /** @} */ diff --git a/src/VBox/VMM/include/HMInternal.mac b/src/VBox/VMM/include/HMInternal.mac index 0078463d0cf0..e8b3994bb1f5 100644 --- a/src/VBox/VMM/include/HMInternal.mac +++ b/src/VBox/VMM/include/HMInternal.mac @@ -1,4 +1,4 @@ -;$Id: HMInternal.mac 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ +;$Id: HMInternal.mac 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ ;; @file ; HM - Internal header file. ; @@ -274,7 +274,8 @@ endstruc %define HM_WSF_IBPB_EXIT RT_BIT_32(0) %define HM_WSF_IBPB_ENTRY RT_BIT_32(1) %define HM_WSF_IBPB_MAN_RET RT_BIT_32(2) -%define HM_WSF_L1D_ENTRY RT_BIT_32(3) -%define HM_WSF_MDS_ENTRY RT_BIT_32(4) -%define HM_WSF_SPEC_CTRL RT_BIT_32(5) +%define HM_WSF_IBPB_PBRSB RT_BIT_32(3) +%define HM_WSF_L1D_ENTRY RT_BIT_32(4) +%define HM_WSF_MDS_ENTRY RT_BIT_32(5) +%define HM_WSF_SPEC_CTRL RT_BIT_32(6) From c8a31de05bfc63b5f49b41e0ebb2c853fb96f252 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Thu, 13 Aug 2026 07:41:47 +0000 Subject: [PATCH 127/176] testmanager: pylint tweaks svn:sync-xref-src-repo-rev: r174873 --- .../ValidationKit/testmanager/webui/wuicontentbase.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py b/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py index fc2b09bf5ef4..77573135bdfa 100644 --- a/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py +++ b/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# $Id: wuicontentbase.py 113947 2026-04-17 23:13:52Z knut.osmundsen@oracle.com $ +# $Id: wuicontentbase.py 115032 2026-08-13 07:41:47Z knut.osmundsen@oracle.com $ """ Test Manager Web-UI - Content Base Classes. @@ -36,7 +36,7 @@ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 """ -__version__ = "$Revision: 113947 $" +__version__ = "$Revision: 115032 $" # Standard python imports. @@ -85,7 +85,7 @@ class WuiLinkBase(WuiHtmlBase): # pylint: disable=too-few-public-methods For passing links from WuiListContentBase._formatListEntry. """ - def __init__(self, sName, sUrlBase, dParams = None, # pylint: disable=too-many-arguments + def __init__(self, sName, sUrlBase, dParams = None, # pylint: disable=too-many-arguments,too-many-positional-arguments sConfirm = None, sTitle = None, sFragmentId = None, fBracketed = True, sExtraAttrs = '', sImgFile = None, sImgClass = 'icon'): WuiHtmlBase.__init__(self); @@ -211,7 +211,7 @@ def __init__(self, sUrlBase, dParams = None, sName = None, sTitle = None, class WuiAdminLink(WuiTmLink): # pylint: disable=too-few-public-methods """ Local link to the test manager's admin portion. """ - def __init__(self, sName, sAction, tsEffectiveDate = None, # pylint: disable=too-many-arguments + def __init__(self, sName, sAction, tsEffectiveDate = None, # pylint: disable=too-many-arguments,too-many-positional-arguments dParams = None, sConfirm = None, sTitle = None, sFragmentId = None, fBracketed = True, sImgFile = None): from testmanager.webui.wuiadmin import WuiAdmin; if not dParams: From a134faeca4ad80a54470a3a401621d8b37ac19a3 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 13 Aug 2026 09:00:46 +0000 Subject: [PATCH 128/176] NetworkServices/tstVBoxNetDhcpd: Use RTMAC / RTmac for handling / printing MACs. svn:sync-xref-src-repo-rev: r174874 --- .../Dhcpd/testcase/tstVBoxNetDhcpd.cpp | 96 +++++++++---------- 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp index 665e070a30e9..4d8476233246 100644 --- a/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp +++ b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVBoxNetDhcpd.cpp 114995 2026-08-12 09:59:45Z andreas.loeffler@oracle.com $ */ +/* $Id: tstVBoxNetDhcpd.cpp 115033 2026-08-13 09:00:46Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetDHCP in-process testcase. */ @@ -400,7 +400,7 @@ static int tstDhcp4AppendOptU32(PTSTPKT pPkt, uint8_t uOpt, uint32_t uValueBe) static int tstBuildDhcp4(PTSTPKT pDhcp, uint8_t uMsgType, uint32_t uXid, - const uint8_t abMac[6], + PCRTMAC pMac, uint32_t uRequestedIpBe, uint32_t uServerIdBe, bool fBadCookie) @@ -416,7 +416,7 @@ static int tstBuildDhcp4(PTSTPKT pDhcp, pHdr->uXid = tstH2N32(uXid); pHdr->uFlags = tstH2N16(0x8000); pHdr->uCookie = fBadCookie ? tstH2N32(0xdeadbeef) : tstH2N32(0x63825363); - memcpy(pHdr->abChAddr, abMac, 6); + memcpy(pHdr->abChAddr, pMac, sizeof(*pMac)); uint8_t b = uMsgType; int rc = tstDhcp4AppendOpt(pDhcp, 53, &b, sizeof(b)); @@ -425,7 +425,7 @@ static int tstBuildDhcp4(PTSTPKT pDhcp, uint8_t abClientId[7]; abClientId[0] = 1; - memcpy(&abClientId[1], abMac, 6); + memcpy(&abClientId[1], pMac, sizeof(*pMac)); rc = tstDhcp4AppendOpt(pDhcp, 61, abClientId, sizeof(abClientId)); if (RT_FAILURE(rc)) return rc; @@ -468,7 +468,7 @@ static int tstBuildDhcp4(PTSTPKT pDhcp, } static int tstBuildUdp4Frame(PTSTPKT pFrame, - const uint8_t abSrcMac[6], + PCRTMAC pSrcMac, uint32_t uSrcIpBe, uint32_t uDstIpBe, uint16_t uSrcPort, @@ -481,7 +481,7 @@ static int tstBuildUdp4Frame(PTSTPKT pFrame, TSTETHHDR Eth; memcpy(Eth.abDst, s_abBcast, sizeof(Eth.abDst)); - memcpy(Eth.abSrc, abSrcMac, sizeof(Eth.abSrc)); + memcpy(Eth.abSrc, pSrcMac, sizeof(Eth.abSrc)); Eth.uType = tstH2N16(0x0800); int rc = tstPktAppend(pFrame, &Eth, sizeof(Eth)); @@ -517,7 +517,7 @@ static int tstBuildUdp4Frame(PTSTPKT pFrame, return tstPktAppend(pFrame, pPayload->ab, pPayload->cb); } -static int tstBuildDhcp6SolicitFrame(PTSTPKT pFrame, const uint8_t abSrcMac[6]) +static int tstBuildDhcp6SolicitFrame(PTSTPKT pFrame, PCRTMAC pSrcMac) { static const uint8_t s_abDstMac[6] = { 0x33, 0x33, 0x00, 0x01, 0x00, 0x02 }; static const uint8_t s_abSrcIp[16] = { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, @@ -529,7 +529,7 @@ static int tstBuildDhcp6SolicitFrame(PTSTPKT pFrame, const uint8_t abSrcMac[6]) TSTETHHDR Eth; memcpy(Eth.abDst, s_abDstMac, sizeof(Eth.abDst)); - memcpy(Eth.abSrc, abSrcMac, sizeof(Eth.abSrc)); + memcpy(Eth.abSrc, pSrcMac, sizeof(Eth.abSrc)); Eth.uType = tstH2N16(0x86dd); int rc = tstPktAppend(pFrame, &Eth, sizeof(Eth)); @@ -885,7 +885,7 @@ static bool tstClientWaitForDhcp6Reply(PTSTCLIENT pClient, uint32_t cMs) } static int tstDhcp4DiscoverOnce(PTSTCLIENT pClient, - const uint8_t abMac[6], + PCRTMAC pMac, uint32_t uXid, PTSTDHCP4REPLY pOffer, uint32_t cMs) @@ -893,11 +893,11 @@ static int tstDhcp4DiscoverOnce(PTSTCLIENT pClient, TSTPKT Dhcp; TSTPKT Frame; - int rc = tstBuildDhcp4(&Dhcp, DHCP4_DISCOVER, uXid, abMac, 0, 0, false); + int rc = tstBuildDhcp4(&Dhcp, DHCP4_DISCOVER, uXid, pMac, 0, 0, false); if (RT_FAILURE(rc)) return rc; - rc = tstBuildUdp4Frame(&Frame, abMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); + rc = tstBuildUdp4Frame(&Frame, pMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); if (RT_FAILURE(rc)) return rc; @@ -908,33 +908,31 @@ static int tstDhcp4DiscoverOnce(PTSTCLIENT pClient, return tstClientWaitForDhcp4(pClient, uXid, DHCP4_OFFER, pOffer, cMs) ? VINF_SUCCESS : VERR_TIMEOUT; } -static bool tstDhcp4Discover(PTSTCLIENT pClient, const uint8_t abMac[6], PTSTDHCP4REPLY pOffer) +static bool tstDhcp4Discover(PTSTCLIENT pClient, PCRTMAC pMac, PTSTDHCP4REPLY pOffer) { /* Retransmissions belong to one DHCP transaction, so delayed replies from an earlier attempt must remain acceptable on a loaded testbox. */ uint32_t const uXid = RTRandU32(); for (unsigned i = 0; i < 8; i++) { - int const rc = tstDhcp4DiscoverOnce(pClient, abMac, uXid, pOffer, 600); + int const rc = tstDhcp4DiscoverOnce(pClient, pMac, uXid, pOffer, 600); if (RT_SUCCESS(rc)) return true; if (rc != VERR_TIMEOUT) { - RTTestFailed(g_hTest, "Sending DHCPDISCOVER for %02x:%02x:%02x:%02x:%02x:%02x (xid %#RX32) failed: %Rrc", - abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], uXid, rc); + RTTestFailed(g_hTest, "Sending DHCPDISCOVER for %RTmac (xid %#RX32) failed: %Rrc", pMac, uXid, rc); return false; } RTThreadSleep(100); } - RTTestFailed(g_hTest, "No DHCPOFFER for %02x:%02x:%02x:%02x:%02x:%02x (xid %#RX32) after 8 attempts", - abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], uXid); + RTTestFailed(g_hTest, "No DHCPOFFER for %RTmac (xid %#RX32) after 8 attempts", pMac, uXid); RTTestFailureDetails(g_hTest, "client receive status: %Rrc; queued frames: %RU32\n", ASMAtomicReadS32(&pClient->rcThread), tstClientQueuedFrameCount(pClient)); return false; } static bool tstDhcp4Request(PTSTCLIENT pClient, - const uint8_t abMac[6], + PCRTMAC pMac, const TSTDHCP4REPLY *pOffer, PTSTDHCP4REPLY pAck) { @@ -945,38 +943,35 @@ static bool tstDhcp4Request(PTSTCLIENT pClient, int rc = tstBuildDhcp4(&Dhcp, DHCP4_REQUEST, uXid, - abMac, + pMac, pOffer->uYiAddrBe, pOffer->uServerIdBe, false); if (RT_FAILURE(rc)) { - RTTestFailed(g_hTest, "Building DHCPREQUEST for %02x:%02x:%02x:%02x:%02x:%02x failed: %Rrc", - abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], rc); + RTTestFailed(g_hTest, "Building DHCPREQUEST for %RTmac failed: %Rrc", pMac, rc); return false; } - rc = tstBuildUdp4Frame(&Frame, abMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); + rc = tstBuildUdp4Frame(&Frame, pMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); if (RT_FAILURE(rc)) { - RTTestFailed(g_hTest, "Building DHCPREQUEST frame for %02x:%02x:%02x:%02x:%02x:%02x failed: %Rrc", - abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], rc); + RTTestFailed(g_hTest, "Building DHCPREQUEST frame for %RTmac failed: %Rrc", pMac, rc); return false; } rc = tstClientSendFrame(pClient, &Frame); if (RT_FAILURE(rc)) { - RTTestFailed(g_hTest, "Sending DHCPREQUEST for %02x:%02x:%02x:%02x:%02x:%02x (xid %#RX32) failed: %Rrc", - abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], uXid, rc); + RTTestFailed(g_hTest, "Sending DHCPREQUEST for %RTmac (xid %#RX32) failed: %Rrc", pMac, uXid, rc); return false; } if (tstClientWaitForDhcp4(pClient, uXid, DHCP4_ACK, pAck, TST_DHCP_TIMEOUT_MS)) return true; - RTTestFailed(g_hTest, "No DHCPACK for %02x:%02x:%02x:%02x:%02x:%02x (xid %#RX32, requested %RTnaipv4)", - abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], uXid, pOffer->uYiAddrBe); + RTTestFailed(g_hTest, "No DHCPACK for %RTmac (xid %#RX32, requested %RTnaipv4)", + pMac, uXid, pOffer->uYiAddrBe); RTTestFailureDetails(g_hTest, "client receive status: %Rrc; queued frames: %RU32\n", ASMAtomicReadS32(&pClient->rcThread), tstClientQueuedFrameCount(pClient)); return false; @@ -993,20 +988,20 @@ static bool tstIPv4InPool(uint32_t uIpBe, uint8_t uFirst, uint8_t uLast) static bool tstSendBadCookieNoReply(PTSTCLIENT pClient) { - static const uint8_t s_abMac[6] = { 0x08, 0x00, 0x27, 0xde, 0xad, 0x01 }; + static const RTMAC s_Mac = { { 0x08, 0x00, 0x27, 0xde, 0xad, 0x01 } }; uint32_t uXid = RTRandU32(); TSTPKT Dhcp; TSTPKT Frame; - int rc = tstBuildDhcp4(&Dhcp, DHCP4_DISCOVER, uXid, s_abMac, 0, 0, true); + int rc = tstBuildDhcp4(&Dhcp, DHCP4_DISCOVER, uXid, &s_Mac, 0, 0, true); if (RT_FAILURE(rc)) { RTTestFailed(g_hTest, "Building bad-cookie DHCPDISCOVER failed: %Rrc", rc); return false; } - rc = tstBuildUdp4Frame(&Frame, s_abMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); + rc = tstBuildUdp4Frame(&Frame, &s_Mac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); if (RT_FAILURE(rc)) { RTTestFailed(g_hTest, "Building bad-cookie DHCPDISCOVER frame failed: %Rrc", rc); @@ -1037,10 +1032,10 @@ static bool tstSendBadCookieNoReply(PTSTCLIENT pClient) static bool tstSendDhcp6SolicitNoReply(PTSTCLIENT pClient) { - static const uint8_t s_abMac[6] = { 0x08, 0x00, 0x27, 0x66, 0x66, 0x66 }; + static const RTMAC s_Mac = { { 0x08, 0x00, 0x27, 0x66, 0x66, 0x66 } }; TSTPKT Frame; - int rc = tstBuildDhcp6SolicitFrame(&Frame, s_abMac); + int rc = tstBuildDhcp6SolicitFrame(&Frame, &s_Mac); if (RT_FAILURE(rc)) { RTTestFailed(g_hTest, "Building DHCPv6 SOLICIT frame failed: %Rrc", rc); @@ -1070,31 +1065,28 @@ static bool tstSendDhcp6SolicitNoReply(PTSTCLIENT pClient) } static bool tstLeaseClient(PTSTCLIENT pClient, - const uint8_t abMac[6], + PCRTMAC pMac, PTSTDHCP4REPLY pAck) { TSTDHCP4REPLY Offer; - if (!tstDhcp4Discover(pClient, abMac, &Offer)) + if (!tstDhcp4Discover(pClient, pMac, &Offer)) return false; if (!tstIPv4InPool(Offer.uYiAddrBe, 10, 12)) { - RTTestFailed(g_hTest, "DHCPOFFER for %02x:%02x:%02x:%02x:%02x:%02x contains out-of-pool address %RTnaipv4", - abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], Offer.uYiAddrBe); + RTTestFailed(g_hTest, "DHCPOFFER for %RTmac contains out-of-pool address %RTnaipv4", pMac, Offer.uYiAddrBe); return false; } if (Offer.uServerIdBe != tstIPv4(10,37,0,1)) { - RTTestFailed(g_hTest, "DHCPOFFER for %02x:%02x:%02x:%02x:%02x:%02x has server ID %RTnaipv4, expected 10.37.0.1", - abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], Offer.uServerIdBe); + RTTestFailed(g_hTest, "DHCPOFFER for %RTmac has server ID %RTnaipv4, expected 10.37.0.1", pMac, Offer.uServerIdBe); return false; } - if (!tstDhcp4Request(pClient, abMac, &Offer, pAck)) + if (!tstDhcp4Request(pClient, pMac, &Offer, pAck)) return false; if (pAck->uYiAddrBe != Offer.uYiAddrBe) { - RTTestFailed(g_hTest, "DHCPACK for %02x:%02x:%02x:%02x:%02x:%02x assigns %RTnaipv4, offered %RTnaipv4", - abMac[0], abMac[1], abMac[2], abMac[3], abMac[4], abMac[5], - pAck->uYiAddrBe, Offer.uYiAddrBe); + RTTestFailed(g_hTest, "DHCPACK for %RTmac assigns %RTnaipv4, offered %RTnaipv4", + pMac, pAck->uYiAddrBe, Offer.uYiAddrBe); return false; } return true; @@ -1163,10 +1155,10 @@ static void tstWireDhcp(void) if (!VBoxNetDhcpdTestIsRunning(pvDhcpd)) RTTestFailed(g_hTest, "VBoxNetDHCP stopped after reporting successful startup"); - static const uint8_t s_abMac1[6] = { 0x08, 0x00, 0x27, 0x12, 0x34, 0x56 }; - static const uint8_t s_abMac2[6] = { 0x08, 0x00, 0x27, 0x00, 0x00, 0x02 }; - static const uint8_t s_abMac3[6] = { 0x08, 0x00, 0x27, 0x00, 0x00, 0x03 }; - static const uint8_t s_abMac4[6] = { 0x08, 0x00, 0x27, 0x00, 0x00, 0x04 }; + static const RTMAC s_Mac1 = { { 0x08, 0x00, 0x27, 0x12, 0x34, 0x56 } }; + static const RTMAC s_Mac2 = { { 0x08, 0x00, 0x27, 0x00, 0x00, 0x02 } }; + static const RTMAC s_Mac3 = { { 0x08, 0x00, 0x27, 0x00, 0x00, 0x03 } }; + static const RTMAC s_Mac4 = { { 0x08, 0x00, 0x27, 0x00, 0x00, 0x04 } }; TSTDHCP4REPLY Ack1; TSTDHCP4REPLY Ack1Again; @@ -1181,13 +1173,13 @@ static void tstWireDhcp(void) RT_ZERO(Offer4); RTTestSub(g_hTest, "first client lease"); - bool const fAck1 = tstLeaseClient(&Client, s_abMac1, &Ack1); + bool const fAck1 = tstLeaseClient(&Client, &s_Mac1, &Ack1); RTTestSub(g_hTest, "first client lease reuse"); bool fAck1Again = false; if (fAck1) { - fAck1Again = tstLeaseClient(&Client, s_abMac1, &Ack1Again); + fAck1Again = tstLeaseClient(&Client, &s_Mac1, &Ack1Again); if (fAck1Again && Ack1Again.uYiAddrBe != Ack1.uYiAddrBe) RTTestFailed(g_hTest, "Repeated lease changed from %RTnaipv4 to %RTnaipv4", Ack1.uYiAddrBe, Ack1Again.uYiAddrBe); @@ -1202,10 +1194,10 @@ static void tstWireDhcp(void) tstSendDhcp6SolicitNoReply(&Client); RTTestSub(g_hTest, "second client lease"); - bool const fAck2 = tstLeaseClient(&Client, s_abMac2, &Ack2); + bool const fAck2 = tstLeaseClient(&Client, &s_Mac2, &Ack2); RTTestSub(g_hTest, "third client lease"); - bool const fAck3 = tstLeaseClient(&Client, s_abMac3, &Ack3); + bool const fAck3 = tstLeaseClient(&Client, &s_Mac3, &Ack3); RTTestSub(g_hTest, "unique client leases"); if (fAck1 && fAck2 && fAck3) @@ -1224,7 +1216,7 @@ static void tstWireDhcp(void) if (fAck1 && fAck2 && fAck3) { uint32_t const uXid = RTRandU32(); - int const rc = tstDhcp4DiscoverOnce(&Client, s_abMac4, uXid, &Offer4, 1000); + int const rc = tstDhcp4DiscoverOnce(&Client, &s_Mac4, uXid, &Offer4, 1000); if (RT_SUCCESS(rc)) RTTestFailed(g_hTest, "Exhausted pool offered %RTnaipv4 to fourth client (xid %#RX32)", Offer4.uYiAddrBe, uXid); From 0e4a5709dc452595188a0ffba52f4dd0229366ec Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 13 Aug 2026 11:21:59 +0000 Subject: [PATCH 129/176] NetworkServices/Dhcpd: Resvoled a @todo: Free bindings on internal database instance destruction. svn:sync-xref-src-repo-rev: r174875 --- src/VBox/NetworkServices/Dhcpd/Db.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VBox/NetworkServices/Dhcpd/Db.cpp b/src/VBox/NetworkServices/Dhcpd/Db.cpp index bd1243d89d70..934f3bde5933 100644 --- a/src/VBox/NetworkServices/Dhcpd/Db.cpp +++ b/src/VBox/NetworkServices/Dhcpd/Db.cpp @@ -1,4 +1,4 @@ -/* $Id: Db.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: Db.cpp 115034 2026-08-13 11:21:59Z andreas.loeffler@oracle.com $ */ /** @file * DHCP server - address database */ @@ -362,7 +362,8 @@ Db::Db() Db::~Db() { - /** @todo free bindings */ + for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it) + delete *it; } From e2c0abf1705cebf53370ea42a601950d71d3de91 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Thu, 13 Aug 2026 15:06:11 +0000 Subject: [PATCH 130/176] Devices/Graphics: removed obsolete code (constant buffers). bugref:10934 svn:sync-xref-src-repo-rev: r174876 --- .../Graphics/DevVGA-SVGA3d-dx-dx11.cpp | 181 +----------------- 1 file changed, 1 insertion(+), 180 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp index c4b5c9c76098..ed104acaf757 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114997 2026-08-12 15:54:46Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 115035 2026-08-13 15:06:11Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -325,10 +325,6 @@ typedef struct DXBOUNDINDEXBUFFER //uint32_t indexBufferOffset; } DXBOUNDINDEXBUFFER; -/** @todo Temporary development define. */ -#define DX_CB - -#ifdef DX_CB /* Constant buffer management: * - allocate a large dynamic buffer * - update the buffer with constant data in SetSingleConstantBuffer using MAP_NO_OVERWRITE @@ -349,7 +345,6 @@ typedef struct DXCONSTANTBUFFERSTATE /* 8 * maximum constant buffer size (4096 * 16) */ #define DX_CONSTANT_UPLOAD_BUFFER_SIZE _512K -#endif /* DX_CB */ typedef struct DXBOUNDRESOURCES /* Currently bound resources. Mirror SVGADXContextMobFormat structure. */ { @@ -360,11 +355,7 @@ typedef struct DXBOUNDRESOURCES /* Currently bound resources. Mirror SVGADXConte } inputAssembly; struct { -#ifndef DX_CB - ID3D11Buffer *constantBuffers[SVGA3D_DX_MAX_CONSTBUFFERS]; -#else DXCONSTANTBUFFERSTATE constantBuffers; -#endif /* DX_CB */ } shaderState[SVGA3D_NUM_SHADERTYPE]; } DXBOUNDRESOURCES; @@ -410,9 +401,7 @@ typedef struct VMSVGA3DBACKENDDXCONTEXT uint32_t cSOTarget; /* How many SO targets are currently set (SetSOTargets) */ -#ifdef DX_CB DXUPLOADBUFFERMANAGER constantBufferManager; -#endif DXBOUNDRESOURCES resources; } VMSVGA3DBACKENDDXCONTEXT; @@ -2642,36 +2631,6 @@ static void dxShaderSet(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, SVGA } -#ifndef DX_CB -static void dxConstantBufferSet(DXDEVICE *pDevice, uint32_t slot, SVGA3dShaderType type, ID3D11Buffer *pConstantBuffer) -{ - switch (type) - { - case SVGA3D_SHADERTYPE_VS: - pDevice->pImmediateContext->VSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_PS: - pDevice->pImmediateContext->PSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_GS: - pDevice->pImmediateContext->GSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_HS: - pDevice->pImmediateContext->HSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_DS: - pDevice->pImmediateContext->DSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_CS: - pDevice->pImmediateContext->CSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - default: - ASSERT_GUEST_FAILED_RETURN_VOID(); - } -} -#endif /* !DX_CB */ - - static void dxSamplerSet(DXDEVICE *pDevice, SVGA3dShaderType type, uint32_t startSampler, uint32_t cSampler, ID3D11SamplerState * const *papSampler) { switch (type) @@ -6262,11 +6221,9 @@ static DECLCALLBACK(int) vmsvga3dBackDXDefineContext(PVGASTATECC pThisCC, PVMSVG AssertPtrReturn(pBackendDXContext, VERR_NO_MEMORY); pDXContext->pBackendDXContext = pBackendDXContext; -#ifdef DX_CB dxUploadBufferManagerInit(&pDXContext->pBackendDXContext->constantBufferManager, 4096 * 16, /* cbMaxData = 4096 constant 16 bytes each */ DX_CONSTANT_UPLOAD_BUFFER_SIZE, D3D11_BIND_CONSTANT_BUFFER); -#endif LogFunc(("cid %d\n", pDXContext->cid)); return VINF_SUCCESS; @@ -6287,15 +6244,7 @@ static DECLCALLBACK(int) vmsvga3dBackDXDestroyContext(PVGASTATECC pThisCC, PVMSV /* Clean up context resources. */ VMSVGA3DBACKENDDXCONTEXT *pBackendDXContext = pDXContext->pBackendDXContext; -#ifndef DX_CB - for (uint32_t idxShaderState = 0; idxShaderState < RT_ELEMENTS(pBackendDXContext->resources.shaderState); ++idxShaderState) - { - ID3D11Buffer **papConstantBuffer = &pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers[0]; - D3D_RELEASE_ARRAY(RT_ELEMENTS(pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers), papConstantBuffer); - } -#else dxUploadBufferManagerUninit(&pDXContext->pBackendDXContext->constantBufferManager); -#endif if (pBackendDXContext->paRenderTargetView) { @@ -6482,99 +6431,6 @@ static DECLCALLBACK(int) vmsvga3dBackDXInvalidateContext(PVGASTATECC pThisCC, PV static DECLCALLBACK(int) vmsvga3dBackDXSetSingleConstantBuffer(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, uint32_t slot, SVGA3dShaderType type, SVGA3dSurfaceId sid, uint32_t offsetInBytes, uint32_t sizeInBytes) { -#ifndef DX_CB - PVMSVGA3DBACKEND pBackend = pThisCC->svga.p3dState->pBackend; - RT_NOREF(pBackend); - - DXDEVICE *pDevice = dxDeviceGet(pThisCC->svga.p3dState); - AssertReturn(pDevice->pDevice, VERR_INVALID_STATE); - - if (sid == SVGA_ID_INVALID) - { - uint32_t const idxShaderState = type - SVGA3D_SHADERTYPE_MIN; - D3D_RELEASE(pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers[slot]); - return VINF_SUCCESS; - } - - PVMSVGA3DSURFACE pSurface; - int rc = vmsvga3dSurfaceFromSid(pThisCC->svga.p3dState, sid, &pSurface); - AssertRCReturn(rc, rc); - - PVMSVGA3DMIPMAPLEVEL pMipLevel; - rc = vmsvga3dMipmapLevel(pSurface, 0, 0, &pMipLevel); - AssertRCReturn(rc, rc); - - uint32_t const cbSurface = pMipLevel->cbSurface; - ASSERT_GUEST_RETURN( offsetInBytes < cbSurface - && sizeInBytes <= cbSurface - offsetInBytes, VERR_INVALID_PARAMETER); - - /* Constant buffers are created on demand. */ - Assert(pSurface->pBackendSurface == NULL); - - /* Upload the current data, if any. */ - D3D11_SUBRESOURCE_DATA *pInitialData = NULL; - D3D11_SUBRESOURCE_DATA initialData; - if (pMipLevel->pSurfaceData) - { - initialData.pSysMem = (uint8_t *)pMipLevel->pSurfaceData + offsetInBytes; - initialData.SysMemPitch = sizeInBytes; - initialData.SysMemSlicePitch = sizeInBytes; - - pInitialData = &initialData; - -#ifdef LOG_ENABLED - if (LogIs8Enabled()) - { - float *pValuesF = (float *)initialData.pSysMem; - for (unsigned i = 0; i < sizeInBytes / sizeof(float) / 4; ++i) - { - Log8(("ConstF /*%d*/ " FLOAT_FMT_STR ", " FLOAT_FMT_STR ", " FLOAT_FMT_STR ", " FLOAT_FMT_STR ",\n", - i, FLOAT_FMT_ARGS(pValuesF[i*4 + 0]), FLOAT_FMT_ARGS(pValuesF[i*4 + 1]), FLOAT_FMT_ARGS(pValuesF[i*4 + 2]), FLOAT_FMT_ARGS(pValuesF[i*4 + 3]))); - } - } -#endif - } - - uint32_t const idxShaderState = type - SVGA3D_SHADERTYPE_MIN; - ID3D11Buffer **ppCurrentBuffer = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers[slot]; - - LogFunc(("constant buffer: [%u][%u]: sid = %u, %u, %u\n", - idxShaderState, slot, sid, offsetInBytes, sizeInBytes)); - - D3D11_BUFFER_DESC bd; - - if (*ppCurrentBuffer) - { - RT_ZERO(bd); - (*ppCurrentBuffer)->GetDesc(&bd); - if (bd.ByteWidth != sizeInBytes) - { - /* Have to create a new one. */ - D3D_RELEASE(*ppCurrentBuffer); /* This will set the pointer to NULL. */ - } - } - - if (!(*ppCurrentBuffer)) - { - RT_ZERO(bd); - bd.ByteWidth = sizeInBytes; - bd.Usage = D3D11_USAGE_DEFAULT; - bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - //bd.CPUAccessFlags = 0; - //bd.MiscFlags = 0; - //bd.StructureByteStride = 0; - - HRESULT hr = pDevice->pDevice->CreateBuffer(&bd, pInitialData, ppCurrentBuffer); - AssertReturn(SUCCEEDED(hr), VERR_NO_MEMORY); - } - else - { - if (pInitialData) - pDevice->pImmediateContext->UpdateSubresource(*ppCurrentBuffer, 0, 0, pInitialData->pSysMem, 0, 0); - } - - return VINF_SUCCESS; -#else DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturn(pDXDevice->pDevice, VERR_INVALID_STATE); @@ -6663,7 +6519,6 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetSingleConstantBuffer(PVGASTATECC pThis } return VINF_SUCCESS; -#endif /* DX_CB */ } @@ -7043,35 +6898,6 @@ static void dxCreateInputLayout(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXConte static void dxSetConstantBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) { -#ifndef DX_CB -//DEBUG_BREAKPOINT_TEST(); - PVMSVGA3DBACKEND pBackend = pThisCC->svga.p3dState->pBackend; - DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); - VMSVGA3DBACKENDDXCONTEXT *pBackendDXContext = pDXContext->pBackendDXContext; - - AssertCompile(RT_ELEMENTS(pBackendDXContext->resources.shaderState[0].constantBuffers) == SVGA3D_DX_MAX_CONSTBUFFERS); - - for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) - { - SVGA3dShaderType const shaderType = (SVGA3dShaderType)(idxShaderState + SVGA3D_SHADERTYPE_MIN); - for (uint32_t idxSlot = 0; idxSlot < SVGA3D_DX_MAX_CONSTBUFFERS; ++idxSlot) - { - ID3D11Buffer **pBufferContext = &pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers[idxSlot]; - ID3D11Buffer **pBufferPipeline = &pBackend->resources.shaderState[idxShaderState].constantBuffers[idxSlot]; - if (*pBufferContext != *pBufferPipeline) - { - LogFunc(("constant buffer: [%u][%u]: %p -> %p\n", - idxShaderState, idxSlot, *pBufferPipeline, *pBufferContext)); - dxConstantBufferSet(pDXDevice, idxSlot, shaderType, *pBufferContext); - - if (*pBufferContext) - (*pBufferContext)->AddRef(); - D3D_RELEASE(*pBufferPipeline); - *pBufferPipeline = *pBufferContext; - } - } - } -#else DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) { @@ -7172,7 +6998,6 @@ static void dxSetConstantBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXCont pCb->StartSlot = 0; pCb->NumBuffers = 0; } -#endif } @@ -9215,11 +9040,7 @@ static void dxSetupPipeline(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) static void dxPostDraw(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) { -#ifndef DX_CB - RT_NOREF(pDXDevice, pDXContext); -#else dxUploadBufferManagerProcessFull(&pDXContext->pBackendDXContext->constantBufferManager, pDXDevice->pImmediateContext); -#endif } From 76c3ef11e22cbf1e17855084062f6e3ee95b153b Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Thu, 13 Aug 2026 20:27:05 +0000 Subject: [PATCH 131/176] NetworkServices/tstVBoxNetDhcpd: Another fix for more deterministic runs. svn:sync-xref-src-repo-rev: r174878 --- src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp index 4d8476233246..fa5aa049f8ef 100644 --- a/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp +++ b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVBoxNetDhcpd.cpp 115033 2026-08-13 09:00:46Z andreas.loeffler@oracle.com $ */ +/* $Id: tstVBoxNetDhcpd.cpp 115037 2026-08-13 20:27:05Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetDHCP in-process testcase. */ @@ -936,7 +936,8 @@ static bool tstDhcp4Request(PTSTCLIENT pClient, const TSTDHCP4REPLY *pOffer, PTSTDHCP4REPLY pAck) { - uint32_t uXid = RTRandU32(); + /* A DHCPREQUEST selecting an offer continues the DISCOVER transaction. */ + uint32_t const uXid = pOffer->uXid; TSTPKT Dhcp; TSTPKT Frame; From 1edd2cc62c8b30feaaa29a75d8dd2a7164dc28fd Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 14 Aug 2026 12:14:02 +0000 Subject: [PATCH 132/176] NetworkServices/tstVBoxIntNetR3Switch: Also handle VERR_NET_NOT_CONNECTED in svcIsDisconnected(). svn:sync-xref-src-repo-rev: r174879 --- src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp b/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp index 07f287f8bb67..12f6e741e32c 100644 --- a/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp +++ b/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVBoxIntNetR3Switch.cpp 114962 2026-08-10 15:04:45Z andreas.loeffler@oracle.com $ */ +/* $Id: tstVBoxIntNetR3Switch.cpp 115038 2026-08-14 12:14:02Z andreas.loeffler@oracle.com $ */ /** @file * tstVBoxIntNetR3Switch - Self-contained testcase for R3 IntNet/IntNetSwitch communication. * @@ -197,6 +197,7 @@ static bool svcIsDisconnected(int rc) { return rc == VERR_BROKEN_PIPE || rc == VERR_PIPE_NOT_CONNECTED + || rc == VERR_NET_NOT_CONNECTED || rc == VERR_NET_CONNECTION_RESET || rc == VERR_NET_CONNECTION_RESET_BY_PEER || rc == VERR_NET_CONNECTION_REFUSED; From e618eb9094721c43776124c60d603d3d08468d0f Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 14 Aug 2026 14:46:06 +0000 Subject: [PATCH 133/176] Main/HostDnsServiceDarwin: Fixed an indentation bug in updateInfo(), potentially leading to all kinds of side effects. Regression of r172091. svn:sync-xref-src-repo-rev: r174880 --- src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp b/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp index 2b32987cec6e..897be0d034af 100644 --- a/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp +++ b/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: HostDnsServiceDarwin.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: HostDnsServiceDarwin.cpp 115039 2026-08-14 14:46:06Z andreas.loeffler@oracle.com $ */ /** @file * Darwin specific DNS information fetching. */ @@ -241,6 +241,7 @@ int HostDnsServiceDarwin::updateInfo(void) { CFStringRef const serverAddressRef = (CFStringRef)CFArrayGetValueAtIndex(serverArrayRef, i); if (serverAddressRef) + { if (!queryCFStringAsUtf8Str(serverAddressRef, strTmp, _16K)) { LogRel(("HostDnsServiceDarwin: idx: %u: Failed to convert address.\n", i)); @@ -273,6 +274,7 @@ int HostDnsServiceDarwin::updateInfo(void) } else LogRel(("HostDnsServiceDarwin: line %u: bad nameserver address %s\n", i, strTmp.c_str())); + } } } @@ -313,4 +315,3 @@ void HostDnsServiceDarwin::Data::performShutdownCallback(void *pInfo) AssertPtrReturnVoid(pThis->m); ASMAtomicXchgBool(&pThis->m->m_fStop, true); } - From fbaa5ff27f1d9d7d990c30a36243c0123f94f02a Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Fri, 14 Aug 2026 17:39:40 +0000 Subject: [PATCH 134/176] NetworkServices/tstVBoxNetDhcpd: Fixed leaks found by ASAN. This is due to the Config object creating a release logger (see r121776, needs to be addressed separately). svn:sync-xref-src-repo-rev: r174881 --- .../NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp index fa5aa049f8ef..ecb929b68e43 100644 --- a/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp +++ b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVBoxNetDhcpd.cpp 115037 2026-08-13 20:27:05Z andreas.loeffler@oracle.com $ */ +/* $Id: tstVBoxNetDhcpd.cpp 115040 2026-08-14 17:39:40Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetDHCP in-process testcase. */ @@ -47,6 +47,7 @@ #include #include "../Config.h" +#include #include "../../NetLib/IntNetIf.h" extern "C" int VBoxNetDhcpdTestStart(int argc, char **argv, void **ppvHandle); @@ -1261,6 +1262,9 @@ int main(int argc, char **argv) RTTestBanner(g_hTest); tstConfigValidation(); + /* Config::create() installs a process-global release logger. Destroy it before + the in-process daemon replaces it with its own logger. */ + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); void *pvSwitch = NULL; rc = VBoxIntNetSwitchTestStart(&pvSwitch); @@ -1273,5 +1277,7 @@ int main(int argc, char **argv) else RTTestFailed(g_hTest, "Embedded IntNet switch startup failed: %Rrc", rc); + /* Destroy the release logger installed by the in-process daemon. */ + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); return RTTestSummaryAndDestroy(g_hTest); } From 69376dfaedbc5b6ce0e200c2f0bbbd943f052569 Mon Sep 17 00:00:00 2001 From: "Knut St. Osmundsen" Date: Fri, 14 Aug 2026 21:54:05 +0000 Subject: [PATCH 135/176] ValKit: cgi & 3.13+ workaround. svn:sync-xref-src-repo-rev: r174882 --- .../ValidationKit/testmanager/core/webservergluecgi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py b/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py index 2c36629ee7a7..e71e70a3978d 100644 --- a/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py +++ b/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# $Id: webservergluecgi.py 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: webservergluecgi.py 115041 2026-08-14 21:54:05Z knut.osmundsen@oracle.com $ """ Test Manager Core - Web Server Abstraction Base Class. @@ -36,12 +36,12 @@ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 """ -__version__ = "$Revision: 112403 $" +__version__ = "$Revision: 115041 $" # Standard python imports. -import cgi; # pylint: disable=deprecated-module ## @todo these will be retired in python 3.13! -import cgitb; # pylint: disable=deprecated-module ## @todo these will be retired in python 3.13! +import cgi; # pylint: disable=deprecated-module # Retired in python 3.13. Install (python3-)legacy-cgi. +import cgitb; # pylint: disable=deprecated-module # Ditto. import os; import sys; From 9aef19b6817f0de1e70f1d3da3e34c2943127c18 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Sat, 15 Aug 2026 14:51:14 +0000 Subject: [PATCH 136/176] Devices/Graphics: state tracker small update for constant buffers. bugref:10934 svn:sync-xref-src-repo-rev: r174883 --- .../Graphics/DevVGA-SVGA3d-dx-dx11.cpp | 90 +++++++++++++++---- .../Devices/Graphics/DevVGA-SVGA3d-dx.cpp | 8 +- .../Devices/Graphics/DevVGA-SVGA3d-internal.h | 6 +- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp index ed104acaf757..549e23d60396 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 115035 2026-08-13 15:06:11Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 115042 2026-08-15 14:51:14Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -6355,6 +6355,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXBindContext(PVGASTATECC pThisCC, PVMSVGA3 static DECLCALLBACK(int) vmsvga3dBackDXSwitchContext(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContextFrom, PVMSVGA3DDXCONTEXT pDXContext) { + for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) + { + DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; + pCb->StartSlot = 0; + pCb->NumBuffers = D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; + } + #ifndef DX_STATE_TRACKER /* The new context state will be applied by the generic DX code. */ RT_NOREF(pThisCC, pDXContextFrom, pDXContext); @@ -6393,13 +6400,6 @@ static DECLCALLBACK(int) vmsvga3dBackDXSwitchContext(PVGASTATECC pThisCC, PVMSVG if (cBoundCSUAV) pDXDevice->pImmediateContext->CSSetUnorderedAccessViews(0, cBoundCSUAV, u.papUnorderedAccessView, NULL); - for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) - { - DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; - pCb->StartSlot = 0; - pCb->NumBuffers = D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; - } - /* Reset vertex buffers. */ uint32_t const cBoundVB = pDXContextFrom ? pDXContextFrom->state.ia.vb.cMaxBound @@ -6436,7 +6436,7 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetSingleConstantBuffer(PVGASTATECC pThis uint32_t const idxShaderState = type - SVGA3D_SHADERTYPE_MIN; DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; - + /* Fetch the data from the surface because it is valid only during processing of SET_SINGLE_CONSTANT_BUFFER. */ if (sid == SVGA3D_INVALID_ID) { /* Clear the constant buffer slot. */ @@ -6896,9 +6896,8 @@ static void dxCreateInputLayout(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXConte } -static void dxSetConstantBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) +static void dxSetConstantBuffers(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) { - DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) { DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; @@ -8327,7 +8326,12 @@ static void dxCheckState(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) { ID3D11ShaderResourceView *paShaderResourceView[SVGA3D_DX_MAX_SRVIEWS]; } sr; - //ID3D11Buffer *pConstantBuffer; + struct + { + ID3D11Buffer *apConstantBuffer[SVGA3D_DX_MAX_CONSTBUFFERS]; + UINT aFirstConstant[SVGA3D_DX_MAX_CONSTBUFFERS]; + UINT aNumConstants[SVGA3D_DX_MAX_CONSTBUFFERS]; + } cb; //ID3D11VertexShader *pVertexShader; //ID3D11HullShader *pHullShader; //ID3D11DomainShader *pDomainShader; @@ -8496,8 +8500,64 @@ static void dxCheckState(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) D3D_RELEASE_ARRAY(SVGA3D_DX_MAX_SRVIEWS, p.sr.paShaderResourceView); } - //pImmediateContext->VSGetConstantBuffers(0, 1, &p.pConstantBuffer); - //D3D_RELEASE(p.pConstantBuffer); + /* Constant buffers */ + RT_ZERO(p); + for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) + { + RT_ZERO(p.cb); + + uint32_t const cMaxBound = pDXContext->state.shader[idxShaderState].constantBuffers.cMaxBound; + if (cMaxBound == 0) + continue; + + AssertCompile(RT_ELEMENTS(p.cb.apConstantBuffer) == SVGA3D_DX_MAX_CONSTBUFFERS); + switch (idxShaderState) + { + case 0: + pImmediateContext->VSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 1: + pImmediateContext->PSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 2: + pImmediateContext->GSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 3: + pImmediateContext->HSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 4: + pImmediateContext->DSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 5: + pImmediateContext->CSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + default: + break; + } + + DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; + AssertCompile(RT_ELEMENTS(pCb->apConstantBuffer) == SVGA3D_DX_MAX_CONSTBUFFERS); + + for (uint32_t i = 0; i < cMaxBound; ++i) + { + uint32_t sid = pDXContext->svgaDXContext.shaderState[idxShaderState].constantBuffers[i].sid; + if (sid != SVGA3D_INVALID_ID) + { + AssertRelease(pCb->apConstantBuffer[i] == p.cb.apConstantBuffer[i]); + AssertRelease(pCb->aFirstConstant[i] == p.cb.aFirstConstant[i]); + AssertRelease(pCb->aNumConstants[i] == p.cb.aNumConstants[i]); + } + else + AssertRelease(pCb->apConstantBuffer[i] == NULL); + } + D3D_RELEASE_ARRAY(cMaxBound, p.cb.apConstantBuffer); + } //pImmediateContext->VSGetShader(&p.pVertexShader, NULL, NULL); //D3D_RELEASE(p.pVertexShader); @@ -8646,7 +8706,7 @@ static void dxSetupPipeline(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) } #endif - dxSetConstantBuffers(pThisCC, pDXContext); + dxSetConstantBuffers(pDXDevice, pDXContext); #ifndef DX_STATE_TRACKER dxSetVertexBuffers(pThisCC, pDXContext); dxSetIndexBuffer(pThisCC, pDXContext); diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp index 19175de2ba48..1fd399de4543 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx.cpp 114997 2026-08-12 15:54:46Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx.cpp 115042 2026-08-15 14:51:14Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - Common code for DX backend interface. */ @@ -553,6 +553,12 @@ int vmsvga3dDXSetSingleConstantBuffer(PVGASTATECC pThisCC, uint32_t idDXContext, pCBB->offsetInBytes = pCmd->offsetInBytes; pCBB->sizeInBytes = pCmd->sizeInBytes; +#ifdef DX_STATE_TRACKER + if (pCBB->sid != SVGA3D_INVALID_ID) + pDXContext->state.shader[idxShaderState].constantBuffers.cMaxBound = + RT_MAX(pCmd->slot + 1, pDXContext->state.shader[idxShaderState].constantBuffers.cMaxBound); +#endif + rc = pSvgaR3State->pFuncsDX->pfnDXSetSingleConstantBuffer(pThisCC, pDXContext, pCmd->slot, pCmd->type, pCmd->sid, pCmd->offsetInBytes, pCmd->sizeInBytes); return rc; } diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h index c8ae90a657e7..068bed2a8881 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-internal.h 114997 2026-08-12 15:54:46Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-internal.h 115042 2026-08-15 14:51:14Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device - 3D part, internal header. */ @@ -1117,6 +1117,10 @@ typedef struct VMSVGA3DDXCONTEXT uint32_t cMaxBound; uint64_t au64Modified[(SVGA3D_DX_MAX_SRVIEWS + 63) / 64]; } shaderResources; + struct + { + uint32_t cMaxBound; + } constantBuffers; } shader[SVGA3D_NUM_SHADERTYPE]; struct { From ff5c8410275f620d995bf4197224a1e9a926cd7b Mon Sep 17 00:00:00 2001 From: Serkan Bayraktar Date: Mon, 17 Aug 2026 11:41:07 +0000 Subject: [PATCH 137/176] API: bugref:11147 Do not allow paths in nvram file name. svn:sync-xref-src-repo-rev: r174885 --- src/VBox/Main/src-server/ApplianceImplImport.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/VBox/Main/src-server/ApplianceImplImport.cpp b/src/VBox/Main/src-server/ApplianceImplImport.cpp index 4a3aeb6e7f89..2a23f00f66aa 100644 --- a/src/VBox/Main/src-server/ApplianceImplImport.cpp +++ b/src/VBox/Main/src-server/ApplianceImplImport.cpp @@ -1,4 +1,4 @@ -/* $Id: ApplianceImplImport.cpp 114949 2026-08-10 13:32:24Z serkan.bayraktar@oracle.com $ */ +/* $Id: ApplianceImplImport.cpp 115044 2026-08-17 11:41:07Z serkan.bayraktar@oracle.com $ */ /** @file * IAppliance and IVirtualSystem COM class implementations. */ @@ -6295,7 +6295,9 @@ void Appliance::i_importMachines(ImportStack &stack) std::list vsdeNvram = vsdescThis->i_findByType(VirtualSystemDescriptionType_NVRAM); if (!vsdeNvram.empty()) stack.strNvramPath = vsdeNvram.front()->strVBoxCurrent; - + /* Do not allow paths in nvram file name. */ + if (RTPathHasPath(stack.strNvramPath.c_str())) + stack.strNvramPath.setNull(); #ifdef VBOX_WITH_USB // USB controller std::list vsdeUSBController = From bab4f56334cd535916d0fda7f95f378c9dcacd43 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 14:51:37 +0000 Subject: [PATCH 138/176] Shared Clipboard: Remove unused transfer and helper code. bugref:4697 svn:sync-xref-src-repo-rev: r174886 --- .../GuestHost/SharedClipboard-transfers.h | 71 +-- include/VBox/GuestHost/SharedClipboard-win.h | 4 - include/VBox/GuestHost/SharedClipboard-x11.h | 1 - include/VBox/GuestHost/SharedClipboard.h | 16 - include/VBox/GuestHost/clipboard-helper.h | 25 +- .../HostServices/VBoxSharedClipboardSvc.h | 19 +- .../common/VBoxGuest/lib/Makefile.kmk | 5 +- .../ClipboardDataObjectImpl-win.cpp | 7 +- .../SharedClipboard/clipboard-common.cpp | 18 +- .../SharedClipboard/clipboard-helper.cpp | 54 +- .../SharedClipboard/clipboard-mime.cpp | 53 -- .../SharedClipboard/clipboard-path.cpp | 228 +------- .../clipboard-transfers-http.cpp | 147 +---- .../SharedClipboard/clipboard-transfers.cpp | 524 +----------------- .../SharedClipboard/clipboard-x11.cpp | 11 - .../VBoxSharedClipboardSvc-transfers.cpp | 82 +-- .../VBoxSharedClipboardSvc-transfers.h | 5 +- 17 files changed, 29 insertions(+), 1241 deletions(-) delete mode 100644 src/VBox/GuestHost/SharedClipboard/clipboard-mime.cpp diff --git a/include/VBox/GuestHost/SharedClipboard-transfers.h b/include/VBox/GuestHost/SharedClipboard-transfers.h index a5ca7bfdb809..1f2fb13abd72 100644 --- a/include/VBox/GuestHost/SharedClipboard-transfers.h +++ b/include/VBox/GuestHost/SharedClipboard-transfers.h @@ -1,4 +1,4 @@ -/* $Id: SharedClipboard-transfers.h 114989 2026-08-11 14:13:05Z andreas.loeffler@oracle.com $ */ +/* $Id: SharedClipboard-transfers.h 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Shared transfer functions between host and guest. */ @@ -506,19 +506,6 @@ typedef struct _SHCLOBJDATACHUNK /** Pointer to a Shared Clipboard transfer object data chunk. */ typedef SHCLOBJDATACHUNK *PSHCLOBJDATACHUNK; -/** - * Structure for handling a single transfer object context. - */ -typedef struct _SHCLCLIENTTRANSFEROBJCTX -{ - /** Pointer to the actual transfer object of this context. */ - SHCLTRANSFER *pTransfer; - /** Object handle of this transfer context. */ - SHCLOBJHANDLE uHandle; -} SHCLCLIENTTRANSFEROBJCTX; -/** Pointer to a Shared Clipboard transfer object context. */ -typedef SHCLCLIENTTRANSFEROBJCTX *PSHCLCLIENTTRANSFEROBJCTX; - typedef struct _SHCLTRANSFEROBJSTATE { /** How many bytes were processed (read / write) so far. */ @@ -1021,9 +1008,6 @@ typedef struct _SHCLHTTPSERVER { /** Critical section for serializing access. */ RTCRITSECT CritSect; - /** Status event for callers to wait for. - * Updates \a enmStatus. */ - RTSEMEVENT StatusEvent; /** Initialized indicator. */ bool fInitialized; /** Running indicator. */ @@ -1065,20 +1049,6 @@ typedef SHCLHTTPCONTEXT *PSHCLHTTPCONTEXT; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ -/** - * Structure for keeping a single transfer context event. - */ -typedef struct _SHCLTRANSFERCTXEVENT -{ - /** Transfer bound to this event. - * Can be NULL if not being used. */ - PSHCLTRANSFER pTransfer; - /** Whether a transfer was registered or not. */ - bool fRegistered; -} SHCLTRANSFERCTXEVENT; -/** Pointer to Shared Clipboard transfer context event. */ -typedef SHCLTRANSFERCTXEVENT *PSHCLTRANSFERCTXEVENT; - /** * Structure for keeping Shared Clipboard transfer context around. * @@ -1088,10 +1058,6 @@ typedef struct _SHCLTRANSFERCTX { /** Critical section for serializing access. */ RTCRITSECT CritSect; - /** Event used for waiting. for transfer context changes. */ - RTSEMEVENT ChangedEvent; - /** Event data for \a ChangedEvent. */ - SHCLTRANSFERCTXEVENT ChangedEventData; /** List of transfers. */ RTLISTANCHOR List; /** Transfer ID allocation bitmap; clear bits are free, set bits are busy. */ @@ -1123,20 +1089,14 @@ PSHCLTXPROVIDERIFACE ShClTransferProviderLocalQueryInterface(PSHCLTXPROVIDER pPr /** @name Shared Clipboard transfer object API. * @{ */ -int ShClTransferObjCtxInit(PSHCLCLIENTTRANSFEROBJCTX pObjCtx); -void ShClTransferObjCtxDestroy(PSHCLCLIENTTRANSFEROBJCTX pObjCtx); -bool ShClTransferObjCtxIsValid(PSHCLCLIENTTRANSFEROBJCTX pObjCtx); - int ShClTransferObjInit(PSHCLTRANSFEROBJ pObj); void ShClTransferObjDestroy(PSHCLTRANSFEROBJ pObj); int ShClTransferObjOpenParmsInit(PSHCLOBJOPENCREATEPARMS pParms); -int ShClTransferObjOpenParmsCopy(PSHCLOBJOPENCREATEPARMS pParmsDst, PSHCLOBJOPENCREATEPARMS pParmsSrc); void ShClTransferObjOpenParmsDestroy(PSHCLOBJOPENCREATEPARMS pParms); int ShClTransferObjOpen(PSHCLTRANSFER pTransfer, PSHCLOBJOPENCREATEPARMS pOpenCreateParms, PSHCLOBJHANDLE phObj); int ShClTransferObjClose(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj); -bool ShClTransferObjIsComplete(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj); int ShClTransferObjRead(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj, void *pvBuf, uint32_t cbBuf, uint32_t fFlags, uint32_t *pcbRead); int ShClTransferObjWrite(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj, void *pvBuf, uint32_t cbBuf, uint32_t fFlags, uint32_t *pcbWritten); PSHCLTRANSFEROBJ ShClTransferObjGet(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj); @@ -1211,7 +1171,6 @@ int ShClTransferInit(PSHCLTRANSFER pTransfer); int ShClTransferDestroy(PSHCLTRANSFER pTransfer); void ShClTransferReset(PSHCLTRANSFER pTransfer); -bool ShClTransferIsRunning(PSHCLTRANSFER pTransfer); bool ShClTransferIsComplete(PSHCLTRANSFER pTransfer); bool ShClTransferIsAborted(PSHCLTRANSFER pTransfer); @@ -1230,20 +1189,16 @@ SHCLTRANSFERID ShClTransferGetID(PSHCLTRANSFER pTransfer); SHCLSESSIONID ShClTransferGetSessionId(PSHCLTRANSFER pTransfer); SHCLTRANSFERGEN ShClTransferGetGeneration(PSHCLTRANSFER pTransfer); SHCLTRANSFERDIR ShClTransferGetDir(PSHCLTRANSFER pTransfer); -int ShClTransferGetRootPathAbs(PSHCLTRANSFER pTransfer, char *pszPath, size_t cbPath); SHCLSOURCE ShClTransferGetSource(PSHCLTRANSFER pTransfer); SHCLTRANSFERSTATUS ShClTransferGetStatus(PSHCLTRANSFER pTransfer); int ShClTransferWaitForStatus(PSHCLTRANSFER pTransfer, RTMSINTERVAL msTimeout, SHCLTRANSFERSTATUS enmStatus); -int ShClTransferWaitForStatusChange(PSHCLTRANSFER pTransfer, RTMSINTERVAL msTimeout, SHCLTRANSFERSTATUS *penmStatus); int ShClTransferListOpen(PSHCLTRANSFER pTransfer, PSHCLLISTOPENPARMS pOpenParms, PSHCLLISTHANDLE phList); int ShClTransferListClose(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList); int ShClTransferListGetHeader(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, PSHCLLISTHDR pHdr); PSHCLLISTHANDLEINFO ShClTransferListGetByHandle(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList); -PSHCLTRANSFEROBJ ShClTransferListGetObj(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, uint64_t uIdx); int ShClTransferListRead(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, PSHCLLISTENTRY pEntry); int ShClTransferListWrite(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, PSHCLLISTENTRY pEntry); -bool ShClTransferListHandleIsValid(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList); PSHCLLIST ShClTransferListAlloc(void); void ShClTransferListFree(PSHCLLIST pList); @@ -1254,8 +1209,6 @@ int ShClTransferListAddEntry(PSHCLLIST pList, PSHCLLISTENTRY pEntry, bool fAppen int ShClTransferListHandleInfoInit(PSHCLLISTHANDLEINFO pInfo); void ShClTransferListHandleInfoDestroy(PSHCLLISTHANDLEINFO pInfo); -int ShClTransferListHdrAlloc(PSHCLLISTHDR *ppListHdr); -void ShClTransferListHdrFree(PSHCLLISTHDR pListHdr); PSHCLLISTHDR ShClTransferListHdrDup(PSHCLLISTHDR pListHdr); int ShClTransferListHdrInit(PSHCLLISTHDR pListHdr); void ShClTransferListHdrDestroy(PSHCLLISTHDR pListHdr); @@ -1263,7 +1216,6 @@ void ShClTransferListHdrReset(PSHCLLISTHDR pListHdr); bool ShClTransferListHdrIsValid(PSHCLLISTHDR pListHdr); int ShClTransferListOpenParmsCopy(PSHCLLISTOPENPARMS pDst, PSHCLLISTOPENPARMS pSrc); -PSHCLLISTOPENPARMS ShClTransferListOpenParmsDup(PSHCLLISTOPENPARMS pParms); int ShClTransferListOpenParmsInit(PSHCLLISTOPENPARMS pParms); void ShClTransferListOpenParmsDestroy(PSHCLLISTOPENPARMS pParms); @@ -1280,7 +1232,6 @@ int ShClTransferSetProvider(PSHCLTRANSFER pTransfer, PSHCLTXPROVIDER pProvider); int ShClTransferRootsSetFromStringListEx(PSHCLTRANSFER pTransfer, const char *pszRoots, size_t cbRoots, const char *pszSep); int ShClTransferRootsSetFromStringList(PSHCLTRANSFER pTransfer, const char *pszRoots, size_t cbRoots); -int ShClTransferRootsSetFromStringListUnicode(PSHCLTRANSFER pTransfer, PRTUTF16 pwszRoots, size_t cbRoots); int ShClTransferRootsSetFromPath(PSHCLTRANSFER pTransfer, const char *pszPath); uint64_t ShClTransferRootsCount(PSHCLTRANSFER pTransfer); PCSHCLLISTENTRY ShClTransferRootsEntryGet(PSHCLTRANSFER pTransfer, uint64_t uIndex); @@ -1299,15 +1250,11 @@ PSHCLTRANSFER ShClTransferCtxGetTransferByKey(PSHCLTRANSFERCTX pTransferCtx, SHC SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration); PSHCLTRANSFER ShClTransferCtxGetTransferByIndex(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx); PSHCLTRANSFER ShClTransferCtxGetTransferLast(PSHCLTRANSFERCTX pTransferCtx); -uint32_t ShClTransferCtxGetRunningTransfers(PSHCLTRANSFERCTX pTransferCtx); uint32_t ShClTransferCtxGetTotalTransfers(PSHCLTRANSFERCTX pTransferCtx); -void ShClTransferCtxCleanup(PSHCLTRANSFERCTX pTransferCtx); bool ShClTransferCtxIsMaximumReached(PSHCLTRANSFERCTX pTransferCtx); -int ShClTransferCtxCreateId(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFERID pidTransfer); int ShClTransferCtxRegister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer, PSHCLTRANSFERID pidTransfer); int ShClTransferCtxRegisterById(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer, SHCLTRANSFERID idTransfer); int ShClTransferCtxUnregisterById(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID idTransfer); -int ShClTransferCtxWait(PSHCLTRANSFERCTX pTransferCtx, RTMSINTERVAL msTimeout, bool fRegister, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer); /** @} */ #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP @@ -1331,17 +1278,13 @@ int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort); int ShClTransferHttpServerStop(PSHCLHTTPSERVER pSrv); int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); -PSHCLTRANSFER ShClTransferHttpServerGetTransferFirst(PSHCLHTTPSERVER pSrv); -PSHCLTRANSFER ShClTransferHttpServerGetTransferLast(PSHCLHTTPSERVER pSrv); bool ShClTransferHttpServerGetTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer); -uint16_t ShClTransferHttpServerGetPort(PSHCLHTTPSERVER pSrv); uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv); char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv); char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer, uint64_t idxEntry); int ShClTransferHttpConvertToStringList(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer, char **ppszData, size_t *pcbData); bool ShClTransferHttpServerIsInitialized(PSHCLHTTPSERVER pSrv); bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv); -int ShClTransferHttpServerWaitForStatusChange(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS fStatus, RTMSINTERVAL msTimeout); /** @} */ #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ @@ -1350,11 +1293,6 @@ int ShClTransferHttpServerWaitForStatusChange(PSHCLHTTPSERVER pSrv, SHCLHTTPSERV */ int ShClPathSanitizeFilename(char *pszPath, size_t cbPath); int ShClPathSanitize(char *pszPath, size_t cbPath); -bool ShClPathIsSymlink(const char *pszPath); -bool ShClPathIsDirectory(const char *pszPath); -int ShClDirectoryCreate(const char *pszPath); -int ShClHlpTransferPathToHostPath(const char *pszDestination, const char *pszTransferPath, - char *pszHostPath, size_t cbHostPath); const char *ShClTransferStatusToStr(SHCLTRANSFERSTATUS enmStatus); int ShClTransferTransformPath(char *pszPath, size_t cbPath); int ShClTransferValidatePath(const char *pcszPath, bool fMustExist); @@ -1364,11 +1302,4 @@ int ShClFsObjInfoQueryLocal(const char *pszPath, PSHCLFSOBJINFO pObjInfo); int ShClFsObjInfoFromIPRT(PSHCLFSOBJINFO pDst, PCRTFSOBJINFO pSrc); /** @} */ -/** @name Shared Clipboard MIME functions. - * @{ - */ -bool ShClMIMEHasFileURLs(const char *pcszFormat, size_t cchFormatMax); -bool ShClMIMENeedsCache(const char *pcszFormat, size_t cchFormatMax); -/** @} */ - #endif /* !VBOX_INCLUDED_GuestHost_SharedClipboard_transfers_h */ diff --git a/include/VBox/GuestHost/SharedClipboard-win.h b/include/VBox/GuestHost/SharedClipboard-win.h index c3eba2d61c1f..288dadf7f74f 100644 --- a/include/VBox/GuestHost/SharedClipboard-win.h +++ b/include/VBox/GuestHost/SharedClipboard-win.h @@ -338,8 +338,6 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili /** Vector containing file system objects with its (cached) objection information. */ typedef std::vector FsObjEntryList; - /** Shared Clipboard context to use. */ - PSHCLCONTEXT m_pCtx; /** The object's current status. */ Status m_enmStatus; /** Last (IPRT-style) error set in conjunction with the status. */ @@ -492,7 +490,6 @@ class ShClWinTransferCtx ShClWinDataObject *pDataObj; }; -int ShClWinTransferGetRoots(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); int ShClWinTransferDropFilesToStringList(DROPFILES *pDropFiles, char **papszList, uint32_t *pcbList); int ShClWinTransferGetRootsFromClipboard(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); @@ -504,4 +501,3 @@ int ShClWinTransferInitialize(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); int ShClWinTransferStart(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); # endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ #endif /* !VBOX_INCLUDED_GuestHost_SharedClipboard_win_h */ - diff --git a/include/VBox/GuestHost/SharedClipboard-x11.h b/include/VBox/GuestHost/SharedClipboard-x11.h index fbfffc999c3b..f58d5d2c9187 100644 --- a/include/VBox/GuestHost/SharedClipboard-x11.h +++ b/include/VBox/GuestHost/SharedClipboard-x11.h @@ -312,7 +312,6 @@ int ShClX11ReadDataFromX11Ex(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RT int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, void *pvBuf, uint32_t cbBuf, uint32_t *pcbBuf); int ShClX11WriteDataToX11Async(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, const void *pvBuf, uint32_t cbBuf, PSHCLEVENT pEvent); int ShClX11WriteDataToX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten); -void ShClX11SetCallbacks(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS int ShClX11TransferConvertToX11(const char *pszSrc, size_t cbSrc, SHCLX11FMT enmFmtX11, void **ppvDst, size_t *pcbDst); int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppszList, size_t *pcbList); diff --git a/include/VBox/GuestHost/SharedClipboard.h b/include/VBox/GuestHost/SharedClipboard.h index 6331729f9c54..481a24058019 100644 --- a/include/VBox/GuestHost/SharedClipboard.h +++ b/include/VBox/GuestHost/SharedClipboard.h @@ -186,21 +186,6 @@ typedef SHCLTRANSFERDIR *PSHCLTRANSFERDIR; */ VBGH_DECL(bool) ShClTransferDirIsValid(SHCLTRANSFERDIR enmDir); -/** - * Shared Clipboard data read request. - */ -typedef struct SHCLDATAREQ -{ - /** In which format the data needs to be sent. */ - SHCLFORMAT uFmt; - /** Read flags; currently unused. */ - uint32_t fFlags; - /** Maximum data (in byte) can be sent. */ - uint32_t cbSize; -} SHCLDATAREQ; -/** Pointer to a shared clipboard data request. */ -typedef SHCLDATAREQ *PSHCLDATAREQ; - /** * Shared Clipboard event payload (optional). */ @@ -386,7 +371,6 @@ typedef SHCLCACHE *PSHCLCACHE; VBGH_DECL(void) ShClCacheInit(PSHCLCACHE pCache); VBGH_DECL(void) ShClCacheTerm(PSHCLCACHE pCache); VBGH_DECL(void) ShClCacheInvalidate(PSHCLCACHE pCache); -VBGH_DECL(void) ShClCacheInvalidateEntry(PSHCLCACHE pCache, SHCLFORMAT uFmt); VBGH_DECL(PSHCLCACHEENTRY) ShClCacheGet(PSHCLCACHE pCache, SHCLFORMAT uFmt); VBGH_DECL(int) ShClCachePrep(PSHCLCACHE pCache, SHCLFORMAT uFmt, size_t cbData, void **ppvData); VBGH_DECL(int) ShClCacheSet(PSHCLCACHE pCache, SHCLFORMAT uFmt, const void *pvData, size_t cbData); diff --git a/include/VBox/GuestHost/clipboard-helper.h b/include/VBox/GuestHost/clipboard-helper.h index d097c6599bcf..2055fc4ea237 100644 --- a/include/VBox/GuestHost/clipboard-helper.h +++ b/include/VBox/GuestHost/clipboard-helper.h @@ -1,4 +1,4 @@ -/* $Id: clipboard-helper.h 114830 2026-07-31 10:02:47Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-helper.h 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Helper functions. */ @@ -114,21 +114,6 @@ int ShClHlpUtf16LenUtf8(PCRTUTF16 pcwszSrc, size_t cwcSrc, size_t *pcbLenSansTer */ int ShClHlpUtf8ValidateExact(const char *pchSrc, size_t cbSrc, size_t *pcchText); -/** - * Validates and duplicates an exact bounded UTF-16 payload. - * - * The input may omit its final terminator. If a terminator is present, it - * must be the final code unit; embedded terminators and trailing data are - * rejected. The returned copy is always terminated. - * - * @returns VBox status code. - * @param pwszSrc UTF-16 payload to duplicate. - * @param cwcSrc Exact payload size in UTF-16 code units. - * @param ppwszDst Where to return the allocated terminated copy. - * Free with RTUtf16Free(). - */ -int ShClHlpUtf16DupExact(PCRTUTF16 pwszSrc, size_t cwcSrc, PRTUTF16 *ppwszDst); - /** * Converts an UTF-16 string with LF EOL to an UTF-16 string with CRLF EOL. * @@ -284,14 +269,6 @@ const char *ShClHlpModeToString(uint32_t uMode); */ const char *ShClHlpTransferStateToString(uint32_t uState); -/** - * Converts a Main API clipboard event type value to a printable string. - * - * @returns Printable event type name. - * @param uEventType Main API VBoxEventType value. - */ -const char *ShClHlpVBoxEventTypeToString(uint32_t uEventType); - /** * Parses a clipboard sharing mode value. * diff --git a/include/VBox/HostServices/VBoxSharedClipboardSvc.h b/include/VBox/HostServices/VBoxSharedClipboardSvc.h index aa8798710525..0d2640ce49ea 100644 --- a/include/VBox/HostServices/VBoxSharedClipboardSvc.h +++ b/include/VBox/HostServices/VBoxSharedClipboardSvc.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc.h 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.h 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - header file for shared clipboard data transfer * interfaces and platform-dependent backend functionality. @@ -88,7 +88,7 @@ typedef struct SHCLCLIENTTRANSFERSTATE /** Shared Clipboard (file) transfer mode. */ uint32_t uTransferMode; -} SHCLCLIENTTRANSFERSTATE, *PSHCLCLIENTTRANSFERSTATE; +} SHCLCLIENTTRANSFERSTATE; /** * Structure for holding a single POD (plain old data) transfer. @@ -107,17 +107,13 @@ typedef struct SHCLCLIENTPODSTATE uint64_t cbReadWritten; /** Timestamp (in ms) of Last read/write operation. */ uint64_t tsLastReadWrittenMs; -} SHCLCLIENTPODSTATE, *PSHCLCLIENTPODSTATE; +} SHCLCLIENTPODSTATE; /** @name SHCLCLIENTSTATE_FLAGS_XXX * @note Part of saved state! * @{ */ /** No Shared Clipboard client flags defined. */ #define SHCLCLIENTSTATE_FLAGS_NONE 0 -/** Client has a guest read operation active. Currently unused. */ -#define SHCLCLIENTSTATE_FLAGS_READ_ACTIVE RT_BIT(0) -/** Client has a guest write operation active. Currently unused. */ -#define SHCLCLIENTSTATE_FLAGS_WRITE_ACTIVE RT_BIT(1) /** @} */ /** @@ -157,9 +153,6 @@ typedef struct SHCLCLIENTLEGACYSTATE */ typedef struct SHCLCLIENTSTATE { - struct SHCLCLIENTSTATE *pNext; - struct SHCLCLIENTSTATE *pPrev; - /** Backend-dependent opaque context structure. * This contains data only known to a certain backend implementation. * Optional and can be NULL. */ @@ -203,7 +196,7 @@ typedef struct _SHCLIENTTRANSFERS SHCLTRANSFERCALLBACKS Callbacks; /** Backends-specific transfers provider to use. */ SHCLTXPROVIDER Provider; -} SHCLIENTTRANSFERS, *PSHCLIENTTRANSFERS; +} SHCLIENTTRANSFERS; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /** Prototypes for the Shared Clipboard backend. */ @@ -563,8 +556,4 @@ int shClSvcTransferSendStatusAsync(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /* Host unit testing interface */ -#ifdef UNIT_TEST -uint32_t TestClipSvcGetMode(void); -#endif - #endif /* !VBOX_INCLUDED_HostServices_VBoxSharedClipboardSvc_h */ diff --git a/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk b/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk index 3caf8efef5db..d0319c16cf78 100644 --- a/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk +++ b/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114575 2026-06-30 15:58:06Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the common guest addition code library. # @@ -164,8 +164,7 @@ ifdef VBOX_WITH_SHARED_CLIPBOARD VBoxGuestR3LibClipboard.cpp ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS VBoxGuestR3Lib_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-mime.cpp + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp endif endif ifdef VBOX_WITH_SHARED_FOLDERS diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp index 5c10fc48cdd7..ef054667284a 100644 --- a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardDataObjectImpl-win.cpp 114661 2026-07-08 10:39:13Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardDataObjectImpl-win.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * ClipboardDataObjectImpl-win.cpp - Shared Clipboard IDataObject implementation. */ @@ -59,8 +59,7 @@ #endif ShClWinDataObject::ShClWinDataObject(void) - : m_pCtx(NULL) - , m_enmStatus(Uninitialized) + : m_enmStatus(Uninitialized) , m_rcStatus(VERR_IPE_UNINITIALIZED_STATUS) , m_lRefCount(0) , m_cFormats(0) @@ -114,8 +113,6 @@ int ShClWinDataObject::Init(PSHCLCONTEXT pCtx, ShClWinDataObject::PCALLBACKS pCa int rc = VINF_SUCCESS; - m_pCtx = pCtx; /* Save opaque context. */ - /* * Set up callback context + table. */ diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp index e43921511604..fe3239f857a9 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-common.cpp 114907 2026-08-10 08:44:49Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-common.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common helper objects. */ @@ -1052,22 +1052,6 @@ VBGH_DECL(void) ShClCacheInvalidate(PSHCLCACHE pCache) shClCacheReInitAllEntries(pCache); } -/** - * Invalidates a specific cache entry. - * - * @param pCache Cache to invalidate. - * @param uFmt Format to invalidate entry for. - */ -VBGH_DECL(void) ShClCacheInvalidateEntry(PSHCLCACHE pCache, SHCLFORMAT uFmt) -{ - AssertPtrReturnVoid(pCache); - AssertMsgReturnVoid(pCache->u32Magic == SHCLCACHE_MAGIC, ("%#x\n", pCache->u32Magic)); - int const idxFmt = ShClFormatToBitNo(uFmt); - AssertMsgReturnVoid((unsigned)idxFmt < RT_ELEMENTS(pCache->aEntries), ("%#x/%d\n", uFmt, idxFmt)); - - shClCacheEntryReInit(&pCache->aEntries[idxFmt]); -} - /** * Gets an entry for a Shared Clipboard format. * diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp index 962f794210ef..0eeeab45fc43 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-helper.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-helper.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Helper functions. */ @@ -29,7 +29,7 @@ #include #include -#include +#include #include #include #include @@ -73,31 +73,6 @@ int ShClHlpUtf8ValidateExact(const char *pchSrc, size_t cbSrc, size_t *pcchText) return rc; } -int ShClHlpUtf16DupExact(PCRTUTF16 pwszSrc, size_t cwcSrc, PRTUTF16 *ppwszDst) -{ - AssertPtrReturn(pwszSrc, VERR_INVALID_POINTER); - AssertPtrReturn(ppwszDst, VERR_INVALID_POINTER); - *ppwszDst = NULL; - AssertReturn(cwcSrc, VERR_INVALID_PARAMETER); - - size_t cwcText = 0; - int rc = RTUtf16LenAndValidateEncoding(pwszSrc, cwcSrc, 0 /* fFlags */, NULL /* pcuc */, &cwcText); - if (RT_FAILURE(rc)) - return rc; - if (cwcText < cwcSrc - 1) - return VERR_BUFFER_UNDERFLOW; - if (cwcText >= RTSTR_MAX / sizeof(RTUTF16)) - return VERR_TOO_MUCH_DATA; - - PRTUTF16 pwszDst = RTUtf16Alloc((cwcText + 1) * sizeof(RTUTF16)); - if (!pwszDst) - return VERR_NO_UTF16_MEMORY; - memcpy(pwszDst, pwszSrc, cwcText * sizeof(RTUTF16)); - pwszDst[cwcText] = '\0'; - *ppwszDst = pwszDst; - return VINF_SUCCESS; -} - int ShClHlpConvUtf16CRLFToUtf8LF(PCRTUTF16 pwszSrc, size_t cwcSrc, char *pszBuf, size_t cbBuf, size_t *pcbLen) { AssertPtrReturn(pwszSrc, VERR_INVALID_POINTER); @@ -852,31 +827,6 @@ const char *ShClHlpTransferStateToString(uint32_t uState) } -/** - * Converts a Main API clipboard event type value to a printable string. - * - * @returns Printable event type name. - * @param uEventType Main API VBoxEventType value. - */ -const char *ShClHlpVBoxEventTypeToString(uint32_t uEventType) -{ - switch (uEventType) - { - case 72: return "OnClipboardModeChanged"; - case 104: return "OnClipboardFileTransferModeChanged"; - case 122: return "OnClipboardError"; - case 126: return "OnClipboardSourceChanged"; - case 127: return "OnClipboardFormatChanged"; - case 128: return "OnClipboardDataChanged"; - case 129: return "OnClipboardTransfer"; - case 130: return "OnClipboardDataRequested"; - default: break; - } - - AssertFailedReturn("unknown"); -} - - /** * Parses a clipboard sharing mode value. * diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-mime.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-mime.cpp deleted file mode 100644 index 63a5fbe02696..000000000000 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-mime.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* $Id: clipboard-mime.cpp 114575 2026-06-30 15:58:06Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard - Path list class. - */ - -/* - * Copyright (C) 2019-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD -#include - -#include - - -bool ShClMIMEHasFileURLs(const char *pcszFormat, size_t cchFormatMax) -{ - /** @todo "text/uri" also an official variant? */ - return ( RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax) == 0 - || RTStrNICmp(pcszFormat, "x-special/gnome-icon-list", cchFormatMax) == 0); -} - -bool ShClMIMENeedsCache(const char *pcszFormat, size_t cchFormatMax) -{ - bool fNeedsDropDir = false; - if (!RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax)) /** @todo Add "x-special/gnome-icon-list"? */ - fNeedsDropDir = true; - - return fNeedsDropDir; -} - diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp index b7cc16959b20..64be9ab36e28 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-path.cpp 114830 2026-07-31 10:02:47Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-path.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Path handling. */ @@ -32,16 +32,9 @@ #define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD #include -#include #include -#include #include #include -#include - -#ifdef RT_OS_WINDOWS -# include -#endif /** @@ -141,222 +134,3 @@ int ShClPathSanitize(char *pszPath, size_t cbPath) return VINF_SUCCESS; } - - -static int shClPathQueryIsSymlink(const char *pszPath, PCRTFSOBJINFO pObjInfo, bool *pfIsSymlink) -{ - *pfIsSymlink = RTFS_IS_SYMLINK(pObjInfo->Attr.fMode); -#ifdef RT_OS_WINDOWS - int rc = VINF_SUCCESS; - if (!*pfIsSymlink) - { - PRTUTF16 pwszPath = NULL; - rc = RTPathWinFromUtf8(&pwszPath, pszPath, 0 /* fFlags */); - if (RT_SUCCESS(rc)) - { - DWORD const fAttributes = GetFileAttributesW(pwszPath); - if (fAttributes == INVALID_FILE_ATTRIBUTES) - rc = RTErrConvertFromWin32(GetLastError()); - else - *pfIsSymlink = RT_BOOL(fAttributes & FILE_ATTRIBUTE_REPARSE_POINT); - RTPathWinFree(pwszPath); - } - } - return rc; -#elif defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) - /* Keep a positive result from either the portable query or the native backend. */ - if (RTSymlinkExists(pszPath)) - *pfIsSymlink = true; - return VINF_SUCCESS; -#else - RT_NOREF(pszPath); - return VINF_SUCCESS; -#endif -} - - -/** - * Checks whether a path is a symbolic link or equivalent. - * - * On Windows this includes all reparse points, such as directory junctions. - * - * @returns Whether @a pszPath is a symbolic link or equivalent. - * @param pszPath Path to inspect. - */ -bool ShClPathIsSymlink(const char *pszPath) -{ - AssertPtrReturn(pszPath, false); - - RTFSOBJINFO ObjInfo; - int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); - bool fIsSymlink = false; - if (RT_SUCCESS(rc)) - rc = shClPathQueryIsSymlink(pszPath, &ObjInfo, &fIsSymlink); - return RT_SUCCESS(rc) && fIsSymlink; -} - - -static int shClPathValidateDirectory(const char *pszPath) -{ - RTFSOBJINFO ObjInfo; - int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); - bool fIsSymlink = false; - if (RT_SUCCESS(rc)) - rc = shClPathQueryIsSymlink(pszPath, &ObjInfo, &fIsSymlink); - if (RT_SUCCESS(rc)) - { - if (fIsSymlink) - rc = VERR_IS_A_SYMLINK; - else if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) - rc = VERR_NOT_A_DIRECTORY; - } - return rc; -} - - -/** - * Checks whether a path is a real directory rather than a symbolic link or - * Windows reparse point. - * - * @returns Whether @a pszPath is a real directory. - * @param pszPath Path to inspect. - */ -bool ShClPathIsDirectory(const char *pszPath) -{ - AssertPtrReturn(pszPath, false); - return RT_SUCCESS(shClPathValidateDirectory(pszPath)); -} - - -/** - * Creates missing components of an absolute directory path. - * - * Every component is validated without accepting symbolic links or Windows - * reparse points. A concurrent creator is accepted only if it created the - * expected real directory. - * - * @returns VBox status code. - * @param pszPath Absolute directory path to create. - */ -int ShClDirectoryCreate(const char *pszPath) -{ - AssertPtrReturn(pszPath, VERR_INVALID_POINTER); - - int rc = RTStrValidateEncoding(pszPath); - if (RT_FAILURE(rc)) - return rc; - - PRTPATHSPLIT pSplit = NULL; - rc = RTPathSplitA(pszPath, &pSplit, RTPATH_STR_F_STYLE_HOST); - if (RT_FAILURE(rc)) - return rc; - if ( !(pSplit->fProps & RTPATH_PROP_ABSOLUTE) - || !pSplit->cComps) - { - RTPathSplitFree(pSplit); - return VERR_INVALID_NAME; - } - - char szPath[RTPATH_MAX]; - rc = RTStrCopy(szPath, sizeof(szPath), pSplit->apszComps[0]); - for (uint16_t i = 0; RT_SUCCESS(rc) && i < pSplit->cComps; ++i) - { - if (i) - rc = RTPathAppend(szPath, sizeof(szPath), pSplit->apszComps[i]); - if (RT_FAILURE(rc)) - break; - - rc = shClPathValidateDirectory(szPath); - if ( RT_FAILURE(rc) - && i > 0 - && ( rc == VERR_FILE_NOT_FOUND - || rc == VERR_PATH_NOT_FOUND)) - { - rc = RTDirCreate(szPath, 0700, 0 /* fCreate */); - if (RT_FAILURE(rc)) - { - int const rcCreate = rc; - rc = shClPathValidateDirectory(szPath); - if ( rc == VERR_FILE_NOT_FOUND - || rc == VERR_PATH_NOT_FOUND) - rc = rcCreate; - } - else - rc = shClPathValidateDirectory(szPath); - } - } - - RTPathSplitFree(pSplit); - return rc; -} - - -/** - * Builds a host path below a transfer destination from a validated - * transfer-relative path. - * - * @returns VBox status code. - * @param pszDestination Absolute host destination directory. - * @param pszTransferPath Transfer-relative path. - * @param pszHostPath Where to return the host path. - * @param cbHostPath Size of @a pszHostPath. - */ -int ShClHlpTransferPathToHostPath(const char *pszDestination, const char *pszTransferPath, - char *pszHostPath, size_t cbHostPath) -{ - AssertPtrReturn(pszDestination, VERR_INVALID_POINTER); - AssertPtrReturn(pszTransferPath, VERR_INVALID_POINTER); - AssertPtrReturn(pszHostPath, VERR_INVALID_POINTER); - AssertReturn(cbHostPath, VERR_INVALID_PARAMETER); - if ( !*pszDestination - || !*pszTransferPath - || pszTransferPath[0] == '/' - || pszTransferPath[0] == '\\' - || strchr(pszTransferPath, '\\') - || strchr(pszTransferPath, ':')) - return VERR_INVALID_NAME; - - int rc = RTStrValidateEncoding(pszTransferPath); - if (RT_FAILURE(rc)) - return rc; - for (const char *pszCur = pszTransferPath; *pszCur; pszCur = RTStrNextCp(pszCur)) - { - RTUNICP const uc = RTStrGetCp(pszCur); - if ( uc < 0x20 - || (uc >= 0x7f && uc <= 0x9f)) - return VERR_INVALID_NAME; - } - rc = RTStrValidateEncoding(pszDestination); - if (RT_FAILURE(rc)) - return rc; - rc = RTStrCopy(pszHostPath, cbHostPath, pszDestination); - if (RT_FAILURE(rc)) - return rc; - - char *pszCopy = RTStrDup(pszTransferPath); - if (!pszCopy) - return VERR_NO_MEMORY; - - char *psz = pszCopy; - while (RT_SUCCESS(rc) && *psz) - { - char *pszSlash = strchr(psz, '/'); - if (pszSlash) - *pszSlash = '\0'; - if ( !*psz - || !strcmp(psz, ".") - || !strcmp(psz, "..") - || (pszSlash && !pszSlash[1])) - { - rc = VERR_INVALID_NAME; - break; - } - rc = RTPathAppend(pszHostPath, cbHostPath, psz); - if (!pszSlash) - break; - psz = pszSlash + 1; - } - - RTStrFree(pszCopy); - return rc; -} diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp index ec2cd47bceb3..60f5f7725491 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-http.cpp 114990 2026-08-11 14:34:45Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-http.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts. */ @@ -881,9 +881,6 @@ static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv) rc = rc2; } - RTSemEventDestroy(pSrv->StatusEvent); - pSrv->StatusEvent = NIL_RTSEMEVENT; - LogFlowFuncLeaveRC(rc); return rc; } @@ -899,19 +896,11 @@ static int shClTransferHttpServerInitInternal(PSHCLHTTPSERVER pSrv) ASMAtomicXchgBool(&pSrv->fInitialized, false); ASMAtomicXchgBool(&pSrv->fRunning, false); pSrv->fStopping = false; - pSrv->StatusEvent = NIL_RTSEMEVENT; pSrv->hHTTPServer = NIL_RTHTTPSERVER; int rc = RTCritSectInit(&pSrv->CritSect); AssertRCReturn(rc, rc); - rc = RTSemEventCreate(&pSrv->StatusEvent); - if (RT_FAILURE(rc)) - { - RTCritSectDelete(&pSrv->CritSect); - return rc; - } - pSrv->uPort = 0; RTListInit(&pSrv->lstTransfers); pSrv->cTransfers = 0; @@ -921,8 +910,6 @@ static int shClTransferHttpServerInitInternal(PSHCLHTTPSERVER pSrv) rc = RTHttpServerResponseInit(&pSrv->Resp); if (RT_FAILURE(rc)) { - RTSemEventDestroy(pSrv->StatusEvent); - pSrv->StatusEvent = NIL_RTSEMEVENT; RTCritSectDelete(&pSrv->CritSect); return rc; } @@ -1031,26 +1018,6 @@ int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort) return rc; } -/** - * Returns a Shared Clipboard HTTP server status as a string. - * - * @returns Status as a string, or "Unknown" if invalid / unknown. - * @param enmStatus HTTP server status to return as a string. - */ -DECLINLINE(const char *) shClTransferHttpServerStatusToStr(SHCLHTTPSERVERSTATUS enmStatus) -{ - switch (enmStatus) - { - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_NONE); - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_STARTED); - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_STOPPED); - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED); - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_TRANSFER_UNREGISTERED); - } - - AssertFailedReturn("Unknown"); -} - /** * Starts the Shared Clipboard HTTP server instance using a random port (>= 49152). * @@ -1488,46 +1455,9 @@ static SHCLHTTPSERVERSTATUS shclTransferHttpServerSetStatusLocked(PSHCLHTTPSERVE pSrv->enmStatus = enmStatus; LogFlowFunc(("fStatus=%#x\n", pSrv->enmStatus)); - int rc2 = RTSemEventSignal(pSrv->StatusEvent); - AssertRC(rc2); - return pSrv->enmStatus; } -/** - * Returns the first transfer in the list. - * - * @returns Pointer to first transfer if found, or NULL if not found. - * @param pSrv HTTP server instance. - */ -PSHCLTRANSFER ShClTransferHttpServerGetTransferFirst(PSHCLHTTPSERVER pSrv) -{ - shClTransferHttpServerLock(pSrv); - - PSHCLHTTPSERVERTRANSFER pHttpTransfer = RTListGetFirst(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node); - - shClTransferHttpServerUnlock(pSrv); - - return pHttpTransfer ? pHttpTransfer->pTransfer : NULL; -} - -/** - * Returns the last transfer in the list. - * - * @returns Pointer to last transfer if found, or NULL if not found. - * @param pSrv HTTP server instance. - */ -PSHCLTRANSFER ShClTransferHttpServerGetTransferLast(PSHCLHTTPSERVER pSrv) -{ - shClTransferHttpServerLock(pSrv); - - PSHCLHTTPSERVERTRANSFER pHttpTransfer = RTListGetLast(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node); - - shClTransferHttpServerUnlock(pSrv); - - return pHttpTransfer ? pHttpTransfer->pTransfer : NULL; -} - /** * Returns a transfer for a specific ID. * @@ -1548,25 +1478,6 @@ bool ShClTransferHttpServerGetTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTr return pTransfer; } -/** - * Returns the used TCP port number of a HTTP server instance. - * - * @returns TCP port number. 0 if not specified yet. - * @param pSrv HTTP server instance to return port for. - */ -uint16_t ShClTransferHttpServerGetPort(PSHCLHTTPSERVER pSrv) -{ - AssertPtrReturn(pSrv, 0); - - shClTransferHttpServerLock(pSrv); - - const uint16_t uPort = pSrv->uPort; - - shClTransferHttpServerUnlock(pSrv); - - return uPort; -} - /** * Returns the number of registered HTTP server transfers of a HTTP server instance. * @@ -1791,62 +1702,6 @@ bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv) return ASMAtomicReadBool(&pSrv->fRunning); } -/** - * Waits for a server status change. - * - * @returns VBox status code. - * @retval VERR_STATE_CHANGED if the HTTP server was uninitialized. - * @param pSrv HTTP server instance to wait for. - * @param fStatus Status to wait for. - * Multiple statuses are possible, @sa SHCLHTTPSERVERSTATUS. - * @param msTimeout Timeout (in ms) to wait. - */ -int ShClTransferHttpServerWaitForStatusChange(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS fStatus, RTMSINTERVAL msTimeout) -{ - AssertPtrReturn(pSrv, VERR_INVALID_POINTER); - AssertMsgReturn(ASMAtomicReadBool(&pSrv->fInitialized), ("Server not initialized yet\n"), VERR_WRONG_ORDER); - - shClTransferHttpServerLock(pSrv); - - uint64_t const tsStartMs = RTTimeMilliTS(); - - int rc = VERR_TIMEOUT; - - LogFlowFunc(("fStatus=%#x, msTimeout=%RU32 -- current is %#x\n", fStatus, msTimeout, pSrv->enmStatus)); - - while (RTTimeMilliTS() - tsStartMs <= msTimeout) - { - if (!pSrv->fInitialized) - { - rc = VERR_STATE_CHANGED; - break; - } - - shClTransferHttpServerUnlock(pSrv); /* Leave lock before waiting. */ - - rc = RTSemEventWait(pSrv->StatusEvent, msTimeout); - - shClTransferHttpServerLock(pSrv); - - if (RT_FAILURE(rc)) - break; - - LogFlowFunc(("Current status now is: %#x\n", pSrv->enmStatus)); - LogRel2(("Shared Clipboard: HTTP server entered status '%s'\n", shClTransferHttpServerStatusToStr(pSrv->enmStatus))); - - if (pSrv->enmStatus & fStatus) - { - rc = VINF_SUCCESS; - break; - } - } - - shClTransferHttpServerUnlock(pSrv); - - LogFlowFuncLeaveRC(rc); - return rc; -} - /********************************************************************************************************************************* * Public Shared Clipboard HTTP context functions * diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index 1cb4171d22aa..5f36d7930644 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 114890 2026-08-07 09:54:48Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -475,48 +475,6 @@ void ShClTransferListHandleInfoDestroy(PSHCLLISTHANDLEINFO pInfo) } } -/** - * Allocates a transfer list header structure. - * - * @returns VBox status code. - * @param ppListHdr Where to store the allocated transfer list header structure on success. - */ -int ShClTransferListHdrAlloc(PSHCLLISTHDR *ppListHdr) -{ - int rc; - - PSHCLLISTHDR pListHdr = (PSHCLLISTHDR)RTMemAllocZ(sizeof(SHCLLISTHDR)); - if (pListHdr) - { - *ppListHdr = pListHdr; - rc = VINF_SUCCESS; - } - else - rc = VERR_NO_MEMORY; - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Frees a transfer list header structure. - * - * @param pListEntry Transfer list header structure to free. - * The pointer will be invalid on return. - */ -void ShClTransferListHdrFree(PSHCLLISTHDR pListHdr) -{ - if (!pListHdr) - return; - - LogFlowFuncEnter(); - - ShClTransferListHdrDestroy(pListHdr); - - RTMemFree(pListHdr); - pListHdr = NULL; -} - /** * Duplicates (allocates) a transfer list header structure. * @@ -643,32 +601,6 @@ int ShClTransferListOpenParmsCopy(PSHCLLISTOPENPARMS pDst, PSHCLLISTOPENPARMS pS return rc; } -/** - * Duplicates a transfer list open parameters structure. - * - * @returns Duplicated transfer list open parameters structure on success, or NULL on failure. - * @param pParms Transfer list open parameters structure to duplicate. - */ -PSHCLLISTOPENPARMS ShClTransferListOpenParmsDup(PSHCLLISTOPENPARMS pParms) -{ - AssertPtrReturn(pParms, NULL); - - PSHCLLISTOPENPARMS pParmsDup = (PSHCLLISTOPENPARMS)RTMemAllocZ(sizeof(SHCLLISTOPENPARMS)); - if (!pParmsDup) - return NULL; - - int rc = ShClTransferListOpenParmsCopy(pParmsDup, pParms); - if (RT_FAILURE(rc)) - { - ShClTransferListOpenParmsDestroy(pParmsDup); - - RTMemFree(pParmsDup); - pParmsDup = NULL; - } - - return pParmsDup; -} - /** * Initializes a transfer list open parameters structure. * @@ -981,47 +913,6 @@ bool ShClTransferListEntryIsValid(PSHCLLISTENTRY pListEntry) * Transfer Object * ********************************************************************************************************************************/ -/** - * Initializes a transfer object context. - * - * @returns VBox status code. - * @param pObjCtx Transfer object context to initialize. - */ -int ShClTransferObjCtxInit(PSHCLCLIENTTRANSFEROBJCTX pObjCtx) -{ - AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER); - - LogFlowFuncEnter(); - - pObjCtx->uHandle = NIL_SHCLOBJHANDLE; - - return VINF_SUCCESS; -} - -/** - * Destroys a transfer object context. - * - * @param pObjCtx Transfer object context to destroy. - */ -void ShClTransferObjCtxDestroy(PSHCLCLIENTTRANSFEROBJCTX pObjCtx) -{ - AssertPtrReturnVoid(pObjCtx); - - LogFlowFuncEnter(); -} - -/** - * Returns if a transfer object context is valid or not. - * - * @returns \c true if valid, \c false if not. - * @param pObjCtx Transfer object context to check. - */ -bool ShClTransferObjCtxIsValid(PSHCLCLIENTTRANSFEROBJCTX pObjCtx) -{ - return ( pObjCtx - && pObjCtx->uHandle != NIL_SHCLOBJHANDLE); -} - /** * Initializes a transfer object structure. * @@ -1109,37 +1000,6 @@ int ShClTransferObjOpenParmsInit(PSHCLOBJOPENCREATEPARMS pParms) return rc; } -/** - * Copies a transfer object open parameters structure from source to destination. - * - * @returns VBox status code. - * @param pParmsDst Where to copy the source transfer object open parameters to. - * @param pParmsSrc Which source transfer object open parameters to copy. - */ -int ShClTransferObjOpenParmsCopy(PSHCLOBJOPENCREATEPARMS pParmsDst, PSHCLOBJOPENCREATEPARMS pParmsSrc) -{ - int rc; - - *pParmsDst = *pParmsSrc; - - if (pParmsSrc->pszPath) - { - Assert(pParmsSrc->cbPath); - pParmsDst->pszPath = RTStrDup(pParmsSrc->pszPath); - if (pParmsDst->pszPath) - { - rc = VINF_SUCCESS; - } - else - rc = VERR_NO_MEMORY; - } - else - rc = VINF_SUCCESS; - - LogFlowFuncLeaveRC(rc); - return rc; -} - /** * Destroys a transfer object open parameters structure. * @@ -1579,23 +1439,6 @@ int ShClTransferDestroy(PSHCLTRANSFER pTransfer) -/** - * Returns whether a transfer is in a running state or not. - * - * @returns @c true if in running state, or @c false if not. - * @param pTransfer Clipboard transfer to return status for. - */ -bool ShClTransferIsRunning(PSHCLTRANSFER pTransfer) -{ - shClTransferLock(pTransfer); - - bool const fRunning = pTransfer->State.enmStatus == SHCLTRANSFERSTATUS_STARTED; - - shClTransferUnlock(pTransfer); - - return fRunning; -} - /** * Returns whether a transfer has been (successfully) completed or not. * @@ -1807,28 +1650,6 @@ PSHCLLISTHANDLEINFO ShClTransferListGetByHandle(PSHCLTRANSFER pTransfer, SHCLLIS return NULL; } -/** - * Returns the a transfer object of a transfer list. - * - * Currently not implemented and wil return NULL. - * - * @returns Pointer to transfer object, or NULL if not found / invalid. - * @param pTransfer Clipboard transfer to return transfer object for. - * @param hList Handle of clipboard transfer list to get object for. - * @param uIdx Index of object to get. - */ -PSHCLTRANSFEROBJ ShClTransferListGetObj(PSHCLTRANSFER pTransfer, - SHCLLISTHANDLE hList, uint64_t uIdx) -{ - AssertPtrReturn(pTransfer, NULL); - - RT_NOREF(hList, uIdx); - - LogFlowFunc(("hList=%RU64\n", hList)); - - return NULL; -} - /** * Reads a single transfer list entry. * @@ -1886,31 +1707,6 @@ int ShClTransferListWrite(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, return rc; } -/** - * Returns whether a given transfer list handle is valid or not. - * - * @returns \c true if list handle is valid, \c false if not. - * @param pTransfer Clipboard transfer to handle. - * @param hList List handle to check. - */ -bool ShClTransferListHandleIsValid(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList) -{ - bool fIsValid = false; - - if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL) - { - fIsValid = ShClTransferListGetByHandle(pTransfer, hList) != NULL; - } - else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE) - { - AssertFailed(); /** @todo Implement. */ - } - else - AssertFailedStmt(fIsValid = false); - - return fIsValid; -} - /** * Copies a transfer callback table from source to destination. * @@ -2378,60 +2174,6 @@ int ShClTransferRootsSetFromStringList(PSHCLTRANSFER pTransfer, const char *pszR return ShClTransferRootsSetFromStringListEx(pTransfer, pszRoots, cbRoots, SHCL_TRANSFER_URI_LIST_SEP_STR); } -/** - * Sets the root list entries for a given clipboard transfer, UTF-16 (Unicode) version. - * - * @returns VBox status code. - * @param pTransfer Transfer to set transfer list entries for. - * @param pwszRoots Unicode string list (separated by CRLF) of root entries to set. - * All entries must have the same root path. - * @param cbRoots Size (in bytes) of string list. Includes zero terminator. - * - * @note Accepts local paths or URI string lists (absolute only). - */ -int ShClTransferRootsSetFromStringListUnicode(PSHCLTRANSFER pTransfer, PRTUTF16 pwszRoots, size_t cbRoots) -{ - AssertPtrReturn(pwszRoots, VERR_INVALID_POINTER); - AssertReturn(cbRoots, VERR_INVALID_PARAMETER); - AssertReturn(cbRoots % sizeof(RTUTF16) == 0, VERR_INVALID_PARAMETER); - - size_t cwcRoots = cbRoots / sizeof(RTUTF16); - - /* This may slightly overestimate the space needed. */ -#if 0 - size_t cbDst = 0; - int rc = ShClHlpUtf16LenUtf8(pwszRoots, cwcRoots, &cbDst); - if (RT_SUCCESS(rc)) - { - cbDst++; /* Add space for terminator. */ - - char *pszDst = (char *)RTStrAlloc(cbDst); - if (pszDst) - { - size_t cbActual = 0; - rc = ShClHlpConvUtf16CRLFToUtf8LF(pwszRoots, cwcRoots, pszDst, cbDst, &cbActual); - if (RT_SUCCESS(rc)) - rc = ShClTransferRootsSetFromStringList(pTransfer, pszDst, cbActual + 1 /* Include terminator */); - - RTStrFree(pszDst); - } - else - rc = VERR_NO_MEMORY; - } -#else - char *pszTmp = NULL; - size_t cbLenSansTerm = 0; - int rc = ShClHlpConvUtf16CRLFToUtf8LFA(pwszRoots, cwcRoots, &pszTmp, &cbLenSansTerm); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferRootsSetFromStringList(pTransfer, pszTmp, cbLenSansTerm + 1 /* Include terminator */); - RTMemFree(pszTmp); - } -#endif - - return rc; -} - /** * Sets a single path as a transfer root. * @@ -2532,29 +2274,6 @@ SHCLTRANSFERDIR ShClTransferGetDir(PSHCLTRANSFER pTransfer) return enmDir; } -/** - * Returns the absolute root path of a transfer. - * - * @returns VBox status code. - * @param pTransfer Clipboard transfer to return absolute root path for. - * @param pszPath Where to store the returned path. - * @param cbPath Size (in bytes) of \a pszPath. - */ -int ShClTransferGetRootPathAbs(PSHCLTRANSFER pTransfer, char *pszPath, size_t cbPath) -{ - AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); - - shClTransferLock(pTransfer); - - AssertMsgReturn(pTransfer->pszPathRootAbs, ("Transfer has no root path set (yet)\n"), VERR_WRONG_ORDER); - - int const rc = RTStrCopy(pszPath, cbPath, pTransfer->pszPathRootAbs); - - shClTransferUnlock(pTransfer); - - return rc; -} - /** * Returns the transfer's source. * @@ -2980,25 +2699,6 @@ static int shClTransferWaitForStatusChangeInternal(PSHCLTRANSFER pTransfer, RTMS return rc; } -/** - * Waits for the transfer status to change. - * - * @returns VBox status code. - * @param pTransfer Clipboard transfer to wait for. - * @param msTimeout Timeout (in ms) to wait. - * @param penmStatus Where to return the new (current) transfer status on success. - * Optional and can be NULL. - */ -int ShClTransferWaitForStatusChange(PSHCLTRANSFER pTransfer, RTMSINTERVAL msTimeout, SHCLTRANSFERSTATUS *penmStatus) -{ - AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); - - int rc = shClTransferWaitForStatusChangeInternal(pTransfer, msTimeout, penmStatus); - - LogFlowFuncLeaveRC(rc); - return rc; -} - /** * Waits for a specific transfer status. * @@ -3080,27 +2780,19 @@ int ShClTransferCtxInit(PSHCLTRANSFERCTX pTransferCtx) int rc = RTCritSectInit(&pTransferCtx->CritSect); if (RT_SUCCESS(rc)) { - rc = RTSemEventCreate(&pTransferCtx->ChangedEvent); - if (RT_SUCCESS(rc)) - { - RT_ZERO(pTransferCtx->ChangedEventData); + RTListInit(&pTransferCtx->List); - RTListInit(&pTransferCtx->List); - - pTransferCtx->cTransfers = 0; - pTransferCtx->cRunning = 0; - pTransferCtx->cMaxRunning = 64; /** @todo Make this configurable? */ - pTransferCtx->cTransferIdsUsed = 0; - pTransferCtx->idSession = NIL_SHCLSESSIONID; - pTransferCtx->uNextGeneration = 1; + pTransferCtx->cTransfers = 0; + pTransferCtx->cRunning = 0; + pTransferCtx->cMaxRunning = 64; /** @todo Make this configurable? */ + pTransferCtx->cTransferIdsUsed = 0; + pTransferCtx->idSession = NIL_SHCLSESSIONID; + pTransferCtx->uNextGeneration = 1; - RT_ZERO(pTransferCtx->bmTransferIds); - RT_ZERO(pTransferCtx->bmTransferIdsUsed); + RT_ZERO(pTransferCtx->bmTransferIds); + RT_ZERO(pTransferCtx->bmTransferIdsUsed); - ShClTransferCtxReset(pTransferCtx); - } - else - RTCritSectDelete(&pTransferCtx->CritSect); + ShClTransferCtxReset(pTransferCtx); } return rc; @@ -3132,9 +2824,6 @@ void ShClTransferCtxDestroy(PSHCLTRANSFERCTX pTransferCtx) shClTransferCtxUnlock(pTransferCtx); - RTSemEventDestroy(pTransferCtx->ChangedEvent); - pTransferCtx->ChangedEvent = NIL_RTSEMEVENT; - if (RTCritSectIsInitialized(&pTransferCtx->CritSect)) RTCritSectDelete(&pTransferCtx->CritSect); } @@ -3180,7 +2869,6 @@ int ShClTransferCtxBeginSession(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idS int rc; if (pTransferCtx->cTransfers == 0) { - RT_ZERO(pTransferCtx->ChangedEventData); RT_ZERO(pTransferCtx->bmTransferIds); RT_ZERO(pTransferCtx->bmTransferIdsUsed); pTransferCtx->idSession = idSession; @@ -3201,26 +2889,6 @@ int ShClTransferCtxBeginSession(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idS return rc; } -/** - * Signals a change event. - * - * @returns VBox status code. - * @param pTransferCtx Transfer context to return transfer for. - * @param fRegistered Whether a transfer got registered or unregistered. - * @param pTransfer Transfer bound to the event. - */ -static int shClTransferCtxSignal(PSHCLTRANSFERCTX pTransferCtx, bool fRegistered, PSHCLTRANSFER pTransfer) -{ - Assert(RTCritSectIsOwner(&pTransferCtx->CritSect)); - - LogFlowFunc(("fRegistered=%RTbool, pTransfer=%p\n", fRegistered, pTransfer)); - - pTransferCtx->ChangedEventData.fRegistered = fRegistered; - pTransferCtx->ChangedEventData.pTransfer = pTransfer; - - return RTSemEventSignal(pTransferCtx->ChangedEvent); -} - /** * Returns a specific clipboard transfer, internal version. * @@ -3362,25 +3030,6 @@ PSHCLTRANSFER ShClTransferCtxGetTransferLast(PSHCLTRANSFERCTX pTransferCtx) return pTransfer; } -/** - * Returns the number of running clipboard transfers for a given transfer context. - * - * @returns Number of running transfers. - * @param pTransferCtx Transfer context to return number for. - */ -uint32_t ShClTransferCtxGetRunningTransfers(PSHCLTRANSFERCTX pTransferCtx) -{ - AssertPtrReturn(pTransferCtx, 0); - - shClTransferCtxLock(pTransferCtx); - - uint32_t const cRunning = pTransferCtx->cRunning; - - shClTransferCtxUnlock(pTransferCtx); - - return cRunning; -} - /** * Returns the number of total clipboard transfers for a given transfer context. * @@ -3466,28 +3115,6 @@ static int shClTransferCreateIDInternal(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANS return VERR_SHCLPB_MAX_TRANSFERS_REACHED; } -/** - * Creates a new transfer ID for a given transfer context. - * - * @returns VBox status code. - * @retval VERR_SHCLPB_MAX_TRANSFERS_REACHED if the maximum of concurrent transfers is reached. - * @param pTransferCtx Transfer context to create transfer ID for. - * @param pidTransfer Where to return the transfer ID on success. - */ -int ShClTransferCtxCreateId(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFERID pidTransfer) -{ - AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); - AssertPtrReturn(pidTransfer, VERR_INVALID_POINTER); - - shClTransferCtxLock(pTransferCtx); - - int rc = shClTransferCreateIDInternal(pTransferCtx, pidTransfer); - - shClTransferCtxUnlock(pTransferCtx); - - return rc; -} - /** * Registers a clipboard transfer with a new transfer ID. * @@ -3514,7 +3141,7 @@ static int shClTransferCtxTransferRegisterExInternal(PSHCLTRANSFERCTX pTransferC pTransferCtx->cTransfers++; - int rc = shClTransferCtxSignal(pTransferCtx, true /* fRegistered */, pTransfer); + int rc = VINF_SUCCESS; Log2Func(("pTransfer=%p, idTransfer=%RU32, idSession=%RU16, uGeneration=%RU64 -- now %RU16 transfer(s)\n", pTransfer, idTransfer, pTransferCtx->idSession, pTransfer->State.uGeneration, pTransferCtx->cTransfers)); @@ -3677,8 +3304,6 @@ int ShClTransferCtxUnregisterById(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID if (pTransfer) { shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer); - - rc = shClTransferCtxSignal(pTransferCtx, false /* fRegistered */, pTransfer); } else if (ASMBitTest(&pTransferCtx->bmTransferIds[0], idTransfer)) { @@ -3694,131 +3319,6 @@ int ShClTransferCtxUnregisterById(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID return rc; } -/** - * Waits for a transfer context event. - * - * @returns VBox status code. - * @param pTransferCtx Transfer context to wait for. - * @param msTimeout Timeout (in ms) to wait. - * @param pEvent Where to return the event data on success. - */ -static int shClTransferCtxWaitInternal(PSHCLTRANSFERCTX pTransferCtx, RTMSINTERVAL msTimeout, PSHCLTRANSFERCTXEVENT pEvent) -{ - LogFlowFunc(("Waiting for transfer context change (%RU32 timeout) ...\n", msTimeout)); - - int rc = RTSemEventWait(pTransferCtx->ChangedEvent, msTimeout); - if (RT_SUCCESS(rc)) - { - shClTransferCtxLock(pTransferCtx); - - memcpy(pEvent, &pTransferCtx->ChangedEventData, sizeof(SHCLTRANSFERCTXEVENT)); - - shClTransferCtxUnlock(pTransferCtx); - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Waits for transfer to be (un-)registered. - * - * @returns VBox status code. - * @param pTransferCtx Transfer context to wait for. - * @param msTimeout Timeout (in ms) to wait. - * @param fRegister Pass \c true for registering, or \c false for unregistering a transfer. - * @param idTransfer Transfer ID to wait for. - * Pass NIL_SHCLTRANSFERID for any transfer. - * @param ppTransfer Where to return the transfer being (un-)registered. Optional and can be NULL. - */ -int ShClTransferCtxWait(PSHCLTRANSFERCTX pTransferCtx, RTMSINTERVAL msTimeout, bool fRegister, SHCLTRANSFERID idTransfer, - PSHCLTRANSFER *ppTransfer) -{ - AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); - - int rc = VERR_TIMEOUT; - - uint64_t const tsStartMs = RTTimeMilliTS(); - uint64_t msLeft = msTimeout; - for (;;) - { - SHCLTRANSFERCTXEVENT Event; - rc = shClTransferCtxWaitInternal(pTransferCtx, msLeft, &Event); - if (RT_FAILURE(rc)) - break; - - shClTransferCtxLock(pTransferCtx); - - if (Event.fRegistered == fRegister) - { - if ( Event.pTransfer - && ( idTransfer == NIL_SHCLTRANSFERID - || ShClTransferGetID(Event.pTransfer) == idTransfer)) - { - if (ppTransfer) - *ppTransfer = Event.pTransfer; - rc = VINF_SUCCESS; - } - } - - shClTransferCtxUnlock(pTransferCtx); - - if (RT_SUCCESS(rc)) - break; - - msLeft -= RT_MIN(msLeft, RTTimeMilliTS() - tsStartMs); - if (msLeft == 0) - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Cleans up all associated transfers which are not needed (anymore). - * This can be due to transfers which only have been announced but not / never being run. - * - * @param pTransferCtx Transfer context to cleanup transfers for. - */ -void ShClTransferCtxCleanup(PSHCLTRANSFERCTX pTransferCtx) -{ - AssertPtrReturnVoid(pTransferCtx); - - shClTransferCtxLock(pTransferCtx); - - LogFlowFunc(("pTransferCtx=%p, cTransfers=%RU16 cRunning=%RU16\n", - pTransferCtx, pTransferCtx->cTransfers, pTransferCtx->cRunning)); - - if (pTransferCtx->cTransfers == 0) - { - shClTransferCtxUnlock(pTransferCtx); - return; - } - - /* Remove all transfers which are not in a running state (e.g. only announced). */ - PSHCLTRANSFER pTransfer, pTransferNext; - RTListForEachSafe(&pTransferCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node) - { - shClTransferLock(pTransfer); - - SHCLTRANSFERSTATUS const enmStatus = shClTransferGetStatusLocked(pTransfer); - LogFlowFunc(("\tTransfer #%RU16: %s\n", pTransfer->State.uID, ShClTransferStatusToStr(enmStatus))); - - if (enmStatus != SHCLTRANSFERSTATUS_STARTED) - { - shClTransferUnlock(pTransfer); - - shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer); - ShClTransferDestroy(pTransfer); - } - else - shClTransferUnlock(pTransfer); - } - - shClTransferCtxUnlock(pTransferCtx); -} - /** * Returns whether the maximum of concurrent transfers of a specific transfer contexthas been reached or not. * diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp index 8d3fd36852bf..343b39137690 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -1290,17 +1290,6 @@ static void shClX11SetCallbacksInternal(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallba RT_ZERO(pCtx->Callbacks); } -/** - * Sets the callback table. - * - * @param pCtx The clipboard context. - * @param pCallbacks Callback table to set. If NULL, the current callback table will be cleared. - */ -void ShClX11SetCallbacks(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks) -{ - shClX11SetCallbacksInternal(pCtx, pCallbacks); -} - /** * Initializes a X11 context of the Shared Clipboard. * diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp index 9f717f721aa1..9be0528a0931 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.cpp 114890 2026-08-07 09:54:48Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal code for transfer (list) handling. */ @@ -1556,43 +1556,6 @@ int ShClSvcTransferMsgHostHandler(uint32_t u32Function, return rc; } -int shClSvcTransferHostMsgHandler(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) -{ - RT_NOREF(pClient); - - int rc; - - switch (pMsg->idMsg) - { - default: - rc = VINF_SUCCESS; - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Reports a transfer status to the guest. - * - * @returns VBox status code. - * @param pClient Client that owns the transfer. - * @param pTransfer Transfer to report status for. - * @param enmSts Status to report. - * @param rcTransfer Result code to report. Optional and depending on status. - * @param ppEvent Where to return the wait event on success. Optional. - * Must be released by the caller with ShClEventRelease(). - */ -int ShClSvcTransferSendStatusAsync(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS enmSts, - int rcTransfer, PSHCLEVENT *ppEvent) -{ - return shClSvcTransferSendStatusAsync(pClient, pTransfer, enmSts, rcTransfer, ppEvent); -} - - - - /** * Starts a transfer, communicating the status to the guest side. * @@ -1621,49 +1584,6 @@ int ShClSvcTransferStart(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) return rc; } -/** - * Stops (and destroys) a transfer, communicating the status to the guest side. - * - * @returns VBox status code. - * @param pClient Client that owns the transfer. - * @param pTransfer Transfer to stop. The pointer will be invalid on success. - * @param fWaitForGuest Set to \c true to wait for acknowledgement from guest, or \c false to skip waiting. - */ -int ShClSvcTransferStop(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, bool fWaitForGuest) -{ - LogRel2(("Shared Clipboard: Stopping transfer %RU16 ...\n", pTransfer->State.uID)); - - ShClSvcClientLock(pClient); - - PSHCLEVENT pEvent; - int rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, - SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS, &pEvent); - if ( RT_SUCCESS(rc) - && fWaitForGuest) - { - LogRel2(("Shared Clipboard: Waiting for stop of transfer %RU16 on guest ...\n", pTransfer->State.uID)); - - ShClSvcClientUnlock(pClient); - - rc = ShClEventWait(pEvent, pTransfer->uTimeoutMs, NULL /* ppPayload */); - if (RT_SUCCESS(rc)) - LogRel2(("Shared Clipboard: Stopped transfer %RU16 on guest\n", pTransfer->State.uID)); - - ShClEventRelease(pEvent); - - ShClSvcClientLock(pClient); - } - - if (RT_FAILURE(rc)) - LogRelMax(16, ("Shared Clipboard: Unable to stop transfer %RU16 on guest, rc=%Rrc\n", - pTransfer->State.uID, rc)); - - ShClSvcClientUnlock(pClient); - - LogFlowFuncLeaveRC(rc); - return rc; -} - /** * Sets the host service's (file) transfer mode. * diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h index 679d36950dbe..b5fc0e67af59 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.h 114423 2026-06-18 07:53:57Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.h 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal header for transfer (list) handling. */ @@ -39,9 +39,6 @@ int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURC void ShClSvcTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); int ShClSvcTransferInit(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); int ShClSvcTransferStart(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); -int ShClSvcTransferStop(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, bool fWaitForGuest); -int ShClSvcTransferSendStatusAsync(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS uStatus, int rcTransfer, PSHCLEVENT *ppEvent); -int ShClSvcTransferRootListReadFromGuest(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); void shClSvcTransferDestroyAll(PSHCLCLIENT pClient); #endif /* !VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_transfers_h */ From dbe2d9f4ddfebdd23edd7bd34d8144578252bf20 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 14:58:27 +0000 Subject: [PATCH 139/176] Shared Clipboard: Saved state fixes / error checking. bugref:4697 svn:sync-xref-src-repo-rev: r174887 --- .../VBoxSharedClipboardSvc.cpp | 94 +++++++++++++------ 1 file changed, 65 insertions(+), 29 deletions(-) diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp index 0578021aee23..f7316a694318 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.cpp 115046 2026-08-17 14:58:27Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host service entry points. */ @@ -529,12 +529,13 @@ static DECLCALLBACK(int) shClSvcSaveState(void *, uint32_t u32ClientID, void *pv PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); - pClient->State.uMode = ShClSvcGetMode(); + ASMAtomicWriteU32(&pClient->State.uMode, ShClSvcGetMode()); /* Write Shared Clipboard saved state version. */ - pVMM->pfnSSMR3PutU32(pSSM, VBOX_SHCL_SAVED_STATE_VER_CURRENT); + int rc = pVMM->pfnSSMR3PutU32(pSSM, VBOX_SHCL_SAVED_STATE_VER_CURRENT); + AssertRCReturn(rc, rc); - int rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL); + rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL); AssertRCReturn(rc, rc); rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /*fFlags*/, &s_aShClSSMClientPODState[0], NULL); @@ -543,29 +544,50 @@ static DECLCALLBACK(int) shClSvcSaveState(void *, uint32_t u32ClientID, void *pv rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /*fFlags*/, &s_aShClSSMClientTransferState[0], NULL); AssertRCReturn(rc, rc); - /* Serialize the client's internal message queue. */ - rc = pVMM->pfnSSMR3PutU64(pSSM, pClient->cMsgAllocated); - AssertRCReturn(rc, rc); + /* Serialize a stable view of the queued messages. cMsgAllocated also + * includes messages temporarily owned by producers and consumers. */ + ShClSvcClientLock(pClient); + uint64_t cMsgs = 0; PSHCLCLIENTMSG pMsg; + RTListForEach(&pClient->MsgQueue, pMsg, SHCLCLIENTMSG, ListEntry) + cMsgs++; + + rc = pVMM->pfnSSMR3PutU64(pSSM, cMsgs); + RTListForEach(&pClient->MsgQueue, pMsg, SHCLCLIENTMSG, ListEntry) { - pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL); - pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL); + if (RT_FAILURE(rc)) + break; - for (uint32_t iParm = 0; iParm < pMsg->cParms; iParm++) - HGCMSvcSSMR3Put(&pMsg->aParms[iParm], pSSM, pVMM); - } + rc = pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, + &s_aShClSSMClientMsgHdr[0], NULL); + if (RT_SUCCESS(rc)) + rc = pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, + &s_aShClSSMClientMsgCtx[0], NULL); - rc = pVMM->pfnSSMR3PutU64(pSSM, pClient->Legacy.cCID); - AssertRCReturn(rc, rc); + for (uint32_t iParm = 0; iParm < pMsg->cParms && RT_SUCCESS(rc); iParm++) + rc = HGCMSvcSSMR3Put(&pMsg->aParms[iParm], pSSM, pVMM); + } + uint64_t cCID = 0; PSHCLCLIENTLEGACYCID pCID; + RTListForEach(&pClient->Legacy.lstCID, pCID, SHCLCLIENTLEGACYCID, Node) + cCID++; + + if (RT_SUCCESS(rc)) + rc = pVMM->pfnSSMR3PutU64(pSSM, cCID); + RTListForEach(&pClient->Legacy.lstCID, pCID, SHCLCLIENTLEGACYCID, Node) { - rc = pVMM->pfnSSMR3PutStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /*fFlags*/, &s_aShClSSMClientLegacyCID[0], NULL); - AssertRCReturn(rc, rc); + if (RT_FAILURE(rc)) + break; + rc = pVMM->pfnSSMR3PutStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /*fFlags*/, + &s_aShClSSMClientLegacyCID[0], NULL); } + + ShClSvcClientUnlock(pClient); + AssertRCReturn(rc, rc); #else /* UNIT_TEST */ RT_NOREF(u32ClientID, pvClient, pSSM, pVMM); #endif /* UNIT_TEST */ @@ -579,8 +601,9 @@ static int shClSvcLoadStateV0(uint32_t u32ClientID, void *pvClient, PSSMHANDLE p uint32_t uMarker; int rc = pVMM->pfnSSMR3GetU32(pSSM, &uMarker); /* Begin marker. */ - AssertRC(rc); - Assert(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */); + AssertRCReturn(rc, rc); + AssertLogRelMsgReturn(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */, + ("Invalid begin marker: %#RX32\n", uMarker), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); rc = pVMM->pfnSSMR3Skip(pSSM, sizeof(uint32_t)); /* Client ID */ AssertRCReturn(rc, rc); @@ -601,7 +624,8 @@ static int shClSvcLoadStateV0(uint32_t u32ClientID, void *pvClient, PSSMHANDLE p rc = pVMM->pfnSSMR3GetU32(pSSM, &uMarker); /* End marker. */ AssertRCReturn(rc, rc); - Assert(uMarker == UINT32_C(0x19920406) /* SSMR3STRUCT_END */); + AssertLogRelMsgReturn(uMarker == UINT32_C(0x19920406) /* SSMR3STRUCT_END */, + ("Invalid end marker: %#RX32\n", uMarker), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); return VINF_SUCCESS; } @@ -634,14 +658,16 @@ static DECLCALLBACK(int) shClSvcLoadState(void *, uint32_t u32ClientID, void *pv { if (lenOrVer >= VBOX_SHCL_SAVED_STATE_VER_6_1RC1) { - pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, - &s_aShClSSMClientState[0], NULL); - pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /* fFlags */, - &s_aShClSSMClientPODState[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, + &s_aShClSSMClientState[0], NULL); + AssertRCReturn(rc, rc); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /* fFlags */, + &s_aShClSSMClientPODState[0], NULL); } else - pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, - &s_aShClSSMClientState61B1[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, + &s_aShClSSMClientState61B1[0], NULL); + AssertRCReturn(rc, rc); rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /* fFlags */, &s_aShClSSMClientTransferState[0], NULL); AssertRCReturn(rc, rc); @@ -660,8 +686,9 @@ static DECLCALLBACK(int) shClSvcLoadState(void *, uint32_t u32ClientID, void *pv uint8_t abPadding[RT_UOFFSETOF(SHCLCLIENTMSG, aParms) + sizeof(VBOXHGCMSVCPARM) * 2]; } u; - pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, - &s_aShClSSMClientMsgHdr[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, + &s_aShClSSMClientMsgHdr[0], NULL); + AssertRCReturn(rc, rc); rc = pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL); AssertRCReturn(rc, rc); @@ -697,9 +724,18 @@ static DECLCALLBACK(int) shClSvcLoadState(void *, uint32_t u32ClientID, void *pv PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); AssertPtrReturn(pCID, VERR_NO_MEMORY); - pVMM->pfnSSMR3GetStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /* fFlags */, - &s_aShClSSMClientLegacyCID[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /* fFlags */, + &s_aShClSSMClientLegacyCID[0], NULL); + if (RT_FAILURE(rc)) + { + RTMemFree(pCID); + return rc; + } + + ShClSvcClientLock(pClient); RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); + pClient->Legacy.cCID++; + ShClSvcClientUnlock(pClient); } } } From 6efd645a548168c52dbe7430f6ecee4a01b5223c Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Mon, 17 Aug 2026 15:05:08 +0000 Subject: [PATCH 140/176] Devices/Graphics: allow an empty shader entry; cleanup debug code. svn:sync-xref-src-repo-rev: r174888 --- .../Devices/Graphics/DevVGA-SVGA3d-dx.cpp | 86 +++++++++---------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp index 1fd399de4543..471e86e6a7a2 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx.cpp 115042 2026-08-15 14:51:14Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx.cpp 115047 2026-08-17 15:05:08Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - Common code for DX backend interface. */ @@ -134,8 +134,41 @@ void vmsvga3dDXInitContextMobData(SVGADXContextMobFormat *p) p->csuaViewIds[i] = SVGA3D_INVALID_ID; } + +#ifdef DUMP_BITMAPS +static void vmsvga3dDXDrawDumpRenderTargets(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, const char *pszPrefix = NULL) +{ + for (uint32_t i = 0; i < SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS; ++i) + { + if (pDXContext->svgaDXContext.renderState.renderTargetViewIds[i] != SVGA3D_INVALID_ID) + { + SVGACOTableDXRTViewEntry *pRTViewEntry = &pDXContext->cot.paRTView[pDXContext->svgaDXContext.renderState.renderTargetViewIds[i]]; + Log(("Dump RT[%u] sid = %u rtvid = %u\n", i, pRTViewEntry->sid, pDXContext->svgaDXContext.renderState.renderTargetViewIds[i])); + + SVGA3dSurfaceImageId image; + image.sid = pRTViewEntry->sid; + image.face = 0; + image.mipmap = 0; + VMSVGA3D_MAPPED_SURFACE map; + int rc = vmsvga3dSurfaceMap(pThisCC, &image, NULL, VMSVGA3D_SURFACE_MAP_READ, VMSVGA3D_MAP_F_NONE, &map); + if (RT_SUCCESS(rc)) + { + vmsvga3dMapWriteBmpFile(&map, pszPrefix ? pszPrefix : "rt-"); + vmsvga3dSurfaceUnmap(pThisCC, &image, &map, /* fWritten = */ false); + } + else + Log(("Map failed %Rrc\n", rc)); + } + } +} +#endif + + DECLINLINE(void) dxPostDraw(PVMSVGA3DDXCONTEXT pDXContext) { +#ifdef DUMP_BITMAPS + vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); +#endif RT_ZERO(pDXContext->state.ia.vb.au32Modified); RT_ZERO(pDXContext->state.shader[0].shaderResources.au64Modified); RT_ZERO(pDXContext->state.shader[1].shaderResources.au64Modified); @@ -696,34 +729,6 @@ int vmsvga3dDXSetSamplers(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmdDX } -#ifdef DUMP_BITMAPS -static void vmsvga3dDXDrawDumpRenderTargets(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, const char *pszPrefix = NULL) -{ - for (uint32_t i = 0; i < SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS; ++i) - { - if (pDXContext->svgaDXContext.renderState.renderTargetViewIds[i] != SVGA3D_INVALID_ID) - { - SVGACOTableDXRTViewEntry *pRTViewEntry = &pDXContext->cot.paRTView[pDXContext->svgaDXContext.renderState.renderTargetViewIds[i]]; - Log(("Dump RT[%u] sid = %u rtvid = %u\n", i, pRTViewEntry->sid, pDXContext->svgaDXContext.renderState.renderTargetViewIds[i])); - - SVGA3dSurfaceImageId image; - image.sid = pRTViewEntry->sid; - image.face = 0; - image.mipmap = 0; - VMSVGA3D_MAPPED_SURFACE map; - int rc = vmsvga3dSurfaceMap(pThisCC, &image, NULL, VMSVGA3D_SURFACE_MAP_READ, VMSVGA3D_MAP_F_NONE, &map); - if (RT_SUCCESS(rc)) - { - vmsvga3dMapWriteBmpFile(&map, pszPrefix ? pszPrefix : "rt-"); - vmsvga3dSurfaceUnmap(pThisCC, &image, &map, /* fWritten = */ false); - } - else - Log(("Map failed %Rrc\n", rc)); - } - } -} -#endif - int vmsvga3dDXDraw(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmdDXDraw const *pCmd) { int rc; @@ -738,9 +743,6 @@ int vmsvga3dDXDraw(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmdDXDraw co rc = pSvgaR3State->pFuncsDX->pfnDXDraw(pThisCC, pDXContext, pCmd->vertexCount, pCmd->startVertexLocation); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -759,9 +761,6 @@ int vmsvga3dDXDrawIndexed(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmdDX rc = pSvgaR3State->pFuncsDX->pfnDXDrawIndexed(pThisCC, pDXContext, pCmd->indexCount, pCmd->startIndexLocation, pCmd->baseVertexLocation); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -781,9 +780,6 @@ int vmsvga3dDXDrawInstanced(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmd rc = pSvgaR3State->pFuncsDX->pfnDXDrawInstanced(pThisCC, pDXContext, pCmd->vertexCountPerInstance, pCmd->instanceCount, pCmd->startVertexLocation, pCmd->startInstanceLocation); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -803,9 +799,6 @@ int vmsvga3dDXDrawIndexedInstanced(PVGASTATECC pThisCC, uint32_t idDXContext, SV rc = pSvgaR3State->pFuncsDX->pfnDXDrawIndexedInstanced(pThisCC, pDXContext, pCmd->indexCountPerInstance, pCmd->instanceCount, pCmd->startIndexLocation, pCmd->baseVertexLocation, pCmd->startInstanceLocation); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -824,9 +817,6 @@ int vmsvga3dDXDrawAuto(PVGASTATECC pThisCC, uint32_t idDXContext) rc = pSvgaR3State->pFuncsDX->pfnDXDrawAuto(pThisCC, pDXContext); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -2750,6 +2740,14 @@ static int dxSanitizeQueryEntry(PVMSVGA3DDXCONTEXT pDXContext, SVGACOTableDXQuer static int dxSanitizeShaderEntry(PVMSVGA3DDXCONTEXT pDXContext, SVGACOTableDXShaderEntry *pEntry) { RT_NOREF(pDXContext); + if (pEntry->type == SVGA3D_SHADERTYPE_INVALID) + { + ASSERT_GUEST_RETURN(pEntry->sizeInBytes == 0, VERR_INVALID_PARAMETER); + ASSERT_GUEST_RETURN(pEntry->offsetInBytes == 0, VERR_INVALID_PARAMETER); + ASSERT_GUEST_RETURN(pEntry->mobid == SVGA3D_INVALID_ID, VERR_INVALID_PARAMETER); + return VINF_SUCCESS; + } + ASSERT_GUEST_RETURN(pEntry->type >= SVGA3D_SHADERTYPE_MIN && pEntry->type < SVGA3D_SHADERTYPE_MAX, VERR_INVALID_PARAMETER); ASSERT_GUEST_RETURN(pEntry->sizeInBytes >= 8, VERR_INVALID_PARAMETER); /* Version Token + Length Token. */ return VINF_SUCCESS; From 5eeb0fac3e07b7e2fc90b5aae7e8830b4a64a150 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 15:07:54 +0000 Subject: [PATCH 141/176] Shared Clipboard: Internal interface cleanup. bugref:4697 svn:sync-xref-src-repo-rev: r174889 --- include/Makefile.kmk | 4 +- .../GuestHost/SharedClipboard-transfers.h | 33 +---- include/VBox/GuestHost/clipboard-helper.h | 25 +--- .../VBox/GuestHost/clipboard-transfers-http.h | 76 ++++++++++++ include/VBox/HostServices/VBoxClipboardSvc.h | 98 +++++++++++++++ .../VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp | 6 +- .../x11/VBoxClient/clipboard-x11.cpp | 5 +- .../SharedClipboard/clipboard-common.cpp | 116 ++---------------- .../clipboard-transfers-http.cpp | 4 +- .../SharedClipboard/clipboard-x11.cpp | 3 + .../testcase/tstClipboardHttpServer.cpp | 4 +- .../VBoxSharedClipboardSvc-client.cpp | 20 +-- .../VBoxSharedClipboardSvc-host.cpp | 4 +- .../VBoxSharedClipboardSvc-transfers.cpp | 18 +-- .../src-client/linux/ClipboardBackendX11.cpp | 4 +- 15 files changed, 228 insertions(+), 192 deletions(-) create mode 100644 include/VBox/GuestHost/clipboard-transfers-http.h diff --git a/include/Makefile.kmk b/include/Makefile.kmk index ed321b28d54c..cf4b6fbe9630 100644 --- a/include/Makefile.kmk +++ b/include/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114158 2026-05-20 15:21:01Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ ## @file # Some hacks to allow syntax and prerequisite include checking of headers. # This makefile doesn't and shouldn't build successfully. @@ -45,6 +45,7 @@ VBOX_HDRS_CPP_FEATURES := \ VBox/GuestHost/GuestControl.h \ VBox/GuestHost/DragAndDrop.h \ VBox/GuestHost/SharedClipboard-transfers.h \ + VBox/GuestHost/clipboard-transfers-http.h \ VBox/dbus.h \ VBox/xrandr.h \ VBox/VBoxCrHgsmi.h \ @@ -90,6 +91,7 @@ VBOX_HDRS_R3_ONLY := \ VBox/vscsi.h \ VBox/ExtPack/% \ VBox/GuestHost/SharedClipboard-transfers.h \ + VBox/GuestHost/clipboard-transfers-http.h \ VBox/GuestHost/SharedClipboard-win.h \ VBox/GuestHost/SharedClipboard-x11.h \ VBox/GuestHost/DragAndDrop.h \ diff --git a/include/VBox/GuestHost/SharedClipboard-transfers.h b/include/VBox/GuestHost/SharedClipboard-transfers.h index 1f2fb13abd72..4ee4d4669607 100644 --- a/include/VBox/GuestHost/SharedClipboard-transfers.h +++ b/include/VBox/GuestHost/SharedClipboard-transfers.h @@ -1,4 +1,4 @@ -/* $Id: SharedClipboard-transfers.h 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: SharedClipboard-transfers.h 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Shared transfer functions between host and guest. */ @@ -1257,37 +1257,6 @@ int ShClTransferCtxRegisterById(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTr int ShClTransferCtxUnregisterById(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID idTransfer); /** @} */ -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP -/** Namespace used as a prefix for HTTP(S) transfer URLs. */ -#define SHCL_HTTPT_URL_NAMESPACE "vbcl" - -/** @name Shared Clipboard HTTP context API. - * @{ - */ -int ShClTransferHttpServerMaybeStart(PSHCLHTTPCONTEXT pCtx); -int ShClTransferHttpServerMaybeStop(PSHCLHTTPCONTEXT pCtx); -/** @} */ - -/** @name Shared Clipboard HTTP server API. - * @{ - */ -int ShClTransferHttpServerInit(PSHCLHTTPSERVER pSrv); -int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv); -int ShClTransferHttpServerStart(PSHCLHTTPSERVER pSrv, unsigned cMaxAttempts, uint16_t *puPort); -int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort); -int ShClTransferHttpServerStop(PSHCLHTTPSERVER pSrv); -int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); -int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); -bool ShClTransferHttpServerGetTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer); -uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv); -char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv); -char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer, uint64_t idxEntry); -int ShClTransferHttpConvertToStringList(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer, char **ppszData, size_t *pcbData); -bool ShClTransferHttpServerIsInitialized(PSHCLHTTPSERVER pSrv); -bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv); -/** @} */ -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ - /** @name Shared Clipboard transfers utility functions. * @{ */ diff --git a/include/VBox/GuestHost/clipboard-helper.h b/include/VBox/GuestHost/clipboard-helper.h index 2055fc4ea237..146338df4d72 100644 --- a/include/VBox/GuestHost/clipboard-helper.h +++ b/include/VBox/GuestHost/clipboard-helper.h @@ -1,4 +1,4 @@ -/* $Id: clipboard-helper.h 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-helper.h 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Helper functions. */ @@ -341,29 +341,6 @@ int ShClDbgDumpHtml(const char *pszSrc, size_t cbSrc); void ShClDbgDumpData(const void *pv, size_t cb, SHCLFORMAT u32Format); #endif /* LOG_ENABLED */ -/** - * Translates a Shared Clipboard host function number to a string. - * - * @returns Function ID string name. - * @param uFn The function to translate. - */ -const char *ShClHostFunctionToStr(uint32_t uFn); - -/** - * Translates a Shared Clipboard host message enum to a string. - * - * @returns Message ID string name. - * @param uMsg The message to translate. - */ -const char *ShClHostMsgToStr(uint32_t uMsg); - -/** - * Translates a Shared Clipboard guest message enum to a string. - * - * @returns Message ID string name. - * @param uMsg The message to translate. - */ -const char *ShClGuestMsgToStr(uint32_t uMsg); char *ShClFormatsToStrA(SHCLFORMATS fFormats); diff --git a/include/VBox/GuestHost/clipboard-transfers-http.h b/include/VBox/GuestHost/clipboard-transfers-http.h new file mode 100644 index 000000000000..f40e82139cda --- /dev/null +++ b/include/VBox/GuestHost/clipboard-transfers-http.h @@ -0,0 +1,76 @@ +/* $Id: clipboard-transfers-http.h 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard - HTTP transfer functions. + */ + +/* + * Copyright (C) 2020-2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_GuestHost_clipboard_transfers_http_h +#define VBOX_INCLUDED_GuestHost_clipboard_transfers_http_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +/** Namespace used as a prefix for HTTP(S) transfer URLs. */ +# define SHCL_HTTPT_URL_NAMESPACE "vbcl" + +/** @name Shared Clipboard HTTP context API. + * @{ + */ +int ShClTransferHttpServerMaybeStart(PSHCLHTTPCONTEXT pCtx); +int ShClTransferHttpServerMaybeStop(PSHCLHTTPCONTEXT pCtx); +/** @} */ + +/** @name Shared Clipboard HTTP server API. + * @{ + */ +int ShClTransferHttpServerInit(PSHCLHTTPSERVER pSrv); +int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv); +int ShClTransferHttpServerStart(PSHCLHTTPSERVER pSrv, unsigned cMaxAttempts, uint16_t *puPort); +int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort); +int ShClTransferHttpServerStop(PSHCLHTTPSERVER pSrv); +int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); +int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); +bool ShClTransferHttpServerGetTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer); +uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv); +char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv); +char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer, uint64_t idxEntry); +int ShClTransferHttpConvertToStringList(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer, char **ppszData, size_t *pcbData); +bool ShClTransferHttpServerIsInitialized(PSHCLHTTPSERVER pSrv); +bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv); +/** @} */ +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + +#endif /* !VBOX_INCLUDED_GuestHost_clipboard_transfers_http_h */ diff --git a/include/VBox/HostServices/VBoxClipboardSvc.h b/include/VBox/HostServices/VBoxClipboardSvc.h index 4c225b155c5c..c8fe22da8bb1 100644 --- a/include/VBox/HostServices/VBoxClipboardSvc.h +++ b/include/VBox/HostServices/VBoxClipboardSvc.h @@ -692,6 +692,104 @@ /** @} */ +/** + * Translates a Shared Clipboard host function number to a string. + * + * @returns Function ID string name. + * @param uFn The function to translate. + */ +DECLINLINE(const char *) ShClSvcHostFunctionToStr(uint32_t uFn) +{ + switch (uFn) + { + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_MODE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_HEADLESS); + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_CANCEL); + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_ERROR); + } + return "Unknown"; +} + +/** + * Translates a Shared Clipboard host message enum to a string. + * + * @returns Message ID string name. + * @param uMsg The message to translate. + */ +DECLINLINE(const char *) ShClSvcHostMsgToStr(uint32_t uMsg) +{ + switch (uMsg) + { + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_QUIT); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_CANCELED); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA_CID); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_CANCEL); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ERROR); + } + return "Unknown"; +} + +/** + * Translates a Shared Clipboard guest message enum to a string. + * + * @returns Message ID string name. + * @param uMsg The message to translate. + */ +DECLINLINE(const char *) ShClSvcGuestMsgToStr(uint32_t uMsg) +{ + switch (uMsg) + { + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FORMATS); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_CONNECT); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FEATURES); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_QUERY_FEATURES); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_GET); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_CANCEL); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPLY); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_OPEN); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_CLOSE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_OPEN); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_CLOSE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ERROR); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE); + } + return "Unknown"; +} + + /* * HGCM parameter structures. */ diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp index b49b5ba31902..3a30643e689c 100644 --- a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxGuestR3LibClipboard.cpp 114988 2026-08-11 13:58:58Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxGuestR3LibClipboard.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared Clipboard. */ @@ -2380,7 +2380,7 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); AssertPtrReturn(pEvent, VERR_INVALID_POINTER); - LogFunc(("Handling idMsg=%RU32 (%s), cParms=%RU32\n", idMsg, ShClHostMsgToStr(idMsg), cParms)); + LogFunc(("Handling idMsg=%RU32 (%s), cParms=%RU32\n", idMsg, ShClSvcHostMsgToStr(idMsg), cParms)); int rc; if (!pCmdCtx->fUseLegacyProtocol) @@ -2856,7 +2856,7 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNext(uint32_t idMsg, uint32_t cParms, PVB int rc; if (!pCtx->fUseLegacyProtocol) { - LogFunc(("Handling idMsg=%RU32 (%s)\n", idMsg, ShClHostMsgToStr(idMsg))); + LogFunc(("Handling idMsg=%RU32 (%s)\n", idMsg, ShClSvcHostMsgToStr(idMsg))); switch (idMsg) { case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index a09795a428a0..9d94e3081770 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-x11.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -43,6 +43,9 @@ #include #include #include +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +# include +#endif #include "VBoxClient.h" #include "clipboard.h" diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp index fe3239f857a9..05e86ab99a02 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-common.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-common.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common helper objects. */ @@ -29,8 +29,10 @@ #define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include #include #include +#include #include #include #include @@ -38,9 +40,9 @@ #include #include +#include #include #include -#include #ifdef VBOX_WITH_SHARED_CLIPBOARD_HOST # include #endif @@ -55,6 +57,9 @@ static int shClEventSourceUnregisterEvent(PSHCLEVENTSOURCE pSource, PSHCLEVENT p static void shClEventDestroy(PSHCLEVENT pEvent); DECLINLINE(PSHCLEVENT) shclEventGet(PSHCLEVENTSOURCE pSource, SHCLEVENTID idEvent); +/** Exclusive upper bound for usable event IDs. */ +static uint32_t const g_idShClEventEnd = UINT32_MAX - 1; + /********************************************************************************************************************************* * Implementation * @@ -158,7 +163,7 @@ int ShClEventSourceInit(PSHCLEVENTSOURCE pSource, SHCLEVENTSOURCEID uID) pSource->uID = uID; /* Choose a random event ID starting point. */ - pSource->idNextEvent = RTRandU32Ex(1, VBOX_SHCL_MAX_EVENTS - 1); + pSource->idNextEvent = RTRandU32Ex(1, g_idShClEventEnd - 1); return VINF_SUCCESS; } @@ -268,7 +273,7 @@ int ShClEventSourceGenerateAndRegisterEvent(PSHCLEVENTSOURCE pSource, PSHCLEVENT for (uint32_t cTries = 0;; cTries++) { SHCLEVENTID idEvent = ++pSource->idNextEvent; - if (idEvent < VBOX_SHCL_MAX_EVENTS) + if (idEvent < g_idShClEventEnd) { /* likely */ } else pSource->idNextEvent = idEvent = 1; /* zero == error, remember! */ @@ -720,103 +725,6 @@ void ShClDbgDumpData(const void *pv, size_t cb, SHCLFORMAT uFormat) #endif /* LOG_ENABLED */ -/** - * Translates a Shared Clipboard host function number to a string. - * - * @returns Function ID string name. - * @param uFn The function to translate. - */ -const char *ShClHostFunctionToStr(uint32_t uFn) -{ - switch (uFn) - { - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_MODE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_HEADLESS); - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_CANCEL); - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_ERROR); - } - return "Unknown"; -} - -/** - * Translates a Shared Clipboard host message enum to a string. - * - * @returns Message ID string name. - * @param uMsg The message to translate. - */ -const char *ShClHostMsgToStr(uint32_t uMsg) -{ - switch (uMsg) - { - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_QUIT); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_FORMATS_REPORT); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_CANCELED); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA_CID); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_CANCEL); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ERROR); - } - return "Unknown"; -} - -/** - * Translates a Shared Clipboard guest message enum to a string. - * - * @returns Message ID string name. - * @param uMsg The message to translate. - */ -const char *ShClGuestMsgToStr(uint32_t uMsg) -{ - switch (uMsg) - { - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FORMATS); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_CONNECT); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FEATURES); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_QUERY_FEATURES); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_GET); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_CANCEL); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPLY); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_OPEN); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_CLOSE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_OPEN); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_CLOSE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ERROR); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE); - } - return "Unknown"; -} - /** * Converts Shared Clipboard formats to a string. * @@ -1497,7 +1405,7 @@ int ShClSvcClientWakeup(PSHCLCLIENT pClient) AssertReturn(pFirstMsg, VERR_INTERNAL_ERROR); LogFunc(("[Client %RU32] Current host message is %s (%RU32), cParms=%RU32\n", - pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + pClient->State.uClientID, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) shClSvcMsgSetPeekReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); @@ -1542,7 +1450,7 @@ int shClSvcClientMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) Assert(RTCritSectIsOwner(&pClient->CritSect)); AssertPtr(pMsg); AssertPtr(pClient); - LogFlowFunc(("idMsg=%s (%u) cParms=%u\n", ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms)); + LogFlowFunc(("idMsg=%s (%u) cParms=%u\n", ShClSvcHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms)); RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); int const rc = ShClSvcClientWakeup(pClient); @@ -1575,7 +1483,7 @@ void ShClSvcClientMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend) AssertPtr(pMsg); LogFlowFunc(("idMsg=%s (%RU32) cParms=%RU32 fAppend=%RTbool\n", - ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms, fAppend)); + ShClSvcHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms, fAppend)); if (fAppend) RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp index 60f5f7725491..b435dbdddae1 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-http.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-http.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts. */ @@ -60,7 +60,7 @@ #include #include -#include +#include /********************************************************************************************************************************* diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp index 343b39137690..874a2120cebd 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -70,6 +70,9 @@ # include # include #endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +# include +#endif #include #include diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp index 203ebec5ec64..413b5e0982cd 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardHttpServer.cpp 114989 2026-08-11 14:13:05Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardHttpServer.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard HTTP server test case. */ @@ -45,7 +45,7 @@ #include #endif -#include +#include /** The release logger. */ diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp index dd4335177102..7ede01d72f59 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-client.cpp 114967 2026-08-10 16:15:33Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-client.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Client/session and message queue handling. */ @@ -290,7 +290,7 @@ DECLCALLBACK(void) shClSvcClientCall(void *, #ifdef LOG_ENABLED Log2Func(("u32ClientID=%RU32, fn=%RU32 (%s), cParms=%RU32, paParms=%p\n", - u32ClientID, u32Function, ShClGuestMsgToStr(u32Function), cParms, paParms)); + u32ClientID, u32Function, ShClSvcGuestMsgToStr(u32Function), cParms, paParms)); for (uint32_t i = 0; i < cParms; i++) { switch (paParms[i].type) @@ -598,7 +598,7 @@ int shClSvcClientMsgPeek(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t { shClSvcMsgSetPeekReturn(pFirstMsg, paParms, cParms); LogFlowFunc(("[Client %RU32] VBOX_SHCL_GUEST_FN_MSG_PEEK_XXX -> VINF_SUCCESS (idMsg=%s (%u), cParms=%u)\n", - pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + pClient->State.uClientID, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); return VINF_SUCCESS; } @@ -658,7 +658,7 @@ int shClSvcClientMsgOldGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32 if (pFirstMsg) { LogFlowFunc(("[Client %RU32] uMsg=%s (%RU32), cParms=%RU32\n", pClient->State.uClientID, - ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); rc = shClSvcMsgSetOldWaitReturn(pFirstMsg, paParms, cParms); AssertPtr(g_pHelpers); @@ -728,24 +728,24 @@ int shClSvcClientMsgGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); if (pFirstMsg) { - LogFlowFunc(("First message is: %s (%u), cParms=%RU32\n", ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + LogFlowFunc(("First message is: %s (%u), cParms=%RU32\n", ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); ASSERT_GUEST_MSG_RETURN(pFirstMsg->idMsg == idMsgExpected || idMsgExpected == UINT32_MAX, ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", - pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, - idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms), + pFirstMsg->idMsg, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, + idMsgExpected, ShClSvcHostMsgToStr(idMsgExpected), cParms), VERR_MISMATCH); ASSERT_GUEST_MSG_RETURN(pFirstMsg->cParms == cParms, ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", - pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, - idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms), + pFirstMsg->idMsg, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, + idMsgExpected, ShClSvcHostMsgToStr(idMsgExpected), cParms), VERR_WRONG_PARAMETER_COUNT); /* Check the parameter types. */ for (uint32_t i = 0; i < cParms; i++) ASSERT_GUEST_MSG_RETURN(pFirstMsg->aParms[i].type == paParms[i].type, ("param #%u: type %u, caller expected %u (idMsg=%u %s)\n", i, pFirstMsg->aParms[i].type, - paParms[i].type, pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg)), + paParms[i].type, pFirstMsg->idMsg, ShClSvcHostMsgToStr(pFirstMsg->idMsg)), VERR_WRONG_PARAMETER_TYPE); /* * Copy out the parameters. diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp index 824b715f4214..411ba78ebb03 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-host.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-host.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host-controlled service handling. */ @@ -117,7 +117,7 @@ DECLCALLBACK(int) shClSvcHostCall(void *, uint32_t u32Function, uint32_t cParms, int rc = VINF_SUCCESS; LogFlowFunc(("u32Function=%RU32 (%s), cParms=%RU32, paParms=%p\n", - u32Function, ShClHostFunctionToStr(u32Function), cParms, paParms)); + u32Function, ShClSvcHostFunctionToStr(u32Function), cParms, paParms)); switch (u32Function) { diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp index 9be0528a0931..aab821558bd5 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal code for transfer (list) handling. */ @@ -261,7 +261,7 @@ static bool shClSvcTransferMsgIsAllowed(uint32_t uMode, uint32_t uMsg) break; } - LogFlowFunc(("uMsg=%RU32 (%s), uMode=%RU32 -> fAllowed=%RTbool\n", uMsg, ShClGuestMsgToStr(uMsg), uMode, fAllowed)); + LogFlowFunc(("uMsg=%RU32 (%s), uMode=%RU32 -> fAllowed=%RTbool\n", uMsg, ShClSvcGuestMsgToStr(uMsg), uMode, fAllowed)); return fAllowed; } @@ -981,7 +981,7 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, RT_NOREF(callHandle, aParms, tsArrival); LogFlowFunc(("uClient=%RU32, u32Function=%RU32 (%s), cParms=%RU32, g_ExtState.pfnExtension=%p\n", - pClient->State.uClientID, u32Function, ShClGuestMsgToStr(u32Function), cParms, g_ExtState.pfnExtension)); + pClient->State.uClientID, u32Function, ShClSvcGuestMsgToStr(u32Function), cParms, g_ExtState.pfnExtension)); if ( u32Function > VBOX_SHCL_GUEST_FN_LAST || !(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) @@ -990,14 +990,14 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS)) { LogRelMax2(16, ("Shared Clipboard: Guest attempted file transfer message %s without negotiated transfer support (features0=%#RX64)\n", - ShClGuestMsgToStr(u32Function), pClient->State.fGuestFeatures0)); + ShClSvcGuestMsgToStr(u32Function), pClient->State.fGuestFeatures0)); return VERR_ACCESS_DENIED; } if (!(g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) { LogRelMax2(16, ("Shared Clipboard: Guest attempted file transfer message %s, but file transfers are disabled for this VM (transfer mode=%#x)\n", - ShClGuestMsgToStr(u32Function), g_fTransferMode)); + ShClSvcGuestMsgToStr(u32Function), g_fTransferMode)); return VERR_ACCESS_DENIED; } @@ -1006,7 +1006,7 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (!shClSvcTransferMsgIsAllowed(uMode, u32Function)) { LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s is not allowed in clipboard mode %RU32\n", - ShClGuestMsgToStr(u32Function), uMode)); + ShClSvcGuestMsgToStr(u32Function), uMode)); return VERR_ACCESS_DENIED; } @@ -1029,14 +1029,14 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (u32Function != VBOX_SHCL_GUEST_FN_REPLY) { LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s used zero context ID; only transfer status replies may do this\n", - ShClGuestMsgToStr(u32Function))); + ShClSvcGuestMsgToStr(u32Function))); return VERR_INVALID_CONTEXT; } } else if (VBOX_SHCL_CONTEXTID_GET_SESSION(uCID) != pClient->State.uSessionID) { LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s used context %#RX64 for session %RU32, expected session %RU32\n", - ShClGuestMsgToStr(u32Function), uCID, VBOX_SHCL_CONTEXTID_GET_SESSION(uCID), pClient->State.uSessionID)); + ShClSvcGuestMsgToStr(u32Function), uCID, VBOX_SHCL_CONTEXTID_GET_SESSION(uCID), pClient->State.uSessionID)); return VERR_INVALID_CONTEXT; } @@ -1050,7 +1050,7 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, && !pTransfer) { LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s references unknown transfer %RU16 (context=%#RX64)\n", - ShClGuestMsgToStr(u32Function), idTransfer, uCID)); + ShClSvcGuestMsgToStr(u32Function), idTransfer, uCID)); return VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; } diff --git a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp index be137d6b8786..cf30a123a69f 100644 --- a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp +++ b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendX11.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendX11.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - X11 backend. */ @@ -52,7 +52,7 @@ #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include # ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP -# include +# include # endif #endif From 7b2bbac11f826dc804754cbbfdb3e2c03ea1e8de Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 15:12:59 +0000 Subject: [PATCH 142/176] Shared Clipboard: Fix file transfer stability and service state handling. bugref:4697 svn:sync-xref-src-repo-rev: r174890 --- .../GuestHost/SharedClipboard-transfers.h | 18 +- include/VBox/GuestHost/SharedClipboard-win.h | 1 + .../HostServices/VBoxSharedClipboardSvc.h | 67 +- .../Additions/win/VBoxTray/VBoxClipboard.cpp | 38 +- .../SharedClipboard/clipboard-common.cpp | 58 +- .../SharedClipboard/clipboard-transfers.cpp | 639 +++++++++++++++--- .../SharedClipboard/clipboard-win.cpp | 36 +- .../VBoxSharedClipboardSvc-backend.cpp | 49 +- .../VBoxSharedClipboardSvc-client.cpp | 208 +++--- .../VBoxSharedClipboardSvc-host.cpp | 66 +- .../VBoxSharedClipboardSvc-internal.h | 56 +- .../VBoxSharedClipboardSvc-transfers.cpp | 216 +++--- .../VBoxSharedClipboardSvc-transfers.h | 7 +- .../VBoxSharedClipboardSvc.cpp | 110 +-- .../testcase/tstClipboardMockHGCM.cpp | 6 +- .../testcase/tstClipboardServiceHost.cpp | 8 +- .../VBoxSharedClipboardSvc-utils.cpp | 7 +- .../darwin/ClipboardBackendDarwin.cpp | 4 +- .../src-client/linux/ClipboardBackendX11.cpp | 33 +- .../src-client/win/ClipboardBackendWin.cpp | 43 +- 20 files changed, 1147 insertions(+), 523 deletions(-) diff --git a/include/VBox/GuestHost/SharedClipboard-transfers.h b/include/VBox/GuestHost/SharedClipboard-transfers.h index 4ee4d4669607..8a7b650e30b2 100644 --- a/include/VBox/GuestHost/SharedClipboard-transfers.h +++ b/include/VBox/GuestHost/SharedClipboard-transfers.h @@ -1,4 +1,4 @@ -/* $Id: SharedClipboard-transfers.h 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: SharedClipboard-transfers.h 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Shared transfer functions between host and guest. */ @@ -918,6 +918,10 @@ typedef struct SHCLTRANSFER RTCRITSECT CritSect; /** Number of references to this transfer. */ uint32_t cRefs; + /** Event signalled after the last transfer reference has been released. */ + RTSEMEVENTMULTI hNoRefsEvent; + /** Owning transfer context until the unregistration callback has completed. */ + PSHCLTRANSFERCTX pOwnerCtx; /** The transfer's state (for SSM, later). */ SHCLTRANSFERSTATE State; /** Absolute path to root entries. */ @@ -1245,11 +1249,23 @@ int ShClTransferCtxInit(PSHCLTRANSFERCTX pTransferCtx); void ShClTransferCtxDestroy(PSHCLTRANSFERCTX pTransferCtx); void ShClTransferCtxReset(PSHCLTRANSFERCTX pTransferCtx); int ShClTransferCtxBeginSession(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idSession); +/** @name Borrowed transfer lookups + * Returned pointers must not outlive external context-lifetime serialization. + * Use a retained lookup across concurrent unregistration or teardown. + * @{ */ PSHCLTRANSFER ShClTransferCtxGetTransferById(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID); PSHCLTRANSFER ShClTransferCtxGetTransferByKey(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration); PSHCLTRANSFER ShClTransferCtxGetTransferByIndex(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx); PSHCLTRANSFER ShClTransferCtxGetTransferLast(PSHCLTRANSFERCTX pTransferCtx); +/** @} */ +/** @name Retained transfer lookups + * Returned pointers must be released with ShClTransferRelease(). + * @{ */ +PSHCLTRANSFER ShClTransferCtxGetTransferByIdRetained(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID); +PSHCLTRANSFER ShClTransferCtxGetTransferByKeyRetained(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration); +/** @} */ uint32_t ShClTransferCtxGetTotalTransfers(PSHCLTRANSFERCTX pTransferCtx); bool ShClTransferCtxIsMaximumReached(PSHCLTRANSFERCTX pTransferCtx); int ShClTransferCtxRegister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer, PSHCLTRANSFERID pidTransfer); diff --git a/include/VBox/GuestHost/SharedClipboard-win.h b/include/VBox/GuestHost/SharedClipboard-win.h index 288dadf7f74f..b97284a09a28 100644 --- a/include/VBox/GuestHost/SharedClipboard-win.h +++ b/include/VBox/GuestHost/SharedClipboard-win.h @@ -494,6 +494,7 @@ int ShClWinTransferDropFilesToStringList(DROPFILES *pDropFiles, char **papszList int ShClWinTransferGetRootsFromClipboard(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); int ShClWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); +void ShClWinTransferUnregister(PSHCLTRANSFER pTransfer); void ShClWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, PSHCLCONTEXT pCtx, ShClWinDataObject::PCALLBACKS pCallbacks); diff --git a/include/VBox/HostServices/VBoxSharedClipboardSvc.h b/include/VBox/HostServices/VBoxSharedClipboardSvc.h index 0d2640ce49ea..448905882484 100644 --- a/include/VBox/HostServices/VBoxSharedClipboardSvc.h +++ b/include/VBox/HostServices/VBoxSharedClipboardSvc.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc.h 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.h 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - header file for shared clipboard data transfer * interfaces and platform-dependent backend functionality. @@ -41,12 +41,7 @@ # pragma once #endif -#include -#include -#include - #include -#include /* For RTCList. */ #include #include @@ -162,7 +157,8 @@ typedef struct SHCLCLIENTSTATE /** The client's session ID. */ SHCLSESSIONID uSessionID; /** Guest feature flags, VBOX_SHCL_GF_0_XXX. */ - uint64_t fGuestFeatures0; + RT_ALIGNAS_MEMB(8) uint64_t + fGuestFeatures0; /** Guest feature flags, VBOX_SHCL_GF_1_XXX. */ uint64_t fGuestFeatures1; /** Chunk size to use for data transfers. */ @@ -245,6 +241,9 @@ typedef struct _SHCLCLIENT } Pending; } SHCLCLIENT, *PSHCLCLIENT; +AssertCompileMemberAlignment(SHCLCLIENT, State.fGuestFeatures0, 8); +AssertCompileMemberAlignment(SHCLCLIENT, State.fGuestFeatures1, 8); + /** * Returns a client's cached Shared Clipboard mode atomically. * @@ -295,55 +294,6 @@ DECLINLINE(uint32_t) ShClSvcClientGetTransferMode(PSHCLCLIENT pClient) } #endif -/** - * Structure for keeping a single event source map entry. - * Currently empty. - */ -typedef struct _SHCLEVENTSOURCEMAPENTRY -{ -} SHCLEVENTSOURCEMAPENTRY; - -/** Map holding information about connected HGCM clients. Key is the (unique) HGCM client ID. - * The value is a weak pointer to PSHCLCLIENT, which is owned by HGCM. */ -typedef std::map ClipboardClientMap; - -/** Map holding information about event sources. Key is the (unique) event source ID. */ -typedef std::map ClipboardEventSourceMap; - -/** Simple queue (list) which holds deferred (waiting) clients. */ -typedef std::list ClipboardClientQueue; - -/** - * Structure for keeping the Shared Clipboard service extension state. - * - * A service extension is optional, and can be installed by a host component - * to communicate with the Shared Clipboard host service. - */ -typedef struct _SHCLEXTSTATE -{ - /** Pointer to the actual service extension handle. - * - * Must return VERR_NOT_SUPPORTED if the extension didn't handle the requested function. - * This will invoke the regular backend then. - */ - PFNHGCMSVCEXT pfnExtension; - /** Opaque pointer to extension-provided data. Don't touch. */ - void *pvExtension; - /** The HGCM client ID currently assigned to this service extension. - * At the moment only one HGCM client can be assigned per extension. */ - uint32_t uClientID; - /** Whether the host service is reading clipboard data currently. */ - bool fReadingData; - /** Whether the service extension has sent the clipboard formats while - * the the host service is reading clipboard data from it. */ - bool fDelayedAnnouncement; - /** The actual clipboard formats announced while the host service - * is reading clipboard data from the extension. */ - uint32_t fDelayedFormats; -} SHCLEXTSTATE, *PSHCLEXTSTATE; - -extern SHCLEXTSTATE g_ExtState; - /** @name Service client functions. * @{ */ @@ -363,6 +313,9 @@ void shClSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, u int shClSvcMsgSetOldWaitReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms); SHCLFORMATS shClSvcHandleFormats(bool fHostToGuest, PSHCLCLIENT pClient, SHCLFORMATS fFormats); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +bool shClSvcClientTransfersAreAllowed(PSHCLCLIENT pClient); +#endif /** @} */ /** @name Service functions, accessible by the backends. @@ -378,8 +331,6 @@ int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLF int ShClSvcReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats); PSHCLBACKEND ShClSvcGetBackend(void); uint32_t ShClSvcGetMode(void); -bool ShClSvcLock(void); -void ShClSvcUnlock(void); /** @} */ /** @name Platform-dependent implementations for the Shared Clipboard host service ("backends"), diff --git a/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp b/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp index 7d8adc4f9693..af75fb7bc93e 100644 --- a/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp +++ b/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxClipboard.cpp 114414 2026-06-17 21:44:21Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxClipboard.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * VBoxClipboard - Shared clipboard, Windows Guest Implementation. */ @@ -94,6 +94,8 @@ static char s_szClipWndClassName[] = SHCL_WIN_WNDCLASS_NAME; *********************************************************************************************************************************/ #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS static DECLCALLBACK(void) vbtrShClTransferCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); +static DECLCALLBACK(void) vbtrShClTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, + PSHCLTRANSFERCTX pTransferCtx); static DECLCALLBACK(void) vbtrShClTransferDestroyCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); @@ -142,6 +144,25 @@ static DECLCALLBACK(void) vbtrShClTransferCreatedCallback(PSHCLTRANSFERCALLBACKC LogRelFlowFuncLeaveRC(rc); } +/** + * @copydoc SHCLTRANSFERCALLBACKS::pfnOnUnregistered + * + * Disables the IDataObject and drops its long-lived transfer reference before + * consuming teardown waits for temporary transfer users. + * + * @thread Clipboard main thread. + */ +static DECLCALLBACK(void) vbtrShClTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, + PSHCLTRANSFERCTX pTransferCtx) +{ + RT_NOREF(pTransferCtx); + + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtr(pTransfer); + + ShClWinTransferUnregister(pTransfer); +} + /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnDestroy * @@ -1032,13 +1053,14 @@ DECLCALLBACK(int) vbtrShClWorker(void *pvInstance, bool volatile *pfShutdown) pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */ pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); - pCtx->CmdCtx.Transfers.Callbacks.pfnOnCreated = vbtrShClTransferCreatedCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnDestroy = vbtrShClTransferDestroyCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialize = vbtrShClTransferInitializeCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbtrShClTransferInitializedCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnStarted = vbtrShClTransferStartedCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbtrShClTransferCompletedCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbtrShClTransferErrorCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnCreated = vbtrShClTransferCreatedCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnUnregistered = vbtrShClTransferUnregisteredCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnDestroy = vbtrShClTransferDestroyCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialize = vbtrShClTransferInitializeCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbtrShClTransferInitializedCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnStarted = vbtrShClTransferStartedCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbtrShClTransferCompletedCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbtrShClTransferErrorCallback; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ int rc; diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp index 05e86ab99a02..527966a3ac77 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-common.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-common.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common helper objects. */ @@ -1184,13 +1184,12 @@ VBGH_DECL(int) ShClCacheTransferAll(PSHCLCACHE pCache, PSHCLCACHE pOtherCache) /** * Handles clipboard formats. * - * This validates file transfer announcements against the negotiated transfer - * features and keeps host-to-guest transfer offers separate from ordinary - * clipboard formats. Older Windows Guest Additions with transfer support + * This suppresses file-transfer announcements until transfers are enabled and + * supported by the guest, and keeps host-to-guest transfer offers separate + * from ordinary clipboard formats. Older Windows Guest Additions with transfer support * (for example 7.2.6 and 7.2.10) expect URI-list offers to be reported on * their own so they can replace the normal clipboard announcement with an OLE - * IDataObject. Do not rely on VBOX_SHCL_GF_0_TRANSFERS_FRONTEND here, as not - * all transfer-capable Guest Additions reliably advertise that extra bit. + * IDataObject. * * @returns The new Shared Clipboard formats. * @param fHostToGuest Reporting direction. @@ -1203,28 +1202,18 @@ SHCLFORMATS shClSvcHandleFormats(bool fHostToGuest, PSHCLCLIENT pClient, SHCLFOR { SHCLFORMATS const fFormatsOrg = fFormats; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - bool fSkipTransfers = false; if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - uint32_t const fTransferMode = ShClSvcClientGetTransferMode(pClient); - if (!(fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) + if (!shClSvcClientTransfersAreAllowed(pClient)) { - LogRelMax(16, ("Shared Clipboard: File transfer format %#x was reported by %s, but file transfers are disabled (mode=%#x), masking it\n", - VBOX_SHCL_FMT_URI_LIST, fHostToGuest ? "host" : "guest", fTransferMode)); - fSkipTransfers = true; - } - - uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - uint64_t const fGuestFeatures0 = ShClSvcClientGetGuestFeatures0(pClient); - if ((fGuestFeatures0 & fRequired) != fRequired) - { - LogRelMax(16, ("Shared Clipboard: File transfer format %#x was reported by %s, but Guest Additions did not negotiate required features (features0=%#RX64, required=%#RX64), masking it\n", - VBOX_SHCL_FMT_URI_LIST, fHostToGuest ? "host" : "guest", fGuestFeatures0, fRequired)); - fSkipTransfers = true; - } - - if (fSkipTransfers) + uint32_t const fTransferMode = ShClSvcClientGetTransferMode(pClient); + uint64_t const fGuestFeatures = ShClSvcClientGetGuestFeatures0(pClient); + uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; + LogRelMax(16, ("Shared Clipboard: File transfer format %#x was reported by %s without enabled and negotiated transfers (mode=%#x, features0=%#RX64, required=%#RX64), masking it\n", + VBOX_SHCL_FMT_URI_LIST, fHostToGuest ? "host" : "guest", fTransferMode, + fGuestFeatures, fRequired)); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; + } else if (fHostToGuest) { if (fFormats != VBOX_SHCL_FMT_URI_LIST) @@ -1254,6 +1243,27 @@ SHCLFORMATS shClSvcHandleFormats(bool fHostToGuest, PSHCLCLIENT pClient, SHCLFOR return fFormats; } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Checks whether file transfers are enabled and supported by a client. + * + * Clipboard direction is deliberately not considered here and must be checked + * separately by the operation being authorized. + * + * @returns true if file transfers may be used, false otherwise. + * @param pClient Client to check. + */ +bool shClSvcClientTransfersAreAllowed(PSHCLCLIENT pClient) +{ + AssertPtrReturn(pClient, false); + + uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; + return (ShClSvcClientGetTransferMode(pClient) & VBOX_SHCL_TRANSFER_MODE_F_ENABLED) + && (ShClSvcClientGetGuestFeatures0(pClient) & fRequired) == fRequired; +} +#endif + void ShClSvcClientLock(PSHCLCLIENT pClient) { int rc2 = RTCritSectEnter(&pClient->CritSect); diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index 5f36d7930644..99963e72da73 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -58,11 +58,20 @@ static void shClTransferSetCallbacks(PSHCLTRANSFER pTransfer, PSHCLTRANSFERCALLB static int shClTransferSetStatus(PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS enmStatus); static int shClTransferThreadCreate(PSHCLTRANSFER pTransfer, PFNSHCLTRANSFERTHREAD pfnThreadFunc, void *pvUser); static int shClTransferThreadDestroy(PSHCLTRANSFER pTransfer, RTMSINTERVAL uTimeoutMs); +static void shClTransferDestroyConsume(PSHCLTRANSFER pTransfer); +static void shclTransferCtxTransferRemoveLocked(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer); +static void shclTransferCtxTransferNotifyUnregistered(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer); static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer); static PSHCLTRANSFER shClTransferCtxGetTransferByIdInternal(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID uId); static PSHCLTRANSFER shClTransferCtxGetTransferByIndexInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_HOST +void ShClSvcTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +void ShClSvcTransferDestroyById(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer); +void ShClSvcTransferDestroyByIdEx(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer, bool fNotifyGuest); +#endif + /** * Checks whether a transfer ID is in the assignable context-local range. @@ -1300,6 +1309,7 @@ static int shClTransferCreateInternal(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSour pTransfer->Thread.fStop = false; pTransfer->pszPathRootAbs = NULL; + pTransfer->pOwnerCtx = NULL; pTransfer->uTimeoutMs = SHCL_TIMEOUT_DEFAULT_MS; pTransfer->cbMaxChunkSize = cbMaxChunkSize; @@ -1322,26 +1332,34 @@ static int shClTransferCreateInternal(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSour ShClTransferListInit(&pTransfer->lstRoots); pTransfer->StatusChangeEvent = NIL_RTSEMEVENT; + pTransfer->hNoRefsEvent = NIL_RTSEMEVENTMULTI; int rc = RTCritSectInit(&pTransfer->CritSect); if (RT_SUCCESS(rc)) { - rc = RTSemEventCreate(&pTransfer->StatusChangeEvent); + rc = RTSemEventMultiCreate(&pTransfer->hNoRefsEvent); if (RT_SUCCESS(rc)) { - rc = ShClEventSourceInit(&pTransfer->Events, 0 /* uID */); + rc = RTSemEventCreate(&pTransfer->StatusChangeEvent); if (RT_SUCCESS(rc)) { - if (pTransfer->Callbacks.pfnOnCreated) - pTransfer->Callbacks.pfnOnCreated(&pTransfer->CallbackCtx); + rc = ShClEventSourceInit(&pTransfer->Events, 0 /* uID */); + if (RT_SUCCESS(rc)) + { + if (pTransfer->Callbacks.pfnOnCreated) + pTransfer->Callbacks.pfnOnCreated(&pTransfer->CallbackCtx); - *ppTransfer = pTransfer; - LogFlowFuncLeaveRC(rc); - return rc; + *ppTransfer = pTransfer; + LogFlowFuncLeaveRC(rc); + return rc; + } + + RTSemEventDestroy(pTransfer->StatusChangeEvent); + pTransfer->StatusChangeEvent = NIL_RTSEMEVENT; } - RTSemEventDestroy(pTransfer->StatusChangeEvent); - pTransfer->StatusChangeEvent = NIL_RTSEMEVENT; + RTSemEventMultiDestroy(pTransfer->hNoRefsEvent); + pTransfer->hNoRefsEvent = NIL_RTSEMEVENTMULTI; } RTCritSectDelete(&pTransfer->CritSect); @@ -1406,12 +1424,8 @@ int ShClTransferDestroy(PSHCLTRANSFER pTransfer) if (!RTCritSectIsInitialized(&pTransfer->CritSect)) return VINF_SUCCESS; - /* Must come before the refcount check below, as the callback might release a reference. */ - if (pTransfer->Callbacks.pfnOnDestroy) - pTransfer->Callbacks.pfnOnDestroy(&pTransfer->CallbackCtx); - - AssertMsgReturn(ASMAtomicReadU32(&pTransfer->cRefs) == 0, - ("Number of references > 0 (%RU32)\n", pTransfer->cRefs), VERR_WRONG_ORDER); + AssertMsgReturn(pTransfer->pOwnerCtx == NULL, + ("Transfer is still registered with context %p\n", pTransfer->pOwnerCtx), VERR_WRONG_ORDER); LogFlowFuncEnter(); @@ -1419,6 +1433,17 @@ int ShClTransferDestroy(PSHCLTRANSFER pTransfer) if (RT_FAILURE(rc)) return rc; + AssertMsgReturn(ASMAtomicReadU32(&pTransfer->cRefs) == 0, + ("Number of references > 0 (%RU32)\n", pTransfer->cRefs), VERR_WRONG_ORDER); + + /* Callback-owned state may still be used by the worker or a temporary + * reference holder. Destroy it only after both have been drained. */ + if (pTransfer->Callbacks.pfnOnDestroy) + { + pTransfer->Callbacks.pfnOnDestroy(&pTransfer->CallbackCtx); + pTransfer->Callbacks.pfnOnDestroy = NULL; + } + ShClTransferReset(pTransfer); if (RTCritSectIsInitialized(&pTransfer->CritSect)) @@ -1430,6 +1455,10 @@ int ShClTransferDestroy(PSHCLTRANSFER pTransfer) ShClEventSourceTerm(&pTransfer->Events); + rc = RTSemEventMultiDestroy(pTransfer->hNoRefsEvent); + AssertRCReturn(rc, rc); + pTransfer->hNoRefsEvent = NIL_RTSEMEVENTMULTI; + RTMemFree(pTransfer); pTransfer = NULL; @@ -1437,6 +1466,88 @@ int ShClTransferDestroy(PSHCLTRANSFER pTransfer) return VINF_SUCCESS; } +/** + * Consumes a transfer during owner shutdown. + * + * Unlike ShClTransferDestroy(), this cannot leave a detached transfer behind: + * callbacks, the transfer worker and outstanding reference holders are drained + * before the transfer is freed. + * + * @param pTransfer Clipboard transfer to consume. The pointer is + * invalid after return. + */ +static void shClTransferDestroyConsume(PSHCLTRANSFER pTransfer) +{ + if (!pTransfer) + return; + + AssertMsgReturnVoid(!RTCritSectIsOwner(&pTransfer->CritSect), + ("The transfer lock must not be held while consuming a transfer\n")); + + int rc = shClTransferThreadDestroy(pTransfer, SHCL_TIMEOUT_DEFAULT_MS); + + shClTransferLock(pTransfer); + bool const fThreadActive = pTransfer->Thread.hThread != NIL_RTTHREAD; + shClTransferUnlock(pTransfer); + if (fThreadActive) + { + LogRel(("Shared Clipboard: Transfer worker did not stop within the normal teardown timeout (%Rrc); " + "continuing to wait safely\n", rc)); + rc = shClTransferThreadDestroy(pTransfer, RT_INDEFINITE_WAIT); + + shClTransferLock(pTransfer); + bool const fThreadStillActive = pTransfer->Thread.hThread != NIL_RTTHREAD + || pTransfer->Thread.fStarted; + shClTransferUnlock(pTransfer); + AssertFatalMsg(!fThreadStillActive, ("Reaping the transfer worker failed with %Rrc\n", rc)); + } + + if (RT_FAILURE(rc)) + LogFlowFunc(("Ignoring completed transfer worker status %Rrc during consuming teardown\n", rc)); + + uint32_t cRefs; + for (;;) + { + /* Serialize the zero observation with the final releaser. It signals + * hNoRefsEvent before dropping this lock, so the event cannot be + * destroyed while ShClTransferRelease() is still using it. */ + shClTransferLock(pTransfer); + cRefs = ASMAtomicReadU32(&pTransfer->cRefs); + shClTransferUnlock(pTransfer); + if (!cRefs) + break; + + LogRel2(("Shared Clipboard: Waiting for %RU32 transfer reference(s) to drain during teardown\n", cRefs)); + rc = RTSemEventMultiWait(pTransfer->hNoRefsEvent, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(rc, ("Waiting for transfer references to drain failed with %Rrc\n", rc)); + } + + /* A retained user may have started a worker while the initial stop was in + * progress. With all retained users gone, no legitimate caller can + * publish another one, so drain the final worker before callback state. */ + rc = shClTransferThreadDestroy(pTransfer, RT_INDEFINITE_WAIT); + + shClTransferLock(pTransfer); + bool const fThreadStillActive = pTransfer->Thread.hThread != NIL_RTTHREAD + || pTransfer->Thread.fStarted; + shClTransferUnlock(pTransfer); + AssertFatalMsg(!fThreadStillActive, ("Final transfer worker drain failed with %Rrc\n", rc)); + + if (RT_FAILURE(rc)) + LogFlowFunc(("Ignoring completed transfer worker status %Rrc after reference drain\n", rc)); + + /* Temporary reference holders may use callback-owned per-transfer state. + * Destroy that state only after all of them and the worker have drained. */ + if (pTransfer->Callbacks.pfnOnDestroy) + { + pTransfer->Callbacks.pfnOnDestroy(&pTransfer->CallbackCtx); + pTransfer->Callbacks.pfnOnDestroy = NULL; + } + + rc = ShClTransferDestroy(pTransfer); + AssertFatalMsgRC(rc, ("Final transfer destruction failed with %Rrc\n", rc)); +} + /** @@ -1505,7 +1616,21 @@ DECLINLINE(void) shClTransferUnlock(PSHCLTRANSFER pTransfer) */ uint32_t ShClTransferAcquire(PSHCLTRANSFER pTransfer) { - return ASMAtomicIncU32(&pTransfer->cRefs); + shClTransferLock(pTransfer); + + uint32_t const cRefs = ASMAtomicReadU32(&pTransfer->cRefs); + AssertRelease(cRefs < UINT32_MAX); + + if (cRefs == 0) + { + int rc = RTSemEventMultiReset(pTransfer->hNoRefsEvent); + AssertFatalMsgRC(rc, ("Resetting the transfer reference event failed with %Rrc\n", rc)); + } + + uint32_t const cRefsNew = ASMAtomicIncU32(&pTransfer->cRefs); + + shClTransferUnlock(pTransfer); + return cRefsNew; } /** @@ -1516,8 +1641,17 @@ uint32_t ShClTransferAcquire(PSHCLTRANSFER pTransfer) */ uint32_t ShClTransferRelease(PSHCLTRANSFER pTransfer) { - const uint32_t cRefs = ASMAtomicDecU32(&pTransfer->cRefs); - Assert(pTransfer->cRefs <= VBOX_SHCL_MAX_TRANSFERS); /* Not perfect, but better than nothing. */ + shClTransferLock(pTransfer); + + AssertRelease(ASMAtomicReadU32(&pTransfer->cRefs) > 0); + uint32_t const cRefs = ASMAtomicDecU32(&pTransfer->cRefs); + if (cRefs == 0) + { + int rc = RTSemEventMultiSignal(pTransfer->hNoRefsEvent); + AssertFatalMsgRC(rc, ("Signalling the transfer reference event failed with %Rrc\n", rc)); + } + + shClTransferUnlock(pTransfer); return cRefs; } @@ -2558,7 +2692,6 @@ static DECLCALLBACK(int) shClTransferThreadWorker(RTTHREAD ThreadSelf, void *pvU shClTransferLock(pTransfer); pTransfer->Thread.fStarted = true; - pTransfer->Thread.fStop = false; shClTransferUnlock(pTransfer); @@ -2637,29 +2770,36 @@ static int shClTransferThreadDestroy(PSHCLTRANSFER pTransfer, RTMSINTERVAL uTime shClTransferLock(pTransfer); - if (!pTransfer->Thread.fStarted) + /* A handle is published before the worker can set fStarted. Treat the + * handle as authoritative so teardown cannot miss a starting worker. */ + if (pTransfer->Thread.hThread == NIL_RTTHREAD) { shClTransferUnlock(pTransfer); return VINF_SUCCESS; } + pTransfer->Thread.fStop = true; LogFlowFuncEnter(); - /* Set stop indicator. */ - pTransfer->Thread.fStop = true; + /* Snapshot the waitable handle. A finite-timeout failure leaves it intact + * for the consuming indefinite retry. */ + RTTHREAD const hThread = pTransfer->Thread.hThread; shClTransferUnlock(pTransfer); /* Leave lock while waiting. */ int rcThread = VERR_IPE_UNINITIALIZED_STATUS; - Assert(pTransfer->Thread.hThread != NIL_RTTHREAD); - int rc = RTThreadWait(pTransfer->Thread.hThread, uTimeoutMs, &rcThread); + Assert(hThread != NIL_RTTHREAD); + int rc = RTThreadWait(hThread, uTimeoutMs, &rcThread); LogFlowFunc(("Waiting for thread resulted in %Rrc (thread exited with %Rrc)\n", rc, rcThread)); if (RT_SUCCESS(rc)) { + shClTransferLock(pTransfer); pTransfer->Thread.fStarted = false; - pTransfer->Thread.hThread = NIL_RTTHREAD; + if (pTransfer->Thread.hThread == hThread) + pTransfer->Thread.hThread = NIL_RTTHREAD; + shClTransferUnlock(pTransfer); rc = rcThread; /* Return the thread rc to the caller. */ } @@ -2810,13 +2950,16 @@ void ShClTransferCtxDestroy(PSHCLTRANSFERCTX pTransferCtx) LogFlowFunc(("pTransferCtx=%p\n", pTransferCtx)); + RTLISTANCHOR ListDestroy; + RTListInit(&ListDestroy); + shClTransferCtxLock(pTransferCtx); PSHCLTRANSFER pTransfer, pTransferNext; RTListForEachSafe(&pTransferCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node) { - shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer); - ShClTransferDestroy(pTransfer); + shclTransferCtxTransferRemoveLocked(pTransferCtx, pTransfer); + RTListAppend(&ListDestroy, &pTransfer->Node); } pTransferCtx->cRunning = 0; @@ -2824,6 +2967,13 @@ void ShClTransferCtxDestroy(PSHCLTRANSFERCTX pTransferCtx) shClTransferCtxUnlock(pTransferCtx); + RTListForEachSafe(&ListDestroy, pTransfer, pTransferNext, SHCLTRANSFER, Node) + { + RTListNodeRemove(&pTransfer->Node); + shclTransferCtxTransferNotifyUnregistered(pTransferCtx, pTransfer); + shClTransferDestroyConsume(pTransfer); + } + if (RTCritSectIsInitialized(&pTransferCtx->CritSect)) RTCritSectDelete(&pTransferCtx->CritSect); } @@ -2956,6 +3106,29 @@ PSHCLTRANSFER ShClTransferCtxGetTransferById(PSHCLTRANSFERCTX pTransferCtx, uint return pTransfer; } +/** + * Returns and retains a clipboard transfer for a specific transfer ID. + * + * @returns Retained clipboard transfer, or NULL if not found. + * @param pTransferCtx Transfer context to return transfer for. + * @param uID ID of the transfer to return. + * + * @note The caller must release a returned transfer with ShClTransferRelease(). + */ +PSHCLTRANSFER ShClTransferCtxGetTransferByIdRetained(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID) +{ + AssertPtrReturn(pTransferCtx, NULL); + + shClTransferCtxLock(pTransferCtx); + + PSHCLTRANSFER pTransfer = shClTransferCtxGetTransferByIdInternal(pTransferCtx, uID); + if (pTransfer) + ShClTransferAcquire(pTransfer); + + shClTransferCtxUnlock(pTransferCtx); + return pTransfer; +} + /** * Returns a clipboard transfer for a specific service-session/transfer/generation key. * @@ -2994,6 +3167,47 @@ PSHCLTRANSFER ShClTransferCtxGetTransferByKey(PSHCLTRANSFERCTX pTransferCtx, SHC return pTransfer; } +/** + * Returns and retains a clipboard transfer for a service-session/transfer/generation key. + * + * @returns Retained clipboard transfer, or NULL if not found or the key does not match. + * @param pTransferCtx Transfer context to return transfer for. + * @param idSession Service session ID to match. + * @param idTransfer Transfer ID to match. + * @param uGeneration Host-private transfer generation to match. + * + * @note The caller must release a returned transfer with ShClTransferRelease(). + */ +PSHCLTRANSFER ShClTransferCtxGetTransferByKeyRetained(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration) +{ + AssertPtrReturn(pTransferCtx, NULL); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), NULL); + + shClTransferCtxLock(pTransferCtx); + + PSHCLTRANSFER pTransfer = NULL; + if (pTransferCtx->idSession == idSession) + { + pTransfer = shClTransferCtxGetTransferByIdInternal(pTransferCtx, idTransfer); + if (pTransfer) + { + PSHCLTRANSFER const pTransferToUnlock = pTransfer; + shClTransferLock(pTransferToUnlock); + if ( pTransferToUnlock->State.idSession != idSession + || pTransferToUnlock->State.uID != idTransfer + || pTransferToUnlock->State.uGeneration != uGeneration) + pTransfer = NULL; + else + ShClTransferAcquire(pTransferToUnlock); + shClTransferUnlock(pTransferToUnlock); + } + } + + shClTransferCtxUnlock(pTransferCtx); + return pTransfer; +} + /** * Returns a clipboard transfer for a specific list index. * @@ -3135,6 +3349,7 @@ static int shClTransferCtxTransferRegisterExInternal(PSHCLTRANSFERCTX pTransferC pTransfer->State.uID = idTransfer; pTransfer->State.idSession = pTransferCtx->idSession; pTransfer->State.uGeneration = shClTransferCtxCreateGenerationInternal(pTransferCtx); + pTransfer->pOwnerCtx = pTransferCtx; shClTransferUnlock(pTransfer); RTListAppend(&pTransferCtx->List, &pTransfer->Node); @@ -3249,14 +3464,14 @@ int ShClTransferCtxRegisterById(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTr } /** - * Removes and unregisters a transfer from a transfer context. + * Removes a transfer from a transfer context. * * @param pTransferCtx Transfer context to remove transfer from. * @param pTransfer Transfer to remove. * * @note Caller needs to take critical section. */ -static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer) +static void shclTransferCtxTransferRemoveLocked(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer) { Assert(RTCritSectIsOwner(&pTransferCtx->CritSect)); @@ -3271,14 +3486,47 @@ static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransfe Assert(pTransferCtx->cTransfers >= pTransferCtx->cRunning); - shClTransferCtxUnlock(pTransferCtx); + LogFlowFunc(("Now %RU32 transfers left\n", pTransferCtx->cTransfers)); +} + +/** + * Notifies a transfer that it was removed from a transfer context. + * + * @param pTransferCtx Transfer context the transfer was removed from. + * @param pTransfer Transfer that was removed. + * + * @note No owner or transfer-context lock may be held by the caller. + */ +static void shclTransferCtxTransferNotifyUnregistered(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer) +{ + Assert(!RTCritSectIsOwner(&pTransferCtx->CritSect)); + Assert(pTransfer->pOwnerCtx == pTransferCtx); if (pTransfer->Callbacks.pfnOnUnregistered) pTransfer->Callbacks.pfnOnUnregistered(&pTransfer->CallbackCtx, pTransferCtx); - shClTransferCtxLock(pTransferCtx); + pTransfer->pOwnerCtx = NULL; +} - LogFlowFunc(("Now %RU32 transfers left\n", pTransferCtx->cTransfers)); +/** + * Removes and unregisters a transfer from a transfer context. + * + * @param pTransferCtx Transfer context to remove transfer from. + * @param pTransfer Transfer to remove. + * + * @note Caller needs to take critical section. + */ +static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer) +{ + Assert(RTCritSectIsOwner(&pTransferCtx->CritSect)); + + shclTransferCtxTransferRemoveLocked(pTransferCtx, pTransfer); + + shClTransferCtxUnlock(pTransferCtx); + + shclTransferCtxTransferNotifyUnregistered(pTransferCtx, pTransfer); + + shClTransferCtxLock(pTransferCtx); } /** @@ -4562,20 +4810,38 @@ static void shClSvcTransferCleanupAllUnused(PSHCLCLIENT pClient) PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; - PSHCLTRANSFER pTransfer, pTransferNext; - RTListForEachSafe(&pTxCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node) + for (;;) { - SHCLTRANSFERSTATUS const enmSts = ShClTransferGetStatus(pTransfer); - if (enmSts != SHCLTRANSFERSTATUS_STARTED) - { - /* Let the guest know. */ - int rc2 = shClSvcTransferSendStatusAsync(pClient, pTransfer, - SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); - AssertRC(rc2); + PSHCLTRANSFER pTransfer = NULL; + + shClTransferCtxLock(pTxCtx); - ShClTransferCtxUnregisterById(pTxCtx, pTransfer->State.uID); - ShClTransferDestroy(pTransfer); + PSHCLTRANSFER pIt; + RTListForEach(&pTxCtx->List, pIt, SHCLTRANSFER, Node) + { + if (ShClTransferGetStatus(pIt) != SHCLTRANSFERSTATUS_STARTED) + { + pTransfer = pIt; + shclTransferCtxTransferRemoveLocked(pTxCtx, pTransfer); + break; + } } + + shClTransferCtxUnlock(pTxCtx); + + if (!pTransfer) + break; + + /* Let the guest know while the client state is still serialized. */ + int rc2 = shClSvcTransferSendStatusAsync(pClient, pTransfer, + SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); + AssertRC(rc2); + + ShClSvcClientUnlock(pClient); + shclTransferCtxTransferNotifyUnregistered(pTxCtx, pTransfer); + + shClTransferDestroyConsume(pTransfer); + ShClSvcClientLock(pClient); } } @@ -4589,24 +4855,37 @@ static void shClSvcTransferCleanupAllUnused(PSHCLCLIENT pClient) * @param enmSource Transfer source to create. * @param idTransfer Transfer ID to use for creation. * If set to NIL_SHCLTRANSFERID, a new transfer ID will be created. - * @param ppTransfer Where to return the created transfer on success. Optional and can be NULL. + * @param ppTransfer Where to return the retained transfer on success. Optional and can be NULL. + * The caller must release it with ShClTransferRelease(). */ int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) { AssertPtrReturn(pClient, VERR_INVALID_POINTER); - /* ppTransfer is optional. */ + if (ppTransfer) + *ppTransfer = NULL; LogFlowFuncEnter(); ShClSvcClientLock(pClient); - /* When creating a new transfer, this is a good time to clean up old stuff we don't need anymore. */ - shClSvcTransferCleanupAllUnused(pClient); - PSHCLTRANSFER pTransfer = NULL; - int rc = ShClTransferCreate(enmDir, enmSource, &pClient->Transfers.Callbacks, &pTransfer); + bool fReleaseCreationRef = false; + int rc = VERR_ACCESS_DENIED; + if (shClSvcClientTransfersAreAllowed(pClient)) + { + /* Cleanup drops the client lock while consuming stale transfers, so + * recheck policy after it reacquires the lock. */ + shClSvcTransferCleanupAllUnused(pClient); + if (shClSvcClientTransfersAreAllowed(pClient)) + rc = ShClTransferCreate(enmDir, enmSource, &pClient->Transfers.Callbacks, &pTransfer); + } if (RT_SUCCESS(rc)) { + /* Establish pointer ownership before registration publishes the + * transfer to concurrent teardown paths. */ + ShClTransferAcquire(pTransfer); + fReleaseCreationRef = true; + if (idTransfer == NIL_SHCLTRANSFERID) rc = ShClTransferCtxRegister(&pClient->Transfers.Ctx, pTransfer, &idTransfer); else @@ -4614,14 +4893,30 @@ int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURC if (RT_SUCCESS(rc)) { if (ppTransfer) + { *ppTransfer = pTransfer; + fReleaseCreationRef = false; /* The caller takes ownership. */ + } } } ShClSvcClientUnlock(pClient); + SHCLTRANSFERID const idCreated = pTransfer ? ShClTransferGetID(pTransfer) : NIL_SHCLTRANSFERID; + if (fReleaseCreationRef) + ShClTransferRelease(pTransfer); + if (RT_FAILURE(rc)) - ShClTransferDestroy(pTransfer); + { + if (ShClTransferIdIsValid(idCreated)) + ShClSvcTransferDestroyById(pClient, idCreated); + else + { + /* Registration never published this transfer, so the creation + * path still owns its pointer exclusively. */ + shClTransferDestroyConsume(pTransfer); + } + } if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Creating transfer failed with %Rrc\n", rc)); @@ -4631,36 +4926,202 @@ int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURC } /** - * Destroys a transfer on the host. + * Detaches a service transfer by ID. + * + * @returns The exclusively claimed transfer, or NULL if it was not registered. + * @param pClient Client owning the transfer context. + * @param idTransfer ID of the transfer to claim. + * @param pExpected Expected transfer pointer, or NULL when claiming + * solely by ID. + */ +static PSHCLTRANSFER shClSvcTransferDetachById(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer, + PSHCLTRANSFER pExpected) +{ + PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + + shClTransferCtxLock(pTxCtx); + + PSHCLTRANSFER pTransfer = shClTransferCtxGetTransferByIdInternal(pTxCtx, idTransfer); + if (pTransfer) + { + if ( !pExpected + || pTransfer == pExpected) + shclTransferCtxTransferRemoveLocked(pTxCtx, pTransfer); + else + { + AssertMsgFailed(("Transfer ID %RU16 resolved to %p instead of exclusively owned transfer %p\n", + idTransfer, pTransfer, pExpected)); + pTransfer = NULL; + } + } + + shClTransferCtxUnlock(pTxCtx); + return pTransfer; +} + +/** + * Finishes destruction of an exclusively claimed service transfer. + * + * @param pClient Client that owned the transfer. + * @param pTransfer Exclusively claimed transfer to consume. + * @param fNotifyGuest Whether to report UNINITIALIZED to the guest. + */ +static void shClSvcTransferDestroyClaimed(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, bool fNotifyGuest) +{ + PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + + if (fNotifyGuest) + { + ShClSvcClientLock(pClient); + int rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, + SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); + AssertRC(rc); + ShClSvcClientUnlock(pClient); + } + + shclTransferCtxTransferNotifyUnregistered(pTxCtx, pTransfer); + + shClTransferDestroyConsume(pTransfer); +} + +/** + * Destroys a transfer on the host by its context-local ID, extended version. + * + * The ID is not reused during a transfer-context session, so claiming under + * the context lock does not depend on the lifetime of a borrowed pointer. + * + * @param pClient Client to destroy transfer for. + * @param idTransfer ID of the transfer to destroy. + * @param fNotifyGuest Whether to report UNINITIALIZED to the guest. + */ +void ShClSvcTransferDestroyByIdEx(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer, bool fNotifyGuest) +{ + AssertPtrReturnVoid(pClient); + AssertReturnVoid(ShClTransferIdIsValid(idTransfer)); + AssertMsgReturnVoid(!RTCritSectIsOwner(&pClient->CritSect), + ("The client lock must not be held while destroying a transfer\n")); + + LogFlowFuncEnter(); + + PSHCLTRANSFER pTransfer = shClSvcTransferDetachById(pClient, idTransfer, NULL /* pExpected */); + if (pTransfer) + shClSvcTransferDestroyClaimed(pClient, pTransfer, fNotifyGuest); + else + LogRel2(("Shared Clipboard: Transfer %RU16 was already detached\n", idTransfer)); + + LogFlowFuncLeave(); +} + +/** + * Destroys a transfer on the host by its context-local ID. + * + * @param pClient Client to destroy transfer for. + * @param idTransfer ID of the transfer to destroy. + */ +void ShClSvcTransferDestroyById(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer) +{ + ShClSvcTransferDestroyByIdEx(pClient, idTransfer, true /* fNotifyGuest */); +} + +/** + * Destroys an exclusively owned transfer pointer on the host. * * @param pClient Client to destroy transfer for. * @param pTransfer Transfer to destroy. * The pointer will be invalid after return. + * + * @note This pointer form is only safe for a newly created transfer whose + * lifetime and publication remain exclusively controlled by the + * caller. General callers must snapshot its ID while the pointer is + * known valid and use ShClSvcTransferDestroyById(). */ void ShClSvcTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) { if (!pTransfer) return; + AssertPtrReturnVoid(pClient); + AssertMsgReturnVoid(!RTCritSectIsOwner(&pClient->CritSect), + ("The client lock must not be held while destroying a transfer\n")); + LogFlowFuncEnter(); + /* The exclusive-ownership contract makes this dereference safe. */ + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + AssertReturnVoid(ShClTransferIdIsValid(idTransfer)); + + PSHCLTRANSFER pClaimed = shClSvcTransferDetachById(pClient, idTransfer, pTransfer /* pExpected */); + if (pClaimed) + shClSvcTransferDestroyClaimed(pClient, pClaimed, true /* fNotifyGuest */); + else + LogRel2(("Shared Clipboard: Exclusively owned transfer %p was already detached\n", pTransfer)); + + LogFlowFuncLeave(); +} + +/** + * Detaches all transfers from a Shared Clipboard client for later destruction. + * + * @param pClient Client to detach transfers from. + * @param pList Destination list for detached transfers. + * + * @note Destruction callbacks are deferred until + * shClSvcTransferDestroyDetachedAll(), so the caller may hold an + * outer ownership lock while invoking this function. + */ +void shClSvcTransferDetachAll(PSHCLCLIENT pClient, PRTLISTANCHOR pList) +{ + AssertPtrReturnVoid(pClient); + AssertPtrReturnVoid(pList); + ShClSvcClientLock(pClient); PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + for (;;) + { + shClTransferCtxLock(pTxCtx); - ShClTransferCtxUnregisterById(pTxCtx, pTransfer->State.uID); + PSHCLTRANSFER pTransfer = shClTransferCtxGetTransferByIndexInternal(pTxCtx, 0 /* Index */); + if (pTransfer) + shclTransferCtxTransferRemoveLocked(pTxCtx, pTransfer); - /* Make sure to let the guest know. */ - int rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, - SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); - AssertRC(rc); + shClTransferCtxUnlock(pTxCtx); - ShClTransferDestroy(pTransfer); - pTransfer = NULL; + if (!pTransfer) + break; + + int rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, + SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); + AssertRC(rc); + + RTListAppend(pList, &pTransfer->Node); + } ShClSvcClientUnlock(pClient); +} - LogFlowFuncLeave(); +/** + * Destroys transfers previously detached by shClSvcTransferDetachAll(). + * + * @param pList List of detached transfers to consume. + * + * @note No service, client or transfer-context ownership lock may be held. + */ +void shClSvcTransferDestroyDetachedAll(PRTLISTANCHOR pList) +{ + AssertPtrReturnVoid(pList); + + PSHCLTRANSFER pTransfer, pTransferNext; + RTListForEachSafe(pList, pTransfer, pTransferNext, SHCLTRANSFER, Node) + { + RTListNodeRemove(&pTransfer->Node); + + PSHCLTRANSFERCTX pTxCtx = pTransfer->pOwnerCtx; + AssertPtr(pTxCtx); + shclTransferCtxTransferNotifyUnregistered(pTxCtx, pTransfer); + + shClTransferDestroyConsume(pTransfer); + } } @@ -4673,16 +5134,16 @@ void shClSvcTransferDestroyAll(PSHCLCLIENT pClient) { if (!pClient) return; + AssertMsgReturnVoid(!RTCritSectIsOwner(&pClient->CritSect), + ("The client lock must not be held while destroying transfers\n")); LogFlowFuncEnter(); - /* Unregister and destroy all transfers. - * Also make sure to let the backend know that all transfers are getting destroyed. - * - * Note: The index always will be 0, as the transfer gets unregistered. */ - PSHCLTRANSFER pTransfer; - while ((pTransfer = ShClTransferCtxGetTransferByIndex(&pClient->Transfers.Ctx, 0 /* Index */))) - ShClSvcTransferDestroy(pClient, pTransfer); + RTLISTANCHOR ListDestroy; + RTListInit(&ListDestroy); + + shClSvcTransferDetachAll(pClient, &ListDestroy); + shClSvcTransferDestroyDetachedAll(&ListDestroy); } #endif /* VBOX_WITH_SHARED_CLIPBOARD_HOST */ @@ -4701,8 +5162,6 @@ int ShClTransferInit(PSHCLTRANSFER pTransfer) ("Wrong status (currently is %s)\n", ShClTransferStatusToStr(pTransfer->State.enmStatus)), shClTransferUnlock(pTransfer), VERR_WRONG_ORDER); - pTransfer->cRefs = 0; - LogFlowFunc(("uID=%RU32, enmDir=%RU32, enmSource=%RU32\n", pTransfer->State.uID, pTransfer->State.enmDir, pTransfer->State.enmSource)); @@ -4761,31 +5220,35 @@ int ShClSvcTransferInit(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) ShClSvcClientLock(pClient); - Assert(ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_NONE); + int rc; + if (!shClSvcClientTransfersAreAllowed(pClient)) + rc = VERR_ACCESS_DENIED; + else + { + Assert(ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_NONE); - PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; - int rc; + if (!ShClTransferCtxIsMaximumReached(pTxCtx)) + { + SHCLTRANSFERDIR const enmDir = ShClTransferGetDir(pTransfer); - if (!ShClTransferCtxIsMaximumReached(pTxCtx)) - { - SHCLTRANSFERDIR const enmDir = ShClTransferGetDir(pTransfer); + LogRel2(("Shared Clipboard: Initializing %s transfer ...\n", + enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "guest -> host" : "host -> guest")); - LogRel2(("Shared Clipboard: Initializing %s transfer ...\n", - enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "guest -> host" : "host -> guest")); + rc = ShClTransferInit(pTransfer); + } + else + rc = VERR_SHCLPB_MAX_TRANSFERS_REACHED; - rc = ShClTransferInit(pTransfer); + /* Tell the guest the outcome. */ + int rc2 = shClSvcTransferSendStatusAsync(pClient, pTransfer, + RT_SUCCESS(rc) + ? SHCLTRANSFERSTATUS_INITIALIZED : SHCLTRANSFERSTATUS_ERROR, rc, + NULL /* ppEvent */); + if (RT_SUCCESS(rc)) + rc = rc2; } - else - rc = VERR_SHCLPB_MAX_TRANSFERS_REACHED; - - /* Tell the guest the outcome. */ - int rc2 = shClSvcTransferSendStatusAsync(pClient, pTransfer, - RT_SUCCESS(rc) - ? SHCLTRANSFERSTATUS_INITIALIZED : SHCLTRANSFERSTATUS_ERROR, rc, - NULL /* ppEvent */); - if (RT_SUCCESS(rc)) - rc = rc2; if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Initializing transfer failed with %Rrc\n", rc)); diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp index 6c2cdaf19bfa..a3cf6016fb29 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-win.cpp 114754 2026-07-22 21:18:51Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard-win.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Windows-specific functions for clipboard handling. */ @@ -1173,6 +1173,32 @@ int ShClWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) return VINF_SUCCESS; } +/** + * Unregisters the data object associated with a Windows transfer. + * + * This disables the data object and drops its long-lived transfer reference + * while leaving the per-transfer context intact for temporary users. + * + * @param pTransfer Shared Clipboard transfer to unregister. + */ +void ShClWinTransferUnregister(PSHCLTRANSFER pTransfer) +{ + AssertPtrReturnVoid(pTransfer); + + if (pTransfer->pvUser) + { + Assert(pTransfer->cbUser == sizeof(ShClWinTransferCtx)); + ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)pTransfer->pvUser; + AssertPtr(pWinURITransferCtx); + + if (pWinURITransferCtx->pDataObj) + { + pWinURITransferCtx->pDataObj->Uninit(); + pWinURITransferCtx->pDataObj = NULL; + } + } +} + /** * Destroys implementation-specific data for a Windows Shared Clipboard transfer. * @@ -1195,13 +1221,7 @@ void ShClWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)pTransfer->pvUser; AssertPtr(pWinURITransferCtx); - /* If the transfer has a data object assigned, uninitialize it here. - * Note: We don't free the object here, as other processes like the Windows Explorer still might refer to it. */ - if (pWinURITransferCtx->pDataObj) - { - pWinURITransferCtx->pDataObj->Uninit(); - pWinURITransferCtx->pDataObj = NULL; - } + ShClWinTransferUnregister(pTransfer); delete pWinURITransferCtx; pWinURITransferCtx = NULL; diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp index f2f1069be207..8919952b805c 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-backend.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-backend.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Backend and extension bridge handling. */ @@ -38,7 +38,6 @@ #include #include -#include #include #include "VBoxSharedClipboardSvc-internal.h" @@ -59,7 +58,7 @@ static DECLCALLBACK(int) shClSvcBackendExtensionCallback(uint32_t u32Function, u */ PSHCLBACKEND ShClSvcGetBackend(void) { - return &g_ShClBackend; + return &g_ShClSvc.Backend; } @@ -68,8 +67,8 @@ static int shClSvcBackendHostCallback(uint32_t u32Function, PSHCLEXTPARMS pvParm LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); int rc; - if (g_ExtState.pfnExtension) - rc = g_ExtState.pfnExtension(g_ExtState.pvExtension, u32Function, pvParms, cbParms); + if (g_ShClSvc.ExtState.pfnExtension) + rc = g_ShClSvc.ExtState.pfnExtension(g_ShClSvc.ExtState.pvExtension, u32Function, pvParms, cbParms); else rc = VERR_NOT_SUPPORTED; @@ -98,7 +97,7 @@ int shClSvcBackendConnect(PSHCLCLIENT pClient) parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); parms.u.ReadWriteData.pClient = pClient; - /* The backend in Main calls: ShClBackendConnect(&g_ShClBackend, pClient); */ + /* The backend in Main calls: ShClBackendConnect(pClient->pBackend, pClient); */ return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT, &parms, sizeof(parms)); } @@ -111,7 +110,7 @@ int shClSvcBackendSync(PSHCLCLIENT pClient) parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); parms.u.ReadWriteData.pClient = pClient; - /* The backend in Main calls: ShClBackendSync(&g_ShClBackend, pClient); */ + /* The backend in Main calls: ShClBackendSync(pClient->pBackend, pClient); */ return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC, &parms, sizeof(parms)); } @@ -123,7 +122,7 @@ void shClSvcBackendDisconnect(PSHCLCLIENT pClient) parms.u.ReadWriteData.pClient = pClient; - /* The backend in Main calls: ShClBackendDisconnect(&g_ShClBackend, pClient); */ + /* The backend in Main calls: ShClBackendDisconnect(pClient->pBackend, pClient); */ shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT, &parms, sizeof(parms)); } @@ -271,25 +270,25 @@ static DECLCALLBACK(int) shClSvcBackendExtensionCallback(uint32_t u32Function, u int rc = VINF_SUCCESS; + shClSvcLock(); + /* Figure out if the client in charge for the service extension still is connected. */ - ClipboardClientMap::const_iterator itClient = g_mapClients.find(g_ExtState.uClientID); - if (itClient != g_mapClients.end()) + PSHCLCLIENT pClient = g_ShClSvc.pActiveClient; + if (pClient) { - PSHCLCLIENT pClient = itClient->second; - AssertPtr(pClient); switch (u32Function) { /* The service extension announces formats to the host. */ case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: { - LogFlowFunc(("VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: g_ExtState.fReadingData=%RTbool\n", - g_ExtState.fReadingData)); - if (!g_ExtState.fReadingData) + LogFlowFunc(("VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: fReadingData=%RTbool\n", + g_ShClSvc.ExtState.fReadingData)); + if (!g_ShClSvc.ExtState.fReadingData) rc = shClSvcBackendReportFormatsToGuest(pClient, u32Format, SHCLSOURCE_REMOTE); else { - g_ExtState.fDelayedAnnouncement = true; - g_ExtState.fDelayedFormats = u32Format; + g_ShClSvc.ExtState.fDelayedAnnouncement = true; + g_ShClSvc.ExtState.fDelayedFormats = u32Format; rc = VINF_SUCCESS; } break; @@ -310,6 +309,8 @@ static DECLCALLBACK(int) shClSvcBackendExtensionCallback(uint32_t u32Function, u else rc = VERR_NOT_FOUND; + shClSvcUnlock(); + LogFlowFuncLeaveRC(rc); return rc; } @@ -327,14 +328,14 @@ DECLCALLBACK(int) shClSvcRegisterExtension(void *, PFNHGCMSVCEXT pfnExtension, v * layers up (in ConsoleVRDPServer::ClipboardCreate()). */ - int rc = RTCritSectEnter(&g_CritSect); - AssertLogRelRCReturn(rc, rc); + shClSvcLock(); + int rc = VINF_SUCCESS; if (pfnExtension) { /* Install extension. */ - g_ExtState.pfnExtension = pfnExtension; - g_ExtState.pvExtension = pvExtension; + g_ShClSvc.ExtState.pfnExtension = pfnExtension; + g_ShClSvc.ExtState.pvExtension = pvExtension; parms.u.SetCallback.pfnCallback = shClSvcBackendExtensionCallback; @@ -359,13 +360,13 @@ DECLCALLBACK(int) shClSvcRegisterExtension(void *, PFNHGCMSVCEXT pfnExtension, v shClSvcBackendDestroy(); /* Uninstall extension. */ - g_ExtState.pvExtension = NULL; - g_ExtState.pfnExtension = NULL; + g_ShClSvc.ExtState.pvExtension = NULL; + g_ShClSvc.ExtState.pfnExtension = NULL; LogRel2(("Shared Clipboard: de-registered service extension\n")); } - RTCritSectLeave(&g_CritSect); + shClSvcUnlock(); return rc; } diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp index 7ede01d72f59..3862568d4a15 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-client.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-client.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Client/session and message queue handling. */ @@ -56,38 +56,33 @@ using namespace HGCM; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ -static int shClSvcClientStateInit(PSHCLCLIENTSTATE pState, uint32_t uClientID); +static int shClSvcClientStateInit(PSHCLCLIENTSTATE pState, uint32_t uClientID, SHCLSESSIONID idSession); static int shClSvcClientStateTerm(PSHCLCLIENTSTATE pState); static void shClSvcClientStateReset(PSHCLCLIENTSTATE pState); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + +/** + * Allocates the next non-zero service session ID. + * + * @returns Session ID. + */ static SHCLSESSIONID shClSvcClientAllocSessionId(void) { - bool const fOwnsSvcLock = RTCritSectIsOwner(&g_CritSect); - if (!fOwnsSvcLock) - { - int rc = RTCritSectEnter(&g_CritSect); - AssertRCReturn(rc, 1); - } + shClSvcLock(); - if ( g_idNextSession == 0 - || g_idNextSession == NIL_SHCLSESSIONID) - g_idNextSession = 1; + if ( g_ShClSvc.idNextSession == 0 + || g_ShClSvc.idNextSession == NIL_SHCLSESSIONID) + g_ShClSvc.idNextSession = 1; - SHCLSESSIONID const idSession = g_idNextSession++; - if ( g_idNextSession == 0 - || g_idNextSession == NIL_SHCLSESSIONID) - g_idNextSession = 1; + SHCLSESSIONID const idSession = g_ShClSvc.idNextSession++; + if ( g_ShClSvc.idNextSession == 0 + || g_ShClSvc.idNextSession == NIL_SHCLSESSIONID) + g_ShClSvc.idNextSession = 1; - if (!fOwnsSvcLock) - { - int rc = RTCritSectLeave(&g_CritSect); - AssertRC(rc); - } + shClSvcUnlock(); return idSession; } -#endif /** * Resets a client's state message queue. @@ -118,6 +113,7 @@ static void shClSvcClientMsgQueueReset(PSHCLCLIENT pClient) /** * Initializes a Shared Clipboard client. * + * @returns VBox status code. * @param pClient Client to initialize. * @param uClientID HGCM client ID to assign client to. */ @@ -125,15 +121,17 @@ int ShClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID) { AssertPtrReturn(pClient, VERR_INVALID_POINTER); + SHCLSESSIONID const idSession = shClSvcClientAllocSessionId(); + /* Assign the client ID. */ pClient->State.uClientID = uClientID; /* Cache the current Shared Clipboard mode for the backend. */ - pClient->State.uMode = ShClSvcGetMode(); + ASMAtomicWriteU32(&pClient->State.uMode, ShClSvcGetMode()); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /* Cache the current Shared Clipboard transfer (file) mode for the backend. */ - pClient->State.Transfers.uTransferMode = g_fTransferMode; + ASMAtomicWriteU32(&pClient->State.Transfers.uTransferMode, shClSvcTransferModeGet()); #endif RTListInit(&pClient->MsgQueue); @@ -144,6 +142,11 @@ int ShClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID) LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + bool fEventSourceInitialized = false; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fTransferCtxInitialized = false; +#endif + int rc = RTCritSectInit(&pClient->CritSect); if (RT_SUCCESS(rc)) { @@ -151,23 +154,41 @@ int ShClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID) rc = ShClEventSourceInit(&pClient->EventSrc, 0 /* ID, ignored */); if (RT_SUCCESS(rc)) { + fEventSourceInitialized = true; LogFlowFunc(("[Client %RU32] Using event source %RU32\n", uClientID, pClient->EventSrc.uID)); /* Reset the client state. */ shClSvcClientStateReset(&pClient->State); /* (Re-)initialize the client state. */ - rc = shClSvcClientStateInit(&pClient->State, uClientID); + rc = shClSvcClientStateInit(&pClient->State, uClientID, idSession); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if (RT_SUCCESS(rc)) { rc = ShClTransferCtxInit(&pClient->Transfers.Ctx); if (RT_SUCCESS(rc)) + { + fTransferCtxInitialized = true; rc = ShClTransferCtxBeginSession(&pClient->Transfers.Ctx, pClient->State.uSessionID); + } } #endif } + + if (RT_FAILURE(rc)) + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fTransferCtxInitialized) + ShClTransferCtxDestroy(&pClient->Transfers.Ctx); +#endif + if (fEventSourceInitialized) + ShClEventSourceTerm(&pClient->EventSrc); + shClSvcClientStateTerm(&pClient->State); + + int const rc2 = RTCritSectDelete(&pClient->CritSect); + AssertRC(rc2); + } } LogFlowFuncLeaveRC(rc); @@ -185,6 +206,11 @@ void shClSvcClientDestroy(PSHCLCLIENT pClient) LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + shClSvcLock(); + if (g_ShClSvc.pActiveClient == pClient) + g_ShClSvc.pActiveClient = NULL; + shClSvcUnlock(); + /* Make sure to send a quit message to the guest so that it can terminate gracefully. */ ShClSvcClientLock(pClient); @@ -194,7 +220,7 @@ void shClSvcClientDestroy(PSHCLCLIENT pClient) HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_QUIT); if (pClient->Pending.cParms > 2) HGCMSvcSetU32(&pClient->Pending.paParms[1], 0); - g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); + g_ShClSvc.pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); pClient->Pending.uType = 0; pClient->Pending.cParms = 0; pClient->Pending.hHandle = NULL; @@ -202,7 +228,9 @@ void shClSvcClientDestroy(PSHCLCLIENT pClient) } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ShClSvcClientUnlock(pClient); shClSvcTransferDestroyAll(pClient); + ShClSvcClientLock(pClient); ShClTransferCtxDestroy(&pClient->Transfers.Ctx); #endif @@ -218,12 +246,6 @@ void shClSvcClientDestroy(PSHCLCLIENT pClient) int rc2 = RTCritSectDelete(&pClient->CritSect); AssertRC(rc2); - ClipboardClientMap::iterator itClient = g_mapClients.find(pClient->State.uClientID); - if (itClient != g_mapClients.end()) - g_mapClients.erase(itClient); - else - AssertFailed(); - LogFlowFuncLeave(); } @@ -237,8 +259,11 @@ void shClSvcClientReset(PSHCLCLIENT pClient) if (!pClient) return; + /* Allocate outside the client lock to preserve the service -> client lock order. */ + SHCLSESSIONID const idSession = shClSvcClientAllocSessionId(); + LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); - RTCritSectEnter(&pClient->CritSect); + ShClSvcClientLock(pClient); uint32_t const uClientID = pClient->State.uClientID; @@ -252,15 +277,17 @@ void shClSvcClientReset(PSHCLCLIENT pClient) RT_ZERO(pClient->Pending); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ShClSvcClientUnlock(pClient); shClSvcTransferDestroyAll(pClient); + ShClSvcClientLock(pClient); #endif shClSvcClientStateReset(&pClient->State); - int rc2 = shClSvcClientStateInit(&pClient->State, uClientID); + int rc2 = shClSvcClientStateInit(&pClient->State, uClientID, idSession); AssertRC(rc2); - pClient->State.uMode = ShClSvcGetMode(); + ASMAtomicWriteU32(&pClient->State.uMode, ShClSvcGetMode()); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - pClient->State.Transfers.uTransferMode = g_fTransferMode; + ASMAtomicWriteU32(&pClient->State.Transfers.uTransferMode, shClSvcTransferModeGet()); if (RT_SUCCESS(rc2)) { rc2 = ShClTransferCtxBeginSession(&pClient->Transfers.Ctx, pClient->State.uSessionID); @@ -268,7 +295,7 @@ void shClSvcClientReset(PSHCLCLIENT pClient) } #endif - RTCritSectLeave(&pClient->CritSect); + ShClSvcClientUnlock(pClient); } DECLCALLBACK(void) shClSvcClientCall(void *, @@ -283,10 +310,6 @@ DECLCALLBACK(void) shClSvcClientCall(void *, RT_NOREF(u32ClientID, pvClient, tsArrival); PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); - pClient->State.uMode = ShClSvcGetMode(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - pClient->State.Transfers.uTransferMode = g_fTransferMode; -#endif #ifdef LOG_ENABLED Log2Func(("u32ClientID=%RU32, fn=%RU32 (%s), cParms=%RU32, paParms=%p\n", @@ -314,7 +337,7 @@ DECLCALLBACK(void) shClSvcClientCall(void *, } } Log2Func(("Client state: fFlags=0x%x, fGuestFeatures0=0x%x, fGuestFeatures1=0x%x\n", - pClient->State.fFlags, pClient->State.fGuestFeatures0, pClient->State.fGuestFeatures1)); + pClient->State.fFlags, ShClSvcClientGetGuestFeatures0(pClient), ShClSvcClientGetGuestFeatures1(pClient))); #endif int rc; @@ -408,7 +431,7 @@ DECLCALLBACK(void) shClSvcClientCall(void *, LogFlowFunc(("[Client %RU32] rc=%Rrc\n", pClient->State.uClientID, rc)); if (rc != VINF_HGCM_ASYNC_EXECUTE) - g_pHelpers->pfnCallComplete(callHandle, rc); + g_ShClSvc.pHelpers->pfnCallComplete(callHandle, rc); } int shClSvcClientNegotiateChunkSize(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, @@ -440,7 +463,7 @@ int shClSvcClientNegotiateChunkSize(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCal paParms[1].u.uint32 = RT_MIN(cbClientMaxChunkSize, pClient->State.cbChunkSize); /* Preferred */ } - int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + int rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); if (RT_SUCCESS(rc)) { Log(("[Client %RU32] chunk size: %#RU32, max: %#RU32\n", @@ -482,17 +505,20 @@ int shClSvcClientReportFeatures(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, /* * Do the work. */ - paParms[0].u.uint64 = g_fHostFeatures0; + paParms[0].u.uint64 = g_ShClSvc.fHostFeatures0; paParms[1].u.uint64 = 0; - int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + int rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); if (RT_SUCCESS(rc)) { - pClient->State.fGuestFeatures0 = fFeatures0; - pClient->State.fGuestFeatures1 = fFeatures1; + ShClSvcClientLock(pClient); + ASMAtomicWriteU64(&pClient->State.fGuestFeatures0, fFeatures0); + ASMAtomicWriteU64(&pClient->State.fGuestFeatures1, fFeatures1); + ShClSvcClientUnlock(pClient); + LogRel2(("Shared Clipboard: Guest reported the following features: %#RX64\n", - pClient->State.fGuestFeatures0)); /* Note: fFeatures1 not used yet. */ - if (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS) + fFeatures0)); /* Note: fFeatures1 not used yet. */ + if (fFeatures0 & VBOX_SHCL_GF_0_TRANSFERS) LogRel2(("Shared Clipboard: Guest supports file transfers\n")); } else @@ -525,9 +551,9 @@ int shClSvcClientMsgQueryFeatures(VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBO /* * Do the work. */ - paParms[0].u.uint64 = g_fHostFeatures0; + paParms[0].u.uint64 = g_ShClSvc.fHostFeatures0; paParms[1].u.uint64 = 0; - int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + int rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); if (RT_FAILURE(rc)) LogFunc(("pfnCallComplete -> %Rrc\n", rc)); @@ -579,7 +605,7 @@ int shClSvcClientMsgPeek(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t */ if (idRestoreCheck != 0) { - uint64_t idRestore = g_pHelpers->pfnGetVMMDevSessionId(g_pHelpers); + uint64_t idRestore = g_ShClSvc.pHelpers->pfnGetVMMDevSessionId(g_ShClSvc.pHelpers); if (idRestoreCheck != idRestore) { paParms[0].u.uint64 = idRestore; @@ -587,7 +613,7 @@ int shClSvcClientMsgPeek(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t pClient->State.uClientID, idRestoreCheck, idRestore)); return VERR_VM_RESTORED; } - Assert(!g_pHelpers->pfnIsCallRestored(hCall)); + Assert(!g_ShClSvc.pHelpers->pfnIsCallRestored(hCall)); } /* @@ -661,8 +687,8 @@ int shClSvcClientMsgOldGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32 ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); rc = shClSvcMsgSetOldWaitReturn(pFirstMsg, paParms, cParms); - AssertPtr(g_pHelpers); - rc = g_pHelpers->pfnCallComplete(hCall, rc); + AssertPtr(g_ShClSvc.pHelpers); + rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, rc); if (rc != VERR_CANCELLED) { RTListNodeRemove(&pFirstMsg->ListEntry); @@ -792,8 +818,8 @@ int shClSvcClientMsgGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t * Complete the message and remove the pending message unless the * guest raced us and cancelled this call in the meantime. */ - AssertPtr(g_pHelpers); - rc = g_pHelpers->pfnCallComplete(hCall, rc); + AssertPtr(g_ShClSvc.pHelpers); + rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, rc); LogFlowFunc(("[Client %RU32] pfnCallComplete -> %Rrc\n", pClient->State.uClientID, rc)); @@ -873,7 +899,7 @@ int shClSvcClientMsgCancel(PSHCLCLIENT pClient, uint32_t cParms) rcComplete = pClient->Pending.cParms == 2 ? VINF_SUCCESS : VERR_TRY_AGAIN; } - g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, rcComplete); + g_ShClSvc.pHelpers->pfnCallComplete(pClient->Pending.hHandle, rcComplete); pClient->Pending.hHandle = NULL; pClient->Pending.paParms = NULL; @@ -911,7 +937,7 @@ int shClSvcClientMsgReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCM */ ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS || ( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B - && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)), + && (ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID)), VERR_WRONG_PARAMETER_COUNT); uintptr_t iParm = 0; @@ -949,15 +975,9 @@ int shClSvcClientMsgReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCM #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS fFormats = shClSvcHandleFormats(false /* fHostToGuest */, pClient, fFormats); #endif - rc = RTCritSectEnter(&g_CritSect); - if (RT_SUCCESS(rc)) - { - rc = shClSvcBackendReportFormatsToHost(pClient, fFormats); - - RTCritSectLeave(&g_CritSect); - } - else - LogRel2(("Shared Clipboard: Unable to take internal lock while receiving guest clipboard announcement: %Rrc\n", rc)); + shClSvcLock(); + rc = shClSvcBackendReportFormatsToHost(pClient, fFormats); + shClSvcUnlock(); } return rc; @@ -1001,7 +1021,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA */ ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_DATA_READ || ( cParms == VBOX_SHCL_CPARMS_DATA_READ_61B - && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)), + && (ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID)), VERR_WRONG_PARAMETER_COUNT); uintptr_t iParm = 0; @@ -1052,7 +1072,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if ( uFormat == VBOX_SHCL_FMT_URI_LIST - && shClSvcHandleFormats(true /* fHostToGuest */, pClient, uFormat) != uFormat) + && !shClSvcClientTransfersAreAllowed(pClient)) #else if (uFormat == VBOX_SHCL_FMT_URI_LIST) #endif @@ -1066,7 +1086,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA */ /** @todo r=bird: I really don't get why you need the State.POD.uFormat * member. I'm sure there is a reason. Incomplete code? */ - if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + if (!(ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID)) { if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE) pClient->State.POD.uFormat = uFormat; @@ -1085,30 +1105,30 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA */ uint32_t cbActual = 0; - int rc = RTCritSectEnter(&g_CritSect); - AssertRCReturn(rc, rc); + shClSvcLock(); - g_ExtState.fReadingData = true; + g_ShClSvc.ExtState.fReadingData = true; /* If there is a service extension active, try reading data from it first. */ - rc = shClSvcBackendReadData(pClient, uFormat, pvData, cbData, &cbActual); + int rc = shClSvcBackendReadData(pClient, uFormat, pvData, cbData, &cbActual); LogRel2(("Shared Clipboard: Read extension clipboard data (fDelayedAnnouncement=%RTbool, fDelayedFormats=%#x, " - "max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", g_ExtState.fDelayedAnnouncement, g_ExtState.fDelayedFormats, + "max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", g_ShClSvc.ExtState.fDelayedAnnouncement, + g_ShClSvc.ExtState.fDelayedFormats, cbData, cbActual, rc)); /* Did the extension send the clipboard formats yet? * Otherwise, do this now. */ - if (g_ExtState.fDelayedAnnouncement) + if (g_ShClSvc.ExtState.fDelayedAnnouncement) { - int rc2 = shClSvcBackendReportFormatsToGuest(pClient, g_ExtState.fDelayedFormats, SHCLSOURCE_REMOTE); + int rc2 = shClSvcBackendReportFormatsToGuest(pClient, g_ShClSvc.ExtState.fDelayedFormats, SHCLSOURCE_REMOTE); AssertRC(rc2); - g_ExtState.fDelayedAnnouncement = false; - g_ExtState.fDelayedFormats = 0; + g_ShClSvc.ExtState.fDelayedAnnouncement = false; + g_ShClSvc.ExtState.fDelayedFormats = 0; } - g_ExtState.fReadingData = false; + g_ShClSvc.ExtState.fReadingData = false; if (RT_SUCCESS(rc)) { @@ -1123,7 +1143,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA rc = VINF_BUFFER_OVERFLOW; } - RTCritSectLeave(&g_CritSect); + shClSvcUnlock(); LogFlowFuncLeaveRC(rc); return rc; @@ -1154,7 +1174,7 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP else return VERR_ACCESS_DENIED; - const bool fReportsContextID = RT_BOOL(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID); + const bool fReportsContextID = RT_BOOL(ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID); /* * Digest parameters. @@ -1249,7 +1269,7 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP */ /** @todo r=bird: I really don't get why you need the State.POD.uFormat * member. I'm sure there is a reason. Incomplete code? */ - if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + if (!(ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID)) { if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE) pClient->State.POD.uFormat = uFormat; @@ -1266,12 +1286,11 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP /* * Write the data to the active host side clipboard. */ - int rc = RTCritSectEnter(&g_CritSect); - AssertRCReturn(rc, rc); + shClSvcLock(); - rc = shClSvcBackendWriteData(pClient, &cmdCtx, uFormat, pvData, cbData); + int const rc = shClSvcBackendWriteData(pClient, &cmdCtx, uFormat, pvData, cbData); - RTCritSectLeave(&g_CritSect); + shClSvcUnlock(); LogFlowFuncLeaveRC(rc); return rc; @@ -1319,8 +1338,9 @@ int shClSvcClientMsgError(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM * @returns VBox status code. * @param pClientState Client state to initialize. * @param uClientID Client ID (HGCM) to use for this client state. + * @param idSession Service session ID to assign. */ -static int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID) +static int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID, SHCLSESSIONID idSession) { LogFlowFuncEnter(); @@ -1328,9 +1348,7 @@ static int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClien /* Register the client. */ pClientState->uClientID = uClientID; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - pClientState->uSessionID = shClSvcClientAllocSessionId(); -#endif + pClientState->uSessionID = idSession; return VINF_SUCCESS; } @@ -1359,8 +1377,8 @@ static void shClSvcClientStateReset(PSHCLCLIENTSTATE pState) { LogFlowFuncEnter(); - pState->fGuestFeatures0 = VBOX_SHCL_GF_NONE; - pState->fGuestFeatures1 = VBOX_SHCL_GF_NONE; + ASMAtomicWriteU64(&pState->fGuestFeatures0, VBOX_SHCL_GF_NONE); + ASMAtomicWriteU64(&pState->fGuestFeatures1, VBOX_SHCL_GF_NONE); pState->cbChunkSize = VBOX_SHCL_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */ pState->enmSource = SHCLSOURCE_INVALID; diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp index 411ba78ebb03..3dfafa810ede 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-host.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-host.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host-controlled service handling. */ @@ -39,7 +39,6 @@ #include #include -#include #include "VBoxSharedClipboardSvc-internal.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS @@ -55,9 +54,20 @@ using namespace HGCM; static void shClSvcHostReset(void); +/** + * Sets the host-controlled Shared Clipboard mode. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if @a uMode is not a VBOX_SHCL_MODE_XXX value. + * @param uMode New VBOX_SHCL_MODE_XXX value. + * + * Invalid values fail closed by switching the effective mode to + * VBOX_SHCL_MODE_OFF. + */ int shClSvcHostModeSet(uint32_t uMode) { int rc = VERR_NOT_SUPPORTED; + uint32_t uModeNew = VBOX_SHCL_MODE_OFF; switch (uMode) { @@ -69,19 +79,25 @@ int shClSvcHostModeSet(uint32_t uMode) RT_FALL_THROUGH(); case VBOX_SHCL_MODE_BIDIRECTIONAL: { - g_uMode = uMode; - + uModeNew = uMode; rc = VINF_SUCCESS; break; } default: - { - g_uMode = VBOX_SHCL_MODE_OFF; break; - } } + shClSvcLock(); + ASMAtomicWriteU32(&g_ShClSvc.uMode, uModeNew); + if (g_ShClSvc.pActiveClient) + { + ShClSvcClientLock(g_ShClSvc.pActiveClient); + ASMAtomicWriteU32(&g_ShClSvc.pActiveClient->State.uMode, uModeNew); + ShClSvcClientUnlock(g_ShClSvc.pActiveClient); + } + shClSvcUnlock(); + LogFlowFuncLeaveRC(rc); return rc; } @@ -92,20 +108,34 @@ int shClSvcHostModeSet(uint32_t uMode) */ static void shClSvcHostReset(void) { - int rc = RTCritSectEnter(&g_CritSect); - AssertRC(rc); - if (RT_FAILURE(rc)) - return; + shClSvcLock(); + + PSHCLCLIENT const pClient = g_ShClSvc.pActiveClient; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RTLISTANCHOR ListDestroy; + RTListInit(&ListDestroy); + + /* Keep the client stable while claiming its transfers, but do not run + * platform callbacks or wait for transfer users under the service lock. */ + if (pClient) + shClSvcTransferDetachAll(pClient, &ListDestroy); + + shClSvcUnlock(); + + shClSvcTransferDestroyDetachedAll(&ListDestroy); + + shClSvcLock(); +#endif - for (ClipboardClientMap::iterator itClient = g_mapClients.begin(); itClient != g_mapClients.end(); ++itClient) - if (itClient->second) - shClSvcClientReset(itClient->second); + if ( pClient + && g_ShClSvc.pActiveClient == pClient) + shClSvcClientReset(pClient); - g_ExtState.fReadingData = false; - g_ExtState.fDelayedAnnouncement = false; - g_ExtState.fDelayedFormats = 0; + g_ShClSvc.ExtState.fReadingData = false; + g_ShClSvc.ExtState.fDelayedAnnouncement = false; + g_ShClSvc.ExtState.fDelayedFormats = 0; - RTCritSectLeave(&g_CritSect); + shClSvcUnlock(); } diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h index 5430fe086d83..7eb9b970cc7c 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-internal.h 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-internal.h 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal service instance state. */ @@ -36,6 +36,24 @@ #include +/** + * State of the optional service extension installed by a host component. + */ +typedef struct SHCLEXTSTATE +{ + /** Registered service extension entry point, or NULL. */ + PFNHGCMSVCEXT pfnExtension; + /** Opaque extension-provided data. */ + void *pvExtension; + /** Whether the host service is reading clipboard data currently. */ + bool fReadingData; + /** Whether the service extension announced formats while data was read. */ + bool fDelayedAnnouncement; + /** Formats announced while the host service was reading data. */ + uint32_t fDelayedFormats; +} SHCLEXTSTATE; + + /** * Shared Clipboard host service instance state. */ @@ -51,18 +69,16 @@ typedef struct SHCLSERVICE RTCRITSECT CritSect; /** Current Shared Clipboard mode. */ uint32_t uMode; + /** Next non-zero service session ID to assign to a client. */ + SHCLSESSIONID idNextSession; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** Current Shared Clipboard file transfer mode. */ uint32_t fTransferMode; - /** Next non-zero service session ID to assign to a client. */ - SHCLSESSIONID idNextSession; #endif /** Service extension state. */ SHCLEXTSTATE ExtState; - /** Connected HGCM clients keyed by client ID. */ - ClipboardClientMap mapClients; - /** Deferred clients ready to process new commands. */ - ClipboardClientQueue listClientsDeferred; + /** The one active HGCM client. This is a weak pointer owned by HGCM. */ + PSHCLCLIENT pActiveClient; /** Host feature mask (VBOX_SHCL_HF_0_XXX). */ uint64_t fHostFeatures0; @@ -70,10 +86,11 @@ typedef struct SHCLSERVICE : pHelpers(NULL) , pTable(NULL) , uMode(VBOX_SHCL_MODE_OFF) + , idNextSession(1) #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS , fTransferMode(VBOX_SHCL_TRANSFER_MODE_F_NONE) - , idNextSession(1) #endif + , pActiveClient(NULL) , fHostFeatures0(VBOX_SHCL_HF_0_CONTEXT_ID #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS | VBOX_SHCL_HF_0_TRANSFERS @@ -91,21 +108,18 @@ typedef SHCLSERVICE *PSHCLSERVICE; /** The single Shared Clipboard HGCM host service instance. */ extern SHCLSERVICE g_ShClSvc; -/* Transitional aliases. These keep the initial instance-state patch small and - will be removed as client/control/state code is split into local units. */ -#define g_ShClBackend (g_ShClSvc.Backend) -#define g_pHelpers (g_ShClSvc.pHelpers) -#define g_pTable (g_ShClSvc.pTable) -#define g_CritSect (g_ShClSvc.CritSect) -#define g_uMode (g_ShClSvc.uMode) +/** @name Service-global locking. + * @{ */ +void shClSvcLock(void); +void shClSvcUnlock(void); +/** @} */ + #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# define g_fTransferMode (g_ShClSvc.fTransferMode) -# define g_idNextSession (g_ShClSvc.idNextSession) +/** @name Service-global transfer policy. + * @{ */ +uint32_t shClSvcTransferModeGet(void); +/** @} */ #endif -#define g_ExtState (g_ShClSvc.ExtState) -#define g_mapClients (g_ShClSvc.mapClients) -#define g_listClientsDeferred (g_ShClSvc.listClientsDeferred) -#define g_fHostFeatures0 (g_ShClSvc.fHostFeatures0) /** @name Host-controlled service handling. * @{ */ diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp index aab821558bd5..836c918a8300 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal code for transfer (list) handling. */ @@ -57,14 +57,15 @@ static int shClSvcTransferModeSet(uint32_t fMode); /** - * Looks up a transfer by service-session/transfer/generation key across all connected clients. + * Looks up a transfer by service-session/transfer/generation key in the active client. * * @returns VBox status code. * @param idSession Service session ID to look up. * @param idTransfer Transfer ID to look up. * @param uGeneration Host-private transfer generation to look up. * @param ppClient Where to return the owning client. - * @param ppTransfer Where to return the transfer. + * @param ppTransfer Where to return the retained transfer. The caller + * must release it with ShClTransferRelease(). */ static int shClSvcTransferFindByKey(SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration, PSHCLCLIENT *ppClient, PSHCLTRANSFER *ppTransfer) @@ -76,45 +77,24 @@ static int shClSvcTransferFindByKey(SHCLSESSIONID idSession, SHCLTRANSFERID idTr *ppClient = NULL; *ppTransfer = NULL; - int rc = RTCritSectEnter(&g_CritSect); - if (RT_FAILURE(rc)) - return rc; + shClSvcLock(); - ClipboardClientMap::const_iterator itClient = g_mapClients.begin(); - while (itClient != g_mapClients.end()) + PSHCLCLIENT pClient = g_ShClSvc.pActiveClient; + if ( pClient + && pClient->State.uSessionID == idSession) { - PSHCLCLIENT pClient = itClient->second; - if ( pClient - && pClient->State.uSessionID == idSession) + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferByKeyRetained(&pClient->Transfers.Ctx, idSession, + idTransfer, uGeneration); + if (pTransfer) { - PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferByKey(&pClient->Transfers.Ctx, idSession, - idTransfer, uGeneration); - if (pTransfer) - { - if (*ppTransfer) - { - rc = VERR_DUPLICATE; - break; - } - - *ppClient = pClient; - *ppTransfer = pTransfer; - } + *ppClient = pClient; + *ppTransfer = pTransfer; } - - ++itClient; } - int rc2 = RTCritSectLeave(&g_CritSect); - AssertRC(rc2); - if (RT_SUCCESS(rc)) - rc = rc2; - - if ( RT_SUCCESS(rc) - && !*ppTransfer) - rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; + shClSvcUnlock(); - return rc; + return *ppTransfer ? VINF_SUCCESS : VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; } @@ -145,13 +125,6 @@ static int shClSvcTransferAbortByHostKey(uint64_t uContextId, SHCLTRANSFERGEN uG ShClSvcClientLock(pClient); - pTransfer = ShClTransferCtxGetTransferByKey(&pClient->Transfers.Ctx, idSession, idTransfer, uGeneration); - if (!pTransfer) - { - ShClSvcClientUnlock(pClient); - return VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; - } - int rcState; if (enmStatus == SHCLTRANSFERSTATUS_CANCELED) rcState = ShClTransferCancel(pTransfer); @@ -162,19 +135,18 @@ static int shClSvcTransferAbortByHostKey(uint64_t uContextId, SHCLTRANSFERGEN uG if (RT_SUCCESS(rcState)) rcStatus = shClSvcTransferSendStatusAsync(pClient, pTransfer, enmStatus, rcTransfer, NULL /* ppEvent */); - int rcUnregister = ShClTransferCtxUnregisterById(&pClient->Transfers.Ctx, idTransfer); - ShClSvcClientUnlock(pClient); - int rcDestroy = ShClTransferDestroy(pTransfer); + /* Drop the lookup retain before the consuming destroy waits for all users. */ + ShClTransferRelease(pTransfer); + + /* The terminal status was already reported above. */ + ShClSvcTransferDestroyByIdEx(pClient, idTransfer, false /* fNotifyGuest */); + if (RT_SUCCESS(rc)) rc = rcState; if (RT_SUCCESS(rc)) rc = rcStatus; - if (RT_SUCCESS(rc)) - rc = rcUnregister; - if (RT_SUCCESS(rc)) - rc = rcDestroy; return rc; } @@ -669,12 +641,19 @@ static int shClSvcTransferGetObjDataChunk(uint32_t cParms, VBOXHGCMSVCPARM aParm * @param pTransfer Transfer to handle reply for. * @param cParms Number of function parameters supplied. * @param aParms Array function parameters supplied. + * @param pfDestroyTransfer Where to return whether the caller must destroy + * the retained transfer after releasing it. */ -static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, uint32_t cParms, VBOXHGCMSVCPARM aParms[]) +static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, uint32_t cParms, + VBOXHGCMSVCPARM aParms[], bool *pfDestroyTransfer) { + AssertPtrReturn(pfDestroyTransfer, VERR_INVALID_POINTER); + *pfDestroyTransfer = false; + LogFlowFunc(("pTransfer=%p\n", pTransfer)); - int rc; + int rc; + bool fReleaseCreatedTransfer = false; uint32_t cbReply = sizeof(SHCLREPLY); PSHCLREPLY pReply = (PSHCLREPLY)RTMemAlloc(cbReply); @@ -745,6 +724,8 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra &pTransfer); if (RT_SUCCESS(rc)) { + fReleaseCreatedTransfer = true; + ShClSvcClientLock(pClient); rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, @@ -838,7 +819,7 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra LogRelMax(16, ("Shared Clipboard: Guest reported error %Rrc for transfer %RU16\n", pReply->rc, pTransfer->State.uID)); - if (g_ExtState.pfnExtension) + if (g_ShClSvc.ExtState.pfnExtension) { SHCLEXTPARMS parms; RT_ZERO(parms); @@ -848,7 +829,8 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra pReply->rc, pTransfer->State.uID); AssertPtrBreakStmt(parms.u.Error.pszMsg, rc = VERR_NO_MEMORY); - g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_ERROR, &parms, sizeof(parms)); + g_ShClSvc.ExtState.pfnExtension(g_ShClSvc.ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_ERROR, + &parms, sizeof(parms)); RTStrFree(parms.u.Error.pszMsg); parms.u.Error.pszMsg = NULL; @@ -868,7 +850,7 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra } /* Tell the backend. */ - if (g_ExtState.pfnExtension) + if (g_ShClSvc.ExtState.pfnExtension) { SHCLEXTPARMS parms; RT_ZERO(parms); @@ -882,8 +864,9 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra * pTransfer, SHCLSOURCE_REMOTE, pReply->u.TransferStatus.uStatus, * pReply->rc); */ - int rc2 = g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, - &parms, sizeof(parms)); + int rc2 = g_ShClSvc.ExtState.pfnExtension(g_ShClSvc.ExtState.pvExtension, + VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, + &parms, sizeof(parms)); if (RT_SUCCESS(rc)) rc = rc2; } @@ -925,12 +908,10 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra break; } - if ( ShClTransferIsAborted(pTransfer) - || ShClTransferIsComplete(pTransfer)) - { - ShClSvcTransferDestroy(pClient, pTransfer); - pTransfer = NULL; - } + if ( pTransfer + && ( ShClTransferIsAborted(pTransfer) + || ShClTransferIsComplete(pTransfer))) + *pfDestroyTransfer = true; if (pPayload) { @@ -956,6 +937,9 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra RTMemFree(pReply); } + if (fReleaseCreatedTransfer) + ShClTransferRelease(pTransfer); + LogFlowFuncLeaveRC(rc); return rc; } @@ -980,24 +964,27 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, { RT_NOREF(callHandle, aParms, tsArrival); - LogFlowFunc(("uClient=%RU32, u32Function=%RU32 (%s), cParms=%RU32, g_ExtState.pfnExtension=%p\n", - pClient->State.uClientID, u32Function, ShClSvcGuestMsgToStr(u32Function), cParms, g_ExtState.pfnExtension)); + LogFlowFunc(("uClient=%RU32, u32Function=%RU32 (%s), cParms=%RU32, pfnExtension=%p\n", + pClient->State.uClientID, u32Function, ShClSvcGuestMsgToStr(u32Function), cParms, + g_ShClSvc.ExtState.pfnExtension)); + uint64_t const fGuestFeatures0 = ShClSvcClientGetGuestFeatures0(pClient); if ( u32Function > VBOX_SHCL_GUEST_FN_LAST - || !(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + || !(fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) return VERR_NOT_IMPLEMENTED; - if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS)) + if (!(fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS)) { LogRelMax2(16, ("Shared Clipboard: Guest attempted file transfer message %s without negotiated transfer support (features0=%#RX64)\n", - ShClSvcGuestMsgToStr(u32Function), pClient->State.fGuestFeatures0)); + ShClSvcGuestMsgToStr(u32Function), fGuestFeatures0)); return VERR_ACCESS_DENIED; } - if (!(g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) + uint32_t const fTransferMode = shClSvcTransferModeGet(); + if (!(fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) { LogRelMax2(16, ("Shared Clipboard: Guest attempted file transfer message %s, but file transfers are disabled for this VM (transfer mode=%#x)\n", - ShClSvcGuestMsgToStr(u32Function), g_fTransferMode)); + ShClSvcGuestMsgToStr(u32Function), fTransferMode)); return VERR_ACCESS_DENIED; } @@ -1044,7 +1031,9 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, * Pre-check: For certain messages we need to make sure that a (right) transfer is present. */ const SHCLTRANSFERID idTransfer = fZeroContext ? NIL_SHCLTRANSFERID : VBOX_SHCL_CONTEXTID_GET_TRANSFER(uCID); - PSHCLTRANSFER pTransfer = fZeroContext ? NULL : ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idTransfer); + PSHCLTRANSFER pTransfer = fZeroContext ? NULL + : ShClTransferCtxGetTransferByIdRetained(&pClient->Transfers.Ctx, + idTransfer); if ( u32Function != VBOX_SHCL_GUEST_FN_REPLY && !pTransfer) @@ -1055,12 +1044,13 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, } rc = VERR_INVALID_PARAMETER; /* Play safe. */ + bool fDestroyTransfer = false; switch (u32Function) { case VBOX_SHCL_GUEST_FN_REPLY: { - rc = shClSvcTransferMsgHandleReply(pClient, pTransfer, cParms, aParms); + rc = shClSvcTransferMsgHandleReply(pClient, pTransfer, cParms, aParms, &fDestroyTransfer); break; } @@ -1069,8 +1059,8 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (cParms != VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ) break; - ASSERT_GUEST_RETURN(aParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Features */ - ASSERT_GUEST_RETURN(aParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); /* # Entries */ + ASSERT_GUEST_STMT_BREAK(aParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Features */ + ASSERT_GUEST_STMT_BREAK(aParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* # Entries */ SHCLLISTHDR rootListHdr; RT_ZERO(rootListHdr); @@ -1118,17 +1108,17 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (cParms != VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ) break; - ASSERT_GUEST_RETURN(aParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Info flags */ - ASSERT_GUEST_RETURN(aParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); /* Entry index # */ - ASSERT_GUEST_RETURN(aParms[3].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Entry name */ - ASSERT_GUEST_RETURN(aParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Info size */ - ASSERT_GUEST_RETURN(aParms[5].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Info data */ + ASSERT_GUEST_STMT_BREAK(aParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Info flags */ + ASSERT_GUEST_STMT_BREAK(aParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Entry index # */ + ASSERT_GUEST_STMT_BREAK(aParms[3].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* Entry name */ + ASSERT_GUEST_STMT_BREAK(aParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Info size */ + ASSERT_GUEST_STMT_BREAK(aParms[5].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* Info data */ uint32_t fInfo; rc = HGCMSvcGetU32(&aParms[1], &fInfo); AssertRCBreak(rc); - ASSERT_GUEST_RETURN(fInfo & VBOX_SHCL_INFO_F_FSOBJINFO, VERR_WRONG_PARAMETER_TYPE); /* Validate info flags. */ + ASSERT_GUEST_STMT_BREAK(fInfo & VBOX_SHCL_INFO_F_FSOBJINFO, rc = VERR_WRONG_PARAMETER_TYPE); /* Validate info flags. */ uint64_t uIdx; rc = HGCMSvcGetU64(&aParms[2], &uIdx); @@ -1362,11 +1352,11 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (cParms != VBOX_SHCL_CPARMS_OBJ_READ) break; - ASSERT_GUEST_RETURN(aParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); /* Object handle */ - ASSERT_GUEST_RETURN(aParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Bytes to read */ - ASSERT_GUEST_RETURN(aParms[3].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ - ASSERT_GUEST_RETURN(aParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Checksum data size */ - ASSERT_GUEST_RETURN(aParms[5].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Checksum data buffer*/ + ASSERT_GUEST_STMT_BREAK(aParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Object handle */ + ASSERT_GUEST_STMT_BREAK(aParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Bytes to read */ + ASSERT_GUEST_STMT_BREAK(aParms[3].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ + ASSERT_GUEST_STMT_BREAK(aParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Checksum data size */ + ASSERT_GUEST_STMT_BREAK(aParms[5].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* Checksum data buffer*/ SHCLOBJHANDLE hObj; rc = HGCMSvcGetU64(&aParms[1], &hObj); /* Get object handle. */ @@ -1458,8 +1448,17 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, ShClSvcClientUnlock(pClient); - ShClSvcTransferDestroy(pClient, pTransfer); + fDestroyTransfer = true; + } + + if (pTransfer) + { + /* A consuming destroy cannot wait while this handler retains the transfer. */ + ShClTransferRelease(pTransfer); + pTransfer = NULL; } + if (fDestroyTransfer) + ShClSvcTransferDestroyById(pClient, idTransfer); LogFlowFunc(("[Client %RU32] Returning rc=%Rrc\n", pClient->State.uClientID, rc)); return rc; @@ -1584,6 +1583,16 @@ int ShClSvcTransferStart(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) return rc; } +/** + * Returns the current host service file transfer mode. + * + * @returns File transfer mode (VBOX_SHCL_TRANSFER_MODE_F_XXX). + */ +uint32_t shClSvcTransferModeGet(void) +{ + return ASMAtomicReadU32(&g_ShClSvc.fTransferMode); +} + /** * Sets the host service's (file) transfer mode. * @@ -1595,26 +1604,35 @@ static int shClSvcTransferModeSet(uint32_t fMode) if (fMode & ~VBOX_SHCL_TRANSFER_MODE_F_VALID_MASK) return VERR_INVALID_FLAGS; - g_fTransferMode = fMode; + shClSvcLock(); + ASMAtomicWriteU32(&g_ShClSvc.fTransferMode, fMode); - LogRel2(("Shared Clipboard: File transfers are now %s\n", - g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED ? "enabled" : "disabled")); - - /* If file transfers are being disabled, make sure to also reset (destroy) all pending transfers. */ - if (!(g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) + PSHCLCLIENT const pClient = g_ShClSvc.pActiveClient; + if (pClient) { - ClipboardClientMap::const_iterator itClient = g_mapClients.begin(); - while (itClient != g_mapClients.end()) - { - PSHCLCLIENT pClient = itClient->second; - AssertPtr(pClient); + ShClSvcClientLock(pClient); + ASMAtomicWriteU32(&pClient->State.Transfers.uTransferMode, fMode); + ShClSvcClientUnlock(pClient); + } - shClSvcTransferDestroyAll(pClient); + LogRel2(("Shared Clipboard: File transfers are now %s\n", + fMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED ? "enabled" : "disabled")); - ++itClient; - } + RTLISTANCHOR ListDestroy; + RTListInit(&ListDestroy); + + /* If file transfers are being disabled, detach all pending transfers from + * the active client while its weak pointer is stable. */ + if (!(fMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) + { + if (pClient) + shClSvcTransferDetachAll(pClient, &ListDestroy); } LogFlowFuncLeaveRC(VINF_SUCCESS); + shClSvcUnlock(); + + shClSvcTransferDestroyDetachedAll(&ListDestroy); + return VINF_SUCCESS; } diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h index b5fc0e67af59..810a212c1bf9 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.h 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.h 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal header for transfer (list) handling. */ @@ -35,8 +35,13 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE call uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival); int ShClSvcTransferMsgHostHandler(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); +/** Returns a retained transfer in @a ppTransfer; the caller must release it. */ int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer); void ShClSvcTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +void ShClSvcTransferDestroyById(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer); +void ShClSvcTransferDestroyByIdEx(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer, bool fNotifyGuest); +void shClSvcTransferDetachAll(PSHCLCLIENT pClient, PRTLISTANCHOR pList); +void shClSvcTransferDestroyDetachedAll(PRTLISTANCHOR pList); int ShClSvcTransferInit(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); int ShClSvcTransferStart(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); void shClSvcTransferDestroyAll(PSHCLCLIENT pClient); diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp index f7316a694318..0012585d6c7b 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc.cpp 115046 2026-08-17 14:58:27Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host service entry points. */ @@ -274,61 +274,63 @@ SHCLSERVICE g_ShClSvc; /** - * Returns the current Shared Clipboard service mode. + * Acquires the service-global critical section. * - * @returns Current Shared Clipboard service mode. + * Lock acquisition is an internal service-lifetime invariant. Failures are + * reported by a debug assertion rather than propagated to callers. */ -uint32_t ShClSvcGetMode(void) +void shClSvcLock(void) { - return g_uMode; + int const rc = RTCritSectEnter(&g_ShClSvc.CritSect); + AssertRC(rc); } - /** - * Takes the global Shared Clipboard service lock. + * Releases the service-global critical section. * - * @returns \c true if locking was successful, or \c false if not. + * Lock release is an internal service-lifetime invariant. Failures are + * reported by a debug assertion rather than propagated to callers. */ -bool ShClSvcLock(void) +void shClSvcUnlock(void) { - return RT_SUCCESS(RTCritSectEnter(&g_CritSect)); + int const rc = RTCritSectLeave(&g_ShClSvc.CritSect); + AssertRC(rc); } + /** - * Unlocks the formerly locked global Shared Clipboard service lock. + * Returns the current Shared Clipboard service mode. + * + * @returns Current Shared Clipboard service mode. */ -void ShClSvcUnlock(void) +uint32_t ShClSvcGetMode(void) { - int rc2 = RTCritSectLeave(&g_CritSect); - AssertRC(rc2); + return ASMAtomicReadU32(&g_ShClSvc.uMode); } - static int shClSvcInit(VBOXHGCMSVCFNTABLE *pTable) { - int rc = RTCritSectInit(&g_CritSect); + int rc = RTCritSectInit(&g_ShClSvc.CritSect); if (RT_SUCCESS(rc)) { shClSvcHostModeSet(VBOX_SHCL_MODE_OFF); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - g_idNextSession = 1; -#endif + g_ShClSvc.idNextSession = 1; /* Normally we would call ShClBackendInit() here but the service extension * has not been loaded at this early stage of the Shared Clipboard service * bringup so thus we save the HGCM service function table now so that we can * pass it along to ShClBackendInit() later at shClSvcConnect() time. */ - g_pTable = pTable; + g_ShClSvc.pTable = pTable; /* Clean up on failure, because 'shClSvcUnload' will not be called * if 'shClSvcInit' returns an error. */ if (RT_FAILURE(rc)) { - RTCritSectDelete(&g_CritSect); + RTCritSectDelete(&g_ShClSvc.CritSect); } } @@ -341,7 +343,7 @@ static DECLCALLBACK(int) shClSvcUnload(void *) shClSvcBackendDestroy(); - RTCritSectDelete(&g_CritSect); + RTCritSectDelete(&g_ShClSvc.CritSect); return VINF_SUCCESS; } @@ -353,15 +355,11 @@ static DECLCALLBACK(int) shClSvcDisconnect(void *, uint32_t u32ClientID, void *p PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); - /* In order to communicate with guest service, HGCM VRDP clipboard extension - * needs to know its connection client ID. Currently, in shClSvcConnect() we always - * cache ID of the first ever connected client. When client disconnects, - * we need to forget its ID and let shClSvcConnect() pick up the next ID when a new - * connection will be requested by guest service (see #10115). */ - if (g_ExtState.uClientID == u32ClientID) - { - g_ExtState.uClientID = 0; - } + shClSvcLock(); + Assert(g_ShClSvc.pActiveClient == pClient); + if (g_ShClSvc.pActiveClient == pClient) + g_ShClSvc.pActiveClient = NULL; + shClSvcUnlock(); shClSvcBackendDisconnect(pClient); @@ -376,19 +374,29 @@ static DECLCALLBACK(int) shClSvcConnect(void *, uint32_t u32ClientID, void *pvCl PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pvClient); - pClient->pBackend = &g_ShClBackend; + pClient->pBackend = &g_ShClSvc.Backend; int rc = ShClSvcClientInit(pClient, u32ClientID); if (RT_SUCCESS(rc)) { - /* Assign weak pointer to client map. */ - /** @todo r=bird: The g_mapClients is only there for looking up - * g_ExtState.uClientID (unserialized btw), so why not use store the - * pClient value directly in g_ExtState instead of the ID? It cannot - * crash any worse that racing map insertion/removal. */ - g_mapClients[u32ClientID] = pClient; /** @todo Handle OOM / collisions? */ - - rc = shClSvcBackendInit(g_pTable); + shClSvcLock(); + + if (g_ShClSvc.pActiveClient) + { + shClSvcUnlock(); + shClSvcClientDestroy(pClient); + return VERR_RESOURCE_BUSY; + } + + /* Refresh the backend policy cache and publish the client while holding + * the service lock, so that policy changes cannot miss this client. */ + ASMAtomicWriteU32(&pClient->State.uMode, ShClSvcGetMode()); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ASMAtomicWriteU32(&pClient->State.Transfers.uTransferMode, shClSvcTransferModeGet()); +#endif + g_ShClSvc.pActiveClient = pClient; + + rc = shClSvcBackendInit(g_ShClSvc.pTable); if (RT_SUCCESS(rc)) { rc = shClSvcBackendConnect(pClient); @@ -397,18 +405,11 @@ static DECLCALLBACK(int) shClSvcConnect(void *, uint32_t u32ClientID, void *pvCl rc = shClSvcBackendSync(pClient); if (RT_SUCCESS(rc)) { - /* For now we ASSUME that the first client that connects is in charge for - communicating with the service extension. */ - /** @todo This isn't optimal, but only the guest really knows which client is in - * focus on the console. See @bugref{10115} for details. */ - if (g_ExtState.uClientID == 0) - g_ExtState.uClientID = u32ClientID; - /* The sync could return VINF_NO_CHANGE if nothing has changed on the host, but older Guest Additions didn't use RT_SUCCESS to but == VINF_SUCCESS to check for success. So just return VINF_SUCCESS here to not break older Guest Additions. */ - LogFunc(("Successfully connected client %#x%s\n", - u32ClientID, g_ExtState.uClientID == u32ClientID ? " - Use by ExtState too" : "")); + LogFunc(("Successfully connected client %#x\n", u32ClientID)); + shClSvcUnlock(); return VINF_SUCCESS; } LogFunc(("ShClBackendSync failed: %Rrc\n", rc)); @@ -417,6 +418,9 @@ static DECLCALLBACK(int) shClSvcConnect(void *, uint32_t u32ClientID, void *pvCl LogFunc(("ShClBackendConnect failed: %Rrc\n", rc)); } LogFunc(("ShClBackendInit failed: %Rrc\n", rc)); + Assert(g_ShClSvc.pActiveClient == pClient); + g_ShClSvc.pActiveClient = NULL; + shClSvcUnlock(); shClSvcClientDestroy(pClient); } @@ -777,18 +781,16 @@ extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTa } else { - g_pHelpers = pTable->pHelpers; + g_ShClSvc.pHelpers = pTable->pHelpers; pTable->cbClient = sizeof(SHCLCLIENT); /* Map legacy clients to root. */ pTable->idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_ROOT; - /* Limit the number of clients to 128 in each category (should be enough), - but set kernel clients to 1. */ + /* Main and the native clipboard backends support one active client. */ for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) - pTable->acMaxClients[i] = 128; - pTable->acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL] = 1; + pTable->acMaxClients[i] = 1; /* Only 16 pending calls per client (1 should be enough). */ for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp index 426363d9ba89..879224de6568 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardMockHGCM.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardMockHGCM.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard host service test case. */ @@ -500,10 +500,10 @@ static void testTransferStatusContextRouting(void) } PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idTargetTransfer); if (pTransfer) - ShClSvcTransferDestroy(pClient, pTransfer); + ShClSvcTransferDestroyById(pClient, idTargetTransfer); pTransfer = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idAmbientTransfer); if (pTransfer) - ShClSvcTransferDestroy(pClient, pTransfer); + ShClSvcTransferDestroyById(pClient, idAmbientTransfer); } if (fGuestCtxInit) { diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp index 8baa19c77713..23654962289a 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardServiceHost.cpp 114974 2026-08-10 17:47:03Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardServiceHost.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard host service test case. */ @@ -692,7 +692,7 @@ static void testTransferHostCancelError(void) RTTESTI_CHECK(pTransferRequested != NULL); SHCLTRANSFERID const idTransferRequested = ShClTransferGetID(pTransferRequested); testGetTransferStatusMessage(&table, g_Client.State.uSessionID, idTransferRequested, SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS); - ShClSvcTransferDestroy(&g_Client, pTransferRequested); + ShClSvcTransferDestroyById(&g_Client, idTransferRequested); SHCLSESSIONID const idSessionBeforeReset = g_Client.State.uSessionID; rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 0, parms); @@ -721,6 +721,8 @@ static void testTransferHostCancelError(void) RTTESTI_CHECK(idSessionCancel == g_Client.State.uSessionID); RTTESTI_CHECK(uGenerationCancel != 0); RTTESTI_CHECK(uGenerationCancel != NIL_SHCLTRANSFERGEN); + ShClTransferRelease(pTransfer); + pTransfer = NULL; testSetTransferKeyParms(parms, idSessionCancel + 1, idTransferCancel, uGenerationCancel); rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 2, parms); @@ -764,6 +766,8 @@ static void testTransferHostCancelError(void) SHCLSESSIONID const idSessionError = ShClTransferGetSessionId(pTransfer); SHCLTRANSFERID const idTransferError = ShClTransferGetID(pTransfer); SHCLTRANSFERGEN const uGenerationError = ShClTransferGetGeneration(pTransfer); + ShClTransferRelease(pTransfer); + pTransfer = NULL; testSetTransferKeyParms(parms, idSessionError, idTransferError, uGenerationError); rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 2, parms); diff --git a/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp b/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp index 3069bc5b5649..07fd13fbe33a 100644 --- a/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp +++ b/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-utils.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-utils.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host service utility functions. */ @@ -266,8 +266,9 @@ int ShClSvcReadDataFromGuestAsync(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSH /* * Allocate messages, one for each format. */ + uint64_t const fGuestFeatures0 = ShClSvcClientGetGuestFeatures0(pClient); PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, - pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID + fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID ? VBOX_SHCL_HOST_MSG_READ_DATA_CID : VBOX_SHCL_HOST_MSG_READ_DATA, 2); if (pMsg) @@ -286,7 +287,7 @@ int ShClSvcReadDataFromGuestAsync(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSH vrc = VINF_SUCCESS; /* Save the context ID in our legacy cruft if we have to deal with old(er) Guest Additions (< 6.1). */ - if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + if (!(fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) { AssertStmt(pClient->Legacy.cCID < 4096, vrc = VERR_TOO_MUCH_DATA); if (RT_SUCCESS(vrc)) diff --git a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp index 9e6af7ab50ef..0c7324555d11 100644 --- a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp +++ b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendDarwin.cpp 114987 2026-08-11 13:50:56Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendDarwin.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host. */ @@ -196,7 +196,7 @@ static int vboxClipboardChanged(SHCLCONTEXT *pCtx, bool fForce) if ( RT_SUCCESS(vrc) && fChanged) { - uint32_t const uMode = pCtx->pClient->State.uMode; + uint32_t const uMode = ShClSvcClientGetMode(pCtx->pClient); if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) vrc = shClBackendReportFormatsToGuestAndMain(pCtx->pClient, fFormats); diff --git a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp index cf30a123a69f..a2a616371792 100644 --- a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp +++ b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendX11.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendX11.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - X11 backend. */ @@ -179,20 +179,30 @@ static void shClSvcX11TransferPublishedCancel(PSHCLCONTEXT pCtx) if (idTransfer == NIL_SHCLTRANSFERID) return; - PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(&pCtx->pClient->Transfers.Ctx, idTransfer); + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferByIdRetained(&pCtx->pClient->Transfers.Ctx, idTransfer); if ( pTransfer && shClSvcX11TransferKeyMatches(idTransfer, uGeneration, pTransfer)) { SHCLTRANSFERSTATUS const enmStatus = ShClTransferGetStatus(pTransfer); if (enmStatus != SHCLTRANSFERSTATUS_STARTED) - ShClSvcTransferDestroy(pCtx->pClient, pTransfer); + { + ShClTransferRelease(pTransfer); + ShClSvcTransferDestroyById(pCtx->pClient, idTransfer); + } else + { LogRel2(("Shared Clipboard: Keeping superseded X11 transfer %RU16/%RU64 alive while it is in use\n", idTransfer, uGeneration)); + ShClTransferRelease(pTransfer); + } } else + { + if (pTransfer) + ShClTransferRelease(pTransfer); LogRel2(("Shared Clipboard: Published X11 transfer %RU16/%RU64 was already gone or replaced\n", idTransfer, uGeneration)); + } } /** Starts the persistent host-side X11 transfer preparation worker. */ @@ -397,7 +407,7 @@ int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) LogFlowFuncEnter(); - uint32_t uMode = pClient->State.uMode; + uint32_t uMode = ShClSvcClientGetMode(pClient); if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) { /* likely */ } @@ -631,7 +641,7 @@ static DECLCALLBACK(int) shClSvcX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint PSHCLCLIENT pClient = pCtx->pClient; AssertPtr(pClient); - uint32_t uMode = pClient->State.uMode; + uint32_t uMode = ShClSvcClientGetMode(pClient); if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) { /* likely */ } @@ -773,8 +783,17 @@ static int shClSvcX11TransferPrepare(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats, ui RTStrFree(pszUriList); - if (!fPublished && pTransfer) - ShClSvcTransferDestroy(pClient, pTransfer); + if (pTransfer) + { + /* ShClSvcTransferCreate returns a retained transfer. Drop that + * ownership before a consuming destroy can wait for users. */ + ShClTransferRelease(pTransfer); + pTransfer = NULL; + } + + if ( !fPublished + && ShClTransferIdIsValid(idTransfer)) + ShClSvcTransferDestroyById(pClient, idTransfer); if (fPublished) LogRel2(("Shared Clipboard: Advertised cached host X11 URI list for transfer %RU16/%RU64, offer generation %RU64\n", diff --git a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp index 99eae9b6c4ba..f808659fadc4 100644 --- a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp +++ b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendWin.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendWin.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Win32 host. */ @@ -373,6 +373,25 @@ static DECLCALLBACK(void) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERC LogFlowFuncLeaveRC(vrc); } +/** + * @copydoc SHCLTRANSFERCALLBACKS::pfnOnUnregistered + * + * Disables the IDataObject and drops its long-lived transfer reference before + * consuming teardown waits for temporary transfer users. + * + * @thread Service main thread. + */ +static DECLCALLBACK(void) shClSvcWinTransferOnUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, + PSHCLTRANSFERCTX pTransferCtx) +{ + RT_NOREF(pTransferCtx); + + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtr(pTransfer); + + ShClWinTransferUnregister(pTransfer); +} + /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnDestroy * @@ -411,10 +430,15 @@ static DECLCALLBACK(int) shClSvcWinDataObjectTransferBeginCallback(ShClWinDataOb NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); if (RT_SUCCESS(vrc)) { + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + /* Initialize the transfer on the host side. */ vrc = ShClSvcTransferInit(pCtx->pClient, pTransfer); + ShClTransferRelease(pTransfer); + pTransfer = NULL; + if (RT_FAILURE(vrc)) - ShClSvcTransferDestroy(pCtx->pClient, pTransfer); + ShClSvcTransferDestroyById(pCtx->pClient, idTransfer); } LogFlowFuncLeaveRC(vrc); @@ -898,10 +922,11 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) pClient->Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */ pClient->Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); - pClient->Transfers.Callbacks.pfnOnCreated = shClSvcWinTransferOnCreatedCallback; - pClient->Transfers.Callbacks.pfnOnInitialize = shClSvcWinTransferOnInitializeCallback; - pClient->Transfers.Callbacks.pfnOnInitialized = shClSvcWinTransferOnInitializedCallback; - pClient->Transfers.Callbacks.pfnOnDestroy = shClSvcWinTransferOnDestroyCallback; + pClient->Transfers.Callbacks.pfnOnCreated = shClSvcWinTransferOnCreatedCallback; + pClient->Transfers.Callbacks.pfnOnInitialize = shClSvcWinTransferOnInitializeCallback; + pClient->Transfers.Callbacks.pfnOnInitialized = shClSvcWinTransferOnInitializedCallback; + pClient->Transfers.Callbacks.pfnOnUnregistered = shClSvcWinTransferOnUnregisteredCallback; + pClient->Transfers.Callbacks.pfnOnDestroy = shClSvcWinTransferOnDestroyCallback; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ } else @@ -947,6 +972,10 @@ int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) pCtx->hThread = NIL_RTTHREAD; } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* Transfer callback tables retain pCtx as their user argument. */ + shClSvcTransferDestroyAll(pClient); +#endif ShClWinCtxDestroy(&pCtx->Win); if (RT_SUCCESS(vrc)) @@ -990,7 +1019,7 @@ int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, int vrc; - uint32_t uMode = pClient->State.uMode; + uint32_t uMode = ShClSvcClientGetMode(pClient); if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) { /* likely */ } From 5c7415f516eddd92aec2208707223242014c1da9 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 15:20:35 +0000 Subject: [PATCH 143/176] Shared Clipboard: Introduced an opaque Main connection. This now clearly separates Main from the host service through a connection object. bugref:4697 svn:sync-xref-src-repo-rev: r174891 --- include/VBox/GuestHost/SharedClipboard-win.h | 63 +- include/VBox/HostServices/VBoxClipboardExt.h | 182 ++- .../HostServices/VBoxSharedClipboardSvc.h | 185 +-- .../ClipboardDataObjectImpl-win.cpp | 258 +++- .../ClipboardStreamImpl-win.cpp | 69 +- .../SharedClipboard/clipboard-common.cpp | 328 +---- .../SharedClipboard/clipboard-transfers.cpp | 8 +- .../SharedClipboard/clipboard-win.cpp | 140 +- .../SharedClipboard/clipboard-x11.cpp | 74 +- .../HostServices/SharedClipboard/Makefile.kmk | 5 +- .../VBoxSharedClipboardSvc-backend.cpp | 372 ----- .../VBoxSharedClipboardSvc-client.cpp | 357 ++++- .../VBoxSharedClipboardSvc-ext.cpp | 545 +++++++ .../VBoxSharedClipboardSvc-internal.h | 52 +- .../VBoxSharedClipboardSvc-transfers.cpp | 60 +- .../VBoxSharedClipboardSvc-transfers.h | 6 +- .../VBoxSharedClipboardSvc-transport.cpp | 689 +++++++++ .../VBoxSharedClipboardSvc.cpp | 84 +- .../SharedClipboard/testcase/Makefile.kmk | 167 +-- .../testcase/tstClipboardMockHGCM.cpp | 1269 ----------------- .../testcase/tstClipboardServiceHost.cpp | 1161 --------------- .../testcase/tstClipboardServiceImpl.cpp | 203 --- src/VBox/Main/Makefile.kmk | 6 +- src/VBox/Main/include/GuestShClBackend.h | 160 +++ src/VBox/Main/include/GuestShClConn.h | 363 +++++ src/VBox/Main/include/GuestShClPrivate.h | 85 +- src/VBox/Main/src-client/GuestShClBackend.cpp | 152 ++ .../Main/src-client/GuestShClBackendPrivate.h | 73 + src/VBox/Main/src-client/GuestShClConn.cpp | 584 ++++++++ src/VBox/Main/src-client/GuestShClPrivate.cpp | 265 +--- src/VBox/Main/src-client/GuestShClSvcExt.cpp | 212 ++- .../VBoxSharedClipboardSvc-utils.cpp | 413 ------ .../darwin/ClipboardBackendDarwin.cpp | 306 ++-- .../src-client/linux/ClipboardBackendX11.cpp | 509 ++++--- .../src-client/win/ClipboardBackendWin.cpp | 478 ++++--- .../tests/unittests/tdUnitTest1.py | 6 +- 36 files changed, 4657 insertions(+), 5232 deletions(-) delete mode 100644 src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp create mode 100644 src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp create mode 100644 src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp delete mode 100644 src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp delete mode 100644 src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp delete mode 100644 src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp create mode 100644 src/VBox/Main/include/GuestShClBackend.h create mode 100644 src/VBox/Main/include/GuestShClConn.h create mode 100644 src/VBox/Main/src-client/GuestShClBackend.cpp create mode 100644 src/VBox/Main/src-client/GuestShClBackendPrivate.h create mode 100644 src/VBox/Main/src-client/GuestShClConn.cpp delete mode 100644 src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp diff --git a/include/VBox/GuestHost/SharedClipboard-win.h b/include/VBox/GuestHost/SharedClipboard-win.h index b97284a09a28..6029a8827c5b 100644 --- a/include/VBox/GuestHost/SharedClipboard-win.h +++ b/include/VBox/GuestHost/SharedClipboard-win.h @@ -113,6 +113,8 @@ typedef struct _SHCLWINAPIOLD #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** Forward declaration for the Windows data object. */ class ShClWinDataObject; +/** Forward declaration for the Windows stream object. */ +class ShClWinStreamImpl; #endif /** @@ -137,7 +139,8 @@ typedef struct _SHCLWINCTX /** Structure for maintaining the old clipboard API. */ SHCLWINAPIOLD oldAPI; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /** The "in-flight" data object for file transfers. + /** The "in-flight" data object for file transfers. This context owns one + * COM reference to the object while the pointer is non-NULL. * This is the current data object which has been created and sent to the Windows clipboard. * That way Windows knows that a potential file transfer is available, but the actual transfer * hasn't been started yet. @@ -155,6 +158,17 @@ int ShClWinClear(void); int ShClWinCtxInit(PSHCLWINCTX pWinCtx); void ShClWinCtxDestroy(PSHCLWINCTX pWinCtx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Disables callbacks on the current in-flight data object. + * + * New callbacks are disabled permanently. The function also waits for an + * active callback running on another thread to return. + * + * @param pWinCtx Windows clipboard context. + */ +void ShClWinCtxDisableDataObjectCallbacks(PSHCLWINCTX pWinCtx); +#endif int ShClWinCheckAndInitNewAPI(PSHCLWINAPINEW pAPI); bool ShClWinIsNewAPI(PSHCLWINAPINEW pAPI); @@ -231,6 +245,10 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili }; /** Pointer to a Shared Clipboard Windows data object callback table. */ typedef CALLBACKS *PCALLBACKS; + /** Transfer-begin callback type. */ + typedef decltype(CALLBACKS::pfnTransferBegin) PFNTRANSFERBEGIN; + /** Transfer-end callback type. */ + typedef decltype(CALLBACKS::pfnTransferEnd) PFNTRANSFEREND; enum Status { @@ -292,6 +310,7 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili int SetTransfer(PSHCLTRANSFER pTransfer); int SetStatus(Status enmStatus, int rcSts = VINF_SUCCESS); + void DisableCallbacks(void); public: @@ -319,8 +338,10 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili bool lookupFormatEtc(LPFORMATETC pFormatEtc, ULONG *puIndex); void registerFormat(LPFORMATETC pFormatEtc, CLIPFORMAT clipFormat, TYMED tyMed = TYMED_HGLOBAL, LONG lindex = -1, DWORD dwAspect = DVASPECT_CONTENT, DVTARGETDEVICE *pTargetDevice = NULL); - int setTransferLocked(PSHCLTRANSFER pTransfer); + int setTransferLocked(PSHCLTRANSFER pTransfer, ShClWinDataObject **ppObjToRelease = NULL); int setStatusLocked(Status enmStatus, int rc = VINF_SUCCESS); + void registerStreamLocked(ShClWinStreamImpl *pStream); + void invalidateStreams(void); protected: @@ -337,6 +358,8 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili /** Vector containing file system objects with its (cached) objection information. */ typedef std::vector FsObjEntryList; + /** List of streams published by this data object. */ + typedef std::vector StreamList; /** The object's current status. */ Status m_enmStatus; @@ -354,15 +377,25 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili LPSTGMEDIUM m_pStgMedium; /** Pointer to the associated transfer object being handled. */ PSHCLTRANSFER m_pTransfer; - /** Current stream object being used. */ - IStream *m_pStream; + /** Published streams. Each entry owns one COM reference. */ + StreamList m_lstStreams; /** Current object index being handled by the data object. * This is needed to create the next IStream object for e.g. the next upcoming file/dir/++ in the transfer. */ ULONG m_uObjIdx; /** List of (cached) file system objects. */ FsObjEntryList m_lstEntries; + /** Whether the critical section has been initialized. */ + bool m_fCritSectInitialized; + /** Whether new backend callbacks may be started. */ + bool m_fCallbacksEnabled; + /** Number of backend callbacks currently executing. */ + uint32_t m_cCallbacks; + /** Native thread executing the sole admitted backend callback. */ + RTNATIVETHREAD m_hCallbackThread; /** Critical section to serialize access. */ RTCRITSECT m_CritSect; + /** Signalled when no backend callback is executing. */ + RTSEMEVENTMULTI m_EventCallbacksDrained; /** Event being triggered when reading the transfer list been completed. */ RTSEMEVENT m_EventListComplete; /** Event being triggered when the object status has been changed. */ @@ -453,6 +486,7 @@ class ShClWinStreamImpl : public IStream static HRESULT Create(ShClWinDataObject *pParent, PSHCLTRANSFER pTransfer, const Utf8Str &strPath, PSHCLFSOBJINFO pObjInfo, IStream **ppStream); + void Invalidate(void); private: /** Pointer to the parent data object. */ @@ -461,6 +495,10 @@ class ShClWinStreamImpl : public IStream LONG m_lRefCount; /** Pointer to the associated Shared Clipboard transfer. */ PSHCLTRANSFER m_pTransfer; + /** Whether the critical section has been initialized. */ + bool m_fCritSectInitialized; + /** Critical section serializing reads and invalidation. */ + RTCRITSECT m_CritSect; /** The object handle to use. */ SHCLOBJHANDLE m_hObj; /** Object path. */ @@ -485,15 +523,26 @@ class ShClWinTransferCtx virtual ~ShClWinTransferCtx() { } - /** Pointer to data object to use for this transfer. Not owned. - * Can be NULL if not being used. */ - ShClWinDataObject *pDataObj; + /** Critical section protecting @a pDataObj. */ + RTCRITSECT CritSect; + /** Pointer to data object to use for this transfer. Owns one COM + * reference while non-NULL. */ + ShClWinDataObject *pDataObj; }; int ShClWinTransferDropFilesToStringList(DROPFILES *pDropFiles, char **papszList, uint32_t *pcbList); int ShClWinTransferGetRootsFromClipboard(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); int ShClWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); +/** + * Unregisters the data object associated with a Windows transfer. + * + * This disables backend callbacks and releases the data object's long-lived + * transfer reference while keeping the per-transfer context valid for + * temporary users. + * + * @param pTransfer Shared Clipboard transfer to unregister. + */ void ShClWinTransferUnregister(PSHCLTRANSFER pTransfer); void ShClWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); diff --git a/include/VBox/HostServices/VBoxClipboardExt.h b/include/VBox/HostServices/VBoxClipboardExt.h index 12cf361481ce..9bc8125e5594 100644 --- a/include/VBox/HostServices/VBoxClipboardExt.h +++ b/include/VBox/HostServices/VBoxClipboardExt.h @@ -41,16 +41,132 @@ #include #include -#include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include #endif -/** Sets (or unsets) a clipboard extension callback. */ +/** Opaque declaration of an HGCM Shared Clipboard client. */ +typedef struct _SHCLCLIENT SHCLCLIENT, *PSHCLCLIENT; +/** Opaque declaration of a Shared Clipboard client command context. */ +typedef struct _SHCLCLIENTCMDCTX SHCLCLIENTCMDCTX, *PSHCLCLIENTCMDCTX; +/** Opaque declaration of a Shared Clipboard transfer. */ +typedef struct SHCLTRANSFER *PSHCLTRANSFER; +/** Opaque declaration of a Shared Clipboard reply. */ +typedef struct _SHCLREPLY *PSHCLREPLY; + +/** Opaque identity of a client owned exclusively by the HGCM service. */ +typedef struct SHCLCLIENTOPAQUE *SHCLCLIENTHANDLE; +/** Opaque retained guest-data reply owned by the HGCM service. */ +typedef struct SHCLGUESTDATATOKENOPAQUE *SHCLGUESTDATATOKEN; +/** Pointer to an opaque guest-data reply token. */ +typedef SHCLGUESTDATATOKEN *PSHCLGUESTDATATOKEN; + +struct SHCLTRANSPORT; +typedef struct SHCLTRANSPORT SHCLTRANSPORT; +typedef SHCLTRANSPORT *PSHCLTRANSPORT; +typedef SHCLTRANSPORT const *PCSHCLTRANSPORT; + +/** Operations implemented by the HGCM service for an opaque client. */ +typedef struct SHCLSVCOPS +{ + /** Size of this operation table. */ + uint32_t cbStruct; + /** Applies service transfer policy and compatibility rules to a format mask. */ + DECLCALLBACKMEMBER(int, pfnFilterFormats, (SHCLCLIENTHANDLE hClient, bool fHostToGuest, + SHCLFORMATS fFormats, SHCLFORMATS *pfFiltered)); + /** Queues a format announcement for the guest, or returns VINF_NO_CHANGE if policy suppresses it. */ + DECLCALLBACKMEMBER(int, pfnReportFormatsToGuest, (SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + SHCLFORMATS *pfReported)); + /** Queues guest data reads without waiting. */ + DECLCALLBACKMEMBER(int, pfnReadDataFromGuestAsync, (SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + PSHCLEVENT *ppEvent)); + /** Reads and waits for one guest clipboard format. */ + DECLCALLBACKMEMBER(int, pfnReadDataFromGuest, (SHCLCLIENTHANDLE hClient, SHCLFORMAT uFormat, + void **ppvData, uint32_t *pcbData)); + /** Validates and retains a guest reply before it is forwarded. */ + DECLCALLBACKMEMBER(int, pfnGuestDataBegin, (SHCLCLIENTHANDLE hClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken)); + /** Signals and releases a retained guest reply token. */ + DECLCALLBACKMEMBER(int, pfnGuestDataComplete, (SHCLCLIENTHANDLE hClient, SHCLGUESTDATATOKEN hToken, + void const *pvData, uint32_t cbData)); + /** Releases a retained guest reply token without signalling it. */ + DECLCALLBACKMEMBER(void, pfnGuestDataCancel, (SHCLCLIENTHANDLE hClient, SHCLGUESTDATATOKEN hToken)); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Retains a transfer selected by ID. */ + DECLCALLBACKMEMBER(PSHCLTRANSFER, pfnTransferGetByIdRetained, (SHCLCLIENTHANDLE hClient, + SHCLTRANSFERID idTransfer)); + /** Retains a transfer selected by its full generation key. */ + DECLCALLBACKMEMBER(PSHCLTRANSFER, pfnTransferGetByKeyRetained, (SHCLCLIENTHANDLE hClient, + SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration)); + /** Creates and retains a service-owned transfer. */ + DECLCALLBACKMEMBER(int, pfnTransferCreate, (SHCLCLIENTHANDLE hClient, SHCLTRANSFERDIR enmDir, + SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, + SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer)); + /** Initializes a service-owned transfer. */ + DECLCALLBACKMEMBER(int, pfnTransferInit, (SHCLCLIENTHANDLE hClient, PSHCLTRANSFER pTransfer)); + /** Destroys a transfer selected by ID. */ + DECLCALLBACKMEMBER(void, pfnTransferDestroyById, (SHCLCLIENTHANDLE hClient, SHCLTRANSFERID idTransfer)); + /** Destroys all transfers for a disconnecting client. */ + DECLCALLBACKMEMBER(void, pfnTransferDestroyAll, (SHCLCLIENTHANDLE hClient)); + /** Initializes a guest-facing provider without exposing the service client. */ + DECLCALLBACKMEMBER(int, pfnTransferProviderInitGuest, (SHCLCLIENTHANDLE hClient, PSHCLTXPROVIDER pProvider)); +#endif +} SHCLSVCOPS; +/** Pointer to a const service operation table. */ +typedef SHCLSVCOPS const *PCSHCLSVCOPS; + +/** + * Non-owning transport value passed to Main instead of the service client. + * + * The service owns both referenced values from the successful backend-connect + * callback through the matching backend-disconnect callback. Operations are + * synchronous and must not retain either value beyond the call. + */ +struct SHCLTRANSPORT +{ + /** Opaque service-owned client identity. */ + SHCLCLIENTHANDLE hClient; + /** Immutable service operation table. */ + PCSHCLSVCOPS pOps; +}; + +/** + * Checks whether a transport references a service client and operation table. + * + * @returns true if @a pTransport is structurally valid, false otherwise. + * @param pTransport Transport to validate. May be NULL. + */ +DECLINLINE(bool) ShClTransportIsValid(PCSHCLTRANSPORT pTransport) +{ + return pTransport != NULL + && pTransport->hClient != NULL + && pTransport->pOps != NULL + && pTransport->pOps->cbStruct == sizeof(*pTransport->pOps); +} + +/** + * Checks whether two transport values identify the same service endpoint. + * + * @returns true if both transports are valid and identify the same client and + * operation table, false otherwise. + * @param pLeft First transport to compare. + * @param pRight Second transport to compare. + */ +DECLINLINE(bool) ShClTransportIsEqual(PCSHCLTRANSPORT pLeft, PCSHCLTRANSPORT pRight) +{ + return ShClTransportIsValid(pLeft) + && ShClTransportIsValid(pRight) + && pLeft->hClient == pRight->hClient + && pLeft->pOps == pRight->pOps; +} + +/** Sets a read / write callback. */ #define VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK (0) /** The guest reports clipboard formats to the extension. */ #define VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST (1) -/** The clipboard service wants to report formats to the guest. */ +/** Reports remote clipboard formats to the guest. */ #define VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST (2) /** The clipboard service requests clipboard data from the extension. */ #define VBOX_CLIPBOARD_EXT_FN_DATA_READ (3) @@ -68,17 +184,19 @@ #define VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT (9) /** The clipboard service syncs with the backend. */ #define VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC (10) -/** The clipboard service requests clipboard data from the extension. */ +/** Requests guest clipboard data for VRDE. */ #define VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE (11) /** The clipboard service initiates the transfer of a file from the guest. */ #define VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER (12) +/** Reserved. */ +#define VBOX_CLIPBOARD_EXT_FN_RESERVED_13 (13) +/** The clipboard service requests the native transfer callback table. */ +#define VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS (14) typedef DECLCALLBACKTYPE(int, FNSHCLEXTCALLBACK,(uint32_t u32Function, uint32_t u32Format, void *pvData, uint32_t cbData)); typedef FNSHCLEXTCALLBACK *PFNSHCLEXTCALLBACK; -/** - * Structure for holding Shared Clipboard service extension parameters. - */ +/** Structure for holding Shared Clipboard service extension parameters. */ typedef struct _SHCLEXTPARMS { union @@ -98,30 +216,24 @@ typedef struct _SHCLEXTPARMS uint32_t cbData; uint32_t cbActual; PSHCLCLIENT pClient; - PSHCLBACKEND pBackend; - VBOXHGCMSVCFNTABLE *pTable; + void *pvReserved0; + void *pvReserved1; PSHCLCLIENTCMDCTX pCmdCtx; - /** Legacy flag indicating that the backend was to avoid host clipboard access. - * @deprecated Ignored; retained for binary compatibility. Must be false. */ bool fHeadless; } ReadWriteData; /** Sets a read / write callback. */ struct { - PFNSHCLEXTCALLBACK - pfnCallback; + PFNSHCLEXTCALLBACK pfnCallback; } SetCallback; /** Reports a clipboard error. */ struct { - /** Clipboard ID. Optional and can be NULL. */ char *pszId; - /** User friendly error message. */ char *pszMsg; - /** IPRT-style error code. */ int rc; } Error; - /** Sends / receives clipboard files */ + /** Sends / receives clipboard files. */ struct { PSHCLCLIENT pClient; @@ -129,9 +241,45 @@ typedef struct _SHCLEXTPARMS SHCLSOURCE enmShClSource; PSHCLREPLY pReply; } FileTransferData; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Queries Main for callbacks to attach to a new transfer. */ + struct + { + PSHCLCLIENT pClient; + PSHCLTRANSFERCALLBACKS pCallbacks; + } TransferCallbacks; +#endif } u; } SHCLEXTPARMS; /** Pointer to Shared Clipboard service extension parameters. */ typedef SHCLEXTPARMS *PSHCLEXTPARMS; +/** Pointer to const Shared Clipboard service extension parameters. */ +typedef SHCLEXTPARMS const *PCSHCLEXTPARMS; + +/** + * Stores an opaque service transport in the reserved extension parameter slots. + * + * @param pParms Extension parameter block to update. + * @param pTransport Transport value to store. + */ +DECLINLINE(void) ShClSvcExtSetTransport(PSHCLEXTPARMS pParms, PCSHCLTRANSPORT pTransport) +{ + pParms->u.ReadWriteData.pvReserved0 = (void *)pTransport->hClient; + pParms->u.ReadWriteData.pvReserved1 = (void *)pTransport->pOps; +} + +/** + * Gets the opaque service transport stored in the reserved parameter slots. + * + * @returns Stored transport value. Use ShClTransportIsValid() before use. + * @param pParms Extension parameter block containing the transport. + */ +DECLINLINE(SHCLTRANSPORT) ShClSvcExtGetTransport(PCSHCLEXTPARMS pParms) +{ + SHCLTRANSPORT Transport; + Transport.hClient = (SHCLCLIENTHANDLE)pParms->u.ReadWriteData.pvReserved0; + Transport.pOps = (PCSHCLSVCOPS)pParms->u.ReadWriteData.pvReserved1; + return Transport; +} #endif /* !VBOX_INCLUDED_HostServices_VBoxClipboardExt_h */ diff --git a/include/VBox/HostServices/VBoxSharedClipboardSvc.h b/include/VBox/HostServices/VBoxSharedClipboardSvc.h index 448905882484..91b4bdbac8a2 100644 --- a/include/VBox/HostServices/VBoxSharedClipboardSvc.h +++ b/include/VBox/HostServices/VBoxSharedClipboardSvc.h @@ -1,7 +1,6 @@ -/* $Id: VBoxSharedClipboardSvc.h 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file - * Shared Clipboard Service - header file for shared clipboard data transfer - * interfaces and platform-dependent backend functionality. + * Shared Clipboard Service - HGCM protocol state and data transfer interfaces. */ /* @@ -148,10 +147,6 @@ typedef struct SHCLCLIENTLEGACYSTATE */ typedef struct SHCLCLIENTSTATE { - /** Backend-dependent opaque context structure. - * This contains data only known to a certain backend implementation. - * Optional and can be NULL. */ - SHCLCONTEXT *pCtx; /** The client's HGCM ID. Not related to the session ID below! */ uint32_t uClientID; /** The client's session ID. */ @@ -188,25 +183,16 @@ typedef struct _SHCLIENTTRANSFERS { /** Transfer context. */ SHCLTRANSFERCTX Ctx; - /** Backends-specific transfers callbacks to use. */ - SHCLTRANSFERCALLBACKS Callbacks; - /** Backends-specific transfers provider to use. */ - SHCLTXPROVIDER Provider; } SHCLIENTTRANSFERS; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ -/** Prototypes for the Shared Clipboard backend. */ -struct SHCLBACKEND; -typedef SHCLBACKEND *PSHCLBACKEND; - /** * Structure for keeping data per (connected) HGCM client. */ typedef struct _SHCLCLIENT { - /** Pointer to associated backend, if any. - * Might be NULL if not being used. */ - PSHCLBACKEND pBackend; + /** HGCM service helpers used to complete deferred guest calls. */ + PVBOXHGCMSVCHELPERS pHelpers; /** General client state data. */ SHCLCLIENTSTATE State; /** The critical section protecting the queue, event source and whatnot. */ @@ -318,174 +304,13 @@ bool shClSvcClientTransfersAreAllowed(PSHCLCLIENT pClient); #endif /** @} */ -/** @name Service functions, accessible by the backends. +/** @name Service functions shared with Main's clipboard backends. * Locking is between the (host) service thread and the platform-dependent (window) thread. * @{ */ -int ShClSvcReadDataFromGuestAsync(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENT *ppEvent); -int ShClSvcReadDataFromGuest(PSHCLCLIENT pClient, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb); -int ShClSvcGuestDataRetainValidatedEvent(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, - SHCLFORMAT uFormat, PSHCLEVENT *ppEvent); -int ShClSvcGuestDataSignalEvent(PSHCLEVENT pEvent, SHCLEVENTID idEvent, void *pvData, uint32_t cbData); -int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); -int ShClSvcReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats); -PSHCLBACKEND ShClSvcGetBackend(void); uint32_t ShClSvcGetMode(void); /** @} */ -/** @name Platform-dependent implementations for the Shared Clipboard host service ("backends"), - * called *only* by the host service. - * @{ - */ -/** - * Structure for keeping Shared Clipboard backend instance data. - */ -typedef struct SHCLBACKEND -{ - /** Callback table to use. - * Some callbacks might be optional and therefore NULL -- see the table for more details. */ - SHCLCALLBACKS Callbacks; - - /** Pointer to the helper functions in the VBOXHGCMSVCFNTABLE for use by the backend. */ - PVBOXHGCMSVCHELPERS pHelpers; -} SHCLBACKEND; -/** Pointer to a Shared Clipboard backend. */ -typedef SHCLBACKEND *PSHCLBACKEND; - -/** - * Called on initialization. - * - * @param pBackend Shared Clipboard backend to initialize. - * @param pTable The HGCM service call and parameter table. Mainly for - * adjusting the limits. - */ -int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable); - -/** - * Called on destruction. - * - * @param pBackend Shared Clipboard backend to destroy. - */ -void ShClBackendDestroy(PSHCLBACKEND pBackend); - -/** - * Called when a new HGCM client connects. - * - * @param pBackend Shared Clipboard backend to set callbacks for. - * @param pCallbacks Backend callbacks to use. - * When NULL is specified, the backend's default callbacks are being used. - */ -void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks); - -/** - * Called when a new HGCM client connects. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to connect to. - * @param pClient Shared Clipboard client context. - */ -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); - -/** - * Called when a HGCM client disconnects. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to disconnect from. - * @param pClient Shared Clipboard client context. - */ -int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); - -/** - * Called when the guest reports available clipboard formats to the host OS. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to announce formats to. - * @param pClient Shared Clipboard client context. - * @param fFormats The announced formats from the guest, - * VBOX_SHCL_FMT_XXX. - */ -int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats); - -/** - * Called when the host reports available clipboard formats to the guest. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to announce formats to. - * @param pClient Shared Clipboard client context. - * @param fFormats The announced formats from the host, - * VBOX_SHCL_FMT_XXX. - */ -int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats); - -/** - * Called when the guest wants to read host clipboard data. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to read data from. - * @param pClient Shared Clipboard client context. - * @param pCmdCtx Shared Clipboard command context. - * @param uFormat Clipboard format to read. - * @param pvData Where to return the read clipboard data. - * @param cbData Size (in bytes) of buffer where to return the clipboard data. - * @param pcbActual Where to return the amount of bytes read. - * - * @todo Document: Can return VINF_HGCM_ASYNC_EXECUTE to defer returning read - * data - */ -int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, - void *pvData, uint32_t cbData, uint32_t *pcbActual); - -/** - * Called when the guest writes clipboard data to the host. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to write data to. - * @param pClient Shared Clipboard client context. - * @param pCmdCtx Shared Clipboard command context. - * @param uFormat Clipboard format to write. - * @param pvData Clipboard data to write. - * @param cbData Size (in bytes) of buffer clipboard data to write. - */ -int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); - -/** - * Called when synchronization of the clipboard contents of the host clipboard with the guest is needed. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to synchronize. - * @param pClient Shared Clipboard client context. - */ -int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); -/** @} */ - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** @name Host implementations for Shared Clipboard transfers. - * @{ - */ -/** - * Called before a transfer gets destroyed. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to use. - * @param pClient Shared Clipboard client context. - * @param pTransfer Shared Clipboard transfer to destroy. - */ -int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); -/** - * Called after a transfer status got processed. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to use. - * @param pClient Shared Clipboard client context. - * @param pTransfer Shared Clipboard transfer to process status for. - * @param enmSource Transfer source which issues the reply. - * @param enmStatus Transfer status. - * @param rcStatus Status code (IPRT-style). Depends on \a enmStatus set. - */ -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus); -/** @} */ -#endif - #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** @name Shared Clipboard transfer interface implementations for guest -> host transfers. * @{ diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp index ef054667284a..a5035cd60ffd 100644 --- a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardDataObjectImpl-win.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardDataObjectImpl-win.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * ClipboardDataObjectImpl-win.cpp - Shared Clipboard IDataObject implementation. */ @@ -66,8 +66,12 @@ ShClWinDataObject::ShClWinDataObject(void) , m_pFormatEtc(NULL) , m_pStgMedium(NULL) , m_pTransfer(NULL) - , m_pStream(NULL) , m_uObjIdx(0) + , m_fCritSectInitialized(false) + , m_fCallbacksEnabled(false) + , m_cCallbacks(0) + , m_hCallbackThread(NIL_RTNATIVETHREAD) + , m_EventCallbacksDrained(NIL_RTSEMEVENTMULTI) , m_EventListComplete(NIL_RTSEMEVENT) , m_EventStatusChanged(NIL_RTSEMEVENT) , m_cfFileDescriptorA(0) @@ -177,16 +181,23 @@ int ShClWinDataObject::Init(PSHCLCONTEXT pCtx, ShClWinDataObject::PCALLBACKS pCa if (RT_SUCCESS(rc)) { - m_cFormats = cAllFormats; - m_enmStatus = Initialized; - rc = RTCritSectInit(&m_CritSect); if (RT_SUCCESS(rc)) { - rc = RTSemEventCreate(&m_EventListComplete); + m_fCritSectInitialized = true; + rc = RTSemEventMultiCreate(&m_EventCallbacksDrained); + if (RT_SUCCESS(rc)) + rc = RTSemEventCreate(&m_EventListComplete); if (RT_SUCCESS(rc)) rc = RTSemEventCreate(&m_EventStatusChanged); } + + if (RT_SUCCESS(rc)) + { + m_fCallbacksEnabled = true; + m_cFormats = cAllFormats; + m_enmStatus = Initialized; + } } LogFlowFunc(("cAllFormats=%RU32, rc=%Rrc\n", cAllFormats, rc)); @@ -200,6 +211,7 @@ void ShClWinDataObject::uninitInternal(void) { LogFlowFuncEnter(); + Assert(m_fCritSectInitialized); lock(); if (m_enmStatus != Uninitialized) @@ -219,9 +231,13 @@ void ShClWinDataObject::uninitInternal(void) } /* Make sure to release the transfer in any state. */ - setTransferLocked(NULL); + ShClWinDataObject *pObjToRelease = NULL; + setTransferLocked(NULL, &pObjToRelease); unlock(); + + if (pObjToRelease) + pObjToRelease->Release(); } /** @@ -231,7 +247,45 @@ void ShClWinDataObject::Uninit(void) { LogFlowFuncEnter(); - uninitInternal(); + if (m_fCritSectInitialized) + { + DisableCallbacks(); + uninitInternal(); + invalidateStreams(); + } + + /* No callback may retain or use its owner after the object is invalidated. */ + m_CallbackCtx.pvUser = NULL; + m_CallbackCtx.pThis = this; +} + +/** + * Permanently disables new backend callbacks and waits for an active callback + * running on another thread to return. + */ +void ShClWinDataObject::DisableCallbacks(void) +{ + if (!m_fCritSectInitialized) + return; + + for (;;) + { + lock(); + m_fCallbacksEnabled = false; + m_Callbacks.pfnTransferBegin = NULL; + m_Callbacks.pfnTransferEnd = NULL; + bool const fWaitCallbacks = m_cCallbacks != 0; + bool const fCallbackThread = fWaitCallbacks + && m_hCallbackThread == RTThreadNativeSelf(); + unlock(); + + if ( !fWaitCallbacks + || fCallbackThread) + break; + + int rc = RTSemEventMultiWait(m_EventCallbacksDrained, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(rc, ("Draining Windows clipboard data-object callbacks failed with %Rrc\n", rc)); + } } /** @@ -241,31 +295,41 @@ void ShClWinDataObject::Destroy(void) { LogFlowFuncEnter(); - if (m_enmStatus == Uninitialized) /* Crit sect not available anymore. */ - return; + Uninit(); + + if (m_fCritSectInitialized) + { + int rc = RTCritSectDelete(&m_CritSect); + AssertRC(rc); + m_fCritSectInitialized = false; + } - uninitInternal(); + m_CallbackCtx.pvUser = NULL; + m_CallbackCtx.pThis = this; + m_cFormats = 0; + m_enmStatus = Uninitialized; - int rc = RTCritSectDelete(&m_CritSect); - AssertRC(rc); + if (m_EventCallbacksDrained != NIL_RTSEMEVENTMULTI) + { + int rc = RTSemEventMultiDestroy(m_EventCallbacksDrained); + AssertRC(rc); + m_EventCallbacksDrained = NIL_RTSEMEVENTMULTI; + } if (m_EventListComplete != NIL_RTSEMEVENT) { - rc = RTSemEventDestroy(m_EventListComplete); + int rc = RTSemEventDestroy(m_EventListComplete); AssertRC(rc); m_EventListComplete = NIL_RTSEMEVENT; } if (m_EventStatusChanged != NIL_RTSEMEVENT) { - rc = RTSemEventDestroy(m_EventStatusChanged); + int rc = RTSemEventDestroy(m_EventStatusChanged); AssertRC(rc); m_EventStatusChanged = NIL_RTSEMEVENT; } - if (m_pStream) - m_pStream = NULL; - if (m_pFormatEtc) { delete[] m_pFormatEtc; @@ -576,7 +640,12 @@ DECLCALLBACK(int) ShClWinDataObject::readThread(PSHCLTRANSFER pTransfer, void *p if (RT_FAILURE(rc)) break; - switch (pThis->m_enmStatus) + pThis->lock(); + Status const enmStatus = pThis->m_enmStatus; + int const rcStatus = pThis->m_rcStatus; + pThis->unlock(); + + switch (enmStatus) { case Uninitialized: /* Can happen due to transfer erros. */ LogRel2(("Shared Clipboard: Data object was uninitialized\n")); @@ -600,8 +669,8 @@ DECLCALLBACK(int) ShClWinDataObject::readThread(PSHCLTRANSFER pTransfer, void *p break; case Error: - LogRel(("Shared Clipboard: Data object: Transfer error %Rrc occurred\n", pThis->m_rcStatus)); - rc = ShClTransferError(pTransfer, pThis->m_rcStatus); + LogRel(("Shared Clipboard: Data object: Transfer error %Rrc occurred\n", rcStatus)); + rc = ShClTransferError(pTransfer, rcStatus); break; default: @@ -609,11 +678,33 @@ DECLCALLBACK(int) ShClWinDataObject::readThread(PSHCLTRANSFER pTransfer, void *p break; } - if (pThis->m_Callbacks.pfnTransferEnd) + pThis->lock(); + PFNTRANSFEREND const pfnTransferEnd = pThis->m_fCallbacksEnabled + ? pThis->m_Callbacks.pfnTransferEnd : NULL; + CALLBACKCTX CallbackCtx = pThis->m_CallbackCtx; + if (pfnTransferEnd) { - int rc2 = pThis->m_Callbacks.pfnTransferEnd(&pThis->m_CallbackCtx, pTransfer, pThis->m_rcStatus); + Assert(pThis->m_cCallbacks == 0); + pThis->m_cCallbacks = 1; + pThis->m_hCallbackThread = RTThreadNativeSelf(); + int rc2 = RTSemEventMultiReset(pThis->m_EventCallbacksDrained); + AssertFatalMsgRC(rc2, ("Resetting the Windows clipboard callback-drain event failed with %Rrc\n", rc2)); + } + pThis->unlock(); + + if (pfnTransferEnd) + { + int rc2 = pfnTransferEnd(&CallbackCtx, pTransfer, rcStatus); if (RT_SUCCESS(rc)) rc = rc2; + + pThis->lock(); + Assert(pThis->m_cCallbacks == 1); + pThis->m_cCallbacks = 0; + pThis->m_hCallbackThread = NIL_RTNATIVETHREAD; + rc2 = RTSemEventMultiSignal(pThis->m_EventCallbacksDrained); + AssertFatalMsgRC(rc2, ("Signalling the Windows clipboard callback-drain event failed with %Rrc\n", rc2)); + pThis->unlock(); } break; @@ -626,6 +717,7 @@ DECLCALLBACK(int) ShClWinDataObject::readThread(PSHCLTRANSFER pTransfer, void *p LogRel(("Shared Clipboard: Transfer read thread failed with %Rrc\n", rc)); LogFlowFuncLeaveRC(rc); + pThis->Release(); return rc; } @@ -926,16 +1018,37 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) { LogRel2(("Shared Clipboard: Requesting data for IDataObject ...\n")); - if (!m_Callbacks.pfnTransferBegin) + if ( !m_fCallbacksEnabled + || !m_Callbacks.pfnTransferBegin) { LogRelMax2(16, ("Shared Clipboard: Cannot start IDataObject transfer because no transfer-begin callback is installed\n")); return VERR_INVALID_POINTER; } + if (m_cCallbacks != 0) + return VERR_TRY_AGAIN; + + PFNTRANSFERBEGIN const pfnTransferBegin = m_Callbacks.pfnTransferBegin; + CALLBACKCTX CallbackCtx = m_CallbackCtx; + m_cCallbacks = 1; + m_hCallbackThread = RTThreadNativeSelf(); + rc = RTSemEventMultiReset(m_EventCallbacksDrained); + AssertFatalMsgRC(rc, ("Resetting the Windows clipboard callback-drain event failed with %Rrc\n", rc)); /* Leave lock while requesting + waiting. */ unlock(); - rc = m_Callbacks.pfnTransferBegin(&m_CallbackCtx); + rc = pfnTransferBegin(&CallbackCtx); + + lock(); + Assert(m_cCallbacks > 0); + if (--m_cCallbacks == 0) + { + m_hCallbackThread = NIL_RTNATIVETHREAD; + int rc2 = RTSemEventMultiSignal(m_EventCallbacksDrained); + AssertFatalMsgRC(rc2, ("Signalling the Windows clipboard callback-drain event failed with %Rrc\n", rc2)); + } + unlock(); + if (RT_SUCCESS(rc)) { LogRel2(("Shared Clipboard: Waiting for IDataObject started status ...\n")); @@ -989,11 +1102,17 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) rc = ShClTransferStart(pTransfer); if (RT_SUCCESS(rc)) { + /* The transfer worker receives a raw user pointer, so retain the + * object until readThread() returns. */ + AddRef(); rc = ShClTransferRun(pTransfer, &ShClWinDataObject::readThread, this /* pvUser */); if (RT_SUCCESS(rc)) fNeedListWait = true; else + { + Release(); LogRelMax2(16, ("Shared Clipboard: Starting IDataObject transfer read thread failed with %Rrc\n", rc)); + } } else LogRelMax2(16, ("Shared Clipboard: Starting IDataObject transfer failed with %Rrc\n", rc)); @@ -1115,14 +1234,17 @@ STDMETHODIMP ShClWinDataObject::GetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMed LogRel2(("Shared Clipboard: Receiving object '%s' ...\n", fsObjEntry.pszPath)); /* Hand-in the provider so that our IStream implementation can continue working with it. */ + IStream *pStream = NULL; hr = ShClWinStreamImpl::Create(this /* pParent */, m_pTransfer, - fsObjEntry.pszPath /* File name */, &fsObjEntry.objInfo /* PSHCLFSOBJINFO */, - &m_pStream); + fsObjEntry.pszPath /* File name */, &fsObjEntry.objInfo /* PSHCLFSOBJINFO */, + &pStream); if (SUCCEEDED(hr)) { /* Hand over the stream to the caller. */ pMedium->tymed = TYMED_ISTREAM; - pMedium->pstm = m_pStream; + pMedium->pstm = pStream; + + registerStreamLocked((ShClWinStreamImpl *)pStream); } } } @@ -1317,9 +1439,10 @@ STDMETHODIMP ShClWinDataObject::StartOperation(IBindCtx *pbcReserved) * @param pTransfer Transfer to assign. * When set to NULL, the transfer will be released from the object. */ -int ShClWinDataObject::setTransferLocked(PSHCLTRANSFER pTransfer) +int ShClWinDataObject::setTransferLocked(PSHCLTRANSFER pTransfer, ShClWinDataObject **ppObjToRelease /* = NULL */) { AssertReturn(RTCritSectIsOwned(&m_CritSect), VERR_WRONG_ORDER); + AssertReturn(!ppObjToRelease || !*ppObjToRelease, VERR_INVALID_PARAMETER); LogFunc(("pTransfer=%p\n", pTransfer)); @@ -1331,14 +1454,26 @@ int ShClWinDataObject::setTransferLocked(PSHCLTRANSFER pTransfer) if (m_enmStatus == Initialized) { - m_pTransfer = pTransfer; - ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)pTransfer->pvUser; AssertPtr(pWinURITransferCtx); - pWinURITransferCtx->pDataObj = this; /* Save a backref to this object. */ + rc = RTCritSectEnter(&pWinURITransferCtx->CritSect); + if (RT_SUCCESS(rc)) + { + Assert(pWinURITransferCtx->pDataObj == NULL); + if (!pWinURITransferCtx->pDataObj) + { + AddRef(); + pWinURITransferCtx->pDataObj = this; + m_pTransfer = pTransfer; + ShClTransferAcquire(pTransfer); + } + else + rc = VERR_WRONG_ORDER; - ShClTransferAcquire(pTransfer); + int rc2 = RTCritSectLeave(&pWinURITransferCtx->CritSect); + AssertRC(rc2); + } } else AssertFailedStmt(rc = VERR_WRONG_ORDER); @@ -1350,7 +1485,22 @@ int ShClWinDataObject::setTransferLocked(PSHCLTRANSFER pTransfer) ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)m_pTransfer->pvUser; AssertPtr(pWinURITransferCtx); - pWinURITransferCtx->pDataObj = NULL; /* Release backref to this object. */ + int rc2 = RTCritSectEnter(&pWinURITransferCtx->CritSect); + AssertFatalMsgRC(rc2, ("Taking the Windows transfer-context lock while detaching failed with %Rrc\n", rc2)); + + if (pWinURITransferCtx->pDataObj == this) + { + pWinURITransferCtx->pDataObj = NULL; + if (ppObjToRelease) + *ppObjToRelease = this; + else + AssertFailed(); + } + else + Assert(pWinURITransferCtx->pDataObj == NULL); + + rc2 = RTCritSectLeave(&pWinURITransferCtx->CritSect); + AssertFatalMsgRC(rc2, ("Releasing the Windows transfer-context lock while detaching failed with %Rrc\n", rc2)); ShClTransferRelease(m_pTransfer); m_pTransfer = NULL; @@ -1375,13 +1525,51 @@ int ShClWinDataObject::SetTransfer(PSHCLTRANSFER pTransfer) { lock(); - int rc = setTransferLocked(pTransfer); + ShClWinDataObject *pObjToRelease = NULL; + int rc = setTransferLocked(pTransfer, &pObjToRelease); unlock(); + if (pObjToRelease) + pObjToRelease->Release(); + return rc; } +/** + * Registers a stream while the data-object lock is held. + * + * @param pStream Stream to register. + */ +void ShClWinDataObject::registerStreamLocked(ShClWinStreamImpl *pStream) +{ + Assert(RTCritSectIsOwned(&m_CritSect)); + AssertPtr(pStream); + + m_lstStreams.push_back(pStream); /** @todo Can this throw? */ + pStream->AddRef(); +} + +/** + * Invalidates all streams and drops the references owned by this data object. + */ +void ShClWinDataObject::invalidateStreams(void) +{ + StreamList lstStreams; + + lock(); + lstStreams.swap(m_lstStreams); + unlock(); + + StreamList::const_iterator it = lstStreams.cbegin(); + while (it != lstStreams.cend()) + { + (*it)->Invalidate(); + (*it)->Release(); + ++it; + } +} + /** * Sets a new status to the data object and signals its waiter. * diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp index aaa50270cbcc..3960603f5221 100644 --- a/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardStreamImpl-win.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: ClipboardStreamImpl-win.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * ClipboardStreamImpl-win.cpp - Shared Clipboard IStream object implementation (guest and host side). */ @@ -64,14 +64,23 @@ ShClWinStreamImpl::ShClWinStreamImpl(ShClWinDataObject *pParent, PSHCLTRANSFER p : m_pParent(pParent) , m_lRefCount(1) /* Our IDataObjct *always* holds the last reference to this object; needed for the callbacks. */ , m_pTransfer(pTransfer) + , m_fCritSectInitialized(false) , m_hObj(NIL_SHCLOBJHANDLE) , m_strPath(strPath) , m_objInfo(*pObjInfo) , m_cbProcessed(0) , m_fIsComplete(false) { + AssertPtr(m_pParent); AssertPtr(m_pTransfer); + int rc = RTCritSectInit(&m_CritSect); + AssertFatalMsgRC(rc, ("Initializing the Windows clipboard stream lock failed with %Rrc\n", rc)); + m_fCritSectInitialized = true; + + m_pParent->AddRef(); + ShClTransferAcquire(m_pTransfer); + LogFunc(("m_strPath=%s\n", m_strPath.c_str())); #ifdef VBOX_SHARED_CLIPBOARD_DEBUG_OBJECT_COUNTS @@ -84,6 +93,14 @@ ShClWinStreamImpl::~ShClWinStreamImpl(void) { LogFlowThisFuncEnter(); + Invalidate(); + if (m_fCritSectInitialized) + { + int rc = RTCritSectDelete(&m_CritSect); + AssertRC(rc); + m_fCritSectInitialized = false; + } + #ifdef VBOX_SHARED_CLIPBOARD_DEBUG_OBJECT_COUNTS g_cDbgStreamObj--; LogFlowFunc(("g_cDataObj=%d, g_cStreamObj=%d, g_cEnumFmtObj=%d\n", g_cDbgDataObj, g_cDbgStreamObj, g_cDbgEnumFmtObj)); @@ -185,16 +202,24 @@ STDMETHODIMP ShClWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG * { LogFlowThisFunc(("Enter: m_cbProcessed=%RU64\n", m_cbProcessed)); - /** @todo Is there any locking required so that parallel reads aren't possible? */ - if (!pvBuffer) return STG_E_INVALIDPOINTER; + int rcLock = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(rcLock, ("Taking the Windows clipboard stream lock failed with %Rrc\n", rcLock)); + + if (!m_pTransfer) + { + RTCritSectLeave(&m_CritSect); + return STG_E_REVERTED; + } + if ( nBytesToRead == 0 || m_fIsComplete) { if (nBytesRead) *nBytesRead = 0; + RTCritSectLeave(&m_CritSect); return S_OK; } @@ -246,6 +271,7 @@ STDMETHODIMP ShClWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG * if (m_fIsComplete) { rc = ShClTransferObjClose(m_pTransfer, m_hObj); + m_hObj = NIL_SHCLOBJHANDLE; if (m_pParent) m_pParent->SetStatus(ShClWinDataObject::Completed); @@ -264,6 +290,9 @@ STDMETHODIMP ShClWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG * if (nBytesRead) *nBytesRead = (ULONG)cbRead; + int rc2 = RTCritSectLeave(&m_CritSect); + AssertRC(rc2); + if (nBytesToRead != cbRead) return S_FALSE; @@ -389,6 +418,7 @@ HRESULT ShClWinStreamImpl::Create(ShClWinDataObject *pParent, PSHCLTRANSFER pTra const Utf8Str &strPath, PSHCLFSOBJINFO pObjInfo, IStream **ppStream) { + AssertPtrReturn(pParent, E_POINTER); AssertPtrReturn(pTransfer, E_POINTER); ShClWinStreamImpl *pStream = new ShClWinStreamImpl(pParent, pTransfer, strPath, pObjInfo); @@ -401,3 +431,36 @@ HRESULT ShClWinStreamImpl::Create(ShClWinDataObject *pParent, PSHCLTRANSFER pTra return E_FAIL; } +/** + * Invalidates this stream and drops its data-object and transfer references. + */ +void ShClWinStreamImpl::Invalidate(void) +{ + if (!m_fCritSectInitialized) + return; + + int rc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(rc, ("Taking the Windows clipboard stream lock during invalidation failed with %Rrc\n", rc)); + + PSHCLTRANSFER pTransfer = m_pTransfer; + ShClWinDataObject *pParent = m_pParent; + SHCLOBJHANDLE const hObj = m_hObj; + m_pTransfer = NULL; + m_pParent = NULL; + m_hObj = NIL_SHCLOBJHANDLE; + + if ( pTransfer + && hObj != NIL_SHCLOBJHANDLE) + { + int rc2 = ShClTransferObjClose(pTransfer, hObj); + AssertRC(rc2); + } + + rc = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(rc, ("Releasing the Windows clipboard stream lock during invalidation failed with %Rrc\n", rc)); + + if (pTransfer) + ShClTransferRelease(pTransfer); + if (pParent) + pParent->Release(); +} diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp index 527966a3ac77..5ea9e059c727 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-common.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-common.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common helper objects. */ @@ -43,9 +43,6 @@ #include #include #include -#ifdef VBOX_WITH_SHARED_CLIPBOARD_HOST -# include -#endif /********************************************************************************************************************************* @@ -1179,326 +1176,3 @@ VBGH_DECL(int) ShClCacheTransferAll(PSHCLCACHE pCache, PSHCLCACHE pOtherCache) return VINF_SUCCESS; } - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_HOST -/** - * Handles clipboard formats. - * - * This suppresses file-transfer announcements until transfers are enabled and - * supported by the guest, and keeps host-to-guest transfer offers separate - * from ordinary clipboard formats. Older Windows Guest Additions with transfer support - * (for example 7.2.6 and 7.2.10) expect URI-list offers to be reported on - * their own so they can replace the normal clipboard announcement with an OLE - * IDataObject. - * - * @returns The new Shared Clipboard formats. - * @param fHostToGuest Reporting direction. - * \c true from host -> guest. - * \c false from guest -> host. - * @param pClient Pointer to client instance. - * @param fFormats Reported clipboard formats. - */ -SHCLFORMATS shClSvcHandleFormats(bool fHostToGuest, PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - SHCLFORMATS const fFormatsOrg = fFormats; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - if (fFormats & VBOX_SHCL_FMT_URI_LIST) - { - if (!shClSvcClientTransfersAreAllowed(pClient)) - { - uint32_t const fTransferMode = ShClSvcClientGetTransferMode(pClient); - uint64_t const fGuestFeatures = ShClSvcClientGetGuestFeatures0(pClient); - uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - LogRelMax(16, ("Shared Clipboard: File transfer format %#x was reported by %s without enabled and negotiated transfers (mode=%#x, features0=%#RX64, required=%#RX64), masking it\n", - VBOX_SHCL_FMT_URI_LIST, fHostToGuest ? "host" : "guest", fTransferMode, - fGuestFeatures, fRequired)); - fFormats &= ~VBOX_SHCL_FMT_URI_LIST; - } - else if (fHostToGuest) - { - if (fFormats != VBOX_SHCL_FMT_URI_LIST) - LogRelMax2(16, ("Shared Clipboard: Host reported file transfer together with regular formats %#x; announcing URI-list alone for Guest Additions compatibility\n", - fFormats & ~VBOX_SHCL_FMT_URI_LIST)); - fFormats = VBOX_SHCL_FMT_URI_LIST; - } - } -#else - RT_NOREF(pClient, fHostToGuest); -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - - if (LogRelIs2Enabled()) - { - char *pszFmts = ShClFormatsToStrA(fFormats); - LogRel2(("Shared Clipboard: %s reported formats %#x/'%s' to %s\n", - fHostToGuest ? "Host" : "Guest", - fFormats, pszFmts ? pszFmts : "", - fHostToGuest ? "guest" : "host")); - RTStrFree(pszFmts); - } - - if (fFormats != fFormatsOrg) - LogRelMax2(16, ("Shared Clipboard: Adjusted %s clipboard formats from %#x to %#x before reporting to %s\n", - fHostToGuest ? "host" : "guest", fFormatsOrg, fFormats, fHostToGuest ? "guest" : "host")); - - return fFormats; -} - - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** - * Checks whether file transfers are enabled and supported by a client. - * - * Clipboard direction is deliberately not considered here and must be checked - * separately by the operation being authorized. - * - * @returns true if file transfers may be used, false otherwise. - * @param pClient Client to check. - */ -bool shClSvcClientTransfersAreAllowed(PSHCLCLIENT pClient) -{ - AssertPtrReturn(pClient, false); - - uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - return (ShClSvcClientGetTransferMode(pClient) & VBOX_SHCL_TRANSFER_MODE_F_ENABLED) - && (ShClSvcClientGetGuestFeatures0(pClient) & fRequired) == fRequired; -} -#endif - -void ShClSvcClientLock(PSHCLCLIENT pClient) -{ - int rc2 = RTCritSectEnter(&pClient->CritSect); - AssertRC(rc2); -} - -void ShClSvcClientUnlock(PSHCLCLIENT pClient) -{ - int rc2 = RTCritSectLeave(&pClient->CritSect); - AssertRC(rc2); -} - -/** - * Allocates a new clipboard message. - * - * @returns Allocated clipboard message, or NULL on failure. - * @param pClient The client which is target of this message. - * @param idMsg The message ID (VBOX_SHCL_HOST_MSG_XXX) to use - * @param cParms The number of parameters the message takes. - */ -PSHCLCLIENTMSG ShClSvcClientMsgAlloc(PSHCLCLIENT pClient, uint32_t idMsg, uint32_t cParms) -{ - RT_NOREF(pClient); - PSHCLCLIENTMSG pMsg = (PSHCLCLIENTMSG)RTMemAllocZ(RT_UOFFSETOF_DYN(SHCLCLIENTMSG, aParms[cParms])); - if (pMsg) - { - uint32_t cAllocated = ASMAtomicIncU32(&pClient->cMsgAllocated); - if (cAllocated <= 4096) - { - RTListInit(&pMsg->ListEntry); - pMsg->cParms = cParms; - pMsg->idMsg = idMsg; - return pMsg; - } - AssertMsgFailed(("Too many messages allocated for client %u! (%u)\n", pClient->State.uClientID, cAllocated)); - ASMAtomicDecU32(&pClient->cMsgAllocated); - RTMemFree(pMsg); - } - return NULL; -} - -/** - * Frees a formerly allocated client clipboard message. - * - * @param pClient The client which was the target of this message. - * @param pMsg Clipboard message to free. - */ -void ShClSvcClientMsgFree(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) -{ - RT_NOREF(pClient); - /** @todo r=bird: Do accounting. */ - if (pMsg) - { - pMsg->idMsg = UINT32_C(0xdeadface); - RTMemFree(pMsg); - - uint32_t cAllocated = ASMAtomicDecU32(&pClient->cMsgAllocated); - Assert(cAllocated < UINT32_MAX / 2); - RT_NOREF(cAllocated); - } -} - -/** - * Sets the VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT - * return parameters. - * - * @param pMsg Message to set return parameters to. - * @param paDstParms The peek parameter vector. - * @param cDstParms The number of peek parameters (at least two). - * @remarks ASSUMES the parameters has been cleared by clientMsgPeek. - */ -void shClSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) -{ - Assert(cDstParms >= 2); - if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT) - paDstParms[0].u.uint32 = pMsg->idMsg; - else - paDstParms[0].u.uint64 = pMsg->idMsg; - paDstParms[1].u.uint32 = pMsg->cParms; - - uint32_t i = RT_MIN(cDstParms, pMsg->cParms + 2); - while (i-- > 2) - switch (pMsg->aParms[i - 2].type) - { - case VBOX_HGCM_SVC_PARM_32BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint32_t); break; - case VBOX_HGCM_SVC_PARM_64BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint64_t); break; - case VBOX_HGCM_SVC_PARM_PTR: paDstParms[i].u.uint32 = pMsg->aParms[i - 2].u.pointer.size; break; - } -} - -/** - * Sets the VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT return parameters. - * - * @returns VBox status code. - * @param pMsg The message which parameters to return to the guest. - * @param paDstParms The peek parameter vector. - * @param cDstParms The number of peek parameters should be exactly two - */ -int shClSvcMsgSetOldWaitReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) -{ - /* - * Assert sanity. - */ - AssertPtr(pMsg); - AssertPtrReturn(paDstParms, VERR_INVALID_POINTER); - AssertReturn(cDstParms >= 2, VERR_INVALID_PARAMETER); - - Assert(pMsg->cParms == 2); - Assert(pMsg->aParms[0].u.uint32 == pMsg->idMsg); - switch (pMsg->idMsg) - { - case VBOX_SHCL_HOST_MSG_READ_DATA: - case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: - break; - default: - AssertFailed(); - } - - /* - * Set the parameters. - */ - if (pMsg->cParms > 0) - paDstParms[0] = pMsg->aParms[0]; - if (pMsg->cParms > 1) - paDstParms[1] = pMsg->aParms[1]; - return VINF_SUCCESS; -} - - -/** - * Wakes up a pending client (i.e. waiting for new messages). - * - * @returns VBox status code. - * @retval VINF_NO_CHANGE if the client is not in pending mode. - * @param pClient Client to wake up. - * - * @note Caller must enter critical section. - */ -int ShClSvcClientWakeup(PSHCLCLIENT pClient) -{ - Assert(RTCritSectIsOwner(&pClient->CritSect)); - int rc = VINF_NO_CHANGE; - - if (pClient->Pending.uType != 0) - { - LogFunc(("[Client %RU32] Waking up ...\n", pClient->State.uClientID)); - - PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); - AssertReturn(pFirstMsg, VERR_INTERNAL_ERROR); - - LogFunc(("[Client %RU32] Current host message is %s (%RU32), cParms=%RU32\n", - pClient->State.uClientID, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); - - if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) - shClSvcMsgSetPeekReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); - else if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) /* Legacy, Guest Additions < 6.1. */ - shClSvcMsgSetOldWaitReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); - else - AssertMsgFailedReturn(("pClient->Pending.uType=%u\n", pClient->Pending.uType), VERR_INTERNAL_ERROR_3); - - rc = pClient->pBackend->pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); - - if ( rc != VERR_CANCELLED - && pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) - { - RTListNodeRemove(&pFirstMsg->ListEntry); - ShClSvcClientMsgFree(pClient, pFirstMsg); - } - - pClient->Pending.hHandle = NULL; - pClient->Pending.paParms = NULL; - pClient->Pending.cParms = 0; - pClient->Pending.uType = 0; - } - else - LogFunc(("[Client %RU32] Not in pending state, skipping wakeup\n", pClient->State.uClientID)); - - return rc; -} - -/** - * Appends a message to the client's queue and wake it up. - * - * @returns VBox status code, though the message is consumed regardless of what - * is returned. - * @param pClient The client to queue the message on. - * @param pMsg The message to queue. Ownership is always - * transfered to the queue. - * - * @note Caller must enter critical section. - */ -int shClSvcClientMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) -{ - Assert(RTCritSectIsOwner(&pClient->CritSect)); - AssertPtr(pMsg); - AssertPtr(pClient); - LogFlowFunc(("idMsg=%s (%u) cParms=%u\n", ShClSvcHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms)); - - RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); - int const rc = ShClSvcClientWakeup(pClient); - if (RT_FAILURE(rc)) - { - PSHCLCLIENTMSG pQueued; - RTListForEach(&pClient->MsgQueue, pQueued, SHCLCLIENTMSG, ListEntry) - if (pQueued == pMsg) - { - RTListNodeRemove(&pQueued->ListEntry); - ShClSvcClientMsgFree(pClient, pQueued); - break; - } - } - return rc; -} - -/** - * Adds a new message to a client'S message queue. - * - * @param pClient Pointer to the client data structure to add new message to. - * @param pMsg Pointer to message to add. The queue then owns the pointer. - * @param fAppend Whether to append or prepend the message to the queue. - * - * @note Caller must enter critical section. - */ -void ShClSvcClientMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend) -{ - Assert(RTCritSectIsOwner(&pClient->CritSect)); - AssertPtr(pMsg); - - LogFlowFunc(("idMsg=%s (%RU32) cParms=%RU32 fAppend=%RTbool\n", - ShClSvcHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms, fAppend)); - - if (fAppend) - RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); - else - RTListPrepend(&pClient->MsgQueue, &pMsg->ListEntry); -} - -#endif /* VBOX_WITH_SHARED_CLIPBOARD_HOST */ diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index 99963e72da73..36dbebb76dfc 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -4853,12 +4853,14 @@ static void shClSvcTransferCleanupAllUnused(PSHCLCLIENT pClient) * @param pClient Client that owns the transfer. * @param enmDir Transfer direction to create. * @param enmSource Transfer source to create. + * @param pCallbacks Callback table to copy into the transfer. Optional and can be NULL. * @param idTransfer Transfer ID to use for creation. * If set to NIL_SHCLTRANSFERID, a new transfer ID will be created. * @param ppTransfer Where to return the retained transfer on success. Optional and can be NULL. * The caller must release it with ShClTransferRelease(). */ -int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) +int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, + PSHCLTRANSFERCALLBACKS pCallbacks, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) { AssertPtrReturn(pClient, VERR_INVALID_POINTER); if (ppTransfer) @@ -4877,7 +4879,7 @@ int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURC * recheck policy after it reacquires the lock. */ shClSvcTransferCleanupAllUnused(pClient); if (shClSvcClientTransfersAreAllowed(pClient)) - rc = ShClTransferCreate(enmDir, enmSource, &pClient->Transfers.Callbacks, &pTransfer); + rc = ShClTransferCreate(enmDir, enmSource, pCallbacks, &pTransfer); } if (RT_SUCCESS(rc)) { diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp index a3cf6016fb29..fa5ffd92d013 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-win.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-win.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Windows-specific functions for clipboard handling. */ @@ -199,10 +199,57 @@ void ShClWinCtxDestroy(PSHCLWINCTX pWinCtx) if (RTCritSectIsInitialized(&pWinCtx->CritSect)) { - int rc2 = RTCritSectDelete(&pWinCtx->CritSect); - AssertRC(rc2); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + int rc2 = RTCritSectEnter(&pWinCtx->CritSect); + AssertFatalMsgRC(rc2, ("Taking the Windows clipboard context lock during teardown failed with %Rrc\n", rc2)); + + /* Keep the owned reference alive while detaching it, then invalidate the + * object before the backend context stored in its callbacks is freed. */ + ShClWinDataObject *pDataObjInFlight = pWinCtx->pDataObjInFlight; + pWinCtx->pDataObjInFlight = NULL; + + rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertFatalMsgRC(rc2, ("Releasing the Windows clipboard context lock during teardown failed with %Rrc\n", rc2)); + + if (pDataObjInFlight) + { + pDataObjInFlight->Uninit(); + pDataObjInFlight->Release(); + } +#endif + int const rcDelete = RTCritSectDelete(&pWinCtx->CritSect); + AssertRC(rcDelete); + } +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Disables new callbacks on the current in-flight data object and waits for an + * active callback running on another thread to return. + * + * @param pWinCtx Windows context whose data-object callbacks to disable. + */ +void ShClWinCtxDisableDataObjectCallbacks(PSHCLWINCTX pWinCtx) +{ + AssertPtrReturnVoid(pWinCtx); + + int rc = RTCritSectEnter(&pWinCtx->CritSect); + AssertFatalMsgRC(rc, ("Taking the Windows clipboard context lock while disabling callbacks failed with %Rrc\n", rc)); + + ShClWinDataObject *pDataObj = pWinCtx->pDataObjInFlight; + if (pDataObj) + pDataObj->AddRef(); + + rc = RTCritSectLeave(&pWinCtx->CritSect); + AssertFatalMsgRC(rc, ("Releasing the Windows clipboard context lock while disabling callbacks failed with %Rrc\n", rc)); + + if (pDataObj) + { + pDataObj->DisableCallbacks(); + pDataObj->Release(); } } +#endif /** * Checks and initializes function pointer which are required for using @@ -1082,25 +1129,22 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, /* Make sure to enter the critical section before setting the clipboard data, as otherwise WM_CLIPBOARDUPDATE * might get called *before* we had the opportunity to set pWinCtx->hWndClipboardOwnerUs below. */ + ShClWinDataObject *pObjToRelease = NULL; + ShClWinDataObject *pObjReplaced = NULL; + int rc = RTCritSectEnter(&pWinCtx->CritSect); if (RT_SUCCESS(rc)) { LogFlowFunc(("pWinCtx->pDataObjInFlight=%p\n", pWinCtx->pDataObjInFlight)); - /* Create a new data object here, assign it as the the current data object in-flight and - * announce it to Windows below. - * - * The data object will be deleted automatically once its refcount reaches 0. - */ + /* Keep an explicit reference while creating and publishing the object. */ ShClWinDataObject *pObj = new ShClWinDataObject(); if (pObj) { + pObj->AddRef(); rc = pObj->Init(pCtx, pCallbacks); - if (RT_SUCCESS(rc)) - { - if (RT_SUCCESS(rc)) - pWinCtx->pDataObjInFlight = pObj; - } + if (RT_FAILURE(rc)) + pObjToRelease = pObj; } else rc = VERR_NO_MEMORY; @@ -1117,10 +1161,10 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, for (unsigned uTries = 0; uTries < 3; uTries++) { - hr = OleSetClipboard(pWinCtx->pDataObjInFlight); + hr = OleSetClipboard(pObj); if (SUCCEEDED(hr)) { - Assert(OleIsCurrentClipboard(pWinCtx->pDataObjInFlight) == S_OK); /* Sanity. */ + Assert(OleIsCurrentClipboard(pObj) == S_OK); /* Sanity. */ /* * Calling OleSetClipboard() changed the clipboard owner, which in turn will let us receive @@ -1129,6 +1173,11 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, */ pWinCtx->hWndClipboardOwnerUs = GetClipboardOwner(); + /* Transfer the local reference to the context only after OLE + * accepted the new object, preserving the old one on failure. */ + pObjReplaced = pWinCtx->pDataObjInFlight; + pWinCtx->pDataObjInFlight = pObj; + LogFlowFunc(("hWndClipboardOwnerUs=%p\n", pWinCtx->hWndClipboardOwnerUs)); break; } @@ -1140,6 +1189,7 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, if (FAILED(hr)) { rc = VERR_ACCESS_DENIED; /** @todo Fudge; fix this. */ + pObjToRelease = pObj; LogRel(("Shared Clipboard: Failed with %Rhrc when setting data object to clipboard\n", hr)); } } @@ -1148,6 +1198,18 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, AssertRC(rc2); } + if (pObjReplaced) + { + pObjReplaced->Uninit(); + pObjReplaced->Release(); + } + + if (pObjToRelease) + { + pObjToRelease->Uninit(); + pObjToRelease->Release(); + } + LogFlowFuncLeaveRC(rc); return rc; } @@ -1166,9 +1228,18 @@ int ShClWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) AssertMsgReturn( pTransfer->pvUser == NULL && pTransfer->cbUser == 0, ("Already initialized Windows-specific data\n"), VERR_WRONG_ORDER); - pTransfer->pvUser = new ShClWinTransferCtx(); /** @todo Can this throw? */ - AssertPtrReturn(pTransfer->pvUser, VERR_INVALID_POINTER); - pTransfer->cbUser = sizeof(ShClWinTransferCtx); + ShClWinTransferCtx *pWinTransferCtx = new ShClWinTransferCtx(); /** @todo Can this throw? */ + AssertPtrReturn(pWinTransferCtx, VERR_NO_MEMORY); + + int rc = RTCritSectInit(&pWinTransferCtx->CritSect); + if (RT_FAILURE(rc)) + { + delete pWinTransferCtx; + return rc; + } + + pTransfer->pvUser = pWinTransferCtx; + pTransfer->cbUser = sizeof(*pWinTransferCtx); return VINF_SUCCESS; } @@ -1176,8 +1247,8 @@ int ShClWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) /** * Unregisters the data object associated with a Windows transfer. * - * This disables the data object and drops its long-lived transfer reference - * while leaving the per-transfer context intact for temporary users. + * This disables its backend callbacks and drops its long-lived transfer + * reference while leaving the per-transfer context intact for temporary users. * * @param pTransfer Shared Clipboard transfer to unregister. */ @@ -1191,10 +1262,19 @@ void ShClWinTransferUnregister(PSHCLTRANSFER pTransfer) ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)pTransfer->pvUser; AssertPtr(pWinURITransferCtx); - if (pWinURITransferCtx->pDataObj) + int rc = RTCritSectEnter(&pWinURITransferCtx->CritSect); + AssertFatalMsgRC(rc, ("Taking the Windows transfer-context lock during teardown failed with %Rrc\n", rc)); + + ShClWinDataObject *pDataObj = pWinURITransferCtx->pDataObj; + pWinURITransferCtx->pDataObj = NULL; + + rc = RTCritSectLeave(&pWinURITransferCtx->CritSect); + AssertFatalMsgRC(rc, ("Releasing the Windows transfer-context lock during teardown failed with %Rrc\n", rc)); + + if (pDataObj) { - pWinURITransferCtx->pDataObj->Uninit(); - pWinURITransferCtx->pDataObj = NULL; + pDataObj->Uninit(); + pDataObj->Release(); } } } @@ -1221,8 +1301,14 @@ void ShClWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)pTransfer->pvUser; AssertPtr(pWinURITransferCtx); + /* Fallback for direct, non-context destruction. Registered consuming + * teardown normally disables and detaches this object in the + * unregistration callback. */ ShClWinTransferUnregister(pTransfer); + int const rc = RTCritSectDelete(&pWinURITransferCtx->CritSect); + AssertRC(rc); + delete pWinURITransferCtx; pWinURITransferCtx = NULL; @@ -1303,6 +1389,8 @@ int ShClWinTransferStart(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) { RT_NOREF(pTransfer); + ShClWinDataObject *pObjHandedOff = NULL; + int rc = RTCritSectEnter(&pWinCtx->CritSect); if (RT_SUCCESS(rc)) { @@ -1311,7 +1399,10 @@ int ShClWinTransferStart(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) { rc = shClWinTransferStartInternal(pWinCtx, pObj); if (RT_SUCCESS(rc)) + { pWinCtx->pDataObjInFlight = NULL; /* Hand off to Windows on success. */ + pObjHandedOff = pObj; + } } else /* No current in-flight data object. */ rc = VERR_WRONG_ORDER; @@ -1320,6 +1411,9 @@ int ShClWinTransferStart(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) AssertRC(rc2); } + if (pObjHandedOff) + pObjHandedOff->Release(); + return rc; } diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp index 874a2120cebd..ea24d5736428 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -502,9 +502,24 @@ static int clipThreadScheduleCall(PSHCLX11CTX pCtx, XtAppAddTimeOut(pCtx->pAppContext, 0, (XtTimerCallbackProc)proc, (XtPointer)client_data); - ssize_t cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN); - Assert(cbWritten == WAKE_UP_STRING_LEN); - RT_NOREF(cbWritten); + + ssize_t cbWritten; + do + cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN); + while (cbWritten < 0 && errno == EINTR); + if (cbWritten < 0) + { + /* A full non-blocking pipe is already readable and will wake the worker. */ + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + int const rc = RTErrConvertFromErrno(errno); + /* The Xt callback is already queued and cannot be cancelled without racing the worker. */ + AssertFatalMsgRC(rc, ("Waking the X11 event thread failed with %Rrc\n", rc)); + } + } + else if (cbWritten != WAKE_UP_STRING_LEN) + AssertFatalMsgFailed(("Waking the X11 event thread wrote only %zd of %zu bytes\n", + cbWritten, (size_t)WAKE_UP_STRING_LEN)); #else RT_NOREF(pCtx); tstThreadScheduleCall(proc, client_data); @@ -1413,6 +1428,8 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + pCtx->Thread = NIL_RTTHREAD; + pCtx->fThreadStarted = false; pCtx->fGrabClipboardOnStart = fGrab; clipResetX11Formats(pCtx); @@ -1429,7 +1446,8 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) pCtx->wakeupPipeRead = pipes[0]; pCtx->wakeupPipeWrite = pipes[1]; - if (!fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK)) + if ( !fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK) + && !fcntl(pCtx->wakeupPipeWrite, F_SETFL, O_NONBLOCK)) { rc = VINF_SUCCESS; } @@ -1452,6 +1470,7 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) { /* The backend must not release pCtx while the worker initializes it. */ rc = RTThreadUserWait(pCtx->Thread, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(rc, ("Waiting for X11 event thread startup failed with %Rrc\n", rc)); } else clipThreadCloseWakeupPipe(pCtx); @@ -1468,10 +1487,9 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) /* The worker signalled its terminal startup failure; reap it before returning. */ int rcThread = VERR_IPE_UNINITIALIZED_STATUS; int rc2 = RTThreadWait(pCtx->Thread, RT_INDEFINITE_WAIT, &rcThread); - if (RT_SUCCESS(rc2)) - rc = RT_FAILURE(rcThread) ? rcThread : VERR_GENERAL_FAILURE; - else - rc = rc2; + AssertFatalMsgRC(rc2, ("Reaping X11 event thread after startup failure failed with %Rrc\n", rc2)); + pCtx->Thread = NIL_RTTHREAD; + rc = RT_FAILURE(rcThread) ? rcThread : VERR_GENERAL_FAILURE; clipThreadCloseWakeupPipe(pCtx); LogRel(("Shared Clipboard: X11 event thread reported an error while starting: %Rrc\n", rc)); @@ -1510,31 +1528,43 @@ int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool fGrab) */ int ShClX11ThreadStop(PSHCLX11CTX pCtx) { - LogRel2(("Shared Clipboard: Signalling the X11 event thread to stop\n")); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->Thread != NIL_RTTHREAD, VERR_INVALID_STATE); - /* Write to the "stop" pipe. */ - int rc = clipThreadScheduleCall(pCtx, clipThreadSignalStop, (XtPointer)pCtx); - if (RT_FAILURE(rc)) + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int rcWait = RTThreadWait(pCtx->Thread, 0 /* cMillies */, &rcThread); + if (RT_SUCCESS(rcWait)) { - LogRel(("Shared Clipboard: cannot notify X11 event thread on shutdown with %Rrc\n", rc)); - return rc; + pCtx->Thread = NIL_RTTHREAD; + pCtx->fThreadStarted = false; + clipThreadCloseWakeupPipe(pCtx); + return rcThread; } + AssertFatalMsg(rcWait == VERR_TIMEOUT, ("Probing X11 event thread failed with %Rrc\n", rcWait)); + + LogRel2(("Shared Clipboard: Signalling the X11 event thread to stop\n")); + + /* Write to the "stop" pipe. */ + int const rcSignal = clipThreadScheduleCall(pCtx, clipThreadSignalStop, (XtPointer)pCtx); + AssertFatalMsgRC(rcSignal, ("Cannot notify X11 event thread on shutdown with %Rrc\n", rcSignal)); LogRel2(("Shared Clipboard: Waiting for X11 event thread to stop ...\n")); - int rcThread; - rc = RTThreadWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */, &rcThread); - if (RT_SUCCESS(rc)) - rc = rcThread; - if (RT_SUCCESS(rc)) + rcWait = RTThreadWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */, &rcThread); + if (RT_FAILURE(rcWait)) { - clipThreadCloseWakeupPipe(pCtx); + LogRel(("Shared Clipboard: X11 event thread did not stop promptly (%Rrc); waiting indefinitely\n", rcWait)); + rcWait = RTThreadWait(pCtx->Thread, RT_INDEFINITE_WAIT, &rcThread); } + AssertFatalMsgRC(rcWait, ("Reaping X11 event thread failed with %Rrc\n", rcWait)); + + pCtx->Thread = NIL_RTTHREAD; + pCtx->fThreadStarted = false; + clipThreadCloseWakeupPipe(pCtx); + int const rc = rcThread; if (RT_SUCCESS(rc)) - { LogRel2(("Shared Clipboard: X11 event thread stopped successfully\n")); - } else LogRel(("Shared Clipboard: Stopping X11 event thread failed with %Rrc\n", rc)); diff --git a/src/VBox/HostServices/SharedClipboard/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/Makefile.kmk index d1faef100249..a27f8de5bcad 100644 --- a/src/VBox/HostServices/SharedClipboard/Makefile.kmk +++ b/src/VBox/HostServices/SharedClipboard/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114993 2026-08-11 14:46:01Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the Shared Clipboard Host Service. # @@ -45,9 +45,10 @@ VBoxSharedClipboard_INCS.win = \ VBoxSharedClipboard_SOURCES = \ VBoxSharedClipboardSvc.cpp \ - VBoxSharedClipboardSvc-backend.cpp \ + VBoxSharedClipboardSvc-ext.cpp \ VBoxSharedClipboardSvc-client.cpp \ VBoxSharedClipboardSvc-host.cpp \ + VBoxSharedClipboardSvc-transport.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp VBoxSharedClipboard_SOURCES.win = \ diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp deleted file mode 100644 index 8919952b805c..000000000000 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/* $Id: VBoxSharedClipboardSvc-backend.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard Service - Backend and extension bridge handling. - */ - -/* - * Copyright (C) 2006-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD -#include -#include /* must be included before hgcmsvc.h */ - -#include -#include -#include - -#include -#include - -#include "VBoxSharedClipboardSvc-internal.h" - - -/********************************************************************************************************************************* -* Internal Functions * -*********************************************************************************************************************************/ -static int shClSvcBackendHostCallback(uint32_t u32Function, PSHCLEXTPARMS pvParms, uint32_t cbParms); -static DECLCALLBACK(int) shClSvcBackendExtensionCallback(uint32_t u32Function, uint32_t u32Format, - void *pvData, uint32_t cbData); - - -/** - * Returns the Shared Clipboard backend in use. - * - * @returns Pointer to backend instance. - */ -PSHCLBACKEND ShClSvcGetBackend(void) -{ - return &g_ShClSvc.Backend; -} - - -static int shClSvcBackendHostCallback(uint32_t u32Function, PSHCLEXTPARMS pvParms, uint32_t cbParms) -{ - LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); - - int rc; - if (g_ShClSvc.ExtState.pfnExtension) - rc = g_ShClSvc.ExtState.pfnExtension(g_ShClSvc.ExtState.pvExtension, u32Function, pvParms, cbParms); - else - rc = VERR_NOT_SUPPORTED; - - LogFlowFunc(("Returning rc=%Rrc\n", rc)); - return rc; -} - - -int shClSvcBackendInit(VBOXHGCMSVCFNTABLE *pTable) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); - parms.u.ReadWriteData.pTable = pTable; - - /* The backend in Main calls: ShClBackendInit(ShClSvcGetBackend(), pTable); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT, &parms, sizeof(parms)); -} - - -int shClSvcBackendConnect(PSHCLCLIENT pClient) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); - parms.u.ReadWriteData.pClient = pClient; - /* The backend in Main calls: ShClBackendConnect(pClient->pBackend, pClient); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT, &parms, sizeof(parms)); -} - - -int shClSvcBackendSync(PSHCLCLIENT pClient) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); - parms.u.ReadWriteData.pClient = pClient; - - /* The backend in Main calls: ShClBackendSync(pClient->pBackend, pClient); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC, &parms, sizeof(parms)); -} - - -void shClSvcBackendDisconnect(PSHCLCLIENT pClient) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pClient = pClient; - - /* The backend in Main calls: ShClBackendDisconnect(pClient->pBackend, pClient); */ - shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT, &parms, sizeof(parms)); -} - - -void shClSvcBackendDestroy(void) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); - - /* The backend in Main calls: ShClBackendDestroy(ShClSvcGetBackend()); */ - shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY, &parms, sizeof(parms)); -} - - -/** - * Reports clipboard formats to the guest. - * - * @note Host backend callers must check if it's active (use - * ShClSvcIsBackendActive) before calling to prevent mixing up the - * VRDE clipboard. - * - * @returns VBox status code. - * @param pClient Client to report clipboard formats to. - * @param fFormats The formats to report (VBOX_SHCL_FMT_XXX), zero - * is okay (empty the clipboard). - * @param enmSource Source the reported formats came from. - * - * @thread Backend thread. - */ -int shClSvcBackendReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource) -{ - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - LogFlowFunc(("fFormats=%#x, enmSource=%RU32\n", fFormats, enmSource)); - - /* - * Check if the service mode allows this operation and whether the guest is - * supposed to be reading from the host. Otherwise, silently ignore reporting - * formats and return VINF_SUCCESS in order to do not trigger client - * termination in shClSvcConnect(). - */ - uint32_t uMode = ShClSvcGetMode(); - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) - { /* likely */ } - else - return VINF_SUCCESS; - - fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); - - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReportFormats.uFormats = fFormats; - parms.u.ReportFormats.pClient = pClient; - parms.u.ReportFormats.enmSource = enmSource; - - /* The backend in Main calls: ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); */ - int rc = shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST, &parms, sizeof(parms)); - - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Reporting formats %#x to guest failed with %Rrc\n", fFormats, rc)); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -int ShClSvcReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - return shClSvcBackendReportFormatsToGuest(pClient, fFormats, SHCLSOURCE_LOCAL); -} - - -int shClSvcBackendReportFormatsToHost(PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReportFormats.uFormats = fFormats; - parms.u.ReportFormats.pClient = pClient; - - /* The backend in Main calls: ShClBackendReportFormats(pClient->pBackend, pClient, fFormats); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, &parms, sizeof(parms)); -} - - -int shClSvcBackendReadData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.uFormat = uFormat; - parms.u.ReadWriteData.pvData = pvData; - parms.u.ReadWriteData.cbData = cbData; - parms.u.ReadWriteData.cbActual = pcbActual ? *pcbActual : 0; - parms.u.ReadWriteData.pClient = pClient; - - /* Read clipboard data from the extension. */ - int rc = shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ, &parms, sizeof(parms)); - if ( RT_SUCCESS(rc) - && pcbActual) - *pcbActual = parms.u.ReadWriteData.cbActual; - - return rc; -} - - -static int shClSvcBackendReadVrdeData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.uFormat = uFormat; - parms.u.ReadWriteData.pvData = pvData; - parms.u.ReadWriteData.cbData = cbData; - parms.u.ReadWriteData.pClient = pClient; - - /* Read clipboard data from the VRDE extension. */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE, &parms, sizeof(parms)); -} - - -int shClSvcBackendWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.uFormat = uFormat; - parms.u.ReadWriteData.pvData = pvData; - parms.u.ReadWriteData.cbData = cbData; - parms.u.ReadWriteData.pClient = pClient; - parms.u.ReadWriteData.pCmdCtx = pCmdCtx; - - /* The backend in Main calls: ShClBackendWriteData(pClient->pBackend, pClient, pCmdCtx, fFormats, pvData, cbData); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof(parms)); -} - - -static DECLCALLBACK(int) shClSvcBackendExtensionCallback(uint32_t u32Function, uint32_t u32Format, void *pvData, uint32_t cbData) -{ - LogFlowFunc(("u32Function=%RU32\n", u32Function)); - - int rc = VINF_SUCCESS; - - shClSvcLock(); - - /* Figure out if the client in charge for the service extension still is connected. */ - PSHCLCLIENT pClient = g_ShClSvc.pActiveClient; - if (pClient) - { - switch (u32Function) - { - /* The service extension announces formats to the host. */ - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - { - LogFlowFunc(("VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: fReadingData=%RTbool\n", - g_ShClSvc.ExtState.fReadingData)); - if (!g_ShClSvc.ExtState.fReadingData) - rc = shClSvcBackendReportFormatsToGuest(pClient, u32Format, SHCLSOURCE_REMOTE); - else - { - g_ShClSvc.ExtState.fDelayedAnnouncement = true; - g_ShClSvc.ExtState.fDelayedFormats = u32Format; - rc = VINF_SUCCESS; - } - break; - } - - /* The service extension wants read data from the guest. */ - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - { - rc = shClSvcBackendReadVrdeData(pClient, u32Format, pvData, cbData); - break; - } - - default: - /* Just skip other messages. */ - break; - } - } - else - rc = VERR_NOT_FOUND; - - shClSvcUnlock(); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -DECLCALLBACK(int) shClSvcRegisterExtension(void *, PFNHGCMSVCEXT pfnExtension, void *pvExtension) -{ - LogFlowFunc(("pfnExtension=%p\n", pfnExtension)); - - SHCLEXTPARMS parms; - RT_ZERO(parms); - - /* - * Reference counting for service extension registration is done a few - * layers up (in ConsoleVRDPServer::ClipboardCreate()). - */ - - shClSvcLock(); - int rc = VINF_SUCCESS; - - if (pfnExtension) - { - /* Install extension. */ - g_ShClSvc.ExtState.pfnExtension = pfnExtension; - g_ShClSvc.ExtState.pvExtension = pvExtension; - - parms.u.SetCallback.pfnCallback = shClSvcBackendExtensionCallback; - - rc = shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - - LogRel2(("Shared Clipboard: registered service extension\n")); - } - else - { - (void) shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - - /* - * When a guest VM using the Shared Clipboard shuts down Console::i_powerDown() - * will call HGCMHostUnregisterServiceExtension() and then VMMDev::hgcmShutdown() - * shortly thereafter. The former call lands here to unregister the extension and - * the latter calls shClSvcUnload() to unload the SharedClipboardSvc.so shared object - * and tear down the backend infrastructure via ShClBackendDestroy(). Unregistering - * the extension disables the host callback which means shClSvcBackendHostCallback() isn't - * able to call ShClBackendDestroy() in shClSvcUnload() so we do that here while the - * host callback is still available. - */ - shClSvcBackendDestroy(); - - /* Uninstall extension. */ - g_ShClSvc.ExtState.pvExtension = NULL; - g_ShClSvc.ExtState.pfnExtension = NULL; - - LogRel2(("Shared Clipboard: de-registered service extension\n")); - } - - shClSvcUnlock(); - - return rc; -} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp index 3862568d4a15..95260db5af9c 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-client.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-client.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Client/session and message queue handling. */ @@ -84,6 +84,346 @@ static SHCLSESSIONID shClSvcClientAllocSessionId(void) return idSession; } +/** + * Handles clipboard formats. + * + * This suppresses file-transfer announcements until transfers are enabled and + * supported by the guest, and keeps host-to-guest transfer offers separate + * from ordinary clipboard formats. Older Windows Guest Additions with + * transfer support + * (for example 7.2.6 and 7.2.10) expect URI-list offers to be reported on + * their own so they can replace the normal clipboard announcement with an OLE + * IDataObject. + * + * @returns The new Shared Clipboard formats. + * @param fHostToGuest Reporting direction. + * \c true from host -> guest. + * \c false from guest -> host. + * @param pClient Pointer to client instance. + * @param fFormats Reported clipboard formats. + */ +SHCLFORMATS shClSvcHandleFormats(bool fHostToGuest, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + SHCLFORMATS const fFormatsOrg = fFormats; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fFormats & VBOX_SHCL_FMT_URI_LIST) + { + if (!shClSvcClientTransfersAreAllowed(pClient)) + { + uint32_t const fTransferMode = ShClSvcClientGetTransferMode(pClient); + uint64_t const fGuestFeatures = ShClSvcClientGetGuestFeatures0(pClient); + uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; + LogRelMax(16, ("Shared Clipboard: File transfer format %#x was reported by %s without enabled and negotiated transfers (mode=%#x, features0=%#RX64, required=%#RX64), masking it\n", + VBOX_SHCL_FMT_URI_LIST, fHostToGuest ? "host" : "guest", fTransferMode, + fGuestFeatures, fRequired)); + fFormats &= ~VBOX_SHCL_FMT_URI_LIST; + } + else if (fHostToGuest) + { + if (fFormats != VBOX_SHCL_FMT_URI_LIST) + LogRelMax2(16, ("Shared Clipboard: Host reported file transfer together with regular formats %#x; announcing URI-list alone for Guest Additions compatibility\n", + fFormats & ~VBOX_SHCL_FMT_URI_LIST)); + fFormats = VBOX_SHCL_FMT_URI_LIST; + } + } +#else + RT_NOREF(pClient, fHostToGuest); +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + if (LogRelIs2Enabled()) + { + char *pszFmts = ShClFormatsToStrA(fFormats); + LogRel2(("Shared Clipboard: %s reported formats %#x/'%s' to %s\n", + fHostToGuest ? "Host" : "Guest", + fFormats, pszFmts ? pszFmts : "", + fHostToGuest ? "guest" : "host")); + RTStrFree(pszFmts); + } + + if (fFormats != fFormatsOrg) + LogRelMax2(16, ("Shared Clipboard: Adjusted %s clipboard formats from %#x to %#x before reporting to %s\n", + fHostToGuest ? "host" : "guest", fFormatsOrg, fFormats, fHostToGuest ? "guest" : "host")); + + return fFormats; +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Checks whether file transfers are enabled and supported by a client. + * + * Clipboard direction is deliberately not considered here and must be checked + * separately by the operation being authorized. + * + * @returns true if file transfers may be used, false otherwise. + * @param pClient Client to check. + */ +bool shClSvcClientTransfersAreAllowed(PSHCLCLIENT pClient) +{ + AssertPtrReturn(pClient, false); + + uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; + return (ShClSvcClientGetTransferMode(pClient) & VBOX_SHCL_TRANSFER_MODE_F_ENABLED) + && (ShClSvcClientGetGuestFeatures0(pClient) & fRequired) == fRequired; +} +#endif + +/** + * Acquires a Shared Clipboard client's critical section. + * + * @param pClient Client to lock. + * + * Lock acquisition is an internal client-lifetime invariant. Failures are + * reported by a debug assertion rather than propagated to callers. + */ +void ShClSvcClientLock(PSHCLCLIENT pClient) +{ + int rc2 = RTCritSectEnter(&pClient->CritSect); + AssertRC(rc2); +} + +/** + * Releases a Shared Clipboard client's critical section. + * + * @param pClient Client to unlock. + * + * Lock release is an internal client-lifetime invariant. Failures are + * reported by a debug assertion rather than propagated to callers. + */ +void ShClSvcClientUnlock(PSHCLCLIENT pClient) +{ + int rc2 = RTCritSectLeave(&pClient->CritSect); + AssertRC(rc2); +} + +/** + * Allocates a new clipboard message. + * + * @returns Allocated clipboard message, or NULL on failure. + * @param pClient The client which is target of this message. + * @param idMsg The message ID (VBOX_SHCL_HOST_MSG_XXX) to use + * @param cParms The number of parameters the message takes. + */ +PSHCLCLIENTMSG ShClSvcClientMsgAlloc(PSHCLCLIENT pClient, uint32_t idMsg, uint32_t cParms) +{ + RT_NOREF(pClient); + PSHCLCLIENTMSG pMsg = (PSHCLCLIENTMSG)RTMemAllocZ(RT_UOFFSETOF_DYN(SHCLCLIENTMSG, aParms[cParms])); + if (pMsg) + { + uint32_t cAllocated = ASMAtomicIncU32(&pClient->cMsgAllocated); + if (cAllocated <= 4096) + { + RTListInit(&pMsg->ListEntry); + pMsg->cParms = cParms; + pMsg->idMsg = idMsg; + return pMsg; + } + AssertMsgFailed(("Too many messages allocated for client %u! (%u)\n", pClient->State.uClientID, cAllocated)); + ASMAtomicDecU32(&pClient->cMsgAllocated); + RTMemFree(pMsg); + } + return NULL; +} + +/** + * Frees a formerly allocated client clipboard message. + * + * @param pClient The client which was the target of this message. + * @param pMsg Clipboard message to free. + */ +void ShClSvcClientMsgFree(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + RT_NOREF(pClient); + /** @todo r=bird: Do accounting. */ + if (pMsg) + { + pMsg->idMsg = UINT32_C(0xdeadface); + RTMemFree(pMsg); + + uint32_t cAllocated = ASMAtomicDecU32(&pClient->cMsgAllocated); + Assert(cAllocated < UINT32_MAX / 2); + RT_NOREF(cAllocated); + } +} + +/** + * Sets the VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT + * return parameters. + * + * @param pMsg Message to set return parameters to. + * @param paDstParms The peek parameter vector. + * @param cDstParms The number of peek parameters (at least two). + * @remarks ASSUMES the parameters has been cleared by clientMsgPeek. + */ +void shClSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) +{ + Assert(cDstParms >= 2); + if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT) + paDstParms[0].u.uint32 = pMsg->idMsg; + else + paDstParms[0].u.uint64 = pMsg->idMsg; + paDstParms[1].u.uint32 = pMsg->cParms; + + uint32_t i = RT_MIN(cDstParms, pMsg->cParms + 2); + while (i-- > 2) + switch (pMsg->aParms[i - 2].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint32_t); break; + case VBOX_HGCM_SVC_PARM_64BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint64_t); break; + case VBOX_HGCM_SVC_PARM_PTR: paDstParms[i].u.uint32 = pMsg->aParms[i - 2].u.pointer.size; break; + } +} + +/** + * Sets the VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT return parameters. + * + * @returns VBox status code. + * @param pMsg The message which parameters to return to the guest. + * @param paDstParms The peek parameter vector. + * @param cDstParms The number of peek parameters should be exactly two + */ +int shClSvcMsgSetOldWaitReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) +{ + /* + * Assert sanity. + */ + AssertPtr(pMsg); + AssertPtrReturn(paDstParms, VERR_INVALID_POINTER); + AssertReturn(cDstParms >= 2, VERR_INVALID_PARAMETER); + + Assert(pMsg->cParms == 2); + Assert(pMsg->aParms[0].u.uint32 == pMsg->idMsg); + switch (pMsg->idMsg) + { + case VBOX_SHCL_HOST_MSG_READ_DATA: + case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + break; + default: + AssertFailed(); + } + + /* + * Set the parameters. + */ + if (pMsg->cParms > 0) + paDstParms[0] = pMsg->aParms[0]; + if (pMsg->cParms > 1) + paDstParms[1] = pMsg->aParms[1]; + return VINF_SUCCESS; +} + + +/** + * Wakes up a pending client (i.e. waiting for new messages). + * + * @returns VBox status code. + * @retval VINF_NO_CHANGE if the client is not in pending mode. + * @param pClient Client to wake up. + * + * @note Caller must enter critical section. + */ +int ShClSvcClientWakeup(PSHCLCLIENT pClient) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + int rc = VINF_NO_CHANGE; + + if (pClient->Pending.uType != 0) + { + LogFunc(("[Client %RU32] Waking up ...\n", pClient->State.uClientID)); + + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + AssertReturn(pFirstMsg, VERR_INTERNAL_ERROR); + + LogFunc(("[Client %RU32] Current host message is %s (%RU32), cParms=%RU32\n", + pClient->State.uClientID, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + + if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) + shClSvcMsgSetPeekReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); + else if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) /* Legacy, Guest Additions < 6.1. */ + shClSvcMsgSetOldWaitReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); + else + AssertMsgFailedReturn(("pClient->Pending.uType=%u\n", pClient->Pending.uType), VERR_INTERNAL_ERROR_3); + + AssertPtrReturn(pClient->pHelpers, VERR_INVALID_POINTER); + rc = pClient->pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); + + if ( rc != VERR_CANCELLED + && pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) + { + RTListNodeRemove(&pFirstMsg->ListEntry); + ShClSvcClientMsgFree(pClient, pFirstMsg); + } + + pClient->Pending.hHandle = NULL; + pClient->Pending.paParms = NULL; + pClient->Pending.cParms = 0; + pClient->Pending.uType = 0; + } + else + LogFunc(("[Client %RU32] Not in pending state, skipping wakeup\n", pClient->State.uClientID)); + + return rc; +} + +/** + * Appends a message to the client's queue and wake it up. + * + * @returns VBox status code, though the message is consumed regardless of what + * is returned. + * @param pClient The client to queue the message on. + * @param pMsg The message to queue. Ownership is always + * transfered to the queue. + * + * @note Caller must enter critical section. + */ +int shClSvcClientMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + AssertPtr(pMsg); + AssertPtr(pClient); + LogFlowFunc(("idMsg=%s (%u) cParms=%u\n", ShClSvcHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms)); + + RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); + int const rc = ShClSvcClientWakeup(pClient); + if (RT_FAILURE(rc)) + { + PSHCLCLIENTMSG pQueued; + RTListForEach(&pClient->MsgQueue, pQueued, SHCLCLIENTMSG, ListEntry) + if (pQueued == pMsg) + { + RTListNodeRemove(&pQueued->ListEntry); + ShClSvcClientMsgFree(pClient, pQueued); + break; + } + } + return rc; +} + +/** + * Adds a new message to a client's message queue. + * + * @param pClient Pointer to the client data structure to add new message to. + * @param pMsg Pointer to message to add. The queue then owns the pointer. + * @param fAppend Whether to append or prepend the message to the queue. + * + * @note Caller must enter critical section. + */ +void ShClSvcClientMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + AssertPtr(pMsg); + + LogFlowFunc(("idMsg=%s (%RU32) cParms=%RU32 fAppend=%RTbool\n", + ShClSvcHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms, fAppend)); + + if (fAppend) + RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); + else + RTListPrepend(&pClient->MsgQueue, &pMsg->ListEntry); +} + + + /** * Resets a client's state message queue. * @@ -125,12 +465,13 @@ int ShClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID) /* Assign the client ID. */ pClient->State.uClientID = uClientID; + pClient->pHelpers = g_ShClSvc.pHelpers; - /* Cache the current Shared Clipboard mode for the backend. */ + /* Cache the current Shared Clipboard mode in the client protocol state. */ ASMAtomicWriteU32(&pClient->State.uMode, ShClSvcGetMode()); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /* Cache the current Shared Clipboard transfer (file) mode for the backend. */ + /* Cache the current Shared Clipboard transfer (file) mode in the client protocol state. */ ASMAtomicWriteU32(&pClient->State.Transfers.uTransferMode, shClSvcTransferModeGet()); #endif @@ -490,7 +831,7 @@ int shClSvcClientNegotiateChunkSize(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCal * @param paParms Array of parameters. */ int shClSvcClientReportFeatures(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, - uint32_t cParms, VBOXHGCMSVCPARM paParms[]) + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) { /* * Validate the request. @@ -976,7 +1317,7 @@ int shClSvcClientMsgReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCM fFormats = shClSvcHandleFormats(false /* fHostToGuest */, pClient, fFormats); #endif shClSvcLock(); - rc = shClSvcBackendReportFormatsToHost(pClient, fFormats); + rc = shClSvcExtReportFormatsToHost(pClient, fFormats); shClSvcUnlock(); } @@ -1110,7 +1451,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA g_ShClSvc.ExtState.fReadingData = true; /* If there is a service extension active, try reading data from it first. */ - int rc = shClSvcBackendReadData(pClient, uFormat, pvData, cbData, &cbActual); + int rc = shClSvcExtReadData(pClient, uFormat, pvData, cbData, &cbActual); LogRel2(("Shared Clipboard: Read extension clipboard data (fDelayedAnnouncement=%RTbool, fDelayedFormats=%#x, " "max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", g_ShClSvc.ExtState.fDelayedAnnouncement, @@ -1121,7 +1462,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA * Otherwise, do this now. */ if (g_ShClSvc.ExtState.fDelayedAnnouncement) { - int rc2 = shClSvcBackendReportFormatsToGuest(pClient, g_ShClSvc.ExtState.fDelayedFormats, SHCLSOURCE_REMOTE); + int rc2 = shClSvcExtReportFormatsToGuest(pClient, g_ShClSvc.ExtState.fDelayedFormats, SHCLSOURCE_REMOTE); AssertRC(rc2); g_ShClSvc.ExtState.fDelayedAnnouncement = false; @@ -1288,7 +1629,7 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP */ shClSvcLock(); - int const rc = shClSvcBackendWriteData(pClient, &cmdCtx, uFormat, pvData, cbData); + int const rc = shClSvcExtWriteData(pClient, &cmdCtx, uFormat, pvData, cbData); shClSvcUnlock(); diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp new file mode 100644 index 000000000000..40ee21df8528 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp @@ -0,0 +1,545 @@ +/* $Id: VBoxSharedClipboardSvc-ext.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard Service - Service extension bridge handling. + */ + +/* + * Copyright (C) 2006-2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include +#include /* must be included before hgcmsvc.h */ + +#include +#include +#include + +#include +#include + +#include "VBoxSharedClipboardSvc-internal.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int shClSvcExtCall(uint32_t u32Function, void *pvParms, uint32_t cbParms); +static DECLCALLBACK(int) shClSvcExtCallback(uint32_t u32Function, uint32_t u32Format, + void *pvData, uint32_t cbData); + + +/** + * Stores a service-client transport in a service-extension parameter block. + * + * @param pParms Parameter block to update. + * @param pClient Service-owned client represented by the transport. + */ +static void shClSvcExtSetClient(PSHCLEXTPARMS pParms, PSHCLCLIENT pClient) +{ + SHCLTRANSPORT Transport; + shClSvcCreateTransport(pClient, &Transport); + ShClSvcExtSetTransport(pParms, &Transport); +} + + +/** + * Calls the registered Main service extension. + * + * @returns VBox status code returned by the extension. + * @retval VERR_NOT_SUPPORTED if no extension is registered. + * @param u32Function VBOX_CLIPBOARD_EXT_FN_XXX function number. + * @param pvParms Function parameters. Optional when @a cbParms is zero. + * @param cbParms Size of the function parameters in bytes. + * + * @thread The caller must ensure that the extension remains registered for the + * duration of the call. + */ +static int shClSvcExtCall(uint32_t u32Function, void *pvParms, uint32_t cbParms) +{ + LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); + + int rc; + if (g_ShClSvc.ExtState.pfnExtension) + rc = g_ShClSvc.ExtState.pfnExtension(g_ShClSvc.ExtState.pvExtension, u32Function, pvParms, cbParms); + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Checks whether a Main service extension is registered. + * + * @returns true if an extension is registered, false otherwise. + */ +bool shClSvcExtIsRegistered(void) +{ + return g_ShClSvc.ExtState.pfnExtension != NULL; +} + + +/** + * Requests process-wide native backend initialization from Main. + * + * @returns VBox status code returned by Main. + */ +int shClSvcExtBackendInit(void) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT, &parms, sizeof(parms)); +} + + +/** + * Connects a service client to the native backend owned by Main. + * + * @returns VBox status code returned by Main. + * @param pClient Service client to connect. + */ +int shClSvcExtBackendConnect(PSHCLCLIENT pClient) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT, &parms, sizeof(parms)); +} + + +/** + * Synchronizes a connected service client with the native backend. + * + * @returns VBox status code returned by Main. + * @param pClient Connected service client to synchronize. + */ +int shClSvcExtBackendSync(PSHCLCLIENT pClient) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC, &parms, sizeof(parms)); +} + + +/** + * Disconnects a service client from the native backend. + * + * @param pClient Connected service client to disconnect. + * + * @note The service-extension callback is synchronous. On return, Main no + * longer retains the service transport for this client. + */ +void shClSvcExtBackendDisconnect(PSHCLCLIENT pClient) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + + shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT, &parms, sizeof(parms)); +} + + +/** + * Requests process-wide native backend destruction from Main. + */ +void shClSvcExtBackendDestroy(void) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + + shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY, &parms, sizeof(parms)); +} + + +/** + * Reports a Shared Clipboard error to Main. + * + * @returns VBox status code returned by Main. + * @param pszId Error identifier. Must remain valid for the call. + * @param pszMsg Human-readable error text. Must remain valid for the call. + * @param rcError VBox status code describing the error. + */ +int shClSvcExtReportError(char *pszId, char *pszMsg, int rcError) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + parms.u.Error.pszId = pszId; + parms.u.Error.rc = rcError; + parms.u.Error.pszMsg = pszMsg; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_ERROR, &parms, sizeof(parms)); +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Queries Main for the native callbacks to attach to a transfer. + * + * @returns VBox status code returned by Main. + * @param pClient Service client owning the transfer. + * @param pCallbacks Where to return the callback table. The table is + * cleared before calling Main. + */ +int shClSvcExtQueryTransferCallbacks(PSHCLCLIENT pClient, PSHCLTRANSFERCALLBACKS pCallbacks) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER); + + RT_ZERO(*pCallbacks); + + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.TransferCallbacks.pClient = pClient; + parms.u.TransferCallbacks.pCallbacks = pCallbacks; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS, &parms, sizeof(parms)); +} + + +/** + * Notifies Main about a transfer status reply. + * + * @returns VBox status code returned by Main. + * @param pClient Service client owning the transfer. + * @param pTransfer Transfer whose status changed. + * @param enmSource Endpoint which supplied the reply. + * @param pReply Status reply. Valid for the duration of the call. + */ +int shClSvcExtNotifyTransferStatus(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + PSHCLREPLY pReply) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.FileTransferData.pClient = pClient; + parms.u.FileTransferData.pTransfer = pTransfer; + parms.u.FileTransferData.enmShClSource = enmSource; + parms.u.FileTransferData.pReply = pReply; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, &parms, sizeof(parms)); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +/** + * Reports remote clipboard formats to the guest through Main. + * + * @returns VBox status code returned by Main. + * @param pClient Service client to report to. + * @param fFormats Remote formats, VBOX_SHCL_FMT_XXX. + * @param enmSource Source of the format announcement. + * + * @thread Backend thread. + */ +int shClSvcExtReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + uint32_t const uMode = ShClSvcGetMode(); + if ( uMode != VBOX_SHCL_MODE_BIDIRECTIONAL + && uMode != VBOX_SHCL_MODE_HOST_TO_GUEST) + return VINF_SUCCESS; + + fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); + + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.ReportFormats.uFormats = fFormats; + parms.u.ReportFormats.pClient = pClient; + parms.u.ReportFormats.enmSource = enmSource; + + int const rc = shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST, &parms, sizeof(parms)); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reporting remote formats %#x to guest failed with %Rrc\n", fFormats, rc)); + return rc; +} + + +/** + * Reports guest clipboard formats to Main. + * + * @returns VBox status code returned by Main. + * @param pClient Service client reporting the formats. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ +int shClSvcExtReportFormatsToHost(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.ReportFormats.uFormats = fFormats; + parms.u.ReportFormats.pClient = pClient; + parms.u.ReportFormats.enmSource = SHCLSOURCE_INVALID; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, &parms, sizeof(parms)); +} + + +/** + * Reads native clipboard data through Main. + * + * @returns VBox status code returned by Main. + * @param pClient Service client requesting the data. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. Optional if @a cbData is zero. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required size. Optional. + */ +int shClSvcExtReadData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.ReadWriteData.uFormat = uFormat; + parms.u.ReadWriteData.pvData = pvData; + parms.u.ReadWriteData.cbData = cbData; + parms.u.ReadWriteData.cbActual = pcbActual ? *pcbActual : 0; + parms.u.ReadWriteData.pClient = pClient; + + /* Read clipboard data from the extension. */ + int rc = shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_DATA_READ, &parms, sizeof(parms)); + if ( RT_SUCCESS(rc) + && pcbActual) + *pcbActual = parms.u.ReadWriteData.cbActual; + + return rc; +} + + +/** + * Writes guest clipboard data through Main. + * + * @returns VBox status code returned by Main. + * @param pClient Service client supplying the data. + * @param pCmdCtx Guest command context identifying the reply. + * @param uFormat Clipboard format of the data. + * @param pvData Data buffer. Optional if @a cbData is zero. + * @param cbData Data size in bytes. + */ +int shClSvcExtWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.ReadWriteData.uFormat = uFormat; + parms.u.ReadWriteData.pvData = pvData; + parms.u.ReadWriteData.cbData = cbData; + parms.u.ReadWriteData.pClient = pClient; + parms.u.ReadWriteData.pCmdCtx = pCmdCtx; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof(parms)); +} + + +/** + * Requests guest clipboard data for the chained remote-desktop extension. + * + * @returns VBox status code returned by Main. + * @param pClient Connected service client. + * @param uFormat Clipboard format to request. + * @param pvData Destination buffer. + * @param cbData Destination buffer size in bytes. + */ +static int shClSvcExtReadVrdeData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.ReadWriteData.uFormat = uFormat; + parms.u.ReadWriteData.pvData = pvData; + parms.u.ReadWriteData.cbData = cbData; + parms.u.ReadWriteData.pClient = pClient; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE, &parms, sizeof(parms)); +} + + +/** + * Handles reverse calls from the chained remote-desktop extension. + * + * @returns VBox status code. + * @param u32Function VBOX_CLIPBOARD_EXT_FN_XXX function number. + * @param u32Format Clipboard format associated with the request. + * @param pvData Optional data buffer. + * @param cbData Data buffer size in bytes. + */ +static DECLCALLBACK(int) shClSvcExtCallback(uint32_t u32Function, uint32_t u32Format, + void *pvData, uint32_t cbData) +{ + PSHCLCLIENT pClient = NULL; + + shClSvcLock(); + if ( g_ShClSvc.pActiveClient + && g_ShClSvc.ExtState.uClientID == g_ShClSvc.pActiveClient->State.uClientID) + { + pClient = g_ShClSvc.pActiveClient; + if (g_ShClSvc.ExtState.cCallbacks++ == 0) + { + int const rcReset = RTSemEventMultiReset(g_ShClSvc.ExtState.hCallbacksDone); + AssertFatalMsgRC(rcReset, ("Resetting the Shared Clipboard callback drain event failed with %Rrc\n", + rcReset)); + } + } + shClSvcUnlock(); + + int rc = VERR_NOT_FOUND; + if (pClient) + { + switch (u32Function) + { + case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: + shClSvcLock(); + if (!g_ShClSvc.ExtState.fReadingData) + { + shClSvcUnlock(); + rc = shClSvcExtReportFormatsToGuest(pClient, u32Format, SHCLSOURCE_REMOTE); + } + else + { + g_ShClSvc.ExtState.fDelayedAnnouncement = true; + g_ShClSvc.ExtState.fDelayedFormats = u32Format; + shClSvcUnlock(); + rc = VINF_SUCCESS; + } + break; + + case VBOX_CLIPBOARD_EXT_FN_DATA_READ: + rc = shClSvcExtReadVrdeData(pClient, u32Format, pvData, cbData); + break; + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + shClSvcLock(); + Assert(g_ShClSvc.ExtState.cCallbacks > 0); + if (g_ShClSvc.ExtState.cCallbacks > 0 && --g_ShClSvc.ExtState.cCallbacks == 0) + { + int const rcSignal = RTSemEventMultiSignal(g_ShClSvc.ExtState.hCallbacksDone); + AssertFatalMsgRC(rcSignal, ("Signalling the Shared Clipboard callback drain event failed with %Rrc\n", + rcSignal)); + } + shClSvcUnlock(); + } + + return rc; +} + + +/** + * Disables new reverse callbacks, drains callbacks already in progress, + * destroys the native backend while the extension remains callable, and then + * clears the matching extension registration. + * + * @returns VBox status code. + */ +int shClSvcExtUnregisterAndDestroy(void) +{ + shClSvcLock(); + PFNHGCMSVCEXT const pfnExtension = g_ShClSvc.ExtState.pfnExtension; + void * const pvExtension = g_ShClSvc.ExtState.pvExtension; + g_ShClSvc.ExtState.uClientID = 0; + shClSvcUnlock(); + + if (!pfnExtension) + return VINF_SUCCESS; + + /* Stop new reverse callbacks before waiting for calls which already + captured the active service client. */ + SHCLEXTPARMS parms; + RT_ZERO(parms); + int rc = pfnExtension(pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); + AssertFatalMsgRC(rc, ("Unregistering the Shared Clipboard extension callback failed with %Rrc\n", rc)); + + int const rcWait = RTSemEventMultiWait(g_ShClSvc.ExtState.hCallbacksDone, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(rcWait, ("Waiting for Shared Clipboard extension callbacks failed with %Rrc\n", rcWait)); + + /* Console unregisters the extension before HGCM disconnects its client. */ + shClSvcExtBackendDestroy(); + + shClSvcLock(); + if ( g_ShClSvc.ExtState.pfnExtension == pfnExtension + && g_ShClSvc.ExtState.pvExtension == pvExtension) + { + g_ShClSvc.ExtState.pvExtension = NULL; + g_ShClSvc.ExtState.pfnExtension = NULL; + } + shClSvcUnlock(); + + LogRel2(("Shared Clipboard: de-registered service extension\n")); + return rc; +} + + +/** + * Registers or unregisters the Main Shared Clipboard service extension. + * + * @returns VBox status code. + * @param pvService HGCM service instance. Not used. + * @param pfnExtension Extension callback to register, or NULL to unregister. + * @param pvExtension Opaque callback argument owned by Main. + */ +DECLCALLBACK(int) shClSvcRegisterExtension(void *pvService, PFNHGCMSVCEXT pfnExtension, void *pvExtension) +{ + RT_NOREF(pvService); + LogFlowFunc(("pfnExtension=%p\n", pfnExtension)); + + if (pfnExtension) + { + shClSvcLock(); + g_ShClSvc.ExtState.pfnExtension = pfnExtension; + g_ShClSvc.ExtState.pvExtension = pvExtension; + shClSvcUnlock(); + + SHCLEXTPARMS parms; + RT_ZERO(parms); + parms.u.SetCallback.pfnCallback = shClSvcExtCallback; + int const rc = pfnExtension(pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); + if (RT_FAILURE(rc)) + { + shClSvcLock(); + if ( g_ShClSvc.ExtState.pfnExtension == pfnExtension + && g_ShClSvc.ExtState.pvExtension == pvExtension) + { + g_ShClSvc.ExtState.pvExtension = NULL; + g_ShClSvc.ExtState.pfnExtension = NULL; + } + shClSvcUnlock(); + return rc; + } + + LogRel2(("Shared Clipboard: registered service extension\n")); + return rc; + } + + return shClSvcExtUnregisterAndDestroy(); +} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h index 7eb9b970cc7c..cd7715602daf 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-internal.h 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-internal.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal service instance state. */ @@ -34,10 +34,11 @@ #include #include +#include /** - * State of the optional service extension installed by a host component. + * State of the service extension which bridges the HGCM service to Main. */ typedef struct SHCLEXTSTATE { @@ -45,6 +46,12 @@ typedef struct SHCLEXTSTATE PFNHGCMSVCEXT pfnExtension; /** Opaque extension-provided data. */ void *pvExtension; + /** HGCM client ID currently assigned to the extension. */ + uint32_t uClientID; + /** Number of in-flight reverse callbacks using the active client. */ + uint32_t cCallbacks; + /** Signalled while no reverse callback is using the active client. */ + RTSEMEVENTMULTI hCallbacksDone; /** Whether the host service is reading clipboard data currently. */ bool fReadingData; /** Whether the service extension announced formats while data was read. */ @@ -59,12 +66,8 @@ typedef struct SHCLEXTSTATE */ typedef struct SHCLSERVICE { - /** The backend instance data. Only one backend at a time is supported currently. */ - SHCLBACKEND Backend; /** HGCM service helper table. */ PVBOXHGCMSVCHELPERS pHelpers; - /** HGCM service function table. */ - VBOXHGCMSVCFNTABLE *pTable; /** Service-global critical section. */ RTCRITSECT CritSect; /** Current Shared Clipboard mode. */ @@ -84,7 +87,6 @@ typedef struct SHCLSERVICE SHCLSERVICE() : pHelpers(NULL) - , pTable(NULL) , uMode(VBOX_SHCL_MODE_OFF) , idNextSession(1) #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS @@ -97,7 +99,6 @@ typedef struct SHCLSERVICE #endif ) { - RT_ZERO(Backend); RT_ZERO(CritSect); RT_ZERO(ExtState); } @@ -153,17 +154,32 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP int shClSvcClientMsgError(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[], int *pRc); /** @} */ -/** @name Backend and extension bridge handling. +/** @name Opaque service-to-Main transport. * @{ */ -int shClSvcBackendInit(VBOXHGCMSVCFNTABLE *pTable); -int shClSvcBackendConnect(PSHCLCLIENT pClient); -int shClSvcBackendSync(PSHCLCLIENT pClient); -void shClSvcBackendDisconnect(PSHCLCLIENT pClient); -void shClSvcBackendDestroy(void); -int shClSvcBackendReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource); -int shClSvcBackendReportFormatsToHost(PSHCLCLIENT pClient, SHCLFORMATS fFormats); -int shClSvcBackendReadData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); -int shClSvcBackendWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); +void shClSvcCreateTransport(PSHCLCLIENT pClient, PSHCLTRANSPORT pTransport); +/** @} */ + +/** @name Service extension bridge handling. + * @{ */ +bool shClSvcExtIsRegistered(void); +int shClSvcExtBackendInit(void); +int shClSvcExtBackendConnect(PSHCLCLIENT pClient); +int shClSvcExtBackendSync(PSHCLCLIENT pClient); +void shClSvcExtBackendDisconnect(PSHCLCLIENT pClient); +void shClSvcExtBackendDestroy(void); +/** Disables and drains reverse callbacks, destroys the backend while the + * extension remains callable, then clears the matching registration. */ +int shClSvcExtUnregisterAndDestroy(void); +int shClSvcExtReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource); +int shClSvcExtReportFormatsToHost(PSHCLCLIENT pClient, SHCLFORMATS fFormats); +int shClSvcExtReadData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); +int shClSvcExtWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); +int shClSvcExtReportError(char *pszId, char *pszMsg, int rcError); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int shClSvcExtQueryTransferCallbacks(PSHCLCLIENT pClient, PSHCLTRANSFERCALLBACKS pCallbacks); +int shClSvcExtNotifyTransferStatus(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + PSHCLREPLY pReply); +#endif DECLCALLBACK(int) shClSvcRegisterExtension(void *pvService, PFNHGCMSVCEXT pfnExtension, void *pvExtension); /** @} */ diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp index 836c918a8300..043e31f31941 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal code for transfer (list) handling. */ @@ -719,9 +719,18 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra * having a pending transfer around. Report back the new transfer ID to the guest then. */ if (pTransfer == NULL) /* Must not exist yet. */ { - rc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, - NIL_SHCLTRANSFERID /* Creates a new transfer ID */, - &pTransfer); + SHCLTRANSFERCALLBACKS Callbacks; + rc = shClSvcExtQueryTransferCallbacks(pClient, &Callbacks); + if (rc == VERR_NOT_SUPPORTED) + { + RT_ZERO(Callbacks); + rc = VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + rc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, + &Callbacks, + NIL_SHCLTRANSFERID /* Creates a new transfer ID */, + &pTransfer); if (RT_SUCCESS(rc)) { fReleaseCreatedTransfer = true; @@ -819,21 +828,16 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra LogRelMax(16, ("Shared Clipboard: Guest reported error %Rrc for transfer %RU16\n", pReply->rc, pTransfer->State.uID)); - if (g_ShClSvc.ExtState.pfnExtension) + if (shClSvcExtIsRegistered()) { - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.Error.rc = pReply->rc; - parms.u.Error.pszMsg = RTStrAPrintf2("Guest reported error %Rrc for transfer %RU16", /** @todo Make the error messages more fine-grained based on rc. */ - pReply->rc, pTransfer->State.uID); - AssertPtrBreakStmt(parms.u.Error.pszMsg, rc = VERR_NO_MEMORY); + char *pszMsg = RTStrAPrintf2("Guest reported error %Rrc for transfer %RU16", /** @todo Make the error messages more fine-grained based on rc. */ + pReply->rc, pTransfer->State.uID); + AssertPtrBreakStmt(pszMsg, rc = VERR_NO_MEMORY); - g_ShClSvc.ExtState.pfnExtension(g_ShClSvc.ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_ERROR, - &parms, sizeof(parms)); + (void) shClSvcExtReportError(NULL, pszMsg, pReply->rc); - RTStrFree(parms.u.Error.pszMsg); - parms.u.Error.pszMsg = NULL; + RTStrFree(pszMsg); + pszMsg = NULL; } rc = ShClTransferError(pTransfer, pReply->rc); @@ -849,24 +853,10 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra } } - /* Tell the backend. */ - if (g_ShClSvc.ExtState.pfnExtension) + /* Notify the service extension. */ + if (shClSvcExtIsRegistered()) { - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.FileTransferData.pClient = pClient; - parms.u.FileTransferData.pTransfer = pTransfer; - parms.u.FileTransferData.enmShClSource = SHCLSOURCE_REMOTE; - parms.u.FileTransferData.pReply = pReply; - - /* The Main backend calls: ShClBackendTransferHandleStatusReply(pClient->pBackend, pClient, - * pTransfer, SHCLSOURCE_REMOTE, pReply->u.TransferStatus.uStatus, - * pReply->rc); - */ - int rc2 = g_ShClSvc.ExtState.pfnExtension(g_ShClSvc.ExtState.pvExtension, - VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, - &parms, sizeof(parms)); + int rc2 = shClSvcExtNotifyTransferStatus(pClient, pTransfer, SHCLSOURCE_REMOTE, pReply); if (RT_SUCCESS(rc)) rc = rc2; } @@ -964,9 +954,9 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, { RT_NOREF(callHandle, aParms, tsArrival); - LogFlowFunc(("uClient=%RU32, u32Function=%RU32 (%s), cParms=%RU32, pfnExtension=%p\n", + LogFlowFunc(("uClient=%RU32, u32Function=%RU32 (%s), cParms=%RU32, fExtRegistered=%RTbool\n", pClient->State.uClientID, u32Function, ShClSvcGuestMsgToStr(u32Function), cParms, - g_ShClSvc.ExtState.pfnExtension)); + shClSvcExtIsRegistered())); uint64_t const fGuestFeatures0 = ShClSvcClientGetGuestFeatures0(pClient); if ( u32Function > VBOX_SHCL_GUEST_FN_LAST diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h index 810a212c1bf9..26b4ecee4675 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.h 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal header for transfer (list) handling. */ @@ -36,7 +36,8 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE call int ShClSvcTransferMsgHostHandler(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); /** Returns a retained transfer in @a ppTransfer; the caller must release it. */ -int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer); +int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, + PSHCLTRANSFERCALLBACKS pCallbacks, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer); void ShClSvcTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); void ShClSvcTransferDestroyById(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer); void ShClSvcTransferDestroyByIdEx(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer, bool fNotifyGuest); @@ -47,4 +48,3 @@ int ShClSvcTransferStart(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); void shClSvcTransferDestroyAll(PSHCLCLIENT pClient); #endif /* !VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_transfers_h */ - diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp new file mode 100644 index 000000000000..1d6ad12ea28d --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp @@ -0,0 +1,689 @@ +/* $Id: VBoxSharedClipboardSvc-transport.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard Service - Opaque Main transport implementation. + */ + +/* + * Copyright (C) 2019-2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include + +#include +#include +#include + +#include "VBoxSharedClipboardSvc-internal.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include "VBoxSharedClipboardSvc-transfers.h" +#endif + + +/** + * Reports native clipboard formats to a guest through the Shared Clipboard HGCM queue. + * + * @returns VBox status code. + * @param hClient Opaque clipboard client to report to. + * @param fFormats Host formats, VBOX_SHCL_FMT_XXX. + * @param pfReported Where to return the filtered formats. Optional. + */ +static DECLCALLBACK(int) shClSvcOpReportFormatsToGuest(SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + SHCLFORMATS *pfReported) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); + if (pfReported) + *pfReported = fFormats; + + uint32_t const uMode = ShClSvcClientGetMode(pClient); + if ( uMode != VBOX_SHCL_MODE_BIDIRECTIONAL + && uMode != VBOX_SHCL_MODE_HOST_TO_GUEST) + return VINF_NO_CHANGE; + + PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); + if (!pMsg) + return VERR_NO_MEMORY; + + HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + HGCMSvcSetU32(&pMsg->aParms[1], fFormats); + + ShClSvcClientLock(pClient); + int const vrc = shClSvcClientMsgAddAndWakeupClient(pClient, pMsg); + ShClSvcClientUnlock(pClient); + return vrc; +} + + +/** + * Validates and retains a pending guest-data event. + * + * @returns VBox status code. Stale replies for expired event IDs are ignored; + * in that case @a ppEvent is set to NULL and VINF_SUCCESS is returned. + * @param hClient Opaque client the data was received from. + * @param uContextId HGCM context ID identifying the pending event. + * @param uFormat Clipboard format of data received. + * @param ppEvent Where to return the retained event. Must be + * released with ShClEventRelease(). + * + * @thread Backend thread. + */ +static DECLCALLBACK(int) shClSvcOpRetainGuestDataEvent(SHCLCLIENTHANDLE hClient, uint64_t uContextId, + SHCLFORMAT uFormat, PSHCLEVENT *ppEvent) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + LogFlowFuncEnter(); + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(ppEvent, VERR_INVALID_POINTER); + *ppEvent = NULL; + + if (!ShClFormatIsValid(uFormat)) + { + LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); + return VERR_INVALID_PARAMETER; + } + + SHCLSESSIONID const idSession = VBOX_SHCL_CONTEXTID_GET_SESSION(uContextId); + SHCLEVENTSOURCEID const idEventSource = VBOX_SHCL_CONTEXTID_GET_TRANSFER(uContextId); + const SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(uContextId); + if ( idEvent == 0 + || idEvent == NIL_SHCLEVENTID) + { + LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid event %#x in context ID %#RX64\n", + idEvent, uContextId)); + return VERR_WRONG_ORDER; + } + if ( idSession != pClient->State.uSessionID + || idEventSource != pClient->EventSrc.uID) + { + LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with mismatching context ID %#RX64" + " (session %#x/%#x, event source %#x/%#x)\n", + uContextId, idSession, pClient->State.uSessionID, + idEventSource, pClient->EventSrc.uID)); + return VERR_INVALID_CONTEXT; + } + + PSHCLEVENT pEvent = ShClEventSourceRetainFromId(&pClient->EventSrc, idEvent); + if (!RT_VALID_PTR(pEvent)) + { + LogRelMax2(16, ("Shared Clipboard: Ignoring late guest clipboard data for expired event %#x\n", idEvent)); + return VINF_SUCCESS; + } + if (pEvent->uUser != uFormat) + { + LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data format %#x for event %#x, expected %#x\n", + uFormat, idEvent, pEvent->uUser)); + ShClEventRelease(pEvent); + return VERR_INVALID_CONTEXT; + } + + *ppEvent = pEvent; + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + + +/** + * Signals a retained guest-data event with clipboard data received from the guest. + * + * @returns VBox status code. + * @param pEvent Retained event to signal. + * @param idEvent Event ID to use for the optional payload wrapper. + * @param pvData Pointer to clipboard data received. This can be + * NULL if @a cbData is zero. + * @param cbData Size (in bytes) of clipboard data received. + * This can be zero. + * + * @thread Backend thread. + */ +static DECLCALLBACK(int) shClSvcOpSignalGuestDataEvent(SHCLCLIENTHANDLE hClient, PSHCLEVENT pEvent, + SHCLEVENTID idEvent, void *pvData, uint32_t cbData) +{ + RT_NOREF(hClient); + LogFlowFuncEnter(); + + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + if (cbData > 0) + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + + /* + * Make a copy of the data so we can attach it to the signal. + * + * Note! We still signal the waiter should we run out of memory, + * because otherwise it will be stuck waiting. + */ + int vrc = VINF_SUCCESS; + PSHCLEVENTPAYLOAD pPayload = NULL; + if (cbData > 0) + vrc = ShClPayloadCreateDupData(idEvent, pvData, cbData, &pPayload); + + /* + * Signal the event. + */ + int vrc2 = ShClEventSignalEx(pEvent, vrc, pPayload); + if (RT_FAILURE(vrc2)) + { + vrc = vrc2; + ShClPayloadDestroy(pPayload); + LogRel(("Shared Clipboard: Signalling of guest clipboard data to the host failed: %Rrc\n", vrc)); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + + +/** + * Validates a guest-data reply and retains its pending event as a token. + * + * @returns VBox status code. + * @param hClient Opaque service client receiving the reply. + * @param pCmdCtx Guest command context containing the reply context ID. + * @param uFormat Clipboard format carried by the reply. + * @param phToken Where to return the retained token. On success, + * pass it exactly once to shClSvcOpGuestDataComplete() + * or shClSvcOpGuestDataCancel(). + */ +static DECLCALLBACK(int) shClSvcOpGuestDataBegin(SHCLCLIENTHANDLE hClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken) +{ + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(phToken, VERR_INVALID_POINTER); + *phToken = NULL; + PSHCLEVENT pEvent = NULL; + int vrc = shClSvcOpRetainGuestDataEvent(hClient, pCmdCtx->uContextID, uFormat, &pEvent); + if (RT_SUCCESS(vrc) && pEvent) + *phToken = (SHCLGUESTDATATOKEN)pEvent; + return vrc; +} + + +/** + * Signals and releases a retained guest-data reply token. + * + * @returns VBox status code from signalling the pending event. + * @param hClient Opaque service client owning the token. + * @param hToken Token returned by shClSvcOpGuestDataBegin(). + * @param pvData Reply data. Optional if @a cbData is zero. + * @param cbData Reply data size in bytes. + */ +static DECLCALLBACK(int) shClSvcOpGuestDataComplete(SHCLCLIENTHANDLE hClient, + SHCLGUESTDATATOKEN hToken, + void const *pvData, uint32_t cbData) +{ + PSHCLEVENT const pEvent = (PSHCLEVENT)hToken; + AssertPtrReturn(pEvent, VERR_INVALID_HANDLE); + + int const vrc = shClSvcOpSignalGuestDataEvent(hClient, pEvent, pEvent->idEvent, + (void *)pvData, cbData); + ShClEventRelease(pEvent); + return vrc; +} + + +/** + * Releases a retained guest-data reply token without signalling its event. + * + * @param hClient Opaque service client owning the token. + * @param hToken Token returned by shClSvcOpGuestDataBegin(). + */ +static DECLCALLBACK(void) shClSvcOpGuestDataCancel(SHCLCLIENTHANDLE hClient, + SHCLGUESTDATATOKEN hToken) +{ + RT_NOREF(hClient); + PSHCLEVENT const pEvent = (PSHCLEVENT)hToken; + AssertPtrReturnVoid(pEvent); + ShClEventRelease(pEvent); +} + + +/** + * Reads clipboard data from the guest, asynchronous version. + * + * @returns VBox status code. + * @param hClient Opaque client to request data from. + * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). + * @param ppEvent Where to return the event for waiting for new data on success. + * Must be released by the caller with ShClEventRelease(). Optional. + * + * @thread On X11: Called from the X11 event thread. + * @thread On Windows: Called from the Windows event thread. + * + * @note This will locally initialize a transfer if VBOX_SHCL_FMT_URI_LIST is being requested from the guest. + */ +static DECLCALLBACK(int) shClSvcOpReadDataFromGuestAsync(SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + PSHCLEVENT *ppEvent) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + LogFlowFunc(("fFormats=%#x\n", fFormats)); + + if (ppEvent) + *ppEvent = NULL; + + SHCLFORMATS const fSupportedFormats = VBOX_SHCL_FMT_UNICODETEXT + | VBOX_SHCL_FMT_BITMAP + | VBOX_SHCL_FMT_HTML +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_FMT_URI_LIST +#endif + ; + if ( fFormats == VBOX_SHCL_FMT_NONE + || (fFormats & ~fSupportedFormats)) + { + LogRelMax2(16, ("Shared Clipboard: Rejecting unsupported guest clipboard data request formats %#x\n", fFormats)); + return VERR_NOT_SUPPORTED; + } + if ( ppEvent + && (fFormats & (fFormats - 1)) != 0) + { + LogRelMax2(16, ("Shared Clipboard: Rejecting multi-format guest clipboard data request %#x with single event output\n", + fFormats)); + return VERR_INVALID_PARAMETER; + } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if ( (fFormats & VBOX_SHCL_FMT_URI_LIST) + && !shClSvcClientTransfersAreAllowed(pClient)) + { + LogRelMax2(16, ("Shared Clipboard: Rejecting host URI-list request without enabled and negotiated transfers\n")); + return VERR_ACCESS_DENIED; + } +#endif + + int vrc = VERR_NOT_SUPPORTED; + + /* Generate a separate message for every (valid) format we support. */ + while (fFormats) + { + /* Pick the next format to get from the mask: */ + /** @todo Make format reporting precedence configurable? */ + SHCLFORMAT fFormat; + if (fFormats & VBOX_SHCL_FMT_UNICODETEXT) + fFormat = VBOX_SHCL_FMT_UNICODETEXT; + else if (fFormats & VBOX_SHCL_FMT_BITMAP) + fFormat = VBOX_SHCL_FMT_BITMAP; + else if (fFormats & VBOX_SHCL_FMT_HTML) + fFormat = VBOX_SHCL_FMT_HTML; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (fFormats & VBOX_SHCL_FMT_URI_LIST) + fFormat = VBOX_SHCL_FMT_URI_LIST; +#endif + else + { + vrc = VERR_NOT_SUPPORTED; + break; + } + + /* Remove it from the mask. */ + fFormats &= ~fFormat; + + if (LogRelIs2Enabled()) + { + char *pszFmt = ShClFormatsToStrA(fFormat); + LogRel2(("Shared Clipboard: Requesting guest clipboard data in format %#x/'%s'\n", + fFormat, pszFmt ? pszFmt : "")); + RTStrFree(pszFmt); + } + /* + * Allocate messages, one for each format. + */ + uint64_t const fGuestFeatures0 = ShClSvcClientGetGuestFeatures0(pClient); + PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, + fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID + ? VBOX_SHCL_HOST_MSG_READ_DATA_CID : VBOX_SHCL_HOST_MSG_READ_DATA, + 2); + if (pMsg) + { + ShClSvcClientLock(pClient); + + PSHCLEVENT pEvent; + vrc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(vrc)) + { + LogFlowFunc(("fFormats=%#x -> fFormat=%#x, idEvent=%#x\n", fFormats, fFormat, pEvent->idEvent)); + pEvent->uUser = fFormat; + + const uint64_t uCID = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, pEvent->idEvent); + + vrc = VINF_SUCCESS; + + /* Save the context ID in our legacy cruft if we have to deal with old(er) Guest Additions (< 6.1). */ + if (!(fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + { + AssertStmt(pClient->Legacy.cCID < 4096, vrc = VERR_TOO_MUCH_DATA); + if (RT_SUCCESS(vrc)) + { + PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); + if (pCID) + { + pCID->uCID = uCID; + pCID->enmType = 0; /* Not used yet. */ + pCID->uFormat = fFormat; + RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); + pClient->Legacy.cCID++; + } + else + vrc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(vrc)) + { + /* + * Format the message. + */ + if (pMsg->idMsg == VBOX_SHCL_HOST_MSG_READ_DATA_CID) + HGCMSvcSetU64(&pMsg->aParms[0], uCID); + else + HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_READ_DATA); + HGCMSvcSetU32(&pMsg->aParms[1], fFormat); + + ShClSvcClientMsgAdd(pClient, pMsg, true /* fAppend */); + /* Wake up the client to let it know that there are new messages. */ + ShClSvcClientWakeup(pClient); + + /* Return event to caller. */ + if (ppEvent) + *ppEvent = pEvent; + } + + /* Remove event from list if caller did not request event handle or in case + * of failure (in this case caller should not release event). */ + if ( RT_FAILURE(vrc) + || !ppEvent) + { + ShClEventRelease(pEvent); + pEvent = NULL; + } + } + else + vrc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + if (RT_FAILURE(vrc)) + ShClSvcClientMsgFree(pClient, pMsg); + + ShClSvcClientUnlock(pClient); + } + else + vrc = VERR_NO_MEMORY; + + if (RT_FAILURE(vrc)) + break; + } + + if (RT_FAILURE(vrc)) + LogRel(("Shared Clipboard: Requesting guest clipboard data failed with %Rrc\n", vrc)); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Reads clipboard data from the guest. + * + * @returns VBox status code. + * @retval VERR_SHCLPB_NO_DATA if no clipboard data is available. + * @param hClient Opaque client to request data from. + * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). + * @param ppv Where to return the allocated data read. + * Must be free'd by the caller. + * @param pcb Where to return number of bytes read. + */ +static DECLCALLBACK(int) shClSvcOpReadDataFromGuest(SHCLCLIENTHANDLE hClient, SHCLFORMAT fFormats, + void **ppv, uint32_t *pcb) +{ + AssertPtrReturn(ppv, VERR_INVALID_POINTER); + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + /* Request data from the guest and wait for data to arrive. */ + PSHCLEVENT pEvent; + int vrc = shClSvcOpReadDataFromGuestAsync(hClient, fFormats, &pEvent); + if (RT_SUCCESS(vrc)) + { + PSHCLEVENTPAYLOAD pPayload; + vrc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload); + if (RT_SUCCESS(vrc)) + { + if ( pPayload + && pPayload->cbData) + { + *ppv = pPayload->pvData; + *pcb = pPayload->cbData; + + LogFlowFunc(("pv=%p, cb=%RU32\n", pPayload->pvData, pPayload->cbData)); + + pPayload->pvData = NULL; + pPayload->cbData = 0; + ShClPayloadDestroy(pPayload); + } + else + { + ShClPayloadDestroy(pPayload); + vrc = VERR_SHCLPB_NO_DATA; + } + } + + ShClEventRelease(pEvent); + } + + if ( RT_FAILURE(vrc) + && vrc != VERR_SHCLPB_NO_DATA) + LogRel(("Shared Clipboard: Reading data from guest failed with %Rrc\n", vrc)); + return vrc; +} + + +/** + * Applies transfer policy and compatibility rules to a clipboard format announcement. + * + * @returns VBox status code. + * @param hClient Opaque service client whose policy to apply. + * @param fHostToGuest Whether the formats flow from host to guest. + * @param fFormats Input formats, VBOX_SHCL_FMT_XXX. + * @param pfFiltered Where to return the filtered format mask. + */ +static DECLCALLBACK(int) shClSvcOpFilterFormats(SHCLCLIENTHANDLE hClient, bool fHostToGuest, + SHCLFORMATS fFormats, SHCLFORMATS *pfFiltered) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pfFiltered, VERR_INVALID_POINTER); + *pfFiltered = shClSvcHandleFormats(fHostToGuest, pClient, fFormats); + return VINF_SUCCESS; +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Retains a transfer selected by its context-local ID. + * + * @returns Retained transfer, or NULL if it was not found. + * @param hClient Opaque service client owning the transfer. + * @param idTransfer Transfer ID to look up. + * + * @note The caller must release a returned transfer with ShClTransferRelease(). + */ +static DECLCALLBACK(PSHCLTRANSFER) shClSvcOpTransferGetByIdRetained(SHCLCLIENTHANDLE hClient, + SHCLTRANSFERID idTransfer) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, NULL); + return ShClTransferCtxGetTransferByIdRetained(&pClient->Transfers.Ctx, idTransfer); +} + + +/** + * Retains a transfer selected by its complete generation key. + * + * @returns Retained transfer, or NULL if the key is stale or unknown. + * @param hClient Opaque service client owning the transfer. + * @param idSession Service session ID. + * @param idTransfer Transfer ID. + * @param uGeneration Transfer generation. + * + * @note The caller must release a returned transfer with ShClTransferRelease(). + */ +static DECLCALLBACK(PSHCLTRANSFER) shClSvcOpTransferGetByKeyRetained(SHCLCLIENTHANDLE hClient, + SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, NULL); + return ShClTransferCtxGetTransferByKeyRetained(&pClient->Transfers.Ctx, idSession, idTransfer, uGeneration); +} + + +/** + * Creates a retained service-owned transfer. + * + * @returns VBox status code. + * @param hClient Opaque service client which will own the transfer. + * @param enmDir Transfer direction. + * @param enmSource Transfer source. + * @param pCallbacks Callback table to copy. Optional. + * @param idTransfer Requested transfer ID, or NIL_SHCLTRANSFERID. + * @param ppTransfer Where to return the retained transfer. Optional. + */ +static DECLCALLBACK(int) shClSvcOpTransferCreate(SHCLCLIENTHANDLE hClient, SHCLTRANSFERDIR enmDir, + SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, + SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) +{ + return ShClSvcTransferCreate((PSHCLCLIENT)hClient, enmDir, enmSource, pCallbacks, idTransfer, ppTransfer); +} + + +/** + * Initializes a service-owned transfer. + * + * @returns VBox status code. + * @param hClient Opaque service client owning the transfer. + * @param pTransfer Transfer to initialize. + */ +static DECLCALLBACK(int) shClSvcOpTransferInit(SHCLCLIENTHANDLE hClient, PSHCLTRANSFER pTransfer) +{ + return ShClSvcTransferInit((PSHCLCLIENT)hClient, pTransfer); +} + + +/** + * Destroys a service-owned transfer selected by ID. + * + * @param hClient Opaque service client owning the transfer. + * @param idTransfer Transfer ID to destroy. + */ +static DECLCALLBACK(void) shClSvcOpTransferDestroyById(SHCLCLIENTHANDLE hClient, SHCLTRANSFERID idTransfer) +{ + ShClSvcTransferDestroyById((PSHCLCLIENT)hClient, idTransfer); +} + + +/** + * Destroys all transfers owned by a disconnecting service client. + * + * @param hClient Opaque service client whose transfers to destroy. + */ +static DECLCALLBACK(void) shClSvcOpTransferDestroyAll(SHCLCLIENTHANDLE hClient) +{ + shClSvcTransferDestroyAll((PSHCLCLIENT)hClient); +} + + +/** + * Initializes a provider which obtains transfer data from the guest. + * + * @returns VBox status code. + * @param hClient Opaque service client used by the provider callbacks. + * @param pProvider Provider structure to initialize. + */ +static DECLCALLBACK(int) shClSvcOpTransferProviderInitGuest(SHCLCLIENTHANDLE hClient, + PSHCLTXPROVIDER pProvider) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pProvider, VERR_INVALID_POINTER); + + RT_ZERO(*pProvider); + pProvider->Interface.pfnRootListRead = ShClSvcTransferIfaceGHRootListRead; + pProvider->Interface.pfnListOpen = ShClSvcTransferIfaceGHListOpen; + pProvider->Interface.pfnListClose = ShClSvcTransferIfaceGHListClose; + pProvider->Interface.pfnListHdrRead = ShClSvcTransferIfaceGHListHdrRead; + pProvider->Interface.pfnListEntryRead = ShClSvcTransferIfaceGHListEntryRead; + pProvider->Interface.pfnObjOpen = ShClSvcTransferIfaceGHObjOpen; + pProvider->Interface.pfnObjClose = ShClSvcTransferIfaceGHObjClose; + pProvider->Interface.pfnObjRead = ShClSvcTransferIfaceGHObjRead; + pProvider->enmSource = SHCLSOURCE_REMOTE; + pProvider->pvUser = pClient; + pProvider->cbUser = sizeof(*pClient); + return VINF_SUCCESS; +} +#endif + + +/** The immutable operation table shared by all service-owned clients. */ +static SHCLSVCOPS const s_ShClSvcOps = +{ + sizeof(s_ShClSvcOps), + shClSvcOpFilterFormats, + shClSvcOpReportFormatsToGuest, + shClSvcOpReadDataFromGuestAsync, + shClSvcOpReadDataFromGuest, + shClSvcOpGuestDataBegin, + shClSvcOpGuestDataComplete, + shClSvcOpGuestDataCancel, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcOpTransferGetByIdRetained, + shClSvcOpTransferGetByKeyRetained, + shClSvcOpTransferCreate, + shClSvcOpTransferInit, + shClSvcOpTransferDestroyById, + shClSvcOpTransferDestroyAll, + shClSvcOpTransferProviderInitGuest, +#endif +}; + + +/** + * Creates the synchronous, non-owning Main transport for a service client. + * + * @param pClient Service-owned client represented by the transport. + * @param pTransport Where to return the transport value. + * + * @note The transport is valid only while the service client remains connected. + * Callers must not retain it past the synchronous disconnect callback. + */ +void shClSvcCreateTransport(PSHCLCLIENT pClient, PSHCLTRANSPORT pTransport) +{ + AssertPtrReturnVoid(pClient); + AssertPtrReturnVoid(pTransport); + pTransport->hClient = (SHCLCLIENTHANDLE)pClient; + pTransport->pOps = &s_ShClSvcOps; +} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp index 0012585d6c7b..c5de7455e5fa 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host service entry points. */ @@ -33,9 +33,10 @@ * and VBoxService depending on the OS, with code shared between host and guest * under src/VBox/GuestHost/SharedClipboard/. * - * The service is split into a platform-independent core and platform-specific - * backends. The backends also make use of the aforementioned shared guest/host - * clipboard code, to avoid code duplication. + * The service implements the HGCM protocol and transport state. Host integration + * and the platform-specific backends live in Main (VBoxC) and are reached through + * the registered service extension. The backends also use the aforementioned + * shared guest/host clipboard code to avoid code duplication. * * @section sec_hostclip_guest_proto The guest communication protocol * @@ -68,8 +69,7 @@ * used after the host has requested data from the guest. * * - * @section sec_hostclip_backend_proto The communication protocol with the - * platform-specific backend + * @section sec_hostclip_protocol_versions Protocol versioning and context IDs * * The initial protocol implementation (called protocol v0) was very simple, * and could only handle simple data (like copied text and so on). It also @@ -310,26 +310,35 @@ uint32_t ShClSvcGetMode(void) } -static int shClSvcInit(VBOXHGCMSVCFNTABLE *pTable) +/** + * Initializes the service-global Shared Clipboard state. + * + * @returns VBox status code. + */ +static int shClSvcInit(void) { int rc = RTCritSectInit(&g_ShClSvc.CritSect); if (RT_SUCCESS(rc)) { - shClSvcHostModeSet(VBOX_SHCL_MODE_OFF); - g_ShClSvc.idNextSession = 1; - - /* Normally we would call ShClBackendInit() here but the service extension - * has not been loaded at this early stage of the Shared Clipboard service - * bringup so thus we save the HGCM service function table now so that we can - * pass it along to ShClBackendInit() later at shClSvcConnect() time. */ - g_ShClSvc.pTable = pTable; - - /* Clean up on failure, because 'shClSvcUnload' will not be called - * if 'shClSvcInit' returns an error. - */ + rc = RTSemEventMultiCreate(&g_ShClSvc.ExtState.hCallbacksDone); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventMultiSignal(g_ShClSvc.ExtState.hCallbacksDone); + if (RT_SUCCESS(rc)) + { + shClSvcHostModeSet(VBOX_SHCL_MODE_OFF); + g_ShClSvc.idNextSession = 1; + } + } + if (RT_FAILURE(rc)) { + if (g_ShClSvc.ExtState.hCallbacksDone != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_ShClSvc.ExtState.hCallbacksDone); + g_ShClSvc.ExtState.hCallbacksDone = NIL_RTSEMEVENTMULTI; + } RTCritSectDelete(&g_ShClSvc.CritSect); } } @@ -341,11 +350,14 @@ static DECLCALLBACK(int) shClSvcUnload(void *) { LogFlowFuncEnter(); - shClSvcBackendDestroy(); + int const rc = shClSvcExtUnregisterAndDestroy(); + AssertLogRelRC(rc); + RTSemEventMultiDestroy(g_ShClSvc.ExtState.hCallbacksDone); + g_ShClSvc.ExtState.hCallbacksDone = NIL_RTSEMEVENTMULTI; RTCritSectDelete(&g_ShClSvc.CritSect); - return VINF_SUCCESS; + return rc; } static DECLCALLBACK(int) shClSvcDisconnect(void *, uint32_t u32ClientID, void *pvClient) @@ -359,9 +371,15 @@ static DECLCALLBACK(int) shClSvcDisconnect(void *, uint32_t u32ClientID, void *p Assert(g_ShClSvc.pActiveClient == pClient); if (g_ShClSvc.pActiveClient == pClient) g_ShClSvc.pActiveClient = NULL; + if (g_ShClSvc.ExtState.uClientID == u32ClientID) + g_ShClSvc.ExtState.uClientID = 0; shClSvcUnlock(); - shClSvcBackendDisconnect(pClient); + int const rcWait = RTSemEventMultiWait(g_ShClSvc.ExtState.hCallbacksDone, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(rcWait, ("Waiting for Shared Clipboard extension callbacks during disconnect failed with %Rrc\n", + rcWait)); + + shClSvcExtBackendDisconnect(pClient); shClSvcClientDestroy(pClient); @@ -374,8 +392,6 @@ static DECLCALLBACK(int) shClSvcConnect(void *, uint32_t u32ClientID, void *pvCl PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pvClient); - pClient->pBackend = &g_ShClSvc.Backend; - int rc = ShClSvcClientInit(pClient, u32ClientID); if (RT_SUCCESS(rc)) { @@ -396,15 +412,17 @@ static DECLCALLBACK(int) shClSvcConnect(void *, uint32_t u32ClientID, void *pvCl #endif g_ShClSvc.pActiveClient = pClient; - rc = shClSvcBackendInit(g_ShClSvc.pTable); + rc = shClSvcExtBackendInit(); if (RT_SUCCESS(rc)) { - rc = shClSvcBackendConnect(pClient); + rc = shClSvcExtBackendConnect(pClient); if (RT_SUCCESS(rc)) { - rc = shClSvcBackendSync(pClient); + rc = shClSvcExtBackendSync(pClient); if (RT_SUCCESS(rc)) { + if (g_ShClSvc.ExtState.uClientID == 0) + g_ShClSvc.ExtState.uClientID = u32ClientID; /* The sync could return VINF_NO_CHANGE if nothing has changed on the host, but older Guest Additions didn't use RT_SUCCESS to but == VINF_SUCCESS to check for success. So just return VINF_SUCCESS here to not break older Guest Additions. */ @@ -412,12 +430,12 @@ static DECLCALLBACK(int) shClSvcConnect(void *, uint32_t u32ClientID, void *pvCl shClSvcUnlock(); return VINF_SUCCESS; } - LogFunc(("ShClBackendSync failed: %Rrc\n", rc)); - shClSvcBackendDisconnect(pClient); + LogFunc(("Service extension BACKEND_SYNC failed: %Rrc\n", rc)); + shClSvcExtBackendDisconnect(pClient); } - LogFunc(("ShClBackendConnect failed: %Rrc\n", rc)); + LogFunc(("Service extension BACKEND_CONNECT failed: %Rrc\n", rc)); } - LogFunc(("ShClBackendInit failed: %Rrc\n", rc)); + LogFunc(("Service extension BACKEND_INIT failed: %Rrc\n", rc)); Assert(g_ShClSvc.pActiveClient == pClient); g_ShClSvc.pActiveClient = NULL; shClSvcUnlock(); @@ -750,7 +768,7 @@ static DECLCALLBACK(int) shClSvcLoadState(void *, uint32_t u32ClientID, void *pv } /* Actual host data are to be reported to guest (SYNC). */ - (void) shClSvcBackendSync(pClient); + (void) shClSvcExtBackendSync(pClient); #else /* UNIT_TEST */ RT_NOREF(u32ClientID, pvClient, pSSM, pVMM, uVersion); @@ -808,7 +826,7 @@ extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTa pTable->pvService = NULL; /* Service specific initialization. */ - rc = shClSvcInit(pTable); + rc = shClSvcInit(); } } diff --git a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk index 63973a3f3bcd..a2c0cd12c609 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk +++ b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114575 2026-06-30 15:58:06Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the Shared Clipboard Host Service testcases. # @@ -30,161 +30,6 @@ include $(KBUILD_PATH)/subheader.kmk if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) - # - # Testcase which mocks HGCM to also test the VbglR3-side of Shared Clipboard. - # - # Goal is to use and test as much guest side code as possible as a self-contained - # binary on the host here. - # - # Note: No #ifdef TESTCASE hacks or similar allowed, has to run - # without #ifdef modifications to the core code! - # - tstClipboardMockHGCM_TEMPLATE = VBoxR3TstExe - tstClipboardMockHGCM_DEFS = VBOX_WITH_HGCM VBOX_WITH_SHARED_CLIPBOARD VBOX_WITH_SHARED_CLIPBOARD_HOST - tstClipboardMockHGCM_SOURCES = \ - tstClipboardMockHGCM.cpp \ - ../VBoxSharedClipboardSvc.cpp \ - ../VBoxSharedClipboardSvc-backend.cpp \ - ../VBoxSharedClipboardSvc-client.cpp \ - ../VBoxSharedClipboardSvc-host.cpp \ - ../../testcase/TstHGCMMock.cpp \ - ../../testcase/TstHGCMMockUtils.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp \ - $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp \ - $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp - tstClipboardMockHGCM_LIBS = $(LIB_RUNTIME) - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - tstClipboardMockHGCM_INCS += $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard - endif - - if1of ($(KBUILD_TARGET), linux solaris) - PROGRAMS += tstClipboardMockHGCM - tstClipboardMockHGCM_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/DisplayServerType.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp - tstClipboardMockHGCM_LIBPATH = \ - $(VBOX_LIBPATH_X11) - tstClipboardMockHGCM_LIBS += \ - Xt \ - X11 - endif - if1of ($(KBUILD_TARGET), win) - PROGRAMS += tstClipboardMockHGCM - tstClipboardMockHGCM_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp - endif - - tstClipboardMockHGCM_CLEAN = $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run - - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - tstClipboardMockHGCM_DEFS += \ - VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS \ - $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP,) - tstClipboardMockHGCM_SOURCES += \ - ../VBoxSharedClipboardSvc-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) - if1of ($(KBUILD_TARGET), linux solaris) - tstClipboardMockHGCM_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp - endif - endif - tstClipboardMockHGCM_SOURCES.win += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp - endif - - if 0 # Enable this if you want automatic runs after compilation. - $$(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run: $$(tstClipboardMockHGCM_1_STAGE_TARGET) - export VBOX_LOG_DEST=nofile; $(tstClipboardMockHGCM_1_STAGE_TARGET) quiet - $(QUIET)$(APPEND) -t "$@" "done" - OTHERS += $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run - endif - - # - # - # - PROGRAMS += tstClipboardServiceHost - tstClipboardServiceHost_TEMPLATE = VBoxR3TstExe - tstClipboardServiceHost_DEFS = VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD_HOST - tstClipboardServiceHost_SOURCES = \ - ../VBoxSharedClipboardSvc.cpp \ - ../VBoxSharedClipboardSvc-backend.cpp \ - ../VBoxSharedClipboardSvc-client.cpp \ - ../VBoxSharedClipboardSvc-host.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp \ - $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ - tstClipboardServiceHost.cpp - tstClipboardServiceHost_INCS += $(PATH_ROOT)/src/VBox/Main/include - tstClipboardServiceHost_LIBS = $(LIB_RUNTIME) - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - tstClipboardServiceHost_INCS += $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard - endif - - if1of ($(KBUILD_TARGET), linux solaris) - tstClipboardServiceHost_DEFS += VBOX_WITH_SHARED_CLIPBOARD_X11_LAZY_LOAD - tstClipboardServiceHost_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp - define def_VBoxTstClipboardServiceHost_libToLazyLoad - tstClipboardServiceHost_SOURCES += \ - $$(tstClipboardServiceHost_0_OUTDIR)/$(2)LazyLoad.asm - tstClipboardServiceHost_CLEAN += \ - $$(tstClipboardServiceHost_0_OUTDIR)/$(2)LazyLoad.asm - $$$$(tstClipboardServiceHost_0_OUTDIR)/$(2)LazyLoad.asm: $$(PATH_ROOT)/src/VBox/GuestHost/$(2).def $(VBOX_DEF_2_LAZY_LOAD) | $$$$(dir $$@) - $$(call MSG_TOOL,VBoxDef2LazyLoad,tstClipboardServiceHost,$$(filter %.def, $$^),$$@) - $$(QUIET)$$(RM) -f -- "$$@" - $$(VBOX_DEF_2_LAZY_LOAD) --explicit-load-function --system --library $(2)$$(SUFF_DLL)$$(if $(3),.$(3),) --output "$$@" $$(filter %.def, $$^) - endef - if1of ($(KBUILD_TARGET), linux) - VBOX_TST_CLIPBOARD_SERVICE_HOST_LIBX11_VER = 6 - VBOX_TST_CLIPBOARD_SERVICE_HOST_LIBXT_VER = 6 - tstClipboardServiceHost_LDFLAGS.linux += $(VBOX_GCC_no-pie) - endif - $(evalcall2 def_VBoxTstClipboardServiceHost_libToLazyLoad,$(PATH_ROOT)/src/VBox/GuestHost/libX11.def,libX11,$(VBOX_TST_CLIPBOARD_SERVICE_HOST_LIBX11_VER)) - $(evalcall2 def_VBoxTstClipboardServiceHost_libToLazyLoad,$(PATH_ROOT)/src/VBox/GuestHost/libXt.def,libXt,$(VBOX_TST_CLIPBOARD_SERVICE_HOST_LIBXT_VER)) - endif - - tstClipboardServiceHost_CLEAN += $(tstClipboardServiceHost_0_OUTDIR)/tstClipboardServiceHost.run - - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - tstClipboardServiceHost_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - tstClipboardServiceHost_SOURCES += \ - ../VBoxSharedClipboardSvc-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp - endif - - # - # - # - PROGRAMS += tstClipboardServiceImpl - tstClipboardServiceImpl_TEMPLATE = VBoxR3TstExe - tstClipboardServiceImpl_DEFS = VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD_HOST - tstClipboardServiceImpl_SOURCES = \ - ../VBoxSharedClipboardSvc.cpp \ - ../VBoxSharedClipboardSvc-backend.cpp \ - ../VBoxSharedClipboardSvc-client.cpp \ - ../VBoxSharedClipboardSvc-host.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ - $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ - tstClipboardServiceImpl.cpp - tstClipboardServiceImpl_SOURCES.win = \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp - tstClipboardServiceImpl_LIBS = $(LIB_RUNTIME) - tstClipboardServiceImpl_CLEAN = $(tstClipboardServiceImpl_0_OUTDIR)/tstClipboardServiceImpl.run - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) # # File transfer tests. @@ -221,14 +66,4 @@ if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBO endif endif -# -# List of above testcases that will be included in the ValKit. -# -ifdef VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING - if1of ($(KBUILD_TARGET), linux solaris win) - VALKIT_UNITTESTS_WHITELIST_GUEST_ADDITIONS += \ - tstClipboardMockHGCM - endif -endif # VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING - include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp deleted file mode 100644 index 879224de6568..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp +++ /dev/null @@ -1,1269 +0,0 @@ -/* $Id: tstClipboardMockHGCM.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard host service test case. - */ - -/* - * Copyright (C) 2011-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#include -#include -#include -#include -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# include -# include "VBoxSharedClipboardSvc-transfers.h" -#endif -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -# include -# include -# include -# include -#endif -#ifdef RT_OS_WINDOWS -# include -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/********************************************************************************************************************************* -* Global Variables * -*********************************************************************************************************************************/ -static RTTEST g_hTest; - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -DECLCALLBACK(int) tstHgcmMockSvcDispatcher(void *pvExtension, uint32_t u32Function, - void *pvParms, uint32_t cbParms); - -/** Test dispatcher which keeps protocol-only transfer tests independent of a display server. */ -static DECLCALLBACK(int) tstClipboardTransferStatusContextDispatcher(void *pvExtension, uint32_t u32Function, - void *pvParms, uint32_t cbParms) -{ - if ( u32Function == VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT - || u32Function == VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT - || u32Function == VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT - || u32Function == VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC) - return VINF_SUCCESS; - return tstHgcmMockSvcDispatcher(pvExtension, u32Function, pvParms, cbParms); -} -#endif - - -/********************************************************************************************************************************* -* Shared Clipboard testing * -*********************************************************************************************************************************/ -struct CLIPBOARDTESTDESC; -/** Pointer to a test description. */ -typedef CLIPBOARDTESTDESC *PTESTDESC; - -struct CLIPBOARDTESTCTX; -/** Pointer to a test context. */ -typedef CLIPBOARDTESTCTX *PCLIPBOARDTESTCTX; - -typedef DECLCALLBACKTYPE(int, FNTESTSETUP,(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx)); -/** Pointer to a test setup callback. */ -typedef FNTESTSETUP *PFNTESTSETUP; - -typedef DECLCALLBACKTYPE(int, FNTESTEXEC,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)); -/** Pointer to a test exec callback. */ -typedef FNTESTEXEC *PFNTESTEXEC; - -typedef DECLCALLBACKTYPE(int, FNTESTDESTROY,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)); -/** Pointer to a test destroy callback. */ -typedef FNTESTDESTROY *PFNTESTDESTROY; - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -typedef struct CLIPBOARDTESTTASKX11 -{ - /** Thread handle for the X11 "populate clipboard" thread. */ - RTTHREAD hThread; - /** Shutdown indicator flag. */ - volatile bool fShutdown; -} CLIPBOARDTESTTASKX11; -#endif - -/** - * Structure for keeping a clipboard test task. - */ -typedef struct CLIPBOARDTESTTASK -{ - SHCLFORMATS enmFmtHst; - SHCLFORMATS enmFmtGst; - /** For testing chunked reads / writes. */ - size_t cbChunk; - /** Data buffer to read / write for this task. - * Can be NULL if not needed. */ - void *pvData; - /** Size (in bytes) of \a pvData. */ - size_t cbData; - /** Number of bytes read / written from / to \a pvData. */ - size_t cbProcessed; -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) - /** X11-specific data */ - CLIPBOARDTESTTASKX11 X11; -#endif -} CLIPBOARDTESTTASK; -typedef CLIPBOARDTESTTASK *PCLIPBOARDTESTTASK; - -/** - * Structure for keeping a clipboard test context. - */ -typedef struct CLIPBOARDTESTCTX -{ - /** The HGCM Mock utils context. */ - TSTHGCMUTILSCTX HGCM; - /** Clipboard-specific task data. */ - CLIPBOARDTESTTASK Task; - struct - { - /** The VbglR3 Shared Clipboard context to work on. */ - VBGLR3SHCLCMDCTX CmdCtx; - } Guest; -} CLIPBOARDTESTCTX; - -/** The one and only clipboard test context. One at a time. */ -CLIPBOARDTESTCTX g_TstCtx; - -/** - * Structure for keeping a clipboard test description. - */ -typedef struct CLIPBOARDTESTDESC -{ - /** The setup callback. */ - PFNTESTSETUP pfnSetup; - /** The exec callback. */ - PFNTESTEXEC pfnExec; - /** The destruction callback. */ - PFNTESTDESTROY pfnDestroy; -} CLIPBOARDTESTDESC; - -typedef struct SHCLCONTEXT -{ -} SHCLCONTEXT; - - -static int tstSetModeRc(PTSTHGCMMOCKSVC pSvc, uint32_t uMode, int rcExpected) -{ - VBOXHGCMSVCPARM aParms[2]; - HGCMSvcSetU32(&aParms[0], uMode); - int rc2 = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, aParms); - RTTESTI_CHECK_MSG_RET(rcExpected == rc2, ("Expected %Rrc, got %Rrc\n", rcExpected, rc2), rc2); - if (RT_SUCCESS(rcExpected)) - { - uint32_t const uModeRet = ShClSvcGetMode(); - RTTESTI_CHECK_MSG_RET(uMode == uModeRet, ("Expected mode %RU32, got %RU32\n", uMode, uModeRet), VERR_WRONG_TYPE); - } - return rc2; -} - -static int tstClipboardSetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uMode) -{ - return tstSetModeRc(pSvc, uMode, VINF_SUCCESS); -} - -static bool tstClipboardGetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uModeExpected) -{ - RT_NOREF(pSvc); - RTTESTI_CHECK_RET(ShClSvcGetMode() == uModeExpected, false); - return true; -} - -static void tstOperationModes(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - uint32_t u32Mode; - int rc; - - RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE"); - - PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); - - /* Reset global variable which doesn't reset itself. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - u32Mode = ShClSvcGetMode(); - RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); - - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - HGCMSvcSetU64(&parms[0], 99); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_HOST_TO_GUEST); - tstSetModeRc(pSvc, 99, VERR_NOT_SUPPORTED); - tstClipboardGetMode(pSvc, VBOX_SHCL_MODE_OFF); -} - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -static void testSetTransferMode(void) -{ - RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE"); - - PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); - - /* Invalid parameter. */ - VBOXHGCMSVCPARM parms[2]; - HGCMSvcSetU64(&parms[0], 99); - int rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - /* Invalid mode. */ - HGCMSvcSetU32(&parms[0], 99); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); - - /* Enable transfers. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - /* Disable transfers again. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_NONE); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -/** - * Verifies that transfer status replies use the transfer ID of the supplied - * transfer rather than a stale transfer ID in the command context. Also - * verifies that callers without a local transfer can use the command context - * unchanged. - */ -static void testTransferStatusContextRouting(void) -{ - RTTestISub("Testing transfer status context routing"); - - PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst(); - SHCLTRANSFERID const idTargetTransfer = 42; - SHCLTRANSFERID const idAmbientTransfer = 43; - - VBGLR3SHCLCMDCTX CmdCtx; - RT_ZERO(CmdCtx); - SHCLTRANSFERCTX GuestTransferCtx; - RT_ZERO(GuestTransferCtx); - SHCLTRANSFERCTX StaleGuestTransferCtx; - RT_ZERO(StaleGuestTransferCtx); - - HGCMCLIENTID const idNextClient = pSvc->uNextClientId; - bool fConnected = false; - bool fGuestCtxInit = false; - bool fGuestRegistered = false; - bool fStaleGuestCtxInit = false; - bool fStaleGuestRegistered = false; - bool fTestDispatcher = false; - PSHCLCLIENT pClient = NULL; - PSHCLTRANSFER pGuestTransfer = NULL; - PSHCLTRANSFER pStaleGuestTransfer = NULL; - PSHCLEVENT pTargetEvent = NULL; - PSHCLEVENT pAmbientEvent = NULL; - int rc = VINF_SUCCESS; - - do - { - VBOXHGCMSVCPARM Parm; - rc = pSvc->fnTable.pfnRegisterExtension(pSvc->fnTable.pvService, - tstClipboardTransferStatusContextDispatcher, NULL); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - fTestDispatcher = true; - - rc = tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_BIDIRECTIONAL); - if (RT_FAILURE(rc)) - break; - - HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - rc = VbglR3ClipboardConnectEx(&CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - fConnected = true; - - RTTESTI_CHECK_MSG_BREAK(CmdCtx.idClient < RT_ELEMENTS(pSvc->aHgcmClient), - ("Client ID %RU32 is out of range\n", CmdCtx.idClient)); - PTSTHGCMMOCKCLIENT const pMockClient = &pSvc->aHgcmClient[CmdCtx.idClient]; - RTTESTI_CHECK_MSG_BREAK(TstHgcmMockSvcWaitForConnect(pSvc) == pMockClient, - ("Unexpected mock client connected for ID %RU32\n", CmdCtx.idClient)); - pClient = (PSHCLCLIENT)pMockClient->pvClient; - RTTESTI_CHECK_MSG_BREAK(pClient != NULL, ("Missing service client for ID %RU32\n", CmdCtx.idClient)); - - PSHCLTRANSFER pHostTarget = NULL; - rc = ShClTransferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, NULL, &pHostTarget); - if (RT_SUCCESS(rc)) - rc = ShClTransferCtxRegisterById(&pClient->Transfers.Ctx, pHostTarget, idTargetTransfer); - if (RT_FAILURE(rc)) - ShClTransferDestroy(pHostTarget); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - rc = ShClTransferInit(pHostTarget); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - PSHCLTRANSFER pHostAmbient = NULL; - rc = ShClTransferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, NULL, &pHostAmbient); - if (RT_SUCCESS(rc)) - rc = ShClTransferCtxRegisterById(&pClient->Transfers.Ctx, pHostAmbient, idAmbientTransfer); - if (RT_FAILURE(rc)) - ShClTransferDestroy(pHostAmbient); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - rc = ShClTransferInit(pHostAmbient); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - rc = ShClTransferCtxInit(&GuestTransferCtx); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - fGuestCtxInit = true; - - rc = ShClTransferCtxBeginSession(&GuestTransferCtx, pClient->State.uSessionID); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - rc = ShClTransferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, NULL, &pGuestTransfer); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - rc = ShClTransferCtxRegisterById(&GuestTransferCtx, pGuestTransfer, idTargetTransfer); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - fGuestRegistered = true; - - rc = ShClTransferCtxInit(&StaleGuestTransferCtx); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - fStaleGuestCtxInit = true; - - SHCLSESSIONID const idStaleSession = pClient->State.uSessionID != 1 ? 1 : 2; - rc = ShClTransferCtxBeginSession(&StaleGuestTransferCtx, idStaleSession); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - rc = ShClTransferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, NULL, &pStaleGuestTransfer); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - rc = ShClTransferCtxRegisterById(&StaleGuestTransferCtx, pStaleGuestTransfer, idTargetTransfer); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - fStaleGuestRegistered = true; - - /* Both transfers deliberately have the same valid event ID. */ - pHostTarget->Events.idNextEvent = 1234; - pHostAmbient->Events.idNextEvent = pHostTarget->Events.idNextEvent; - rc = ShClEventSourceGenerateAndRegisterEvent(&pHostTarget->Events, &pTargetEvent); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - rc = ShClEventSourceGenerateAndRegisterEvent(&pHostAmbient->Events, &pAmbientEvent); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - RTTESTI_CHECK_MSG_BREAK(pTargetEvent->idEvent == pAmbientEvent->idEvent, - ("Expected matching event IDs, got %RU32 and %RU32\n", - pTargetEvent->idEvent, pAmbientEvent->idEvent)); - - /* - * The command context deliberately refers to a different live - * transfer. The supplied transfer must select the target transfer. - */ - CmdCtx.idContext = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, - idAmbientTransfer, pAmbientEvent->idEvent); - rc = VbglR3ClipboardTransferSendStatus(&CmdCtx, pGuestTransfer, - SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - /* A mismatched ambient transfer also makes its event stale. */ - int const rcTargetEvent = ShClEventWait(pTargetEvent, 0 /* msTimeout */, NULL /* ppPayload */); - int const rcAmbientEvent = ShClEventWait(pAmbientEvent, 0 /* msTimeout */, NULL /* ppPayload */); - RTTESTI_CHECK_RC(rcTargetEvent, VERR_TIMEOUT); - RTTESTI_CHECK_RC(rcAmbientEvent, VERR_TIMEOUT); - if (rcTargetEvent != VERR_TIMEOUT || rcAmbientEvent != VERR_TIMEOUT) - break; - - /* A stale transfer's session must not be replaced by the ambient session. */ - rc = VbglR3ClipboardTransferSendStatus(&CmdCtx, pStaleGuestTransfer, - SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); - RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); - if (rc != VERR_INVALID_CONTEXT) - break; - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idTargetTransfer) != NULL); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idAmbientTransfer) != NULL); - RTTESTI_CHECK_RC(ShClEventWait(pTargetEvent, 0 /* msTimeout */, NULL /* ppPayload */), VERR_TIMEOUT); - RTTESTI_CHECK_RC(ShClEventWait(pAmbientEvent, 0 /* msTimeout */, NULL /* ppPayload */), VERR_TIMEOUT); - - RTTESTI_CHECK(ShClEventRelease(pTargetEvent) == 0); - pTargetEvent = NULL; - RTTESTI_CHECK(ShClEventRelease(pAmbientEvent) == 0); - pAmbientEvent = NULL; - - rc = VbglR3ClipboardTransferSendStatus(&CmdCtx, pGuestTransfer, - SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - - PSHCLTRANSFER const pTargetAfter - = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idTargetTransfer); - PSHCLTRANSFER const pAmbientAfter - = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idAmbientTransfer); - RTTESTI_CHECK_MSG(pTargetAfter == NULL, - ("Target transfer %RU16 was not canceled\n", idTargetTransfer)); - RTTESTI_CHECK_MSG(pAmbientAfter != NULL, - ("Ambient transfer %RU16 was canceled instead\n", idAmbientTransfer)); - if (pTargetAfter != NULL || pAmbientAfter == NULL) - break; - - /* No local transfer is available on some error paths: keep the context unchanged. */ - rc = VbglR3ClipboardTransferSendStatus(&CmdCtx, NULL, - SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - break; - RTTESTI_CHECK_MSG(ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idAmbientTransfer) == NULL, - ("Context transfer %RU16 was not canceled\n", idAmbientTransfer)); - } while (0); - - if (pClient) - { - if (pTargetEvent) - { - RTTESTI_CHECK(ShClEventRelease(pTargetEvent) == 0); - pTargetEvent = NULL; - } - if (pAmbientEvent) - { - RTTESTI_CHECK(ShClEventRelease(pAmbientEvent) == 0); - pAmbientEvent = NULL; - } - PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idTargetTransfer); - if (pTransfer) - ShClSvcTransferDestroyById(pClient, idTargetTransfer); - pTransfer = ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idAmbientTransfer); - if (pTransfer) - ShClSvcTransferDestroyById(pClient, idAmbientTransfer); - } - if (fGuestCtxInit) - { - if (!fGuestRegistered && pGuestTransfer) - ShClTransferDestroy(pGuestTransfer); - ShClTransferCtxDestroy(&GuestTransferCtx); - } - else if (pGuestTransfer) - ShClTransferDestroy(pGuestTransfer); - - if (fStaleGuestCtxInit) - { - if (!fStaleGuestRegistered && pStaleGuestTransfer) - ShClTransferDestroy(pStaleGuestTransfer); - ShClTransferCtxDestroy(&StaleGuestTransferCtx); - } - else if (pStaleGuestTransfer) - ShClTransferDestroy(pStaleGuestTransfer); - - if (fConnected) - { - int const rcDisconnect = VbglR3ClipboardDisconnectEx(&CmdCtx); - RTTESTI_CHECK_RC_OK(rcDisconnect); - if (RT_SUCCESS(rcDisconnect)) - { - /* This mock has four monotonically allocated client slots. Reclaim - the fully disconnected slot so this test does not consume one. */ - RTTESTI_CHECK(pSvc->uNextClientId == idNextClient + 1); - pSvc->uNextClientId = idNextClient; - } - } - - if (fTestDispatcher) - { - int const rcRestore = pSvc->fnTable.pfnRegisterExtension(pSvc->fnTable.pvService, - tstHgcmMockSvcDispatcher, NULL); - RTTESTI_CHECK_RC_OK(rcRestore); - } - - VBOXHGCMSVCPARM Parm; - HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_NONE); - RTTESTI_CHECK_RC_OK(TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm)); - RTTESTI_CHECK_RC_OK(tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_OFF)); -} -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - -#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) -/** Verifies that the X11 backend fails cleanly when no display is available. */ -static void testX11UnavailableBackend(void) -{ - RTTestISub("Testing X11 backend without a display"); - - PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst(); - void *pvClient = RTMemAllocZ(pSvc->fnTable.cbClient); - RTTESTI_CHECK_MSG_RETV(pvClient, ("Failed to allocate a service client\n")); - - const char *pszDisplay = RTEnvGet("DISPLAY"); - char *pszDisplaySaved = pszDisplay ? RTStrDup(pszDisplay) : NULL; - if (pszDisplay && !pszDisplaySaved) - { - RTTESTI_CHECK_MSG(false, ("Failed to save DISPLAY before the test\n")); - RTMemFree(pvClient); - return; - } - - int rc = RTEnvUnset("DISPLAY"); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - RTStrFree(pszDisplaySaved); - RTMemFree(pvClient); - return; - } - - rc = pSvc->fnTable.pfnConnect(pSvc->fnTable.pvService, UINT32_C(100), pvClient, - VMMDEV_REQUESTOR_USR_NOT_GIVEN /* fRequestor */, false /* fRestoring */); - - int const rcRestore = pszDisplaySaved ? RTEnvSet("DISPLAY", pszDisplaySaved) : VINF_SUCCESS; - RTStrFree(pszDisplaySaved); - RTTESTI_CHECK_RC(rcRestore, VINF_SUCCESS); - - RTTESTI_CHECK_RC(rc, VERR_NOT_SUPPORTED); - if (RT_SUCCESS(rc)) - RTTESTI_CHECK_RC_OK(pSvc->fnTable.pfnDisconnect(pSvc->fnTable.pvService, UINT32_C(100), pvClient)); - RTMemFree(pvClient); -} -#endif - -static void testGuestSimple(void) -{ - RTTestISub("Testing client (guest) API - Simple"); - - PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); - - /* Preparations. */ - VBGLR3SHCLCMDCTX Ctx; - RT_ZERO(Ctx); - - /* - * Multiple connects / disconnects. - */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID)); - RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); - /* Report bogus guest features while connecting. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, 0xdeadbeef)); - RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); - - RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID)); - - /* - * Feature tests. - */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0x0, NULL /* pfHostFeatures */)); - /* Report bogus features to the host. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0xdeadb33f, NULL /* pfHostFeatures */)); - - /* - * Access denied tests. - */ - tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_OFF); - - /* Try reading data from host. */ - uint8_t abData[32]; uint32_t cbIgnored; - RTTESTI_CHECK_RC(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, - abData, sizeof(abData), &cbIgnored), VERR_ACCESS_DENIED); - /* Try writing data without reporting formats before (legacy). */ - RTTESTI_CHECK_RC(VbglR3ClipboardWriteData(Ctx.idClient, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED); - /* Try writing data without reporting formats before. */ - RTTESTI_CHECK_RC(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED); - /* Report bogus formats to the host. */ - RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f), VERR_ACCESS_DENIED); - /* Report supported formats to host. */ - RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, - VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML), - VERR_ACCESS_DENIED); - /* - * Access allowed tests. - */ - tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_BIDIRECTIONAL); - - /* Try reading data from host. */ - int rc = VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, - abData, sizeof(abData), &cbIgnored); - /* - * The VbglR3ClipboardConnectEx() call above reaches the X11 - * ShClBackendConnect() routine which calls ShClX11ThreadStart() to start the - * "SHCLX11" thread, an Xt thread for handling the Shared Clipboard, which - * sits in clipThreadMain() where it loops calling XtGetSelectionValue(3X11). - * When the "SHCLX11" thread is starting up at connect time, clipThreadMain() - * calls clipQueryX11Targets() which calls XtGetSelectionValue(3X11) to request - * the supported targets of the clipboard selection and then asynchronously calls - * the specified callback, clipQueryX11TargetsCallback(), with the results. - * Meanwhile, clipThreadMain() signals its parent and ShClX11ThreadStart() then - * returns as does ShClBackendConnect(). Attempting to read from the clipboard - * here before clipQueryX11TargetsCallback() returns will fail when - * ShClBackendReadData() -> ShClX11ReadDataFromX11() -> - * shClX11ReadDataFromX11Internal() -> ShClX11ReadDataFromX11Async() -> - * ShClX11ReadDataFromX11Worker() finds the clipboard busy (fXtBusy == true) - * and returns VERR_TRY_AGAIN. - */ - if (rc == VERR_TRY_AGAIN) - { - RTThreadSleep(RT_MS_1SEC); - rc = VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, - abData, sizeof(abData), &cbIgnored); - } - RTTESTI_CHECK_RC_OK(rc); - /* Report bogus formats to the host. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f)); - /* Report supported formats to host. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, - VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML)); - /* Tear down. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); -} - -static RTUTF16 tstGetRandUtf8(void) -{ - return RTRandU32Ex(0x20, 0x7A); -} - -static char *tstGenerateUtf8StringA(uint32_t uCch) -{ - char * pszRand = (char *)RTMemAlloc(uCch + 1); - for (uint32_t i = 0; i < uCch; i++) - pszRand[i] = tstGetRandUtf8(); - pszRand[uCch] = 0; - return pszRand; -} - -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) -static RTUTF16 tstGetRandUtf16(void) -{ - RTUTF16 wc; - do - { - wc = (RTUTF16)RTRandU32Ex(1, 0xfffd); - } while (wc >= 0xd800 && wc <= 0xdfff); - return wc; -} - -static PRTUTF16 tstGenerateUtf16StringA(uint32_t uCch) -{ - PRTUTF16 pwszRand = (PRTUTF16)RTMemAlloc((uCch + 1) * sizeof(RTUTF16)); - for (uint32_t i = 0; i < uCch; i++) - pwszRand[i] = tstGetRandUtf16(); - pwszRand[uCch] = 0; - return pwszRand; -} -#endif /* RT_OS_WINDOWS) || RT_OS_OS2 */ - -static void testHostCall(void) -{ - tstOperationModes(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - testSetTransferMode(); -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ -} - - -/********************************************************************************************************************************* - * Test: Guest reading from host * - ********************************************************************************************************************************/ -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -/* Called from SHCLX11 thread. */ -static DECLCALLBACK(int) tstTestReadFromHost_ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) -{ - RT_NOREF(pCtx, fFormats, pvUser); - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "tstTestReadFromHost_SvcReportFormatsCallback: fFormats=%#x\n", fFormats); - return VINF_SUCCESS; -} - -/* Called by the backend, e.g. for X11 in the SHCLX11 thread. */ -static DECLCALLBACK(int) tstTestReadFromHost_OnClipboardReadCallback(PSHCLCONTEXT pCtx, - SHCLFORMAT uFmt, void **ppv, size_t *pcb, void *pvUser) -{ - RT_NOREF(pCtx, uFmt, pvUser); - - PCLIPBOARDTESTTASK pTask = (PCLIPBOARDTESTTASK)TstHGCMUtilsTaskGetCurrent(&g_TstCtx.HGCM)->pvUser; - - void *pvData = NULL; - size_t cbData = pTask->cbData - pTask->cbProcessed; - if (cbData) - { - pvData = RTMemDup((uint8_t *)pTask->pvData + pTask->cbProcessed, cbData); - AssertPtr(pvData); - } - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host reporting back %RU32 bytes of data\n", cbData); - - *ppv = pvData; - *pcb = cbData; - - return VINF_SUCCESS; -} -#endif /* (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */ - -typedef struct TSTUSERMOCK -{ -#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) - SHCLX11CTX X11Ctx; -#endif - PSHCLCONTEXT pCtx; -} TSTUSERMOCK; -typedef TSTUSERMOCK *PTSTUSERMOCK; - -static void tstTestReadFromHost_MockInit(PTSTUSERMOCK pUsrMock, const char *pszName) -{ -#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) - SHCLCALLBACKS Callbacks; - RT_ZERO(Callbacks); - Callbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback; - Callbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback; - - pUsrMock->pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); - AssertPtrReturnVoid(pUsrMock->pCtx); - - ShClX11Init(&pUsrMock->X11Ctx, &Callbacks, pUsrMock->pCtx); - ShClX11ThreadStartEx(&pUsrMock->X11Ctx, pszName, false /* fGrab */); - /* Give the clipboard time to synchronise. */ - RTThreadSleep(500); -#else - RT_NOREF(pUsrMock, pszName); -#endif /* RT_OS_LINUX */ -} - -static void tstTestReadFromHost_MockDestroy(PTSTUSERMOCK pUsrMock) -{ -#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) - ShClX11ThreadStop(&pUsrMock->X11Ctx); - ShClX11Term(&pUsrMock->X11Ctx); - RTMemFree(pUsrMock->pCtx); -#else - RT_NOREF(pUsrMock); -#endif -} - -static int tstTestReadFromHost_DoIt(PCLIPBOARDTESTCTX pCtx, PCLIPBOARDTESTTASK pTask) -{ - size_t cbDst = RT_MAX(_64K, pTask->cbData); - uint8_t *pabDst = (uint8_t *)RTMemAllocZ(cbDst); - AssertPtrReturn(pabDst, VERR_NO_MEMORY); - - AssertPtr(pTask->pvData); /* Racing condition with host thread? */ - Assert(pTask->cbChunk); /* Buggy test? */ - Assert(pTask->cbChunk <= pTask->cbData); /* Ditto. */ - - size_t cbToRead = pTask->cbData; - switch (pTask->enmFmtGst) - { - case VBOX_SHCL_FMT_UNICODETEXT: -#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */ - cbToRead *= sizeof(RTUTF16); -#endif - break; - - default: - break; - } - - PVBGLR3SHCLCMDCTX pCmdCtx = &pCtx->Guest.CmdCtx; - - /* Do random chunked reads. */ - uint32_t const cChunkedReads = RTRandU32Ex(1, 16); - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "%RU32 chunked reads\n", cChunkedReads); - for (uint32_t i = 0; i < cChunkedReads; i++) - { - /* Note! VbglR3ClipboardReadData() currently does not support chunked reads! - * It in turn returns VINF_BUFFER_OVERFLOW when the supplied buffer was too small. */ - - uint32_t cbChunk = RTRandU32Ex(1, (uint32_t)(pTask->cbData / cChunkedReads)); - uint32_t cbRead = 0; - RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Guest trying to read %RU32 bytes\n", cbChunk); - int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, cbChunk, &cbRead); - if ( vrc2 == VINF_SUCCESS - && cbRead == 0) /* No data there yet? */ - { - RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "No data (yet) from host\n"); - RTThreadSleep(10); - continue; - } - RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Trying to read host clipboard data with a %RU32 byte buffer -> %Rrc (%RU32)\n", cbChunk, vrc2, cbRead); - RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_BUFFER_OVERFLOW, (g_hTest, "Got %Rrc, expected VINF_BUFFER_OVERFLOW\n", vrc2)); - } - - /* Last read: Read the data with a buffer big enough. This must succeed. */ - RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Reading full data (%zu)\n", pTask->cbData); - uint32_t cbRead = 0; - int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, (uint32_t)cbDst, &cbRead); - RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_SUCCESS, (g_hTest, "Got %Rrc, expected VINF_SUCCESS\n", vrc2)); - RTTEST_CHECK_MSG(g_hTest, cbRead == cbToRead, (g_hTest, "Read %RU32 bytes, expected %zu\n", cbRead, cbToRead)); - - if (pTask->enmFmtGst == VBOX_SHCL_FMT_UNICODETEXT) - RTTEST_CHECK_MSG(g_hTest, RTUtf16ValidateEncoding((PRTUTF16)pabDst) == VINF_SUCCESS, (g_hTest, "Read data is not valid UTF-16\n")); - if (cbRead == cbToRead) - { -#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */ - PRTUTF16 pwszSrc = NULL; - RTTEST_CHECK(g_hTest, RT_SUCCESS(RTStrToUtf16((const char *)pTask->pvData, &pwszSrc))); - RTTEST_CHECK_MSG(g_hTest, memcmp(pwszSrc, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n")); - RTUtf16Free(pwszSrc); -#else - RTTEST_CHECK_MSG(g_hTest, memcmp(pTask->pvData, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n")); -#endif - } - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Read data from host:\n%.*Rhxd\n", cbRead, pabDst); - - RTMemFree(pabDst); - - return VINF_SUCCESS; -} - -static DECLCALLBACK(int) tstTestReadFromHost_ThreadGuest(PTSTHGCMUTILSCTX pCtx, void *pvCtx) -{ - RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */ - - PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvCtx; - AssertPtr(pTstCtx); - - RT_ZERO(pTstCtx->Guest.CmdCtx); - RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardConnectEx(&pTstCtx->Guest.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID)); - - RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */ - - PCLIPBOARDTESTTASK pTstTask = (PCLIPBOARDTESTTASK)pCtx->Task.pvUser; - AssertPtr(pTstTask); - tstTestReadFromHost_DoIt(pTstCtx, pTstTask); - - RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardDisconnectEx(&pTstCtx->Guest.CmdCtx)); - - /* Signal that the task ended. */ - TstHGCMUtilsTaskSignal(&pCtx->Task, VINF_SUCCESS); - - return VINF_SUCCESS; -} - -static DECLCALLBACK(int) tstTestReadFromHost_ClientConnectedCallback(PTSTHGCMUTILSCTX pCtx, PTSTHGCMMOCKCLIENT pClient, - void *pvUser) -{ - RT_NOREF(pCtx, pClient); - - PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvUser; - AssertPtr(pTstCtx); RT_NOREF(pTstCtx); - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Client %RU32 connected\n", pClient->idClient); - return VINF_SUCCESS; -} - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -/** - * This is an X11 clipboard "copy" thread which takes ownership of the - * clipboard, waits for requests of the clipboard selection contents from - * X11 clients, and processes each request by copying the randomly generated - * UTF8 data passed in to the clipboard selection before finally notifying - * the requesting X client when complete. - */ -static DECLCALLBACK(int) tstSetClipboardContents(RTTHREAD hThread, void *pvUser) -{ - PCLIPBOARDTESTTASK pTask = (PCLIPBOARDTESTTASK)pvUser; - - Display *pX11Display = XOpenDisplay(NULL); - if (!pX11Display) - { - RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "XOpenDisplay(3X11) failed\n"); - return VERR_NOT_AVAILABLE; - } - - /* Create an unmapped subwindow which will own the "CLIPBOARD" selection and - * will receive messages from X clients accessing the clipboard. */ - Window window = XCreateSimpleWindow(pX11Display, XDefaultRootWindow(pX11Display), 0, 0, 1, 1, 0, 0, 0); - - /* Populate the atom identifiers needed for our clipboard operations. */ - Atom clipboard = XInternAtom(pX11Display, "CLIPBOARD", False); - Atom targets = XInternAtom(pX11Display, "TARGETS", False); - Atom utf8_string = XInternAtom(pX11Display, "UTF8_STRING", False); - - /* In order to copy data to the clipboard we must first have our window - * become the owner of the "CLIPBOARD" selection. */ - XSetSelectionOwner(pX11Display, clipboard, window, CurrentTime); - if (XGetSelectionOwner(pX11Display, clipboard) != window) - { - RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, - "XSetSelectionOwner(3X11): failed to become owner of clipboard selection\n"); - return VERR_ACCESS_DENIED; - } - - /* Initial setup completed, notify our callee that we are now ready for action. */ - RTThreadUserSignal(hThread); - - while (!ASMAtomicReadBool(&pTask->X11.fShutdown)) - { - /* Check the event queue to see if any events have been received from the X - * server. */ - if (XPending(pX11Display) > 0) - { - /* Copy the first event from the event queue into the specified XEvent - * structure and remove it from the queue. */ - XEvent event; - XNextEvent(pX11Display, &event); - - /* X11 clients which want to paste the contents of the clipboard selection - * call XConvertSelection(3X11) which the X server responds to by sending - * a 'SelectionRequest' event to the X11 client which currently owns the - * clipboard selection. This happens in our case via: shClSvcConnect() -> - * ShClBackendConnect() -> ShClX11ThreadStart() -> ShClX11ThreadStartEx() -> - * clipThreadMain() -> clipQueryX11Targets() -> XtGetSelectionValue(). - * XtGetSelectionValue() calls XConvertSelection() internally. */ - if (event.type == SelectionRequest) - { - /* The 'SelectionRequest' event contains details of the X11 client requestor - * such as their 'window' ('requestor'), the desired format of the clipboard - * contents ('target'), and which property on their window that they would - * like the clipboard contents copied to ('property'). We use this data to - * populate a 'SelectionNotify' event which we send back to the requestor. */ - XSelectionRequestEvent *pReq = &event.xselectionrequest; - XSelectionEvent selectionNotifyEvent = { 0 }; - - selectionNotifyEvent.type = SelectionNotify; - selectionNotifyEvent.display = pReq->display; - selectionNotifyEvent.requestor = pReq->requestor; - selectionNotifyEvent.selection = pReq->selection; - selectionNotifyEvent.target = pReq->target; - selectionNotifyEvent.property = pReq->property; - selectionNotifyEvent.time = pReq->time; - - /* X11 clients typically send an initial 'SelectionRequest' event containing - * a 'target' containing the "TARGETS" atom to request a list of valid - * supported target atoms. After taking ownership of the "CLIPBOARD" - * selection other X11 clients may also send a 'SelectionRequest' event with - * a 'target' of the "TARGETS" atom. */ - if (selectionNotifyEvent.target == targets) - { - Atom supported[] = { targets, utf8_string }; - XChangeProperty(pX11Display, pReq->requestor, pReq->property, XA_ATOM, 32, - PropModeReplace, (unsigned char *)supported, RT_ELEMENTS(supported)); - } - else if (selectionNotifyEvent.target == utf8_string) - { - /* Update the property which the requestor chose to contain the clipboard - * selection contents on the requetor's window with the UTF8-formatted - * contents of the clipboard. */ - XChangeProperty(pX11Display, pReq->requestor, pReq->property, pReq->target, 8, - PropModeReplace, (unsigned char *)pTask->pvData, pTask->cbData); - } - else - { - /* We don't support the requested format. Note that if the size of the - * request to the X server exceeds the maximum request size described in the - * X Consortium's Inter-Client Communication Conventions Manual (ICCM) - * section 2.5 'Large Data Transfers' then the requestor will send a target - * of type 'INCR' meaning the data will be sent incrementally. The Xlib - * Programming Manual describes how the maximum request size can be - * calculated: - * maxsize = XExtendedMaxRequestSize(); - * if (!maxsize) maxsize = XMaxRequestSize(); - * maxsize *= 4 - * On Solaris 11.4 and various Linux distros the lower bound is 64K * 4 which - * is greater than the possible maximum size passed in here of 8K so there is - * no need to include support for the 'INCR' target here. - */ - selectionNotifyEvent.property = None; - } - - /* Send a 'SelectionNotify' event to the requestor of the clipboard - * selection contents with either the list of supported targets or else the - * clipboard contents in their chosen property. */ - XSendEvent(pX11Display, selectionNotifyEvent.requestor, True, 0, (XEvent *)&selectionNotifyEvent); - XFlush(pX11Display); - } - } - else - RTThreadSleep(RT_MS_1SEC / 2); - } - - XDestroyWindow(pX11Display, window); - XCloseDisplay(pX11Display); - - return VINF_SUCCESS; -} -#endif - -static DECLCALLBACK(int) tstTestReadFromHostSetup(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx) -{ - RT_NOREF(ppvCtx); - int rc = VINF_SUCCESS; - - /* Set the right clipboard mode, so that the guest can read from the host. */ - tstClipboardSetMode(TstHgcmMockSvcInst(), VBOX_SHCL_MODE_BIDIRECTIONAL); - - /* Start the host thread first, so that the guest thread can connect to it later. */ - TSTHGCMUTILSHOSTCALLBACKS HostCallbacks; - RT_ZERO(HostCallbacks); - HostCallbacks.pfnOnClientConnected = tstTestReadFromHost_ClientConnectedCallback; - TstHGCMUtilsHostThreadStart(&pTstCtx->HGCM, &HostCallbacks, pTstCtx /* pvUser */); - - PCLIPBOARDTESTTASK pTask = &pTstCtx->Task; - AssertPtr(pTask); - pTask->enmFmtGst = VBOX_SHCL_FMT_UNICODETEXT; - pTask->enmFmtHst = pTask->enmFmtGst; - pTask->cbChunk = RTRandU32Ex(1, 512); - pTask->cbData = RT_ALIGN_32(pTask->cbChunk * RTRandU32Ex(1, 16), 2); - Assert(pTask->cbData % sizeof(RTUTF16) == 0); -#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) - pTask->pvData = tstGenerateUtf8StringA(pTask->cbData); - pTask->cbData++; /* Add terminating zero. */ - pTask->X11.fShutdown = false; - rc = RTThreadCreate(&pTask->X11.hThread, tstSetClipboardContents, pTask, 0, RTTHREADTYPE_DEFAULT, - RTTHREADFLAGS_WAITABLE, "X11Copy"); - if (RT_SUCCESS(rc)) - rc = RTThreadUserWait(pTask->X11.hThread, RT_MS_5SEC); - if (RT_FAILURE(rc)) - return VERR_NOT_SUPPORTED; - -#else - pTask->pvData = tstGenerateUtf16StringA((uint32_t)(pTask->cbData /* We use bytes == chars here */)); - pTask->cbData *= sizeof(RTUTF16); - pTask->cbData += sizeof(RTUTF16); /* Add terminating zero. */ -#endif - pTask->cbProcessed = 0; - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) - /* Initialize the Shared Clipboard backend callbacks. */ - PSHCLBACKEND pBackend = ShClSvcGetBackend(); - - SHCLCALLBACKS ShClCallbacks; - RT_ZERO(ShClCallbacks); - ShClCallbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback; - ShClCallbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback; - ShClBackendSetCallbacks(pBackend, &ShClCallbacks); -#elif defined (RT_OS_WINDOWS) - rc = ShClWinOpen(GetDesktopWindow()); - if (RT_SUCCESS(rc)) - { - rc = ShClWinDataWrite(CF_UNICODETEXT, pTask->pvData, (uint32_t)pTask->cbData); - ShClWinClose(); - } -#endif /* defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */ - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host data (%RU32):\n%.*Rhxd\n", pTask->cbData, pTask->cbData, pTask->pvData); - return rc; -} - -static DECLCALLBACK(int) tstTestReadFromHostExec(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx) -{ - RT_NOREF(pvCtx); - - RTTestISub("Testing guest reading from the host clipboard"); - - TstHGCMUtilsGuestThreadStart(&pTstCtx->HGCM, tstTestReadFromHost_ThreadGuest, pTstCtx); - - PTSTHGCMUTILSTASK pHGCMTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(&pTstCtx->HGCM); - - bool fUseMock = false; - TSTUSERMOCK UsrMock; - if (fUseMock) - tstTestReadFromHost_MockInit(&UsrMock, "tstX11Hst"); - - /* Wait until the task has been finished. */ - TstHGCMUtilsTaskWait(pHGCMTask, RT_MS_30SEC); - - if (fUseMock) - tstTestReadFromHost_MockDestroy(&UsrMock); - - return VINF_SUCCESS; -} - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -/** - * Stops the X11 "copy" thread (tstSetClipboardContents()). - * - * @return VBox status code. - * @param pTstCtx A pointer to a clipboard test task which contains - * the X11 "copy" thread's details. - */ -static int tstClipboardCopyThreadStop(PCLIPBOARDTESTCTX pTstCtx) -{ - PCLIPBOARDTESTTASK pTask = &pTstCtx->Task; - ASMAtomicWriteBool(&pTask->X11.fShutdown, true); - - int rcThread; - int rc = RTThreadWait(pTask->X11.hThread, RT_MS_30SEC, &rcThread); - if (RT_SUCCESS(rc)) - rc = rcThread; - if (RT_SUCCESS(rc)) - pTask->X11.hThread = NIL_RTTHREAD; - - return rc; -} -#endif - -static DECLCALLBACK(int) tstTestReadFromHostDestroy(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx) -{ - RT_NOREF(pvCtx); - int vrc; - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) - vrc = tstClipboardCopyThreadStop(pTstCtx); - if (RT_FAILURE(vrc)) - RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "tstSetClipboardContents() failed: rc=%Rrc\n", vrc); -#endif - - vrc = TstHGCMUtilsGuestThreadStop(&pTstCtx->HGCM); - AssertRC(vrc); - - vrc = TstHGCMUtilsHostThreadStop(&pTstCtx->HGCM); - AssertRC(vrc); - - return vrc; -} - - -/********************************************************************************************************************************* -* Main * -*********************************************************************************************************************************/ - -/** Test definition table. */ -CLIPBOARDTESTDESC g_aTests[] = -{ - /* Tests guest reading clipboard data from the host. */ - { tstTestReadFromHostSetup, tstTestReadFromHostExec, tstTestReadFromHostDestroy } -}; -/** Number of tests defined. */ -unsigned g_cTests = RT_ELEMENTS(g_aTests); - -static int tstOne(PTESTDESC pTstDesc) -{ - PCLIPBOARDTESTCTX pTstCtx = &g_TstCtx; - - void *pvCtx; - int rc = pTstDesc->pfnSetup(pTstCtx, &pvCtx); - if (RT_SUCCESS(rc)) - { - rc = pTstDesc->pfnExec(pTstCtx, pvCtx); - - int rc2 = pTstDesc->pfnDestroy(pTstCtx, pvCtx); - if (RT_SUCCESS(rc)) - rc = rc2; - } - - return rc; -} - -int main() -{ - /* - * Init the runtime, test and say hello. - */ - RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardMockHGCM", &g_hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - RTTestBanner(g_hTest); - - PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst(); - TstHgcmMockSvcCreate(pSvc); - TstHgcmMockSvcStart(pSvc); - - RT_ZERO(g_TstCtx); - - PTSTHGCMUTILSCTX pCtx = &g_TstCtx.HGCM; - TstHGCMUtilsCtxInit(pCtx, pSvc); - - PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(pCtx); - TstHGCMUtilsTaskInit(pTask); - pTask->pvUser = &g_TstCtx.Task; - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - testTransferStatusContextRouting(); -#endif - -#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) - testX11UnavailableBackend(); -#endif - - /* - * Run the remaining guest/backend tests only when an X11 display is - * available on Unix systems. - */ -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) - VBGHDISPLAYSERVERTYPE const enmDisplayType = VBGHDisplayServerTypeDetect(); - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Display server type = %s\n", VBGHDisplayServerTypeToStr(enmDisplayType)); - if (enmDisplayType == VBGHDISPLAYSERVERTYPE_X11) -#endif - { - for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) - tstOne(&g_aTests[i]); - - testGuestSimple(); - } - - testHostCall(); - - TstHGCMUtilsTaskDestroy(pTask); - - TstHgcmMockSvcStop(pSvc); - TstHgcmMockSvcDestroy(pSvc); - - /* - * Summary - */ - return RTTestSummaryAndDestroy(g_hTest); -} diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp deleted file mode 100644 index 23654962289a..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp +++ /dev/null @@ -1,1161 +0,0 @@ -/* $Id: tstClipboardServiceHost.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard host service test case. - */ - -/* - * Copyright (C) 2011-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#define LOG_ENABLED -#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD -#include - -#include -#include -#include -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# include -# include "VBoxSharedClipboardSvc-transfers.h" -#endif - -#include -#include -#include -#include -#include - -extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable); -/** - * The following no-op functions which correspond to their Shared Clipboard - * backend namesakes (ShClBackend*()) are used by the dispatcher function - * below (tstHgcmMockSvcDispatcher()) for intercepting unused backend calls. - * - * Note: These host service tests exercise the HGCM service layer, - * not the platform clipboard backends! - */ -static int tstShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) { pBackend->pHelpers = pTable->pHelpers; return VINF_SUCCESS; } -static void tstShClBackendDestroy(PSHCLBACKEND) { } -static int tstShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } -static int tstShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } -static int tstShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } -static int tstShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } -static int tstShClBackendReportFormatsToGuest(PSHCLBACKEND, PSHCLCLIENT, uint32_t) { AssertFailed(); return VINF_SUCCESS; } -static const void *g_pvBackendReadData = NULL; -static uint32_t g_cbBackendReadData = 0; -static SHCLFORMAT g_uBackendReadFormat = VBOX_SHCL_FMT_NONE; -static uint32_t g_cBackendReadDataCalls = 0; -static int tstShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT uFormat, - void *pvData, uint32_t cbData, uint32_t *pcbActual) -{ - g_cBackendReadDataCalls++; - AssertPtrReturn(g_pvBackendReadData, VERR_WRONG_ORDER); - AssertReturn(uFormat == g_uBackendReadFormat, VERR_INVALID_PARAMETER); - AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); - - *pcbActual = g_cbBackendReadData; - if (g_cbBackendReadData <= cbData) - memcpy(pvData, g_pvBackendReadData, g_cbBackendReadData); - return VINF_SUCCESS; -} -static int tstShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } - -static SHCLCLIENT g_Client; -static VBOXHGCMSVCHELPERS g_Helpers = { NULL }; - -/** Simple call handle structure for the guest call completion callback */ -struct VBOXHGCMCALLHANDLE_TYPEDEF -{ - /** Where to store the result code */ - int32_t rc; -}; - -/** Call completion callback for guest calls. */ -static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) -{ - callHandle->rc = rc; - return VINF_SUCCESS; -} - -/** - * A copy of the GuestShCl::hgcmDispatcher() dispatcher routine which - * handles callbacks from the Shared Clipboard host service. For the - * variety of tests here we only need ShClBackendInit() to get called to - * setup the shared clipboard for the tests and the remainder of the - * backend routines are routed to no-op equivalent routines. - */ -DECLCALLBACK(int) tstHgcmMockSvcDispatcher(void *pvExtension, uint32_t u32Function, - void *pvParms, uint32_t cbParms) -{ - NOREF(pvExtension); - int rc = VINF_SUCCESS; - PSHCLEXTPARMS pParms = (PSHCLEXTPARMS)pvParms; /* pParms might be NULL, depending on the message. */ - - LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); - - switch (u32Function) - { - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: // via VBOX_SHCL_GUEST_FN_REPORT_FORMATS in the guest - { - PSHCLCLIENT pClient = pParms->u.ReportFormats.pClient; - SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; - - rc = tstShClBackendReportFormats(pClient->pBackend, pClient, fFormats); - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Reporting guest clipboard formats to the host failed with %Rrc\n", rc)); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - { - PSHCLCLIENT pClient = pParms->u.ReportFormats.pClient; - SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; - - rc = tstShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: // via VBOX_SHCL_GUEST_FN_DATA_READ in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - SHCLCLIENTCMDCTX cmdCtx; - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - rc = tstShClBackendReadData(pClient->pBackend, pClient, &cmdCtx, fFormats, pvData, cbData, - &pParms->u.ReadWriteData.cbActual); - if (RT_SUCCESS(rc)) - LogRel2(("Shared Clipboard: Read host clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, - pParms->u.ReadWriteData.cbActual)); - else - LogRel(("Shared Clipboard: Reading host clipboard data failed with %Rrc\n", rc)); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: // via VBOX_SHCL_GUEST_FN_DATA_WRITE in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - PSHCLCLIENTCMDCTX pCmdCtx = pParms->u.ReadWriteData.pCmdCtx; - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - rc = tstShClBackendWriteData(pClient->pBackend, pClient, pCmdCtx, fFormats, pvData, cbData); - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Writing guest clipboard data to the host failed with %Rrc\n", rc)); - /* Complete any pending events. */ - int rc2 = ShClSvcGuestDataSignal(pClient, pCmdCtx, fFormats, pvData, cbData); - if (RT_FAILURE(rc2)) - LogRel(("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", rc2)); - AssertRC(rc2); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - VBOXHGCMSVCFNTABLE *pTable = pParms->u.ReadWriteData.pTable; - rc = tstShClBackendInit(pBackend, pTable); - break; - } - - // via VbglR3HGCMDisconnect()->...HGCMService::DisconnectClient()->...HGCMService::instanceDestroy()->...shClSvcUnload() - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - tstShClBackendDestroy(pBackend); - rc = VINF_SUCCESS; - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: // via VbglR3ClipboardConnect()->VbglR3HGCMConnect() in the guest - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = tstShClBackendConnect(pBackend, pClient); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: // via VbglR3ClipboardDisconnect()->VbglR3HGCMDisconnect() in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = tstShClBackendDisconnect(pClient->pBackend, pClient); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = tstShClBackendSync(pBackend, pClient); - break; - } - - default: - break; - } - - return rc; -} - -static int setupTable(VBOXHGCMSVCFNTABLE *pTable) -{ - pTable->cbSize = sizeof(*pTable); - pTable->u32Version = VBOX_HGCM_SVC_VERSION; - g_Helpers.pfnCallComplete = callComplete; - pTable->pHelpers = &g_Helpers; - int rc = VBoxHGCMSvcLoad(pTable); - RTTESTI_CHECK_MSG_RET(RT_SUCCESS(rc), ("rc=%Rrc\n", rc), rc); - return pTable->pfnRegisterExtension(pTable->pvService, tstHgcmMockSvcDispatcher, NULL); -} - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** Issues a host clipboard data read as a guest. */ -static int testHostDataReadCall(VBOXHGCMSVCFNTABLE *pTable, SHCLFORMAT uFormat, - void *pvData, uint32_t cbData, uint32_t *pcbActual) -{ - VBOXHGCMSVCPARM aParms[VBOX_SHCL_CPARMS_DATA_READ]; - HGCMSvcSetU32(&aParms[0], uFormat); - HGCMSvcSetPv(&aParms[1], pvData, cbData); - HGCMSvcSetU32(&aParms[2], 0); - - VBOXHGCMCALLHANDLE_TYPEDEF Call; - Call.rc = VERR_IPE_UNINITIALIZED_STATUS; - pTable->pfnCall(NULL, &Call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aParms), aParms, 0); - if (pcbActual) - *pcbActual = aParms[2].u.uint32; - return Call.rc; -} -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - -static void testSetMode(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - uint32_t u32Mode; - int rc; - - RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - /* Reset global variable which doesn't reset itself. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - u32Mode = ShClSvcGetMode(); - RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); - - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - HGCMSvcSetU64(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_HOST_TO_GUEST); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - u32Mode = ShClSvcGetMode(); - RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_HOST_TO_GUEST, ("u32Mode=%u\n", (unsigned) u32Mode)); - - HGCMSvcSetU32(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_NOT_SUPPORTED); - - u32Mode = ShClSvcGetMode(); - RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -/** Tests that the legacy headless host function ID remains reserved and unimplemented. */ -static void testReservedHostFunction(void) -{ - VBOXHGCMSVCFNTABLE table; - - RTTestISub("Testing unimplemented VBOX_SHCL_HOST_FN_SET_HEADLESS"); - int rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 0, NULL); - RTTESTI_CHECK_RC(rc, VERR_NOT_IMPLEMENTED); - - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -static void testSetTransferMode(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - - RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE"); - int rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - /* Invalid parameter. */ - HGCMSvcSetU64(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - /* Invalid mode. */ - HGCMSvcSetU32(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); - - /* Enable transfers. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - /* Disable transfers again. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_NONE); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -static void testGetTransferStatusMessage(VBOXHGCMSVCFNTABLE *pTable, SHCLSESSIONID idSessionExpected, - SHCLTRANSFERID idTransferExpected, SHCLTRANSFERSTATUS enmStatusExpected, - int rcTransferExpected) -{ - struct VBOXHGCMSVCPARM aStatusParms[VBOX_SHCL_CPARMS_TRANSFER_STATUS]; - VBOXHGCMCALLHANDLE_TYPEDEF call; - - HGCMSvcSetU64(&aStatusParms[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); - HGCMSvcSetU32(&aStatusParms[1], 0); - HGCMSvcSetU32(&aStatusParms[2], 0); - HGCMSvcSetU32(&aStatusParms[3], 0); - HGCMSvcSetU32(&aStatusParms[4], 0); - - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - pTable->pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatusParms), aStatusParms, 0); - RTTESTI_CHECK_RC_OK(call.rc); - uint64_t const uContext = aStatusParms[0].u.uint64; - RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(uContext) == idSessionExpected); - RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(uContext) == idTransferExpected); - RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != 0); - RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != NIL_SHCLEVENTID); - RTTESTI_CHECK(aStatusParms[2].u.uint32 == enmStatusExpected); - RTTESTI_CHECK((int32_t)aStatusParms[3].u.uint32 == rcTransferExpected); -} - -static void testSetTransferKeyParms(VBOXHGCMSVCPARM aParms[], SHCLSESSIONID idSession, - SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration) -{ - HGCMSvcSetU64(&aParms[0], VBOX_SHCL_CONTEXTID_MAKE(idSession, idTransfer, 0)); - HGCMSvcSetU64(&aParms[1], uGeneration); -} - -/** Tests short and exactly-sized host clipboard data reads. */ -static void testHostDataReadBufferSizing(void) -{ - VBOXHGCMSVCFNTABLE table; - VBOXHGCMSVCPARM parms[1]; - VBOXHGCMSVCPARM aReadParms[VBOX_SHCL_CPARMS_DATA_READ]; - VBOXHGCMCALLHANDLE_TYPEDEF call; - static uint8_t s_abData[_4K + 17]; - uint8_t abShort[_4K]; - uint8_t abExact[sizeof(s_abData)]; - - RTTestISub("Testing host data read buffer sizing"); - int rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, RT_ELEMENTS(parms), parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - for (uint32_t off = 0; off < sizeof(s_abData); off++) - s_abData[off] = (uint8_t)off; - g_pvBackendReadData = s_abData; - g_cbBackendReadData = sizeof(s_abData); - g_uBackendReadFormat = VBOX_SHCL_FMT_UNICODETEXT; - g_cBackendReadDataCalls = 0; - - HGCMSvcSetU32(&aReadParms[0], g_uBackendReadFormat); - HGCMSvcSetPv(&aReadParms[1], abShort, sizeof(abShort)); - HGCMSvcSetU32(&aReadParms[2], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aReadParms), aReadParms, 0); - RTTESTI_CHECK_RC(call.rc, VINF_BUFFER_OVERFLOW); - RTTESTI_CHECK(aReadParms[2].u.uint32 == sizeof(s_abData)); - - HGCMSvcSetPv(&aReadParms[1], abExact, sizeof(abExact)); - HGCMSvcSetU32(&aReadParms[2], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aReadParms), aReadParms, 0); - RTTESTI_CHECK_RC_OK(call.rc); - RTTESTI_CHECK(aReadParms[2].u.uint32 == sizeof(s_abData)); - RTTESTI_CHECK(memcmp(abExact, s_abData, sizeof(s_abData)) == 0); - RTTESTI_CHECK(g_cBackendReadDataCalls == 2); - - g_pvBackendReadData = NULL; - g_cbBackendReadData = 0; - g_uBackendReadFormat = VBOX_SHCL_FMT_NONE; - g_cBackendReadDataCalls = 0; - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC_OK(rc); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC_OK(rc); -} - -/** Tests validation and feature gating for host clipboard data reads. */ -static void testHostDataReadValidation(void) -{ - VBOXHGCMSVCFNTABLE table; - VBOXHGCMSVCPARM Parm; - static const char s_szUriList[] = "file:///private/host-file.txt\r\n"; - char szData[sizeof(s_szUriList)]; - - RTTestISub("Testing host data read validation"); - int rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&Parm, VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, &Parm); - RTTESTI_CHECK_RC_OK(rc); - - HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_NONE); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - g_pvBackendReadData = s_szUriList; - g_cbBackendReadData = sizeof(s_szUriList); - g_uBackendReadFormat = VBOX_SHCL_FMT_UNICODETEXT; - g_cBackendReadDataCalls = 0; - - g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_NONE; - uint32_t cbActual = 0; - rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData), &cbActual); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(cbActual == sizeof(s_szUriList)); - RTTESTI_CHECK(memcmp(szData, s_szUriList, sizeof(s_szUriList)) == 0); - RTTESTI_CHECK(g_cBackendReadDataCalls == 1); - - g_uBackendReadFormat = VBOX_SHCL_FMT_URI_LIST; - g_cBackendReadDataCalls = 0; - g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_URI_LIST, szData, sizeof(szData), &cbActual); - RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); - RTTESTI_CHECK(g_cBackendReadDataCalls == 0); - - HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); - RTTESTI_CHECK_RC_OK(rc); - - static const uint64_t s_afMissingFeatures[] = - { - VBOX_SHCL_GF_NONE, - VBOX_SHCL_GF_0_CONTEXT_ID, - VBOX_SHCL_GF_0_TRANSFERS - }; - for (size_t i = 0; i < RT_ELEMENTS(s_afMissingFeatures); i++) - { - g_Client.State.fGuestFeatures0 = s_afMissingFeatures[i]; - rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_URI_LIST, szData, sizeof(szData), &cbActual); - RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); - RTTESTI_CHECK(g_cBackendReadDataCalls == 0); - } - - g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_URI_LIST, szData, sizeof(szData), &cbActual); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(cbActual == sizeof(s_szUriList)); - RTTESTI_CHECK(memcmp(szData, s_szUriList, sizeof(s_szUriList)) == 0); - RTTESTI_CHECK(g_cBackendReadDataCalls == 1); - - rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_URI_LIST | VBOX_SHCL_FMT_UNICODETEXT, - szData, sizeof(szData), &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - RTTESTI_CHECK(g_cBackendReadDataCalls == 1); - - rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_NONE, szData, sizeof(szData), &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - RTTESTI_CHECK(g_cBackendReadDataCalls == 1); - - rc = testHostDataReadCall(&table, VBOX_SHCL_FMT_VALID_MASK + 1, szData, sizeof(szData), &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - RTTESTI_CHECK(g_cBackendReadDataCalls == 1); - - HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_NONE); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); - RTTESTI_CHECK_RC_OK(rc); - - g_pvBackendReadData = NULL; - g_cbBackendReadData = 0; - g_uBackendReadFormat = VBOX_SHCL_FMT_NONE; - g_cBackendReadDataCalls = 0; - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC_OK(rc); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC_OK(rc); -} - -/** - * Tests transfer format filtering for disabled or unsupported transfers. - */ -static void testTransferFormatFiltering(void) -{ - RTTestISub("Testing transfer format filtering"); - - static const struct - { - const char *pszName; - uint32_t fTransferMode; - uint64_t fGuestFeatures0; - SHCLFORMATS fExpectedHostToGuest; - SHCLFORMATS fExpectedGuestToHost; - } s_aTests[] = - { - { "disabled", VBOX_SHCL_TRANSFER_MODE_F_NONE, - VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS, - VBOX_SHCL_FMT_UNICODETEXT, VBOX_SHCL_FMT_UNICODETEXT }, - { "no features", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, VBOX_SHCL_GF_NONE, - VBOX_SHCL_FMT_UNICODETEXT, VBOX_SHCL_FMT_UNICODETEXT }, - { "transfers", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, VBOX_SHCL_GF_0_TRANSFERS, - VBOX_SHCL_FMT_UNICODETEXT, VBOX_SHCL_FMT_UNICODETEXT }, - { "context-id", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, VBOX_SHCL_GF_0_CONTEXT_ID, - VBOX_SHCL_FMT_UNICODETEXT, VBOX_SHCL_FMT_UNICODETEXT }, - { "7.2 transfer", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, - VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS, - VBOX_SHCL_FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST | VBOX_SHCL_FMT_UNICODETEXT }, - { "frontend", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, - VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS | VBOX_SHCL_GF_0_TRANSFERS_FRONTEND, - VBOX_SHCL_FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST | VBOX_SHCL_FMT_UNICODETEXT } - }; - - SHCLFORMATS const fInput = VBOX_SHCL_FMT_URI_LIST | VBOX_SHCL_FMT_UNICODETEXT; - for (size_t i = 0; i < RT_ELEMENTS(s_aTests); i++) - { - RT_ZERO(g_Client); - g_Client.State.Transfers.uTransferMode = s_aTests[i].fTransferMode; - g_Client.State.fGuestFeatures0 = s_aTests[i].fGuestFeatures0; - for (unsigned iDirection = 0; iDirection < 2; iDirection++) - { - bool const fHostToGuest = iDirection == 0; - SHCLFORMATS const fExpected = fHostToGuest ? s_aTests[i].fExpectedHostToGuest - : s_aTests[i].fExpectedGuestToHost; - SHCLFORMATS const fFiltered = shClSvcHandleFormats(fHostToGuest, &g_Client, fInput); - RTTESTI_CHECK_MSG(fFiltered == fExpected, - ("%s/%s fFiltered=%#x expected=%#x\n", s_aTests[i].pszName, - fHostToGuest ? "H2G" : "G2H", fFiltered, fExpected)); - } - } -} - - -/** - * Tests that transfer messages require both guest feature bits. - */ -static void testTransferGuestFeatures(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - - RTTestISub("Testing transfer guest features"); - int rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU64(&parms[0], VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, 42, 0)); - HGCMSvcSetU64(&parms[1], 1); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(parms), parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_NOT_IMPLEMENTED); - - g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_0_TRANSFERS; - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(parms), parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_NOT_IMPLEMENTED); - - g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID; - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(parms), parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_ACCESS_DENIED); - - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_TRANSFERS; - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(parms), parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -static void testTransferHostCancelError(void) -{ - struct VBOXHGCMSVCPARM parms[3]; - struct VBOXHGCMSVCPARM aObjCloseParms[VBOX_SHCL_CPARMS_OBJ_CLOSE]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - - RTTestISub("Testing transfer host cancel/error"); - int rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - RTTESTI_CHECK(g_Client.State.uSessionID != 0); - RTTESTI_CHECK(g_Client.State.uSessionID != NIL_SHCLSESSIONID); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - - struct VBOXHGCMSVCPARM aReplyParms[VBOX_SHCL_CPARMS_REPLY_MIN + 1]; - HGCMSvcSetU64(&aReplyParms[0], 0 /* no transfer context for SHCLTRANSFERSTATUS_REQUESTED */); - HGCMSvcSetU32(&aReplyParms[1], VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS); - HGCMSvcSetU32(&aReplyParms[2], VINF_SUCCESS); - HGCMSvcSetPv(&aReplyParms[3], NULL, 0); - HGCMSvcSetU32(&aReplyParms[4], SHCLTRANSFERSTATUS_REQUESTED); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_REPLY, RT_ELEMENTS(aReplyParms), aReplyParms, 0); - RTTESTI_CHECK_RC_OK(call.rc); - PSHCLTRANSFER pTransferRequested = ShClTransferCtxGetTransferByIndex(&g_Client.Transfers.Ctx, 0); - RTTESTI_CHECK(pTransferRequested != NULL); - SHCLTRANSFERID const idTransferRequested = ShClTransferGetID(pTransferRequested); - testGetTransferStatusMessage(&table, g_Client.State.uSessionID, idTransferRequested, SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS); - ShClSvcTransferDestroyById(&g_Client, idTransferRequested); - - SHCLSESSIONID const idSessionBeforeReset = g_Client.State.uSessionID; - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 0, parms); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(g_Client.State.uSessionID != idSessionBeforeReset); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - - HGCMSvcSetU32(&parms[0], 42); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - testSetTransferKeyParms(parms, g_Client.State.uSessionID, 42, 1); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 3, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - PSHCLTRANSFER pTransfer; - rc = ShClSvcTransferCreate(&g_Client, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, - NIL_SHCLTRANSFERID, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - SHCLSESSIONID const idSessionCancel = ShClTransferGetSessionId(pTransfer); - SHCLTRANSFERID const idTransferCancel = ShClTransferGetID(pTransfer); - SHCLTRANSFERGEN const uGenerationCancel = ShClTransferGetGeneration(pTransfer); - RTTESTI_CHECK(idSessionCancel == g_Client.State.uSessionID); - RTTESTI_CHECK(uGenerationCancel != 0); - RTTESTI_CHECK(uGenerationCancel != NIL_SHCLTRANSFERGEN); - ShClTransferRelease(pTransfer); - pTransfer = NULL; - - testSetTransferKeyParms(parms, idSessionCancel + 1, idTransferCancel, uGenerationCancel); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferCancel) != NULL); - - testSetTransferKeyParms(parms, idSessionCancel, idTransferCancel, uGenerationCancel + 1); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferCancel) != NULL); - - HGCMSvcSetU64(&aObjCloseParms[0], VBOX_SHCL_CONTEXTID_MAKE(idSessionCancel + 1, idTransferCancel, 0)); - HGCMSvcSetU64(&aObjCloseParms[1], 1); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjCloseParms), aObjCloseParms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_INVALID_CONTEXT); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferCancel) != NULL); - - testSetTransferKeyParms(parms, idSessionCancel, idTransferCancel, uGenerationCancel); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 2, parms); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferCancel) == NULL); - testGetTransferStatusMessage(&table, idSessionCancel, idTransferCancel, SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); - - HGCMSvcSetU64(&aObjCloseParms[0], VBOX_SHCL_CONTEXTID_MAKE(idSessionCancel, idTransferCancel, 0)); - HGCMSvcSetU64(&aObjCloseParms[1], 1); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjCloseParms), aObjCloseParms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - - testSetTransferKeyParms(parms, g_Client.State.uSessionID, 42, 1); - HGCMSvcSetU32(&parms[2], (uint32_t)VERR_ACCESS_DENIED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 3, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - - rc = ShClSvcTransferCreate(&g_Client, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, - NIL_SHCLTRANSFERID, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - SHCLSESSIONID const idSessionError = ShClTransferGetSessionId(pTransfer); - SHCLTRANSFERID const idTransferError = ShClTransferGetID(pTransfer); - SHCLTRANSFERGEN const uGenerationError = ShClTransferGetGeneration(pTransfer); - ShClTransferRelease(pTransfer); - pTransfer = NULL; - - testSetTransferKeyParms(parms, idSessionError, idTransferError, uGenerationError); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - HGCMSvcSetU32(&parms[2], (uint32_t)VERR_CANCELLED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 3, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferError) != NULL); - - testSetTransferKeyParms(parms, idSessionError, idTransferError, uGenerationError + 1); - HGCMSvcSetU32(&parms[2], (uint32_t)VERR_ACCESS_DENIED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 3, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferError) != NULL); - - testSetTransferKeyParms(parms, idSessionError, idTransferError, uGenerationError); - HGCMSvcSetU32(&parms[2], (uint32_t)VERR_ACCESS_DENIED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 3, parms); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferError) == NULL); - testGetTransferStatusMessage(&table, idSessionError, idTransferError, SHCLTRANSFERSTATUS_ERROR, VERR_ACCESS_DENIED); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_NONE); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - -/* Adds a host data read request message to the client's message queue. */ -static void testMsgAddReadData(PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - int rc = ShClSvcReadDataFromGuestAsync(pClient, fFormats, NULL /* ppEvent */); - RTTESTI_CHECK_RC_OK(rc); -} - -/* Does testing of VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, needed for providing compatibility to older Guest Additions clients. */ -static void testGetHostMsgOld(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - int rc; - - RTTestISub("Setting up VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT test"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - /* Unless we are bidirectional the host message requests will be dropped. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - - RTTestISub("Testing one format, waiting guest call."); - RT_ZERO(g_Client); - HGCMSvcSetU32(&parms[0], 0); - HGCMSvcSetU32(&parms[1], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */ - testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ - table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - - RTTestISub("Testing one format, no waiting guest calls."); - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_HTML); - HGCMSvcSetU32(&parms[0], 0); - HGCMSvcSetU32(&parms[1], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ - table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - - RTTestISub("Testing two formats, waiting guest call."); - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - HGCMSvcSetU32(&parms[0], 0); - HGCMSvcSetU32(&parms[1], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */ - testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ - table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - - RTTestISub("Testing two formats, no waiting guest calls."); - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); - HGCMSvcSetU32(&parms[0], 0); - HGCMSvcSetU32(&parms[1], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ - table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - table.pfnUnload(NULL); -} - -/** - * Tests late guest data replies for an expired host wait event. - * - * The test creates a context-ID capable client, queues an asynchronous - * read-data request, drains the resulting READ_DATA_CID host message, and then - * releases the associated event to simulate the host-side read timing out. It - * finally calls ShClSvcGuestDataSignal() with the stale context ID, as a guest - * would do when its DATA_WRITE reply arrives after the host has stopped waiting. - * - * This is needed because guest response timing is guest-controlled and cannot - * be trusted. The expected behavior is to drop the - * stale payload and report success to the service dispatcher, because there is - * no remaining host waiter that can consume the data. - */ -static void testGuestDataSignalExpiredEvent(void) -{ - struct VBOXHGCMSVCPARM parms[1]; - struct VBOXHGCMSVCPARM aMsgParms[2]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - int rc; - - RTTestISub("Testing late guest data for expired event"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID; - - PSHCLEVENT pEvent = NULL; - rc = ShClSvcReadDataFromGuestAsync(&g_Client, VBOX_SHCL_FMT_UNICODETEXT, &pEvent); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK_RETV(pEvent != NULL); - - uint64_t const uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID, pEvent->idEvent); - - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - HGCMSvcSetU64(&aMsgParms[0], VBOX_SHCL_HOST_MSG_READ_DATA_CID); - HGCMSvcSetU32(&aMsgParms[1], 0); - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_MSG_GET, 2, aMsgParms, 0); - RTTESTI_CHECK_RC_OK(call.rc); - RTTESTI_CHECK(aMsgParms[0].u.uint64 == uContextID); - RTTESTI_CHECK(aMsgParms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); - - SHCLCLIENTCMDCTX cmdCtx; - RT_ZERO(cmdCtx); - cmdCtx.uContextID = uContextID; - - char szData[] = "late data"; - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC_OK(rc); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -/** - * Tests guest data reply validation for still pending host wait events. - */ -static void testGuestDataSignalRejectsMismatches(void) -{ - struct VBOXHGCMSVCPARM parms[1]; - struct VBOXHGCMSVCPARM aMsgParms[2]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - int rc; - - RTTestISub("Testing guest data context validation"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID; - - PSHCLEVENT pEvent = NULL; - rc = ShClSvcReadDataFromGuestAsync(&g_Client, VBOX_SHCL_FMT_UNICODETEXT, &pEvent); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK_RETV(pEvent != NULL); - - uint64_t const uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID, pEvent->idEvent); - - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - HGCMSvcSetU64(&aMsgParms[0], VBOX_SHCL_HOST_MSG_READ_DATA_CID); - HGCMSvcSetU32(&aMsgParms[1], 0); - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_MSG_GET, 2, aMsgParms, 0); - RTTESTI_CHECK_RC_OK(call.rc); - RTTESTI_CHECK(aMsgParms[0].u.uint64 == uContextID); - RTTESTI_CHECK(aMsgParms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - - SHCLCLIENTCMDCTX cmdCtx; - RT_ZERO(cmdCtx); - char szData[] = "pending data"; - - cmdCtx.uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID, 0); - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC(rc, VERR_WRONG_ORDER); - - cmdCtx.uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID + 1, - g_Client.EventSrc.uID, pEvent->idEvent); - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); - - cmdCtx.uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID + 1, pEvent->idEvent); - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); - - cmdCtx.uContextID = uContextID; - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_HTML, szData, sizeof(szData)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); - - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); - - PSHCLEVENT pMultiEvent = NULL; - rc = ShClSvcReadDataFromGuestAsync(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML, &pMultiEvent); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - RTTESTI_CHECK(pMultiEvent == NULL); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -/** - * Tests guest DATA_WRITE format validation before forwarding data to the backend. - */ -static void testGuestDataWriteRejectsInvalidFormats(void) -{ - struct VBOXHGCMSVCPARM parms[1]; - struct VBOXHGCMSVCPARM aWriteParms[VBOX_SHCL_CPARMS_DATA_WRITE]; - static const SHCLFORMAT s_aInvalidFormats[] = - { - VBOX_SHCL_FMT_NONE, - VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML, - VBOX_SHCL_FMT_VALID_MASK + 1 - }; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - char szData[] = "invalid format data"; - int rc; - - RTTestISub("Testing guest DATA_WRITE invalid format rejection"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID; - - for (size_t i = 0; i < RT_ELEMENTS(s_aInvalidFormats); i++) - { - RTTestISubF("Testing guest DATA_WRITE invalid format %#x", s_aInvalidFormats[i]); - HGCMSvcSetU64(&aWriteParms[0], VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID, 1)); - HGCMSvcSetU32(&aWriteParms[1], s_aInvalidFormats[i]); - aWriteParms[2].type = VBOX_HGCM_SVC_PARM_PTR; - aWriteParms[2].u.pointer.addr = szData; - aWriteParms[2].u.pointer.size = sizeof(szData); - - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWriteParms), aWriteParms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_INVALID_PARAMETER); - } - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -static void testHostCall(void) -{ - testSetMode(); - testReservedHostFunction(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - testSetTransferMode(); - testTransferFormatFiltering(); - testTransferGuestFeatures(); - testTransferHostCancelError(); - testHostDataReadBufferSizing(); - testHostDataReadValidation(); -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - testGuestDataSignalExpiredEvent(); - testGuestDataSignalRejectsMismatches(); - testGuestDataWriteRejectsInvalidFormats(); -} - -int main(int argc, char *argv[]) -{ - /* - * Init the runtime, test and say hello. - */ - const char *pcszExecName; - NOREF(argc); - pcszExecName = strrchr(argv[0], '/'); - pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - RTTestBanner(hTest); - - /* Don't let assertions in the host service panic (core dump) the test cases. */ - RTAssertSetMayPanic(false); - - /* - * Run the tests. - */ - testHostCall(); - testGetHostMsgOld(); - - /* - * Summary - */ - return RTTestSummaryAndDestroy(hTest); -} - - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER, SHCLSOURCE, SHCLTRANSFERSTATUS, int) { return VINF_SUCCESS; } -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp deleted file mode 100644 index bc40897e4aa1..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* $Id: tstClipboardServiceImpl.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard host service implementation (backend) test case. - */ - -/* - * Copyright (C) 2020-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include -#include -#ifdef RT_OS_WINDOWS -# include -#endif - -#include -#include -#include - -extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *ptable); - -static SHCLCLIENT g_Client; -static VBOXHGCMSVCHELPERS g_Helpers = { NULL }; - -/** Simple call handle structure for the guest call completion callback */ -struct VBOXHGCMCALLHANDLE_TYPEDEF -{ - /** Where to store the result code */ - int32_t rc; -}; - -/** Call completion callback for guest calls. */ -static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) -{ - callHandle->rc = rc; - return VINF_SUCCESS; -} - -static int setupTable(VBOXHGCMSVCFNTABLE *pTable) -{ - pTable->cbSize = sizeof(*pTable); - pTable->u32Version = VBOX_HGCM_SVC_VERSION; - g_Helpers.pfnCallComplete = callComplete; - pTable->pHelpers = &g_Helpers; - return VBoxHGCMSvcLoad(pTable); -} - -int ShClBackendInit(PSHCLBACKEND, VBOXHGCMSVCFNTABLE *) { return VINF_SUCCESS; } -void ShClBackendDestroy(PSHCLBACKEND) { } -int ShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } -int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } -int ShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } -int ShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } -int ShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } -int ShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } - -static void testAnnounceAndReadData(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - int rc; - - RTTestISub("Setting up client ..."); - RTTestIDisableAssertions(); - - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - /* Unless we are bidirectional the host message requests will be dropped. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - rc = ShClSvcClientInit(&g_Client, 1 /* clientId */); - RTTESTI_CHECK_RC_OK(rc); - - RTTestIRestoreAssertions(); -} - -#ifdef RT_OS_WINDOWS -# include "VBoxOrgCfHtml1.h" /* From chrome 97.0.4692.71 */ -# include "VBoxOrgMimeHtml1.h" - -static void testHtmlCf(void) -{ - RTTestISub("CF_HTML"); - - char *pszOutput = NULL; - uint32_t cbOutput = UINT32_MAX/2; - RTTestIDisableAssertions(); - RTTESTI_CHECK_RC(ShClWinConvertCFHTMLToMIME("", 0, &pszOutput, &cbOutput), VERR_INVALID_PARAMETER); - RTTestIRestoreAssertions(); - - pszOutput = NULL; - cbOutput = UINT32_MAX/2; - RTTESTI_CHECK_RC(ShClWinConvertCFHTMLToMIME((char *)&g_abVBoxOrgCfHtml1[0], g_cbVBoxOrgCfHtml1, - &pszOutput, &cbOutput), VINF_SUCCESS); - RTTESTI_CHECK(cbOutput == g_cbVBoxOrgMimeHtml1); - RTTESTI_CHECK(memcmp(pszOutput, g_abVBoxOrgMimeHtml1, cbOutput) == 0); - RTMemFree(pszOutput); - - - static RTSTRTUPLE const s_aRoundTrips[] = - { - { RT_STR_TUPLE("") }, - { RT_STR_TUPLE("1") }, - { RT_STR_TUPLE("12") }, - { RT_STR_TUPLE("123") }, - { RT_STR_TUPLE("1234") }, - { RT_STR_TUPLE("12345") }, - { RT_STR_TUPLE("123456") }, - { RT_STR_TUPLE("1234567") }, - { RT_STR_TUPLE("12345678") }, - { RT_STR_TUPLE("123456789") }, - { RT_STR_TUPLE("1234567890") }, - { RT_STR_TUPLE("

asdfkjhasdflhj

") }, - { RT_STR_TUPLE("

asdfkjhasdflhj

\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") }, - { (const char *)g_abVBoxOrgMimeHtml1, sizeof(g_abVBoxOrgMimeHtml1) }, - }; - - for (size_t i = 0; i < RT_ELEMENTS(s_aRoundTrips); i++) - { - int rc; - char *pszCfHtml = NULL; - uint32_t cbCfHtml = UINT32_MAX/2; - rc = ShClWinConvertMIMEToCFHTML(s_aRoundTrips[i].psz, s_aRoundTrips[i].cch + 1, &pszCfHtml, &cbCfHtml); - if (rc == VINF_SUCCESS) - { - if (strlen(pszCfHtml) + 1 != cbCfHtml) - RTTestIFailed("#%u: ShClWinConvertMIMEToCFHTML(%s, %#zx,,) returned incorrect length: %#x, actual %#zx", - i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, cbCfHtml, strlen(pszCfHtml) + 1); - - char *pszHtml = NULL; - uint32_t cbHtml = UINT32_MAX/4; - rc = ShClWinConvertCFHTMLToMIME(pszCfHtml, (uint32_t)strlen(pszCfHtml), &pszHtml, &cbHtml); - if (rc == VINF_SUCCESS) - { - if (strlen(pszHtml) + 1 != cbHtml) - RTTestIFailed("#%u: ShClWinConvertCFHTMLToMIME(%s, %#zx,,) returned incorrect length: %#x, actual %#zx", - i, pszHtml, strlen(pszHtml), cbHtml, strlen(pszHtml) + 1); - if (strcmp(pszHtml, s_aRoundTrips[i].psz) != 0) - RTTestIFailed("#%u: roundtrip for '%s' LB %#zx failed, ended up with '%s'", - i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, pszHtml); - RTMemFree(pszHtml); - } - else - RTTestIFailed("#%u: ShClWinConvertCFHTMLToMIME(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS", - i, pszCfHtml, strlen(pszCfHtml), rc); - RTMemFree(pszCfHtml); - } - else - RTTestIFailed("#%u: ShClWinConvertMIMEToCFHTML(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS", - i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, rc); - } -} - -#endif /* RT_OS_WINDOWS */ - - -int main(int argc, char *argv[]) -{ - /* - * Init the runtime, test and say hello. - */ - const char *pcszExecName; - NOREF(argc); - pcszExecName = strrchr(argv[0], '/'); - pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - RTTestBanner(hTest); - - /* - * Run the tests. - */ - testAnnounceAndReadData(); -#ifdef RT_OS_WINDOWS - testHtmlCf(); -#endif - - /* - * Summary - */ - return RTTestSummaryAndDestroy(hTest); -} diff --git a/src/VBox/Main/Makefile.kmk b/src/VBox/Main/Makefile.kmk index 07516b936e65..3ae79339a47e 100644 --- a/src/VBox/Main/Makefile.kmk +++ b/src/VBox/Main/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114613 2026-07-03 15:57:50Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ ## @file # Makefile for the VBox Main module. # @@ -1303,12 +1303,12 @@ if !defined(VBOX_ONLY_SDK) && !defined(VBOX_ONLY_EXTPACKS) # Note this goes on f src-client/GuestSessionImpl.cpp endif ifdef VBOX_WITH_SHARED_CLIPBOARD - VBoxC_DEFS += VBOX_WITH_SHARED_CLIPBOARD_HOST VBoxC_SOURCES += \ + src-client/GuestShClBackend.cpp \ + src-client/GuestShClConn.cpp \ src-client/GuestShClHelpers.cpp \ src-client/GuestShClPrivate.cpp \ src-client/GuestShClSvcExt.cpp \ - src-client/VBoxSharedClipboardSvc-utils.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp if1of ($(KBUILD_TARGET), linux solaris freebsd) diff --git a/src/VBox/Main/include/GuestShClBackend.h b/src/VBox/Main/include/GuestShClBackend.h new file mode 100644 index 000000000000..675c94ecad6d --- /dev/null +++ b/src/VBox/Main/include/GuestShClBackend.h @@ -0,0 +1,160 @@ +/* $Id: GuestShClBackend.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Native backend dispatcher. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef MAIN_INCLUDED_GuestShClBackend_h +#define MAIN_INCLUDED_GuestShClBackend_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +class GuestShClConn; +struct SHCLBACKENDOPS; +/** Pointer to a native Shared Clipboard backend operation table. */ +typedef struct SHCLBACKENDOPS const *PCSHCLBACKENDOPS; + +/** + * Dispatches Shared Clipboard operations to a native platform backend. + * + * The selected operation table is fixed when the object is constructed. + * Platform implementations and their entry points remain private to VBoxC. + */ +class ShClBackend +{ +public: + /** Creates a dispatcher using the native backend selected for this host. */ + ShClBackend(void); + + /** Destroys the dispatcher. The backend must be disconnected first. */ + ~ShClBackend(void); + + /** + * Initializes the selected native backend. + * + * @returns VBox status code. + */ + int init(void); + + /** Destroys the selected native backend. */ + void destroy(void); + + /** + * Replaces the native backend callback table for test purposes. + * + * Does nothing when the selected backend does not implement callback + * replacement. + * + * @param pCallbacks Callback table, or NULL for backend defaults. + */ + void setCallbacks(PSHCLCALLBACKS pCallbacks); + + /** + * Connects a Main service connection to the selected native backend. + * + * @returns VBox status code. + * @param pConn Main connection owning the service endpoint. + */ + int connect(GuestShClConn *pConn); + + /** + * Disconnects the current native backend context. + * + * @returns VBox status code. + */ + int disconnect(void); + + /** + * Reports guest clipboard formats to the selected native backend. + * + * @returns VBox status code. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ + int reportFormats(SHCLFORMATS fFormats); + + /** + * Reads native clipboard data for the guest. + * + * @returns VBox status code. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the required or actual byte count. + */ + int readData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); + + /** + * Writes guest clipboard data to the selected native backend. + * + * @returns VBox status code. + * @param uFormat Clipboard format to write. + * @param pvData Clipboard data to write. + * @param cbData Clipboard data size in bytes. + */ + int writeData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData); + + /** + * Synchronizes the selected native backend with the guest. + * + * @returns VBox status code. + */ + int sync(void); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** + * Gets the selected native backend's callbacks for a new transfer. + * + * @param pCallbacks Where to return the callback table. + */ + void transferGetCallbacks(PSHCLTRANSFERCALLBACKS pCallbacks); + + /** + * Handles a transfer status reply in the selected native backend. + * + * @returns VBox status code. + * @param pTransfer Transfer whose status changed. + * @param enmSource Source issuing the reply. + * @param enmStatus New transfer status. + * @param rcStatus Status-specific VBox status code. + */ + int transferHandleStatusReply(PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus); +#endif + +private: + /** No copy construction. */ + ShClBackend(ShClBackend const &rThat); + /** No assignment. */ + ShClBackend &operator=(ShClBackend const &rThat); + + /** Selected native backend operation table; immutable. */ + PCSHCLBACKENDOPS m_pOps; + /** Opaque connection context owned by the selected native backend. */ + PSHCLCONTEXT m_pCtx; +}; + +#endif /* !MAIN_INCLUDED_GuestShClBackend_h */ diff --git a/src/VBox/Main/include/GuestShClConn.h b/src/VBox/Main/include/GuestShClConn.h new file mode 100644 index 000000000000..70df4fd98862 --- /dev/null +++ b/src/VBox/Main/include/GuestShClConn.h @@ -0,0 +1,363 @@ +/* $Id: GuestShClConn.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Service connection management. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef MAIN_INCLUDED_GuestShClConn_h +#define MAIN_INCLUDED_GuestShClConn_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "GuestShClBackend.h" + +#include +#include + +class GuestShCl; + +/** + * Represents Main's connection to one Shared Clipboard HGCM client. + * + * The HGCM service owns the opaque client referenced by the service endpoint, + * whereas this object owns the corresponding native backend context. Public + * operations pin both values internally so that service disconnect can wait + * for callers without exposing an ownership guard to users of this class. + */ +class GuestShClConn +{ +public: + /** + * Creates an empty Shared Clipboard connection. + * + * @param pOwner GuestShCl instance owning this connection. May be + * NULL in an out-of-process testcase. + * @throws VBox status code if internal synchronization cannot be created. + */ + GuestShClConn(GuestShCl *pOwner); + + /** + * Destroys the connection object. + * + * The native backend and service client must have been disconnected first. + */ + ~GuestShClConn(void); + + /** + * Initializes the process-wide native clipboard backend. + * + * @returns VBox status code. + */ + int initBackend(void); + + /** + * Disconnects the current client, if any, and destroys the native backend. + * + * @returns VBox status code from disconnecting the active backend context. + */ + int destroyBackend(void); + + /** + * Replaces the native backend callback table for test purposes. + * + * @param pCallbacks Callback table, or NULL for backend defaults. + */ + void setBackendCallbacks(PSHCLCALLBACKS pCallbacks); + + /** + * Connects a service endpoint to the native clipboard backend. + * + * @returns VBox status code. + * @param pTransport Service endpoint supplied by the HGCM service. + */ + int connect(PCSHCLTRANSPORT pTransport); + + /** + * Disconnects the service endpoint from the native clipboard backend. + * + * @returns VBox status code. + * @param pTransport Service endpoint being disconnected. + */ + int disconnect(PCSHCLTRANSPORT pTransport); + + /** + * Checks whether a service endpoint identifies the active connection. + * + * @returns true if @a pTransport identifies this connection, otherwise false. + * @param pTransport Service endpoint to compare. + */ + bool matches(PCSHCLTRANSPORT pTransport) const; + + /** + * Checks whether a service client is connected. + * + * @returns true if connected, otherwise false. + */ + bool isConnected(void) const; + + /** + * Queues a clipboard format announcement for the guest. + * + * @returns VBox status code. + * @param fFormats Formats to report, VBOX_SHCL_FMT_XXX. + * @param pfReported Where to return the filtered formats. Optional. + */ + int reportFormatsToGuest(SHCLFORMATS fFormats, SHCLFORMATS *pfReported = NULL); + + /** + * Publishes formats discovered by the native host clipboard backend. + * + * @returns VBox status code. + * @param fFormats Formats to publish, VBOX_SHCL_FMT_XXX. + */ + int reportLocalFormats(SHCLFORMATS fFormats); + + /** + * Requests clipboard data from the guest without waiting for a reply. + * + * @returns VBox status code. + * @param fFormats Requested formats, VBOX_SHCL_FMT_XXX. + * @param ppEvent Where to return the reply event. Optional. + */ + int readDataFromGuestAsync(SHCLFORMATS fFormats, PSHCLEVENT *ppEvent); + + /** + * Requests and waits for clipboard data from the guest. + * + * @returns VBox status code. + * @param uFormat Requested format, VBOX_SHCL_FMT_XXX. + * @param ppvData Where to return the allocated data buffer. + * @param pcbData Where to return the data size. + */ + int readDataFromGuest(SHCLFORMAT uFormat, void **ppvData, uint32_t *pcbData); + + /** + * Validates and retains a pending guest-data reply. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if the reply was retained or if its event already + * expired. In the latter case @a phToken is set to NULL. + * @param pCmdCtx Command context identifying the pending reply. + * @param uFormat Format carried by the reply. + * @param phToken Where to return the retained reply token, or + * NULL if the event already expired. A returned + * token pins the connection until it is passed to + * guestDataComplete() or guestDataCancel(). + */ + int guestDataBegin(PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken); + + /** + * Signals and releases a retained guest-data reply. + * + * @returns VBox status code. + * @param hToken Retained reply token returned by guestDataBegin(). + * @param pvData Reply data. Optional when @a cbData is zero. + * @param cbData Reply data size in bytes. + */ + int guestDataComplete(SHCLGUESTDATATOKEN hToken, void const *pvData, uint32_t cbData); + + /** + * Releases a retained guest-data reply without signalling it. + * + * @param hToken Retained reply token returned by guestDataBegin(). + */ + void guestDataCancel(SHCLGUESTDATATOKEN hToken); + + /** + * Synchronizes the native clipboard backend with the guest. + * + * @returns VBox status code. + */ + int syncBackend(void); + + /** + * Reports guest formats to the native clipboard backend. + * + * @returns VBox status code. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ + int reportFormatsToBackend(SHCLFORMATS fFormats); + + /** + * Reads data from the native clipboard backend. + * + * @returns VBox status code. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size. + * @param pcbActual Where to return the actual or required size. + */ + int readDataFromBackend(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); + + /** + * Writes guest data to the native clipboard backend. + * + * @returns VBox status code. + * @param uFormat Clipboard format to write. + * @param pvData Data buffer. Optional when @a cbData is zero. + * @param cbData Data size in bytes. + */ + int writeDataToBackend(SHCLFORMAT uFormat, void *pvData, uint32_t cbData); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** + * Returns the native backend callbacks for a new transfer. + * + * @returns VBox status code. + * @param pCallbacks Where to return the callback table. + */ + int transferGetCallbacks(PSHCLTRANSFERCALLBACKS pCallbacks); + + /** + * Handles a transfer status reply in the native backend. + * + * @returns VBox status code. + * @param pTransfer Transfer whose status changed. + * @param enmSource Endpoint issuing the reply. + * @param enmStatus New transfer status. + * @param rcStatus Status-specific VBox status code. + */ + int transferHandleStatusReply(PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus); + + /** + * Retains a service transfer selected by ID. + * + * @returns Retained transfer on success, or NULL if not found or disconnected. + * @param idTransfer Transfer ID to look up. + */ + PSHCLTRANSFER transferGetByIdRetained(SHCLTRANSFERID idTransfer); + + /** + * Retains a service transfer selected by its complete generation key. + * + * @returns Retained transfer on success, or NULL if not found or disconnected. + * @param idSession Service session ID. + * @param idTransfer Transfer ID. + * @param uGeneration Transfer generation. + */ + PSHCLTRANSFER transferGetByKeyRetained(SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration); + + /** + * Creates and retains a service-owned transfer. + * + * @returns VBox status code. + * @param enmDir Transfer direction. + * @param enmSource Transfer source. + * @param pCallbacks Transfer callback table. + * @param idTransfer Requested transfer ID. + * @param ppTransfer Where to return the retained transfer. + */ + int transferCreate(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, + SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer); + + /** + * Initializes a service-owned transfer. + * + * @returns VBox status code. + * @param pTransfer Transfer to initialize. + */ + int transferInit(PSHCLTRANSFER pTransfer); + + /** + * Destroys a service-owned transfer selected by ID. + * + * @param idTransfer Transfer ID to destroy. + */ + void transferDestroyById(SHCLTRANSFERID idTransfer); + + /** + * Destroys every transfer owned by the connected service client. + * + * This teardown operation remains available while disconnect is closing + * the connection because transfer callbacks can retain backend state. + */ + void transferDestroyAll(void); + + /** + * Initializes a provider which reads transfer data from the guest. + * + * @returns VBox status code. + * @param pProvider Provider to initialize. + */ + int transferProviderInitGuest(PSHCLTXPROVIDER pProvider); + +#endif + +private: + /** Internal connection lifecycle states. */ + enum State + { + /** No service client is associated with the object. */ + State_Disconnected = 0, + /** The native backend is establishing its per-client context. */ + State_Connecting, + /** The service client and native backend context are usable. */ + State_Connected, + /** New calls are blocked while existing calls and backend workers drain. */ + State_Closing + }; + + /** No copy construction. */ + GuestShClConn(GuestShClConn const &rThat); + /** No assignment. */ + GuestShClConn &operator=(GuestShClConn const &rThat); + + /** + * Begins an operation using the current connection. + * + * @returns VBox status code. + * @param pTransport Where to return the stable service endpoint. + */ + int i_callBegin(PSHCLTRANSPORT pTransport); + /** + * Finishes an operation begun by i_callBegin(). + */ + void i_callEnd(void); + /** + * Waits for every operation begun by i_callBegin() to finish. + */ + void i_waitForCalls(void); + + /** GuestShCl instance owning this object for the full object lifetime; immutable. */ + GuestShCl *m_pOwner; + /** Serializes connection state and active-call accounting. */ + mutable RTCRITSECT m_CritSect; + /** Signalled while no service or backend calls are active. */ + RTSEMEVENTMULTI m_hCallsDone; + /** Current lifecycle state; protected by m_CritSect. */ + State m_enmState; + /** Whether the process-wide native clipboard backend is initialized; protected by m_CritSect. */ + bool m_fBackendInitialized; + /** Number of operations currently using m_Transport or m_Backend; protected by m_CritSect. */ + uint32_t m_cCalls; + /** Non-owning endpoint for the service-owned HGCM client; protected by m_CritSect. */ + SHCLTRANSPORT m_Transport; + /** Native backend dispatcher and its platform connection context. */ + ShClBackend m_Backend; +}; + +#endif /* !MAIN_INCLUDED_GuestShClConn_h */ diff --git a/src/VBox/Main/include/GuestShClPrivate.h b/src/VBox/Main/include/GuestShClPrivate.h index 7f3040231405..8b2ddcedee41 100644 --- a/src/VBox/Main/include/GuestShClPrivate.h +++ b/src/VBox/Main/include/GuestShClPrivate.h @@ -1,4 +1,4 @@ -/* $Id: GuestShClPrivate.h 114584 2026-07-01 13:24:25Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClPrivate.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Private Shared Clipboard code for the Main API. */ @@ -32,30 +32,25 @@ #endif #include -#include - -#include +#include /** * Forward prototype declarations. */ class Console; +class GuestShClConn; -/** - * Struct for keeping a Shared Clipboard service extension. - */ +/** Chained Shared Clipboard service extension used by the remote desktop server. */ struct SHCLSVCEXT { - /** Service extension callback function. - * Setting this to NULL deactivates the extension. */ - PFNHGCMSVCEXT pfnExt; - /** User-supplied service extension data. Might be NULL if not being used. */ - void * pvExt; - /** Pointer to an optional extension callback. - * Might be NULL if not being used. */ - PFNSHCLEXTCALLBACK pfnExtCallback; + /** Chained service extension callback, or NULL. */ + PFNHGCMSVCEXT pfnExt; + /** Opaque callback argument. */ + void *pvExt; + /** Reverse callback installed by the HGCM service, or NULL. */ + PFNSHCLEXTCALLBACK pfnExtCallback; }; -/** Pointer to a Shared Clipboard service extension. */ +/** Pointer to a chained Shared Clipboard service extension. */ typedef SHCLSVCEXT *PSHCLSVCEXT; /** @@ -134,9 +129,6 @@ class GuestShCl bool i_isHostDataSeqCurrentLocked(uint64_t uSeq); uint64_t i_getGuestDataSeq(void); bool i_isGuestDataSeqCurrent(uint64_t uSeq); - int i_beginGuestRead(PSHCLCLIENT *ppClient); - void i_endGuestRead(void); - void i_waitForGuestReads(void); /** @} */ public: @@ -149,7 +141,7 @@ class GuestShCl int ReportFormatsToHost(SHCLFORMATS fFormats); int WriteDataToHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbData); int ReportFormatsToGuest(SHCLFORMATS fFormats); - int ReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource); + int ReportFormatsToGuest(GuestShClConn *pConn, SHCLFORMATS fFormats, SHCLSOURCE enmSource); int ReportError(const char *pcszId, int vrc, const char *pcszMsgFmt, ...); int RegisterServiceExtension(PFNHGCMSVCEXT pfnExtension, void *pvExtension); int UnregisterServiceExtension(PFNHGCMSVCEXT pfnExtension); @@ -164,30 +156,28 @@ class GuestShCl protected: - /** @name Service extension callback helpers. - * @{ */ int i_forwardToSvcExt(uint32_t u32Function, void *pvParms, uint32_t cbParms); - int i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32_t cbParms); - /** @} */ + int i_svcExtParmsValidate(uint32_t u32Function, void *pvParms, uint32_t cbParms); protected: /** @name Service extension callback handlers. * @{ */ - int i_handleSvcExtSetCallback(PSHCLEXTPARMS pParms); - int i_handleSvcExtReportFormatsToHost(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtReportFormatsToGuest(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtDataRead(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtDataReadVrde(PSHCLEXTPARMS pParms); - int i_handleSvcExtDataWrite(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendInit(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendDestroy(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendConnect(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendDisconnect(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendSync(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtError(PSHCLEXTPARMS pParms); + int i_svcExtSetCallback(PSHCLEXTPARMS pParms); + int i_svcExtReportFormatsToHostCallback(PSHCLEXTPARMS pParms); + int i_svcExtReportFormatsToGuestCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtDataReadCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtDataReadVrdeCallback(PSHCLEXTPARMS pParms); + int i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtBackendInitCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtBackendDestroyCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtBackendConnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtBackendDisconnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtBackendSyncCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtErrorCallback(PSHCLEXTPARMS pParms); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - int i_handleSvcExtFileTransfer(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtTransferGetCallbacksCallback(PSHCLEXTPARMS pParms); + int i_svcExtFileTransferCallback(PSHCLEXTPARMS pParms); #endif /** @} */ @@ -198,24 +188,12 @@ class GuestShCl Console *m_pConsole; /** Critical section to serialize access. */ RTCRITSECT m_CritSect; - /** Pointer an additional service extension handle to serve (daisy chaining). - * - * This currently only is being used by the Console VRDP server helper class (historical reasons). - * We might want to transform this into a map later if we (ever) need more than one service extension, - * or drop this concept althogether when we move the service stuff out of the VM process (later). */ + /** Main-owned connection encapsulating the service endpoint and native backend context. */ + GuestShClConn *m_pConn; + /** Chained remote-desktop service extension. */ SHCLSVCEXT m_SvcExtVRDP; - /** Pointer to an optional extension callback. - * Might be NULL if not being used. */ + /** Reverse callback supplied by the HGCM service, or NULL. */ PFNSHCLEXTCALLBACK m_pfnExtCallback; - /** Active guest clipboard client, if any. - * Weak pointer owned by the HGCM service and protected by m_CritSect. */ - PSHCLCLIENT m_pClient; - /** Whether new guest data reads using m_pClient are blocked. */ - bool m_fGuestReadsBlocked; - /** Number of active guest data reads using m_pClient outside m_CritSect. */ - uint32_t m_cGuestReads; - /** Signalled when no guest data reads are active. */ - RTSEMEVENTMULTI m_hGuestReadsDone; /** Host data sequence counter, protected by m_CritSect. */ uint64_t m_uHostDataSeq; /** Guest data sequence counter, protected by m_CritSect. */ @@ -232,4 +210,3 @@ class GuestShCl #define GuestShClInst() GuestShCl::GetInst() #endif /* !MAIN_INCLUDED_GuestShClPrivate_h */ - diff --git a/src/VBox/Main/src-client/GuestShClBackend.cpp b/src/VBox/Main/src-client/GuestShClBackend.cpp new file mode 100644 index 000000000000..c561a628fcad --- /dev/null +++ b/src/VBox/Main/src-client/GuestShClBackend.cpp @@ -0,0 +1,152 @@ +/* $Id: GuestShClBackend.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Native backend dispatcher implementation. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "GuestShClBackend.h" +#include "GuestShClBackendPrivate.h" + +#include + +#include +#include + + +ShClBackend::ShClBackend(void) + : m_pOps(ShClBackendGetOps()) + , m_pCtx(NULL) +{ + AssertPtr(m_pOps); +} + + +ShClBackend::~ShClBackend(void) +{ + Assert(m_pCtx == NULL); +} + + +int ShClBackend::init(void) +{ + AssertPtrReturn(m_pOps, VERR_INVALID_STATE); + AssertPtrReturn(m_pOps->pfnInit, VERR_INVALID_STATE); + return m_pOps->pfnInit(); +} + + +void ShClBackend::destroy(void) +{ + AssertPtrReturnVoid(m_pOps); + AssertPtrReturnVoid(m_pOps->pfnDestroy); + m_pOps->pfnDestroy(); +} + + +void ShClBackend::setCallbacks(PSHCLCALLBACKS pCallbacks) +{ + AssertPtrReturnVoid(m_pOps); + if (m_pOps->pfnSetCallbacks) + m_pOps->pfnSetCallbacks(pCallbacks); +} + + +int ShClBackend::connect(GuestShClConn *pConn) +{ + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(m_pOps, VERR_INVALID_STATE); + AssertPtrReturn(m_pOps->pfnConnect, VERR_INVALID_STATE); + AssertReturn(m_pCtx == NULL, VERR_RESOURCE_BUSY); + + PSHCLCONTEXT pCtx = NULL; + int vrc = m_pOps->pfnConnect(pConn, &pCtx); + if (RT_SUCCESS(vrc)) + { + if (pCtx) + m_pCtx = pCtx; + else + vrc = VERR_INTERNAL_ERROR; + } + return vrc; +} + + +int ShClBackend::disconnect(void) +{ + AssertPtrReturn(m_pOps, VERR_INVALID_STATE); + AssertPtrReturn(m_pOps->pfnDisconnect, VERR_INVALID_STATE); + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + + PSHCLCONTEXT const pCtx = m_pCtx; + int const vrc = m_pOps->pfnDisconnect(pCtx); + m_pCtx = NULL; + return vrc; +} + + +int ShClBackend::reportFormats(SHCLFORMATS fFormats) +{ + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnReportFormats(m_pCtx, fFormats); +} + + +int ShClBackend::readData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnReadData(m_pCtx, uFormat, pvData, cbData, pcbActual); +} + + +int ShClBackend::writeData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnWriteData(m_pCtx, uFormat, pvData, cbData); +} + + +int ShClBackend::sync(void) +{ + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnSync(m_pCtx); +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +void ShClBackend::transferGetCallbacks(PSHCLTRANSFERCALLBACKS pCallbacks) +{ + AssertPtrReturnVoid(pCallbacks); + AssertPtrReturnVoid(m_pCtx); + m_pOps->pfnTransferGetCallbacks(m_pCtx, pCallbacks); +} + + +int ShClBackend::transferHandleStatusReply(PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) +{ + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnTransferHandleStatusReply(m_pCtx, pTransfer, enmSource, enmStatus, rcStatus); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ diff --git a/src/VBox/Main/src-client/GuestShClBackendPrivate.h b/src/VBox/Main/src-client/GuestShClBackendPrivate.h new file mode 100644 index 000000000000..f485e5169f42 --- /dev/null +++ b/src/VBox/Main/src-client/GuestShClBackendPrivate.h @@ -0,0 +1,73 @@ +/* $Id: GuestShClBackendPrivate.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Internal native backend operation table. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef MAIN_INCLUDED_SRC_src_client_GuestShClBackendPrivate_h +#define MAIN_INCLUDED_SRC_src_client_GuestShClBackendPrivate_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "GuestShClBackend.h" + +/** Native platform backend operations used by ShClBackend. */ +typedef struct SHCLBACKENDOPS +{ + /** Initializes the platform backend. */ + int (*pfnInit)(void); + /** Destroys the platform backend. */ + void (*pfnDestroy)(void); + /** Replaces the native callback table for test purposes. Optional. */ + void (*pfnSetCallbacks)(PSHCLCALLBACKS pCallbacks); + /** Connects a Main service connection and returns its platform context. */ + int (*pfnConnect)(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx); + /** Disconnects and destroys a platform connection context. */ + int (*pfnDisconnect)(PSHCLCONTEXT pCtx); + /** Reports guest formats to the native clipboard. */ + int (*pfnReportFormats)(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats); + /** Reads native clipboard data for the guest. */ + int (*pfnReadData)(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); + /** Writes guest clipboard data to the native clipboard. */ + int (*pfnWriteData)(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); + /** Synchronizes native clipboard state with the guest. */ + int (*pfnSync)(PSHCLCONTEXT pCtx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Returns callbacks for a new transfer. */ + void (*pfnTransferGetCallbacks)(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks); + /** Handles a transfer status reply. */ + int (*pfnTransferHandleStatusReply)(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus); +#endif +} SHCLBACKENDOPS; + +/** + * Returns the native Shared Clipboard backend operations for this host. + * + * @returns Immutable native backend operation table. Never NULL. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void); + +#endif /* !MAIN_INCLUDED_SRC_src_client_GuestShClBackendPrivate_h */ diff --git a/src/VBox/Main/src-client/GuestShClConn.cpp b/src/VBox/Main/src-client/GuestShClConn.cpp new file mode 100644 index 000000000000..e6522a170ec4 --- /dev/null +++ b/src/VBox/Main/src-client/GuestShClConn.cpp @@ -0,0 +1,584 @@ +/* $Id: GuestShClConn.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Service connection management implementation. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include + +#include "GuestShClConn.h" +#include "GuestShClBackend.h" +#ifdef VBOX_COM_INPROC +# include "GuestShClPrivate.h" +#endif + +#include + +#include +#include +#include + + +GuestShClConn::GuestShClConn(GuestShCl *pOwner) + : m_pOwner(pOwner) + , m_hCallsDone(NIL_RTSEMEVENTMULTI) + , m_enmState(State_Disconnected) + , m_fBackendInitialized(false) + , m_cCalls(0) +{ + RT_ZERO(m_Transport); + + int vrc = RTCritSectInit(&m_CritSect); + if (RT_FAILURE(vrc)) + throw vrc; + + vrc = RTSemEventMultiCreate(&m_hCallsDone); + if (RT_FAILURE(vrc)) + { + RTCritSectDelete(&m_CritSect); + throw vrc; + } + vrc = RTSemEventMultiSignal(m_hCallsDone); + if (RT_FAILURE(vrc)) + { + RTSemEventMultiDestroy(m_hCallsDone); + m_hCallsDone = NIL_RTSEMEVENTMULTI; + RTCritSectDelete(&m_CritSect); + throw vrc; + } +} + + +GuestShClConn::~GuestShClConn(void) +{ + Assert(m_enmState == State_Disconnected); + Assert(m_cCalls == 0); + Assert(!m_fBackendInitialized); + + if (m_hCallsDone != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(m_hCallsDone); + m_hCallsDone = NIL_RTSEMEVENTMULTI; + } + if (RTCritSectIsInitialized(&m_CritSect)) + RTCritSectDelete(&m_CritSect); +} + + +int GuestShClConn::i_callBegin(PSHCLTRANSPORT pTransport) +{ + AssertPtrReturn(pTransport, VERR_INVALID_POINTER); + RT_ZERO(*pTransport); + + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + + if ( (m_enmState == State_Connecting || m_enmState == State_Connected) + && ShClTransportIsValid(&m_Transport)) + { + *pTransport = m_Transport; + if (m_cCalls++ == 0) + { + int const vrcReset = RTSemEventMultiReset(m_hCallsDone); + AssertFatalMsgRC(vrcReset, ("Resetting the Shared Clipboard connection call-drain event failed with %Rrc\n", + vrcReset)); + } + vrc = VINF_SUCCESS; + } + else + vrc = VERR_SHCLPB_NO_DATA; + + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock failed with %Rrc\n", vrcLeave)); + return vrc; +} + + +void GuestShClConn::i_callEnd(void) +{ + int const vrc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrc, ("Taking the Shared Clipboard connection lock while ending a call failed with %Rrc\n", vrc)); + + Assert(m_cCalls > 0); + if (m_cCalls > 0 && --m_cCalls == 0) + { + int const vrcSignal = RTSemEventMultiSignal(m_hCallsDone); + AssertFatalMsgRC(vrcSignal, ("Signalling the Shared Clipboard connection call-drain event failed with %Rrc\n", + vrcSignal)); + } + + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock while ending a call failed with %Rrc\n", + vrcLeave)); +} + + +void GuestShClConn::i_waitForCalls(void) +{ + for (;;) + { + int vrc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrc, ("Taking the Shared Clipboard connection lock while draining calls failed with %Rrc\n", vrc)); + bool const fDone = m_cCalls == 0; + vrc = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrc, ("Releasing the Shared Clipboard connection lock while draining calls failed with %Rrc\n", vrc)); + if (fDone) + return; + + vrc = RTSemEventMultiWait(m_hCallsDone, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(vrc, ("Draining Shared Clipboard connection calls failed with %Rrc\n", vrc)); + } +} + + +int GuestShClConn::initBackend(void) +{ + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + bool const fInitialized = m_fBackendInitialized; + RTCritSectLeave(&m_CritSect); + if (fInitialized) + return VINF_SUCCESS; + + vrc = m_Backend.init(); + if (RT_SUCCESS(vrc)) + { + int const vrcLock = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrcLock, ("Taking the Shared Clipboard connection lock after backend initialization failed with %Rrc\n", + vrcLock)); + m_fBackendInitialized = true; + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, + ("Releasing the Shared Clipboard connection lock after backend initialization failed " + "with %Rrc\n", vrcLeave)); + } + return vrc; +} + + +int GuestShClConn::destroyBackend(void) +{ + SHCLTRANSPORT Transport; + bool fConnected; + bool fInitialized; + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + Transport = m_Transport; + fConnected = m_enmState == State_Connected; + fInitialized = m_fBackendInitialized; + RTCritSectLeave(&m_CritSect); + + int vrcDisconnect = VINF_SUCCESS; + if (fConnected) + vrcDisconnect = disconnect(&Transport); + + if (fInitialized) + { + m_Backend.destroy(); + vrc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrc, ("Taking the Shared Clipboard connection lock after backend destruction failed with %Rrc\n", vrc)); + m_fBackendInitialized = false; + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock after backend destruction failed with %Rrc\n", + vrcLeave)); + } + return RT_FAILURE(vrcDisconnect) ? vrcDisconnect : vrc; +} + + +void GuestShClConn::setBackendCallbacks(PSHCLCALLBACKS pCallbacks) +{ + m_Backend.setCallbacks(pCallbacks); +} + + +int GuestShClConn::connect(PCSHCLTRANSPORT pTransport) +{ + AssertReturn(ShClTransportIsValid(pTransport), VERR_INVALID_HANDLE); + + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + if (m_enmState != State_Disconnected) + { + RTCritSectLeave(&m_CritSect); + return VERR_RESOURCE_BUSY; + } + m_Transport = *pTransport; + m_enmState = State_Connecting; + RTCritSectLeave(&m_CritSect); + + vrc = m_Backend.connect(this); + + int const vrcLock = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrcLock, ("Taking the Shared Clipboard connection lock after backend connection failed with %Rrc\n", + vrcLock)); + if (RT_SUCCESS(vrc)) + m_enmState = State_Connected; + else + { + m_enmState = State_Disconnected; + RT_ZERO(m_Transport); + } + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock after backend connection failed with %Rrc\n", + vrcLeave)); + return vrc; +} + + +int GuestShClConn::disconnect(PCSHCLTRANSPORT pTransport) +{ + AssertReturn(ShClTransportIsValid(pTransport), VERR_INVALID_HANDLE); + + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + if ( m_enmState != State_Connected + || !ShClTransportIsEqual(&m_Transport, pTransport)) + { + RTCritSectLeave(&m_CritSect); + return VERR_INVALID_HANDLE; + } + m_enmState = State_Closing; + RTCritSectLeave(&m_CritSect); + + i_waitForCalls(); + vrc = m_Backend.disconnect(); + + int const vrcLock = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrcLock, ("Taking the Shared Clipboard connection lock after backend disconnection failed with %Rrc\n", + vrcLock)); + RT_ZERO(m_Transport); + m_enmState = State_Disconnected; + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock after backend disconnection failed with %Rrc\n", + vrcLeave)); + return vrc; +} + + +bool GuestShClConn::matches(PCSHCLTRANSPORT pTransport) const +{ + bool fMatches = false; + int const vrc = RTCritSectEnter(&m_CritSect); + if (RT_SUCCESS(vrc)) + { + fMatches = (m_enmState == State_Connecting || m_enmState == State_Connected) + && ShClTransportIsEqual(&m_Transport, pTransport); + RTCritSectLeave(&m_CritSect); + } + return fMatches; +} + + +bool GuestShClConn::isConnected(void) const +{ + bool fConnected = false; + int const vrc = RTCritSectEnter(&m_CritSect); + if (RT_SUCCESS(vrc)) + { + fConnected = m_enmState == State_Connected; + RTCritSectLeave(&m_CritSect); + } + return fConnected; +} + + +#define SHCL_CONN_SVC_CALL_BEGIN(a_Transport) \ + SHCLTRANSPORT a_Transport; \ + int vrc = i_callBegin(&(a_Transport)); \ + if (RT_FAILURE(vrc)) \ + return vrc + +#define SHCL_CONN_BACKEND_CALL_BEGIN(a_Transport) \ + SHCLTRANSPORT a_Transport; \ + int vrc = i_callBegin(&(a_Transport)); \ + if (RT_FAILURE(vrc)) \ + return vrc + + +int GuestShClConn::reportFormatsToGuest(SHCLFORMATS fFormats, SHCLFORMATS *pfReported) +{ + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnReportFormatsToGuest(Transport.hClient, fFormats, pfReported); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::reportLocalFormats(SHCLFORMATS fFormats) +{ +#ifdef VBOX_COM_INPROC + if (m_pOwner) + return m_pOwner->ReportFormatsToGuest(this, fFormats, SHCLSOURCE_LOCAL); +#endif + return reportFormatsToGuest(fFormats); +} + + +int GuestShClConn::readDataFromGuestAsync(SHCLFORMATS fFormats, PSHCLEVENT *ppEvent) +{ + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnReadDataFromGuestAsync(Transport.hClient, fFormats, ppEvent); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::readDataFromGuest(SHCLFORMAT uFormat, void **ppvData, uint32_t *pcbData) +{ + AssertPtrReturn(ppvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbData, VERR_INVALID_POINTER); + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnReadDataFromGuest(Transport.hClient, uFormat, ppvData, pcbData); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::guestDataBegin(PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken) +{ + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(phToken, VERR_INVALID_POINTER); + *phToken = NULL; + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnGuestDataBegin(Transport.hClient, pCmdCtx, uFormat, phToken); + if ( RT_FAILURE(vrc) + || !*phToken) + i_callEnd(); + return vrc; +} + + +int GuestShClConn::guestDataComplete(SHCLGUESTDATATOKEN hToken, void const *pvData, uint32_t cbData) +{ + AssertPtrReturn(hToken, VERR_INVALID_HANDLE); + + SHCLTRANSPORT Transport; + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + if (m_cCalls > 0 && ShClTransportIsValid(&m_Transport)) + Transport = m_Transport; + else + { + RT_ZERO(Transport); + vrc = VERR_INVALID_STATE; + } + RTCritSectLeave(&m_CritSect); + + if (RT_SUCCESS(vrc)) + { + vrc = Transport.pOps->pfnGuestDataComplete(Transport.hClient, hToken, pvData, cbData); + i_callEnd(); + } + return vrc; +} + + +void GuestShClConn::guestDataCancel(SHCLGUESTDATATOKEN hToken) +{ + AssertPtrReturnVoid(hToken); + + SHCLTRANSPORT Transport; + int const vrc = RTCritSectEnter(&m_CritSect); + if (RT_SUCCESS(vrc)) + { + if (m_cCalls > 0 && ShClTransportIsValid(&m_Transport)) + Transport = m_Transport; + else + RT_ZERO(Transport); + RTCritSectLeave(&m_CritSect); + + if (ShClTransportIsValid(&Transport)) + { + Transport.pOps->pfnGuestDataCancel(Transport.hClient, hToken); + i_callEnd(); + } + } +} + + +int GuestShClConn::syncBackend(void) +{ + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + vrc = m_Backend.sync(); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::reportFormatsToBackend(SHCLFORMATS fFormats) +{ + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + SHCLFORMATS fFiltered = VBOX_SHCL_FMT_NONE; + vrc = Transport.pOps->pfnFilterFormats(Transport.hClient, false /* fHostToGuest */, fFormats, &fFiltered); + if (RT_SUCCESS(vrc)) + fFormats = fFiltered; +#endif + if (RT_SUCCESS(vrc)) + vrc = m_Backend.reportFormats(fFormats); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::readDataFromBackend(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + vrc = m_Backend.readData(uFormat, pvData, cbData, pcbActual); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::writeDataToBackend(SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + vrc = m_Backend.writeData(uFormat, pvData, cbData); + i_callEnd(); + return vrc; +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int GuestShClConn::transferGetCallbacks(PSHCLTRANSFERCALLBACKS pCallbacks) +{ + AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER); + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + m_Backend.transferGetCallbacks(pCallbacks); + i_callEnd(); + return VINF_SUCCESS; +} + + +int GuestShClConn::transferHandleStatusReply(PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) +{ + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + vrc = m_Backend.transferHandleStatusReply(pTransfer, enmSource, enmStatus, rcStatus); + i_callEnd(); + return vrc; +} + + +PSHCLTRANSFER GuestShClConn::transferGetByIdRetained(SHCLTRANSFERID idTransfer) +{ + SHCLTRANSPORT Transport; + int const vrc = i_callBegin(&Transport); + if (RT_FAILURE(vrc)) + return NULL; + PSHCLTRANSFER const pTransfer = Transport.pOps->pfnTransferGetByIdRetained(Transport.hClient, idTransfer); + i_callEnd(); + return pTransfer; +} + + +PSHCLTRANSFER GuestShClConn::transferGetByKeyRetained(SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration) +{ + SHCLTRANSPORT Transport; + int const vrc = i_callBegin(&Transport); + if (RT_FAILURE(vrc)) + return NULL; + PSHCLTRANSFER const pTransfer = Transport.pOps->pfnTransferGetByKeyRetained(Transport.hClient, + idSession, idTransfer, uGeneration); + i_callEnd(); + return pTransfer; +} + + +int GuestShClConn::transferCreate(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, + PSHCLTRANSFERCALLBACKS pCallbacks, SHCLTRANSFERID idTransfer, + PSHCLTRANSFER *ppTransfer) +{ + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnTransferCreate(Transport.hClient, enmDir, enmSource, pCallbacks, + idTransfer, ppTransfer); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::transferInit(PSHCLTRANSFER pTransfer) +{ + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnTransferInit(Transport.hClient, pTransfer); + i_callEnd(); + return vrc; +} + + +void GuestShClConn::transferDestroyById(SHCLTRANSFERID idTransfer) +{ + SHCLTRANSPORT Transport; + int const vrc = i_callBegin(&Transport); + if (RT_SUCCESS(vrc)) + { + Transport.pOps->pfnTransferDestroyById(Transport.hClient, idTransfer); + i_callEnd(); + } +} + + +void GuestShClConn::transferDestroyAll(void) +{ + SHCLTRANSPORT Transport; + int const vrc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrc, ("Taking the Shared Clipboard connection lock while destroying transfers failed with %Rrc\n", vrc)); + Transport = m_Transport; + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock while destroying transfers failed with %Rrc\n", + vrcLeave)); + if (ShClTransportIsValid(&Transport)) + Transport.pOps->pfnTransferDestroyAll(Transport.hClient); +} + + +int GuestShClConn::transferProviderInitGuest(PSHCLTXPROVIDER pProvider) +{ + AssertPtrReturn(pProvider, VERR_INVALID_POINTER); + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnTransferProviderInitGuest(Transport.hClient, pProvider); + i_callEnd(); + return vrc; +} + + +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +#undef SHCL_CONN_BACKEND_CALL_BEGIN +#undef SHCL_CONN_SVC_CALL_BEGIN diff --git a/src/VBox/Main/src-client/GuestShClPrivate.cpp b/src/VBox/Main/src-client/GuestShClPrivate.cpp index 1641d5181696..a7c89adb6238 100644 --- a/src/VBox/Main/src-client/GuestShClPrivate.cpp +++ b/src/VBox/Main/src-client/GuestShClPrivate.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClPrivate.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClPrivate.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Private Shared Clipboard code. */ @@ -32,6 +32,7 @@ # include "ClipboardImpl.h" # include "ConsoleImpl.h" # include "GuestShClPrivate.h" +# include "GuestShClConn.h" # include "ProgressImpl.h" # include @@ -63,11 +64,8 @@ GuestShCl* GuestShCl::s_pInstance = NULL; GuestShCl::GuestShCl(Console *pConsole) : m_pConsole(pConsole) + , m_pConn(NULL) , m_pfnExtCallback(NULL) - , m_pClient(NULL) - , m_fGuestReadsBlocked(false) - , m_cGuestReads(0) - , m_hGuestReadsDone(NIL_RTSEMEVENTMULTI) , m_uHostDataSeq(0) , m_uGuestDataSeq(0) { @@ -79,13 +77,15 @@ GuestShCl::GuestShCl(Console *pConsole) if (RT_FAILURE(vrc)) throw vrc; - vrc = RTSemEventMultiCreate(&m_hGuestReadsDone); - if (RT_FAILURE(vrc)) + try + { + m_pConn = new GuestShClConn(this); + } + catch (...) { RTCritSectDelete(&m_CritSect); - throw vrc; + throw; } - RTSemEventMultiSignal(m_hGuestReadsDone); } GuestShCl::~GuestShCl(void) @@ -100,17 +100,12 @@ void GuestShCl::uninit(void) { LogFlowFuncEnter(); - if (m_hGuestReadsDone != NIL_RTSEMEVENTMULTI) + if (m_pConn) { - int vrc = lock(); - if (RT_SUCCESS(vrc)) - { - m_fGuestReadsBlocked = true; - unlock(); - } - i_waitForGuestReads(); - RTSemEventMultiDestroy(m_hGuestReadsDone); - m_hGuestReadsDone = NIL_RTSEMEVENTMULTI; + int const vrc = m_pConn->destroyBackend(); + AssertRC(vrc); + delete m_pConn; + m_pConn = NULL; } if (RTCritSectIsInitialized(&m_CritSect)) @@ -119,9 +114,6 @@ void GuestShCl::uninit(void) RT_ZERO(m_SvcExtVRDP); m_pfnExtCallback = NULL; - m_pClient = NULL; - m_fGuestReadsBlocked = false; - m_cGuestReads = 0; m_uHostDataSeq = 0; m_uGuestDataSeq = 0; } @@ -298,71 +290,6 @@ bool GuestShCl::i_isGuestDataSeqCurrent(uint64_t uSeq) } -/** - * Starts a guest data read that will use the active service client outside m_CritSect. - * - * @returns VBox status code. - * @param ppClient Where to return the active client. - */ -int GuestShCl::i_beginGuestRead(PSHCLCLIENT *ppClient) -{ - AssertPtrReturn(ppClient, VERR_INVALID_POINTER); - *ppClient = NULL; - - int vrc = lock(); - if (RT_FAILURE(vrc)) - return vrc; - - if ( m_pClient - && !m_fGuestReadsBlocked) - { - *ppClient = m_pClient; - if (m_cGuestReads++ == 0) - RTSemEventMultiReset(m_hGuestReadsDone); - vrc = VINF_SUCCESS; - } - else - vrc = VERR_SHCLPB_NO_DATA; - - unlock(); - return vrc; -} - - -/** Ends a guest data read started by i_beginGuestRead(). */ -void GuestShCl::i_endGuestRead(void) -{ - int const vrc = lock(); - if (RT_SUCCESS(vrc)) - { - Assert(m_cGuestReads > 0); - if (m_cGuestReads > 0 && --m_cGuestReads == 0) - RTSemEventMultiSignal(m_hGuestReadsDone); - unlock(); - } -} - - -/** Waits until all guest data reads using the active client have completed. */ -void GuestShCl::i_waitForGuestReads(void) -{ - for (;;) - { - int vrc = lock(); - if (RT_FAILURE(vrc)) - return; - bool const fDone = m_cGuestReads == 0; - unlock(); - if (fDone) - return; - vrc = RTSemEventMultiWait(m_hGuestReadsDone, RT_INDEFINITE_WAIT); - AssertRC(vrc); - if (RT_FAILURE(vrc)) - return; - } -} - - /** * Registers a Shared Clipboard service extension. * @@ -469,17 +396,7 @@ int GuestShCl::ReadDataFromGuest(SHCLFORMAT uFormat, void **ppvData, uint32_t *p *ppvData = NULL; *pcbData = 0; - PSHCLCLIENT pClient = NULL; - int vrc = i_beginGuestRead(&pClient); - if (RT_FAILURE(vrc)) - return vrc; - - /* Do not hold m_CritSect while waiting for the guest reply: the reply callback - * validates the active client under the same lock before signalling the event. */ - vrc = ShClSvcReadDataFromGuest(pClient, uFormat, ppvData, pcbData); - - i_endGuestRead(); - return vrc; + return m_pConn->readDataFromGuest(uFormat, ppvData, pcbData); } /** @@ -498,21 +415,7 @@ int GuestShCl::ReadDataFromHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbDat AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); *pcbActual = 0; - SHCLCLIENTCMDCTX cmdCtx; - RT_ZERO(cmdCtx); - - PSHCLCLIENT pClient = NULL; - int vrc = i_beginGuestRead(&pClient); - if (RT_FAILURE(vrc)) - return vrc; - - if (pClient->pBackend) - vrc = ShClBackendReadData(pClient->pBackend, pClient, &cmdCtx, uFormat, pvData, cbData, pcbActual); - else - vrc = VERR_SHCLPB_NO_DATA; - - i_endGuestRead(); - return vrc; + return m_pConn->readDataFromBackend(uFormat, pvData, cbData, pcbActual); } /** @@ -530,23 +433,8 @@ int GuestShCl::ReportFormatsToHost(SHCLFORMATS fFormats) ++m_uGuestDataSeq; unlock(); - PSHCLCLIENT pClient = NULL; - vrc = i_beginGuestRead(&pClient); - if (RT_FAILURE(vrc)) - return vrc == VERR_SHCLPB_NO_DATA ? VINF_SUCCESS : vrc; - - if (pClient->pBackend) - { -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - fFormats = shClSvcHandleFormats(false /* fHostToGuest */, pClient, fFormats); -#endif - vrc = ShClBackendReportFormats(pClient->pBackend, pClient, fFormats); - } - else - vrc = VINF_SUCCESS; - - i_endGuestRead(); - return vrc; + vrc = m_pConn->reportFormatsToBackend(fFormats); + return vrc == VERR_SHCLPB_NO_DATA ? VINF_SUCCESS : vrc; } /** @@ -563,21 +451,8 @@ int GuestShCl::WriteDataToHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbData if (cbData) AssertPtrReturn(pvData, VERR_INVALID_POINTER); - SHCLCLIENTCMDCTX cmdCtx; - RT_ZERO(cmdCtx); - - PSHCLCLIENT pClient = NULL; - int vrc = i_beginGuestRead(&pClient); - if (RT_FAILURE(vrc)) - return vrc == VERR_SHCLPB_NO_DATA ? VINF_SUCCESS : vrc; - - if (pClient->pBackend) - vrc = ShClBackendWriteData(pClient->pBackend, pClient, &cmdCtx, uFormat, pvData, cbData); - else - vrc = VINF_SUCCESS; - - i_endGuestRead(); - return vrc; + int const vrc = m_pConn->writeDataToBackend(uFormat, pvData, cbData); + return vrc == VERR_SHCLPB_NO_DATA ? VINF_SUCCESS : vrc; } /** @@ -588,25 +463,11 @@ int GuestShCl::WriteDataToHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbData */ int GuestShCl::ReportFormatsToGuest(SHCLFORMATS fFormats) { - int vrc = lock(); - if (RT_FAILURE(vrc)) - return vrc; - - i_incHostDataSeqLocked(); - - PSHCLCLIENT pClient = m_pClient; - if ( pClient - && pClient->pBackend) - { -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); -#endif - vrc = ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); - } - else - vrc = VINF_SUCCESS; - - unlock(); + int const vrc = m_pConn->reportFormatsToGuest(fFormats); + if (vrc == VERR_SHCLPB_NO_DATA || vrc == VINF_NO_CHANGE) + return VINF_SUCCESS; + if (RT_SUCCESS(vrc)) + i_incHostDataSeq(); return vrc; } @@ -615,13 +476,14 @@ int GuestShCl::ReportFormatsToGuest(SHCLFORMATS fFormats) * successful reports to the console clipboard event source. * * @returns VBox status code. - * @param pClient Clipboard client to report to. + * @param pConn Connection to report through. * @param fFormats Formats to report to the guest. * @param enmSource Source of the format report. */ -int GuestShCl::ReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource) +int GuestShCl::ReportFormatsToGuest(GuestShClConn *pConn, SHCLFORMATS fFormats, SHCLSOURCE enmSource) { - AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertReturn(pConn == m_pConn, VERR_INVALID_HANDLE); ClipboardSource_T enmClipboardSource = ClipboardSource_Custom; switch (enmSource) @@ -638,36 +500,16 @@ int GuestShCl::ReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, S AssertFailedReturn(VERR_INVALID_PARAMETER); } - /* Reuse the guest-read lifetime guard to keep the weak service client valid - * while the platform backend reports the formats. */ - PSHCLCLIENT pActiveClient = NULL; - int vrc = i_beginGuestRead(&pActiveClient); - if (RT_FAILURE(vrc)) - return vrc; - if (pClient != pActiveClient) - { - i_endGuestRead(); - return VERR_SHCLPB_NO_DATA; - } - - if (enmSource == SHCLSOURCE_LOCAL) - i_incHostDataSeq(); - else - i_incGuestDataSeq(); - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); -#endif - - if (pClient->pBackend) - vrc = ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); - else - vrc = VINF_SUCCESS; - - i_endGuestRead(); - + int const vrc = pConn->reportFormatsToGuest(fFormats, &fFormats); + if (vrc == VINF_NO_CHANGE) + return VINF_SUCCESS; if (RT_SUCCESS(vrc)) { + if (enmSource == SHCLSOURCE_LOCAL) + i_incHostDataSeq(); + else + i_incGuestDataSeq(); + AssertPtr(m_pConsole->i_getClipboard()); if (m_pConsole->i_getClipboard()) m_pConsole->i_getClipboard()->i_reportFormats(VBOX_SHCL_MAIN_CLIENT_NONE, @@ -736,69 +578,72 @@ DECLCALLBACK(int) GuestShCl::s_HgcmDispatcher(void *pvExtension, uint32_t u32Fun GuestShCl *pThis = reinterpret_cast(pvExtension); AssertPtrReturn(pThis, VERR_INVALID_POINTER); - int vrc = pThis->i_validateSvcExtParms(u32Function, pvParms, cbParms); + int vrc = pThis->i_svcExtParmsValidate(u32Function, pvParms, cbParms); if (RT_FAILURE(vrc)) { LogFlowFuncLeaveRC(vrc); return vrc; } - PSHCLEXTPARMS pParms = (PSHCLEXTPARMS)pvParms; /* pParms might be NULL for unknown messages. */ vrc = VERR_NOT_SUPPORTED; switch (u32Function) { case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: - vrc = pThis->i_handleSvcExtSetCallback(pParms); + vrc = pThis->i_svcExtSetCallback((PSHCLEXTPARMS)pvParms); break; case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - vrc = pThis->i_handleSvcExtReportFormatsToHost(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtReportFormatsToHostCallback((PSHCLEXTPARMS)pvParms); break; case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - vrc = pThis->i_handleSvcExtReportFormatsToGuest(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtReportFormatsToGuestCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - vrc = pThis->i_handleSvcExtDataRead(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtDataReadCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE: - vrc = pThis->i_handleSvcExtDataReadVrde(pParms); + vrc = pThis->i_svcExtDataReadVrdeCallback((PSHCLEXTPARMS)pvParms); break; case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: - vrc = pThis->i_handleSvcExtDataWrite(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtDataWriteCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: - vrc = pThis->i_handleSvcExtBackendInit(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendInitCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: - vrc = pThis->i_handleSvcExtBackendDestroy(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendDestroyCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: - vrc = pThis->i_handleSvcExtBackendConnect(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendConnectCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: - vrc = pThis->i_handleSvcExtBackendDisconnect(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendDisconnectCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: - vrc = pThis->i_handleSvcExtBackendSync(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendSyncCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_ERROR: - vrc = pThis->i_handleSvcExtError(pParms); + vrc = pThis->i_svcExtErrorCallback((PSHCLEXTPARMS)pvParms); break; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: + vrc = pThis->i_svcExtTransferGetCallbacksCallback((PSHCLEXTPARMS)pvParms); + break; + case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: - vrc = pThis->i_handleSvcExtFileTransfer(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtFileTransferCallback((PSHCLEXTPARMS)pvParms); break; #endif diff --git a/src/VBox/Main/src-client/GuestShClSvcExt.cpp b/src/VBox/Main/src-client/GuestShClSvcExt.cpp index ca8f7e56b8dc..94cde0e2a8ad 100644 --- a/src/VBox/Main/src-client/GuestShClSvcExt.cpp +++ b/src/VBox/Main/src-client/GuestShClSvcExt.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClSvcExt.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClSvcExt.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard service extension handling for Main. */ @@ -31,6 +31,7 @@ #include "ConsoleImpl.h" #include "ClipboardImpl.h" #include "GuestShClPrivate.h" +#include "GuestShClConn.h" #include "Global.h" #include @@ -111,7 +112,7 @@ static int shClSvcExtValidateUtf8Z(const char *pcszString, bool fAllowNull, bool * @param pvData Data buffer pointer. Can be NULL only when \a cbData is zero. * @param cbData Data buffer size in bytes. */ -static int shClSvcExtValidateDataBuffer(void *pvData, uint32_t cbData) +static int shClSvcExtValidateDataBuffer(void const *pvData, uint32_t cbData) { AssertReturn(cbData <= VBOX_SHCL_MAX_CHUNK_SIZE, VERR_INVALID_PARAMETER); if (cbData) @@ -142,13 +143,14 @@ int GuestShCl::i_forwardToSvcExt(uint32_t u32Function, void *pvParms, uint32_t c * * @returns VBox status code. * @retval VERR_INVALID_POINTER if a required pointer is NULL or not a valid host pointer. - * @retval VERR_INVALID_PARAMETER if a parameter ist invalid. + * @retval VERR_INVALID_PARAMETER if a parameter is invalid. + * @retval VERR_RESOURCE_BUSY if a backend connection is requested while a client is active. * @param u32Function Service extension function being dispatched. * @param pvParms Raw service extension parameters to validate. Optional for unknown * function IDs. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtParmsValidate(uint32_t u32Function, void *pvParms, uint32_t cbParms) { switch (u32Function) { @@ -165,6 +167,7 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: #endif AssertReturn(RT_VALID_PTR(pvParms), VERR_INVALID_POINTER); @@ -175,93 +178,62 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 return VINF_SUCCESS; } - PSHCLCLIENT pActiveClient = NULL; - int vrc = lock(); - if (RT_SUCCESS(vrc)) - { - pActiveClient = m_pClient; - unlock(); - } - else - return vrc; - PSHCLEXTPARMS const pParms = (PSHCLEXTPARMS)pvParms; + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); +#define SHCL_VALIDATE_ACTIVE(a_Transport) \ + do { AssertReturn(m_pConn->matches(&(a_Transport)), VERR_INVALID_HANDLE); } while (0) + int vrc; switch (u32Function) { case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: + SHCL_VALIDATE_ACTIVE(Transport); AssertReturn(ShClFormatsAreValid(pParms->u.ReportFormats.uFormats), VERR_INVALID_PARAMETER); - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReportFormats.pClient == pActiveClient, VERR_INVALID_PARAMETER); - AssertPtrReturn(pParms->u.ReportFormats.pClient->pBackend, VERR_INVALID_POINTER); AssertReturn(pParms->u.ReportFormats.enmSource == SHCLSOURCE_INVALID, VERR_INVALID_PARAMETER); return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: + SHCL_VALIDATE_ACTIVE(Transport); AssertReturn(ShClFormatsAreValid(pParms->u.ReportFormats.uFormats), VERR_INVALID_PARAMETER); - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReportFormats.pClient == pActiveClient, VERR_INVALID_PARAMETER); - AssertPtrReturn(pParms->u.ReportFormats.pClient->pBackend, VERR_INVALID_POINTER); AssertReturn(ShClSourceIsValid(pParms->u.ReportFormats.enmSource), VERR_INVALID_PARAMETER); return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - vrc = shClSvcExtValidateFormat(pParms->u.ReadWriteData.uFormat, u32Function); - if (RT_FAILURE(vrc)) - return vrc; - vrc = shClSvcExtValidateDataBuffer(pParms->u.ReadWriteData.pvData, pParms->u.ReadWriteData.cbData); - if (RT_FAILURE(vrc)) - return vrc; - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReadWriteData.pClient == pActiveClient, VERR_INVALID_PARAMETER); - AssertPtrReturn(pParms->u.ReadWriteData.pClient->pBackend, VERR_INVALID_POINTER); - return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE: + SHCL_VALIDATE_ACTIVE(Transport); vrc = shClSvcExtValidateFormat(pParms->u.ReadWriteData.uFormat, u32Function); if (RT_FAILURE(vrc)) return vrc; vrc = shClSvcExtValidateDataBuffer(pParms->u.ReadWriteData.pvData, pParms->u.ReadWriteData.cbData); - if (RT_FAILURE(vrc)) - return vrc; - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReadWriteData.pClient == pActiveClient, VERR_INVALID_PARAMETER); - return VINF_SUCCESS; + return vrc; case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: + SHCL_VALIDATE_ACTIVE(Transport); vrc = shClSvcExtValidateFormat(pParms->u.ReadWriteData.uFormat, u32Function); if (RT_FAILURE(vrc)) return vrc; vrc = shClSvcExtValidateDataBuffer(pParms->u.ReadWriteData.pvData, pParms->u.ReadWriteData.cbData); if (RT_FAILURE(vrc)) return vrc; - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReadWriteData.pClient == pActiveClient, VERR_INVALID_PARAMETER); - AssertPtrReturn(pParms->u.ReadWriteData.pClient->pBackend, VERR_INVALID_POINTER); AssertPtrReturn(pParms->u.ReadWriteData.pCmdCtx, VERR_INVALID_POINTER); return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: - AssertReturn(pActiveClient == NULL, VERR_INVALID_PARAMETER); - return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: - AssertReturn(pActiveClient == NULL, VERR_INVALID_PARAMETER); + AssertReturn(!m_pConn->isConnected(), VERR_RESOURCE_BUSY); + AssertReturn(ShClTransportIsValid(&Transport), VERR_INVALID_HANDLE); return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); + SHCL_VALIDATE_ACTIVE(Transport); return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_ERROR: @@ -275,23 +247,29 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 return VINF_SUCCESS; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: + SHCL_VALIDATE_ACTIVE(Transport); + AssertPtrReturn(pParms->u.TransferCallbacks.pCallbacks, VERR_INVALID_POINTER); + return VINF_SUCCESS; + case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: { - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.FileTransferData.pClient == pActiveClient, VERR_INVALID_PARAMETER); - PSHCLCLIENT const pClient = pParms->u.FileTransferData.pClient; - AssertPtrReturn(pClient->pBackend, VERR_INVALID_POINTER); + SHCL_VALIDATE_ACTIVE(Transport); PSHCLTRANSFER const pTransfer = pParms->u.FileTransferData.pTransfer; AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); AssertPtrReturn(pParms->u.FileTransferData.pReply, VERR_INVALID_POINTER); AssertReturn(ShClSourceIsValid(pParms->u.FileTransferData.enmShClSource), VERR_INVALID_PARAMETER); PSHCLTRANSFER const pRegisteredTransfer - = ShClTransferCtxGetTransferByKey(&pClient->Transfers.Ctx, - ShClTransferGetSessionId(pTransfer), - ShClTransferGetID(pTransfer), - ShClTransferGetGeneration(pTransfer)); + = m_pConn->transferGetByKeyRetained(ShClTransferGetSessionId(pTransfer), + ShClTransferGetID(pTransfer), + ShClTransferGetGeneration(pTransfer)); if (pRegisteredTransfer != pTransfer) + { + if (pRegisteredTransfer) + ShClTransferRelease(pRegisteredTransfer); return VERR_INVALID_CONTEXT; + } + ShClTransferRelease(pRegisteredTransfer); PSHCLREPLY const pReply = pParms->u.FileTransferData.pReply; AssertReturn(pReply->uType == VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS, VERR_INVALID_PARAMETER); @@ -308,6 +286,7 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 default: return VINF_SUCCESS; } +#undef SHCL_VALIDATE_ACTIVE } @@ -317,7 +296,7 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 * @returns VBox status code. * @param pParms Service extension parameters containing the callback to install. */ -int GuestShCl::i_handleSvcExtSetCallback(PSHCLEXTPARMS pParms) +int GuestShCl::i_svcExtSetCallback(PSHCLEXTPARMS pParms) { m_pfnExtCallback = pParms->u.SetCallback.pfnCallback; return VINF_SUCCESS; @@ -331,10 +310,8 @@ int GuestShCl::i_handleSvcExtSetCallback(PSHCLEXTPARMS pParms) * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtReportFormatsToHost(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtReportFormatsToHostCallback(PSHCLEXTPARMS pParms) { SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; @@ -346,7 +323,7 @@ int GuestShCl::i_handleSvcExtReportFormatsToHost(PSHCLEXTPARMS pParms, void *pvP fFormats, ClipboardSource_Guest, true /* fForceNotify */); - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, pvParms, cbParms); + int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, pParms, sizeof(*pParms)); return vrc == VERR_NOT_SUPPORTED ? VINF_SUCCESS : vrc; } @@ -362,7 +339,7 @@ int GuestShCl::i_handleSvcExtReportFormatsToHost(PSHCLEXTPARMS pParms, void *pvP * @param pvParms Raw service extension parameters to forward to the chained extension. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtReportFormatsToGuest(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtReportFormatsToGuestCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { RT_NOREF(pParms); @@ -382,7 +359,7 @@ int GuestShCl::i_handleSvcExtReportFormatsToGuest(PSHCLEXTPARMS pParms, void *pv * @param pvParms Raw service extension parameters to forward to the chained extension. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtDataRead(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtDataReadCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_DATA_READ, pvParms, cbParms); if (vrc == VERR_NOT_SUPPORTED) @@ -418,15 +395,14 @@ int GuestShCl::i_handleSvcExtDataRead(PSHCLEXTPARMS pParms, void *pvParms, uint3 * @returns VBox status code. * @param pParms Service extension parameters describing the read request. */ -int GuestShCl::i_handleSvcExtDataReadVrde(PSHCLEXTPARMS pParms) +int GuestShCl::i_svcExtDataReadVrdeCallback(PSHCLEXTPARMS pParms) { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; PSHCLEVENT pEvent; void *pvData = pParms->u.ReadWriteData.pvData; uint32_t cbData = pParms->u.ReadWriteData.cbData; - int vrc = ShClSvcReadDataFromGuestAsync(pClient, fFormats, &pEvent); + int vrc = m_pConn->readDataFromGuestAsync(fFormats, &pEvent); if (RT_SUCCESS(vrc)) { PSHCLEVENTPAYLOAD pPayload = NULL; @@ -459,15 +435,14 @@ int GuestShCl::i_handleSvcExtDataReadVrde(PSHCLEXTPARMS pParms) * @param pvParms Raw service extension parameters to forward to the chained extension. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtDataWrite(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; PSHCLCLIENTCMDCTX pCmdCtx = pParms->u.ReadWriteData.pCmdCtx; SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - PSHCLEVENT pEvent = NULL; - int vrc = ShClSvcGuestDataRetainValidatedEvent(pClient, pCmdCtx, fFormats, &pEvent); - if (RT_FAILURE(vrc) || !pEvent) + SHCLGUESTDATATOKEN hToken = NULL; + int vrc = m_pConn->guestDataBegin(pCmdCtx, fFormats, &hToken); + if (RT_FAILURE(vrc) || !hToken) return vrc; int const vrcChained = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, pvParms, cbParms); @@ -476,15 +451,16 @@ int GuestShCl::i_handleSvcExtDataWrite(PSHCLEXTPARMS pParms, void *pvParms, uint void *pvData = pParms->u.ReadWriteData.pvData; uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLEVENTID const idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); - vrc = ShClSvcGuestDataSignalEvent(pEvent, idEvent, pvData, cbData); + vrc = m_pConn->guestDataComplete(hToken, pvData, cbData); + hToken = NULL; if (RT_FAILURE(vrc)) LogRelMax(16, ("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", vrc)); AssertRC(vrc); } else vrc = vrcChained; - ShClEventRelease(pEvent); + if (hToken) + m_pConn->guestDataCancel(hToken); return vrc; } @@ -500,15 +476,12 @@ int GuestShCl::i_handleSvcExtDataWrite(PSHCLEXTPARMS pParms, void *pvParms, uint * @param pvParms Raw service extension parameters to forward to the chained extension. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtBackendInit(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendInitCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { + RT_NOREF(pParms); int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT, pvParms, cbParms); if (vrc == VERR_NOT_SUPPORTED) - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - VBOXHGCMSVCFNTABLE *pTable = pParms->u.ReadWriteData.pTable; - vrc = ShClBackendInit(pBackend, pTable); - } + vrc = m_pConn->initBackend(); return vrc; } @@ -524,15 +497,12 @@ int GuestShCl::i_handleSvcExtBackendInit(PSHCLEXTPARMS pParms, void *pvParms, ui * @param pvParms Raw service extension parameters to forward to the chained extension. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtBackendDestroy(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendDestroyCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { + RT_NOREF(pParms); int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY, pvParms, cbParms); if (vrc == VERR_NOT_SUPPORTED) - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - ShClBackendDestroy(pBackend); - vrc = VINF_SUCCESS; - } + vrc = m_pConn->destroyBackend(); return vrc; } @@ -548,21 +518,13 @@ int GuestShCl::i_handleSvcExtBackendDestroy(PSHCLEXTPARMS pParms, void *pvParms, * @param pvParms Raw service extension parameters to forward to the chained extension. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtBackendConnect(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendConnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT, pvParms, cbParms); if (vrc == VERR_NOT_SUPPORTED) { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - vrc = ShClBackendConnect(pBackend, pClient); - if (RT_SUCCESS(vrc)) - { - lock(); - m_pClient = pClient; - m_fGuestReadsBlocked = false; - unlock(); - } + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + vrc = m_pConn->connect(&Transport); } return vrc; } @@ -579,28 +541,13 @@ int GuestShCl::i_handleSvcExtBackendConnect(PSHCLEXTPARMS pParms, void *pvParms, * @param pvParms Raw service extension parameters to forward to the chained extension. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtBackendDisconnect(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendDisconnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT, pvParms, cbParms); if (vrc == VERR_NOT_SUPPORTED) { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - lock(); - if (m_pClient == pClient) - m_fGuestReadsBlocked = true; - unlock(); - - i_waitForGuestReads(); - - vrc = ShClBackendDisconnect(pClient->pBackend, pClient); - - lock(); - if (m_pClient == pClient) - { - m_pClient = NULL; - m_fGuestReadsBlocked = false; - } - unlock(); + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + vrc = m_pConn->disconnect(&Transport); } return vrc; } @@ -617,15 +564,12 @@ int GuestShCl::i_handleSvcExtBackendDisconnect(PSHCLEXTPARMS pParms, void *pvPar * @param pvParms Raw service extension parameters to forward to the chained extension. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtBackendSync(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendSyncCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { + RT_NOREF(pParms); int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC, pvParms, cbParms); if (vrc == VERR_NOT_SUPPORTED) - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - vrc = ShClBackendSync(pBackend, pClient); - } + vrc = m_pConn->syncBackend(); return vrc; } @@ -638,13 +582,28 @@ int GuestShCl::i_handleSvcExtBackendSync(PSHCLEXTPARMS pParms, void *pvParms, ui * @returns VBox status code. * @param pParms Service extension parameters containing the error details. */ -int GuestShCl::i_handleSvcExtError(PSHCLEXTPARMS pParms) +int GuestShCl::i_svcExtErrorCallback(PSHCLEXTPARMS pParms) { return ReportError(pParms->u.Error.pszId, pParms->u.Error.rc, pParms->u.Error.pszMsg); } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Handles VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS from the Shared Clipboard host service. + * + * @returns VBox status code. + * @param pParms Service extension parameters containing the callback destination. + */ +int GuestShCl::i_svcExtTransferGetCallbacksCallback(PSHCLEXTPARMS pParms) +{ + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + return m_pConn->matches(&Transport) + ? m_pConn->transferGetCallbacks(pParms->u.TransferCallbacks.pCallbacks) + : VERR_INVALID_PARAMETER; +} + + /** * Handles VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER from the Shared Clipboard host service. * @@ -653,12 +612,9 @@ int GuestShCl::i_handleSvcExtError(PSHCLEXTPARMS pParms) * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtFileTransfer(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtFileTransferCallback(PSHCLEXTPARMS pParms) { - PSHCLCLIENT pClient = pParms->u.FileTransferData.pClient; PSHCLTRANSFER pTransfer = pParms->u.FileTransferData.pTransfer; SHCLSOURCE const enmShClSource = pParms->u.FileTransferData.enmShClSource; PSHCLREPLY pReply = pParms->u.FileTransferData.pReply; @@ -668,10 +624,10 @@ int GuestShCl::i_handleSvcExtFileTransfer(PSHCLEXTPARMS pParms, void *pvParms, u SHCLTRANSFERSTATUS const enmStatus = pReply->u.TransferStatus.uStatus; int const vrcTransfer = (int)pReply->rc; - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, pvParms, cbParms); + int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, pParms, sizeof(*pParms)); if (vrc == VERR_NOT_SUPPORTED) - vrc = ShClBackendTransferHandleStatusReply(pClient->pBackend, pClient, pTransfer, enmShClSource, - pReply->u.TransferStatus.uStatus, (int)pReply->rc); + vrc = m_pConn->transferHandleStatusReply(pTransfer, enmShClSource, + pReply->u.TransferStatus.uStatus, (int)pReply->rc); if (RT_SUCCESS(vrc)) { diff --git a/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp b/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp deleted file mode 100644 index 07fd13fbe33a..000000000000 --- a/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp +++ /dev/null @@ -1,413 +0,0 @@ -/* $Id: VBoxSharedClipboardSvc-utils.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard Service - Host service utility functions. - */ - -/* - * Copyright (C) 2019-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD -#include - -#include -#include -#include - - -/** - * Validates and retains a pending guest-data event. - * - * @returns VBox status code. Stale replies for expired event IDs are ignored; - * in that case @a ppEvent is set to NULL and VINF_SUCCESS is returned. - * @param pClient Client the guest clipboard data was received from. - * @param pCmdCtx Client command context. - * @param uFormat Clipboard format of data received. - * @param ppEvent Where to return the retained event. Must be - * released with ShClEventRelease(). - * - * @thread Backend thread. - */ -int ShClSvcGuestDataRetainValidatedEvent(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, - PSHCLEVENT *ppEvent) -{ - LogFlowFuncEnter(); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); - AssertPtrReturn(ppEvent, VERR_INVALID_POINTER); - *ppEvent = NULL; - - if (!ShClFormatIsValid(uFormat)) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); - return VERR_INVALID_PARAMETER; - } - - SHCLSESSIONID const idSession = VBOX_SHCL_CONTEXTID_GET_SESSION(pCmdCtx->uContextID); - SHCLEVENTSOURCEID const idEventSource = VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->uContextID); - const SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); - if ( idEvent == 0 - || idEvent == NIL_SHCLEVENTID) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid event %#x in context ID %#RX64\n", - idEvent, pCmdCtx->uContextID)); - return VERR_WRONG_ORDER; - } - if ( idSession != pClient->State.uSessionID - || idEventSource != pClient->EventSrc.uID) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with mismatching context ID %#RX64" - " (session %#x/%#x, event source %#x/%#x)\n", - pCmdCtx->uContextID, idSession, pClient->State.uSessionID, - idEventSource, pClient->EventSrc.uID)); - return VERR_INVALID_CONTEXT; - } - - PSHCLEVENT pEvent = ShClEventSourceRetainFromId(&pClient->EventSrc, idEvent); - if (!RT_VALID_PTR(pEvent)) - { - LogRelMax2(16, ("Shared Clipboard: Ignoring late guest clipboard data for expired event %#x\n", idEvent)); - return VINF_SUCCESS; - } - if (pEvent->uUser != uFormat) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data format %#x for event %#x, expected %#x\n", - uFormat, idEvent, pEvent->uUser)); - ShClEventRelease(pEvent); - return VERR_INVALID_CONTEXT; - } - - *ppEvent = pEvent; - LogFlowFuncLeaveRC(VINF_SUCCESS); - return VINF_SUCCESS; -} - - -/** - * Signals a retained guest-data event with clipboard data received from the guest. - * - * @returns VBox status code. - * @param pEvent Retained event to signal. - * @param idEvent Event ID to use for the optional payload wrapper. - * @param pvData Pointer to clipboard data received. This can be - * NULL if @a cbData is zero. - * @param cbData Size (in bytes) of clipboard data received. - * This can be zero. - * - * @thread Backend thread. - */ -int ShClSvcGuestDataSignalEvent(PSHCLEVENT pEvent, SHCLEVENTID idEvent, void *pvData, uint32_t cbData) -{ - LogFlowFuncEnter(); - - AssertPtrReturn(pEvent, VERR_INVALID_POINTER); - if (cbData > 0) - AssertPtrReturn(pvData, VERR_INVALID_POINTER); - - /* - * Make a copy of the data so we can attach it to the signal. - * - * Note! We still signal the waiter should we run out of memory, - * because otherwise it will be stuck waiting. - */ - int vrc = VINF_SUCCESS; - PSHCLEVENTPAYLOAD pPayload = NULL; - if (cbData > 0) - vrc = ShClPayloadCreateDupData(idEvent, pvData, cbData, &pPayload); - - /* - * Signal the event. - */ - int vrc2 = ShClEventSignalEx(pEvent, vrc, pPayload); - if (RT_FAILURE(vrc2)) - { - vrc = vrc2; - ShClPayloadDestroy(pPayload); - LogRel(("Shared Clipboard: Signalling of guest clipboard data to the host failed: %Rrc\n", vrc)); - } - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - - -/** - * Signals the host that clipboard data from the guest has been received. - * - * @returns VBox status code. Stale replies for expired event IDs are ignored. - * @param pClient Client the guest clipboard data was received from. - * @param pCmdCtx Client command context. - * @param uFormat Clipboard format of data received. - * @param pvData Pointer to clipboard data received. This can be - * NULL if @a cbData is zero. - * @param cbData Size (in bytes) of clipboard data received. - * This can be zero. - * - * @thread Backend thread. - */ -int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) -{ - LogFlowFuncEnter(); - - PSHCLEVENT pEvent = NULL; - int vrc = ShClSvcGuestDataRetainValidatedEvent(pClient, pCmdCtx, uFormat, &pEvent); - if (RT_FAILURE(vrc) || !pEvent) - return vrc; - - SHCLEVENTID const idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); - vrc = ShClSvcGuestDataSignalEvent(pEvent, idEvent, pvData, cbData); - ShClEventRelease(pEvent); - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -/** - * Reads clipboard data from the guest, asynchronous version. - * - * @returns VBox status code. - * @param pClient Client to request to read data form. - * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). - * @param ppEvent Where to return the event for waiting for new data on success. - * Must be released by the caller with ShClEventRelease(). Optional. - * - * @thread On X11: Called from the X11 event thread. - * @thread On Windows: Called from the Windows event thread. - * - * @note This will locally initialize a transfer if VBOX_SHCL_FMT_URI_LIST is being requested from the guest. - */ -int ShClSvcReadDataFromGuestAsync(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENT *ppEvent) -{ - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - LogFlowFunc(("fFormats=%#x\n", fFormats)); - - if (ppEvent) - *ppEvent = NULL; - - SHCLFORMATS const fSupportedFormats = VBOX_SHCL_FMT_UNICODETEXT - | VBOX_SHCL_FMT_BITMAP - | VBOX_SHCL_FMT_HTML -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - | VBOX_SHCL_FMT_URI_LIST -#endif - ; - if ( fFormats == VBOX_SHCL_FMT_NONE - || (fFormats & ~fSupportedFormats)) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting unsupported guest clipboard data request formats %#x\n", fFormats)); - return VERR_NOT_SUPPORTED; - } - if ( ppEvent - && (fFormats & (fFormats - 1)) != 0) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting multi-format guest clipboard data request %#x with single event output\n", - fFormats)); - return VERR_INVALID_PARAMETER; - } - - int vrc = VERR_NOT_SUPPORTED; - - /* Generate a separate message for every (valid) format we support. */ - while (fFormats) - { - /* Pick the next format to get from the mask: */ - /** @todo Make format reporting precedence configurable? */ - SHCLFORMAT fFormat; - if (fFormats & VBOX_SHCL_FMT_UNICODETEXT) - fFormat = VBOX_SHCL_FMT_UNICODETEXT; - else if (fFormats & VBOX_SHCL_FMT_BITMAP) - fFormat = VBOX_SHCL_FMT_BITMAP; - else if (fFormats & VBOX_SHCL_FMT_HTML) - fFormat = VBOX_SHCL_FMT_HTML; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - else if (fFormats & VBOX_SHCL_FMT_URI_LIST) - fFormat = VBOX_SHCL_FMT_URI_LIST; -#endif - else - { - vrc = VERR_NOT_SUPPORTED; - break; - } - - /* Remove it from the mask. */ - fFormats &= ~fFormat; - - if (LogRelIs2Enabled()) - { - char *pszFmt = ShClFormatsToStrA(fFormat); - LogRel2(("Shared Clipboard: Requesting guest clipboard data in format %#x/'%s'\n", - fFormat, pszFmt ? pszFmt : "")); - RTStrFree(pszFmt); - } - /* - * Allocate messages, one for each format. - */ - uint64_t const fGuestFeatures0 = ShClSvcClientGetGuestFeatures0(pClient); - PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, - fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID - ? VBOX_SHCL_HOST_MSG_READ_DATA_CID : VBOX_SHCL_HOST_MSG_READ_DATA, - 2); - if (pMsg) - { - ShClSvcClientLock(pClient); - - PSHCLEVENT pEvent; - vrc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); - if (RT_SUCCESS(vrc)) - { - LogFlowFunc(("fFormats=%#x -> fFormat=%#x, idEvent=%#x\n", fFormats, fFormat, pEvent->idEvent)); - pEvent->uUser = fFormat; - - const uint64_t uCID = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, pEvent->idEvent); - - vrc = VINF_SUCCESS; - - /* Save the context ID in our legacy cruft if we have to deal with old(er) Guest Additions (< 6.1). */ - if (!(fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) - { - AssertStmt(pClient->Legacy.cCID < 4096, vrc = VERR_TOO_MUCH_DATA); - if (RT_SUCCESS(vrc)) - { - PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); - if (pCID) - { - pCID->uCID = uCID; - pCID->enmType = 0; /* Not used yet. */ - pCID->uFormat = fFormat; - RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); - pClient->Legacy.cCID++; - } - else - vrc = VERR_NO_MEMORY; - } - } - - if (RT_SUCCESS(vrc)) - { - /* - * Format the message. - */ - if (pMsg->idMsg == VBOX_SHCL_HOST_MSG_READ_DATA_CID) - HGCMSvcSetU64(&pMsg->aParms[0], uCID); - else - HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_READ_DATA); - HGCMSvcSetU32(&pMsg->aParms[1], fFormat); - - ShClSvcClientMsgAdd(pClient, pMsg, true /* fAppend */); - /* Wake up the client to let it know that there are new messages. */ - ShClSvcClientWakeup(pClient); - - /* Return event to caller. */ - if (ppEvent) - *ppEvent = pEvent; - } - - /* Remove event from list if caller did not request event handle or in case - * of failure (in this case caller should not release event). */ - if ( RT_FAILURE(vrc) - || !ppEvent) - { - ShClEventRelease(pEvent); - pEvent = NULL; - } - } - else - vrc = VERR_SHCLPB_MAX_EVENTS_REACHED; - - if (RT_FAILURE(vrc)) - ShClSvcClientMsgFree(pClient, pMsg); - - ShClSvcClientUnlock(pClient); - } - else - vrc = VERR_NO_MEMORY; - - if (RT_FAILURE(vrc)) - break; - } - - if (RT_FAILURE(vrc)) - LogRel(("Shared Clipboard: Requesting guest clipboard data failed with %Rrc\n", vrc)); - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -/** - * Reads clipboard data from the guest. - * - * @returns VBox status code. - * @retval VERR_SHCLPB_NO_DATA if no clipboard data is available. - * @param pClient Client to request to read data form. - * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). - * @param ppv Where to return the allocated data read. - * Must be free'd by the caller. - * @param pcb Where to return number of bytes read. - */ -int ShClSvcReadDataFromGuest(PSHCLCLIENT pClient, SHCLFORMAT fFormats, void **ppv, uint32_t *pcb) -{ - AssertPtrReturn(ppv, VERR_INVALID_POINTER); - AssertPtrReturn(pcb, VERR_INVALID_POINTER); - - LogFlowFuncEnter(); - - /* Request data from the guest and wait for data to arrive. */ - PSHCLEVENT pEvent; - int vrc = ShClSvcReadDataFromGuestAsync(pClient, fFormats, &pEvent); - if (RT_SUCCESS(vrc)) - { - PSHCLEVENTPAYLOAD pPayload; - vrc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload); - if (RT_SUCCESS(vrc)) - { - if ( pPayload - && pPayload->cbData) - { - *ppv = pPayload->pvData; - *pcb = pPayload->cbData; - - LogFlowFunc(("pv=%p, cb=%RU32\n", pPayload->pvData, pPayload->cbData)); - - pPayload->pvData = NULL; - pPayload->cbData = 0; - ShClPayloadDestroy(pPayload); - } - else - { - ShClPayloadDestroy(pPayload); - vrc = VERR_SHCLPB_NO_DATA; - } - } - - ShClEventRelease(pEvent); - } - - if ( RT_FAILURE(vrc) - && vrc != VERR_SHCLPB_NO_DATA) - LogRel(("Shared Clipboard: Reading data from guest failed with %Rrc\n", vrc)); - return vrc; -} diff --git a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp index 0c7324555d11..e675aa133671 100644 --- a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp +++ b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendDarwin.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendDarwin.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host. */ @@ -31,7 +31,9 @@ *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD #include -#include +#include "GuestShClBackend.h" +#include "../GuestShClBackendPrivate.h" +#include "GuestShClConn.h" #include #include @@ -42,9 +44,6 @@ #include #include "darwin-pasteboard.h" -#ifdef VBOX_COM_INPROC -# include "GuestShClPrivate.h" -#endif /********************************************************************************************************************************* @@ -59,9 +58,9 @@ typedef struct SHCLCONTEXT bool volatile fTerminate; /** The reference to the current pasteboard */ PasteboardRef hPasteboard; - /** Shared clipboard client. */ - PSHCLCLIENT pClient; - /** Whether @a pClient may be used by the pasteboard poller. */ + /** Main connection to the Shared Clipboard service. */ + GuestShClConn *pConn; + /** Whether @a pConn may be used by the pasteboard poller. */ bool fClientReady; /** Random 64-bit number embedded into szGuestOwnershipFlavor. */ uint64_t idGuestOwnership; @@ -88,10 +87,7 @@ static SHCLCONTEXT g_ctx; /** @copydoc SHCLTXPROVIDERIFACE::pfnRootListRead */ static DECLCALLBACK(int) shClSvcDarwinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pProviderCtx) { - PSHCLCLIENT pClient = (PSHCLCLIENT)pProviderCtx->pvUser; - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - SHCLCONTEXT *pCtx = pClient->State.pCtx; + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pProviderCtx->pvUser; AssertPtrReturn(pCtx, VERR_INVALID_POINTER); char *pszRoots = NULL; @@ -117,23 +113,24 @@ static DECLCALLBACK(int) shClSvcDarwinTransferIfaceHGRootListRead(PSHCLTXPROVIDE /** @copydoc SHCLTRANSFERCALLBACKS::pfnOnCreated */ static DECLCALLBACK(void) shClSvcDarwinTransferOnCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { - PSHCLCLIENT pClient = (PSHCLCLIENT)pCbCtx->pvUser; - AssertPtrReturnVoid(pClient); + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; + AssertPtrReturnVoid(pCtx); PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtrReturnVoid(pTransfer); - RT_ZERO(pClient->Transfers.Provider); if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_TO_REMOTE && ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL) { - ShClTransferProviderLocalQueryInterface(&pClient->Transfers.Provider); - pClient->Transfers.Provider.Interface.pfnRootListRead = shClSvcDarwinTransferIfaceHGRootListRead; - pClient->Transfers.Provider.enmSource = SHCLSOURCE_LOCAL; - pClient->Transfers.Provider.pvUser = pClient; - pClient->Transfers.Provider.cbUser = sizeof(*pClient); - - int const vrc = ShClTransferSetProvider(pTransfer, &pClient->Transfers.Provider); + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); + ShClTransferProviderLocalQueryInterface(&Provider); + Provider.Interface.pfnRootListRead = shClSvcDarwinTransferIfaceHGRootListRead; + Provider.enmSource = SHCLSOURCE_LOCAL; + Provider.pvUser = pCtx; + Provider.cbUser = sizeof(*pCtx); + + int const vrc = ShClTransferSetProvider(pTransfer, &Provider); AssertRC(vrc); } } @@ -153,14 +150,6 @@ static DECLCALLBACK(int) shClSvcDarwinTransferOnInitializeCallback(PSHCLTRANSFER #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ -static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ -#ifdef VBOX_COM_INPROC - return GuestShCl::GetInst()->ReportFormatsToGuest(pClient, fFormats, SHCLSOURCE_LOCAL); -#endif - return ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); -} - /** * Checks if something is present on the clipboard and calls shclSvcReportMsg. * @@ -170,14 +159,14 @@ static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMA * its change was already observed. * */ -static int vboxClipboardChanged(SHCLCONTEXT *pCtx, bool fForce) +static int vboxClipboardChanged(PSHCLCONTEXT pCtx, bool fForce) { int vrc = VINF_SUCCESS; uint32_t fFormats = 0; RTCritSectEnter(&pCtx->CritSect); - if ( pCtx->pClient + if ( pCtx->pConn && pCtx->fClientReady) { /* Retrieve the formats currently in the clipboard and supported by VBox. */ @@ -193,14 +182,8 @@ static int vboxClipboardChanged(SHCLCONTEXT *pCtx, bool fForce) if (RT_SUCCESS(vrc)) vrc = vrc2; } - if ( RT_SUCCESS(vrc) - && fChanged) - { - uint32_t const uMode = ShClSvcClientGetMode(pCtx->pClient); - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) - vrc = shClBackendReportFormatsToGuestAndMain(pCtx->pClient, fFormats); - } + if (RT_SUCCESS(vrc) && fChanged) + vrc = pCtx->pConn->reportLocalFormats(fFormats); } RTCritSectLeave(&pCtx->CritSect); @@ -216,7 +199,7 @@ static int vboxClipboardChanged(SHCLCONTEXT *pCtx, bool fForce) */ static DECLCALLBACK(int) vboxClipboardThread(RTTHREAD ThreadSelf, void *pvUser) { - SHCLCONTEXT *pCtx = (SHCLCONTEXT *)pvUser; + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser; AssertPtr(pCtx); LogFlowFuncEnter(); int vrc; @@ -236,7 +219,12 @@ static DECLCALLBACK(int) vboxClipboardThread(RTTHREAD ThreadSelf, void *pvUser) } -int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +/** + * Initializes the process-wide macOS clipboard backend. + * + * @returns VBox status code. + */ +static int shClBackendDarwinInit(void) { g_ctx.fTerminate = false; g_ctx.fClientReady = false; @@ -261,8 +249,6 @@ int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) return vrc; } - pBackend->pHelpers = pTable->pHelpers; - vrc = RTThreadCreate(&g_ctx.hThread, vboxClipboardThread, &g_ctx, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP"); if (RT_FAILURE(vrc)) @@ -276,10 +262,11 @@ int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) return vrc; } -void ShClBackendDestroy(PSHCLBACKEND pBackend) +/** + * Destroys the process-wide macOS clipboard backend. + */ +static void shClBackendDarwinDestroy(void) { - RT_NOREF(pBackend); - /* * Signal the termination of the polling thread and wait for it to respond. */ @@ -289,7 +276,7 @@ void ShClBackendDestroy(PSHCLBACKEND pBackend) int vrc = RTThreadUserSignal(g_ctx.hThread); AssertRC(vrc); vrc = RTThreadWait(g_ctx.hThread, RT_INDEFINITE_WAIT, NULL); - AssertRC(vrc); + AssertFatalMsgRC(vrc, ("Reaping the Darwin clipboard poller failed with %Rrc\n", vrc)); g_ctx.hThread = NIL_RTTHREAD; } @@ -297,7 +284,7 @@ void ShClBackendDestroy(PSHCLBACKEND pBackend) * Destroy the hPasteboard and uninitialize the global context record. */ destroyPasteboard(&g_ctx.hPasteboard); - g_ctx.pClient = NULL; + g_ctx.pConn = NULL; g_ctx.fClientReady = false; if (RTCritSectIsInitialized(&g_ctx.CritSectPasteboard)) @@ -306,25 +293,27 @@ void ShClBackendDestroy(PSHCLBACKEND pBackend) RTCritSectDelete(&g_ctx.CritSect); } -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +/** + * Connects a Main service connection to the macOS clipboard backend. + * + * @returns VBox status code. + * @param pConn Main service connection to associate. + * @param ppCtx Where to return the backend context. + */ +static int shClBackendDarwinConnect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) { - RT_NOREF(pBackend); + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); + *ppCtx = NULL; RTCritSectEnter(&g_ctx.CritSect); int vrc; - if (g_ctx.pClient == NULL) + if (!g_ctx.pConn) { - pClient->State.pCtx = &g_ctx; - g_ctx.pClient = pClient; + g_ctx.pConn = pConn; g_ctx.fClientReady = false; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_ZERO(pClient->Transfers.Callbacks); - pClient->Transfers.Callbacks.pvUser = pClient; - pClient->Transfers.Callbacks.cbUser = sizeof(*pClient); - pClient->Transfers.Callbacks.pfnOnCreated = shClSvcDarwinTransferOnCreatedCallback; - pClient->Transfers.Callbacks.pfnOnInitialize = shClSvcDarwinTransferOnInitializeCallback; -#endif + *ppCtx = &g_ctx; vrc = VINF_SUCCESS; } else @@ -335,52 +324,102 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) return vrc; } -int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Returns the macOS callbacks for a new transfer. + * + * @param pCtx Connected backend context. + * @param pCallbacks Where to return the callback table. + */ +static void shClBackendDarwinTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) +{ + AssertPtrReturnVoid(pCallbacks); + RT_ZERO(*pCallbacks); + AssertPtrReturnVoid(pCtx); + AssertPtrReturnVoid(pCtx->pConn); + + pCallbacks->pvUser = pCtx; + pCallbacks->cbUser = sizeof(*pCtx); + pCallbacks->pfnOnCreated = shClSvcDarwinTransferOnCreatedCallback; + pCallbacks->pfnOnInitialize = shClSvcDarwinTransferOnInitializeCallback; +} +#endif + +/** + * Synchronizes macOS clipboard state with a connected guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + */ +static int shClBackendDarwinSync(PSHCLCONTEXT pCtx) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); - /* GuestShCl records the active client after ShClBackendConnect returns. Do - * not expose it to the poller before that lifetime guard is in place. */ + /* GuestShClConn publishes the connection while shClBackendDarwinConnect runs, but + * do not expose it to the poller until the initial service sync. */ RTCritSectEnter(&g_ctx.CritSect); int vrc = VINF_SUCCESS; - if (pClient->State.pCtx->pClient == pClient) - pClient->State.pCtx->fClientReady = true; + if (pCtx->pConn) + pCtx->fClientReady = true; else vrc = VERR_NOT_SUPPORTED; RTCritSectLeave(&g_ctx.CritSect); /* Sync the host clipboard content with the client. */ if (RT_SUCCESS(vrc)) - vrc = vboxClipboardChanged(pClient->State.pCtx, true /* fForce */); + vrc = vboxClipboardChanged(pCtx, true /* fForce */); return vrc; } -int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +/** + * Disconnects a Main service connection from the macOS clipboard backend. + * + * @returns VBox status code. + * @param pCtx Backend context to disconnect. + */ +static int shClBackendDarwinDisconnect(PSHCLCONTEXT pCtx) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + GuestShClConn * const pConn = pCtx->pConn; +#endif RTCritSectEnter(&g_ctx.CritSect); - if (pClient->State.pCtx->pClient == pClient) + if (pCtx->pConn) { - pClient->State.pCtx->fClientReady = false; - pClient->State.pCtx->pClient = NULL; + pCtx->fClientReady = false; + pCtx->pConn = NULL; } RTCritSectLeave(&g_ctx.CritSect); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* Transfer callback tables retain pCtx as their user argument. */ + pConn->transferDestroyAll(); +#endif + return VINF_SUCCESS; } -int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +/** + * Reports guest clipboard formats to the macOS pasteboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ +static int shClBackendDarwinReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFunc(("fFormats=%02X\n", fFormats)); if (fFormats == VBOX_SHCL_FMT_NONE) { - SHCLCONTEXT *pCtx = pClient->State.pCtx; RTCritSectEnter(&pCtx->CritSectPasteboard); int vrcClear = clearPasteboard(pCtx->hPasteboard, &pCtx->hStrOwnershipFlavor); RTCritSectLeave(&pCtx->CritSectPasteboard); @@ -394,7 +433,6 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR fFormats &= ~VBOX_SHCL_FMT_URI_LIST; if (fFormats == VBOX_SHCL_FMT_NONE) { - SHCLCONTEXT *pCtx = pClient->State.pCtx; RTCritSectEnter(&pCtx->CritSectPasteboard); int vrcClear = clearPasteboard(pCtx->hPasteboard, &pCtx->hStrOwnershipFlavor); RTCritSectLeave(&pCtx->CritSectPasteboard); @@ -403,7 +441,6 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR } #endif - SHCLCONTEXT *pCtx = pClient->State.pCtx; RTCritSectEnter(&pCtx->CritSectPasteboard); /* @@ -429,73 +466,61 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR /* * Now, request the data from the guest. */ - return ShClSvcReadDataFromGuestAsync(pClient, fFormats, NULL /* ppEvent */); + return pCtx->pConn->readDataFromGuestAsync(fFormats, NULL /* ppEvent */); } /** - * The host reports clipboard formats to the guest clipboard. + * Reads clipboard data from the macOS pasteboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormat Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required byte count. */ -int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - RT_NOREF(pBackend); - - int vrc; - - PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); - if (pMsg) - { - HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); - HGCMSvcSetU32(&pMsg->aParms[1], fFormats); - - ShClSvcClientLock(pClient); - - vrc = shClSvcClientMsgAddAndWakeupClient(pClient, pMsg); - - ShClSvcClientUnlock(pClient); - } - else - vrc = VERR_NO_MEMORY; - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT fFormat, - void *pvData, uint32_t cbData, uint32_t *pcbActual) +static int shClBackendDarwinReadData(PSHCLCONTEXT pCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) { - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); AssertPtrReturn(pvData, VERR_INVALID_POINTER); AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); - RT_NOREF(pBackend, pCmdCtx); - - RTCritSectEnter(&pClient->State.pCtx->CritSectPasteboard); + RTCritSectEnter(&pCtx->CritSectPasteboard); /* Default to no data available. */ *pcbActual = 0; - int vrc = readFromPasteboard(pClient->State.pCtx->hPasteboard, fFormat, pvData, cbData, pcbActual); + int vrc = readFromPasteboard(pCtx->hPasteboard, fFormat, pvData, cbData, pcbActual); if (RT_FAILURE(vrc)) LogRel(("Shared Clipboard: Error reading host clipboard data from macOS, vrc=%Rrc\n", vrc)); - RTCritSectLeave(&pClient->State.pCtx->CritSectPasteboard); + RTCritSectLeave(&pCtx->CritSectPasteboard); return vrc; } -int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData) +/** + * Writes guest clipboard data to the macOS pasteboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormat Clipboard format to write. + * @param pvData Data buffer. + * @param cbData Data size in bytes. + */ +static int shClBackendDarwinWriteData(PSHCLCONTEXT pCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData) { - RT_NOREF(pBackend, pCmdCtx); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFuncEnter(); - RTCritSectEnter(&pClient->State.pCtx->CritSectPasteboard); + RTCritSectEnter(&pCtx->CritSectPasteboard); - int vrc = writeToPasteboard(pClient->State.pCtx->hPasteboard, pClient->State.pCtx->idGuestOwnership, - pvData, cbData, fFormat); + int vrc = writeToPasteboard(pCtx->hPasteboard, pCtx->idGuestOwnership, pvData, cbData, fFormat); - RTCritSectLeave(&pClient->State.pCtx->CritSectPasteboard); + RTCritSectLeave(&pCtx->CritSectPasteboard); if (RT_FAILURE(vrc)) LogRel(("Shared Clipboard: Writing guest data to the macOS pasteboard failed, vrc=%Rrc\n", vrc)); @@ -505,24 +530,51 @@ int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENT } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# ifndef UNIT_TEST /** * Handles transfer status replies from the guest. * * @returns VBox status code. - * @param pBackend Shared Clipboard backend. - * @param pClient Shared Clipboard client context. + * @param pCtx Shared Clipboard backend context. * @param pTransfer Shared Clipboard transfer. * @param enmSource Transfer source which issued the reply. * @param enmStatus Transfer status. * @param rcStatus Transfer status code. */ -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, - SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus) +static int shClBackendDarwinTransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) { - RT_NOREF(pBackend, pClient, pTransfer, enmSource, enmStatus, rcStatus); + RT_NOREF(pCtx, pTransfer, enmSource, enmStatus, rcStatus); return VINF_SUCCESS; } -# endif /* !UNIT_TEST */ #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +/** Native macOS Shared Clipboard backend operations. */ +static SHCLBACKENDOPS const s_ShClBackendDarwinOps = +{ + shClBackendDarwinInit, + shClBackendDarwinDestroy, + NULL, + shClBackendDarwinConnect, + shClBackendDarwinDisconnect, + shClBackendDarwinReportFormats, + shClBackendDarwinReadData, + shClBackendDarwinWriteData, + shClBackendDarwinSync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClBackendDarwinTransferGetCallbacks, + shClBackendDarwinTransferHandleStatusReply, +#endif +}; + + +/** + * Returns the native macOS Shared Clipboard backend operations. + * + * @returns Immutable macOS backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &s_ShClBackendDarwinOps; +} diff --git a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp index a2a616371792..a9769ceff421 100644 --- a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp +++ b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendX11.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendX11.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - X11 backend. */ @@ -42,26 +42,20 @@ #include #include #include -#include +#include "GuestShClBackend.h" +#include "../GuestShClBackendPrivate.h" +#include "GuestShClConn.h" #include -#ifdef VBOX_COM_INPROC -# include "GuestShClPrivate.h" -#endif - #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include -# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP -# include -# endif #endif - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# include "VBoxSharedClipboardSvc-transfers.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +# include #endif -/* Number of currently extablished connections. */ -static volatile uint32_t g_cShClConnections; +/** Test callback overrides applied when constructing a new X11 context. */ +static SHCLCALLBACKS g_ShClCallbackOverrides; /********************************************************************************************************************************* @@ -77,8 +71,10 @@ struct SHCLCONTEXT RTCRITSECT CritSect; /** X11 context data. */ SHCLX11CTX X11; - /** Pointer to the VBox host client data structure. */ - PSHCLCLIENT pClient; + /** Main connection to the Shared Clipboard service. */ + GuestShClConn *pConn; + /** Event source used for synchronous X11 reads. */ + SHCLEVENTSOURCE EventSrc; /** We set this when we start shutting down as a hint not to post any new * requests. */ bool fShuttingDown; @@ -179,7 +175,7 @@ static void shClSvcX11TransferPublishedCancel(PSHCLCONTEXT pCtx) if (idTransfer == NIL_SHCLTRANSFERID) return; - PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferByIdRetained(&pCtx->pClient->Transfers.Ctx, idTransfer); + PSHCLTRANSFER pTransfer = pCtx->pConn->transferGetByIdRetained(idTransfer); if ( pTransfer && shClSvcX11TransferKeyMatches(idTransfer, uGeneration, pTransfer)) { @@ -187,7 +183,7 @@ static void shClSvcX11TransferPublishedCancel(PSHCLCONTEXT pCtx) if (enmStatus != SHCLTRANSFERSTATUS_STARTED) { ShClTransferRelease(pTransfer); - ShClSvcTransferDestroyById(pCtx->pClient, idTransfer); + pCtx->pConn->transferDestroyById(idTransfer); } else { @@ -226,8 +222,12 @@ static int shClSvcX11TransferPreparationStart(PSHCLCONTEXT pCtx) return VINF_SUCCESS; pCtx->fShuttingDown = true; - RTSemEventSignal(pCtx->hX11TransferPreparationEvent); - RTThreadWait(pCtx->hX11TransferPreparationThread, RT_INDEFINITE_WAIT, NULL); + int const vrcSignal = RTSemEventSignal(pCtx->hX11TransferPreparationEvent); + AssertFatalMsgRC(vrcSignal, ("Signalling the X11 transfer preparation worker after startup failure" + " failed with %Rrc\n", vrcSignal)); + int const vrcWait = RTThreadWait(pCtx->hX11TransferPreparationThread, RT_INDEFINITE_WAIT, NULL); + AssertFatalMsgRC(vrcWait, ("Reaping the X11 transfer preparation worker after startup failure" + " failed with %Rrc\n", vrcWait)); pCtx->hX11TransferPreparationThread = NIL_RTTHREAD; } @@ -248,22 +248,23 @@ static int shClSvcX11TransferPreparationStop(PSHCLCONTEXT pCtx) } int vrc = RTCritSectEnter(&pCtx->CritSect); - AssertRCReturn(vrc, vrc); + AssertFatalMsgRC(vrc, ("Entering X11 backend critical section during shutdown failed with %Rrc\n", vrc)); pCtx->fShuttingDown = true; pCtx->X11TransferState.uOfferGeneration++; vrc = RTCritSectLeave(&pCtx->CritSect); - AssertRCReturn(vrc, vrc); + AssertFatalMsgRC(vrc, ("Leaving the X11 backend critical section during shutdown failed with %Rrc\n", vrc)); int vrc2 = RTSemEventSignal(pCtx->hX11TransferPreparationEvent); + AssertFatalMsgRC(vrc2, ("Signalling the X11 transfer preparation worker during shutdown failed with %Rrc\n", vrc2)); if (RT_SUCCESS(vrc)) vrc = vrc2; vrc2 = RTThreadWait(pCtx->hX11TransferPreparationThread, RT_INDEFINITE_WAIT, NULL); - if (RT_FAILURE(vrc2)) - return vrc2; + AssertFatalMsgRC(vrc2, ("Reaping the X11 transfer preparation worker during shutdown failed with %Rrc\n", vrc2)); pCtx->hX11TransferPreparationThread = NIL_RTTHREAD; vrc2 = RTSemEventDestroy(pCtx->hX11TransferPreparationEvent); + AssertFatalMsgRC(vrc2, ("Destroying the X11 transfer preparation event failed with %Rrc\n", vrc2)); if (RT_SUCCESS(vrc)) vrc = vrc2; pCtx->hX11TransferPreparationEvent = NIL_RTSEMEVENT; @@ -276,38 +277,42 @@ static int shClSvcX11TransferPreparationStop(PSHCLCONTEXT pCtx) /********************************************************************************************************************************* * Backend implementation * *********************************************************************************************************************************/ -int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +/** + * Initializes the process-wide X11 clipboard backend. + * + * @returns VBox status code. + */ +static int shClBackendX11Init(void) { - RT_NOREF(pBackend); - LogFlowFuncEnter(); - /* Override the connection limit. */ - for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) - pTable->acMaxClients[i] = RT_MIN(VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX, pTable->acMaxClients[i]); - - RT_ZERO(pBackend->Callbacks); - /* Use internal callbacks by default. */ - pBackend->Callbacks.pfnReportFormats = shClSvcX11ReportFormatsCallback; - pBackend->Callbacks.pfnOnRequestDataFromSource = shClSvcX11RequestDataFromSourceCallback; - - pBackend->pHelpers = pTable->pHelpers; + RT_ZERO(g_ShClCallbackOverrides); return VINF_SUCCESS; } -void ShClBackendDestroy(PSHCLBACKEND pBackend) +/** + * Destroys the process-wide X11 clipboard backend. + */ +static void shClBackendX11Destroy(void) { - RT_NOREF(pBackend); - LogFlowFuncEnter(); } -void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks) +/** + * Replaces the X11 callback table for testing. + * + * @param pCallbacks Callback overrides, or NULL to restore defaults. + */ +static void shClBackendX11SetCallbacks(PSHCLCALLBACKS pCallbacks) { + RT_ZERO(g_ShClCallbackOverrides); + if (!pCallbacks) + return; + #define SET_FN_IF_NOT_NULL(a_Fn) \ if (pCallbacks->pfn##a_Fn) \ - pBackend->Callbacks.pfn##a_Fn = pCallbacks->pfn##a_Fn; + g_ShClCallbackOverrides.pfn##a_Fn = pCallbacks->pfn##a_Fn; SET_FN_IF_NOT_NULL(ReportFormats); SET_FN_IF_NOT_NULL(OnClipboardRead); @@ -319,20 +324,23 @@ void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks) } /** - * @note On the host, we assume that some other application already owns - * the clipboard and leave ownership to X11. + * Connects a Main service connection to the X11 clipboard backend. + * + * @returns VBox status code. + * @param pConn Main service connection to associate. + * @param ppCtx Where to return the allocated backend context. + * + * @note On the host, another application is assumed to own the clipboard; + * ownership remains with X11 until guest formats are announced. */ -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +static int shClBackendX11Connect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) { - int vrc; + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); - /* Check if maximum allowed connections count has reached. */ - if (ASMAtomicIncU32(&g_cShClConnections) > VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX) - { - ASMAtomicDecU32(&g_cShClConnections); - LogRel(("Shared Clipboard: maximum amount for client connections reached\n")); - return VERR_OUT_OF_RESOURCES; - } + *ppCtx = NULL; + + int vrc; PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); if (pCtx) @@ -340,112 +348,148 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) vrc = RTCritSectInit(&pCtx->CritSect); if (RT_SUCCESS(vrc)) { - vrc = ShClX11Init(&pCtx->X11, &pBackend->Callbacks, pCtx); - if (RT_SUCCESS(vrc)) + vrc = ShClEventSourceInit(&pCtx->EventSrc, 0 /* idEvtSrc */); + if (RT_FAILURE(vrc)) { - pClient->State.pCtx = pCtx; - pCtx->pClient = pClient; + RTCritSectDelete(&pCtx->CritSect); + RTMemFree(pCtx); + return vrc; + } -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /* - * Set callbacks. - * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. - * - * Used for starting / stopping the HTTP server. - */ - RT_ZERO(pClient->Transfers.Callbacks); - - pClient->Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */ - pClient->Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); - - pClient->Transfers.Callbacks.pfnOnCreated = shClSvcX11TransferOnCreatedCallback; - pClient->Transfers.Callbacks.pfnOnInitialize = shClSvcX11TransferOnInitCallback; - pClient->Transfers.Callbacks.pfnOnDestroy = shClSvcX11TransferOnDestroyCallback; - pClient->Transfers.Callbacks.pfnOnUnregistered = shClSvcX11TransferOnUnregisteredCallback; -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + SHCLCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pfnReportFormats = shClSvcX11ReportFormatsCallback; + Callbacks.pfnOnRequestDataFromSource = shClSvcX11RequestDataFromSourceCallback; + +#define SET_FN_IF_NOT_NULL(a_Fn) \ + if (g_ShClCallbackOverrides.pfn##a_Fn) \ + Callbacks.pfn##a_Fn = g_ShClCallbackOverrides.pfn##a_Fn; + + SET_FN_IF_NOT_NULL(ReportFormats); + SET_FN_IF_NOT_NULL(OnClipboardRead); + SET_FN_IF_NOT_NULL(OnClipboardWrite); + SET_FN_IF_NOT_NULL(OnRequestDataFromSource); + SET_FN_IF_NOT_NULL(OnSendDataToDest); + +#undef SET_FN_IF_NOT_NULL + + vrc = ShClX11Init(&pCtx->X11, &Callbacks, pCtx); + if (RT_SUCCESS(vrc)) + { + pCtx->pConn = pConn; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP vrc = shClSvcX11TransferPreparationStart(pCtx); #endif if (RT_SUCCESS(vrc)) + { vrc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */); + if (RT_SUCCESS(vrc)) + *ppCtx = pCtx; + } + if (RT_FAILURE(vrc)) { #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP shClSvcX11TransferPreparationStop(pCtx); + AssertFatal(pCtx->hX11TransferPreparationThread == NIL_RTTHREAD); #endif - ShClX11Term(&pCtx->X11); + AssertFatal(pCtx->X11.Thread == NIL_RTTHREAD); + int const vrcTerm = ShClX11Term(&pCtx->X11); + AssertFatalMsgRC(vrcTerm, ("Terminating X11 context after backend startup failure failed with %Rrc\n", + vrcTerm)); } } + else + { + int const vrcTerm = ShClX11Term(&pCtx->X11); + AssertFatalMsgRC(vrcTerm, ("Terminating partially initialized X11 context failed with %Rrc\n", + vrcTerm)); + } if (RT_FAILURE(vrc)) - RTCritSectDelete(&pCtx->CritSect); + { + ShClEventSourceTerm(&pCtx->EventSrc); + int const vrcDelete = RTCritSectDelete(&pCtx->CritSect); + AssertFatalMsgRC(vrcDelete, ("Deleting X11 backend critical section after startup failure" + " failed with %Rrc\n", vrcDelete)); + } } if (RT_FAILURE(vrc)) - { - pClient->State.pCtx = NULL; RTMemFree(pCtx); - } } else vrc = VERR_NO_MEMORY; - if (RT_FAILURE(vrc)) - { - /* Restore active connections count. */ - ASMAtomicDecU32(&g_cShClConnections); - } - LogFlowFuncLeaveRC(vrc); return vrc; } -int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Returns the X11 callbacks for a new transfer. + * + * @param pCtx Connected backend context. + * @param pCallbacks Where to return the callback table. + */ +static void shClBackendX11TransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) { - RT_NOREF(pBackend); + AssertPtrReturnVoid(pCallbacks); + RT_ZERO(*pCallbacks); + AssertPtrReturnVoid(pCtx); + AssertPtrReturnVoid(pCtx->pConn); + + pCallbacks->pvUser = pCtx; + pCallbacks->cbUser = sizeof(*pCtx); + pCallbacks->pfnOnCreated = shClSvcX11TransferOnCreatedCallback; + pCallbacks->pfnOnInitialize = shClSvcX11TransferOnInitCallback; + pCallbacks->pfnOnDestroy = shClSvcX11TransferOnDestroyCallback; + pCallbacks->pfnOnUnregistered = shClSvcX11TransferOnUnregisteredCallback; +} +#endif - LogFlowFuncEnter(); +/** + * Synchronizes X11 clipboard state with a connected guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + */ +static int shClBackendX11Sync(PSHCLCONTEXT pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); - uint32_t uMode = ShClSvcClientGetMode(pClient); - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) - { /* likely */ } - else - return VINF_SUCCESS; + LogFlowFuncEnter(); /* Tell the guest we have no data in case X11 is not available. If * there is data in the host clipboard it will automatically be sent to * the guest when the clipboard starts up. */ - int vrc = ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, VBOX_SHCL_FMT_NONE); + int const vrc = pCtx->pConn->reportFormatsToGuest(VBOX_SHCL_FMT_NONE); LogFlowFuncLeaveRC(vrc); return vrc; } /** - * Shuts down the shared clipboard service and "disconnect" the guest. - * Note! Host glue code + * Disconnects and destroys an X11 clipboard backend context. + * + * @returns VBox status code. + * @param pCtx Backend context to disconnect. */ -int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +static int shClBackendX11Disconnect(PSHCLCONTEXT pCtx) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFuncEnter(); - PSHCLCONTEXT pCtx = pClient->State.pCtx; - AssertPtr(pCtx); - /* Stop transfer preparation before releasing either the client or X11 * context it uses. This also makes later X11 data requests fail. */ int vrc; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP vrc = shClSvcX11TransferPreparationStop(pCtx); - if (pCtx->hX11TransferPreparationThread != NIL_RTTHREAD) - { - LogRel(("Shared Clipboard: Host X11 transfer preparation worker did not terminate: %Rrc\n", vrc)); - return vrc; - } + AssertFatal(pCtx->hX11TransferPreparationThread == NIL_RTTHREAD); #else pCtx->fShuttingDown = true; vrc = VINF_SUCCESS; @@ -454,23 +498,30 @@ int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) int vrc2 = ShClX11ThreadStop(&pCtx->X11); if (RT_SUCCESS(vrc)) vrc = vrc2; - /** @todo handle this slightly more reasonably, or be really sure - * it won't go wrong. */ - AssertRC(vrc2); + AssertFatal(pCtx->X11.Thread == NIL_RTTHREAD); - ShClX11Term(&pCtx->X11); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /* Transfer callback tables retain pCtx as their user argument. Destroy * all transfers before deleting that context; the service-side client * teardown which follows treats an already empty context as a no-op. */ - shClSvcTransferDestroyAll(pClient); + pCtx->pConn->transferDestroyAll(); #endif - RTCritSectDelete(&pCtx->CritSect); + vrc2 = ShClX11Term(&pCtx->X11); + AssertFatalMsgRC(vrc2, ("Terminating X11 clipboard context failed with %Rrc\n", vrc2)); + if (RT_SUCCESS(vrc)) + vrc = vrc2; - RTMemFree(pCtx); + vrc2 = ShClEventSourceTerm(&pCtx->EventSrc); + AssertFatalMsgRC(vrc2, ("Terminating X11 backend event source failed with %Rrc\n", vrc2)); + if (RT_SUCCESS(vrc)) + vrc = vrc2; - /* Decrease active connections count. */ - ASMAtomicDecU32(&g_cShClConnections); + vrc2 = RTCritSectDelete(&pCtx->CritSect); + AssertFatalMsgRC(vrc2, ("Deleting X11 backend critical section failed with %Rrc\n", vrc2)); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + RTMemFree(pCtx); LogFlowFuncLeaveRC(vrc); return vrc; @@ -478,10 +529,15 @@ int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) /** * Reports clipboard formats to the host clipboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. */ -int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +static int shClBackendX11ReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); #if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) if (fFormats & VBOX_SHCL_FMT_URI_LIST) @@ -493,9 +549,6 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR #endif #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - PSHCLCONTEXT pCtx = pClient->State.pCtx; - AssertPtrReturn(pCtx, VERR_INVALID_POINTER); - int vrc = RTCritSectEnter(&pCtx->CritSect); if (RT_SUCCESS(vrc)) { @@ -523,7 +576,7 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR vrc = vrc2; } #else - int vrc = ShClX11ReportFormatsToX11Async(&pClient->State.pCtx->X11, fFormats); + int vrc = ShClX11ReportFormatsToX11Async(&pCtx->X11, fFormats); #endif LogFlowFuncLeaveRC(vrc); @@ -531,14 +584,14 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR } /** - * The host reports clipboard formats to the guest clipboard. + * Reports formats discovered by X11 to the connected guest. + * + * @returns VBox status code. + * @param pConn Main service connection to report through. + * @param fFormats Native formats, VBOX_SHCL_FMT_XXX. */ -int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +static int shClBackendX11ReportLocalFormats(GuestShClConn *pConn, SHCLFORMATS fFormats) { - RT_NOREF(pBackend); - - int vrc; - #if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) if (fFormats & VBOX_SHCL_FMT_URI_LIST) { @@ -548,31 +601,7 @@ int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, } #endif - PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); - if (pMsg) - { - HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); - HGCMSvcSetU32(&pMsg->aParms[1], fFormats); - - ShClSvcClientLock(pClient); - - vrc = shClSvcClientMsgAddAndWakeupClient(pClient, pMsg); - - ShClSvcClientUnlock(pClient); - } - else - vrc = VERR_NO_MEMORY; - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ -#ifdef VBOX_COM_INPROC - return GuestShCl::GetInst()->ReportFormatsToGuest(pClient, fFormats, SHCLSOURCE_LOCAL); -#endif - return ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); + return pConn->reportLocalFormats(fFormats); } /** @@ -580,25 +609,25 @@ static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMA * * Schedules a request to the X11 event thread. * - * @note We always fail or complete asynchronously. + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required byte count. */ -int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, - void *pvData, uint32_t cbData, uint32_t *pcbActual) +static int shClBackendX11ReadData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) { - RT_NOREF(pBackend); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); - AssertPtrReturn(pvData, VERR_INVALID_POINTER); - AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); - RT_NOREF(pCmdCtx); - - LogFlowFunc(("pClient=%p, uFormat=%#x, pv=%p, cb=%RU32, pcbActual=%p\n", - pClient, uFormat, pvData, cbData, pcbActual)); + LogFlowFunc(("pConn=%p, uFormat=%#x, pv=%p, cb=%RU32, pcbActual=%p\n", + pCtx->pConn, uFormat, pvData, cbData, pcbActual)); uint32_t cbRead; - int vrc = ShClX11ReadDataFromX11(&pClient->State.pCtx->X11, &pClient->EventSrc, + int vrc = ShClX11ReadDataFromX11(&pCtx->X11, &pCtx->EventSrc, SHCL_TIMEOUT_DEFAULT_MS, uFormat, pvData, cbData, &cbRead); if (RT_SUCCESS(vrc)) { @@ -613,10 +642,18 @@ int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTC return vrc; } -int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, - SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +/** + * Writes guest clipboard data through the X11 backend. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param uFormat Clipboard format to write. + * @param pvData Data buffer. + * @param cbData Data size in bytes. + */ +static int shClBackendX11WriteData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) { - RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + RT_NOREF(pCtx, uFormat, pvData, cbData); LogFlowFuncEnter(); @@ -637,18 +674,9 @@ static DECLCALLBACK(int) shClSvcX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint LogFlowFunc(("pCtx=%p, fFormats=%#x\n", pCtx, fFormats)); - int vrc = VINF_SUCCESS; - PSHCLCLIENT pClient = pCtx->pClient; - AssertPtr(pClient); - - uint32_t uMode = ShClSvcClientGetMode(pClient); - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) - { /* likely */ } - else - return VINF_SUCCESS; + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); - vrc = shClBackendReportFormatsToGuestAndMain(pClient, fFormats); + int const vrc = shClBackendX11ReportLocalFormats(pCtx->pConn, fFormats); LogFlowFuncLeaveRC(vrc); return vrc; @@ -668,14 +696,13 @@ static int shClSvcX11TransferPrepare(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats, ui { AssertReturn(fFormats & VBOX_SHCL_FMT_URI_LIST, VERR_INVALID_PARAMETER); - PSHCLCLIENT const pClient = pCtx->pClient; - AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); /* Preserve the established protocol sequence by consuming the URI-list * data reply before creating and initializing the file transfer. */ void *pvData = NULL; uint32_t cbData = 0; - int vrc = ShClSvcReadDataFromGuest(pClient, VBOX_SHCL_FMT_URI_LIST, &pvData, &cbData); + int vrc = pCtx->pConn->readDataFromGuest(VBOX_SHCL_FMT_URI_LIST, &pvData, &cbData); RTMemFree(pvData); if ( RT_SUCCESS(vrc) && !shClSvcX11TransferOfferIsCurrent(pCtx, uOfferGeneration)) @@ -683,8 +710,12 @@ static int shClSvcX11TransferPrepare(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats, ui PSHCLTRANSFER pTransfer = NULL; if (RT_SUCCESS(vrc)) - vrc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, - NIL_SHCLTRANSFERID, &pTransfer); + { + SHCLTRANSFERCALLBACKS Callbacks; + shClBackendX11TransferGetCallbacks(pCtx, &Callbacks); + vrc = pCtx->pConn->transferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, &Callbacks, + NIL_SHCLTRANSFERID, &pTransfer); + } SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; SHCLTRANSFERGEN uGeneration = NIL_SHCLTRANSFERGEN; @@ -710,7 +741,7 @@ static int shClSvcX11TransferPrepare(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats, ui } if (RT_SUCCESS(vrc)) - vrc = ShClSvcTransferInit(pClient, pTransfer); + vrc = pCtx->pConn->transferInit(pTransfer); if (RT_SUCCESS(vrc)) { /* Wait on this transfer object, never on global HTTP-server state. */ @@ -785,7 +816,7 @@ static int shClSvcX11TransferPrepare(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats, ui if (pTransfer) { - /* ShClSvcTransferCreate returns a retained transfer. Drop that + /* pfnTransferCreate returns a retained transfer. Drop that * ownership before a consuming destroy can wait for users. */ ShClTransferRelease(pTransfer); pTransfer = NULL; @@ -793,7 +824,7 @@ static int shClSvcX11TransferPrepare(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats, ui if ( !fPublished && ShClTransferIdIsValid(idTransfer)) - ShClSvcTransferDestroyById(pClient, idTransfer); + pCtx->pConn->transferDestroyById(idTransfer); if (fPublished) LogRel2(("Shared Clipboard: Advertised cached host X11 URI list for transfer %RU16/%RU64, offer generation %RU64\n", @@ -890,51 +921,43 @@ static DECLCALLBACK(void) shClSvcX11TransferOnCreatedCallback(PSHCLTRANSFERCALLB PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); - PSHCLCLIENT const pClient = pCtx->pClient; - AssertPtr(pClient); + AssertPtrReturnVoid(pCtx->pConn); /* * Set transfer provider. - * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. + * Those will be registered when a new transfer gets initialized. */ - /* Set the interface to the local provider by default first. */ - RT_ZERO(pClient->Transfers.Provider); - ShClTransferProviderLocalQueryInterface(&pClient->Transfers.Provider); - - PSHCLTXPROVIDERIFACE pIface = &pClient->Transfers.Provider.Interface; + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); - pClient->Transfers.Provider.enmSource = pClient->State.enmSource; - pClient->Transfers.Provider.pvUser = pClient; + int vrc = VINF_SUCCESS; switch (ShClTransferGetDir(pTransfer)) { case SHCLTRANSFERDIR_FROM_REMOTE: /* Guest -> Host. */ { - pIface->pfnRootListRead = ShClSvcTransferIfaceGHRootListRead; - - pIface->pfnListOpen = ShClSvcTransferIfaceGHListOpen; - pIface->pfnListClose = ShClSvcTransferIfaceGHListClose; - pIface->pfnListHdrRead = ShClSvcTransferIfaceGHListHdrRead; - pIface->pfnListEntryRead = ShClSvcTransferIfaceGHListEntryRead; - - pIface->pfnObjOpen = ShClSvcTransferIfaceGHObjOpen; - pIface->pfnObjClose = ShClSvcTransferIfaceGHObjClose; - pIface->pfnObjRead = ShClSvcTransferIfaceGHObjRead; + vrc = pCtx->pConn->transferProviderInitGuest(&Provider); break; } case SHCLTRANSFERDIR_TO_REMOTE: /* Host -> Guest. */ { - pIface->pfnRootListRead = shClSvcX11TransferIfaceHGRootListRead; + ShClTransferProviderLocalQueryInterface(&Provider); + Provider.Interface.pfnRootListRead = shClSvcX11TransferIfaceHGRootListRead; + Provider.enmSource = SHCLSOURCE_LOCAL; + Provider.pvUser = pCtx; + Provider.cbUser = sizeof(*pCtx); break; } default: - AssertFailed(); + AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); } - int vrc = ShClTransferSetProvider(pTransfer, &pClient->Transfers.Provider); RT_NOREF(vrc); + if (RT_SUCCESS(vrc)) + vrc = ShClTransferSetProvider(pTransfer, &Provider); + RT_NOREF(vrc); LogFlowFuncLeaveRC(vrc); } @@ -1111,8 +1134,7 @@ static DECLCALLBACK(int) shClSvcX11RequestDataFromSourceCallback(PSHCLCONTEXT pC return VERR_NOT_SUPPORTED; #endif - PSHCLCLIENT const pClient = pCtx->pClient; - int vrc = ShClSvcReadDataFromGuest(pClient, uFmt, ppv, pcb); + int const vrc = pCtx->pConn->readDataFromGuest(uFmt, ppv, pcb); if (RT_FAILURE(vrc)) LogRel(("Shared Clipboard: Requesting X11 data in format %#x from guest failed with %Rrc\n", uFmt, vrc)); @@ -1122,15 +1144,22 @@ static DECLCALLBACK(int) shClSvcX11RequestDataFromSourceCallback(PSHCLCONTEXT pC } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# ifndef UNIT_TEST /** * Handles transfer status replies from the guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param pTransfer Transfer whose status changed. + * @param enmSource Endpoint which supplied the reply. + * @param enmStatus New transfer status. + * @param rcStatus Status-specific VBox status code. */ -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus) +static int shClBackendX11TransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) { - RT_NOREF(pBackend, pClient, enmSource, rcStatus); - - PSHCLCONTEXT pCtx = pClient->State.pCtx; RT_NOREF(pCtx); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + RT_NOREF(enmSource, rcStatus); if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) /* Guest -> Host */ { @@ -1158,7 +1187,6 @@ int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pCli return VINF_SUCCESS; } -# endif /* !UNIT_TEST */ /********************************************************************************************************************************* @@ -1170,16 +1198,15 @@ static DECLCALLBACK(int) shClSvcX11TransferIfaceHGRootListRead(PSHCLTXPROVIDERCT { LogFlowFuncEnter(); - PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; - AssertPtr(pClient); - - AssertPtr(pClient->State.pCtx); - PSHCLX11CTX pX11 = &pClient->State.pCtx->X11; + PSHCLCONTEXT pBackendCtx = (PSHCLCONTEXT)pCtx->pvUser; + AssertPtrReturn(pBackendCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pBackendCtx->pConn, VERR_INVALID_POINTER); + PSHCLX11CTX pX11 = &pBackendCtx->X11; /* X supplies the data asynchronously, so we need to wait for data to arrive first. */ void *pvData; uint32_t cbData; - int vrc = ShClX11ReadDataFromX11Ex(pX11, &pClient->EventSrc, SHCL_TIMEOUT_DEFAULT_MS, VBOX_SHCL_FMT_URI_LIST, + int vrc = ShClX11ReadDataFromX11Ex(pX11, &pBackendCtx->EventSrc, SHCL_TIMEOUT_DEFAULT_MS, VBOX_SHCL_FMT_URI_LIST, &pvData, &cbData); if (RT_SUCCESS(vrc)) { @@ -1200,3 +1227,33 @@ static DECLCALLBACK(int) shClSvcX11TransferIfaceHGRootListRead(PSHCLTXPROVIDERCT return vrc; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +/** Native X11 Shared Clipboard backend operations. */ +static SHCLBACKENDOPS const s_ShClBackendX11Ops = +{ + shClBackendX11Init, + shClBackendX11Destroy, + shClBackendX11SetCallbacks, + shClBackendX11Connect, + shClBackendX11Disconnect, + shClBackendX11ReportFormats, + shClBackendX11ReadData, + shClBackendX11WriteData, + shClBackendX11Sync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClBackendX11TransferGetCallbacks, + shClBackendX11TransferHandleStatusReply, +#endif +}; + + +/** + * Returns the native X11 Shared Clipboard backend operations. + * + * @returns Immutable X11 backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &s_ShClBackendX11Ops; +} diff --git a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp index f808659fadc4..ee5713db0c77 100644 --- a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp +++ b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendWin.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendWin.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Win32 host. */ @@ -34,7 +34,9 @@ #include #include -#include +#include "GuestShClBackend.h" +#include "../GuestShClBackendPrivate.h" +#include "GuestShClConn.h" #include #include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS @@ -55,26 +57,21 @@ #include #include /* Needed for shell objects. */ -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# include "VBoxSharedClipboardSvc-transfers.h" -#endif -#ifdef VBOX_COM_INPROC -# include "GuestShClPrivate.h" -#endif - /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** - * Global context information used by the host glue for the X11 clipboard backend. + * Global context information used by the host glue for the Windows clipboard backend. */ struct SHCLCONTEXT { /** Handle for window message handling thread. */ RTTHREAD hThread; - /** Structure for keeping and communicating with service client. */ - PSHCLCLIENT pClient; + /** Result of initializing the window message handling thread. */ + int vrcThreadStartup; + /** Main connection to the Shared Clipboard service. */ + GuestShClConn *pConn; /** Windows-specific context data. */ SHCLWINCTX Win; }; @@ -84,6 +81,7 @@ struct SHCLCONTEXT * Prototypes * *********************************************************************************************************************************/ static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx); +static int shClBackendWinThreadStop(PSHCLCONTEXT pCtx, bool fWaitDiagnostic); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS static DECLCALLBACK(int) shClSvcWinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pCtx); @@ -178,7 +176,7 @@ static int vboxClipboardSvcWinDataGet(SHCLFORMAT u32Format, const void *pvSrc, u */ static int vboxClipboardSvcWinReadDataFromGuestWorker(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppvData, uint32_t *pcbData) { - return ShClSvcReadDataFromGuest(pCtx->pClient, uFmt, ppvData, pcbData); + return pCtx->pConn->readDataFromGuest(uFmt, ppvData, pcbData); } static int vboxClipboardSvcWinReadDataFromGuest(PSHCLCONTEXT pCtx, UINT uWinFormat, void **ppvData, uint32_t *pcbData) @@ -234,22 +232,15 @@ static DECLCALLBACK(void) shClSvcWinTransferOnCreatedCallback(PSHCLTRANSFERCALLB PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); - PSHCLCLIENT const pClient = pCtx->pClient; - AssertPtr(pClient); + AssertPtrReturnVoid(pCtx->pConn); /* * Set transfer provider. - * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. + * Those will be registered when a new transfer gets initialized. */ - /* Set the interface to the local provider by default first. */ - RT_ZERO(pClient->Transfers.Provider); - ShClTransferProviderLocalQueryInterface(&pClient->Transfers.Provider); - - PSHCLTXPROVIDERIFACE pIface = &pClient->Transfers.Provider.Interface; - - pClient->Transfers.Provider.enmSource = pClient->State.enmSource; - pClient->Transfers.Provider.pvUser = pClient; + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); int vrc = VINF_SUCCESS; @@ -257,22 +248,17 @@ static DECLCALLBACK(void) shClSvcWinTransferOnCreatedCallback(PSHCLTRANSFERCALLB { case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ { - pIface->pfnRootListRead = ShClSvcTransferIfaceGHRootListRead; - - pIface->pfnListOpen = ShClSvcTransferIfaceGHListOpen; - pIface->pfnListClose = ShClSvcTransferIfaceGHListClose; - pIface->pfnListHdrRead = ShClSvcTransferIfaceGHListHdrRead; - pIface->pfnListEntryRead = ShClSvcTransferIfaceGHListEntryRead; - - pIface->pfnObjOpen = ShClSvcTransferIfaceGHObjOpen; - pIface->pfnObjClose = ShClSvcTransferIfaceGHObjClose; - pIface->pfnObjRead = ShClSvcTransferIfaceGHObjRead; + vrc = pCtx->pConn->transferProviderInitGuest(&Provider); break; } case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ { - pIface->pfnRootListRead = shClSvcWinTransferIfaceHGRootListRead; + ShClTransferProviderLocalQueryInterface(&Provider); + Provider.Interface.pfnRootListRead = shClSvcWinTransferIfaceHGRootListRead; + Provider.enmSource = SHCLSOURCE_LOCAL; + Provider.pvUser = pCtx; + Provider.cbUser = sizeof(*pCtx); break; } @@ -283,7 +269,7 @@ static DECLCALLBACK(void) shClSvcWinTransferOnCreatedCallback(PSHCLTRANSFERCALLB if (RT_SUCCESS(vrc)) { - vrc = ShClTransferSetProvider(pTransfer, &pClient->Transfers.Provider); + vrc = ShClTransferSetProvider(pTransfer, &Provider); if (RT_SUCCESS(vrc)) vrc = ShClWinTransferCreate(&pCtx->Win, pTransfer); } @@ -376,8 +362,8 @@ static DECLCALLBACK(void) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERC /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnUnregistered * - * Disables the IDataObject and drops its long-lived transfer reference before - * consuming teardown waits for temporary transfer users. + * Disables callbacks on the IDataObject and drops its long-lived transfer + * reference before consuming teardown waits for temporary transfer users. * * @thread Service main thread. */ @@ -425,20 +411,23 @@ static DECLCALLBACK(int) shClSvcWinDataObjectTransferBeginCallback(ShClWinDataOb PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; AssertPtr(pCtx); + SHCLTRANSFERCALLBACKS Callbacks; + shClBackendWinTransferGetCallbacks(pCtx, &Callbacks); + PSHCLTRANSFER pTransfer; - int vrc = ShClSvcTransferCreate(pCtx->pClient, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, - NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); + int vrc = pCtx->pConn->transferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, &Callbacks, + NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); if (RT_SUCCESS(vrc)) { SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); /* Initialize the transfer on the host side. */ - vrc = ShClSvcTransferInit(pCtx->pClient, pTransfer); + vrc = pCtx->pConn->transferInit(pTransfer); ShClTransferRelease(pTransfer); pTransfer = NULL; if (RT_FAILURE(vrc)) - ShClSvcTransferDestroyById(pCtx->pClient, idTransfer); + pCtx->pConn->transferDestroyById(idTransfer); } LogFlowFuncLeaveRC(vrc); @@ -551,7 +540,7 @@ static LRESULT CALLBACK vboxClipboardSvcWinWndProcMain(PSHCLCONTEXT pCtx, RTStrFree(pszFmts); } if ( uFmtVBox == VBOX_SHCL_FMT_NONE - || pCtx->pClient == NULL) + || !pCtx->pConn) { /* Unsupported clipboard format is requested. */ LogFunc(("WM_RENDERFORMAT unsupported format requested or client is not active\n")); @@ -561,7 +550,7 @@ static LRESULT CALLBACK vboxClipboardSvcWinWndProcMain(PSHCLCONTEXT pCtx, { void *pvData = NULL; uint32_t cbData = 0; - int vrc = ShClSvcReadDataFromGuest(pCtx->pClient, uFmtVBox, &pvData, &cbData); + int vrc = pCtx->pConn->readDataFromGuest(uFmtVBox, &pvData, &cbData); if (RT_SUCCESS(vrc)) { /* Wrap HTML clipboard content info CF_HTML format if needed. */ @@ -772,8 +761,9 @@ DECLCALLBACK(int) vboxClipboardSvcWinThread(RTTHREAD hThreadSelf, void *pvUser) LogRel(("Shared Clipboard: Initialized window thread OLE\n")); } #endif + pCtx->vrcThreadStartup = vrc; int vrc2 = RTThreadUserSignal(hThreadSelf); - AssertRC(vrc2); + AssertFatalMsgRC(vrc2, ("Signalling Windows clipboard thread startup failed with %Rrc\n", vrc2)); fThreadSignalled = true; @@ -809,21 +799,84 @@ DECLCALLBACK(int) vboxClipboardSvcWinThread(RTTHREAD hThreadSelf, void *pvUser) if (!fThreadSignalled) { + pCtx->vrcThreadStartup = vrc; int vrc2 = RTThreadUserSignal(hThreadSelf); - AssertRC(vrc2); + AssertFatalMsgRC(vrc2, ("Signalling terminal Windows clipboard thread startup failed with %Rrc\n", vrc2)); } LogFlowFuncLeaveRC(vrc); return vrc; } -static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +/** + * Stops and joins the Windows clipboard worker. + * + * @returns Worker result. + * @param pCtx Backend context whose worker to stop. + * @param fWaitDiagnostic Whether to log a finite diagnostic wait before + * waiting indefinitely. + */ +static int shClBackendWinThreadStop(PSHCLCONTEXT pCtx, bool fWaitDiagnostic) { -#ifdef VBOX_COM_INPROC - return GuestShCl::GetInst()->ReportFormatsToGuest(pClient, fFormats, SHCLSOURCE_LOCAL); -#else - return ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); -#endif + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->hThread != NIL_RTTHREAD, VERR_INVALID_STATE); + + RTNATIVETHREAD const idNativeThread = RTThreadGetNative(pCtx->hThread); + AssertFatal(idNativeThread != NIL_RTNATIVETHREAD); + + /* A context without a window is on a terminal startup/exit path and does not enter GetMessage(). */ + bool fStopPosted = pCtx->Win.hWnd == NULL; + DWORD dwWindowPostError = ERROR_SUCCESS; + if (pCtx->Win.hWnd) + { + fStopPosted = RT_BOOL(PostMessage(pCtx->Win.hWnd, WM_DESTROY, 0 /* wParam */, 0 /* lParam */)); + if (!fStopPosted) + dwWindowPostError = GetLastError(); + } + + DWORD dwThreadPostError = ERROR_SUCCESS; + if (!fStopPosted) + { + fStopPosted = RT_BOOL(PostThreadMessage((DWORD)idNativeThread, WM_QUIT, 0 /* wParam */, 0 /* lParam */)); + if (!fStopPosted) + dwThreadPostError = GetLastError(); + } + + int vrcThread = VERR_IPE_UNINITIALIZED_STATUS; + if (!fStopPosted) + { + /* Posting can legitimately lose a race with an already exiting worker. */ + int const vrcWait = RTThreadWait(pCtx->hThread, RT_MS_30SEC /* cMillies */, &vrcThread); + if (RT_SUCCESS(vrcWait)) + { + pCtx->hThread = NIL_RTTHREAD; + return vrcThread; + } + + AssertFatalMsgFailed(("Could not stop Windows clipboard thread %RTnthrd: PostMessage error %u," + " PostThreadMessage error %u, wait status %Rrc\n", + idNativeThread, dwWindowPostError, dwThreadPostError, vrcWait)); + } + + int vrcWait; + if (fWaitDiagnostic) + { + vrcWait = RTThreadWait(pCtx->hThread, RT_MS_30SEC /* cMillies */, &vrcThread); + if (RT_FAILURE(vrcWait)) + { + LogRel(("Shared Clipboard: Windows clipboard thread did not terminate promptly (%Rrc); waiting indefinitely\n", + vrcWait)); + vrcWait = RTThreadWait(pCtx->hThread, RT_INDEFINITE_WAIT, &vrcThread); + } + } + else + vrcWait = RTThreadWait(pCtx->hThread, RT_INDEFINITE_WAIT, &vrcThread); + + /* Main releases the client after disconnect, so allowing this worker to survive is never safe. */ + AssertFatalMsgRC(vrcWait, ("Reaping Windows clipboard thread %RTnthrd failed with %Rrc\n", + idNativeThread, vrcWait)); + pCtx->hThread = NIL_RTTHREAD; + return vrcThread; } /** @@ -841,12 +894,12 @@ static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx) int vrc; - if (pCtx->pClient) + if (pCtx->pConn) { SHCLFORMATS fFormats = 0; vrc = ShClWinGetFormats(&pCtx->Win, &fFormats); if (RT_SUCCESS(vrc)) - vrc = shClBackendReportFormatsToGuestAndMain(pCtx->pClient, fFormats); + vrc = pCtx->pConn->reportLocalFormats(fFormats); } else /* If we don't have any client data (yet), bail out. */ vrc = VINF_NO_CHANGE; @@ -859,9 +912,13 @@ static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx) /********************************************************************************************************************************* * Backend implementation * *********************************************************************************************************************************/ -int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +/** + * Initializes the process-wide Windows clipboard backend. + * + * @returns VBox status code. + */ +static int shClBackendWinInit(void) { - pBackend->pHelpers = pTable->pHelpers; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS HRESULT hr = OleInitialize(NULL); if (FAILED(hr)) @@ -876,19 +933,29 @@ int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) return VINF_SUCCESS; } -void ShClBackendDestroy(PSHCLBACKEND pBackend) +/** + * Destroys the process-wide Windows clipboard backend. + */ +static void shClBackendWinDestroy(void) { - RT_NOREF(pBackend); - #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */ OleUninitialize(); #endif } -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +/** + * Connects a Main service connection to the Windows clipboard backend. + * + * @returns VBox status code. + * @param pConn Main service connection to associate. + * @param ppCtx Where to return the allocated backend context. + */ +static int shClBackendWinConnect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) { - RT_NOREF(pBackend); + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); + *ppCtx = NULL; LogFlowFuncEnter(); @@ -897,6 +964,9 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); if (pCtx) { + pCtx->pConn = pConn; + pCtx->vrcThreadStartup = VERR_IPE_UNINITIALIZED_STATUS; + vrc = ShClWinCtxInit(&pCtx->Win); if (RT_SUCCESS(vrc)) { @@ -904,30 +974,47 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ShClWin"); if (RT_SUCCESS(vrc)) { - int vrc2 = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */); - AssertRC(vrc2); + vrc = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */); + if (RT_FAILURE(vrc)) + { + int const vrcStartup = vrc; + LogRel(("Shared Clipboard: Waiting for the Windows clipboard thread to initialize failed with %Rrc;" + " waiting for it before tearing down the context\n", vrc)); + + /* The worker always signals after either creating its window or reaching a terminal startup failure. */ + int vrc2; + do + { + vrc2 = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC); + if (vrc2 == VERR_TIMEOUT) + LogRel(("Shared Clipboard: Windows clipboard thread still has not completed startup;" + " waiting again\n")); + } while (vrc2 == VERR_TIMEOUT); + + AssertFatalMsgRC(vrc2, ("Waiting for Windows clipboard thread %RTnthrd to finish startup failed" + " with %Rrc\n", RTThreadGetNative(pCtx->hThread), vrc2)); + shClBackendWinThreadStop(pCtx, false /* fWaitDiagnostic */); + + /* Preserve the startup-wait error requested by the caller. */ + vrc = vrcStartup; + } + else if (RT_FAILURE(pCtx->vrcThreadStartup)) + { + /* The worker can keep its window loop alive after a terminal clipboard-chain initialization failure. */ + vrc = pCtx->vrcThreadStartup; + shClBackendWinThreadStop(pCtx, false /* fWaitDiagnostic */); + } } } - pClient->State.pCtx = pCtx; - pClient->State.pCtx->pClient = pClient; - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /* - * Set callbacks. - * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. - */ - RT_ZERO(pClient->Transfers.Callbacks); - - pClient->Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */ - pClient->Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); - - pClient->Transfers.Callbacks.pfnOnCreated = shClSvcWinTransferOnCreatedCallback; - pClient->Transfers.Callbacks.pfnOnInitialize = shClSvcWinTransferOnInitializeCallback; - pClient->Transfers.Callbacks.pfnOnInitialized = shClSvcWinTransferOnInitializedCallback; - pClient->Transfers.Callbacks.pfnOnUnregistered = shClSvcWinTransferOnUnregisteredCallback; - pClient->Transfers.Callbacks.pfnOnDestroy = shClSvcWinTransferOnDestroyCallback; -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + if (RT_SUCCESS(vrc)) + *ppCtx = pCtx; + else + { + AssertFatal(pCtx->hThread == NIL_RTTHREAD); + ShClWinCtxDestroy(&pCtx->Win); + RTMemFree(pCtx); + } } else vrc = VERR_NO_MEMORY; @@ -936,69 +1023,89 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) return vrc; } -int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Returns the Windows callbacks for a new transfer. + * + * @param pCtx Connected backend context. + * @param pCallbacks Where to return the callback table. + */ +static void shClBackendWinTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) { - RT_NOREF(pBackend); + AssertPtrReturnVoid(pCallbacks); + RT_ZERO(*pCallbacks); + AssertPtrReturnVoid(pCtx); + AssertPtrReturnVoid(pCtx->pConn); + + pCallbacks->pvUser = pCtx; + pCallbacks->cbUser = sizeof(*pCtx); + pCallbacks->pfnOnCreated = shClSvcWinTransferOnCreatedCallback; + pCallbacks->pfnOnInitialize = shClSvcWinTransferOnInitializeCallback; + pCallbacks->pfnOnInitialized = shClSvcWinTransferOnInitializedCallback; + pCallbacks->pfnOnUnregistered = shClSvcWinTransferOnUnregisteredCallback; + pCallbacks->pfnOnDestroy = shClSvcWinTransferOnDestroyCallback; +} +#endif +/** + * Synchronizes Windows clipboard state with a connected guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + */ +static int shClBackendWinSync(PSHCLCONTEXT pCtx) +{ /* Sync the host clipboard content with the client. */ - return vboxClipboardSvcWinSyncInternal(pClient->State.pCtx); + return vboxClipboardSvcWinSyncInternal(pCtx); } -int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +/** + * Disconnects and destroys a Windows clipboard backend context. + * + * @returns VBox status code. + * @param pCtx Backend context to disconnect. + */ +static int shClBackendWinDisconnect(PSHCLCONTEXT pCtx) { - RT_NOREF(pBackend); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFuncEnter(); int vrc = VINF_SUCCESS; - PSHCLCONTEXT pCtx = pClient->State.pCtx; - if (pCtx) + if (pCtx->hThread != NIL_RTTHREAD) { - if (pCtx->Win.hWnd) - PostMessage(pCtx->Win.hWnd, WM_DESTROY, 0 /* wParam */, 0 /* lParam */); - - if (pCtx->hThread != NIL_RTTHREAD) - { - LogFunc(("Waiting for thread to terminate ...\n")); - - /* Wait for the window thread to terminate. */ - vrc = RTThreadWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */, NULL); - if (RT_FAILURE(vrc)) - LogRel(("Shared Clipboard: Waiting for window thread termination failed with vrc=%Rrc\n", vrc)); - - pCtx->hThread = NIL_RTTHREAD; - } + LogFunc(("Waiting for thread to terminate ...\n")); + vrc = shClBackendWinThreadStop(pCtx, true /* fWaitDiagnostic */); + } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /* Transfer callback tables retain pCtx as their user argument. */ - shClSvcTransferDestroyAll(pClient); -#endif - ShClWinCtxDestroy(&pCtx->Win); - - if (RT_SUCCESS(vrc)) - { - RTMemFree(pCtx); - pCtx = NULL; + /* Disable an in-flight IDataObject callback before the consuming detach + * pass below. */ + ShClWinCtxDisableDataObjectCallbacks(&pCtx->Win); - pClient->State.pCtx = NULL; - } - } + /* Transfer callbacks retain pCtx as their user argument. */ + pCtx->pConn->transferDestroyAll(); +#endif + ShClWinCtxDestroy(&pCtx->Win); + RTMemFree(pCtx); LogFlowFuncLeaveRC(vrc); return vrc; } -int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +/** + * Reports guest clipboard formats to Windows. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ +static int shClBackendWinReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) { - RT_NOREF(pBackend); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - PSHCLCONTEXT pCtx = pClient->State.pCtx; AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFunc(("fFormats=0x%x, hWnd=%p\n", fFormats, pCtx->Win.hWnd)); @@ -1011,59 +1118,28 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR return VINF_SUCCESS; } -int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - RT_NOREF(pBackend); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - int vrc; - - uint32_t uMode = ShClSvcClientGetMode(pClient); - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) - { /* likely */ } - else - return VINF_SUCCESS; - - fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); - - PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); - if (pMsg) - { - HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); - HGCMSvcSetU32(&pMsg->aParms[1], fFormats); - - ShClSvcClientLock(pClient); - - vrc = shClSvcClientMsgAddAndWakeupClient(pClient, pMsg); - - ShClSvcClientUnlock(pClient); - } - else - vrc = VERR_NO_MEMORY; - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, - SHCLFORMAT uFmt, void *pvData, uint32_t cbData, uint32_t *pcbActual) +/** + * Reads data from the Windows clipboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param uFmt Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required byte count. + */ +static int shClBackendWinReadData(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void *pvData, uint32_t cbData, uint32_t *pcbActual) { - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); AssertPtrReturn(pvData, VERR_INVALID_POINTER); AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); - RT_NOREF(pBackend, pCmdCtx); - - AssertPtrReturn(pClient->State.pCtx, VERR_INVALID_POINTER); - LogFlowFunc(("uFmt=%#x\n", uFmt)); HANDLE hClip = NULL; - const PSHCLWINCTX pWinCtx = &pClient->State.pCtx->Win; + const PSHCLWINCTX pWinCtx = &pCtx->Win; /* * The guest wants to read data in the given format. @@ -1199,10 +1275,18 @@ int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTC return vrc; } -int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, - SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +/** + * Writes guest clipboard data through the Windows backend. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param uFormat Clipboard format to write. + * @param pvData Data buffer. + * @param cbData Data size in bytes. + */ +static int shClBackendWinWriteData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) { - RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + RT_NOREF(pCtx, uFormat, pvData, cbData); LogFlowFuncEnter(); @@ -1213,17 +1297,23 @@ int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENT } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# ifndef UNIT_TEST /** * Handles transfer status replies from the guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param pTransfer Transfer whose status changed. + * @param enmSource Endpoint which supplied the reply. + * @param enmStatus New transfer status. + * @param rcStatus Status-specific VBox status code. */ -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus) +static int shClBackendWinTransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) { - RT_NOREF(pBackend, pClient, pTransfer, enmSource, enmStatus, rcStatus); + RT_NOREF(pCtx, pTransfer, enmSource, enmStatus, rcStatus); return VINF_SUCCESS; } -# endif /* !UNIT_TEST */ /********************************************************************************************************************************* @@ -1235,11 +1325,9 @@ static DECLCALLBACK(int) shClSvcWinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCT { LogFlowFuncEnter(); - PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; - AssertPtr(pClient); - - AssertPtr(pClient->State.pCtx); - PSHCLWINCTX pWin = &pClient->State.pCtx->Win; + PSHCLCONTEXT pBackendCtx = (PSHCLCONTEXT)pCtx->pvUser; + AssertPtrReturn(pBackendCtx, VERR_INVALID_POINTER); + PSHCLWINCTX pWin = &pBackendCtx->Win; int vrc = ShClWinTransferGetRootsFromClipboard(pWin, pCtx->pTransfer); @@ -1247,3 +1335,33 @@ static DECLCALLBACK(int) shClSvcWinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCT return vrc; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +/** Native Windows Shared Clipboard backend operations. */ +static SHCLBACKENDOPS const s_ShClBackendWinOps = +{ + shClBackendWinInit, + shClBackendWinDestroy, + NULL, + shClBackendWinConnect, + shClBackendWinDisconnect, + shClBackendWinReportFormats, + shClBackendWinReadData, + shClBackendWinWriteData, + shClBackendWinSync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClBackendWinTransferGetCallbacks, + shClBackendWinTransferHandleStatusReply, +#endif +}; + + +/** + * Returns the native Windows Shared Clipboard backend operations. + * + * @returns Immutable Windows backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &s_ShClBackendWinOps; +} diff --git a/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py b/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py index 35b75737c18b..6741ce30ece3 100755 --- a/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py +++ b/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# $Id: tdUnitTest1.py 113466 2026-03-19 12:10:45Z brent.paulson@oracle.com $ +# $Id: tdUnitTest1.py 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ """ VirtualBox Validation Kit - Unit Tests. @@ -37,7 +37,7 @@ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 """ -__version__ = "$Revision: 113466 $" +__version__ = "$Revision: 115050 $" # Standard Python imports. @@ -160,7 +160,6 @@ class tdUnitTest1(vbox.TestDriver): 'testcase/tstClipboardX11Smoke': '', # (Old naming, deprecated) Needs X, not available on all test boxes. 'testcase/tstClipboardGH-X11Smoke': '', # (New name) Ditto. 'testcase/tstClipboardHttpServerX11': '', # Ditto. - 'testcase/tstClipboardMockHGCM': '', # Ditto. 'tstClipboardQt': '', # Is interactive and needs Qt, needed for Qt clipboard bugfixing. 'testcase/tstClipboardQt': '', # In case it moves here. 'tstDragAndDropQt': '', # Is interactive and needs Qt, needed for Qt drag'n drop bugfixing. @@ -302,7 +301,6 @@ class tdUnitTest1(vbox.TestDriver): kdTestCasesWhiteList = { 'testcase/tstFile': '', 'testcase/tstFileLock': '', - 'testcase/tstClipboardMockHGCM': '', # Requires X on Linux OSes. Execute on remote targets only (guests). 'testcase/tstRTFsQueries': '', 'testcase/tstRTLocalIpc': '', 'testcase/tstRTPathQueryInfo': '', From 47688861aff6959266889fc28ddaa604f85596b1 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Mon, 17 Aug 2026 15:26:27 +0000 Subject: [PATCH 144/176] WDDM: removed obsolete define. svn:sync-xref-src-repo-rev: r174892 --- .../Video/mp/wddm/gallium/VBoxMPGaWddm.cpp | 201 ++++++++---------- 1 file changed, 92 insertions(+), 109 deletions(-) diff --git a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPGaWddm.cpp b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPGaWddm.cpp index b67386f40b27..1a9a92436e6f 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPGaWddm.cpp +++ b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPGaWddm.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxMPGaWddm.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxMPGaWddm.cpp 115051 2026-08-17 15:26:27Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox Windows Guest Mesa3D - Gallium driver interface for WDDM kernel mode driver. */ @@ -521,10 +521,6 @@ static void gaReportFence(PVBOXMP_DEVEXT pDevExt) } } -/* If there are no commands but we need to trigger fence submission anyway, then submit a buffer of this size. */ -#define GA_DMA_MIN_SUBMIT_SIZE 4 -AssertCompile(GA_DMA_MIN_SUBMIT_SIZE < sizeof(SVGA3dCmdHeader)); - DECLINLINE(PVBOXWDDM_ALLOCATION) getAllocationFromAllocationListEntry(DXGK_ALLOCATIONLIST *pAllocationListEntry) { PVBOXWDDM_OPENALLOCATION pOA = (PVBOXWDDM_OPENALLOCATION)pAllocationListEntry->hDeviceSpecificAllocation; @@ -1135,15 +1131,8 @@ static NTSTATUS gaRenderGA3D(PVBOXWDDM_CONTEXT pContext, DXGKARG_RENDER *pRender void *pvTarget = pRender->pDmaBuffer; uint32_t const cbTarget = pRender->DmaSize; GAHWRENDERDATA *pHwRenderData = NULL; - if (cbTarget > GA_DMA_MIN_SUBMIT_SIZE) - { - Status = SvgaRenderCommands(pGaDevExt->hw.pSvga, pContext->pSvgaContext, pvTarget, cbTarget, pvSource, cbSource, - &u32TargetLength, &u32ProcessedLength, &pHwRenderData); - } - else - { - Status = STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; - } + Status = SvgaRenderCommands(pGaDevExt->hw.pSvga, pContext->pSvgaContext, pvTarget, cbTarget, pvSource, cbSource, + &u32TargetLength, &u32ProcessedLength, &pHwRenderData); GAFENCEOBJECT *pFO = NULL; if (Status == STATUS_SUCCESS) @@ -1267,125 +1256,119 @@ static NTSTATUS gaBuildPagingBufferOld(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGI if (pBuildPagingBuffer->DmaBufferPrivateDataSize >= sizeof(GARENDERDATA)) { // void *pvTarget = pBuildPagingBuffer->pDmaBuffer; - const uint32_t cbTarget = pBuildPagingBuffer->DmaSize; - if (cbTarget > GA_DMA_MIN_SUBMIT_SIZE) + // const uint32_t cbTarget = pBuildPagingBuffer->DmaSize; + + switch (pBuildPagingBuffer->Operation) { - switch (pBuildPagingBuffer->Operation) + case DXGK_OPERATION_TRANSFER: { - case DXGK_OPERATION_TRANSFER: + GALOG(("DXGK_OPERATION_TRANSFER: %p: @0x%x, cb 0x%x; src: %d:%p; dst: %d:%p; flags 0x%x, off 0x%x\n", + pBuildPagingBuffer->Transfer.hAllocation, + pBuildPagingBuffer->Transfer.TransferOffset, + pBuildPagingBuffer->Transfer.TransferSize, + pBuildPagingBuffer->Transfer.Source.SegmentId, + pBuildPagingBuffer->Transfer.Source.pMdl, + pBuildPagingBuffer->Transfer.Destination.SegmentId, + pBuildPagingBuffer->Transfer.Destination.pMdl, + pBuildPagingBuffer->Transfer.Flags.Value, + pBuildPagingBuffer->Transfer.MdlOffset)); + if (pBuildPagingBuffer->Transfer.Source.SegmentId == 0) { - GALOG(("DXGK_OPERATION_TRANSFER: %p: @0x%x, cb 0x%x; src: %d:%p; dst: %d:%p; flags 0x%x, off 0x%x\n", - pBuildPagingBuffer->Transfer.hAllocation, - pBuildPagingBuffer->Transfer.TransferOffset, - pBuildPagingBuffer->Transfer.TransferSize, - pBuildPagingBuffer->Transfer.Source.SegmentId, - pBuildPagingBuffer->Transfer.Source.pMdl, - pBuildPagingBuffer->Transfer.Destination.SegmentId, - pBuildPagingBuffer->Transfer.Destination.pMdl, - pBuildPagingBuffer->Transfer.Flags.Value, - pBuildPagingBuffer->Transfer.MdlOffset)); - if (pBuildPagingBuffer->Transfer.Source.SegmentId == 0) + /* SysMem source. */ + if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 1) { - /* SysMem source. */ - if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 1) - { - /* SysMem -> VRAM. */ - Status = gaSoftwarePagingTransfer(pDevExt, pBuildPagingBuffer); - if (Status == STATUS_SUCCESS) - { - /* Generate a NOP. */ - Status = STATUS_NOT_SUPPORTED; - } - } - else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) + /* SysMem -> VRAM. */ + Status = gaSoftwarePagingTransfer(pDevExt, pBuildPagingBuffer); + if (Status == STATUS_SUCCESS) { - /* SysMem -> SysMem, should not happen, bugcheck. */ - AssertFailed(); - Status = STATUS_INVALID_PARAMETER; - } - else - { - /* SysMem -> GPU surface. Our driver probably does not need it. - * SVGA_3D_CMD_SURFACE_DMA(GMR -> Surface)? - */ - AssertFailed(); + /* Generate a NOP. */ Status = STATUS_NOT_SUPPORTED; } } - else if (pBuildPagingBuffer->Transfer.Source.SegmentId == 1) + else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) { - /* VRAM source. */ - if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) - { - /* VRAM -> SysMem. */ - Status = gaSoftwarePagingTransfer(pDevExt, pBuildPagingBuffer); - if (Status == STATUS_SUCCESS) - { - /* Generate a NOP. */ - Status = STATUS_NOT_SUPPORTED; - } - } - else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 1) - { - /* VRAM -> VRAM, should not happen, bugcheck. */ - AssertFailed(); - Status = STATUS_INVALID_PARAMETER; - } - else + /* SysMem -> SysMem, should not happen, bugcheck. */ + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + } + else + { + /* SysMem -> GPU surface. Our driver probably does not need it. + * SVGA_3D_CMD_SURFACE_DMA(GMR -> Surface)? + */ + AssertFailed(); + Status = STATUS_NOT_SUPPORTED; + } + } + else if (pBuildPagingBuffer->Transfer.Source.SegmentId == 1) + { + /* VRAM source. */ + if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) + { + /* VRAM -> SysMem. */ + Status = gaSoftwarePagingTransfer(pDevExt, pBuildPagingBuffer); + if (Status == STATUS_SUCCESS) { - /* VRAM -> GPU surface. Our driver probably does not need it. - * SVGA_3D_CMD_SURFACE_DMA(SVGA_GMR_FRAMEBUFFER -> Surface)? - */ - AssertFailed(); + /* Generate a NOP. */ Status = STATUS_NOT_SUPPORTED; } } + else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 1) + { + /* VRAM -> VRAM, should not happen, bugcheck. */ + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + } else { - /* GPU surface. Our driver probably does not need it. - * SVGA_3D_CMD_SURFACE_DMA(Surface -> GMR)? + /* VRAM -> GPU surface. Our driver probably does not need it. + * SVGA_3D_CMD_SURFACE_DMA(SVGA_GMR_FRAMEBUFFER -> Surface)? */ AssertFailed(); Status = STATUS_NOT_SUPPORTED; } - - /** @todo Ignore for now. */ - if (Status == STATUS_NOT_SUPPORTED) - { - /* NOP */ - Status = STATUS_SUCCESS; - } - } break; - - case DXGK_OPERATION_FILL: + } + else { - GALOG(("DXGK_OPERATION_FILL: %p: cb 0x%x, pattern 0x%x, %d:0x%08X\n", - pBuildPagingBuffer->Fill.hAllocation, - pBuildPagingBuffer->Fill.FillSize, - pBuildPagingBuffer->Fill.FillPattern, - pBuildPagingBuffer->Fill.Destination.SegmentId, - pBuildPagingBuffer->Fill.Destination.SegmentAddress.LowPart)); - /* NOP */ - } break; + /* GPU surface. Our driver probably does not need it. + * SVGA_3D_CMD_SURFACE_DMA(Surface -> GMR)? + */ + AssertFailed(); + Status = STATUS_NOT_SUPPORTED; + } - case DXGK_OPERATION_DISCARD_CONTENT: + /** @todo Ignore for now. */ + if (Status == STATUS_NOT_SUPPORTED) { - GALOG(("DXGK_OPERATION_DISCARD_CONTENT: %p: flags 0x%x, %d:0x%08X\n", - pBuildPagingBuffer->DiscardContent.hAllocation, - pBuildPagingBuffer->DiscardContent.Flags, - pBuildPagingBuffer->DiscardContent.SegmentId, - pBuildPagingBuffer->DiscardContent.SegmentAddress.LowPart)); /* NOP */ - } break; + Status = STATUS_SUCCESS; + } + } break; - default: - AssertFailed(); - break; - } - } - else - { - Status = STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + case DXGK_OPERATION_FILL: + { + GALOG(("DXGK_OPERATION_FILL: %p: cb 0x%x, pattern 0x%x, %d:0x%08X\n", + pBuildPagingBuffer->Fill.hAllocation, + pBuildPagingBuffer->Fill.FillSize, + pBuildPagingBuffer->Fill.FillPattern, + pBuildPagingBuffer->Fill.Destination.SegmentId, + pBuildPagingBuffer->Fill.Destination.SegmentAddress.LowPart)); + /* NOP */ + } break; + + case DXGK_OPERATION_DISCARD_CONTENT: + { + GALOG(("DXGK_OPERATION_DISCARD_CONTENT: %p: flags 0x%x, %d:0x%08X\n", + pBuildPagingBuffer->DiscardContent.hAllocation, + pBuildPagingBuffer->DiscardContent.Flags, + pBuildPagingBuffer->DiscardContent.SegmentId, + pBuildPagingBuffer->DiscardContent.SegmentAddress.LowPart)); + /* NOP */ + } break; + + default: + AssertFailed(); + break; } /* Fill RenderData description in any case, it will be ignored if the above code failed. */ From 1e33567d1216076279006466b37bd3995c30e0da Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 15:35:37 +0000 Subject: [PATCH 145/176] Shared Clipboard: Introduced an opaque Main connection. This now clearly separates Main from the host service through a connection object [build fixes]. bugref:4697 svn:sync-xref-src-repo-rev: r174893 --- .../ClipboardDataObjectImpl-win.cpp | 26 ++++++++++--------- .../SharedClipboard/clipboard-x11.cpp | 4 +++ .../VBoxSharedClipboardSvc.cpp | 3 ++- src/VBox/Main/src-client/GuestShClBackend.cpp | 10 +++---- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp index a5035cd60ffd..00bd9ba70772 100644 --- a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardDataObjectImpl-win.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardDataObjectImpl-win.cpp 115052 2026-08-17 15:35:37Z andreas.loeffler@oracle.com $ */ /** @file * ClipboardDataObjectImpl-win.cpp - Shared Clipboard IDataObject implementation. */ @@ -1555,18 +1555,20 @@ void ShClWinDataObject::registerStreamLocked(ShClWinStreamImpl *pStream) */ void ShClWinDataObject::invalidateStreams(void) { - StreamList lstStreams; - - lock(); - lstStreams.swap(m_lstStreams); - unlock(); - - StreamList::const_iterator it = lstStreams.cbegin(); - while (it != lstStreams.cend()) + for (;;) { - (*it)->Invalidate(); - (*it)->Release(); - ++it; + lock(); + if (m_lstStreams.empty()) + { + unlock(); + break; + } + ShClWinStreamImpl *pStream = m_lstStreams.back(); + m_lstStreams.pop_back(); + unlock(); + + pStream->Invalidate(); + pStream->Release(); } } diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp index ea24d5736428..f273e1e6d608 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -510,7 +510,11 @@ static int clipThreadScheduleCall(PSHCLX11CTX pCtx, if (cbWritten < 0) { /* A full non-blocking pipe is already readable and will wake the worker. */ +# if EAGAIN == EWOULDBLOCK + if (errno != EAGAIN) +# else if (errno != EAGAIN && errno != EWOULDBLOCK) +# endif { int const rc = RTErrConvertFromErrno(errno); /* The Xt callback is already queued and cannot be cancelled without racing the worker. */ diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp index c5de7455e5fa..f671dfd0170a 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.cpp 115052 2026-08-17 15:35:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host service entry points. */ @@ -363,6 +363,7 @@ static DECLCALLBACK(int) shClSvcUnload(void *) static DECLCALLBACK(int) shClSvcDisconnect(void *, uint32_t u32ClientID, void *pvClient) { LogFunc(("u32ClientID=%RU32\n", u32ClientID)); + RT_NOREF(u32ClientID); PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); diff --git a/src/VBox/Main/src-client/GuestShClBackend.cpp b/src/VBox/Main/src-client/GuestShClBackend.cpp index c561a628fcad..5ea32bb0e7bc 100644 --- a/src/VBox/Main/src-client/GuestShClBackend.cpp +++ b/src/VBox/Main/src-client/GuestShClBackend.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClBackend.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClBackend.cpp 115052 2026-08-17 15:35:37Z andreas.loeffler@oracle.com $ */ /** @file * Main Shared Clipboard - Native backend dispatcher implementation. */ @@ -51,7 +51,7 @@ ShClBackend::~ShClBackend(void) int ShClBackend::init(void) { AssertPtrReturn(m_pOps, VERR_INVALID_STATE); - AssertPtrReturn(m_pOps->pfnInit, VERR_INVALID_STATE); + AssertReturn(m_pOps->pfnInit != NULL, VERR_INVALID_STATE); return m_pOps->pfnInit(); } @@ -59,7 +59,7 @@ int ShClBackend::init(void) void ShClBackend::destroy(void) { AssertPtrReturnVoid(m_pOps); - AssertPtrReturnVoid(m_pOps->pfnDestroy); + AssertReturnVoid(m_pOps->pfnDestroy != NULL); m_pOps->pfnDestroy(); } @@ -76,7 +76,7 @@ int ShClBackend::connect(GuestShClConn *pConn) { AssertPtrReturn(pConn, VERR_INVALID_POINTER); AssertPtrReturn(m_pOps, VERR_INVALID_STATE); - AssertPtrReturn(m_pOps->pfnConnect, VERR_INVALID_STATE); + AssertReturn(m_pOps->pfnConnect != NULL, VERR_INVALID_STATE); AssertReturn(m_pCtx == NULL, VERR_RESOURCE_BUSY); PSHCLCONTEXT pCtx = NULL; @@ -95,7 +95,7 @@ int ShClBackend::connect(GuestShClConn *pConn) int ShClBackend::disconnect(void) { AssertPtrReturn(m_pOps, VERR_INVALID_STATE); - AssertPtrReturn(m_pOps->pfnDisconnect, VERR_INVALID_STATE); + AssertReturn(m_pOps->pfnDisconnect != NULL, VERR_INVALID_STATE); AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); PSHCLCONTEXT const pCtx = m_pCtx; From 33c9dff2ce7062ed3833eb2546db9cd1ec14f800 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 15:54:26 +0000 Subject: [PATCH 146/176] =?UTF-8?q?Shared=20Clipboard:=20Introduced=20an?= =?UTF-8?q?=20opaque=20Main=20connection.=20This=20now=20clearly=20separat?= =?UTF-8?q?es=20Main=20from=20the=20host=20service=20through=20a=20connect?= =?UTF-8?q?ion=20object=20[build=20fixes].=20=E2=80=8Bbugref:4697?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174894 --- src/VBox/Main/src-client/win/ClipboardBackendWin.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp index ee5713db0c77..8306d028cc6e 100644 --- a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp +++ b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendWin.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendWin.cpp 115053 2026-08-17 15:54:26Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Win32 host. */ @@ -85,6 +85,7 @@ static int shClBackendWinThreadStop(PSHCLCONTEXT pCtx, bool fWaitDiagnostic); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS static DECLCALLBACK(int) shClSvcWinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pCtx); +static void shClBackendWinTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks); #endif From 7189038d4457fd0b0a27cda072e57acbf286fcc6 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 16:27:08 +0000 Subject: [PATCH 147/176] Shared Clipboard: VRDE clipboard traffic routing now goes directly through Main instead to the not required host service detour anymore. bugref:4697 svn:sync-xref-src-repo-rev: r174895 --- include/VBox/HostServices/VBoxClipboardExt.h | 6 +- .../VBoxSharedClipboardSvc-client.cpp | 23 +- .../VBoxSharedClipboardSvc-ext.cpp | 171 +-------- .../VBoxSharedClipboardSvc-host.cpp | 6 +- .../VBoxSharedClipboardSvc-internal.h | 19 +- .../VBoxSharedClipboardSvc.cpp | 34 +- src/VBox/Main/include/ClipboardImpl.h | 3 +- src/VBox/Main/include/ConsoleVRDPServer.h | 22 +- src/VBox/Main/include/GuestShClPrivate.h | 39 +-- src/VBox/Main/src-client/ClipboardImpl.cpp | 20 +- src/VBox/Main/src-client/ConsoleImpl.cpp | 44 ++- .../src-client/ConsoleImplConfigCommon.cpp | 7 +- .../Main/src-client/ConsoleVRDPServer.cpp | 279 +++++++-------- src/VBox/Main/src-client/GuestShClPrivate.cpp | 178 +++++----- src/VBox/Main/src-client/GuestShClSvcExt.cpp | 330 +++++++----------- 15 files changed, 456 insertions(+), 725 deletions(-) diff --git a/include/VBox/HostServices/VBoxClipboardExt.h b/include/VBox/HostServices/VBoxClipboardExt.h index 9bc8125e5594..7947f42f9680 100644 --- a/include/VBox/HostServices/VBoxClipboardExt.h +++ b/include/VBox/HostServices/VBoxClipboardExt.h @@ -162,11 +162,11 @@ DECLINLINE(bool) ShClTransportIsEqual(PCSHCLTRANSPORT pLeft, PCSHCLTRANSPORT pRi && pLeft->pOps == pRight->pOps; } -/** Sets a read / write callback. */ +/** Reserved. Formerly installed a reverse callback into the HGCM service. */ #define VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK (0) /** The guest reports clipboard formats to the extension. */ #define VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST (1) -/** Reports remote clipboard formats to the guest. */ +/** Reserved. Formerly bounced remote format reports back through the service. */ #define VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST (2) /** The clipboard service requests clipboard data from the extension. */ #define VBOX_CLIPBOARD_EXT_FN_DATA_READ (3) @@ -184,7 +184,7 @@ DECLINLINE(bool) ShClTransportIsEqual(PCSHCLTRANSPORT pLeft, PCSHCLTRANSPORT pRi #define VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT (9) /** The clipboard service syncs with the backend. */ #define VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC (10) -/** Requests guest clipboard data for VRDE. */ +/** Reserved. Formerly bounced VRDE guest-data reads back through the service. */ #define VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE (11) /** The clipboard service initiates the transfer of a file from the guest. */ #define VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER (12) diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp index 95260db5af9c..c8d9e5ad996b 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-client.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-client.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Client/session and message queue handling. */ @@ -1448,29 +1448,12 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA shClSvcLock(); - g_ShClSvc.ExtState.fReadingData = true; - - /* If there is a service extension active, try reading data from it first. */ + /* Read data from Main, which selects the remote or local host provider. */ int rc = shClSvcExtReadData(pClient, uFormat, pvData, cbData, &cbActual); - LogRel2(("Shared Clipboard: Read extension clipboard data (fDelayedAnnouncement=%RTbool, fDelayedFormats=%#x, " - "max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", g_ShClSvc.ExtState.fDelayedAnnouncement, - g_ShClSvc.ExtState.fDelayedFormats, + LogRel2(("Shared Clipboard: Read extension clipboard data (max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", cbData, cbActual, rc)); - /* Did the extension send the clipboard formats yet? - * Otherwise, do this now. */ - if (g_ShClSvc.ExtState.fDelayedAnnouncement) - { - int rc2 = shClSvcExtReportFormatsToGuest(pClient, g_ShClSvc.ExtState.fDelayedFormats, SHCLSOURCE_REMOTE); - AssertRC(rc2); - - g_ShClSvc.ExtState.fDelayedAnnouncement = false; - g_ShClSvc.ExtState.fDelayedFormats = 0; - } - - g_ShClSvc.ExtState.fReadingData = false; - if (RT_SUCCESS(rc)) { /* Return the actual size required to fullfil the request. */ diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp index 40ee21df8528..0ec76130c4e5 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-ext.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-ext.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Service extension bridge handling. */ @@ -47,8 +47,6 @@ * Internal Functions * *********************************************************************************************************************************/ static int shClSvcExtCall(uint32_t u32Function, void *pvParms, uint32_t cbParms); -static DECLCALLBACK(int) shClSvcExtCallback(uint32_t u32Function, uint32_t u32Format, - void *pvData, uint32_t cbData); /** @@ -248,41 +246,6 @@ int shClSvcExtNotifyTransferStatus(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ -/** - * Reports remote clipboard formats to the guest through Main. - * - * @returns VBox status code returned by Main. - * @param pClient Service client to report to. - * @param fFormats Remote formats, VBOX_SHCL_FMT_XXX. - * @param enmSource Source of the format announcement. - * - * @thread Backend thread. - */ -int shClSvcExtReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource) -{ - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - uint32_t const uMode = ShClSvcGetMode(); - if ( uMode != VBOX_SHCL_MODE_BIDIRECTIONAL - && uMode != VBOX_SHCL_MODE_HOST_TO_GUEST) - return VINF_SUCCESS; - - fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); - - SHCLEXTPARMS parms; - RT_ZERO(parms); - shClSvcExtSetClient(&parms, pClient); - parms.u.ReportFormats.uFormats = fFormats; - parms.u.ReportFormats.pClient = pClient; - parms.u.ReportFormats.enmSource = enmSource; - - int const rc = shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST, &parms, sizeof(parms)); - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Reporting remote formats %#x to guest failed with %Rrc\n", fFormats, rc)); - return rc; -} - - /** * Reports guest clipboard formats to Main. * @@ -360,105 +323,7 @@ int shClSvcExtWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORM /** - * Requests guest clipboard data for the chained remote-desktop extension. - * - * @returns VBox status code returned by Main. - * @param pClient Connected service client. - * @param uFormat Clipboard format to request. - * @param pvData Destination buffer. - * @param cbData Destination buffer size in bytes. - */ -static int shClSvcExtReadVrdeData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - shClSvcExtSetClient(&parms, pClient); - parms.u.ReadWriteData.uFormat = uFormat; - parms.u.ReadWriteData.pvData = pvData; - parms.u.ReadWriteData.cbData = cbData; - parms.u.ReadWriteData.pClient = pClient; - - return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE, &parms, sizeof(parms)); -} - - -/** - * Handles reverse calls from the chained remote-desktop extension. - * - * @returns VBox status code. - * @param u32Function VBOX_CLIPBOARD_EXT_FN_XXX function number. - * @param u32Format Clipboard format associated with the request. - * @param pvData Optional data buffer. - * @param cbData Data buffer size in bytes. - */ -static DECLCALLBACK(int) shClSvcExtCallback(uint32_t u32Function, uint32_t u32Format, - void *pvData, uint32_t cbData) -{ - PSHCLCLIENT pClient = NULL; - - shClSvcLock(); - if ( g_ShClSvc.pActiveClient - && g_ShClSvc.ExtState.uClientID == g_ShClSvc.pActiveClient->State.uClientID) - { - pClient = g_ShClSvc.pActiveClient; - if (g_ShClSvc.ExtState.cCallbacks++ == 0) - { - int const rcReset = RTSemEventMultiReset(g_ShClSvc.ExtState.hCallbacksDone); - AssertFatalMsgRC(rcReset, ("Resetting the Shared Clipboard callback drain event failed with %Rrc\n", - rcReset)); - } - } - shClSvcUnlock(); - - int rc = VERR_NOT_FOUND; - if (pClient) - { - switch (u32Function) - { - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - shClSvcLock(); - if (!g_ShClSvc.ExtState.fReadingData) - { - shClSvcUnlock(); - rc = shClSvcExtReportFormatsToGuest(pClient, u32Format, SHCLSOURCE_REMOTE); - } - else - { - g_ShClSvc.ExtState.fDelayedAnnouncement = true; - g_ShClSvc.ExtState.fDelayedFormats = u32Format; - shClSvcUnlock(); - rc = VINF_SUCCESS; - } - break; - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - rc = shClSvcExtReadVrdeData(pClient, u32Format, pvData, cbData); - break; - - default: - rc = VERR_NOT_SUPPORTED; - break; - } - - shClSvcLock(); - Assert(g_ShClSvc.ExtState.cCallbacks > 0); - if (g_ShClSvc.ExtState.cCallbacks > 0 && --g_ShClSvc.ExtState.cCallbacks == 0) - { - int const rcSignal = RTSemEventMultiSignal(g_ShClSvc.ExtState.hCallbacksDone); - AssertFatalMsgRC(rcSignal, ("Signalling the Shared Clipboard callback drain event failed with %Rrc\n", - rcSignal)); - } - shClSvcUnlock(); - } - - return rc; -} - - -/** - * Disables new reverse callbacks, drains callbacks already in progress, - * destroys the native backend while the extension remains callable, and then - * clears the matching extension registration. + * Unregisters the Main service extension, then tears down its backend. * * @returns VBox status code. */ @@ -467,22 +332,11 @@ int shClSvcExtUnregisterAndDestroy(void) shClSvcLock(); PFNHGCMSVCEXT const pfnExtension = g_ShClSvc.ExtState.pfnExtension; void * const pvExtension = g_ShClSvc.ExtState.pvExtension; - g_ShClSvc.ExtState.uClientID = 0; shClSvcUnlock(); if (!pfnExtension) return VINF_SUCCESS; - /* Stop new reverse callbacks before waiting for calls which already - captured the active service client. */ - SHCLEXTPARMS parms; - RT_ZERO(parms); - int rc = pfnExtension(pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - AssertFatalMsgRC(rc, ("Unregistering the Shared Clipboard extension callback failed with %Rrc\n", rc)); - - int const rcWait = RTSemEventMultiWait(g_ShClSvc.ExtState.hCallbacksDone, RT_INDEFINITE_WAIT); - AssertFatalMsgRC(rcWait, ("Waiting for Shared Clipboard extension callbacks failed with %Rrc\n", rcWait)); - /* Console unregisters the extension before HGCM disconnects its client. */ shClSvcExtBackendDestroy(); @@ -496,7 +350,7 @@ int shClSvcExtUnregisterAndDestroy(void) shClSvcUnlock(); LogRel2(("Shared Clipboard: de-registered service extension\n")); - return rc; + return VINF_SUCCESS; } @@ -520,25 +374,8 @@ DECLCALLBACK(int) shClSvcRegisterExtension(void *pvService, PFNHGCMSVCEXT pfnExt g_ShClSvc.ExtState.pvExtension = pvExtension; shClSvcUnlock(); - SHCLEXTPARMS parms; - RT_ZERO(parms); - parms.u.SetCallback.pfnCallback = shClSvcExtCallback; - int const rc = pfnExtension(pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - if (RT_FAILURE(rc)) - { - shClSvcLock(); - if ( g_ShClSvc.ExtState.pfnExtension == pfnExtension - && g_ShClSvc.ExtState.pvExtension == pvExtension) - { - g_ShClSvc.ExtState.pvExtension = NULL; - g_ShClSvc.ExtState.pfnExtension = NULL; - } - shClSvcUnlock(); - return rc; - } - LogRel2(("Shared Clipboard: registered service extension\n")); - return rc; + return VINF_SUCCESS; } return shClSvcExtUnregisterAndDestroy(); diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp index 3dfafa810ede..fb8ff097a9ea 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-host.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-host.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host-controlled service handling. */ @@ -131,10 +131,6 @@ static void shClSvcHostReset(void) && g_ShClSvc.pActiveClient == pClient) shClSvcClientReset(pClient); - g_ShClSvc.ExtState.fReadingData = false; - g_ShClSvc.ExtState.fDelayedAnnouncement = false; - g_ShClSvc.ExtState.fDelayedFormats = 0; - shClSvcUnlock(); } diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h index cd7715602daf..4bb66d2e771b 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-internal.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-internal.h 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal service instance state. */ @@ -46,18 +46,6 @@ typedef struct SHCLEXTSTATE PFNHGCMSVCEXT pfnExtension; /** Opaque extension-provided data. */ void *pvExtension; - /** HGCM client ID currently assigned to the extension. */ - uint32_t uClientID; - /** Number of in-flight reverse callbacks using the active client. */ - uint32_t cCallbacks; - /** Signalled while no reverse callback is using the active client. */ - RTSEMEVENTMULTI hCallbacksDone; - /** Whether the host service is reading clipboard data currently. */ - bool fReadingData; - /** Whether the service extension announced formats while data was read. */ - bool fDelayedAnnouncement; - /** Formats announced while the host service was reading data. */ - uint32_t fDelayedFormats; } SHCLEXTSTATE; @@ -167,10 +155,9 @@ int shClSvcExtBackendConnect(PSHCLCLIENT pClient); int shClSvcExtBackendSync(PSHCLCLIENT pClient); void shClSvcExtBackendDisconnect(PSHCLCLIENT pClient); void shClSvcExtBackendDestroy(void); -/** Disables and drains reverse callbacks, destroys the backend while the - * extension remains callable, then clears the matching registration. */ +/** Destroys the backend while the extension remains callable, then clears the + * matching registration. */ int shClSvcExtUnregisterAndDestroy(void); -int shClSvcExtReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource); int shClSvcExtReportFormatsToHost(PSHCLCLIENT pClient, SHCLFORMATS fFormats); int shClSvcExtReadData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); int shClSvcExtWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp index f671dfd0170a..96e819ce08a5 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc.cpp 115052 2026-08-17 15:35:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host service entry points. */ @@ -321,26 +321,8 @@ static int shClSvcInit(void) if (RT_SUCCESS(rc)) { - rc = RTSemEventMultiCreate(&g_ShClSvc.ExtState.hCallbacksDone); - if (RT_SUCCESS(rc)) - { - rc = RTSemEventMultiSignal(g_ShClSvc.ExtState.hCallbacksDone); - if (RT_SUCCESS(rc)) - { - shClSvcHostModeSet(VBOX_SHCL_MODE_OFF); - g_ShClSvc.idNextSession = 1; - } - } - - if (RT_FAILURE(rc)) - { - if (g_ShClSvc.ExtState.hCallbacksDone != NIL_RTSEMEVENTMULTI) - { - RTSemEventMultiDestroy(g_ShClSvc.ExtState.hCallbacksDone); - g_ShClSvc.ExtState.hCallbacksDone = NIL_RTSEMEVENTMULTI; - } - RTCritSectDelete(&g_ShClSvc.CritSect); - } + shClSvcHostModeSet(VBOX_SHCL_MODE_OFF); + g_ShClSvc.idNextSession = 1; } return rc; @@ -353,8 +335,6 @@ static DECLCALLBACK(int) shClSvcUnload(void *) int const rc = shClSvcExtUnregisterAndDestroy(); AssertLogRelRC(rc); - RTSemEventMultiDestroy(g_ShClSvc.ExtState.hCallbacksDone); - g_ShClSvc.ExtState.hCallbacksDone = NIL_RTSEMEVENTMULTI; RTCritSectDelete(&g_ShClSvc.CritSect); return rc; @@ -372,14 +352,8 @@ static DECLCALLBACK(int) shClSvcDisconnect(void *, uint32_t u32ClientID, void *p Assert(g_ShClSvc.pActiveClient == pClient); if (g_ShClSvc.pActiveClient == pClient) g_ShClSvc.pActiveClient = NULL; - if (g_ShClSvc.ExtState.uClientID == u32ClientID) - g_ShClSvc.ExtState.uClientID = 0; shClSvcUnlock(); - int const rcWait = RTSemEventMultiWait(g_ShClSvc.ExtState.hCallbacksDone, RT_INDEFINITE_WAIT); - AssertFatalMsgRC(rcWait, ("Waiting for Shared Clipboard extension callbacks during disconnect failed with %Rrc\n", - rcWait)); - shClSvcExtBackendDisconnect(pClient); shClSvcClientDestroy(pClient); @@ -422,8 +396,6 @@ static DECLCALLBACK(int) shClSvcConnect(void *, uint32_t u32ClientID, void *pvCl rc = shClSvcExtBackendSync(pClient); if (RT_SUCCESS(rc)) { - if (g_ShClSvc.ExtState.uClientID == 0) - g_ShClSvc.ExtState.uClientID = u32ClientID; /* The sync could return VINF_NO_CHANGE if nothing has changed on the host, but older Guest Additions didn't use RT_SUCCESS to but == VINF_SUCCESS to check for success. So just return VINF_SUCCESS here to not break older Guest Additions. */ diff --git a/src/VBox/Main/include/ClipboardImpl.h b/src/VBox/Main/include/ClipboardImpl.h index aace68f7cc33..9bda72ad47d8 100644 --- a/src/VBox/Main/include/ClipboardImpl.h +++ b/src/VBox/Main/include/ClipboardImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardImpl.h 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Console clipboard API. */ @@ -97,6 +97,7 @@ class ATL_NO_VTABLE Clipboard : const std::vector > &aFormats); HRESULT i_getCurrentStateForEvent(ClipboardSource_T *aSource, std::vector > &aFormats); + HRESULT i_getCurrentSource(ClipboardSource_T *aSource); LONG64 i_nextEventRevision(); HRESULT i_reset(); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS diff --git a/src/VBox/Main/include/ConsoleVRDPServer.h b/src/VBox/Main/include/ConsoleVRDPServer.h index 88448bc7dfbf..fe596a9c105d 100644 --- a/src/VBox/Main/include/ConsoleVRDPServer.h +++ b/src/VBox/Main/include/ConsoleVRDPServer.h @@ -1,4 +1,4 @@ -/* $Id: ConsoleVRDPServer.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: ConsoleVRDPServer.h 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console VRDE Server Helper class and implementation of IVRDEServerInfo */ @@ -45,8 +45,9 @@ #include #include -#include +#include #include +#include #include "SchemaDefs.h" @@ -137,8 +138,10 @@ class ConsoleVRDPServer bool isRemoteUSBThreadRunning (void); void waitRemoteUSBThreadEvent (RTMSINTERVAL cMillies); - void ClipboardCreate (uint32_t u32ClientId); - void ClipboardDelete (uint32_t u32ClientId); + int ClipboardReportGuestFormats(SHCLFORMATS fFormats) const; + int ClipboardReadRemoteData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) const; + int ClipboardWriteGuestData(SHCLFORMAT uFormat, const void *pvData, uint32_t cbData) const; + void ClipboardSetGuestShClAvailable(bool fAvailable); /* * Forwarders to VRDP server library. @@ -227,14 +230,17 @@ class ConsoleVRDPServer RTCRITSECT mCritSect; + /** Whether direct clipboard callbacks may acquire the GuestShCl singleton. */ + bool mfClipboardGuestShClAvailable; + /** Number of direct clipboard callbacks currently using GuestShCl. */ + uint32_t mcClipboardGuestShClCalls; + /** Signalled while no direct clipboard callback is using GuestShCl. */ + RTSEMEVENTMULTI mhClipboardGuestShClCallsDone; + int lockConsoleVRDPServer (void); void unlockConsoleVRDPServer (void); - int mcClipboardRefs; - PFNSHCLEXTCALLBACK mpfnClipboardCallback; - static DECLCALLBACK(int) ClipboardCallback (void *pvCallback, uint32_t u32ClientId, uint32_t u32Function, uint32_t u32Format, const void *pvData, uint32_t cbData); - static DECLCALLBACK(int) ClipboardServiceExtension(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms); #ifdef VBOX_WITH_USB RemoteUSBBackend *usbBackendFindByUUID (const Guid *pGuid); diff --git a/src/VBox/Main/include/GuestShClPrivate.h b/src/VBox/Main/include/GuestShClPrivate.h index 8b2ddcedee41..3afda7b6b75e 100644 --- a/src/VBox/Main/include/GuestShClPrivate.h +++ b/src/VBox/Main/include/GuestShClPrivate.h @@ -1,4 +1,4 @@ -/* $Id: GuestShClPrivate.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClPrivate.h 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Private Shared Clipboard code for the Main API. */ @@ -40,19 +40,6 @@ class Console; class GuestShClConn; -/** Chained Shared Clipboard service extension used by the remote desktop server. */ -struct SHCLSVCEXT -{ - /** Chained service extension callback, or NULL. */ - PFNHGCMSVCEXT pfnExt; - /** Opaque callback argument. */ - void *pvExt; - /** Reverse callback installed by the HGCM service, or NULL. */ - PFNSHCLEXTCALLBACK pfnExtCallback; -}; -/** Pointer to a chained Shared Clipboard service extension. */ -typedef SHCLSVCEXT *PSHCLSVCEXT; - /** * Private singleton class for managing the Shared Clipboard implementation within Main. * @@ -129,6 +116,7 @@ class GuestShCl bool i_isHostDataSeqCurrentLocked(uint64_t uSeq); uint64_t i_getGuestDataSeq(void); bool i_isGuestDataSeqCurrent(uint64_t uSeq); + int i_reportRemoteFormatsToGuestNow(SHCLFORMATS fFormats); /** @} */ public: @@ -141,10 +129,9 @@ class GuestShCl int ReportFormatsToHost(SHCLFORMATS fFormats); int WriteDataToHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbData); int ReportFormatsToGuest(SHCLFORMATS fFormats); + int ReportRemoteFormatsToGuest(SHCLFORMATS fFormats); int ReportFormatsToGuest(GuestShClConn *pConn, SHCLFORMATS fFormats, SHCLSOURCE enmSource); int ReportError(const char *pcszId, int vrc, const char *pcszMsgFmt, ...); - int RegisterServiceExtension(PFNHGCMSVCEXT pfnExtension, void *pvExtension); - int UnregisterServiceExtension(PFNHGCMSVCEXT pfnExtension); /** @} */ public: @@ -156,19 +143,15 @@ class GuestShCl protected: - int i_forwardToSvcExt(uint32_t u32Function, void *pvParms, uint32_t cbParms); int i_svcExtParmsValidate(uint32_t u32Function, void *pvParms, uint32_t cbParms); protected: /** @name Service extension callback handlers. * @{ */ - int i_svcExtSetCallback(PSHCLEXTPARMS pParms); int i_svcExtReportFormatsToHostCallback(PSHCLEXTPARMS pParms); - int i_svcExtReportFormatsToGuestCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_svcExtDataReadCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_svcExtDataReadVrdeCallback(PSHCLEXTPARMS pParms); - int i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtDataReadCallback(PSHCLEXTPARMS pParms); + int i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms); int i_svcExtBackendInitCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); int i_svcExtBackendDestroyCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); int i_svcExtBackendConnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); @@ -188,12 +171,16 @@ class GuestShCl Console *m_pConsole; /** Critical section to serialize access. */ RTCRITSECT m_CritSect; + /** Serializes remote format publication and the handoff after a remote data read. */ + RTCRITSECT m_RemoteFormatsCritSect; /** Main-owned connection encapsulating the service endpoint and native backend context. */ GuestShClConn *m_pConn; - /** Chained remote-desktop service extension. */ - SHCLSVCEXT m_SvcExtVRDP; - /** Reverse callback supplied by the HGCM service, or NULL. */ - PFNSHCLEXTCALLBACK m_pfnExtCallback; + /** Whether Main is synchronously reading from the remote clipboard provider. */ + bool m_fRemoteDataReadActive; + /** Whether a remote format announcement arrived during that read. */ + bool m_fRemoteFormatsPending; + /** Latest remote formats deferred until the active read completes. */ + SHCLFORMATS m_fPendingRemoteFormats; /** Host data sequence counter, protected by m_CritSect. */ uint64_t m_uHostDataSeq; /** Guest data sequence counter, protected by m_CritSect. */ diff --git a/src/VBox/Main/src-client/ClipboardImpl.cpp b/src/VBox/Main/src-client/ClipboardImpl.cpp index aefe77249009..af03883f6595 100644 --- a/src/VBox/Main/src-client/ClipboardImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardImpl.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Console clipboard API. */ @@ -2011,6 +2011,24 @@ HRESULT Clipboard::i_getCurrentStateForEvent(ClipboardSource_T *aSource, } +/** + * Returns the current clipboard source. + * + * @returns COM status code. + * @param aSource Where to return the current clipboard source. + */ +HRESULT Clipboard::i_getCurrentSource(ClipboardSource_T *aSource) +{ + AssertPtrReturn(aSource, E_POINTER); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertPtrReturn(mData, E_FAIL); + + *aSource = mData->mSource; + return S_OK; +} + + /** * Allocates the next clipboard event revision. * diff --git a/src/VBox/Main/src-client/ConsoleImpl.cpp b/src/VBox/Main/src-client/ConsoleImpl.cpp index 43d2aba13dec..6106fe847ce6 100644 --- a/src/VBox/Main/src-client/ConsoleImpl.cpp +++ b/src/VBox/Main/src-client/ConsoleImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ConsoleImpl.cpp 114560 2026-06-29 08:32:23Z andreas.loeffler@oracle.com $ */ +/* $Id: ConsoleImpl.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console COM Class implementation */ @@ -1657,11 +1657,6 @@ void Console::i_VRDPClientDisconnect(uint32_t u32ClientId, mConsoleVRDPServer->USBBackendDelete(u32ClientId); } - if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_CLIPBOARD) - { - mConsoleVRDPServer->ClipboardDelete(u32ClientId); - } - #ifdef VBOX_WITH_AUDIO_VRDE if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_AUDIO) { @@ -1731,9 +1726,7 @@ void Console::i_VRDPInterceptClipboard(uint32_t u32ClientId) AutoCaller autoCaller(this); AssertComRCReturnVoid(autoCaller.hrc()); - AssertReturnVoid(mConsoleVRDPServer); - - mConsoleVRDPServer->ClipboardCreate(u32ClientId); + RT_NOREF(u32ClientId); LogFlowFuncLeave(); return; @@ -9272,10 +9265,13 @@ HRESULT Console::i_powerDown(IProgress *aProgress /*= NULL*/) # ifdef VBOX_WITH_SHARED_CLIPBOARD if (m_hHgcmSvcExtShCl) { - HGCMHostUnregisterServiceExtension(m_hHgcmSvcExtShCl); - m_hHgcmSvcExtShCl = NULL; + i_consoleVRDPServer()->ClipboardSetGuestShClAvailable(false); + int const vrcUnregister = HGCMHostUnregisterServiceExtension(m_hHgcmSvcExtShCl); + if (RT_SUCCESS(vrcUnregister)) + m_hHgcmSvcExtShCl = NULL; + else + LogRel(("Shared Clipboard: Unregistering the HGCM service extension failed with %Rrc\n", vrcUnregister)); } - GuestShCl::DestroyInstance(); #endif # ifdef VBOX_WITH_DRAG_AND_DROP @@ -9288,6 +9284,13 @@ HRESULT Console::i_powerDown(IProgress *aProgress /*= NULL*/) m_pVMMDev->hgcmShutdown(); +# ifdef VBOX_WITH_SHARED_CLIPBOARD + m_hHgcmSvcExtShCl = NULL; + /* Keep the extension target alive through HGCM shutdown. Service + * unloading may perform the final synchronous teardown callback. */ + GuestShCl::DestroyInstance(); +# endif + alock.acquire(); } @@ -11667,7 +11670,23 @@ void Console::i_powerUpThreadTask(VMPowerUpTask *pTask) if (pConsole->m_pVMMDev) { alock.release(); /* just to be on the safe side... */ +#ifdef VBOX_WITH_SHARED_CLIPBOARD + if (pConsole->m_hHgcmSvcExtShCl) + { + pConsole->i_consoleVRDPServer()->ClipboardSetGuestShClAvailable(false); + int const vrcUnregister = HGCMHostUnregisterServiceExtension(pConsole->m_hHgcmSvcExtShCl); + if (RT_SUCCESS(vrcUnregister)) + pConsole->m_hHgcmSvcExtShCl = NULL; + else + LogRel(("Shared Clipboard: Unregistering the HGCM service extension after VM creation failure" + " failed with %Rrc\n", vrcUnregister)); + } +#endif pConsole->m_pVMMDev->hgcmShutdown(true /*fUvmIsInvalid*/); +#ifdef VBOX_WITH_SHARED_CLIPBOARD + pConsole->m_hHgcmSvcExtShCl = NULL; + GuestShCl::DestroyInstance(); +#endif alock.acquire(); } pVMM->pfnVMR3ReleaseUVM(pConsole->mpUVM); @@ -12410,4 +12429,3 @@ const PDMDRVREG Console::DrvStatusReg = /* u32EndVersion */ PDM_DRVREG_VERSION }; - diff --git a/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp b/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp index ce6b21d514c4..a276f1f177ea 100644 --- a/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp +++ b/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp @@ -1,4 +1,4 @@ -/* $Id: ConsoleImplConfigCommon.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: ConsoleImplConfigCommon.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console COM Class implementation - VM Configuration Bits. * @@ -50,6 +50,7 @@ #include "BusAssignmentManager.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD # include "GuestShClPrivate.h" +# include "ConsoleVRDPServer.h" #endif #ifdef VBOX_WITH_DRAG_AND_DROP # include "GuestImpl.h" @@ -4018,7 +4019,9 @@ int Console::i_configVmmDev(ComPtr pMachine, BusAssignmentManager *pBu vrc = HGCMHostRegisterServiceExtension(&m_hHgcmSvcExtShCl, "VBoxSharedClipboard", &GuestShCl::s_HgcmDispatcher, pGuestShCl); - if (RT_FAILURE(vrc)) + if (RT_SUCCESS(vrc)) + i_consoleVRDPServer()->ClipboardSetGuestShClAvailable(true); + else Log(("Cannot register VBoxSharedClipboard extension, vrc=%Rrc\n", vrc)); } else diff --git a/src/VBox/Main/src-client/ConsoleVRDPServer.cpp b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp index c315942a59ad..948108b120b7 100644 --- a/src/VBox/Main/src-client/ConsoleVRDPServer.cpp +++ b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp @@ -1,4 +1,4 @@ -/* $Id: ConsoleVRDPServer.cpp 114604 2026-07-03 12:17:31Z andreas.loeffler@oracle.com $ */ +/* $Id: ConsoleVRDPServer.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console VRDP helper class. */ @@ -32,7 +32,7 @@ #include "ConsoleImpl.h" #include "DisplayImpl.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD -# include "GuestShClPrivate.h" /* For (un-)registering the service extension. */ +# include "GuestShClPrivate.h" #endif #include "KeyboardImpl.h" #include "MouseImpl.h" @@ -1356,10 +1356,14 @@ ConsoleVRDPServer::ConsoleVRDPServer(Console *console) mConsole = console; int vrc = RTCritSectInit(&mCritSect); - AssertRC(vrc); - - mcClipboardRefs = 0; - mpfnClipboardCallback = NULL; + AssertFatalMsgRC(vrc, ("Initializing the VRDE server critical section failed with %Rrc\n", vrc)); + mfClipboardGuestShClAvailable = false; + mcClipboardGuestShClCalls = 0; + mhClipboardGuestShClCallsDone = NIL_RTSEMEVENTMULTI; + vrc = RTSemEventMultiCreate(&mhClipboardGuestShClCallsDone); + AssertFatalMsgRC(vrc, ("Creating the VRDE clipboard callback event failed with %Rrc\n", vrc)); + vrc = RTSemEventMultiSignal(mhClipboardGuestShClCallsDone); + AssertFatalMsgRC(vrc, ("Signalling the VRDE clipboard callback event failed with %Rrc\n", vrc)); #ifdef VBOX_WITH_USB mUSBBackends.pHead = NULL; mUSBBackends.pTail = NULL; @@ -1437,8 +1441,15 @@ ConsoleVRDPServer::ConsoleVRDPServer(Console *console) ConsoleVRDPServer::~ConsoleVRDPServer() { + ClipboardSetGuestShClAvailable(false); Stop(); + if (mhClipboardGuestShClCallsDone != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(mhClipboardGuestShClCallsDone); + mhClipboardGuestShClCallsDone = NIL_RTSEMEVENTMULTI; + } + if (mConsoleListener) { ComPtr es; @@ -3286,6 +3297,19 @@ void ConsoleVRDPServer::unlockConsoleVRDPServer(void) RTCritSectLeave(&mCritSect); } +/** + * Handles a clipboard request received from a remote-desktop client. + * + * @returns VBox status code. + * @param pvCallback ConsoleVRDPServer instance receiving the request. + * @param u32ClientId Remote client ID. + * @param u32Function VRDE_CLIPBOARD_FUNCTION_XXX request number. + * @param u32Format Clipboard format associated with the request. + * @param pvData Request data. Optional if @a cbData is zero. + * @param cbData Request data size in bytes. + * + * @thread VRDE callback thread. + */ DECLCALLBACK(int) ConsoleVRDPServer::ClipboardCallback(void *pvCallback, uint32_t u32ClientId, uint32_t u32Function, @@ -3296,178 +3320,161 @@ DECLCALLBACK(int) ConsoleVRDPServer::ClipboardCallback(void *pvCallback, LogFlowFunc(("pvCallback = %p, u32ClientId = %d, u32Function = %d, u32Format = 0x%08X, pvData = %p, cbData = %d\n", pvCallback, u32ClientId, u32Function, u32Format, pvData, cbData)); - int vrc = VINF_SUCCESS; + ConsoleVRDPServer *pServer = static_cast(pvCallback); + AssertPtrReturn(pServer, VERR_INVALID_POINTER); - ConsoleVRDPServer *pServer = static_cast (pvCallback); + RT_NOREF(u32ClientId, pvData, cbData); - RT_NOREF(u32ClientId); + int vrc = pServer->lockConsoleVRDPServer(); + AssertRCReturn(vrc, vrc); + bool const fCallGuestShCl = pServer->mfClipboardGuestShClAvailable; + if (fCallGuestShCl && pServer->mcClipboardGuestShClCalls++ == 0) + { + int const vrcReset = RTSemEventMultiReset(pServer->mhClipboardGuestShClCallsDone); + AssertFatalMsgRC(vrcReset, ("Resetting the VRDE clipboard callback event failed with %Rrc\n", vrcReset)); + } + pServer->unlockConsoleVRDPServer(); + + if (!fCallGuestShCl) + return VERR_NOT_AVAILABLE; switch (u32Function) { case VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE: { - if (pServer->mpfnClipboardCallback) +#ifdef VBOX_WITH_SHARED_CLIPBOARD + if (!ShClFormatsAreValid(u32Format)) + vrc = VERR_INVALID_PARAMETER; + else { - vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, - u32Format, - (void *)pvData, - cbData); + GuestShCl *pGuestShCl = GuestShCl::TryGetInst(); + vrc = pGuestShCl ? pGuestShCl->ReportRemoteFormatsToGuest(u32Format) : VERR_NOT_AVAILABLE; } +#else + vrc = VERR_NOT_SUPPORTED; +#endif } break; case VRDE_CLIPBOARD_FUNCTION_DATA_READ: { - if (pServer->mpfnClipboardCallback) +#ifdef VBOX_WITH_SHARED_CLIPBOARD + GuestShCl *pGuestShCl = GuestShCl::TryGetInst(); + if (!pGuestShCl) + vrc = VERR_NOT_AVAILABLE; + else { - vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ, - u32Format, - (void *)pvData, - cbData); + void *pvGuestData = NULL; + uint32_t cbGuestData = 0; + vrc = pGuestShCl->ReadDataFromGuest(u32Format, &pvGuestData, &cbGuestData); + /* GuestShCl mirrors a successful guest reply to VRDE before + * completing the read operation. */ + RTMemFree(pvGuestData); } +#else + vrc = VERR_NOT_SUPPORTED; +#endif } break; default: - { vrc = VERR_NOT_SUPPORTED; - } break; + break; } + int const vrcLock = pServer->lockConsoleVRDPServer(); + AssertFatalMsgRC(vrcLock, ("Locking the VRDE server after a clipboard callback failed with %Rrc\n", vrcLock)); + Assert(pServer->mcClipboardGuestShClCalls > 0); + if (pServer->mcClipboardGuestShClCalls > 0 && --pServer->mcClipboardGuestShClCalls == 0) + { + int const vrcSignal = RTSemEventMultiSignal(pServer->mhClipboardGuestShClCallsDone); + AssertFatalMsgRC(vrcSignal, ("Signalling the VRDE clipboard callback event failed with %Rrc\n", vrcSignal)); + } + pServer->unlockConsoleVRDPServer(); + return vrc; } /** - * Service extension callback called by GuestShCl::s_HgcmDispatcher(). + * Publishes or withdraws the GuestShCl singleton from direct VRDE callbacks. * - * @returns VBox status code. - * @retval VERR_NOT_SUPPORTED if the extension didn't handle the requested function. This will invoke the regular backend then. - * @param pvExtension Pointer to service extension. - * @param u32Function Callback HGCM message ID. - * @param pvParms Pointer to optional data provided for a particular message. Optional. - * @param cbParms Size (in bytes) of \a pvParms. + * Withdrawing it waits until every callback which previously acquired it has + * returned, so the caller may safely destroy GuestShCl afterwards. + * + * @param fAvailable Whether GuestShCl may be acquired. */ -/* static */ -DECLCALLBACK(int) ConsoleVRDPServer::ClipboardServiceExtension(void *pvExtension, - uint32_t u32Function, void *pvParms, uint32_t cbParms) +void ConsoleVRDPServer::ClipboardSetGuestShClAvailable(bool fAvailable) { - RT_NOREF(cbParms); - LogFlowFunc(("pvExtension = %p, u32Function = %d, pvParms = %p, cbParms = %d\n", - pvExtension, u32Function, pvParms, cbParms)); - - int vrc = VINF_SUCCESS; - - ConsoleVRDPServer *pServer = static_cast (pvExtension); - AssertPtrReturn(pServer, VERR_INVALID_POINTER); - - SHCLEXTPARMS *pParms = (SHCLEXTPARMS *)pvParms; - AssertPtrReturn(pParms, VERR_INVALID_POINTER); + int const vrc = lockConsoleVRDPServer(); + AssertFatalMsgRC(vrc, ("Locking the VRDE server for clipboard publication failed with %Rrc\n", vrc)); + mfClipboardGuestShClAvailable = fAvailable; + unlockConsoleVRDPServer(); - switch (u32Function) + if (!fAvailable) { - case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: - { - pServer->mpfnClipboardCallback = pParms->u.SetCallback.pfnCallback; - } break; - - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - { - /* The guest announces clipboard formats to the host. This must be delivered to all clients. */ - if (mpEntryPoints && pServer->mhServer) - { - mpEntryPoints->VRDEClipboard(pServer->mhServer, - VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE, - pParms->u.ReportFormats.uFormats, - NULL, - 0, - NULL); - } - } break; - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - { - /* The clipboard service expects that the pvData buffer will be filled - * with clipboard data. The server returns the data from the client that - * announced the requested format most recently. - */ - if (mpEntryPoints && pServer->mhServer) - { - mpEntryPoints->VRDEClipboard(pServer->mhServer, - VRDE_CLIPBOARD_FUNCTION_DATA_READ, - pParms->u.ReadWriteData.uFormat, - pParms->u.ReadWriteData.pvData, - pParms->u.ReadWriteData.cbData, - &pParms->u.ReadWriteData.cbActual); - } - } break; - - case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: - { - if (mpEntryPoints && pServer->mhServer) - { - mpEntryPoints->VRDEClipboard(pServer->mhServer, - VRDE_CLIPBOARD_FUNCTION_DATA_WRITE, - pParms->u.ReadWriteData.uFormat, - pParms->u.ReadWriteData.pvData, - pParms->u.ReadWriteData.cbData, - NULL); - } - /* - * VRDE only mirrors the data here. Return VERR_NOT_SUPPORTED so the - * regular Main path still signals the pending guest-read event exactly - * once; returning success would consume the reply and leave Main waiters - * blocked. - */ - vrc = VERR_NOT_SUPPORTED; - } break; - - default: - vrc = VERR_NOT_SUPPORTED; + int const vrcWait = RTSemEventMultiWait(mhClipboardGuestShClCallsDone, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(vrcWait, ("Waiting for direct VRDE clipboard callbacks failed with %Rrc\n", vrcWait)); } - - return vrc; } -void ConsoleVRDPServer::ClipboardCreate(uint32_t u32ClientId) +/** + * Reports guest clipboard formats to all connected remote-desktop clients. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if the active VRDE server has no clipboard interface. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ +int ConsoleVRDPServer::ClipboardReportGuestFormats(SHCLFORMATS fFormats) const { - RT_NOREF(u32ClientId); - - int vrc = lockConsoleVRDPServer(); - if (RT_SUCCESS(vrc)) + if (mpEntryPoints && mhServer && mpEntryPoints->VRDEClipboard) { - if (mcClipboardRefs == 0) - { -#ifdef VBOX_WITH_SHARED_CLIPBOARD - vrc = GuestShClInst()->RegisterServiceExtension(ClipboardServiceExtension, this /* pvExtension */); - AssertRC(vrc); -#endif /* VBOX_WITH_SHARED_CLIPBOARD */ - } - - mcClipboardRefs++; - unlockConsoleVRDPServer(); + mpEntryPoints->VRDEClipboard(mhServer, VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE, + fFormats, NULL, 0, NULL); + return VINF_SUCCESS; } + return VERR_NOT_SUPPORTED; } -void ConsoleVRDPServer::ClipboardDelete(uint32_t u32ClientId) +/** + * Reads data from the remote clipboard provider selected by the VRDE server. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if the active VRDE server has no clipboard interface. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. Optional if @a cbData is zero. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required byte count. + */ +int ConsoleVRDPServer::ClipboardReadRemoteData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, + uint32_t *pcbActual) const { - RT_NOREF(u32ClientId); - - int vrc = lockConsoleVRDPServer(); - if (RT_SUCCESS(vrc)) + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + *pcbActual = 0; + if (mpEntryPoints && mhServer && mpEntryPoints->VRDEClipboard) { - Assert(mcClipboardRefs); - if (mcClipboardRefs > 0) - { - mcClipboardRefs--; - - if (mcClipboardRefs == 0) - { -#ifdef VBOX_WITH_SHARED_CLIPBOARD - GuestShClInst()->UnregisterServiceExtension(ClipboardServiceExtension); -#endif /* VBOX_WITH_SHARED_CLIPBOARD */ - } - } + mpEntryPoints->VRDEClipboard(mhServer, VRDE_CLIPBOARD_FUNCTION_DATA_READ, + uFormat, pvData, cbData, pcbActual); + return VINF_SUCCESS; + } + return VERR_NOT_SUPPORTED; +} - unlockConsoleVRDPServer(); +/** + * Sends guest clipboard data to the remote client which requested it. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if the active VRDE server has no clipboard interface. + * @param uFormat Clipboard format of the data. + * @param pvData Data buffer. Optional if @a cbData is zero. + * @param cbData Data size in bytes. + */ +int ConsoleVRDPServer::ClipboardWriteGuestData(SHCLFORMAT uFormat, const void *pvData, uint32_t cbData) const +{ + if (mpEntryPoints && mhServer && mpEntryPoints->VRDEClipboard) + { + mpEntryPoints->VRDEClipboard(mhServer, VRDE_CLIPBOARD_FUNCTION_DATA_WRITE, + uFormat, (void *)pvData, cbData, NULL); + return VINF_SUCCESS; } + return VERR_NOT_SUPPORTED; } /* That is called on INPUT thread of the VRDP server. diff --git a/src/VBox/Main/src-client/GuestShClPrivate.cpp b/src/VBox/Main/src-client/GuestShClPrivate.cpp index a7c89adb6238..682a74b4ef3f 100644 --- a/src/VBox/Main/src-client/GuestShClPrivate.cpp +++ b/src/VBox/Main/src-client/GuestShClPrivate.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClPrivate.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClPrivate.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Private Shared Clipboard code. */ @@ -60,29 +60,35 @@ GuestShCl* GuestShCl::s_pInstance = NULL; - - GuestShCl::GuestShCl(Console *pConsole) : m_pConsole(pConsole) , m_pConn(NULL) - , m_pfnExtCallback(NULL) + , m_fRemoteDataReadActive(false) + , m_fRemoteFormatsPending(false) + , m_fPendingRemoteFormats(VBOX_SHCL_FMT_NONE) , m_uHostDataSeq(0) , m_uGuestDataSeq(0) { LogFlowFuncEnter(); - RT_ZERO(m_SvcExtVRDP); - int vrc = RTCritSectInit(&m_CritSect); if (RT_FAILURE(vrc)) throw vrc; + vrc = RTCritSectInit(&m_RemoteFormatsCritSect); + if (RT_FAILURE(vrc)) + { + RTCritSectDelete(&m_CritSect); + throw vrc; + } + try { m_pConn = new GuestShClConn(this); } catch (...) { + RTCritSectDelete(&m_RemoteFormatsCritSect); RTCritSectDelete(&m_CritSect); throw; } @@ -108,12 +114,14 @@ void GuestShCl::uninit(void) m_pConn = NULL; } + if (RTCritSectIsInitialized(&m_RemoteFormatsCritSect)) + RTCritSectDelete(&m_RemoteFormatsCritSect); if (RTCritSectIsInitialized(&m_CritSect)) RTCritSectDelete(&m_CritSect); - RT_ZERO(m_SvcExtVRDP); - - m_pfnExtCallback = NULL; + m_fRemoteDataReadActive = false; + m_fRemoteFormatsPending = false; + m_fPendingRemoteFormats = VBOX_SHCL_FMT_NONE; m_uHostDataSeq = 0; m_uGuestDataSeq = 0; } @@ -290,78 +298,6 @@ bool GuestShCl::i_isGuestDataSeqCurrent(uint64_t uSeq) } -/** - * Registers a Shared Clipboard service extension. - * - * @returns VBox status code. - * @param pfnExtension Service extension to register. - * @param pvExtension User-supplied data pointer. Optional. - */ -int GuestShCl::RegisterServiceExtension(PFNHGCMSVCEXT pfnExtension, void *pvExtension) -{ - AssertPtrReturn(pfnExtension, VERR_INVALID_POINTER); - /* pvExtension is optional. */ - - lock(); - - LogFlowFunc(("m_pfnExtCallback=%p\n", this->m_pfnExtCallback)); - - PSHCLSVCEXT pExt = &this->m_SvcExtVRDP; /* Currently we only have one extension only. */ - - Assert(pExt->pfnExt == NULL); - - pExt->pfnExt = pfnExtension; - pExt->pvExt = pvExtension; - pExt->pfnExtCallback = this->m_pfnExtCallback; /* Assign callback function. Optional and can be NULL. */ - - if (pExt->pfnExtCallback) - { - /* Make sure to also give the extension the ability to use the callback. */ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.SetCallback.pfnCallback = pExt->pfnExtCallback; - - /* ignore rc, callback is optional */ pExt->pfnExt(pExt->pvExt, - VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - } - - unlock(); - - return VINF_SUCCESS; -} - -/** - * Unregisters a Shared Clipboard service extension. - * - * @returns VBox status code. - * @param pfnExtension Service extension to unregister. - */ -int GuestShCl::UnregisterServiceExtension(PFNHGCMSVCEXT pfnExtension) -{ - AssertPtrReturn(pfnExtension, VERR_INVALID_POINTER); - - lock(); - - PSHCLSVCEXT pExt = &this->m_SvcExtVRDP; /* Currently we only have one extension only. */ - - AssertReturnStmt(pExt->pfnExt == pfnExtension, unlock(), VERR_INVALID_PARAMETER); - AssertPtr(pExt->pfnExt); - - /* Unregister the callback (setting to NULL). */ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - /* ignore rc, callback is optional */ pExt->pfnExt(pExt->pvExt, - VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - - RT_BZERO(pExt, sizeof(SHCLSVCEXT)); - - unlock(); - - return VINF_SUCCESS; -} - /** * Sends a (blocking) message to the host side of the host service. * @@ -471,6 +407,68 @@ int GuestShCl::ReportFormatsToGuest(SHCLFORMATS fFormats) return vrc; } +/** + * Reports remote clipboard formats to the active guest clipboard client. + * + * @returns VBox status code. + * @param fFormats Formats reported by the remote clipboard peer. + */ +int GuestShCl::ReportRemoteFormatsToGuest(SHCLFORMATS fFormats) +{ + AssertReturn(ShClFormatsAreValid(fFormats), VERR_INVALID_PARAMETER); + + int vrc = RTCritSectEnter(&m_RemoteFormatsCritSect); + AssertRCReturn(vrc, vrc); + + vrc = lock(); + if (RT_FAILURE(vrc)) + { + RTCritSectLeave(&m_RemoteFormatsCritSect); + return vrc; + } + if (m_fRemoteDataReadActive) + { + m_fRemoteFormatsPending = true; + m_fPendingRemoteFormats = fFormats; + unlock(); + RTCritSectLeave(&m_RemoteFormatsCritSect); + return VINF_SUCCESS; + } + unlock(); + + vrc = i_reportRemoteFormatsToGuestNow(fFormats); + int const vrcLeave = RTCritSectLeave(&m_RemoteFormatsCritSect); + AssertRC(vrcLeave); + return vrc; +} + +/** + * Reports remote clipboard formats immediately. + * + * @returns VBox status code. + * @param fFormats Remote formats, VBOX_SHCL_FMT_XXX. + * + * @note The caller must serialize remote reports and handle deferral while + * a remote-data read is active. + */ +int GuestShCl::i_reportRemoteFormatsToGuestNow(SHCLFORMATS fFormats) +{ + AssertReturn(ShClFormatsAreValid(fFormats), VERR_INVALID_PARAMETER); + + int const vrc = m_pConn->reportFormatsToGuest(fFormats, &fFormats); + if (vrc == VERR_SHCLPB_NO_DATA || vrc == VINF_NO_CHANGE) + return VINF_SUCCESS; + if (RT_SUCCESS(vrc)) + { + i_incHostDataSeq(); + Clipboard *pClipboard = m_pConsole->i_getClipboard(); + if (pClipboard) + pClipboard->i_reportFormats(VBOX_SHCL_MAIN_CLIENT_NONE, fFormats, ClipboardSource_Remote, + true /* fForceNotify */); + } + return vrc; +} + /** * Reports clipboard formats to the guest via the service backend and mirrors * successful reports to the console clipboard event source. @@ -589,28 +587,16 @@ DECLCALLBACK(int) GuestShCl::s_HgcmDispatcher(void *pvExtension, uint32_t u32Fun switch (u32Function) { - case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: - vrc = pThis->i_svcExtSetCallback((PSHCLEXTPARMS)pvParms); - break; - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: vrc = pThis->i_svcExtReportFormatsToHostCallback((PSHCLEXTPARMS)pvParms); break; - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - vrc = pThis->i_svcExtReportFormatsToGuestCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); - break; - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - vrc = pThis->i_svcExtDataReadCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); - break; - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE: - vrc = pThis->i_svcExtDataReadVrdeCallback((PSHCLEXTPARMS)pvParms); + vrc = pThis->i_svcExtDataReadCallback((PSHCLEXTPARMS)pvParms); break; case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: - vrc = pThis->i_svcExtDataWriteCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); + vrc = pThis->i_svcExtDataWriteCallback((PSHCLEXTPARMS)pvParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: @@ -648,7 +634,7 @@ DECLCALLBACK(int) GuestShCl::s_HgcmDispatcher(void *pvExtension, uint32_t u32Fun #endif default: - vrc = pThis->i_forwardToSvcExt(u32Function, pvParms, cbParms); + vrc = VERR_NOT_SUPPORTED; break; } diff --git a/src/VBox/Main/src-client/GuestShClSvcExt.cpp b/src/VBox/Main/src-client/GuestShClSvcExt.cpp index 94cde0e2a8ad..dfccc7967268 100644 --- a/src/VBox/Main/src-client/GuestShClSvcExt.cpp +++ b/src/VBox/Main/src-client/GuestShClSvcExt.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClSvcExt.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClSvcExt.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard service extension handling for Main. */ @@ -29,6 +29,7 @@ #include "LoggingNew.h" #include "ConsoleImpl.h" +#include "ConsoleVRDPServer.h" #include "ClipboardImpl.h" #include "GuestShClPrivate.h" #include "GuestShClConn.h" @@ -121,23 +122,6 @@ static int shClSvcExtValidateDataBuffer(void const *pvData, uint32_t cbData) } -/** - * Forwards a Shared Clipboard service extension request to the chained VRDP extension. - * - * @returns VBox status code returned by the chained extension, or VERR_NOT_SUPPORTED if no - * chained extension is registered. - * @param u32Function Service extension function to forward. - * @param pvParms Raw service extension parameters to forward. - * @param cbParms Size, in bytes, of \a pvParms. - */ -int GuestShCl::i_forwardToSvcExt(uint32_t u32Function, void *pvParms, uint32_t cbParms) -{ - PSHCLSVCEXT const pSvcExtVRDP = &m_SvcExtVRDP; /* Currently we have one extension only. */ - if (pSvcExtVRDP->pfnExt) - return pSvcExtVRDP->pfnExt(pSvcExtVRDP->pvExt, u32Function, pvParms, cbParms); - return VERR_NOT_SUPPORTED; -} - /** * Validates Shared Clipboard service extension parameters before dispatching a request. * @@ -154,11 +138,8 @@ int GuestShCl::i_svcExtParmsValidate(uint32_t u32Function, void *pvParms, uint32 { switch (u32Function) { - case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - case VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE: case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: case VBOX_CLIPBOARD_EXT_FN_ERROR: case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: @@ -185,9 +166,6 @@ int GuestShCl::i_svcExtParmsValidate(uint32_t u32Function, void *pvParms, uint32 int vrc; switch (u32Function) { - case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: - return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: SHCL_VALIDATE_ACTIVE(Transport); AssertReturn(ShClFormatsAreValid(pParms->u.ReportFormats.uFormats), @@ -195,15 +173,7 @@ int GuestShCl::i_svcExtParmsValidate(uint32_t u32Function, void *pvParms, uint32 AssertReturn(pParms->u.ReportFormats.enmSource == SHCLSOURCE_INVALID, VERR_INVALID_PARAMETER); return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - SHCL_VALIDATE_ACTIVE(Transport); - AssertReturn(ShClFormatsAreValid(pParms->u.ReportFormats.uFormats), - VERR_INVALID_PARAMETER); - AssertReturn(ShClSourceIsValid(pParms->u.ReportFormats.enmSource), VERR_INVALID_PARAMETER); - return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - case VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE: SHCL_VALIDATE_ACTIVE(Transport); vrc = shClSvcExtValidateFormat(pParms->u.ReadWriteData.uFormat, u32Function); if (RT_FAILURE(vrc)) @@ -290,23 +260,10 @@ int GuestShCl::i_svcExtParmsValidate(uint32_t u32Function, void *pvParms, uint32 } -/** - * Handles VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK from the Shared Clipboard host service. - * - * @returns VBox status code. - * @param pParms Service extension parameters containing the callback to install. - */ -int GuestShCl::i_svcExtSetCallback(PSHCLEXTPARMS pParms) -{ - m_pfnExtCallback = pParms->u.SetCallback.pfnCallback; - return VINF_SUCCESS; -} - - /** * Handles VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST from the Shared Clipboard host service. * - * Reports guest clipboard formats to Main and forwards the notification to the chained VRDP extension. + * Reports guest clipboard formats to Main and connected remote-desktop clients. * * @returns VBox status code. * @param pParms Decoded service extension parameters. @@ -320,30 +277,11 @@ int GuestShCl::i_svcExtReportFormatsToHostCallback(PSHCLEXTPARMS pParms) AssertPtr(m_pConsole->i_getClipboard()); if (m_pConsole->i_getClipboard()) m_pConsole->i_getClipboard()->i_reportFormats(VBOX_SHCL_MAIN_CLIENT_NONE, - fFormats, ClipboardSource_Guest, - true /* fForceNotify */); + fFormats, ClipboardSource_Guest, + true /* fForceNotify */); - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, pParms, sizeof(*pParms)); - return vrc == VERR_NOT_SUPPORTED ? VINF_SUCCESS : vrc; -} - - -/** - * Handles VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST from the Shared Clipboard host service. - * - * Forwards host or remote clipboard format notifications to the chained VRDP extension without - * implicitly publishing them through Main or the regular backend. - * - * @returns VBox status code. - * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. - */ -int GuestShCl::i_svcExtReportFormatsToGuestCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) -{ - RT_NOREF(pParms); - - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST, pvParms, cbParms); + ConsoleVRDPServer *pVrde = m_pConsole->i_consoleVRDPServer(); + int const vrc = pVrde ? pVrde->ClipboardReportGuestFormats(fFormats) : VERR_NOT_SUPPORTED; return vrc == VERR_NOT_SUPPORTED ? VINF_SUCCESS : vrc; } @@ -351,75 +289,90 @@ int GuestShCl::i_svcExtReportFormatsToGuestCallback(PSHCLEXTPARMS pParms, void * /** * Handles VBOX_CLIPBOARD_EXT_FN_DATA_READ from the Shared Clipboard host service. * - * Lets the chained VRDP extension service the request first, then reads clipboard data provided - * explicitly through Main. + * Reads clipboard data from the provider selected by Main. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_svcExtDataReadCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtDataReadCallback(PSHCLEXTPARMS pParms) { - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_DATA_READ, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - { - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - - Clipboard *pClipboard = m_pConsole->i_getClipboard(); - if (pClipboard) - { - HRESULT hrc = pClipboard->i_readDataForGuest(fFormats, pvData, cbData, &pParms->u.ReadWriteData.cbActual); - vrc = SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_NO_DATA; - } - else - vrc = VERR_NOT_AVAILABLE; - if (RT_SUCCESS(vrc)) - LogRel2(("Shared Clipboard: Read Main clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, - pParms->u.ReadWriteData.cbActual)); - else - LogRel2(("Shared Clipboard: No explicit Main clipboard data available, vrc=%Rrc\n", vrc)); - } - return vrc; -} - - -/** - * Handles VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE from the Shared Clipboard host service. - * - * Reads clipboard data asynchronously from the guest for the VRDE extension path and copies the - * received payload into the supplied extension buffer. - * - * @returns VBox status code. - * @param pParms Service extension parameters describing the read request. - */ -int GuestShCl::i_svcExtDataReadVrdeCallback(PSHCLEXTPARMS pParms) -{ - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - PSHCLEVENT pEvent; void *pvData = pParms->u.ReadWriteData.pvData; uint32_t cbData = pParms->u.ReadWriteData.cbData; + SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; + Clipboard *pClipboard = m_pConsole->i_getClipboard(); + ClipboardSource_T enmSource = ClipboardSource_Custom; + HRESULT hrc = pClipboard ? pClipboard->i_getCurrentSource(&enmSource) : E_FAIL; - int vrc = m_pConn->readDataFromGuestAsync(fFormats, &pEvent); - if (RT_SUCCESS(vrc)) + int vrc; + if (SUCCEEDED(hrc) && enmSource == ClipboardSource_Remote) { - PSHCLEVENTPAYLOAD pPayload = NULL; - vrc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload); - if (RT_SUCCESS(vrc)) + int vrcLock = RTCritSectEnter(&m_RemoteFormatsCritSect); + if (RT_FAILURE(vrcLock)) + return vrcLock; + vrcLock = lock(); + if (RT_FAILURE(vrcLock)) + { + RTCritSectLeave(&m_RemoteFormatsCritSect); + return vrcLock; + } + Assert(!m_fRemoteDataReadActive); + m_fRemoteDataReadActive = true; + unlock(); + int const vrcLeave = RTCritSectLeave(&m_RemoteFormatsCritSect); + AssertRC(vrcLeave); + + ConsoleVRDPServer *pVrde = m_pConsole->i_consoleVRDPServer(); + vrc = pVrde ? pVrde->ClipboardReadRemoteData(fFormats, pvData, cbData, + &pParms->u.ReadWriteData.cbActual) + : VERR_NOT_AVAILABLE; + + vrcLock = RTCritSectEnter(&m_RemoteFormatsCritSect); + if (RT_SUCCESS(vrcLock)) { - if (pPayload) + bool fHavePendingFormats = false; + SHCLFORMATS fPendingFormats = VBOX_SHCL_FMT_NONE; + int const vrcState = lock(); + if (RT_SUCCESS(vrcState)) { - memcpy(pvData, pPayload->pvData, RT_MIN(cbData, pPayload->cbData)); - ShClPayloadDestroy(pPayload); - pPayload = NULL; + m_fRemoteDataReadActive = false; + if (m_fRemoteFormatsPending) + { + fHavePendingFormats = true; + fPendingFormats = m_fPendingRemoteFormats; + m_fRemoteFormatsPending = false; + m_fPendingRemoteFormats = VBOX_SHCL_FMT_NONE; + } + unlock(); } else - pvData = NULL; + AssertRC(vrcState); + + if (fHavePendingFormats) + { + int const vrcFormats = i_reportRemoteFormatsToGuestNow(fPendingFormats); + if (RT_FAILURE(vrcFormats)) + LogRel(("Shared Clipboard: Reporting formats deferred during a remote read failed with %Rrc\n", + vrcFormats)); + } + int const vrcLeavePending = RTCritSectLeave(&m_RemoteFormatsCritSect); + AssertRC(vrcLeavePending); } - ShClEventRelease(pEvent); + else + AssertRC(vrcLock); + } + else if (pClipboard) + { + hrc = pClipboard->i_readDataForGuest(fFormats, pvData, cbData, &pParms->u.ReadWriteData.cbActual); + vrc = SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_NO_DATA; } + else + vrc = VERR_NOT_AVAILABLE; + + if (RT_SUCCESS(vrc)) + LogRel2(("Shared Clipboard: Read Main clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, + pParms->u.ReadWriteData.cbActual)); + else + LogRel2(("Shared Clipboard: No Main clipboard data available, vrc=%Rrc\n", vrc)); return vrc; } @@ -427,40 +380,33 @@ int GuestShCl::i_svcExtDataReadVrdeCallback(PSHCLEXTPARMS pParms) /** * Handles VBOX_CLIPBOARD_EXT_FN_DATA_WRITE from the Shared Clipboard host service. * - * Lets the chained VRDP extension service the request first, then signals pending guest-data waiters - * without implicitly writing guest data to the host clipboard backend. + * Mirrors the reply to VRDE and signals pending guest-data waiters. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms) { - PSHCLCLIENTCMDCTX pCmdCtx = pParms->u.ReadWriteData.pCmdCtx; SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - SHCLGUESTDATATOKEN hToken = NULL; - int vrc = m_pConn->guestDataBegin(pCmdCtx, fFormats, &hToken); + int vrc = m_pConn->guestDataBegin(pParms->u.ReadWriteData.pCmdCtx, fFormats, &hToken); if (RT_FAILURE(vrc) || !hToken) return vrc; - int const vrcChained = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, pvParms, cbParms); - if (vrcChained == VERR_NOT_SUPPORTED) + void const *pvData = pParms->u.ReadWriteData.pvData; + uint32_t cbData = pParms->u.ReadWriteData.cbData; + ConsoleVRDPServer *pVrde = m_pConsole->i_consoleVRDPServer(); + if (pVrde) { - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - - vrc = m_pConn->guestDataComplete(hToken, pvData, cbData); - hToken = NULL; - if (RT_FAILURE(vrc)) - LogRelMax(16, ("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", vrc)); - AssertRC(vrc); + int const vrcVrde = pVrde->ClipboardWriteGuestData(fFormats, pvData, cbData); + if (RT_FAILURE(vrcVrde) && vrcVrde != VERR_NOT_SUPPORTED) + LogRelMax2(16, ("Shared Clipboard: Mirroring guest clipboard data to VRDE failed with %Rrc\n", vrcVrde)); } - else - vrc = vrcChained; - if (hToken) - m_pConn->guestDataCancel(hToken); + + vrc = m_pConn->guestDataComplete(hToken, pvData, cbData); + if (RT_FAILURE(vrc)) + LogRelMax(16, ("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", vrc)); + AssertRC(vrc); return vrc; } @@ -468,109 +414,93 @@ int GuestShCl::i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms, void *pvParms, ui /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT from the Shared Clipboard host service. * - * Lets the chained VRDP extension initialize first, then initializes the regular Shared Clipboard - * backend if the extension did not handle the request. + * Initializes Main's native Shared Clipboard backend once. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ int GuestShCl::i_svcExtBackendInitCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - RT_NOREF(pParms); - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - vrc = m_pConn->initBackend(); - return vrc; + RT_NOREF(pParms, pvParms, cbParms); + + return m_pConn->initBackend(); } /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY from the Shared Clipboard host service. * - * Lets the chained VRDP extension tear down first, then destroys the regular Shared Clipboard backend - * if the extension did not handle the request. + * Disconnects an active client, if any, and destroys Main's native backend. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ int GuestShCl::i_svcExtBackendDestroyCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - RT_NOREF(pParms); - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - vrc = m_pConn->destroyBackend(); - return vrc; + RT_NOREF(pParms, pvParms, cbParms); + + return m_pConn->destroyBackend(); } /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT from the Shared Clipboard host service. * - * Lets the chained VRDP extension connect first, then connects the regular backend and records the - * active HGCM client when the backend connection succeeds. + * Connects a client to Main's native backend and records the returned context. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ int GuestShCl::i_svcExtBackendConnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - { - SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); - vrc = m_pConn->connect(&Transport); - } - return vrc; + RT_NOREF(pvParms, cbParms); + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + + return m_pConn->connect(&Transport); } /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT from the Shared Clipboard host service. * - * Lets the chained VRDP extension disconnect first, then disconnects the regular backend and clears - * the active HGCM client if it matches the disconnecting client. + * Disconnects a client from Main's native backend and clears its context. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ int GuestShCl::i_svcExtBackendDisconnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - { - SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); - vrc = m_pConn->disconnect(&Transport); - } - return vrc; + RT_NOREF(pvParms, cbParms); + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + + return m_pConn->disconnect(&Transport); } /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC from the Shared Clipboard host service. * - * Lets the chained VRDP extension synchronize first, then synchronizes the regular Shared Clipboard - * backend if the extension did not handle the request. + * Synchronizes Main's native Shared Clipboard backend. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ int GuestShCl::i_svcExtBackendSyncCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - RT_NOREF(pParms); - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - vrc = m_pConn->syncBackend(); - return vrc; + RT_NOREF(pvParms, cbParms); + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + + return m_pConn->matches(&Transport) ? m_pConn->syncBackend() : VERR_INVALID_PARAMETER; } @@ -607,14 +537,14 @@ int GuestShCl::i_svcExtTransferGetCallbacksCallback(PSHCLEXTPARMS pParms) /** * Handles VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER from the Shared Clipboard host service. * - * Lets the chained VRDP extension handle transfer status first, then forwards the transfer status - * reply to the regular Shared Clipboard backend if the extension did not handle the request. + * Forwards a transfer status reply to the Shared Clipboard backend and Main. * * @returns VBox status code. * @param pParms Decoded service extension parameters. */ int GuestShCl::i_svcExtFileTransferCallback(PSHCLEXTPARMS pParms) { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); PSHCLTRANSFER pTransfer = pParms->u.FileTransferData.pTransfer; SHCLSOURCE const enmShClSource = pParms->u.FileTransferData.enmShClSource; PSHCLREPLY pReply = pParms->u.FileTransferData.pReply; @@ -624,10 +554,10 @@ int GuestShCl::i_svcExtFileTransferCallback(PSHCLEXTPARMS pParms) SHCLTRANSFERSTATUS const enmStatus = pReply->u.TransferStatus.uStatus; int const vrcTransfer = (int)pReply->rc; - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, pParms, sizeof(*pParms)); - if (vrc == VERR_NOT_SUPPORTED) - vrc = m_pConn->transferHandleStatusReply(pTransfer, enmShClSource, - pReply->u.TransferStatus.uStatus, (int)pReply->rc); + int vrc = m_pConn->matches(&Transport) + ? m_pConn->transferHandleStatusReply(pTransfer, enmShClSource, + pReply->u.TransferStatus.uStatus, (int)pReply->rc) + : VERR_INVALID_PARAMETER; if (RT_SUCCESS(vrc)) { From aaed5b352f32f3465e80dd14733bfb0bca14ff74 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 16:40:05 +0000 Subject: [PATCH 148/176] Shared Clipboard: Added more user-visible error logging; some more input validation. bugref:4697 svn:sync-xref-src-repo-rev: r174896 --- include/VBox/HostServices/VBoxClipboardSvc.h | 1 + .../VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp | 30 +++++++++--- .../x11/VBoxClient/clipboard-x11.cpp | 8 ++-- .../ClipboardDataObjectImpl-win.cpp | 20 ++++---- .../SharedClipboard/VbghWaylandClipboard.cpp | 6 +-- .../SharedClipboard/clipboard-common.cpp | 4 +- .../clipboard-transfers-http.cpp | 6 +-- .../clipboard-transfers-provider-local.cpp | 4 +- .../SharedClipboard/clipboard-transfers.cpp | 12 +++-- .../SharedClipboard/clipboard-win.cpp | 8 ++-- .../SharedClipboard/clipboard-x11.cpp | 20 +++++--- .../VBoxSharedClipboardSvc-client.cpp | 21 +++++--- .../VBoxSharedClipboardSvc-transfers.cpp | 37 +++++++++----- .../VBoxSharedClipboardSvc-transport.cpp | 19 ++++---- src/VBox/Main/src-client/ClipboardImpl.cpp | 48 +++++++++---------- src/VBox/Main/src-client/GuestShClSvcExt.cpp | 8 ++-- .../darwin/ClipboardBackendDarwin.cpp | 8 ++-- .../src-client/linux/ClipboardBackendX11.cpp | 12 ++--- 18 files changed, 162 insertions(+), 110 deletions(-) diff --git a/include/VBox/HostServices/VBoxClipboardSvc.h b/include/VBox/HostServices/VBoxClipboardSvc.h index c8fe22da8bb1..47fe964656c4 100644 --- a/include/VBox/HostServices/VBoxClipboardSvc.h +++ b/include/VBox/HostServices/VBoxClipboardSvc.h @@ -261,6 +261,7 @@ * @retval VERR_INVALID_CLIENT_ID * @retval VERR_WRONG_PARAMETER_COUNT * @retval VERR_WRONG_PARAMETER_TYPE + * @retval VERR_INVALID_FLAGS if the format mask contains unknown bits. * @retval VERR_NOT_SUPPORTED if all the formats are unsupported, host * clipboard will be empty. * @retval VERR_ACCESS_DENIED if the clipboard mode is not bi-directional or diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp index 3a30643e689c..6f224808b2b4 100644 --- a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxGuestR3LibClipboard.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxGuestR3LibClipboard.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared Clipboard. */ @@ -1042,9 +1042,19 @@ VBGLR3DECL(int) VbglR3ClipboardTransferSendStatus(PVBGLR3SHCLCMDCTX pCtx, PSHCLT if (pTransfer) { SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); - AssertReturn(ShClTransferIdIsValid(idTransfer), VERR_INVALID_PARAMETER); + if (!ShClTransferIdIsValid(idTransfer)) + { + LogRelMax(16, ("Shared Clipboard: Cannot send status %s for invalid transfer ID %RU16\n", + ShClTransferStatusToStr(uStatus), idTransfer)); + return VERR_INVALID_PARAMETER; + } SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); - AssertReturn(idSession != 0 && idSession != NIL_SHCLSESSIONID, VERR_INVALID_PARAMETER); + if (idSession == 0 || idSession == NIL_SHCLSESSIONID) + { + LogRelMax(16, ("Shared Clipboard: Cannot send status %s for transfer %RU16 without a valid service session\n", + ShClTransferStatusToStr(uStatus), idTransfer)); + return VERR_INVALID_PARAMETER; + } SHCLEVENTID const idEvent = VBOX_SHCL_CONTEXTID_GET_SESSION(idContext) == idSession && VBOX_SHCL_CONTEXTID_GET_TRANSFER(idContext) == idTransfer ? VBOX_SHCL_CONTEXTID_GET_EVENT(idContext) : 0; @@ -2400,9 +2410,14 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, { const SHCLTRANSFERID idTransfer = VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext); - LogRel2(("Shared Clipboard: Received status %s (%Rrc) for transfer %RU16 in session %RU16\n", - ShClTransferStatusToStr(transferReport.uStatus), transferReport.rc, idTransfer, - VBOX_SHCL_CONTEXTID_GET_SESSION(pCmdCtx->idContext))); + if (transferReport.uStatus == SHCLTRANSFERSTATUS_ERROR) + LogRelMax(16, ("Shared Clipboard: Received error status %Rrc for transfer %RU16 in session %RU16\n", + transferReport.rc, idTransfer, + VBOX_SHCL_CONTEXTID_GET_SESSION(pCmdCtx->idContext))); + else + LogRel2(("Shared Clipboard: Received status %s (%Rrc) for transfer %RU16 in session %RU16\n", + ShClTransferStatusToStr(transferReport.uStatus), transferReport.rc, idTransfer, + VBOX_SHCL_CONTEXTID_GET_SESSION(pCmdCtx->idContext))); SHCLSOURCE enmSource = SHCLSOURCE_INVALID; @@ -2805,6 +2820,9 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, && RT_FAILURE(rc) && rc != VERR_INVALID_CONTEXT) { + LogRelMax(16, ("Shared Clipboard: Handling host message %s for context %#RX64 failed with %Rrc\n", + ShClSvcHostMsgToStr(idMsg), pCmdCtx->idContext, rc)); + /* Report transfer-specific error back to the host. */ int rc2 = vbglR3ClipboardTransferSendStatusEx(pCmdCtx, pCmdCtx->idContext, SHCLTRANSFERSTATUS_ERROR, rc); AssertRC(rc2); diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index 9d94e3081770..e3fd4cb1ad2a 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-x11.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -339,7 +339,7 @@ static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALL { if (!vbclX11TransferStateMatches(pCtx, pTransfer)) { - LogRel2(("Shared Clipboard: Rejecting unbound initialized transfer %RU16/%RU64\n", + LogRelMax(16, ("Shared Clipboard: Rejecting unbound initialized transfer %RU16/%RU64\n", ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); int rc2 = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pTransfer, SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); @@ -538,7 +538,7 @@ static DECLCALLBACK(int) vbclX11OnRequestDataFromSourceCallback(PSHCLCONTEXT pCt * X11 code normally handles a miss without invoking this callback. * Refuse it here as well: never start or wait for a transfer from the * X11 event thread. */ - LogRel2(("Shared Clipboard: X11 URI-list conversion missed its prepared URI-list data cache\n")); + LogRelMax(16, ("Shared Clipboard: X11 URI-list conversion missed its prepared URI-list data cache\n")); rc = VERR_SHCLPB_NO_DATA; } else /* Anything else */ @@ -570,7 +570,7 @@ static DECLCALLBACK(int) vbclX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_ #if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - LogRel2(("Shared Clipboard: X11 guest requires HTTP transfer support for URI-list offers, masking format\n")); + LogRelMax(16, ("Shared Clipboard: X11 guest requires HTTP transfer support for URI-list offers, masking format\n")); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; } #endif diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp index 00bd9ba70772..78cbe31514de 100644 --- a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardDataObjectImpl-win.cpp 115052 2026-08-17 15:35:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardDataObjectImpl-win.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * ClipboardDataObjectImpl-win.cpp - Shared Clipboard IDataObject implementation. */ @@ -895,7 +895,7 @@ int ShClWinDataObject::createUnicodeTextFromTransferRoots(PSHCLTRANSFER pTransfe uint64_t const cRoots = ShClTransferRootsCount(pTransfer); if (!cRoots) { - LogRelMax2(16, ("Shared Clipboard: Cannot provide CF_UNICODETEXT for file transfer because the transfer has no root entries\n")); + LogRelMax(16, ("Shared Clipboard: Cannot provide CF_UNICODETEXT for file transfer because the transfer has no root entries\n")); return VERR_NOT_FOUND; } @@ -911,7 +911,7 @@ int ShClWinDataObject::createUnicodeTextFromTransferRoots(PSHCLTRANSFER pTransfe rc = RTStrCalcUtf16LenEx(pRootEntry->pszName, RTSTR_MAX, &cwcRoot); if (RT_FAILURE(rc)) { - LogRelMax2(16, ("Shared Clipboard: Cannot convert transfer root '%s' to UTF-16 length for CF_UNICODETEXT, rc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Cannot convert transfer root '%s' to UTF-16 length for CF_UNICODETEXT, rc=%Rrc\n", pRootEntry->pszName, rc)); break; } @@ -1021,7 +1021,7 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) if ( !m_fCallbacksEnabled || !m_Callbacks.pfnTransferBegin) { - LogRelMax2(16, ("Shared Clipboard: Cannot start IDataObject transfer because no transfer-begin callback is installed\n")); + LogRelMax(16, ("Shared Clipboard: Cannot start IDataObject transfer because no transfer-begin callback is installed\n")); return VERR_INVALID_POINTER; } if (m_cCallbacks != 0) @@ -1065,13 +1065,13 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) if (RT_FAILURE(rc)) { - LogRelMax2(16, ("Shared Clipboard: Waiting for IDataObject transfer to start failed, rc=%Rrc\n", rc)); + LogRelMax(16, ("Shared Clipboard: Waiting for IDataObject transfer to start failed, rc=%Rrc\n", rc)); return rc; } if (m_enmStatus != Running) { - LogRelMax2(16, ("Shared Clipboard: IDataObject transfer did not enter running state (status=%#x)\n", m_enmStatus)); + LogRelMax(16, ("Shared Clipboard: IDataObject transfer did not enter running state (status=%#x)\n", m_enmStatus)); return VERR_WRONG_ORDER; } } @@ -1111,11 +1111,11 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) else { Release(); - LogRelMax2(16, ("Shared Clipboard: Starting IDataObject transfer read thread failed with %Rrc\n", rc)); + LogRelMax(16, ("Shared Clipboard: Starting IDataObject transfer read thread failed with %Rrc\n", rc)); } } else - LogRelMax2(16, ("Shared Clipboard: Starting IDataObject transfer failed with %Rrc\n", rc)); + LogRelMax(16, ("Shared Clipboard: Starting IDataObject transfer failed with %Rrc\n", rc)); } if ( RT_SUCCESS(rc) @@ -1125,7 +1125,7 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) LogRel2(("Shared Clipboard: Waiting for IDataObject listing to arrive ...\n")); rc = RTSemEventWait(m_EventListComplete, RT_MS_10SEC); if (RT_FAILURE(rc)) - LogRelMax2(16, ("Shared Clipboard: Timed out or failed waiting for IDataObject transfer listing, rc=%Rrc\n", rc)); + LogRelMax(16, ("Shared Clipboard: Timed out or failed waiting for IDataObject transfer listing, rc=%Rrc\n", rc)); } ShClTransferRelease(pTransfer); @@ -1138,7 +1138,7 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) || m_enmStatus != Running || m_lstEntries.empty())) /* Still in running state and with a listing? */ { - LogRelMax2(16, ("Shared Clipboard: IDataObject transfer listing is unavailable after wait (transfer=%p, status=%#x, entries=%zu)\n", + LogRelMax(16, ("Shared Clipboard: IDataObject transfer listing is unavailable after wait (transfer=%p, status=%#x, entries=%zu)\n", m_pTransfer, m_enmStatus, m_lstEntries.size())); rc = VERR_SHCLPB_NO_DATA; } diff --git a/src/VBox/GuestHost/SharedClipboard/VbghWaylandClipboard.cpp b/src/VBox/GuestHost/SharedClipboard/VbghWaylandClipboard.cpp index 37060feb95b9..5ca40889ca2c 100644 --- a/src/VBox/GuestHost/SharedClipboard/VbghWaylandClipboard.cpp +++ b/src/VBox/GuestHost/SharedClipboard/VbghWaylandClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VbghWaylandClipboard.cpp 114766 2026-07-24 18:01:54Z knut.osmundsen@oracle.com $ */ +/* $Id: VbghWaylandClipboard.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Guest / Host common code - Wayland Clipboard. */ @@ -386,14 +386,14 @@ static int vbghWaylandClipboardReadGuestDataIntoCache(PSHCLWAYLANDCTX pThis, int if (RT_SUCCESS(rc)) LogRel5(("Put %zu bytes into cache for %#x (from %s)\n", cbVBoxData, fVBoxFmt, pszMimeType)); else - LogRel2(("Failed to put %zu bytes into cache for %#x (from %s): %Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to put %zu bytes into Wayland cache for %#x (from %s): %Rrc\n", cbVBoxData, fVBoxFmt, pszMimeType, rc)); } else { rc = VERR_VERSION_MISMATCH; RTCritSectLeave(&pThis->CritSect); - LogRel2(("Failed to put %zu bytes into cache for %#x (from %s): version changed\n", + LogRelMax(16, ("Shared Clipboard: Failed to put %zu bytes into Wayland cache for %#x (from %s): version changed\n", cbVBoxData, fVBoxFmt, pszMimeType)); } RTMemFree(pvVBoxData); diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp index 5ea9e059c727..86c8b83e6ff5 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-common.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-common.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common helper objects. */ @@ -534,7 +534,7 @@ int ShClEventWaitEx(PSHCLEVENT pEvent, RTMSINTERVAL uTimeoutMs, int *pRc, PSHCLE } if (RT_FAILURE(rc)) - LogRel2(("Shared Clipboard: Waiting for event %RU32 failed, rc=%Rrc\n", pEvent->idEvent, rc)); + LogRelMax(16, ("Shared Clipboard: Waiting for event %RU32 failed, rc=%Rrc\n", pEvent->idEvent, rc)); LogFlowFuncLeaveRC(rc); return rc; diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp index b435dbdddae1..a630e69a673e 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-http.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-http.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts. */ @@ -452,7 +452,7 @@ DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTP } if (!pSrvTx) - LogRel2(("Shared Clipboard: HTTP URL '%s' not valid\n", pszUrl)); + LogRelMax(16, ("Shared Clipboard: HTTP URL '%s' is not valid\n", pszUrl)); LogFlowFunc(("pszUrl=%s, pSrvTx=%p\n", pszUrl, pSrvTx)); return pSrvTx; @@ -784,7 +784,7 @@ static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData, rc = VERR_NOT_SUPPORTED; } else - LogRel2(("Shared Clipboard: Supplied entry information for '%s' not supported (fInfo=%#x, cbInfo=%RU32\n", + LogRelMax(16, ("Shared Clipboard: Supplied entry information for '%s' is not supported (fInfo=%#x, cbInfo=%RU32)\n", pEntry->pszName, pEntry->fInfo, pEntry->cbInfo)); /* Note: Directories / symlinks or other fancy stuff is not supported here (yet) -- would require using WebDAV. */ if ( RT_FAILURE(rc) diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp index ba80e34740fa..fdbafb301713 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-provider-local.cpp 114961 2026-08-10 15:01:12Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-provider-local.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Transfers interface implementation for local file systems. */ @@ -203,7 +203,7 @@ static int shClTransferLocalPathEnsureNoSymlinks(const char *pszPathAbs) break; if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode)) { - LogRelMax2(16, ("Shared Clipboard: Path component '%s' is a symbolic link\n", szPath)); + LogRelMax(16, ("Shared Clipboard: Path component '%s' is a symbolic link\n", szPath)); rc = VERR_IS_A_SYMLINK; break; } diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index 36dbebb76dfc..ddc54adf874a 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -910,7 +910,7 @@ bool ShClTransferListEntryIsValid(PSHCLLISTENTRY pListEntry) { size_t const cbName = RT_MIN((size_t)pListEntry->cbName, (size_t)SHCLLISTENTRY_MAX_NAME); size_t const cchName = pListEntry->pszName && cbName ? RTStrNLen(pListEntry->pszName, cbName) : 0; - LogRel2(("Shared Clipboard: List entry '%.*s' is invalid\n", + LogRelMax(16, ("Shared Clipboard: List entry '%.*s' is invalid\n", (int)RT_MIN(cchName, (size_t)128), pListEntry->pszName ? pListEntry->pszName : "")); } @@ -4747,8 +4747,12 @@ static int shClSvcTransferSendStatusExAsync(PSHCLCLIENT pClient, SHCLTRANSFERID rc = ShClSvcClientWakeup(pClient); if (RT_SUCCESS(rc)) { - LogRel2(("Shared Clipboard: Reported status %s (rc=%Rrc) of transfer %RU16 to guest\n", - ShClTransferStatusToStr(enmSts), rcTransfer, idTransfer)); + if (enmSts == SHCLTRANSFERSTATUS_ERROR) + LogRelMax(16, ("Shared Clipboard: Reported error status %Rrc for transfer %RU16 to guest\n", + rcTransfer, idTransfer)); + else + LogRel2(("Shared Clipboard: Reported status %s (rc=%Rrc) of transfer %RU16 to guest\n", + ShClTransferStatusToStr(enmSts), rcTransfer, idTransfer)); if (ppEvent) { diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp index fa5ffd92d013..07dc83a10093 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-win.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-win.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Windows-specific functions for clipboard handling. */ @@ -152,7 +152,7 @@ int ShClWinClear(void) return VINF_SUCCESS; int const rc = RTErrConvertFromWin32(dwLastErr); - LogRel2(("Shared Clipboard: Clearing Windows clipboard failed with %Rrc (0x%x)\n", rc, dwLastErr)); + LogRelMax(16, ("Shared Clipboard: Clearing Windows clipboard failed with %Rrc (0x%x)\n", rc, dwLastErr)); return rc; } @@ -488,7 +488,7 @@ SHCLFORMAT ShClWinClipboardFormatToVBox(UINT uFormat) || (RTStrCmp(szFormatName, "FileGroupDescriptorW") == 0) || (RTStrCmp(szFormatName, CFSTR_FILECONTENTS) == 0)) # endif - LogRel2(("Shared Clipboard: Windows virtual-file clipboard format '%s' is not supported as a host file-transfer source yet\n", + LogRelMax(16, ("Shared Clipboard: Windows virtual-file clipboard format '%s' is not supported as a host file-transfer source yet\n", szFormatName)); #endif } @@ -690,7 +690,7 @@ int ShClWinConvertMIMEToCFHTML(const char *pszSource, size_t cb, char **ppszOutp { /* likely */ } else { - LogRel2(("Shared Clipboard: Error: Invalid source fragment.for HTML MIME data, rc=%Rrc\n", rc)); + LogRelMax(16, ("Shared Clipboard: Invalid source fragment for HTML MIME data, rc=%Rrc\n", rc)); return rc; } size_t const cchFragment = strlen(pszSource); /* Unfortunately the validator doesn't return the length. */ diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp index f273e1e6d608..384d0f306021 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -759,7 +759,7 @@ SHCL_X11_DECL(SHCLX11FMTIDX) clipGetURIListFormatFromTargets(PSHCLX11CTX pCtx, LogRelMax2(16, ("Shared Clipboard: Selected X11 URI-list target '%s' for host file transfer\n", g_aFormats[idxFmtURI].pcszAtom)); else if (fSawUnsupportedTransferMetadata) - LogRelMax2(16, ("Shared Clipboard: X11 clipboard had file-transfer metadata but no supported file list target " + LogRelMax(16, ("Shared Clipboard: X11 clipboard had file-transfer metadata but no supported file list target " "(for example text/uri-list); host file transfer will not be announced\n")); return idxFmtURI; @@ -2428,7 +2428,7 @@ int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppsz int rc = RTStrValidateEncodingEx((char *)pvData, cbData, 0 /* fFlags */); if (RT_FAILURE(rc)) { - LogRelMax2(16, ("Shared Clipboard: X11 URI-list clipboard data is not valid UTF-8 (%zu bytes), rc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: X11 URI-list clipboard data is not valid UTF-8 (%zu bytes), rc=%Rrc\n", cbData, rc)); return rc; } @@ -2460,7 +2460,7 @@ int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppsz if ( pchCur < pchEnd && *pchCur == '\0') { - LogRelMax2(16, ("Shared Clipboard: X11 URI-list clipboard data contains an embedded NUL byte; refusing file transfer\n")); + LogRelMax(16, ("Shared Clipboard: X11 URI-list clipboard data contains an embedded NUL byte; refusing file transfer\n")); rc = VERR_INVALID_PARAMETER; break; } @@ -2517,7 +2517,7 @@ int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppsz if (!cEntries) { - LogRelMax2(16, ("Shared Clipboard: X11 URI-list clipboard data contained no file entries; refusing file transfer\n")); + LogRelMax(16, ("Shared Clipboard: X11 URI-list clipboard data contained no file entries; refusing file transfer\n")); return VERR_SHCLPB_NO_DATA; } @@ -2719,7 +2719,7 @@ SHCL_X11_DECL(void) clipConvertDataFromX11Worker(void *pClient, void *pvSrc, uns { const char *pszTarget = pReq->Read.idxFmtX11 < RT_ELEMENTS(g_aFormats) ? g_aFormats[pReq->Read.idxFmtX11].pcszAtom : ""; - LogRelMax2(16, ("Shared Clipboard: Refusing to parse X11 clipboard target '%s' as a file list\n", + LogRelMax(16, ("Shared Clipboard: Refusing to parse X11 clipboard target '%s' as a file list\n", pszTarget)); rc = VERR_NOT_SUPPORTED; } @@ -2769,7 +2769,10 @@ SHCL_X11_DECL(void) clipConvertDataFromX11Worker(void *pClient, void *pvSrc, uns pPayload = NULL; } - LogRel2(("Shared Clipboard: Converting X11 clipboard data completed with %Rrc\n", rc)); + if (RT_FAILURE(rc)) + LogRelMax(16, ("Shared Clipboard: Converting X11 clipboard data failed with %Rrc\n", rc)); + else + LogRel2(("Shared Clipboard: Converting X11 clipboard data completed with %Rrc\n", rc)); RTMemFree(pReq); RTMemFree(pvDst); @@ -2979,7 +2982,10 @@ static void ShClX11ReadDataFromX11Worker(void *pvUserData, void * /* interval */ RTMemFree(pReq); } - LogRel2(("Shared Clipboard: Reading X11 clipboard data completed with %Rrc\n", rc)); + if (RT_FAILURE(rc)) + LogRelMax(16, ("Shared Clipboard: Reading X11 clipboard data failed with %Rrc\n", rc)); + else + LogRel2(("Shared Clipboard: Reading X11 clipboard data completed with %Rrc\n", rc)); LogFlowFuncLeaveRC(rc); } diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp index c8d9e5ad996b..bc3c647eedfb 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-client.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-client.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Client/session and message queue handling. */ @@ -762,7 +762,7 @@ DECLCALLBACK(void) shClSvcClientCall(void *, #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS rc = ShClSvcTransferMsgClientHandler(pClient, callHandle, u32Function, cParms, paParms, tsArrival); #else - LogRel2(("Shared Clipboard: Unknown guest function: %u (%#x)\n", u32Function, u32Function)); + LogRelMax(16, ("Shared Clipboard: Unknown guest function: %u (%#x)\n", u32Function, u32Function)); rc = VERR_NOT_IMPLEMENTED; #endif break; @@ -1085,6 +1085,8 @@ int shClSvcClientMsgGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t /* * Validate the request. */ + ASSERT_GUEST_MSG_RETURN(cParms >= 2, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT); + uint32_t const idMsgExpected = cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT ? paParms[0].u.uint32 : cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT ? paParms[0].u.uint64 : UINT32_MAX; @@ -1290,6 +1292,7 @@ int shClSvcClientMsgReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCM } ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); uint32_t fFormats = paParms[iParm].u.uint32; + ASSERT_GUEST_RETURN(ShClFormatsAreValid(fFormats), VERR_INVALID_FLAGS); iParm++; if (cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B) { @@ -1407,7 +1410,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA if (!ShClFormatIsValid(uFormat)) { - LogRelMax2(16, ("Shared Clipboard: Rejecting host clipboard data request with invalid format %#x\n", uFormat)); + LogRelMax(16, ("Shared Clipboard: Rejecting host clipboard data request with invalid format %#x\n", uFormat)); return VERR_INVALID_PARAMETER; } @@ -1418,7 +1421,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA if (uFormat == VBOX_SHCL_FMT_URI_LIST) #endif { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest file transfer data request without enabled and negotiated transfers\n")); + LogRelMax(16, ("Shared Clipboard: Rejecting guest file transfer data request without enabled and negotiated transfers\n")); return VERR_ACCESS_DENIED; } @@ -1451,8 +1454,12 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA /* Read data from Main, which selects the remote or local host provider. */ int rc = shClSvcExtReadData(pClient, uFormat, pvData, cbData, &cbActual); - LogRel2(("Shared Clipboard: Read extension clipboard data (max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", - cbData, cbActual, rc)); + if (RT_FAILURE(rc)) + LogRelMax(16, ("Shared Clipboard: Reading extension clipboard data (max %RU32 bytes) failed after %RU32 bytes: %Rrc\n", + cbData, cbActual, rc)); + else + LogRel2(("Shared Clipboard: Read extension clipboard data (max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", + cbData, cbActual, rc)); if (RT_SUCCESS(rc)) { @@ -1542,7 +1549,7 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP iParm++; if (!ShClFormatIsValid(uFormat)) { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); return VERR_INVALID_PARAMETER; } if (cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B) diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp index 043e31f31941..e458ac09a335 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal code for transfer (list) handling. */ @@ -271,6 +271,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU32(&aParms[idxParm], &pReply->u.TransferStatus.uStatus); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("uTransferStatus=%RU32 (%s)\n", pReply->u.TransferStatus.uStatus, ShClTransferStatusToStr(pReply->u.TransferStatus.uStatus))); @@ -281,6 +283,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ListOpen.uHandle); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("hListOpen=%RU64\n", pReply->u.ListOpen.uHandle)); break; @@ -290,6 +294,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ListClose.uHandle); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("hListClose=%RU64\n", pReply->u.ListClose.uHandle)); break; @@ -299,6 +305,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ObjOpen.uHandle); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("hObjOpen=%RU64\n", pReply->u.ObjOpen.uHandle)); break; @@ -308,6 +316,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ObjClose.uHandle); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("hObjClose=%RU64\n", pReply->u.ObjClose.uHandle)); break; @@ -641,11 +651,12 @@ static int shClSvcTransferGetObjDataChunk(uint32_t cParms, VBOXHGCMSVCPARM aParm * @param pTransfer Transfer to handle reply for. * @param cParms Number of function parameters supplied. * @param aParms Array function parameters supplied. + * @param fZeroContext Whether the guest supplied the special zero context ID. * @param pfDestroyTransfer Where to return whether the caller must destroy * the retained transfer after releasing it. */ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, uint32_t cParms, - VBOXHGCMSVCPARM aParms[], bool *pfDestroyTransfer) + VBOXHGCMSVCPARM aParms[], bool fZeroContext, bool *pfDestroyTransfer) { AssertPtrReturn(pfDestroyTransfer, VERR_INVALID_POINTER); *pfDestroyTransfer = false; @@ -656,7 +667,7 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra bool fReleaseCreatedTransfer = false; uint32_t cbReply = sizeof(SHCLREPLY); - PSHCLREPLY pReply = (PSHCLREPLY)RTMemAlloc(cbReply); + PSHCLREPLY pReply = (PSHCLREPLY)RTMemAllocZ(cbReply); if (pReply) { rc = shClSvcTransferMsgGetReply(cParms, aParms, pReply); @@ -666,12 +677,14 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra && pReply->u.TransferStatus.uStatus == SHCLTRANSFERSTATUS_REQUESTED) { /* SHCLTRANSFERSTATUS_REQUESTED is special, as it doesn't provide a transfer. */ + if (!fZeroContext) + rc = VERR_INVALID_CONTEXT; } else /* Everything else needs a valid transfer ID. */ { if (!pTransfer) { - LogRelMax2(16, ("Shared Clipboard: Guest reply did not specify a valid transfer context (reply type=%RU32)\n", + LogRelMax(16, ("Shared Clipboard: Guest reply did not specify a valid transfer context (reply type=%RU32)\n", pReply->uType)); rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; } @@ -748,7 +761,7 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra } else { - LogRelMax2(16, ("Shared Clipboard: Guest requested host -> guest transfer, but clipboard mode %RU32 does not allow it\n", + LogRelMax(16, ("Shared Clipboard: Guest requested host -> guest transfer, but clipboard mode %RU32 does not allow it\n", uMode)); rc = VERR_INVALID_PARAMETER; } @@ -965,7 +978,7 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (!(fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS)) { - LogRelMax2(16, ("Shared Clipboard: Guest attempted file transfer message %s without negotiated transfer support (features0=%#RX64)\n", + LogRelMax(16, ("Shared Clipboard: Guest attempted file transfer message %s without negotiated transfer support (features0=%#RX64)\n", ShClSvcGuestMsgToStr(u32Function), fGuestFeatures0)); return VERR_ACCESS_DENIED; } @@ -973,7 +986,7 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, uint32_t const fTransferMode = shClSvcTransferModeGet(); if (!(fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) { - LogRelMax2(16, ("Shared Clipboard: Guest attempted file transfer message %s, but file transfers are disabled for this VM (transfer mode=%#x)\n", + LogRelMax(16, ("Shared Clipboard: Guest attempted file transfer message %s, but file transfers are disabled for this VM (transfer mode=%#x)\n", ShClSvcGuestMsgToStr(u32Function), fTransferMode)); return VERR_ACCESS_DENIED; } @@ -982,7 +995,7 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, uint32_t const uMode = ShClSvcGetMode(); if (!shClSvcTransferMsgIsAllowed(uMode, u32Function)) { - LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s is not allowed in clipboard mode %RU32\n", + LogRelMax(16, ("Shared Clipboard: Guest file transfer message %s is not allowed in clipboard mode %RU32\n", ShClSvcGuestMsgToStr(u32Function), uMode)); return VERR_ACCESS_DENIED; } @@ -1005,14 +1018,14 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, * SHCLTRANSFERSTATUS_REQUESTED without an existing transfer context. */ if (u32Function != VBOX_SHCL_GUEST_FN_REPLY) { - LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s used zero context ID; only transfer status replies may do this\n", + LogRelMax(16, ("Shared Clipboard: Guest file transfer message %s used zero context ID; only transfer status replies may do this\n", ShClSvcGuestMsgToStr(u32Function))); return VERR_INVALID_CONTEXT; } } else if (VBOX_SHCL_CONTEXTID_GET_SESSION(uCID) != pClient->State.uSessionID) { - LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s used context %#RX64 for session %RU32, expected session %RU32\n", + LogRelMax(16, ("Shared Clipboard: Guest file transfer message %s used context %#RX64 for session %RU32, expected session %RU32\n", ShClSvcGuestMsgToStr(u32Function), uCID, VBOX_SHCL_CONTEXTID_GET_SESSION(uCID), pClient->State.uSessionID)); return VERR_INVALID_CONTEXT; } @@ -1028,7 +1041,7 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if ( u32Function != VBOX_SHCL_GUEST_FN_REPLY && !pTransfer) { - LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s references unknown transfer %RU16 (context=%#RX64)\n", + LogRelMax(16, ("Shared Clipboard: Guest file transfer message %s references unknown transfer %RU16 (context=%#RX64)\n", ShClSvcGuestMsgToStr(u32Function), idTransfer, uCID)); return VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; } @@ -1040,7 +1053,7 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, { case VBOX_SHCL_GUEST_FN_REPLY: { - rc = shClSvcTransferMsgHandleReply(pClient, pTransfer, cParms, aParms, &fDestroyTransfer); + rc = shClSvcTransferMsgHandleReply(pClient, pTransfer, cParms, aParms, fZeroContext, &fDestroyTransfer); break; } diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp index 1d6ad12ea28d..e387efe89b90 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transport.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transport.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Opaque Main transport implementation. */ @@ -104,7 +104,7 @@ static DECLCALLBACK(int) shClSvcOpRetainGuestDataEvent(SHCLCLIENTHANDLE hClient, if (!ShClFormatIsValid(uFormat)) { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); return VERR_INVALID_PARAMETER; } @@ -114,14 +114,14 @@ static DECLCALLBACK(int) shClSvcOpRetainGuestDataEvent(SHCLCLIENTHANDLE hClient, if ( idEvent == 0 || idEvent == NIL_SHCLEVENTID) { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid event %#x in context ID %#RX64\n", + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid event %#x in context ID %#RX64\n", idEvent, uContextId)); return VERR_WRONG_ORDER; } if ( idSession != pClient->State.uSessionID || idEventSource != pClient->EventSrc.uID) { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with mismatching context ID %#RX64" + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data with mismatching context ID %#RX64" " (session %#x/%#x, event source %#x/%#x)\n", uContextId, idSession, pClient->State.uSessionID, idEventSource, pClient->EventSrc.uID)); @@ -131,12 +131,12 @@ static DECLCALLBACK(int) shClSvcOpRetainGuestDataEvent(SHCLCLIENTHANDLE hClient, PSHCLEVENT pEvent = ShClEventSourceRetainFromId(&pClient->EventSrc, idEvent); if (!RT_VALID_PTR(pEvent)) { - LogRelMax2(16, ("Shared Clipboard: Ignoring late guest clipboard data for expired event %#x\n", idEvent)); + LogRelMax(16, ("Shared Clipboard: Ignoring late guest clipboard data for expired event %#x\n", idEvent)); return VINF_SUCCESS; } if (pEvent->uUser != uFormat) { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data format %#x for event %#x, expected %#x\n", + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data format %#x for event %#x, expected %#x\n", uFormat, idEvent, pEvent->uUser)); ShClEventRelease(pEvent); return VERR_INVALID_CONTEXT; @@ -152,6 +152,7 @@ static DECLCALLBACK(int) shClSvcOpRetainGuestDataEvent(SHCLCLIENTHANDLE hClient, * Signals a retained guest-data event with clipboard data received from the guest. * * @returns VBox status code. + * @param hClient Opaque handle of the connected client. * @param pEvent Retained event to signal. * @param idEvent Event ID to use for the optional payload wrapper. * @param pvData Pointer to clipboard data received. This can be @@ -297,13 +298,13 @@ static DECLCALLBACK(int) shClSvcOpReadDataFromGuestAsync(SHCLCLIENTHANDLE hClien if ( fFormats == VBOX_SHCL_FMT_NONE || (fFormats & ~fSupportedFormats)) { - LogRelMax2(16, ("Shared Clipboard: Rejecting unsupported guest clipboard data request formats %#x\n", fFormats)); + LogRelMax(16, ("Shared Clipboard: Rejecting unsupported guest clipboard data request formats %#x\n", fFormats)); return VERR_NOT_SUPPORTED; } if ( ppEvent && (fFormats & (fFormats - 1)) != 0) { - LogRelMax2(16, ("Shared Clipboard: Rejecting multi-format guest clipboard data request %#x with single event output\n", + LogRelMax(16, ("Shared Clipboard: Rejecting multi-format guest clipboard data request %#x with single event output\n", fFormats)); return VERR_INVALID_PARAMETER; } @@ -311,7 +312,7 @@ static DECLCALLBACK(int) shClSvcOpReadDataFromGuestAsync(SHCLCLIENTHANDLE hClien if ( (fFormats & VBOX_SHCL_FMT_URI_LIST) && !shClSvcClientTransfersAreAllowed(pClient)) { - LogRelMax2(16, ("Shared Clipboard: Rejecting host URI-list request without enabled and negotiated transfers\n")); + LogRelMax(16, ("Shared Clipboard: Rejecting host URI-list request without enabled and negotiated transfers\n")); return VERR_ACCESS_DENIED; } #endif diff --git a/src/VBox/Main/src-client/ClipboardImpl.cpp b/src/VBox/Main/src-client/ClipboardImpl.cpp index af03883f6595..cea6fc50fbba 100644 --- a/src/VBox/Main/src-client/ClipboardImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardImpl.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardImpl.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Console clipboard API. */ @@ -1924,7 +1924,7 @@ HRESULT Clipboard::i_readDataForFormat(ClipboardAction_T aAction, { LogFunc(("Guest clipboard payload too large: format=%#x, cb=%RU32, max=%RU32\n", uFormat, cbData, s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Guest clipboard data is too large: format %#x, %RU32 bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Guest clipboard data is too large: format %#x, %RU32 bytes (limit %RU32 bytes)\n", uFormat, cbData, s_cbClipboardReadMax)); RTMemFree(pvData); return mData->mParent->setErrorBoth(VBOX_E_SHCL_TOO_MUCH_DATA, VERR_TOO_MUCH_DATA, @@ -1936,7 +1936,7 @@ HRESULT Clipboard::i_readDataForFormat(ClipboardAction_T aAction, if (RT_FAILURE(vrc2)) { LogFunc(("Converting guest clipboard data failed: format=%#x, cb=%RU32, vrc=%Rrc\n", uFormat, cbData, vrc2)); - LogRelMax2(16, ("Shared Clipboard: Failed to convert guest clipboard data: format %#x, %RU32 bytes, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to convert guest clipboard data: format %#x, %RU32 bytes, vrc=%Rrc\n", uFormat, cbData, vrc2)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_GUEST_ERROR, vrc2, Console::tr("Converting shared clipboard data failed with %Rrc"), vrc2); @@ -1950,13 +1950,13 @@ HRESULT Clipboard::i_readDataForFormat(ClipboardAction_T aAction, if (vrc == VERR_NOT_AVAILABLE) { LogFunc(("No guest clipboard client connected for read: format=%#x\n", uFormat)); - LogRelMax2(16, ("Shared Clipboard: Cannot read guest clipboard data, no guest clipboard client is connected (format %#x)\n", + LogRelMax(16, ("Shared Clipboard: Cannot read guest clipboard data, no guest clipboard client is connected (format %#x)\n", uFormat)); return mData->mParent->setError(VBOX_E_SHCL_NO_DATA, Console::tr("No guest clipboard client is currently connected")); } LogFunc(("Reading guest clipboard data failed: format=%#x, vrc=%Rrc\n", uFormat, vrc)); - LogRelMax2(16, ("Shared Clipboard: Reading guest clipboard data failed: format %#x, vrc=%Rrc\n", uFormat, vrc)); + LogRelMax(16, ("Shared Clipboard: Reading guest clipboard data failed: format %#x, vrc=%Rrc\n", uFormat, vrc)); aBuffer.clear(); return mData->mParent->setErrorBoth(VBOX_E_SHCL_GUEST_ERROR, vrc, Console::tr("Reading shared clipboard data failed with %Rrc"), vrc); @@ -2428,7 +2428,7 @@ HRESULT Clipboard::i_writeData(VBOXSHCLMAINCLIENTID aClientId, { LogFunc(("Rejecting oversized clipboard write: mime=%s, cb=%zu, max=%RU32\n", aMimeType.c_str(), aBuffer.size(), s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Refusing to write too much clipboard data: MIME '%s', %zu bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Refusing to write too much clipboard data: MIME '%s', %zu bytes (limit %RU32 bytes)\n", aMimeType.c_str(), aBuffer.size(), s_cbClipboardReadMax)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_TOO_MUCH_DATA, VERR_TOO_MUCH_DATA, Console::tr("Writing shared clipboard data exceeded the supported size (%RU32 bytes)"), @@ -2449,7 +2449,7 @@ HRESULT Clipboard::i_writeData(VBOXSHCLMAINCLIENTID aClientId, { LogFunc(("Converting Main clipboard data failed: format=%#x, mime=%s, cb=%zu, vrc=%Rrc\n", uFormat, aMimeType.c_str(), aBuffer.size(), vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to convert clipboard data for guest: MIME '%s', format %#x, %zu bytes, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to convert clipboard data for guest: MIME '%s', format %#x, %zu bytes, vrc=%Rrc\n", aMimeType.c_str(), uFormat, aBuffer.size(), vrc)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_ERROR, vrc, Console::tr("Converting shared clipboard data failed with %Rrc"), vrc); @@ -2460,7 +2460,7 @@ HRESULT Clipboard::i_writeData(VBOXSHCLMAINCLIENTID aClientId, { LogFunc(("Converted clipboard write is too large: format=%#x, cb=%zu, max=%RU32\n", uFormat, abProtocolBuffer.size(), s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Converted clipboard data is too large for the guest: format %#x, %zu bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Converted clipboard data is too large for the guest: format %#x, %zu bytes (limit %RU32 bytes)\n", uFormat, abProtocolBuffer.size(), s_cbClipboardReadMax)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_TOO_MUCH_DATA, VERR_TOO_MUCH_DATA, Console::tr("Writing shared clipboard data exceeded the supported size (%RU32 bytes)"), @@ -2501,7 +2501,7 @@ HRESULT Clipboard::i_writeData(VBOXSHCLMAINCLIENTID aClientId, if (RT_FAILURE(vrc)) { LogFunc(("Reporting write format to guest failed: format=%#x, vrc=%Rrc\n", uFormat, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to report clipboard format %#x to the guest, vrc=%Rrc\n", uFormat, vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to report clipboard format %#x to the guest, vrc=%Rrc\n", uFormat, vrc)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_GUEST_ERROR, vrc, Console::tr("Writing shared clipboard data failed with %Rrc"), vrc); } @@ -2579,7 +2579,7 @@ HRESULT Clipboard::i_writeFormats(VBOXSHCLMAINCLIENTID aClientId, if (RT_FAILURE(vrc)) { LogFunc(("Reporting formats to guest failed: fFormats=%#x, vrc=%Rrc\n", fFormats, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to report clipboard formats %#x to the guest, vrc=%Rrc\n", fFormats, vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to report clipboard formats %#x to the guest, vrc=%Rrc\n", fFormats, vrc)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_GUEST_ERROR, vrc, Console::tr("Writing shared clipboard formats failed with %Rrc"), vrc); } @@ -2717,7 +2717,7 @@ HRESULT Clipboard::i_hostClipboardReportFormats(VBOXSHCLMAINCLIENTID aClientId, if (RT_FAILURE(vrc)) { LogFunc(("Reporting formats to native host clipboard failed: fFormats=%#x, vrc=%Rrc\n", fFormats, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to report clipboard formats %#x to the native host clipboard, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to report clipboard formats %#x to the native host clipboard, vrc=%Rrc\n", fFormats, vrc)); return setErrorBoth(VBOX_E_SHCL_ERROR, vrc, tr("Reporting shared clipboard formats to the host failed with %Rrc"), vrc); @@ -3045,7 +3045,7 @@ HRESULT Clipboard::i_hostClipboardSetData(VBOXSHCLMAINCLIENTID aClientId, if (RT_FAILURE(vrc)) { LogFunc(("Reporting setData format to native host clipboard failed: format=%#x, vrc=%Rrc\n", uFormat, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to report clipboard format %#x to the native host clipboard, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to report clipboard format %#x to the native host clipboard, vrc=%Rrc\n", uFormat, vrc)); return setErrorBoth(VBOX_E_SHCL_ERROR, vrc, tr("Reporting shared clipboard formats to the host failed with %Rrc"), vrc); @@ -3057,7 +3057,7 @@ HRESULT Clipboard::i_hostClipboardSetData(VBOXSHCLMAINCLIENTID aClientId, { LogFunc(("Writing setData payload to native host clipboard failed: format=%#x, cb=%zu, vrc=%Rrc\n", uFormat, abProtocolBuffer.size(), vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to write clipboard data to the native host clipboard, format %#x, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to write clipboard data to the native host clipboard, format %#x, vrc=%Rrc\n", uFormat, vrc)); return setErrorBoth(VBOX_E_SHCL_ERROR, vrc, tr("Writing shared clipboard data to the host failed with %Rrc"), vrc); @@ -3117,7 +3117,7 @@ HRESULT Clipboard::i_hostClipboardClear(VBOXSHCLMAINCLIENTID aClientId) if (RT_FAILURE(vrc)) { LogFunc(("Clearing native host clipboard failed: vrc=%Rrc\n", vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to clear the native host clipboard, vrc=%Rrc\n", vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to clear the native host clipboard, vrc=%Rrc\n", vrc)); return setErrorBoth(VBOX_E_SHCL_ERROR, vrc, tr("Clearing the shared clipboard on the host failed with %Rrc"), vrc); } @@ -3150,7 +3150,7 @@ HRESULT Clipboard::i_reset() if (RT_FAILURE(vrc)) { LogFunc(("Reset HGCM host call failed: vrc=%Rrc\n", vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to reset service state, vrc=%Rrc\n", vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to reset service state, vrc=%Rrc\n", vrc)); return mData->mParent->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, Console::tr("Resetting shared clipboard state failed with %Rrc"), vrc); } @@ -3227,7 +3227,7 @@ HRESULT Clipboard::i_transferCancel(SHCLSESSIONID aServiceSessionId, SHCLTRANSFE { LogFunc(("Cancel transfer HGCM host call failed: session=%RU16, id=%RU16, generation=%RU64, vrc=%Rrc\n", aServiceSessionId, aTransferId, aGeneration, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to cancel transfer %RU16, vrc=%Rrc\n", aTransferId, vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to cancel transfer %RU16, vrc=%Rrc\n", aTransferId, vrc)); return mData->mParent->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, Console::tr("Canceling shared clipboard transfer failed with %Rrc"), vrc); } @@ -3295,7 +3295,7 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c if (!pszMimeType) { LogFunc(("Service requested unsupported format: uFormat=%#x\n", uFormat)); - LogRelMax2(16, ("Shared Clipboard: Service requested unsupported clipboard format %#x\n", uFormat)); + LogRelMax(16, ("Shared Clipboard: Service requested unsupported clipboard format %#x\n", uFormat)); return E_INVALIDARG; } @@ -3461,7 +3461,7 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c { LogFunc(("Converting cached data for service request failed: format=%#x, cbMain=%zu, vrc=%Rrc\n", uFormat, abMainBuffer.size(), vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to convert cached clipboard data for service request: format %#x, %zu bytes, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to convert cached clipboard data for service request: format %#x, %zu bytes, vrc=%Rrc\n", uFormat, abMainBuffer.size(), vrc)); return E_FAIL; } @@ -3470,7 +3470,7 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c if (cbActual > UINT32_MAX) { LogFunc(("Cached data for service request is too large: format=%#x, cb=%zu\n", uFormat, cbActual)); - LogRelMax2(16, ("Shared Clipboard: Cached clipboard data is too large for service request: format %#x, %zu bytes\n", + LogRelMax(16, ("Shared Clipboard: Cached clipboard data is too large for service request: format %#x, %zu bytes\n", uFormat, cbActual)); return E_FAIL; } @@ -3635,7 +3635,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo if (cbData && !pvData) { LogFunc(("Reported data has invalid pointer: fFormat=%#x, cbData=%RU32\n", fFormat, cbData)); - LogRelMax2(16, ("Shared Clipboard: Service reported clipboard data without a buffer: formats %#x, %RU32 bytes\n", + LogRelMax(16, ("Shared Clipboard: Service reported clipboard data without a buffer: formats %#x, %RU32 bytes\n", fFormat, cbData)); return E_POINTER; } @@ -3643,7 +3643,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo { LogFunc(("Reported data is too large: fFormat=%#x, cbData=%RU32, max=%RU32\n", fFormat, cbData, s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Service reported too much clipboard data: formats %#x, %RU32 bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Service reported too much clipboard data: formats %#x, %RU32 bytes (limit %RU32 bytes)\n", fFormat, cbData, s_cbClipboardReadMax)); return E_FAIL; } @@ -3652,7 +3652,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo if (uFormat == VBOX_SHCL_FMT_NONE) { LogFunc(("Reported data has no supported format: fFormat=%#x\n", fFormat)); - LogRelMax2(16, ("Shared Clipboard: Service reported unsupported clipboard formats %#x\n", fFormat)); + LogRelMax(16, ("Shared Clipboard: Service reported unsupported clipboard formats %#x\n", fFormat)); return E_INVALIDARG; } @@ -3664,7 +3664,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo if (RT_FAILURE(vrc)) { LogFunc(("Converting reported data failed: uFormat=%#x, cbData=%RU32, vrc=%Rrc\n", uFormat, cbData, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to convert reported clipboard data: format %#x, %RU32 bytes, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to convert reported clipboard data: format %#x, %RU32 bytes, vrc=%Rrc\n", uFormat, cbData, vrc)); return E_FAIL; } @@ -3672,7 +3672,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo { LogFunc(("Converted reported data is too large: uFormat=%#x, cb=%zu, max=%RU32\n", uFormat, abBuffer.size(), s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Converted reported clipboard data is too large: format %#x, %zu bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Converted reported clipboard data is too large: format %#x, %zu bytes (limit %RU32 bytes)\n", uFormat, abBuffer.size(), s_cbClipboardReadMax)); return E_FAIL; } diff --git a/src/VBox/Main/src-client/GuestShClSvcExt.cpp b/src/VBox/Main/src-client/GuestShClSvcExt.cpp index dfccc7967268..e69127fc2b68 100644 --- a/src/VBox/Main/src-client/GuestShClSvcExt.cpp +++ b/src/VBox/Main/src-client/GuestShClSvcExt.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClSvcExt.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClSvcExt.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard service extension handling for Main. */ @@ -63,7 +63,7 @@ static int shClSvcExtValidateFormat(SHCLFORMAT uFormat, uint32_t u32Function) { if (ShClFormatIsValid(uFormat)) return VINF_SUCCESS; - LogRelMax2(16, ("Shared Clipboard: Rejecting service-extension function %RU32 with invalid format %#x\n", + LogRelMax(16, ("Shared Clipboard: Rejecting service-extension function %RU32 with invalid format %#x\n", u32Function, uFormat)); return VERR_INVALID_PARAMETER; } @@ -372,7 +372,7 @@ int GuestShCl::i_svcExtDataReadCallback(PSHCLEXTPARMS pParms) LogRel2(("Shared Clipboard: Read Main clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, pParms->u.ReadWriteData.cbActual)); else - LogRel2(("Shared Clipboard: No Main clipboard data available, vrc=%Rrc\n", vrc)); + LogRelMax(16, ("Shared Clipboard: No Main clipboard data available, vrc=%Rrc\n", vrc)); return vrc; } @@ -400,7 +400,7 @@ int GuestShCl::i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms) { int const vrcVrde = pVrde->ClipboardWriteGuestData(fFormats, pvData, cbData); if (RT_FAILURE(vrcVrde) && vrcVrde != VERR_NOT_SUPPORTED) - LogRelMax2(16, ("Shared Clipboard: Mirroring guest clipboard data to VRDE failed with %Rrc\n", vrcVrde)); + LogRelMax(16, ("Shared Clipboard: Mirroring guest clipboard data to VRDE failed with %Rrc\n", vrcVrde)); } vrc = m_pConn->guestDataComplete(hToken, pvData, cbData); diff --git a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp index e675aa133671..c79dcb755087 100644 --- a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp +++ b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendDarwin.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendDarwin.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host. */ @@ -274,7 +274,9 @@ static void shClBackendDarwinDestroy(void) if (g_ctx.hThread != NIL_RTTHREAD) { int vrc = RTThreadUserSignal(g_ctx.hThread); - AssertRC(vrc); + if (RT_FAILURE(vrc)) + LogRelMax(16, ("Shared Clipboard: Waking the Darwin clipboard poller during shutdown failed with %Rrc;" + " waiting for its polling interval to expire\n", vrc)); vrc = RTThreadWait(g_ctx.hThread, RT_INDEFINITE_WAIT, NULL); AssertFatalMsgRC(vrc, ("Reaping the Darwin clipboard poller failed with %Rrc\n", vrc)); g_ctx.hThread = NIL_RTTHREAD; @@ -429,7 +431,7 @@ static int shClBackendDarwinReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormat #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - LogRel2(("Shared Clipboard: Darwin backend does not support guest-to-host file-transfer offers yet\n")); + LogRelMax(16, ("Shared Clipboard: Darwin backend does not support guest-to-host file-transfer offers yet\n")); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; if (fFormats == VBOX_SHCL_FMT_NONE) { diff --git a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp index a9769ceff421..647ae0aa71cd 100644 --- a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp +++ b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendX11.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendX11.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - X11 backend. */ @@ -542,7 +542,7 @@ static int shClBackendX11ReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) #if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - LogRelMax2(16, ("Shared Clipboard: X11 backend cannot expose guest URI-list data because HTTP transfer support is not built in; masking format %#x\n", + LogRelMax(16, ("Shared Clipboard: X11 backend cannot expose guest URI-list data because HTTP transfer support is not built in; masking format %#x\n", VBOX_SHCL_FMT_URI_LIST)); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; } @@ -595,7 +595,7 @@ static int shClBackendX11ReportLocalFormats(GuestShClConn *pConn, SHCLFORMATS fF #if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - LogRelMax2(16, ("Shared Clipboard: X11 backend cannot announce host URI-list data because HTTP transfer support is not built in; masking format %#x\n", + LogRelMax(16, ("Shared Clipboard: X11 backend cannot announce host URI-list data because HTTP transfer support is not built in; masking format %#x\n", VBOX_SHCL_FMT_URI_LIST)); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; } @@ -1126,7 +1126,7 @@ static DECLCALLBACK(int) shClSvcX11RequestDataFromSourceCallback(PSHCLCONTEXT pC { /* URI targets are advertised cache-only after the worker prepared the * exact transfer. Never fall back to guest I/O on the X11 thread. */ - LogRel2(("Shared Clipboard: Host X11 URI-list conversion missed its prepared URI-list data cache\n")); + LogRelMax(16, ("Shared Clipboard: Host X11 URI-list conversion missed its prepared URI-list data cache\n")); return VERR_SHCLPB_NO_DATA; } #elif defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) @@ -1215,13 +1215,13 @@ static DECLCALLBACK(int) shClSvcX11TransferIfaceHGRootListRead(PSHCLTXPROVIDERCT LogRelMax2(16, ("Shared Clipboard: Host reported %RU64 X11 root entries for transfer to guest\n", ShClTransferRootsCount(pCtx->pTransfer))); else - LogRelMax2(16, ("Shared Clipboard: Converting X11 URI-list clipboard data (%RU32 bytes) to transfer roots failed with %Rrc\n", + LogRelMax(16, ("Shared Clipboard: Converting X11 URI-list clipboard data (%RU32 bytes) to transfer roots failed with %Rrc\n", cbData, vrc)); RTMemFree(pvData); } else - LogRelMax2(16, ("Shared Clipboard: Reading X11 URI-list clipboard data for transfer failed with %Rrc\n", vrc)); + LogRelMax(16, ("Shared Clipboard: Reading X11 URI-list clipboard data for transfer failed with %Rrc\n", vrc)); LogFlowFuncLeaveRC(vrc); return vrc; From 339255da482ea4ebcd1308098dde874251cd2c8c Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 16:44:52 +0000 Subject: [PATCH 149/176] Shared Clipboard: Revamped host service and Main tests to reflect the new architecture better. bugref:4697 svn:sync-xref-src-repo-rev: r174897 --- .../SharedClipboard/testcase/.scm-settings | 7 +- .../SharedClipboard/testcase/Makefile.kmk | 59 +- .../SharedClipboard/testcase/VBoxOrgCfHtml1.h | 165 - .../testcase/VBoxOrgCfHtml1.txt | Bin 1962 -> 0 bytes .../testcase/VBoxOrgMimeHtml1.h | 152 - .../testcase/VBoxOrgMimeHtml1.txt | 1 - .../testcase/tstClipboardDataObjectWin.cpp | 1227 ------ .../testcase/tstClipboardHostService.cpp | 957 ++++ .../testcase/tstClipboardTransfers.cpp | 1280 ------ .../HostServices/testcase/TstHGCMMock.cpp | 127 +- src/VBox/Main/include/ClipboardSessionImpl.h | 12 +- .../Main/src-client/ClipboardSessionImpl.cpp | 34 +- src/VBox/Main/testcase/Makefile.kmk | 137 +- src/VBox/Main/testcase/tstClipboard.cpp | 3868 ----------------- src/VBox/Main/testcase/tstClipboardAPI.cpp | 441 ++ src/VBox/Main/testcase/tstClipboardMain.cpp | 913 ++++ .../testcase/tstClipboardMain2HostSvc.cpp | 759 ++++ 17 files changed, 3269 insertions(+), 6870 deletions(-) delete mode 100644 src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h delete mode 100644 src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt delete mode 100644 src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h delete mode 100644 src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt delete mode 100644 src/VBox/HostServices/SharedClipboard/testcase/tstClipboardDataObjectWin.cpp create mode 100644 src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp delete mode 100644 src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp delete mode 100644 src/VBox/Main/testcase/tstClipboard.cpp create mode 100644 src/VBox/Main/testcase/tstClipboardAPI.cpp create mode 100644 src/VBox/Main/testcase/tstClipboardMain.cpp create mode 100644 src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp diff --git a/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings index 5deb90e352ed..19425caf6213 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings +++ b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings @@ -1,4 +1,4 @@ -# $Id: .scm-settings 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: .scm-settings 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ ## @file # Source code massager settings for the host HGCM services. # @@ -26,8 +26,3 @@ # /*.h: --guard-relative-to-dir . - -# The file ends with a '\0' byte, so we need to treat it as a binary. -# This also makes sure we use CRLF as EOL, so VBoxOrgCfHtml1.h can be recreated -# w/o differencing between unix & windows hosts. -/VBoxOrgCfHtml1.txt: --treat-as binary diff --git a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk index a2c0cd12c609..3e3a7b4eef0e 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk +++ b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk @@ -1,6 +1,6 @@ -# $Id: Makefile.kmk 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ ## @file -# Sub-Makefile for the Shared Clipboard Host Service testcases. +# Sub-Makefile for the Shared Clipboard Host Service testcase. # # @@ -29,40 +29,35 @@ SUB_DEPTH = ../../../../.. include $(KBUILD_PATH)/subheader.kmk if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + PROGRAMS += tstClipboardHostService + tstClipboardHostService_TEMPLATE = VBoxR3TstExe + tstClipboardHostService_DEFS = VBOX_WITH_HGCM VBOX_WITH_SHARED_CLIPBOARD_HOST UNIT_TEST + tstClipboardHostService_SOURCES = \ + ../VBoxSharedClipboardSvc.cpp \ + ../VBoxSharedClipboardSvc-ext.cpp \ + ../VBoxSharedClipboardSvc-client.cpp \ + ../VBoxSharedClipboardSvc-host.cpp \ + ../VBoxSharedClipboardSvc-transport.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ + tstClipboardHostService.cpp + tstClipboardHostService_LIBS = $(LIB_RUNTIME) + tstClipboardHostService_CLEAN = $(tstClipboardHostService_0_OUTDIR)/tstClipboardHostService.run if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - # - # File transfer tests. - # - PROGRAMS += tstClipboardTransfers - tstClipboardTransfers_TEMPLATE = VBoxR3TstExe - tstClipboardTransfers_DEFS = VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - tstClipboardTransfers_SOURCES = \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ + tstClipboardHostService_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardHostService_SOURCES += \ + ../VBoxSharedClipboardSvc-transfers.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp \ - tstClipboardTransfers.cpp + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp + endif - # - # Windows IDataObject tests. - # - PROGRAMS.win += tstClipboardDataObjectWin - tstClipboardDataObjectWin_TEMPLATE = VBoxR3TstExe - tstClipboardDataObjectWin_DEFS += UNICODE VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - tstClipboardDataObjectWin_SOURCES = \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp \ - tstClipboardDataObjectWin.cpp - tstClipboardDataObjectWin_LIBS = $(LIB_RUNTIME) + $$(tstClipboardHostService_0_OUTDIR)/tstClipboardHostService.run: $$(tstClipboardHostService_1_STAGE_TARGET) | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstClipboardHostService_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardHostService_0_OUTDIR)/tstClipboardHostService.run endif endif diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h deleted file mode 100644 index e398bbc34e14..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h +++ /dev/null @@ -1,165 +0,0 @@ -/* $Id: VBoxOrgCfHtml1.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ -/** @file - * Shared Clipboard host service test case C data file of VBoxOrgCfHtml1.txt. - */ - -/* - * Copyright (C) 2022-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#ifndef VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h -#define VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include - -const unsigned char g_abVBoxOrgCfHtml1[] = -{ - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x30, 0x2e, 0x39, 0x0d, 0x0a, 0x53, 0x74, 0x61, /* 0x00000000: Version:0.9..Sta */ - 0x72, 0x74, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x34, /* 0x00000010: rtHTML:000000014 */ - 0x34, 0x0d, 0x0a, 0x45, 0x6e, 0x64, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, /* 0x00000020: 4..EndHTML:00000 */ - 0x30, 0x31, 0x39, 0x36, 0x31, 0x0d, 0x0a, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, /* 0x00000030: 01961..StartFrag */ - 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x38, 0x30, 0x0d, /* 0x00000040: ment:0000000180. */ - 0x0a, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, /* 0x00000050: .EndFragment:000 */ - 0x30, 0x30, 0x30, 0x31, 0x39, 0x32, 0x35, 0x0d, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, /* 0x00000060: 0001925..SourceU */ - 0x52, 0x4c, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, /* 0x00000070: RL:https:..www.v */ - 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x0d, 0x0a, /* 0x00000080: irtualbox.org... */ - 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0d, 0x0a, 0x3c, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x0d, 0x0a, /* 0x00000090: .... */ - 0x3c, 0x21, 0x2d, 0x2d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, /* 0x000000a0: See "<.span>About Vir */ - 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, /* 0x00000520: tualBox<.a>" for an */ - 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, /* 0x00000770: introduction.<. */ - 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x21, 0x2d, 0x2d, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, /* 0x00000780: span>..<.body> */ - 0x0d, 0x0a, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x00, /* 0x000007a0: ..<.html>. */ -}; - -const unsigned g_cbVBoxOrgCfHtml1 = sizeof(g_abVBoxOrgCfHtml1); - -#endif /* !VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt deleted file mode 100644 index cef5f7c9490f0cd2639aa75d052d1e07da277cd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1962 zcmeHI&99R{5ck}q|HD2GMw4!#wKbN0B)&xB;d!yz`vO~-blJt-;nDinJ5Xq`iSOXa zLkR&g-~0OwGdpP=7##-V#c;4iVe!lNuTNocz9zHTaPUbdJ=tXOe$rZgwj#}?#*6ib zps~J@PZrZTd6?41^2fKQFheX{==)Vwji13{DO6;RV`Ec)I9O$vs|~#)lbq`J1LsXy zT~N+9D_0239G;Y1d$Cc*hRmkXn_$H1`|XnLjYhr~IjB>}cG4z7ixGSMvoyGT1axSn z$ZLgQTcO=qF=CFsxsw+5Z3qXyWXLA(ieo3JlZH$in+w&-o`nTL`4prAODo-9^s%60 z|LIyuNHYxCEC`lNNknNm9WDl)UM-C+GAb-&(GvpqYc@c`Lteq0LILSPAFWUIhp6SZ3vZ?Y+>SwVA&=_w1uNdR-IV zl;1(#1LfSC^eYe`5g|=PM+VVc=()En|8H8>+kA{niR{Ys$`|*sb00hZK6X6XNtV&t g0!?!?DdTz+j=MQTOOCc|Z^hB#^Sce`H!JbgZxn8%00000 diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h deleted file mode 100644 index a1c56bc5b22d..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h +++ /dev/null @@ -1,152 +0,0 @@ -/* $Id: VBoxOrgMimeHtml1.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ -/** @file - * Shared Clipboard host service test case C data file of VBoxOrgMimeHtml1.txt. - */ - -/* - * Copyright (C) 2022-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#ifndef VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h -#define VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include - -const unsigned char g_abVBoxOrgMimeHtml1[] = -{ - 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, /* 0x00000000: See */ - 0x20, 0x22, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x61, 0x20, 0x63, 0x6c, 0x61, 0x73, /* 0x00000240: "<.span>About Virtual */ - 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, /* 0x00000470: Box<.a>" for an int */ - 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, /* 0x000006c0: roduction.<.span */ - 0x3e, 0x00 /* 0x000006d0: > */ -}; - -const unsigned g_cbVBoxOrgMimeHtml1 = sizeof(g_abVBoxOrgMimeHtml1); - -#endif /* !VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt deleted file mode 100644 index 6d5b49011ac3..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt +++ /dev/null @@ -1 +0,0 @@ -See "About VirtualBox" for an introduction. \ No newline at end of file diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardDataObjectWin.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardDataObjectWin.cpp deleted file mode 100644 index 206aa5ec3d03..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardDataObjectWin.cpp +++ /dev/null @@ -1,1227 +0,0 @@ -/* $Id: tstClipboardDataObjectWin.cpp 114651 2026-07-08 09:46:19Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard Windows IDataObject testcase. - */ - -/* - * Copyright (C) 2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - - -typedef struct TESTFILE -{ - const char *pszNameUtf8; - const WCHAR *pwszName; -} TESTFILE; - - -/* - * Keep all filenames escaped so the source file itself does not depend on the - * editor/source encoding. The narrow strings are UTF-8. The wide strings are - * UTF-16 code units expected in FILEDESCRIPTORW::cFileName. - */ -static const TESTFILE g_aFiles[] = -{ - { - /* - * plain-ascii.txt - * - * Pure ASCII baseline. - */ - "\x70\x6c\x61\x69\x6e\x2d\x61\x73\x63\x69\x69\x2e\x74\x78\x74", - L"\x0070\x006c\x0061\x0069\x006e\x002d\x0061\x0073\x0063\x0069\x0069\x002e\x0074\x0078\x0074" - }, - { - /* - * cafe-with-accents: - * - * c a f U+00E9 - U+00FC b e r - U+00C5 n g s t r U+00F6 m .txt - * - * Latin characters: - * U+00E9 LATIN SMALL LETTER E WITH ACUTE - * U+00FC LATIN SMALL LETTER U WITH DIAERESIS - * U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE - * U+00F6 LATIN SMALL LETTER O WITH DIAERESIS - */ - "\x63\x61\x66\xc3\xa9\x2d\xc3\xbc\x62\x65\x72\x2d\xc3\x85\x6e\x67\x73\x74\x72\xc3\xb6\x6d\x2e\x74\x78\x74", - L"\x0063\x0061\x0066\x00e9\x002d\x00fc\x0062\x0065\x0072\x002d\x00c5\x006e\x0067\x0073\x0074\x0072\x00f6\x006d\x002e\x0074\x0078\x0074" - }, - { - /* - * cafe + combining acute accent: - * - * c a f e U+0301 - n f d .txt - * - * This visually resembles precomposed "cafe with acute", but contains: - * U+0065 LATIN SMALL LETTER E - * U+0301 COMBINING ACUTE ACCENT - * - * The IDataObject path must preserve this exact sequence and must not - * normalize it to precomposed U+00E9. - */ - "\x63\x61\x66\x65\xcc\x81\x2d\x6e\x66\x64\x2e\x74\x78\x74", - L"\x0063\x0061\x0066\x0065\x0301\x002d\x006e\x0066\x0064\x002e\x0074\x0078\x0074" - }, - { - /* - * Russian Cyrillic: - * - * Privet-mir.txt, roughly "hello-world.txt" - * - * Unicode: - * U+041F U+0440 U+0438 U+0432 U+0435 U+0442 - * U+002D - * U+043C U+0438 U+0440 - * - * This is the main mojibake regression case. If UTF-8 bytes are - * exposed through FILEDESCRIPTORA instead of FILEDESCRIPTORW, this - * often turns into text beginning with U+00D0 / U+00D1 mojibake. - */ - "\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82\x2d\xd0\xbc\xd0\xb8\xd1\x80\x2e\x74\x78\x74", - L"\x041f\x0440\x0438\x0432\x0435\x0442\x002d\x043c\x0438\x0440\x002e\x0074\x0078\x0074" - }, - { - /* - * Greek: - * - * dokimi-kosmos.txt, roughly "test-world.txt" - * - * Includes: - * U+03AE GREEK SMALL LETTER ETA WITH TONOS - * U+03CC GREEK SMALL LETTER OMICRON WITH TONOS - * U+03C2 GREEK SMALL LETTER FINAL SIGMA - */ - "\xce\xb4\xce\xbf\xce\xba\xce\xb9\xce\xbc\xce\xae\x2d\xce\xba\xcf\x8c\xcf\x83\xce\xbc\xce\xbf\xcf\x82\x2e\x74\x78\x74", - L"\x03b4\x03bf\x03ba\x03b9\x03bc\x03ae\x002d\x03ba\x03cc\x03c3\x03bc\x03bf\x03c2\x002e\x0074\x0078\x0074" - }, - { - /* - * Polish / Central European Latin: - * - * za + U+017C U+00F3 U+0142 U+0107 - * ge + U+0119 U+015B l U+0105 - * ja + U+017A U+0144 - * - * Useful for catching accidental host ANSI code page assumptions. - */ - "\x7a\x61\xc5\xbc\xc3\xb3\xc5\x82\xc4\x87\x2d\x67\xc4\x99\xc5\x9b\x6c\xc4\x85\x2d\x6a\x61\xc5\xba\xc5\x84\x2e\x74\x78\x74", - L"\x007a\x0061\x017c\x00f3\x0142\x0107\x002d\x0067\x0119\x015b\x006c\x0105\x002d\x006a\x0061\x017a\x0144\x002e\x0074\x0078\x0074" - }, - { - /* - * Hebrew, right-to-left: - * - * shalom-olam.txt, roughly "hello-world" / "peace-world" - * - * The test compares logical Unicode order, not visual display order. - */ - "\xd7\xa9\xd7\x9c\xd7\x95\xd7\x9d\x2d\xd7\xa2\xd7\x95\xd7\x9c\xd7\x9d\x2e\x74\x78\x74", - L"\x05e9\x05dc\x05d5\x05dd\x002d\x05e2\x05d5\x05dc\x05dd\x002e\x0074\x0078\x0074" - }, - { - /* - * Arabic, right-to-left: - * - * marhaba-alam.txt, "hello-world.txt" - * - * The test compares logical Unicode order, not visual display order. - */ - "\xd9\x85\xd8\xb1\xd8\xad\xd8\xa8\xd8\xa7\x2d\xd8\xb9\xd8\xa7\xd9\x84\xd9\x85\x2e\x74\x78\x74", - L"\x0645\x0631\x062d\x0628\x0627\x002d\x0639\x0627\x0644\x0645\x002e\x0074\x0078\x0074" - }, - { - /* - * Simplified Chinese: - * - * U+4E2D U+6587 - U+6D4B U+8BD5 .txt - * - * Meaning: - * first word: Chinese language/text - * second word: test - */ - "\xe4\xb8\xad\xe6\x96\x87\x2d\xe6\xb5\x8b\xe8\xaf\x95\x2e\x74\x78\x74", - L"\x4e2d\x6587\x002d\x6d4b\x8bd5\x002e\x0074\x0078\x0074" - }, - { - /* - * Japanese: - * - * U+65E5 U+672C U+8A9E - U+30C6 U+30B9 U+30C8 .txt - * - * Meaning: - * first word: Japanese language - * second word: test, written in katakana - */ - "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e\x2d\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88\x2e\x74\x78\x74", - L"\x65e5\x672c\x8a9e\x002d\x30c6\x30b9\x30c8\x002e\x0074\x0078\x0074" - }, - { - /* - * Korean Hangul: - * - * U+D55C U+AE00 - U+D14C U+C2A4 U+D2B8 .txt - * - * Meaning: - * first word: Hangul - * second word: test - */ - "\xed\x95\x9c\xea\xb8\x80\x2d\xed\x85\x8c\xec\x8a\xa4\xed\x8a\xb8\x2e\x74\x78\x74", - L"\xd55c\xae00\x002d\xd14c\xc2a4\xd2b8\x002e\x0074\x0078\x0074" - }, - { - /* - * Emoji / non-BMP: - * - * emoji - U+1F600 GRINNING FACE - file.txt - * - * UTF-8: - * F0 9F 98 80 - * - * UTF-16: - * D83D DE00 - */ - "\x65\x6d\x6f\x6a\x69\x2d\xf0\x9f\x98\x80\x2d\x66\x69\x6c\x65\x2e\x74\x78\x74", - L"\x0065\x006d\x006f\x006a\x0069\x002d\xd83d\xde00\x002d\x0066\x0069\x006c\x0065\x002e\x0074\x0078\x0074" - }, - { - /* - * Non-BMP CJK Extension B: - * - * cjk-ext - U+2000B CJK UNIFIED IDEOGRAPH-2000B .txt - * - * UTF-8: - * F0 A0 80 8B - * - * UTF-16: - * D840 DC0B - */ - "\x63\x6a\x6b\x2d\x65\x78\x74\x2d\xf0\xa0\x80\x8b\x2e\x74\x78\x74", - L"\x0063\x006a\x006b\x002d\x0065\x0078\x0074\x002d\xd840\xdc0b\x002e\x0074\x0078\x0074" - }, - { - /* - * Deliberate mojibake sentinel: - * - * mojibake - U+00D0 U+0178 U+00D1 U+20AC - * U+00D0 U+00B8 U+00D0 U+00B2 U+00D0 U+00B5 - * U+00D1 U+201A .txt - * - * These are the literal Unicode characters often produced when UTF-8 - * Cyrillic bytes are interpreted as Windows-1252-ish text. - * - * This testcase verifies that the IDataObject path preserves the - * literal filename and does not try to heuristically repair mojibake. - */ - "\x6d\x6f\x6a\x69\x62\x61\x6b\x65\x2d\xc3\x90\xc5\xb8\xc3\x91\xe2\x82\xac\xc3\x90\xc2\xb8\xc3\x90\xc2\xb2\xc3\x90\xc2\xb5\xc3\x91\xe2\x80\x9a\x2e\x74\x78\x74", - L"\x006d\x006f\x006a\x0069\x0062\x0061\x006b\x0065\x002d\x00d0\x0178\x00d1\x20ac\x00d0\x00b8\x00d0\x00b2\x00d0\x00b5\x00d1\x201a\x002e\x0074\x0078\x0074" - } -}; - - -typedef struct TESTCTX -{ - PSHCLTRANSFER pTransfer; -} TESTCTX; - - -static DECLCALLBACK(int) testTransferBegin(ShClWinDataObject::PCALLBACKCTX pCbCtx) -{ - AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); - AssertPtrReturn(pCbCtx->pThis, VERR_INVALID_POINTER); - - TESTCTX *pThis = (TESTCTX *)pCbCtx->pvUser; - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pThis->pTransfer, VERR_INVALID_POINTER); - - int rc = pCbCtx->pThis->SetTransfer(pThis->pTransfer); - if (RT_SUCCESS(rc)) - rc = pCbCtx->pThis->SetStatus(ShClWinDataObject::Running); - return rc; -} - - -static DECLCALLBACK(int) testTransferEnd(ShClWinDataObject::PCALLBACKCTX pCbCtx, - PSHCLTRANSFER pTransfer, int rcTransfer) -{ - RT_NOREF(pCbCtx, pTransfer, rcTransfer); - return VINF_SUCCESS; -} - - -static size_t testGetExpectedFileContent(unsigned i, char *pszBuf, size_t cbBuf) -{ - AssertReturn(i < RT_ELEMENTS(g_aFiles), 0); - AssertReturn(cbBuf > 0, 0); - - pszBuf[0] = '\0'; - - RTStrPrintf(pszBuf, cbBuf, - "tstClipboardDataObjectWin payload #%u: %s\n", - i, g_aFiles[i].pszNameUtf8); - - pszBuf[cbBuf - 1] = '\0'; - return RTStrNLen(pszBuf, cbBuf); -} - - -static void testReleaseStgMedium(STGMEDIUM *pMedium) -{ - if (!pMedium) - return; - - if (pMedium->pUnkForRelease) - pMedium->pUnkForRelease->Release(); - else if (pMedium->tymed == TYMED_HGLOBAL && pMedium->hGlobal) - GlobalFree(pMedium->hGlobal); - else if (pMedium->tymed == TYMED_ISTREAM && pMedium->pstm) - pMedium->pstm->Release(); - - RT_ZERO(*pMedium); -} - - -static int testCreateTempDir(char *pszTempDir, size_t cbTempDir) -{ - char szTempDir[RTPATH_MAX]; - - int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardDataObjectWin"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirCreate(szTempDir, 0700, 0); - if (rc == VERR_ALREADY_EXISTS) - rc = VINF_SUCCESS; - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "XXXXXX"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirCreateTemp(szTempDir, 0700); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTStrCopy(pszTempDir, cbTempDir, szTempDir); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - return VINF_SUCCESS; -} - - -static int testCreateFile(const char *pszTempDir, unsigned i, - char *pszPath, size_t cbPath) -{ - AssertReturn(i < RT_ELEMENTS(g_aFiles), VERR_INVALID_PARAMETER); - - int rc = RTPathJoin(pszPath, cbPath, pszTempDir, g_aFiles[i].pszNameUtf8); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - RTFILE hFile; - rc = RTFileOpen(&hFile, pszPath, RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - char szPayload[1024]; - size_t const cbPayload = testGetExpectedFileContent(i, szPayload, sizeof(szPayload)); - - rc = RTFileWrite(hFile, szPayload, cbPayload, NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - int rc2 = RTFileClose(hFile); - RTTESTI_CHECK_RC(rc2, VINF_SUCCESS); - - return RT_SUCCESS(rc) ? rc2 : rc; -} - - -static int testCreateTransfer(const char * const *papszRoots, unsigned cRoots, - PSHCLTRANSFER *ppTransfer) -{ - *ppTransfer = NULL; - - PSHCLTRANSFER pTransfer = NULL; - char *pszRoots = NULL; - - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, - NULL /* pCallbacks */, &pTransfer); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - return rc; - } - - for (;;) - { - rc = ShClWinTransferCreate(NULL /* pCtx */, pTransfer); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - SHCLTXPROVIDER Provider; - RT_ZERO(Provider); - if (!ShClTransferProviderLocalQueryInterface(&Provider)) - { - RTTestIFailed("ShClTransferProviderLocalQueryInterface failed"); - rc = VERR_NOT_SUPPORTED; - break; - } - - rc = ShClTransferSetProvider(pTransfer, &Provider); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - for (unsigned i = 0; i < cRoots; i++) - { - rc = RTStrAAppend(&pszRoots, papszRoots[i]); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - rc = RTStrAAppend(&pszRoots, SHCL_TRANSFER_URI_LIST_SEP_STR); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - } - - if (RT_FAILURE(rc)) - break; - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - rc = ShClTransferInit(pTransfer); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - *ppTransfer = pTransfer; - pTransfer = NULL; - break; - } - - RTStrFree(pszRoots); - - if (pTransfer) - { - ShClWinTransferDestroy(NULL /* pCtx */, pTransfer); - - int rc2 = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc2); - if (RT_SUCCESS(rc)) - rc = rc2; - } - - return rc; -} - - -static void testPrintWideNameN(const char *pszWhat, unsigned i, - WCHAR const *pwszName, size_t cwcMax) -{ - WCHAR wszTmp[MAX_PATH + 1]; - - size_t cwcName = 0; - while (cwcName < cwcMax && pwszName[cwcName] != L'\0') - cwcName++; - - size_t const cwcCopy = cwcName < RT_ELEMENTS(wszTmp) - 1 - ? cwcName - : RT_ELEMENTS(wszTmp) - 1; - - memcpy(wszTmp, pwszName, cwcCopy * sizeof(wszTmp[0])); - wszTmp[cwcCopy] = L'\0'; - - char *pszName = NULL; - int rc = RTUtf16ToUtf8((PCRTUTF16)wszTmp, &pszName); - if (RT_SUCCESS(rc)) - { - RTTestIPrintf(RTTESTLVL_ALWAYS, "%s #%u: \"%s\"%s\n", - pszWhat, i, pszName, - cwcName == cwcMax ? " " : ""); - RTStrFree(pszName); - } - else - RTTestIPrintf(RTTESTLVL_ALWAYS, "%s #%u: %s\n", - pszWhat, i, rc, - cwcName == cwcMax ? " " : ""); -} - - -static void testPrintAnsiNameN(const char *pszWhat, unsigned i, - const char *pszName, size_t cchMax) -{ - char szTmp[MAX_PATH + 1]; - - size_t const cchName = RTStrNLen(pszName, cchMax); - size_t const cchCopy = cchName < RT_ELEMENTS(szTmp) - 1 - ? cchName - : RT_ELEMENTS(szTmp) - 1; - - memcpy(szTmp, pszName, cchCopy); - szTmp[cchCopy] = '\0'; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "%s #%u: \"%s\"%s\n", - pszWhat, i, szTmp, - cchName == cchMax ? " " : ""); -} - - -static void testPrintFileUnderTest(unsigned i) -{ - RTTestIPrintf(RTTESTLVL_ALWAYS, "Preparing file #%u, input UTF-8 name: \"%s\"\n", - i, g_aFiles[i].pszNameUtf8); - - testPrintWideNameN("Preparing file expected UTF-16 name", - i, g_aFiles[i].pwszName, MAX_PATH); -} - - -static void testCheckFileGroupDescriptorW(HGLOBAL hGlobal) -{ - SIZE_T const cbActual = GlobalSize(hGlobal); - SIZE_T const cbExpected = sizeof(FILEGROUPDESCRIPTORW) - + (RT_ELEMENTS(g_aFiles) - 1) * sizeof(FILEDESCRIPTORW); - - if (cbActual < cbExpected) - { - RTTestIFailed("FILEGROUPDESCRIPTORW HGLOBAL too small: got %zu bytes, expected at least %zu bytes", - cbActual, cbExpected); - return; - } - - FILEGROUPDESCRIPTORW *pFGD = (FILEGROUPDESCRIPTORW *)GlobalLock(hGlobal); - if (!pFGD) - { - RTTestIFailed("GlobalLock(FILEGROUPDESCRIPTORW) failed, lasterr=%u", GetLastError()); - return; - } - - if (pFGD->cItems != RT_ELEMENTS(g_aFiles)) - { - RTTestIFailed("FILEGROUPDESCRIPTORW cItems=%u, expected %u", - (unsigned)pFGD->cItems, (unsigned)RT_ELEMENTS(g_aFiles)); - GlobalUnlock(hGlobal); - return; - } - - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - FILEDESCRIPTORW const *pFD = &pFGD->fgd[i]; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "Verifying FILEDESCRIPTORW item #%u\n", i); - RTTestIPrintf(RTTESTLVL_ALWAYS, "Verifying file #%u, original UTF-8 name: \"%s\"\n", - i, g_aFiles[i].pszNameUtf8); - - testPrintWideNameN("Verifying file expected UTF-16 name", - i, g_aFiles[i].pwszName, MAX_PATH); - testPrintWideNameN("Verifying file actual FILEDESCRIPTORW name", - i, pFD->cFileName, RT_ELEMENTS(pFD->cFileName)); - - if (!(pFD->dwFlags & FD_ATTRIBUTES)) - RTTestIFailed("file #%u: FD_ATTRIBUTES is not set", i); - - if (!(pFD->dwFlags & FD_FILESIZE)) - RTTestIFailed("file #%u: FD_FILESIZE is not set", i); - - if (pFD->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - RTTestIFailed("file #%u: unexpectedly marked as directory", i); - -#ifdef FD_UNICODE - if (!(pFD->dwFlags & FD_UNICODE)) - RTTestIFailed("file #%u: FD_UNICODE is not set", i); -#endif - - if (RTUtf16NCmp(pFD->cFileName, - g_aFiles[i].pwszName, - RT_ELEMENTS(pFD->cFileName)) != 0) - RTTestIFailed("file #%u: FILEDESCRIPTORW cFileName mismatch", i); - - char szExpected[1024]; - uint64_t const cbExpectedFile = testGetExpectedFileContent(i, szExpected, sizeof(szExpected)); - uint64_t const cbActualFile = ((uint64_t)pFD->nFileSizeHigh << 32) | pFD->nFileSizeLow; - - if (cbActualFile != cbExpectedFile) - RTTestIFailed("file #%u: FILEDESCRIPTORW file size %RU64, expected %RU64", - i, cbActualFile, cbExpectedFile); - } - - GlobalUnlock(hGlobal); -} - - -static void testCheckFileGroupDescriptorA(HGLOBAL hGlobal) -{ - SIZE_T const cbActual = GlobalSize(hGlobal); - SIZE_T const cbExpected = sizeof(FILEGROUPDESCRIPTORA) - + (RT_ELEMENTS(g_aFiles) - 1) * sizeof(FILEDESCRIPTORA); - - if (cbActual < cbExpected) - { - RTTestIFailed("FILEGROUPDESCRIPTORA HGLOBAL too small: got %zu bytes, expected at least %zu bytes", - cbActual, cbExpected); - return; - } - - FILEGROUPDESCRIPTORA *pFGD = (FILEGROUPDESCRIPTORA *)GlobalLock(hGlobal); - if (!pFGD) - { - RTTestIFailed("GlobalLock(FILEGROUPDESCRIPTORA) failed, lasterr=%u", GetLastError()); - return; - } - - if (pFGD->cItems != RT_ELEMENTS(g_aFiles)) - { - RTTestIFailed("FILEGROUPDESCRIPTORA cItems=%u, expected %u", - (unsigned)pFGD->cItems, (unsigned)RT_ELEMENTS(g_aFiles)); - GlobalUnlock(hGlobal); - return; - } - - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - FILEDESCRIPTORA const *pFD = &pFGD->fgd[i]; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "Verifying FILEDESCRIPTORA item #%u\n", i); - RTTestIPrintf(RTTESTLVL_ALWAYS, "Verifying file #%u, original UTF-8 name: \"%s\"\n", - i, g_aFiles[i].pszNameUtf8); - - testPrintAnsiNameN("Verifying file actual FILEDESCRIPTORA name", - i, pFD->cFileName, RT_ELEMENTS(pFD->cFileName)); - - if (RTStrNLen(pFD->cFileName, RT_ELEMENTS(pFD->cFileName)) == RT_ELEMENTS(pFD->cFileName)) - RTTestIFailed("file #%u: FILEDESCRIPTORA cFileName is not NUL-terminated", i); - - /* - * Only assert exact A-name equality for the pure ASCII case. For - * non-ASCII names, the A format is inherently codepage-dependent and - * should not be the Unicode correctness oracle. - */ - if (i == 0 && strcmp(pFD->cFileName, g_aFiles[i].pszNameUtf8) != 0) - RTTestIFailed("file #%u: FILEDESCRIPTORA ASCII cFileName mismatch: got \"%s\", expected \"%s\"", - i, pFD->cFileName, g_aFiles[i].pszNameUtf8); - - if (!(pFD->dwFlags & FD_ATTRIBUTES)) - RTTestIFailed("file #%u: FILEDESCRIPTORA FD_ATTRIBUTES is not set", i); - - if (!(pFD->dwFlags & FD_FILESIZE)) - RTTestIFailed("file #%u: FILEDESCRIPTORA FD_FILESIZE is not set", i); - -#ifdef FD_UNICODE - if (pFD->dwFlags & FD_UNICODE) - RTTestIFailed("file #%u: FILEDESCRIPTORA unexpectedly has FD_UNICODE set", i); -#endif - - if (pFD->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - RTTestIFailed("file #%u: FILEDESCRIPTORA unexpectedly marked as directory", i); - - char szExpected[1024]; - uint64_t const cbExpectedFile = testGetExpectedFileContent(i, szExpected, sizeof(szExpected)); - uint64_t const cbActualFile = ((uint64_t)pFD->nFileSizeHigh << 32) | pFD->nFileSizeLow; - - if (cbActualFile != cbExpectedFile) - RTTestIFailed("file #%u: FILEDESCRIPTORA file size %RU64, expected %RU64", - i, cbActualFile, cbExpectedFile); - } - - GlobalUnlock(hGlobal); -} - - -/** - * Creates the expected CF_UNICODETEXT root-name list. - * - * @returns VBox status code. - * @param ppwszText Where to return the allocated UTF-16 text. - */ -static int testCreateExpectedUnicodeText(PRTUTF16 *ppwszText) -{ - AssertPtrReturn(ppwszText, VERR_INVALID_POINTER); - - *ppwszText = NULL; - - size_t cwcText = 1; /* Terminator. */ - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - cwcText += RTUtf16Len((PCRTUTF16)g_aFiles[i].pwszName); - if (i + 1 < RT_ELEMENTS(g_aFiles)) - cwcText += 2; /* CRLF separator. */ - } - - PRTUTF16 pwszText = (PRTUTF16)RTMemAllocZ(cwcText * sizeof(RTUTF16)); - if (!pwszText) - return VERR_NO_MEMORY; - - PRTUTF16 pwszDst = pwszText; - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - size_t const cwcName = RTUtf16Len((PCRTUTF16)g_aFiles[i].pwszName); - memcpy(pwszDst, g_aFiles[i].pwszName, cwcName * sizeof(RTUTF16)); - pwszDst += cwcName; - - if (i + 1 < RT_ELEMENTS(g_aFiles)) - { - *pwszDst++ = '\r'; - *pwszDst++ = '\n'; - } - } - - *pwszDst = '\0'; - *ppwszText = pwszText; - return VINF_SUCCESS; -} - - -/** - * Checks the CF_UNICODETEXT text representation of the transfer roots. - * - * @param pDataObj Data object to check. - */ -static void testCheckUnicodeText(ShClWinDataObject *pDataObj) -{ - AssertPtrReturnVoid(pDataObj); - - RTTestISub("IDataObject / CF_UNICODETEXT"); - - FORMATETC FormatEtc; - RT_ZERO(FormatEtc); - FormatEtc.cfFormat = CF_UNICODETEXT; - FormatEtc.dwAspect = DVASPECT_CONTENT; - FormatEtc.lindex = -1; - FormatEtc.tymed = TYMED_HGLOBAL; - - HRESULT hrc = pDataObj->QueryGetData(&FormatEtc); - if (hrc != S_OK) - { - RTTestIFailed("QueryGetData(CF_UNICODETEXT) returned %Rhrc, expected S_OK", hrc); - return; - } - - STGMEDIUM Medium; - RT_ZERO(Medium); - - hrc = pDataObj->GetData(&FormatEtc, &Medium); - if (hrc != S_OK) - { - RTTestIFailed("GetData(CF_UNICODETEXT) returned %Rhrc, expected S_OK", hrc); - testReleaseStgMedium(&Medium); - return; - } - - if (Medium.tymed != TYMED_HGLOBAL) - RTTestIFailed("CF_UNICODETEXT tymed %#x, expected TYMED_HGLOBAL", Medium.tymed); - - if (!Medium.hGlobal) - RTTestIFailed("CF_UNICODETEXT returned NULL HGLOBAL"); - else - { - PRTUTF16 pwszExpected = NULL; - int rc = testCreateExpectedUnicodeText(&pwszExpected); - if (RT_FAILURE(rc)) - RTTESTI_CHECK_RC_OK(rc); - else - { - PCRTUTF16 pcwszActual = (PCRTUTF16)GlobalLock(Medium.hGlobal); - if (!pcwszActual) - RTTestIFailed("GlobalLock(CF_UNICODETEXT) failed, lasterr=%u", GetLastError()); - else - { - if (RTUtf16Cmp(pcwszActual, pwszExpected) != 0) - { - char *pszActual = NULL; - char *pszExpected = NULL; - RTUtf16ToUtf8(pcwszActual, &pszActual); - RTUtf16ToUtf8(pwszExpected, &pszExpected); - RTTestIFailed("CF_UNICODETEXT mismatch: got \"%s\", expected \"%s\"", - pszActual ? pszActual : "", - pszExpected ? pszExpected : ""); - RTStrFree(pszActual); - RTStrFree(pszExpected); - } - - GlobalUnlock(Medium.hGlobal); - } - - RTMemFree(pwszExpected); - } - } - - testReleaseStgMedium(&Medium); -} - - -static void testCheckFileDescriptorA(ShClWinDataObject *pDataObj) -{ - AssertPtrReturnVoid(pDataObj); - - RTTestISub("IDataObject / CFSTR_FILEDESCRIPTORA"); - - CLIPFORMAT const cfFileDescriptorA = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA); - if (!cfFileDescriptorA) - { - RTTestIFailed("RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA) failed, lasterr=%u", - GetLastError()); - return; - } - - FORMATETC FormatEtc; - RT_ZERO(FormatEtc); - FormatEtc.cfFormat = cfFileDescriptorA; - FormatEtc.dwAspect = DVASPECT_CONTENT; - FormatEtc.lindex = -1; - FormatEtc.tymed = TYMED_HGLOBAL; - - HRESULT hrc = pDataObj->QueryGetData(&FormatEtc); - if (hrc != S_OK) - { - RTTestIFailed("QueryGetData(CFSTR_FILEDESCRIPTORA) returned %Rhrc, expected S_OK", - hrc); - return; - } - - STGMEDIUM Medium; - RT_ZERO(Medium); - - hrc = pDataObj->GetData(&FormatEtc, &Medium); - if (hrc != S_OK) - { - RTTestIFailed("GetData(CFSTR_FILEDESCRIPTORA) returned %Rhrc, expected S_OK", - hrc); - testReleaseStgMedium(&Medium); - return; - } - - if (Medium.tymed != TYMED_HGLOBAL) - RTTestIFailed("CFSTR_FILEDESCRIPTORA tymed %#x, expected TYMED_HGLOBAL", - Medium.tymed); - - if (!Medium.hGlobal) - RTTestIFailed("CFSTR_FILEDESCRIPTORA returned NULL HGLOBAL"); - else - testCheckFileGroupDescriptorA(Medium.hGlobal); - - testReleaseStgMedium(&Medium); -} - - -static void testCheckFileContentsStream(unsigned i, IStream *pStream) -{ - AssertPtrReturnVoid(pStream); - AssertReturnVoid(i < RT_ELEMENTS(g_aFiles)); - - char szExpected[1024]; - size_t const cbExpected = testGetExpectedFileContent(i, szExpected, sizeof(szExpected)); - - STATSTG StatStg; - RT_ZERO(StatStg); - - HRESULT hrc = pStream->Stat(&StatStg, STATFLAG_NONAME); - if (hrc != S_OK) - RTTestIFailed("file #%u: IStream::Stat returned %Rhrc, expected S_OK", i, hrc); - else if ((uint64_t)StatStg.cbSize.QuadPart != cbExpected) - RTTestIFailed("file #%u: IStream size %RU64, expected %zu", - i, (uint64_t)StatStg.cbSize.QuadPart, cbExpected); - - char abBuf[2048]; - RT_ZERO(abBuf); - - if (cbExpected > sizeof(abBuf)) - { - RTTestIFailed("file #%u: expected payload too large for test buffer: %zu > %zu", - i, cbExpected, sizeof(abBuf)); - return; - } - - ULONG cbRead = 0; - hrc = pStream->Read(abBuf, sizeof(abBuf), &cbRead); - if (hrc != S_OK && hrc != S_FALSE) - RTTestIFailed("file #%u: IStream::Read returned %Rhrc, expected S_OK or S_FALSE", - i, hrc); - - RTTestIPrintf(RTTESTLVL_ALWAYS, "CFSTR_FILECONTENTS item #%u read %RU32 bytes: \"%.*s\"\n", - i, (uint32_t)cbRead, (int)cbRead, abBuf); - - if (cbRead != cbExpected) - RTTestIFailed("file #%u: IStream::Read read %RU32 bytes, expected %zu", - i, (uint32_t)cbRead, cbExpected); - else if (memcmp(abBuf, szExpected, cbExpected) != 0) - RTTestIFailed("file #%u: IStream payload mismatch", i); - - ULONG cbReadAgain = ~0U; - hrc = pStream->Read(abBuf, sizeof(abBuf), &cbReadAgain); - if (hrc != S_OK && hrc != S_FALSE) - RTTestIFailed("file #%u: second EOF IStream::Read returned %Rhrc, expected S_OK or S_FALSE", - i, hrc); - - if (cbReadAgain != 0) - RTTestIFailed("file #%u: second EOF IStream::Read returned %RU32 bytes, expected 0", - i, (uint32_t)cbReadAgain); -} - - -static void testCheckFileContents(ShClWinDataObject *pDataObj) -{ - AssertPtrReturnVoid(pDataObj); - - RTTestISub("IDataObject / CFSTR_FILECONTENTS"); - - CLIPFORMAT const cfFileContents = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILECONTENTS); - if (!cfFileContents) - { - RTTestIFailed("RegisterClipboardFormat(CFSTR_FILECONTENTS) failed, lasterr=%u", - GetLastError()); - return; - } - - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - RTTestIPrintf(RTTESTLVL_ALWAYS, - "Requesting CFSTR_FILECONTENTS item #%u: \"%s\"\n", - i, g_aFiles[i].pszNameUtf8); - - FORMATETC FormatEtc; - RT_ZERO(FormatEtc); - FormatEtc.cfFormat = cfFileContents; - FormatEtc.dwAspect = DVASPECT_CONTENT; - FormatEtc.lindex = (LONG)i; - FormatEtc.tymed = TYMED_ISTREAM; - - HRESULT hrc = pDataObj->QueryGetData(&FormatEtc); - if (hrc != S_OK) - { - RTTestIFailed("file #%u: QueryGetData(CFSTR_FILECONTENTS) returned %Rhrc, expected S_OK", - i, hrc); - continue; - } - - STGMEDIUM Medium; - RT_ZERO(Medium); - - hrc = pDataObj->GetData(&FormatEtc, &Medium); - if (hrc != S_OK) - { - RTTestIFailed("file #%u: GetData(CFSTR_FILECONTENTS) returned %Rhrc, expected S_OK", - i, hrc); - testReleaseStgMedium(&Medium); - continue; - } - - if (Medium.tymed != TYMED_ISTREAM) - RTTestIFailed("file #%u: CFSTR_FILECONTENTS tymed %#x, expected TYMED_ISTREAM", - i, Medium.tymed); - - if (!Medium.pstm) - RTTestIFailed("file #%u: CFSTR_FILECONTENTS returned NULL IStream", i); - else - testCheckFileContentsStream(i, Medium.pstm); - - testReleaseStgMedium(&Medium); - } -} - - -/** - * Tests Windows clipboard format conversion for file-transfer formats. - */ -static void testClipboardFormatToVBoxFileTransferFormats(void) -{ - RTTestISub("ShClWinClipboardFormatToVBox / file-transfer formats"); - - SHCLFORMAT uFmt = ShClWinClipboardFormatToVBox(CF_HDROP); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_URI_LIST, - ("CF_HDROP: uFmt=%#x expected=%#x\n", uFmt, VBOX_SHCL_FMT_URI_LIST)); - - CLIPFORMAT const cfFileDescriptorA = (CLIPFORMAT)RegisterClipboardFormatA("FileGroupDescriptor"); - if (!cfFileDescriptorA) - RTTestIFailed("RegisterClipboardFormatA(\"FileGroupDescriptor\") failed, lasterr=%u", GetLastError()); - if (cfFileDescriptorA) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileDescriptorA); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileGroupDescriptor via RegisterClipboardFormatA: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } - - CLIPFORMAT const cfFileDescriptorAU = (CLIPFORMAT)RegisterClipboardFormatW(L"FileGroupDescriptor"); - if (!cfFileDescriptorAU) - RTTestIFailed("RegisterClipboardFormatW(L\"FileGroupDescriptor\") failed, lasterr=%u", GetLastError()); - if (cfFileDescriptorAU) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileDescriptorAU); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileGroupDescriptor via RegisterClipboardFormatW: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } - - CLIPFORMAT const cfFileDescriptorW = (CLIPFORMAT)RegisterClipboardFormatW(L"FileGroupDescriptorW"); - if (!cfFileDescriptorW) - RTTestIFailed("RegisterClipboardFormatW(L\"FileGroupDescriptorW\") failed, lasterr=%u", GetLastError()); - if (cfFileDescriptorW) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileDescriptorW); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileGroupDescriptorW via RegisterClipboardFormatW: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } - - CLIPFORMAT const cfFileContents = (CLIPFORMAT)RegisterClipboardFormatA("FileContents"); - if (!cfFileContents) - RTTestIFailed("RegisterClipboardFormatA(\"FileContents\") failed, lasterr=%u", GetLastError()); - if (cfFileContents) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileContents); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileContents via RegisterClipboardFormatA: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } - - CLIPFORMAT const cfFileContentsU = (CLIPFORMAT)RegisterClipboardFormatW(L"FileContents"); - if (!cfFileContentsU) - RTTestIFailed("RegisterClipboardFormatW(L\"FileContents\") failed, lasterr=%u", GetLastError()); - if (cfFileContentsU) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileContentsU); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileContents via RegisterClipboardFormatW: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } -} - - -static void testFileDescriptorW(void) -{ - RTTestISub("IDataObject / CFSTR_FILEDESCRIPTORW"); - - /* - * Keep Ctx alive until after pDataObj has been released. The data object - * stores the callback context pointer passed to Init(). - */ - TESTCTX Ctx; - RT_ZERO(Ctx); - - char szTempDir[RTPATH_MAX] = ""; - char aaszFilePaths[RT_ELEMENTS(g_aFiles)][RTPATH_MAX]; - const char *apszRoots[RT_ELEMENTS(g_aFiles)]; - RT_ZERO(aaszFilePaths); - RT_ZERO(apszRoots); - - PSHCLTRANSFER pTransfer = NULL; - ShClWinDataObject *pDataObj = NULL; - - STGMEDIUM StgMedium; - RT_ZERO(StgMedium); - - int rc = VINF_SUCCESS; - - for (;;) - { - rc = testCreateTempDir(szTempDir, sizeof(szTempDir)); - if (RT_FAILURE(rc)) - break; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "Temporary test directory: %s\n", szTempDir); - - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - testPrintFileUnderTest(i); - - rc = testCreateFile(szTempDir, - i, - aaszFilePaths[i], - sizeof(aaszFilePaths[i])); - if (RT_FAILURE(rc)) - break; - - apszRoots[i] = aaszFilePaths[i]; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "Created test file #%u: %s\n", - i, aaszFilePaths[i]); - } - - if (RT_FAILURE(rc)) - break; - - rc = testCreateTransfer(apszRoots, RT_ELEMENTS(g_aFiles), &pTransfer); - if (RT_FAILURE(rc)) - break; - - Ctx.pTransfer = pTransfer; - - ShClWinDataObject::CALLBACKS Callbacks; - RT_ZERO(Callbacks); - Callbacks.pfnTransferBegin = testTransferBegin; - Callbacks.pfnTransferEnd = testTransferEnd; - - pDataObj = new ShClWinDataObject(); - if (!pDataObj) - { - RTTestIFailed("new ShClWinDataObject failed"); - rc = VERR_NO_MEMORY; - break; - } - - pDataObj->AddRef(); - - rc = pDataObj->Init((PSHCLCONTEXT)&Ctx, &Callbacks); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - testCheckUnicodeText(pDataObj); - - CLIPFORMAT const cfFileDescriptorW = (CLIPFORMAT)RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW); - if (!cfFileDescriptorW) - { - RTTestIFailed("RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW) failed, lasterr=%u", - GetLastError()); - rc = VERR_INTERNAL_ERROR; - break; - } - - FORMATETC FormatEtc; - RT_ZERO(FormatEtc); - FormatEtc.cfFormat = cfFileDescriptorW; - FormatEtc.dwAspect = DVASPECT_CONTENT; - FormatEtc.lindex = -1; - FormatEtc.tymed = TYMED_HGLOBAL; - - HRESULT hrc = pDataObj->QueryGetData(&FormatEtc); - if (hrc != S_OK) - { - RTTestIFailed("QueryGetData(CFSTR_FILEDESCRIPTORW) returned %Rhrc, expected S_OK", hrc); - break; - } - - hrc = pDataObj->GetData(&FormatEtc, &StgMedium); - if (hrc != S_OK) - { - RTTestIFailed("GetData(CFSTR_FILEDESCRIPTORW) returned %Rhrc, expected S_OK", hrc); - break; - } - - if (StgMedium.tymed != TYMED_HGLOBAL) - RTTestIFailed("GetData returned tymed %#x, expected TYMED_HGLOBAL", - StgMedium.tymed); - - if (!StgMedium.hGlobal) - RTTestIFailed("GetData returned a NULL HGLOBAL"); - - if (StgMedium.tymed == TYMED_HGLOBAL && StgMedium.hGlobal) - testCheckFileGroupDescriptorW(StgMedium.hGlobal); - - testCheckFileDescriptorA(pDataObj); - - /* - * Keep CFSTR_FILECONTENTS last. Consuming the final stream can - * transition the transfer/data-object state to Completed. - */ - testCheckFileContents(pDataObj); - - break; - } - - /* - * Cleanup. - */ - testReleaseStgMedium(&StgMedium); - - if (pDataObj) - { - pDataObj->Release(); - pDataObj = NULL; - } - - if (pTransfer) - { - ShClWinTransferDestroy(NULL /* pCtx */, pTransfer); - - int rc2 = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc2); - - pTransfer = NULL; - Ctx.pTransfer = NULL; - } - - if (szTempDir[0]) - { - int rc2 = RTDirRemoveRecursive(szTempDir, RTDIRRMREC_F_CONTENT_AND_DIR); - RTTESTI_CHECK_RC_OK(rc2); - } -} - - -int main(int argc, char **argv) -{ - RT_NOREF(argc, argv); - - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardDataObjectWin", &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - - RTTestBanner(hTest); - - bool const fMayPanic = RTAssertSetMayPanic(false); - bool const fQuiet = RTAssertSetQuiet(true); - - testClipboardFormatToVBoxFileTransferFormats(); - testFileDescriptorW(); - - RTAssertSetQuiet(fQuiet); - RTAssertSetMayPanic(fMayPanic); - - return RTTestSummaryAndDestroy(hTest); -} diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp new file mode 100644 index 000000000000..e42b9546e007 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp @@ -0,0 +1,957 @@ +/* $Id: tstClipboardHostService.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard Host Service testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include + + +/** @page pg_tstClipboardHostService Shared Clipboard Host Service testcase + * + * This is the Host Service-side unit test for the Shared Clipboard HGCM + * boundary. It runs the production service sources in-process through their + * exported HGCM table. A small extension sink records notifications and uses + * the published service operation table; it does not emulate Main policy. + * + * The test checks connection ownership, host policy and feature negotiation, + * message wakeup/removal, clipboard-data replies, representative malformed + * guest calls, and the transfer control-plane lifecycle. Guest parameters are + * always treated as hostile, and rejected calls must not mutate service or + * extension state. + * + * Guest Additions, native backends, format conversion, filesystem providers, + * HTTP transport, transfer contents and VMM saved-state serialization are out + * of scope for this compact test. + */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** State attached to an HGCM guest-call handle. */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Whether the service completed the call. */ + bool fCompleted; + /** Completion status supplied by the service. */ + int32_t rc; +}; + +/** Minimal stand-in for Main's Shared Clipboard service extension. */ +typedef struct TSTCLEXT +{ + /** Transport received with the latest client connection. */ + SHCLTRANSPORT Transport; + /** Number of connect notifications. */ + uint32_t cConnect; + /** Number of disconnect notifications. */ + uint32_t cDisconnect; + /** Number of synchronization notifications. */ + uint32_t cSync; + /** Number of guest format announcements. */ + uint32_t cGuestFormats; + /** Number of host clipboard data reads requested by the guest. */ + uint32_t cDataReads; + /** Number of guest clipboard data writes forwarded by the service. */ + uint32_t cDataWrites; + /** Last format mask announced by the guest. */ + SHCLFORMATS fGuestFormats; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Number of transfer-status callback-table queries. */ + uint32_t cTransferCallbackQueries; + /** Number of guest transfer-status notifications. */ + uint32_t cTransferStatuses; + /** Session ID of the last transfer-status notification. */ + SHCLSESSIONID idTransferSession; + /** Transfer ID of the last transfer-status notification. */ + SHCLTRANSFERID idTransfer; + /** Generation of the last transfer-status notification. */ + SHCLTRANSFERGEN uTransferGeneration; + /** Direction of the last transfer-status notification. */ + SHCLTRANSFERDIR enmTransferDir; + /** Source of the last transfer-status notification. */ + SHCLSOURCE enmTransferSource; + /** Last transfer status reported by the guest. */ + SHCLTRANSFERSTATUS enmTransferStatus; + /** Last transfer result reported by the guest. */ + int rcTransfer; +#endif +} TSTCLEXT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Test handle. */ +static RTTEST g_hTest; +/** Loaded service function table. */ +static VBOXHGCMSVCFNTABLE g_Table; +/** HGCM helpers supplied to the service. */ +static VBOXHGCMSVCHELPERS g_Helpers; +/** Minimal service extension state. */ +static TSTCLEXT g_Ext; +/** Data returned for VBOX_SHCL_GUEST_FN_DATA_READ. */ +static uint8_t const g_abHostData[] = { 't', 'e', 's', 't', '\0' }; + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable); + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** + * Completes one synthetic guest call. + * + * @returns VINF_SUCCESS. + * @param hCall Synthetic call handle to complete. + * @param rc Guest-call result. + */ +static DECLCALLBACK(int) tstCallComplete(VBOXHGCMCALLHANDLE hCall, int32_t rc) +{ + hCall->fCompleted = true; + hCall->rc = rc; + return VINF_SUCCESS; +} + + +/** + * Dispatches service-extension requests without implementing a native backend. + * + * The test records the transport contract and provides fixed POD clipboard data; + * platform policy and Main behavior deliberately stay outside this testcase. + * + * @returns VBox status code. + * @param pvExtension Test extension state. + * @param uFunction VBOX_CLIPBOARD_EXT_FN_XXX function number. + * @param pvParms Service-extension parameters. + * @param cbParms Size of @a pvParms in bytes. + */ +static DECLCALLBACK(int) tstExtension(void *pvExtension, uint32_t uFunction, void *pvParms, uint32_t cbParms) +{ + TSTCLEXT * const pExt = (TSTCLEXT *)pvExtension; + RTTESTI_CHECK_RET(cbParms == sizeof(SHCLEXTPARMS), VERR_INVALID_PARAMETER); + PSHCLEXTPARMS const pParms = (PSHCLEXTPARMS)pvParms; + + switch (uFunction) + { + case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: + case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: + pExt->Transport = ShClSvcExtGetTransport(pParms); + RTTESTI_CHECK_RET(ShClTransportIsValid(&pExt->Transport), VERR_INVALID_PARAMETER); + pExt->cConnect++; + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + RTTESTI_CHECK_RET(ShClTransportIsEqual(&pExt->Transport, &Transport), VERR_INVALID_PARAMETER); + pExt->cDisconnect++; + return VINF_SUCCESS; + } + + case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + RTTESTI_CHECK_RET(ShClTransportIsEqual(&pExt->Transport, &Transport), VERR_INVALID_PARAMETER); + pExt->cSync++; + return VINF_SUCCESS; + } + + case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: + pExt->cGuestFormats++; + pExt->fGuestFormats = pParms->u.ReportFormats.uFormats; + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_DATA_READ: + pExt->cDataReads++; + pParms->u.ReadWriteData.cbActual = sizeof(g_abHostData); + if (pParms->u.ReadWriteData.cbData >= sizeof(g_abHostData)) + memcpy(pParms->u.ReadWriteData.pvData, g_abHostData, sizeof(g_abHostData)); + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + RTTESTI_CHECK_RET(ShClTransportIsEqual(&pExt->Transport, &Transport), VERR_INVALID_PARAMETER); + pExt->cDataWrites++; + + SHCLGUESTDATATOKEN hToken; + int rc = Transport.pOps->pfnGuestDataBegin(Transport.hClient, pParms->u.ReadWriteData.pCmdCtx, + pParms->u.ReadWriteData.uFormat, &hToken); + if (RT_SUCCESS(rc) && hToken) + rc = Transport.pOps->pfnGuestDataComplete(Transport.hClient, hToken, + pParms->u.ReadWriteData.pvData, + pParms->u.ReadWriteData.cbData); + return rc; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: + RTTESTI_CHECK_RET(pParms->u.TransferCallbacks.pCallbacks != NULL, VERR_INVALID_PARAMETER); + RT_ZERO(*pParms->u.TransferCallbacks.pCallbacks); + pExt->cTransferCallbackQueries++; + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: + { + PSHCLTRANSFER const pTransfer = pParms->u.FileTransferData.pTransfer; + SHCLREPLY const * const pReply = pParms->u.FileTransferData.pReply; + RTTESTI_CHECK_RET(pTransfer != NULL, VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RET(pReply != NULL, VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RET(pReply->uType == VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS, VERR_INVALID_PARAMETER); + + pExt->cTransferStatuses++; + pExt->idTransferSession = ShClTransferGetSessionId(pTransfer); + pExt->idTransfer = ShClTransferGetID(pTransfer); + pExt->uTransferGeneration = ShClTransferGetGeneration(pTransfer); + pExt->enmTransferDir = ShClTransferGetDir(pTransfer); + pExt->enmTransferSource = pParms->u.FileTransferData.enmShClSource; + pExt->enmTransferStatus = pReply->u.TransferStatus.uStatus; + pExt->rcTransfer = pReply->rc; + return VINF_SUCCESS; + } +#endif + + default: + return VERR_NOT_SUPPORTED; + } +} + + +/** + * Starts a guest call and returns its completion state to the caller. + * + * @param pvClient HGCM client state. + * @param uFunction Guest function number. + * @param cParms Number of HGCM parameters. + * @param paParms HGCM parameters. Optional if @a cParms is zero. + * @param pCall Call state which remains valid until completion. + */ +static void tstGuestCallStart(void *pvClient, uint32_t uFunction, uint32_t cParms, + VBOXHGCMSVCPARM *paParms, VBOXHGCMCALLHANDLE_TYPEDEF *pCall) +{ + pCall->fCompleted = false; + pCall->rc = VERR_IPE_UNINITIALIZED_STATUS; + g_Table.pfnCall(g_Table.pvService, pCall, 1 /* idClient */, pvClient, + uFunction, cParms, paParms, 0 /* tsArrival */); +} + + +/** + * Executes a guest call which is expected to complete synchronously. + * + * @returns Guest-call result. + * @param pvClient HGCM client state. + * @param uFunction Guest function number. + * @param cParms Number of HGCM parameters. + * @param paParms HGCM parameters. Optional if @a cParms is zero. + */ +static int tstGuestCall(void *pvClient, uint32_t uFunction, uint32_t cParms, VBOXHGCMSVCPARM *paParms) +{ + VBOXHGCMCALLHANDLE_TYPEDEF Call; + tstGuestCallStart(pvClient, uFunction, cParms, paParms, &Call); + RTTESTI_CHECK_MSG_RET(Call.fCompleted, ("Guest function %RU32 was not completed\n", uFunction), + VERR_INTERNAL_ERROR); + return Call.rc; +} + + +/** + * Executes an intentionally malformed guest call with guest assertions suppressed. + * + * @returns Guest-call result. + * @param pvClient HGCM client state. + * @param uFunction Guest function number. + * @param cParms Number of HGCM parameters. + * @param paParms HGCM parameters. Optional if @a cParms is zero. + */ +static int tstGuestCallUntrusted(void *pvClient, uint32_t uFunction, uint32_t cParms, VBOXHGCMSVCPARM *paParms) +{ + int rc = RTTestIDisableAssertions(); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + int const rcCall = tstGuestCall(pvClient, uFunction, cParms, paParms); + + rc = RTTestIRestoreAssertions(); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + return rcCall; +} + + +/** + * Loads the service and verifies its small HGCM registration contract. + * + * @returns VBox status code. + */ +static int tstLoadService(void) +{ + RTTestISub("HGCM service registration"); + + RT_ZERO(g_Table); + RT_ZERO(g_Helpers); + g_Helpers.pfnCallComplete = tstCallComplete; + g_Table.cbSize = sizeof(g_Table); + g_Table.u32Version = VBOX_HGCM_SVC_VERSION; + g_Table.pHelpers = &g_Helpers; + + int const rc = VBoxHGCMSvcLoad(&g_Table); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + RTTESTI_CHECK(g_Table.cbClient > 0); + RTTESTI_CHECK(g_Table.pfnConnect != NULL); + RTTESTI_CHECK(g_Table.pfnDisconnect != NULL); + RTTESTI_CHECK(g_Table.pfnCall != NULL); + RTTESTI_CHECK(g_Table.pfnHostCall != NULL); + RTTESTI_CHECK(g_Table.pfnRegisterExtension != NULL); + for (uintptr_t i = 0; i < RT_ELEMENTS(g_Table.acMaxClients); i++) + RTTESTI_CHECK(g_Table.acMaxClients[i] == 1); + + RT_ZERO(g_Ext); + return g_Table.pfnRegisterExtension(g_Table.pvService, tstExtension, &g_Ext); +} + + +/** + * Checks connection ownership and the opaque transport supplied to Main. + * + * @param ppvClient Where to return the connected HGCM client state. + */ +static void tstConnection(void **ppvClient) +{ + RTTestISub("Client connection and transport"); + + void *pvClient = RTMemAllocZ(g_Table.cbClient); + void *pvOther = RTMemAllocZ(g_Table.cbClient); + if (!pvClient || !pvOther) + { + RTTestIFailed("Allocating HGCM client state failed"); + RTMemFree(pvOther); + RTMemFree(pvClient); + return; + } + + int rc = g_Table.pfnConnect(g_Table.pvService, 1, pvClient, 0 /* fRequestor */, false /* fRestoring */); + if (RT_FAILURE(rc)) + { + RTTestIFailed("Connecting the first client failed: %Rrc", rc); + RTMemFree(pvOther); + RTMemFree(pvClient); + return; + } + RTTESTI_CHECK(g_Ext.cConnect == 1); + RTTESTI_CHECK(g_Ext.cSync == 1); + + rc = g_Table.pfnConnect(g_Table.pvService, 2, pvOther, 0 /* fRequestor */, false /* fRestoring */); + RTTESTI_CHECK_RC(rc, VERR_RESOURCE_BUSY); + RTTESTI_CHECK(g_Ext.cConnect == 1); + if (RT_SUCCESS(rc)) + { + int const rcDisconnect = g_Table.pfnDisconnect(g_Table.pvService, 2, pvOther); + RTTESTI_CHECK_RC(rcDisconnect, VINF_SUCCESS); + RTMemFree(pvOther); + *ppvClient = pvClient; + return; + } + RTMemFree(pvOther); + + *ppvClient = pvClient; +} + + +/** + * Checks host policy propagation and guest feature negotiation. + * + * @param pvClient Connected HGCM client state. + */ +static void tstPolicyAndFeatures(void *pvClient) +{ + RTTestISub("Policy and feature negotiation"); + + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, VBOX_SHCL_MODE_BIDIRECTIONAL); + int rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + SHCLFORMATS fFiltered = VBOX_SHCL_FMT_NONE; + rc = g_Ext.Transport.pOps->pfnFilterFormats(g_Ext.Transport.hClient, true /* fHostToGuest */, + VBOX_SHCL_FMT_URI_LIST, &fFiltered); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(fFiltered == VBOX_SHCL_FMT_NONE); + + uint32_t const cDataReadsBefore = g_Ext.cDataReads; + uint8_t abData[8]; + VBOXHGCMSVCPARM aRead[3]; + HGCMSvcSetU32(&aRead[0], VBOX_SHCL_FMT_URI_LIST); + HGCMSvcSetPv(&aRead[1], abData, sizeof(abData)); + HGCMSvcSetU32(&aRead[2], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(g_Ext.cDataReads == cDataReadsBefore); + + PSHCLEVENT pEvent = (PSHCLEVENT)(uintptr_t)1; + rc = g_Ext.Transport.pOps->pfnReadDataFromGuestAsync(g_Ext.Transport.hClient, + VBOX_SHCL_FMT_URI_LIST, &pEvent); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(pEvent == NULL); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +#endif + + uint64_t const fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_GF_0_TRANSFERS +#endif + ; + VBOXHGCMSVCPARM aParms[2]; + HGCMSvcSetU64(&aParms[0], fGuestFeatures0); + HGCMSvcSetU64(&aParms[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aParms), aParms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(aParms[0].u.uint64 & VBOX_SHCL_HF_0_CONTEXT_ID); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + fFiltered = VBOX_SHCL_FMT_NONE; + rc = g_Ext.Transport.pOps->pfnFilterFormats(g_Ext.Transport.hClient, true /* fHostToGuest */, + VBOX_SHCL_FMT_URI_LIST, &fFiltered); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(fFiltered == VBOX_SHCL_FMT_URI_LIST); +#endif +} + + +/** + * Checks representative hostile guest values at the HGCM protocol boundary. + * + * @param pvClient Connected HGCM client state. + */ +static void tstUntrustedGuestInput(void *pvClient) +{ + RTTestISub("Untrusted guest input"); + + uint64_t const fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_GF_0_TRANSFERS +#endif + ; + int rc = tstGuestCall(pvClient, UINT32_MAX, 0, NULL); + RTTESTI_CHECK_RC(rc, VERR_NOT_IMPLEMENTED); + + VBOXHGCMSVCPARM aFeatures[2]; + HGCMSvcSetU64(&aFeatures[0], fGuestFeatures0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, 1, aFeatures); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_COUNT); + + HGCMSvcSetU64(&aFeatures[0], fGuestFeatures0); + HGCMSvcSetU32(&aFeatures[1], 0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_TYPE); + + HGCMSvcSetU64(&aFeatures[0], fGuestFeatures0); + HGCMSvcSetU64(&aFeatures[1], 0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + uint32_t const cGuestFormatsBefore = g_Ext.cGuestFormats; + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU64(&Parm, VBOX_SHCL_FMT_UNICODETEXT); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_TYPE); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_FMT_VALID_MASK + 1); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); + RTTESTI_CHECK(g_Ext.cGuestFormats == cGuestFormatsBefore); + + uint8_t abData[8]; + VBOXHGCMSVCPARM aRead[VBOX_SHCL_CPARMS_DATA_READ]; + HGCMSvcSetU32(&aRead[0], VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); + HGCMSvcSetPv(&aRead[1], abData, sizeof(abData)); + HGCMSvcSetU32(&aRead[2], UINT32_MAX); + uint32_t const cDataReadsBefore = g_Ext.cDataReads; + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + RTTESTI_CHECK(aRead[2].u.uint32 == UINT32_MAX); + + HGCMSvcSetU32(&aRead[0], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetU32(&aRead[1], 0); + HGCMSvcSetU32(&aRead[2], UINT32_MAX); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_TYPE); + RTTESTI_CHECK(g_Ext.cDataReads == cDataReadsBefore); + + VBOXHGCMSVCPARM aWrite[VBOX_SHCL_CPARMS_DATA_WRITE]; + HGCMSvcSetU64(&aWrite[0], VBOX_SHCL_CONTEXTID_MAKE(UINT16_MAX, 0, 1)); + HGCMSvcSetU32(&aWrite[1], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetPv(&aWrite[2], abData, sizeof(abData)); + uint32_t const cDataWritesBefore = g_Ext.cDataWrites; + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWrite), aWrite); + RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); + + HGCMSvcSetU32(&aWrite[1], VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWrite), aWrite); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + RTTESTI_CHECK(g_Ext.cDataWrites == cDataWritesBefore); + + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, 0, NULL); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_COUNT); + HGCMSvcSetU32(&Parm, VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, 1, &Parm); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_COUNT); +} + + +/** + * Checks one deferred host-to-guest message from wakeup through removal. + * + * @param pvClient Connected HGCM client state. + */ +static void tstMessageQueue(void *pvClient) +{ + RTTestISub("Deferred message queue"); + + VBOXHGCMSVCPARM aPeek[2]; + HGCMSvcSetU32(&aPeek[0], 0); + HGCMSvcSetU32(&aPeek[1], 0); + VBOXHGCMCALLHANDLE_TYPEDEF Call; + tstGuestCallStart(pvClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT, RT_ELEMENTS(aPeek), aPeek, &Call); + RTTESTI_CHECK(!Call.fCompleted); + + VBOXHGCMSVCPARM aOtherPeek[2]; + HGCMSvcSetU32(&aOtherPeek[0], 0); + HGCMSvcSetU32(&aOtherPeek[1], 0); + VBOXHGCMCALLHANDLE_TYPEDEF OtherCall; + int rc = RTTestIDisableAssertions(); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + tstGuestCallStart(pvClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT, + RT_ELEMENTS(aOtherPeek), aOtherPeek, &OtherCall); + rc = RTTestIRestoreAssertions(); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(OtherCall.fCompleted); + if (OtherCall.fCompleted) + RTTESTI_CHECK_RC(OtherCall.rc, VERR_RESOURCE_BUSY); + else + { + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_CANCEL, 0, NULL); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + return; + } + + SHCLFORMATS fReported = VBOX_SHCL_FMT_NONE; + rc = g_Ext.Transport.pOps->pfnReportFormatsToGuest(g_Ext.Transport.hClient, + VBOX_SHCL_FMT_UNICODETEXT, &fReported); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(Call.fCompleted); + if (!Call.fCompleted) + { + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_CANCEL, 0, NULL); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(Call.fCompleted); + return; + } + RTTESTI_CHECK_RC(Call.rc, VINF_SUCCESS); + RTTESTI_CHECK(aPeek[0].u.uint32 == VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + RTTESTI_CHECK(aPeek[1].u.uint32 == 2); + RTTESTI_CHECK(fReported == VBOX_SHCL_FMT_UNICODETEXT); + + VBOXHGCMSVCPARM aWrongGet[2]; + HGCMSvcSetU32(&aWrongGet[0], VBOX_SHCL_HOST_MSG_QUIT); + HGCMSvcSetU32(&aWrongGet[1], 0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aWrongGet), aWrongGet); + RTTESTI_CHECK_RC(rc, VERR_MISMATCH); + + HGCMSvcSetU32(&aPeek[0], 0); + HGCMSvcSetU32(&aPeek[1], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT, RT_ELEMENTS(aPeek), aPeek); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(aPeek[0].u.uint32 == VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + RTTESTI_CHECK(aPeek[1].u.uint32 == 2); + + VBOXHGCMSVCPARM aGet[2]; + HGCMSvcSetU32(&aGet[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + HGCMSvcSetU32(&aGet[1], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aGet), aGet); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(aGet[0].u.uint32 == VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + RTTESTI_CHECK(aGet[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + + HGCMSvcSetU32(&aPeek[0], 0); + HGCMSvcSetU32(&aPeek[1], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT, RT_ELEMENTS(aPeek), aPeek); + RTTESTI_CHECK_RC(rc, VERR_TRY_AGAIN); +} + + +/** + * Checks service ownership of a valid guest clipboard-data reply context. + * + * @param pvClient Connected HGCM client state. + */ +static void tstGuestDataReply(void *pvClient) +{ + RTTestISub("Guest data reply context"); + + PSHCLEVENT pEvent = NULL; + int rc = g_Ext.Transport.pOps->pfnReadDataFromGuestAsync(g_Ext.Transport.hClient, + VBOX_SHCL_FMT_UNICODETEXT, &pEvent); + RTTESTI_CHECK_RC_RETV(rc, VINF_SUCCESS); + RTTESTI_CHECK_RETV(pEvent != NULL); + + VBOXHGCMSVCPARM aGet[2]; + HGCMSvcSetU64(&aGet[0], VBOX_SHCL_HOST_MSG_READ_DATA_CID); + HGCMSvcSetU32(&aGet[1], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aGet), aGet); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + uint64_t const uContext = aGet[0].u.uint64; + RTTESTI_CHECK(aGet[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + + static uint8_t const s_abGuestData[] = { 'g', 'u', 'e', 's', 't', '\0' }; + VBOXHGCMSVCPARM aWrite[VBOX_SHCL_CPARMS_DATA_WRITE]; + HGCMSvcSetU64(&aWrite[0], uContext); + HGCMSvcSetU32(&aWrite[1], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetPv(&aWrite[2], (void *)s_abGuestData, sizeof(s_abGuestData)); + uint32_t const cDataWritesBefore = g_Ext.cDataWrites; + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWrite), aWrite); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(g_Ext.cDataWrites == cDataWritesBefore + 1); + + PSHCLEVENTPAYLOAD pPayload = NULL; + rc = ShClEventWait(pEvent, RT_MS_1SEC, &pPayload); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK(pPayload != NULL); + if (pPayload) + { + RTTESTI_CHECK(pPayload->cbData == sizeof(s_abGuestData)); + RTTESTI_CHECK(memcmp(pPayload->pvData, s_abGuestData, sizeof(s_abGuestData)) == 0); + ShClPayloadDestroy(pPayload); + } + } + RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); +} + + +/** + * Checks the two central guest-to-host POD calls and mode enforcement. + * + * @param pvClient Connected HGCM client state. + */ +static void tstGuestPodCalls(void *pvClient) +{ + RTTestISub("Guest POD protocol"); + + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, VBOX_SHCL_FMT_UNICODETEXT); + int rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(g_Ext.cGuestFormats == 1); + RTTESTI_CHECK(g_Ext.fGuestFormats == VBOX_SHCL_FMT_UNICODETEXT); + + uint8_t abData[sizeof(g_abHostData)]; + VBOXHGCMSVCPARM aRead[3]; + HGCMSvcSetU32(&aRead[0], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetPv(&aRead[1], abData, sizeof(abData)); + HGCMSvcSetU32(&aRead[2], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(aRead[2].u.uint32 == sizeof(g_abHostData)); + RTTESTI_CHECK(memcmp(abData, g_abHostData, sizeof(g_abHostData)) == 0); + + uint8_t bData = 0; + HGCMSvcSetPv(&aRead[1], &bData, sizeof(bData)); + HGCMSvcSetU32(&aRead[2], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VINF_BUFFER_OVERFLOW); + RTTESTI_CHECK(aRead[2].u.uint32 == sizeof(g_abHostData)); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_MODE_HOST_TO_GUEST); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + HGCMSvcSetU32(&Parm, VBOX_SHCL_FMT_UNICODETEXT); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(g_Ext.cGuestFormats == 1); +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Initializes a guest transfer-status reply. */ +static void tstTransferReplyInit(VBOXHGCMSVCPARM aParms[VBOX_SHCL_CPARMS_REPLY_MIN + 1], + uint64_t uContext, SHCLTRANSFERSTATUS enmStatus) +{ + HGCMSvcSetU64(&aParms[0], uContext); + HGCMSvcSetU32(&aParms[1], VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS); + HGCMSvcSetU32(&aParms[2], VINF_SUCCESS); + HGCMSvcSetPv(&aParms[3], NULL, 0); + HGCMSvcSetU32(&aParms[4], enmStatus); +} + + +/** Gets and checks one service-owned transfer status message. */ +static void tstTransferStatusGet(void *pvClient, SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, + SHCLTRANSFERSTATUS enmStatus, int rcTransfer) +{ + VBOXHGCMSVCPARM aParms[VBOX_SHCL_CPARMS_TRANSFER_STATUS]; + HGCMSvcSetU64(&aParms[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aParms[1], 0); + HGCMSvcSetU32(&aParms[2], 0); + HGCMSvcSetU32(&aParms[3], 0); + HGCMSvcSetU32(&aParms[4], 0); + + int const rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aParms), aParms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + uint64_t const uContext = aParms[0].u.uint64; + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(uContext) == idSession); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(uContext) == idTransfer); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != 0); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != NIL_SHCLEVENTID); + RTTESTI_CHECK(aParms[1].u.uint32 == SHCLTRANSFERDIR_TO_REMOTE); + RTTESTI_CHECK(aParms[2].u.uint32 == enmStatus); + RTTESTI_CHECK((int32_t)aParms[3].u.uint32 == rcTransfer); + RTTESTI_CHECK(aParms[4].u.uint32 == 0); +} + + +/** + * Checks transfer gates, hostile identifiers and one complete service-owned lifecycle. + * + * @param pvClient Connected HGCM client state. + */ +static void tstTransfers(void *pvClient) +{ + RTTestISub("Clipboard transfer protocol"); + + VBOXHGCMSVCPARM aObjectClose[VBOX_SHCL_CPARMS_OBJ_CLOSE]; + HGCMSvcSetU64(&aObjectClose[0], VBOX_SHCL_CONTEXTID_MAKE(UINT16_MAX, 42, 0)); + HGCMSvcSetU64(&aObjectClose[1], 1); + + VBOXHGCMSVCPARM aFeatures[2]; + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + int rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, + RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + PSHCLTRANSFER pDeniedTransfer = (PSHCLTRANSFER)(uintptr_t)1; + rc = g_Ext.Transport.pOps->pfnTransferCreate(g_Ext.Transport.hClient, SHCLTRANSFERDIR_FROM_REMOTE, + SHCLSOURCE_REMOTE, NULL, NIL_SHCLTRANSFERID, &pDeniedTransfer); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(pDeniedTransfer == NULL); + + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, + RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_NONE); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + pDeniedTransfer = (PSHCLTRANSFER)(uintptr_t)1; + rc = g_Ext.Transport.pOps->pfnTransferCreate(g_Ext.Transport.hClient, SHCLTRANSFERDIR_FROM_REMOTE, + SHCLSOURCE_REMOTE, NULL, NIL_SHCLTRANSFERID, &pDeniedTransfer); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(pDeniedTransfer == NULL); + + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, 0, NULL); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU32(&aObjectClose[0], 0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, + RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_TYPE); + + HGCMSvcSetU64(&aObjectClose[0], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); + + VBOXHGCMSVCPARM aReply[VBOX_SHCL_CPARMS_REPLY_MIN + 1]; + tstTransferReplyInit(aReply, 0, SHCLTRANSFERSTATUS_REQUESTED); + uint32_t const cQueriesBefore = g_Ext.cTransferCallbackQueries; + uint32_t const cStatusesBefore = g_Ext.cTransferStatuses; + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN, aReply); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + RTTESTI_CHECK(g_Ext.cTransferCallbackQueries == cQueriesBefore); + RTTESTI_CHECK(g_Ext.cTransferStatuses == cStatusesBefore); + + tstTransferReplyInit(aReply, 0, SHCLTRANSFERSTATUS_REQUESTED); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPLY, RT_ELEMENTS(aReply), aReply); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(g_Ext.cTransferCallbackQueries == cQueriesBefore + 1); + RTTESTI_CHECK(g_Ext.cTransferStatuses == cStatusesBefore + 1); + SHCLSESSIONID const idSession = g_Ext.idTransferSession; + RTTESTI_CHECK(idSession != NIL_SHCLSESSIONID); + RTTESTI_CHECK(g_Ext.idTransfer != NIL_SHCLTRANSFERID); + RTTESTI_CHECK(g_Ext.uTransferGeneration != NIL_SHCLTRANSFERGEN); + RTTESTI_CHECK(g_Ext.enmTransferDir == SHCLTRANSFERDIR_TO_REMOTE); + RTTESTI_CHECK(g_Ext.enmTransferSource == SHCLSOURCE_REMOTE); + RTTESTI_CHECK(g_Ext.enmTransferStatus == SHCLTRANSFERSTATUS_REQUESTED); + RTTESTI_CHECK(g_Ext.rcTransfer == VINF_SUCCESS); + + uint32_t const cQueriesAfterRequest = g_Ext.cTransferCallbackQueries; + uint32_t const cStatusesAfterRequest = g_Ext.cTransferStatuses; + tstTransferReplyInit(aReply, VBOX_SHCL_CONTEXTID_MAKE(idSession, g_Ext.idTransfer + 1, 0), + SHCLTRANSFERSTATUS_REQUESTED); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPLY, RT_ELEMENTS(aReply), aReply); + RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); + RTTESTI_CHECK(g_Ext.cTransferCallbackQueries == cQueriesAfterRequest); + RTTESTI_CHECK(g_Ext.cTransferStatuses == cStatusesAfterRequest); + + HGCMSvcSetU64(&aObjectClose[0], VBOX_SHCL_CONTEXTID_MAKE(idSession + 1, g_Ext.idTransfer, 0)); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); + HGCMSvcSetU64(&aObjectClose[0], VBOX_SHCL_CONTEXTID_MAKE(idSession, g_Ext.idTransfer + 1, 0)); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); + + tstTransferStatusGet(pvClient, idSession, g_Ext.idTransfer, SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS); + + PSHCLTRANSFER pTransfer = g_Ext.Transport.pOps->pfnTransferGetByIdRetained(g_Ext.Transport.hClient, + g_Ext.idTransfer); + RTTESTI_CHECK(pTransfer != NULL); + if (pTransfer) + { + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, + RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + rc = g_Ext.Transport.pOps->pfnTransferInit(g_Ext.Transport.hClient, pTransfer); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, + RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + ShClTransferRelease(pTransfer); + } + + VBOXHGCMSVCPARM aCancel[2]; + HGCMSvcSetU64(&aCancel[0], VBOX_SHCL_CONTEXTID_MAKE(idSession, g_Ext.idTransfer, 0)); + HGCMSvcSetU64(&aCancel[1], g_Ext.uTransferGeneration + 1); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_CANCEL, RT_ELEMENTS(aCancel), aCancel); + RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); + + HGCMSvcSetU64(&aCancel[1], g_Ext.uTransferGeneration); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_CANCEL, RT_ELEMENTS(aCancel), aCancel); + RTTESTI_CHECK_RC_OK(rc); + tstTransferStatusGet(pvClient, idSession, g_Ext.idTransfer, SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + + HGCMSvcSetU64(&aObjectClose[0], VBOX_SHCL_CONTEXTID_MAKE(idSession, g_Ext.idTransfer, 0)); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_CANCEL, RT_ELEMENTS(aCancel), aCancel); + RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +/** + * Disconnects the client and unloads the service. + * + * @param pvClient Connected HGCM client state. May be NULL. + */ +static void tstShutdown(void *pvClient) +{ + RTTestISub("Service shutdown"); + + int rc; + if (pvClient) + { + rc = g_Table.pfnDisconnect(g_Table.pvService, 1, pvClient); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(g_Ext.cDisconnect == 1); + RTMemFree(pvClient); + } + + rc = g_Table.pfnRegisterExtension(g_Table.pvService, NULL, NULL); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + rc = g_Table.pfnUnload(g_Table.pvService); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +} + + +/** Testcase entry point. */ +int main(void) +{ + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardHostService", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + void *pvClient = NULL; + int const rc = tstLoadService(); + if (RT_SUCCESS(rc)) + { + tstConnection(&pvClient); + if (pvClient) + { + tstPolicyAndFeatures(pvClient); + tstUntrustedGuestInput(pvClient); + tstMessageQueue(pvClient); + tstGuestDataReply(pvClient); + tstGuestPodCalls(pvClient); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstTransfers(pvClient); +#endif + } + tstShutdown(pvClient); + } + else + RTTestIFailed("Loading the host service failed: %Rrc", rc); + + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp deleted file mode 100644 index 20586e0610eb..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp +++ /dev/null @@ -1,1280 +0,0 @@ -/* $Id: tstClipboardTransfers.cpp 114907 2026-08-10 08:44:49Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard transfers test case. - */ - -/* - * Copyright (C) 2019-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - - -/** @name TST_SHCL_TRANSFER_STATUS_F_XXX - Testcase transfer status flags. - * - * Each flag uses the numeric SHCLTRANSFERSTATUS value as its bit position. - * This lets expected transition masks name statuses directly and keeps them - * independent of the order of the status array used to exercise the masks. - * @{ */ -#define TST_SHCL_TRANSFER_STATUS_F(a_enmStatus) RT_BIT_32(a_enmStatus) -#define TST_SHCL_TRANSFER_STATUS_F_NONE TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_NONE) -#define TST_SHCL_TRANSFER_STATUS_F_REQUESTED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_REQUESTED) -#define TST_SHCL_TRANSFER_STATUS_F_INITIALIZED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_INITIALIZED) -#define TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_UNINITIALIZED) -#define TST_SHCL_TRANSFER_STATUS_F_STARTED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_STARTED) -#define TST_SHCL_TRANSFER_STATUS_F_COMPLETED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_COMPLETED) -#define TST_SHCL_TRANSFER_STATUS_F_CANCELED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_CANCELED) -#define TST_SHCL_TRANSFER_STATUS_F_KILLED TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_KILLED) -#define TST_SHCL_TRANSFER_STATUS_F_ERROR TST_SHCL_TRANSFER_STATUS_F(SHCLTRANSFERSTATUS_ERROR) -/** @} */ - - -static int testCreateTempDir(RTTEST hTest, const char *pszTestcase, char *pszTempDir, size_t cbTempDir) -{ - char szTempDir[RTPATH_MAX]; - int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirCreate(szTempDir, 0700, 0); - if (rc == VERR_ALREADY_EXISTS) - rc = VINF_SUCCESS; - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "XXXXX"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirCreateTemp(szTempDir, 0700); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - char szTempDirReal[RTPATH_MAX]; - rc = RTPathReal(szTempDir, szTempDirReal, sizeof(szTempDirReal)); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathJoin(pszTempDir, cbTempDir, szTempDirReal, pszTestcase); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Created temporary directory: %s\n", pszTempDir); - - return rc; -} - -static int testRemoveTempDir(RTTEST hTest) -{ - char szTempDir[RTPATH_MAX]; - int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirRemoveRecursive(szTempDir, RTDIRRMREC_F_CONTENT_AND_DIR); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Removed temporary directory: %s\n", szTempDir); - - return rc; -} - -static int testCreateDir(RTTEST hTest, const char *pszPathToCreate) -{ - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating directory: %s\n", pszPathToCreate); - - int rc = RTDirCreateFullPath(pszPathToCreate, 0700); - if (rc == VERR_ALREADY_EXISTS) - rc = VINF_SUCCESS; - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - return rc; -} - -static int testCreateFile(RTTEST hTest, const char *pszTempDir, const char *pszFileName, uint32_t fOpen, size_t cbSize, - char **ppszFilePathAbs) -{ - char szFilePath[RTPATH_MAX]; - - int rc = RTStrCopy(szFilePath, sizeof(szFilePath), pszTempDir); - RTTESTI_CHECK_RC_OK_RET(rc, rc); - - rc = RTPathAppend(szFilePath, sizeof(szFilePath), pszFileName); - RTTESTI_CHECK_RC_OK_RET(rc, rc); - - char *pszDirToCreate = RTStrDup(szFilePath); - RTTESTI_CHECK_RET(pszDirToCreate, VERR_NO_MEMORY); - - RTPathStripFilename(pszDirToCreate); - - rc = testCreateDir(hTest, pszDirToCreate); - RTTESTI_CHECK_RC_OK_RET(rc, rc); - - RTStrFree(pszDirToCreate); - pszDirToCreate = NULL; - - if (!fOpen) - fOpen = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE; - - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating file: %s\n", szFilePath); - - RTFILE hFile; - rc = RTFileOpen(&hFile, szFilePath, fOpen); - if (RT_SUCCESS(rc)) - { - if (cbSize) - { - /** @todo Fill in some random stuff. */ - } - - rc = RTFileClose(hFile); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - } - - if (ppszFilePathAbs) - *ppszFilePathAbs = RTStrDup(szFilePath); - - return rc; -} - -typedef struct TESTTRANSFERROOTENTRY -{ - TESTTRANSFERROOTENTRY(const RTCString &a_strPath) - : strPath(a_strPath) { } - - RTCString strPath; -} TESTTRANSFERROOTENTRY; - -static int testAddRootEntry(RTTEST hTest, const char *pszTempDir, - const TESTTRANSFERROOTENTRY &rootEntry, char **ppszRoots) -{ - char *pszRoots = NULL; - - const char *pszPath = rootEntry.strPath.c_str(); - - char *pszPathAbs; - int rc = testCreateFile(hTest, pszTempDir, pszPath, 0, 0, &pszPathAbs); - RTTESTI_CHECK_RC_OK_RET(rc, rc); - - rc = RTStrAAppend(&pszRoots, pszPathAbs); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszPathAbs); - - *ppszRoots = pszRoots; - - return rc; -} - -static int testAddRootEntries(RTTEST hTest, const char *pszTempDir, - RTCList &lstBase, RTCList lstToExtend, - char **ppszRoots) -{ - int rc = VINF_SUCCESS; - - char *pszRoots = NULL; - - for (size_t i = 0; i < lstBase.size(); ++i) - { - char *pszEntry = NULL; - rc = testAddRootEntry(hTest, pszTempDir, lstBase.at(i), &pszEntry); - RTTESTI_CHECK_RC_OK_BREAK(rc); - rc = RTStrAAppend(&pszRoots, pszEntry); - RTTESTI_CHECK_RC_OK_BREAK(rc); - RTStrFree(pszEntry); - } - - for (size_t i = 0; i < lstToExtend.size(); ++i) - { - char *pszEntry = NULL; - rc = testAddRootEntry(hTest, pszTempDir, lstToExtend.at(i), &pszEntry); - RTTESTI_CHECK_RC_OK_BREAK(rc); - rc = RTStrAAppend(&pszRoots, pszEntry); - RTTESTI_CHECK_RC_OK_BREAK(rc); - RTStrFree(pszEntry); - } - - if (RT_SUCCESS(rc)) - *ppszRoots = pszRoots; - - return rc; -} - -static void testTransferRootsSetSingle(RTTEST hTest, - RTCList &lstBase, RTCList lstToExtend, - int rcExpected) -{ - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - SHCLTXPROVIDER Provider; - RTTESTI_CHECK(ShClTransferProviderLocalQueryInterface(&Provider) != NULL); - RTTESTI_CHECK_RC_OK(ShClTransferSetProvider(pTransfer, &Provider)); - - char szTestTransferRootsSetDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferRootsSet", szTestTransferRootsSetDir, sizeof(szTestTransferRootsSetDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - /* This is the file we're trying to access (but not supposed to). */ - rc = testCreateFile(hTest, szTestTransferRootsSetDir, "must-not-access-this", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - char *pszRoots; - rc = testAddRootEntries(hTest, szTestTransferRootsSetDir, lstBase, lstToExtend, &pszRoots); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC(rc, rcExpected); - - RTStrFree(pszRoots); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -static void testTransferObjOpenSingle(RTTEST hTest, - RTCList &lstRoots, const char *pszObjPath, int rcExpected) -{ - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferObjOpenDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferObjOpen", szTestTransferObjOpenDir, sizeof(szTestTransferObjOpenDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - /* This is the file we're trying to access (but not supposed to). */ - rc = testCreateFile(hTest, szTestTransferObjOpenDir, "file1.txt", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - RTCList lstToExtendEmpty; - - char *pszRoots; - rc = testAddRootEntries(hTest, szTestTransferObjOpenDir, lstRoots, lstToExtendEmpty, &pszRoots); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLOBJOPENCREATEPARMS openCreateParms; - rc = ShClTransferObjOpenParmsInit(&openCreateParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, pszObjPath); - RTTESTI_CHECK_RC_OK(rc); - - SHCLOBJHANDLE hObj; - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC(rc, rcExpected); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferObjClose(pTransfer, hObj); - RTTESTI_CHECK_RC_OK(rc); - } - - ShClTransferObjOpenParmsDestroy(&openCreateParms); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -static void testPathSanitize(void) -{ - RTTestISub("Testing path sanitizing"); - - /* Valid dotted names: expect success and no path changes. */ - char szValid[] = "dir.with.dots/file...txt"; - int rc = ShClPathSanitize(szValid, sizeof(szValid)); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - RTTESTI_CHECK(!strcmp(szValid, "dir.with.dots/file...txt")); - - /* Current-directory components are not transferable. */ - char szDot[] = "dir/./file.txt"; - rc = ShClPathSanitize(szDot, sizeof(szDot)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - /* Parent-directory components are not transferable. */ - char szDotDot[] = "dir/../file.txt"; - rc = ShClPathSanitize(szDotDot, sizeof(szDotDot)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - /* Malformed UTF-8 paths are rejected before use. */ - uint8_t abInvalidUtf8[] = { 0xc0, 0xaf, 0 }; - rc = ShClPathSanitize((char *)abInvalidUtf8, sizeof(abInvalidUtf8)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_UTF8_ENCODING); - - /* Unterminated bounded path buffers are rejected. */ - char szUnterminated[] = { 'a', 'b' }; - rc = ShClPathSanitize(szUnterminated, sizeof(szUnterminated)); - RTTESTI_CHECK_RC(rc, VERR_BUFFER_OVERFLOW); -} - -typedef struct TESTEVENTWAITCTX -{ - PSHCLEVENT pEvent; - PSHCLEVENTPAYLOAD pPayload; - int rcWait; -} TESTEVENTWAITCTX; -typedef TESTEVENTWAITCTX *PTESTEVENTWAITCTX; - -static DECLCALLBACK(int) testEventWaitThread(RTTHREAD hThread, void *pvUser) -{ - PTESTEVENTWAITCTX pCtx = (PTESTEVENTWAITCTX)pvUser; - - int rc = RTThreadUserSignal(hThread); - if (RT_SUCCESS(rc)) - pCtx->rcWait = rc = ShClEventWait(pCtx->pEvent, RT_MS_5SEC, &pCtx->pPayload); - return rc; -} - -static DECLCALLBACK(int) testEventReleaseThread(RTTHREAD hThread, void *pvUser) -{ - int rc = RTThreadUserSignal(hThread); - if (RT_SUCCESS(rc)) - rc = ShClEventRelease((PSHCLEVENT)pvUser) == 0 ? VINF_SUCCESS : VERR_INTERNAL_ERROR; - return rc; -} - -static void testEvents(void) -{ - RTTestISub("Testing events"); - - SHCLEVENTSOURCE Source; - RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 0)); - RTTESTI_CHECK(ShClEventSourceGetLast(&Source) == NULL); /* Should be empty. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); /* Destroying a second time, intentional. */ - - RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); - PSHCLEVENT pEvent; - RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); - - uint32_t const uPayloadData = UINT32_C(0x12345678); - PSHCLEVENTPAYLOAD pPayload = NULL; - RTTESTI_CHECK_RC_OK(ShClPayloadCreateDupData(42, &uPayloadData, sizeof(uPayloadData), &pPayload)); - int rc = ShClEventSignal(pEvent, pPayload); - RTTESTI_CHECK_RC_OK(rc); - if (RT_FAILURE(rc)) - ShClPayloadDestroy(pPayload); - - /* Reset must not destroy resources owned by an event which is still referenced. */ - ShClEventSourceReset(&Source); - RTTESTI_CHECK(ShClEventSourceGetLast(&Source) == NULL); /* Event still valid, but removed from the source. */ - - PSHCLEVENTPAYLOAD pPayloadResult = NULL; - RTTESTI_CHECK_RC_OK(ShClEventWait(pEvent, 0, &pPayloadResult)); - RTTESTI_CHECK(pPayloadResult != NULL); - if (pPayloadResult) - { - RTTESTI_CHECK(pPayloadResult->uID == 42); - RTTESTI_CHECK(pPayloadResult->cbData == sizeof(uPayloadData)); - RTTESTI_CHECK(pPayloadResult->pvData != NULL); - if (pPayloadResult->pvData) - RTTESTI_CHECK(*(uint32_t *)pPayloadResult->pvData == uPayloadData); - ShClPayloadDestroy(pPayloadResult); - } - - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); /* Free'd event, as ref count is 0. */ - RTTESTI_CHECK(ShClEventSourceGetLast(&Source) == NULL); /* Now it should be empty. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); - - /* Reset an event source while another thread waits on one of its events. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); - RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); - - TESTEVENTWAITCTX WaitCtx; - RT_ZERO(WaitCtx); - WaitCtx.pEvent = pEvent; - WaitCtx.rcWait = VERR_IPE_UNINITIALIZED_STATUS; - - RTTHREAD hThread; - rc = RTThreadCreate(&hThread, testEventWaitThread, &WaitCtx, 0, RTTHREADTYPE_DEFAULT, - RTTHREADFLAGS_WAITABLE, "ShClEvtWait"); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - { - RTTESTI_CHECK_RC_OK(RTThreadUserWait(hThread, RT_MS_5SEC)); - RTThreadSleep(10); /* Let the thread enter ShClEventWait(). */ - - ShClEventSourceReset(&Source); - RTTESTI_CHECK_RC_OK(ShClEventSignal(pEvent, NULL)); - - int rcThread; - int rcWait = RTThreadWait(hThread, RT_MS_5SEC, &rcThread); - RTTESTI_CHECK_RC_OK(rcWait); - if (RT_FAILURE(rcWait)) /* Do not release the event while the waiter might still be using it. */ - rcWait = RTThreadWait(hThread, RT_INDEFINITE_WAIT, &rcThread); - if (RT_SUCCESS(rcWait)) - { - RTTESTI_CHECK_RC_OK(rcThread); - RTTESTI_CHECK_RC_OK(WaitCtx.rcWait); - RTTESTI_CHECK(WaitCtx.pPayload == NULL); - } - } - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); - - /* A final release must take the source lock before publishing a zero reference count. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); - RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); - RTTESTI_CHECK_RC_OK(RTCritSectEnter(&Source.CritSect)); - - rc = RTThreadCreate(&hThread, testEventReleaseThread, pEvent, 0, RTTHREADTYPE_DEFAULT, - RTTHREADFLAGS_WAITABLE, "ShClEvtRel"); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - { - RTTESTI_CHECK_RC_OK(RTThreadUserWait(hThread, RT_MS_5SEC)); - for (unsigned i = 0; i < 1000 && RTCritSectGetWaiters(&Source.CritSect) == 0; ++i) - RTThreadSleep(1); - RTTESTI_CHECK(RTCritSectGetWaiters(&Source.CritSect) > 0); - RTTESTI_CHECK(ShClEventGetRefs(pEvent) == 1); - - ShClEventSourceReset(&Source); - } - RTTESTI_CHECK_RC_OK(RTCritSectLeave(&Source.CritSect)); - if (RT_SUCCESS(rc)) - { - int rcThread; - int rcWait = RTThreadWait(hThread, RT_MS_5SEC, &rcThread); - RTTESTI_CHECK_RC_OK(rcWait); - if (RT_FAILURE(rcWait)) /* Do not terminate the source while the releaser might still be using it. */ - rcWait = RTThreadWait(hThread, RT_INDEFINITE_WAIT, &rcThread); - if (RT_SUCCESS(rcWait)) - RTTESTI_CHECK_RC_OK(rcThread); - } - else - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); - - /* Test delayed destruction of the event by retaining it. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); - RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); - RTTESTI_CHECK_RC_OK(ShClEventRetain(pEvent)); - RTTESTI_CHECK(ShClEventGetRefs(pEvent) == 2); - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); - RTTESTI_CHECK(ShClEventGetRefs(pEvent) == 2); /* Make sure the ref count didn't drop due to ShClEventSourceDestroy(). */ - RTTESTI_CHECK(ShClEventRelease(pEvent) == 1); - RTTESTI_CHECK(ShClEventGetRefs(pEvent) == 1); - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); /* Free'd event, as ref count is 0. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); /* Try to destruct again. */ -} - -static void testTransferBasics(void) -{ - RTTestISub("Testing transfer basics"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClTransferGetID(pTransfer) == NIL_SHCLTRANSFERID); - RTTESTI_CHECK(ShClTransferGetSessionId(pTransfer) == NIL_SHCLSESSIONID); - RTTESTI_CHECK(ShClTransferGetGeneration(pTransfer) == NIL_SHCLTRANSFERGEN); - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - pTransfer = NULL; /* Was free'd above. */ - rc = ShClTransferDestroy(pTransfer); /* Second time, intentional. */ - RTTESTI_CHECK_RC_OK(rc); - - PSHCLLIST pList = ShClTransferListAlloc(); - RTTESTI_CHECK(pList != NULL); - rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - ShClTransferListFree(pList); - pList = NULL; - ShClTransferListFree(pList); /* Second time, intentional. */ - - SHCLLISTENTRY Entry; - RTTESTI_CHECK_RC_OK(ShClTransferListEntryInit(&Entry)); - ShClTransferListEntryDestroy(&Entry); - ShClTransferListEntryDestroy(&Entry); /* Second time, intentional. */ - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -/** - * Tests common Shared Clipboard and transfer validation. - */ -static void testTransferValidation(void) -{ - RTTestISub("Testing Shared Clipboard validation"); - - RTTESTI_CHECK(ShClFormatIsValid(VBOX_SHCL_FMT_UNICODETEXT)); - RTTESTI_CHECK(ShClFormatIsValid(VBOX_SHCL_FMT_BITMAP)); - RTTESTI_CHECK(ShClFormatIsValid(VBOX_SHCL_FMT_HTML)); - RTTESTI_CHECK(ShClFormatIsValid(VBOX_SHCL_FMT_URI_LIST)); - RTTESTI_CHECK(!ShClFormatIsValid(VBOX_SHCL_FMT_NONE)); - RTTESTI_CHECK(!ShClFormatIsValid(VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP)); - RTTESTI_CHECK(!ShClFormatIsValid(VBOX_SHCL_FMT_VALID_MASK + 1)); - - RTTESTI_CHECK(ShClFormatsAreValid(VBOX_SHCL_FMT_NONE)); - RTTESTI_CHECK(ShClFormatsAreValid(VBOX_SHCL_FMT_VALID_MASK)); - RTTESTI_CHECK(!ShClFormatsAreValid(VBOX_SHCL_FMT_VALID_MASK + 1)); - - RTTESTI_CHECK(ShClTransferDirIsValid(SHCLTRANSFERDIR_FROM_REMOTE)); - RTTESTI_CHECK(ShClTransferDirIsValid(SHCLTRANSFERDIR_TO_REMOTE)); - RTTESTI_CHECK(!ShClTransferDirIsValid(SHCLTRANSFERDIR_UNKNOWN)); - RTTESTI_CHECK(!ShClTransferDirIsValid(SHCLTRANSFERDIR_32BIT_HACK)); - - RTTESTI_CHECK(ShClSourceIsValid(SHCLSOURCE_LOCAL)); - RTTESTI_CHECK(ShClSourceIsValid(SHCLSOURCE_REMOTE)); - RTTESTI_CHECK(!ShClSourceIsValid(SHCLSOURCE_INVALID)); - RTTESTI_CHECK(!ShClSourceIsValid(SHCLSOURCE_32BIT_HACK)); - - RTTESTI_CHECK(ShClTransferIdIsValid(1)); - RTTESTI_CHECK(ShClTransferIdIsValid(VBOX_SHCL_MAX_TRANSFERS - 2)); - RTTESTI_CHECK(!ShClTransferIdIsValid(0)); - RTTESTI_CHECK(!ShClTransferIdIsValid(VBOX_SHCL_MAX_TRANSFERS - 1)); - RTTESTI_CHECK(!ShClTransferIdIsValid(NIL_SHCLTRANSFERID)); - RTTESTI_CHECK(!ShClTransferIdIsValid(UINT32_MAX)); - - RTTESTI_CHECK(ShClTransferKeyIsValid(1, 1, 1)); - RTTESTI_CHECK(ShClTransferKeyIsValid(1, VBOX_SHCL_MAX_TRANSFERS - 2, 1)); - RTTESTI_CHECK(!ShClTransferKeyIsValid(0, 1, 1)); - RTTESTI_CHECK(!ShClTransferKeyIsValid(NIL_SHCLSESSIONID, 1, 1)); - RTTESTI_CHECK(!ShClTransferKeyIsValid(1, 0, 1)); - RTTESTI_CHECK(!ShClTransferKeyIsValid(1, VBOX_SHCL_MAX_TRANSFERS - 1, 1)); - RTTESTI_CHECK(!ShClTransferKeyIsValid(1, NIL_SHCLTRANSFERID, 1)); - RTTESTI_CHECK(!ShClTransferKeyIsValid(1, UINT32_MAX, 1)); - RTTESTI_CHECK(!ShClTransferKeyIsValid(1, 1, 0)); - RTTESTI_CHECK(!ShClTransferKeyIsValid(1, 1, NIL_SHCLTRANSFERGEN)); - - /** Expected classification and transitions for each transfer status. */ - static struct - { - /** Transfer status under test. */ - SHCLTRANSFERSTATUS enmStatus; - /** Whether the status is terminal. */ - bool fTerminal; - /** Flags identifying statuses to which the status may transition. */ - uint32_t fTransitions; - } const s_aStatusTests[] = - { - { SHCLTRANSFERSTATUS_NONE, false, TST_SHCL_TRANSFER_STATUS_F_NONE }, - { SHCLTRANSFERSTATUS_REQUESTED, false, TST_SHCL_TRANSFER_STATUS_F_REQUESTED | TST_SHCL_TRANSFER_STATUS_F_INITIALIZED | TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED | TST_SHCL_TRANSFER_STATUS_F_CANCELED | TST_SHCL_TRANSFER_STATUS_F_KILLED | TST_SHCL_TRANSFER_STATUS_F_ERROR }, - { SHCLTRANSFERSTATUS_INITIALIZED, false, TST_SHCL_TRANSFER_STATUS_F_INITIALIZED | TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED | TST_SHCL_TRANSFER_STATUS_F_STARTED | TST_SHCL_TRANSFER_STATUS_F_COMPLETED | TST_SHCL_TRANSFER_STATUS_F_CANCELED | TST_SHCL_TRANSFER_STATUS_F_KILLED | TST_SHCL_TRANSFER_STATUS_F_ERROR }, - { SHCLTRANSFERSTATUS_UNINITIALIZED, true, TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED }, - { SHCLTRANSFERSTATUS_STARTED, false, TST_SHCL_TRANSFER_STATUS_F_UNINITIALIZED | TST_SHCL_TRANSFER_STATUS_F_STARTED | TST_SHCL_TRANSFER_STATUS_F_COMPLETED | TST_SHCL_TRANSFER_STATUS_F_CANCELED | TST_SHCL_TRANSFER_STATUS_F_KILLED | TST_SHCL_TRANSFER_STATUS_F_ERROR }, - { SHCLTRANSFERSTATUS_COMPLETED, true, TST_SHCL_TRANSFER_STATUS_F_COMPLETED }, - { SHCLTRANSFERSTATUS_CANCELED, true, TST_SHCL_TRANSFER_STATUS_F_CANCELED }, - { SHCLTRANSFERSTATUS_KILLED, true, TST_SHCL_TRANSFER_STATUS_F_KILLED }, - { SHCLTRANSFERSTATUS_ERROR, true, TST_SHCL_TRANSFER_STATUS_F_ERROR } - }; - - for (size_t i = 0; i < RT_ELEMENTS(s_aStatusTests); ++i) - { - SHCLTRANSFERSTATUS const enmStatus = s_aStatusTests[i].enmStatus; - RTTESTI_CHECK(ShClTransferStatusIsValid(enmStatus)); - RTTESTI_CHECK(ShClTransferStatusIsTerminal(enmStatus) == s_aStatusTests[i].fTerminal); - - bool const fFailureStatus = enmStatus == SHCLTRANSFERSTATUS_KILLED - || enmStatus == SHCLTRANSFERSTATUS_ERROR; - RTTESTI_CHECK(ShClTransferStatusResultIsValid(enmStatus, VINF_SUCCESS) - == ( !fFailureStatus - && enmStatus != SHCLTRANSFERSTATUS_CANCELED)); - RTTESTI_CHECK(ShClTransferStatusResultIsValid(enmStatus, VERR_GENERAL_FAILURE) == fFailureStatus); - RTTESTI_CHECK(ShClTransferStatusResultIsValid(enmStatus, VERR_CANCELLED) - == (fFailureStatus || enmStatus == SHCLTRANSFERSTATUS_CANCELED)); - - for (size_t j = 0; j < RT_ELEMENTS(s_aStatusTests); ++j) - RTTESTI_CHECK(ShClTransferStatusTransitionIsValid(enmStatus, s_aStatusTests[j].enmStatus) - == RT_BOOL(s_aStatusTests[i].fTransitions - & TST_SHCL_TRANSFER_STATUS_F(s_aStatusTests[j].enmStatus))); - } - - SHCLTRANSFERSTATUS const enmInvalid = UINT32_C(0xfeed); - RTTESTI_CHECK(!ShClTransferStatusIsValid(SHCLTRANSFERSTATUS_32BIT_SIZE_HACK)); - RTTESTI_CHECK(!ShClTransferStatusIsValid(enmInvalid)); - RTTESTI_CHECK(!ShClTransferStatusIsTerminal(enmInvalid)); - RTTESTI_CHECK(!ShClTransferStatusResultIsValid(enmInvalid, VINF_SUCCESS)); - RTTESTI_CHECK(!ShClTransferStatusTransitionIsValid(enmInvalid, enmInvalid)); - RTTESTI_CHECK(!ShClTransferStatusTransitionIsValid(SHCLTRANSFERSTATUS_REQUESTED, enmInvalid)); -} - -/** - * Tests zero-length object data chunk duplication. - */ -static void testTransferObjDataChunkDupZeroLength(void) -{ - RTTestISub("Testing zero-length transfer object data chunk duplication"); - - SHCLOBJDATACHUNK Chunk; - RT_ZERO(Chunk); - Chunk.uHandle = 42; - Chunk.pvData = NULL; - Chunk.cbData = 0; - - PSHCLOBJDATACHUNK pDup = ShClTransferObjDataChunkDup(&Chunk); - RTTESTI_CHECK_RETV(pDup != NULL); - RTTESTI_CHECK(pDup->uHandle == Chunk.uHandle); - RTTESTI_CHECK(pDup->pvData == NULL); - RTTESTI_CHECK(pDup->cbData == 0); - ShClTransferObjDataChunkFree(pDup); - - Chunk.cbData = 1; - pDup = ShClTransferObjDataChunkDup(&Chunk); - RTTESTI_CHECK(pDup == NULL); -} - - -static void testTransferContextIdentity(void) -{ - RTTestISub("Testing transfer context identity"); - - SHCLTRANSFERCTX Ctx; - int rc = ShClTransferCtxInit(&Ctx); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferCtxBeginSession(&Ctx, 7); - RTTESTI_CHECK_RC_OK(rc); - - PSHCLTRANSFER pTransfer; - rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; - rc = ShClTransferCtxRegister(&Ctx, pTransfer, &idTransfer); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(idTransfer != NIL_SHCLTRANSFERID); - RTTESTI_CHECK(ShClTransferGetSessionId(pTransfer) == 7); - RTTESTI_CHECK(ShClTransferGetGeneration(pTransfer) != 0); - RTTESTI_CHECK(ShClTransferGetGeneration(pTransfer) != NIL_SHCLTRANSFERGEN); - - SHCLTRANSFERGEN const uGeneration = ShClTransferGetGeneration(pTransfer); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 7, idTransfer, uGeneration) == pTransfer); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 8, idTransfer, uGeneration) == NULL); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 7, idTransfer, uGeneration + 1) == NULL); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 7, idTransfer + 1, uGeneration) == NULL); - - SHCLTRANSFERID idDuplicate = NIL_SHCLTRANSFERID; - rc = ShClTransferCtxRegister(&Ctx, pTransfer, &idDuplicate); - RTTESTI_CHECK_RC(rc, VERR_ALREADY_EXISTS); - rc = ShClTransferCtxRegisterById(&Ctx, pTransfer, idTransfer + 1); - RTTESTI_CHECK_RC(rc, VERR_ALREADY_EXISTS); - - rc = ShClTransferCtxUnregisterById(&Ctx, idTransfer); - RTTESTI_CHECK_RC_OK(rc); - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - PSHCLTRANSFER pTransferReuse; - rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransferReuse); - RTTESTI_CHECK_RC_OK_RETV(rc); - rc = ShClTransferCtxRegisterById(&Ctx, pTransferReuse, idTransfer); - RTTESTI_CHECK_RC(rc, VERR_ALREADY_EXISTS); - rc = ShClTransferDestroy(pTransferReuse); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferCtxBeginSession(&Ctx, 8); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransferReuse); - RTTESTI_CHECK_RC_OK_RETV(rc); - rc = ShClTransferCtxRegisterById(&Ctx, pTransferReuse, idTransfer); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClTransferGetSessionId(pTransferReuse) == 8); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 7, idTransfer, uGeneration) == NULL); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 8, idTransfer, - ShClTransferGetGeneration(pTransferReuse)) == pTransferReuse); - - rc = ShClTransferCtxUnregisterById(&Ctx, idTransfer); - RTTESTI_CHECK_RC_OK(rc); - rc = ShClTransferDestroy(pTransferReuse); - RTTESTI_CHECK_RC_OK(rc); - - ShClTransferCtxDestroy(&Ctx); -} - -static void testTransferRootsSet(RTTEST hTest) -{ - RTTestISub("Testing setting transfer roots"); - - /* Define the (valid) transfer root set. */ - RTCList lstBase; - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt")); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt")); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt")); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt")); - - RTCList lstBreakout; - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VINF_SUCCESS); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("../must-not-access-this")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("does-not-exist/file1.txt")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/../must-not-access-this")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/./../must-not-access-this")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("../does-not-exist")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - RTCList lstBoundaryBase; - lstBoundaryBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); - - RTCList lstBoundaryBreakout; - lstBoundaryBreakout.append(TESTTRANSFERROOTENTRY("my-transfer-10/file2.txt")); - /* Sibling prefix confusion is rejected (my-transfer-1 vs my-transfer-10). */ - testTransferRootsSetSingle(hTest, lstBoundaryBase, lstBoundaryBreakout, VERR_PATH_DOES_NOT_START_WITH_ROOT); -} - -static void testTransferObjOpenPrefixBoundary(RTTEST hTest) -{ - RTTestISub("Testing transfer object root prefix boundaries"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferObjOpenDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferObjOpenPrefixBoundary", szTestTransferObjOpenDir, - sizeof(szTestTransferObjOpenDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - RTCList lstRoots; - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/foo")); - - RTCList lstToExtendEmpty; - - char *pszRoots; - rc = testAddRootEntries(hTest, szTestTransferObjOpenDir, lstRoots, lstToExtendEmpty, &pszRoots); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = testCreateFile(hTest, szTestTransferObjOpenDir, "my-transfer-1/foobar", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLOBJOPENCREATEPARMS openCreateParms; - rc = ShClTransferObjOpenParmsInit(&openCreateParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, "foo"); - RTTESTI_CHECK_RC_OK(rc); - - /* Exact authorized root entry opens successfully. */ - SHCLOBJHANDLE hObj; - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferObjClose(pTransfer, hObj); - RTTESTI_CHECK_RC_OK(rc); - } - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, "foobar"); - RTTESTI_CHECK_RC_OK(rc); - - /* Sibling path sharing the same string prefix is not authorized. */ - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC(rc, VERR_PATH_NOT_FOUND); - - ShClTransferObjOpenParmsDestroy(&openCreateParms); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -/** - * Tests that resetting a transfer closes active object handles. - * - * @param hTest The test handle. - */ -static void testTransferResetClosesObjectHandles(RTTEST hTest) -{ - RTTestISub("Testing transfer reset closes object handles"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreateEx(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, SHCL_TRANSFER_DEFAULT_MAX_CHUNK_SIZE, - SHCL_TRANSFER_DEFAULT_MAX_LIST_HANDLES, 1 /* cMaxObjHandles */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferResetDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferResetHandleAccounting", szTestTransferResetDir, - sizeof(szTestTransferResetDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char *pszFilePathAbs = NULL; - rc = testCreateFile(hTest, szTestTransferResetDir, "file1.txt", 0, 0, &pszFilePathAbs); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char *pszRoots = NULL; - rc = RTStrAAppend(&pszRoots, pszFilePathAbs); - RTTESTI_CHECK_RC_OK(rc); - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - SHCLOBJOPENCREATEPARMS OpenParms; - rc = ShClTransferObjOpenParmsInit(&OpenParms); - RTTESTI_CHECK_RC_OK(rc); - rc = RTStrCopy(OpenParms.pszPath, OpenParms.cbPath, "file1.txt"); - RTTESTI_CHECK_RC_OK(rc); - - SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; - rc = ShClTransferObjOpen(pTransfer, &OpenParms, &hObj); - RTTESTI_CHECK_RC_OK(rc); - - ShClTransferReset(pTransfer); - - rc = ShClTransferObjClose(pTransfer, hObj); - RTTESTI_CHECK_RC(rc, VERR_NOT_FOUND); - - ShClTransferObjOpenParmsDestroy(&OpenParms); - RTStrFree(pszRoots); - RTStrFree(pszFilePathAbs); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - - -static void testTransferObjReadWriteValidation(void) -{ - RTTestISub("Testing transfer object read/write validation"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreateEx(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, 4 /* cbMaxChunkSize */, - SHCL_TRANSFER_DEFAULT_MAX_LIST_HANDLES, SHCL_TRANSFER_DEFAULT_MAX_OBJ_HANDLES, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char abBuf[8]; - uint32_t cbActual = 0; - - /* Invalid read handles, pointers, sizes, chunks and flags are rejected with their precise status codes. */ - rc = ShClTransferObjRead(pTransfer, NIL_SHCLOBJHANDLE, abBuf, 1, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_HANDLE); - rc = ShClTransferObjRead(pTransfer, 1, NULL, 1, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_POINTER); - rc = ShClTransferObjRead(pTransfer, 1, abBuf, 0, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - rc = ShClTransferObjRead(pTransfer, 1, abBuf, sizeof(abBuf), 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_BUFFER_OVERFLOW); - rc = ShClTransferObjRead(pTransfer, 1, abBuf, 1, 1 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); - - /* Invalid write handles, pointers, sizes, chunks and flags are rejected with their precise status codes. */ - rc = ShClTransferObjWrite(pTransfer, NIL_SHCLOBJHANDLE, abBuf, 1, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_HANDLE); - rc = ShClTransferObjWrite(pTransfer, 1, NULL, 1, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_POINTER); - rc = ShClTransferObjWrite(pTransfer, 1, abBuf, 0, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - rc = ShClTransferObjWrite(pTransfer, 1, abBuf, sizeof(abBuf), 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_BUFFER_OVERFLOW); - rc = ShClTransferObjWrite(pTransfer, 1, abBuf, 1, 1 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -static void testTransferListOpenEmptyPath(RTTEST hTest) -{ - RTTestISub("Testing transfer list empty-path rejection"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferListOpenDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferListOpenEmptyPath", szTestTransferListOpenDir, - sizeof(szTestTransferListOpenDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char szRootDir[RTPATH_MAX]; - rc = RTPathJoin(szRootDir, sizeof(szRootDir), szTestTransferListOpenDir, "my-transfer-1/dir"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = testCreateDir(hTest, szRootDir); - RTTESTI_CHECK_RC_OK(rc); - rc = testCreateFile(hTest, szTestTransferListOpenDir, "my-transfer-1/dir/file1.txt", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - char *pszRoots = RTStrDup(szRootDir); - RTTESTI_CHECK_RETV(pszRoots); - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLLISTOPENPARMS openParms; - rc = ShClTransferListOpenParmsInit(&openParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(openParms.pszPath, openParms.cbPath, ""); - RTTESTI_CHECK_RC_OK(rc); - rc = RTStrCopy(openParms.pszFilter, openParms.cbFilter, ""); - RTTESTI_CHECK_RC_OK(rc); - - /* Empty list-open paths must not enumerate the common root. */ - SHCLLISTHANDLE hList; - rc = ShClTransferListOpen(pTransfer, &openParms, &hList); - RTTESTI_CHECK_RC(rc, VERR_PATH_NOT_FOUND); - - ShClTransferListOpenParmsDestroy(&openParms); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -/** - * Tests local-provider list handling for a single file root. - * - * @param hTest Test handle. - */ -static void testTransferListSingleFileRoot(RTTEST hTest) -{ - RTTestISub("Testing single-file transfer list handling"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferListDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferListSingleFileRoot", szTestTransferListDir, sizeof(szTestTransferListDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char *pszRootFileAbs = NULL; - rc = testCreateFile(hTest, szTestTransferListDir, "my-transfer-1/file1.txt", 0, 0, &pszRootFileAbs); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char *pszRoots = RTStrDup(pszRootFileAbs); - RTTESTI_CHECK_RETV(pszRoots); - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLLISTOPENPARMS OpenParms; - rc = ShClTransferListOpenParmsInit(&OpenParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(OpenParms.pszPath, OpenParms.cbPath, "file1.txt"); - RTTESTI_CHECK_RC_OK(rc); - rc = RTStrCopy(OpenParms.pszFilter, OpenParms.cbFilter, ""); - RTTESTI_CHECK_RC_OK(rc); - - SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; - rc = ShClTransferListOpen(pTransfer, &OpenParms, &hList); - RTTESTI_CHECK_RC_OK(rc); - - if (RT_SUCCESS(rc)) - { - SHCLLISTHDR Hdr; - rc = ShClTransferListGetHeader(pTransfer, hList, &Hdr); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(Hdr.cEntries == 1); - - SHCLLISTENTRY Entry; - rc = ShClTransferListEntryInit(&Entry); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferListRead(pTransfer, hList, &Entry); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - RTTESTI_CHECK(strcmp(Entry.pszName, "file1.txt") == 0); - ShClTransferListEntryDestroy(&Entry); - } - - SHCLLISTENTRY WriteEntry; - rc = ShClTransferListEntryInit(&WriteEntry); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferListWrite(pTransfer, hList, &WriteEntry); - RTTESTI_CHECK_RC(rc, VERR_NOT_SUPPORTED); - ShClTransferListEntryDestroy(&WriteEntry); - } - - rc = ShClTransferListClose(pTransfer, hList); - RTTESTI_CHECK_RC_OK(rc); - } - - ShClTransferListOpenParmsDestroy(&OpenParms); - RTStrFree(pszRootFileAbs); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - - -static void testTransferSymlinkRejection(RTTEST hTest) -{ - RTTestISub("Testing transfer symlink rejection"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferSymlinkDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferSymlinkRejection", szTestTransferSymlinkDir, - sizeof(szTestTransferSymlinkDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char szRootDir[RTPATH_MAX]; - rc = RTPathJoin(szRootDir, sizeof(szRootDir), szTestTransferSymlinkDir, "my-transfer-1/dir"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = testCreateDir(hTest, szRootDir); - RTTESTI_CHECK_RC_OK(rc); - - char *pszTargetFileAbs = NULL; - rc = testCreateFile(hTest, szTestTransferSymlinkDir, "my-transfer-1/target.txt", 0, 0, &pszTargetFileAbs); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char szLinkFile[RTPATH_MAX]; - rc = RTPathJoin(szLinkFile, sizeof(szLinkFile), szRootDir, "link-file"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = RTSymlinkCreate(szLinkFile, pszTargetFileAbs, RTSYMLINKTYPE_FILE, 0 /* fCreate */); - if (RT_FAILURE(rc)) - { - RTTestSkipped(hTest, "RTSymlinkCreate(file) failed: %Rrc", rc); - RTStrFree(pszTargetFileAbs); - RTTESTI_CHECK_RC_OK(ShClTransferDestroy(pTransfer)); - return; - } - - char szEscapeDir[RTPATH_MAX]; - rc = RTPathJoin(szEscapeDir, sizeof(szEscapeDir), szTestTransferSymlinkDir, "escape"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = testCreateDir(hTest, szEscapeDir); - RTTESTI_CHECK_RC_OK(rc); - rc = testCreateFile(hTest, szTestTransferSymlinkDir, "escape/secret.txt", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - char szLinkDir[RTPATH_MAX]; - rc = RTPathJoin(szLinkDir, sizeof(szLinkDir), szRootDir, "link-out"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = RTSymlinkCreate(szLinkDir, szEscapeDir, RTSYMLINKTYPE_DIR, 0 /* fCreate */); - if (RT_FAILURE(rc)) - { - RTTestSkipped(hTest, "RTSymlinkCreate(dir) failed: %Rrc", rc); - RTTESTI_CHECK_RC_OK(RTSymlinkDelete(szLinkFile, 0 /* fDelete */)); - RTStrFree(pszTargetFileAbs); - RTTESTI_CHECK_RC_OK(ShClTransferDestroy(pTransfer)); - return; - } - - char *pszRoots = RTStrDup(szRootDir); - RTTESTI_CHECK_RETV(pszRoots); - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLOBJOPENCREATEPARMS openCreateParms; - rc = ShClTransferObjOpenParmsInit(&openCreateParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, "dir/link-file"); - RTTESTI_CHECK_RC_OK(rc); - - /* Final-component symlinks are rejected before file open. */ - SHCLOBJHANDLE hObj; - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC(rc, VERR_IS_A_SYMLINK); - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, "dir/link-out/secret.txt"); - RTTESTI_CHECK_RC_OK(rc); - - /* Intermediate directory symlink breakout is rejected. */ - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC(rc, VERR_IS_A_SYMLINK); - - ShClTransferObjOpenParmsDestroy(&openCreateParms); - RTStrFree(pszTargetFileAbs); - RTTESTI_CHECK_RC_OK(RTSymlinkDelete(szLinkFile, 0 /* fDelete */)); - RTTESTI_CHECK_RC_OK(RTSymlinkDelete(szLinkDir, 0 /* fDelete */)); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -static void testTransferObjOpen(RTTEST hTest) -{ - RTTestISub("Testing setting transfer object open"); - - /* Define the (valid) transfer root set. */ - RTCList lstRoots; - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/file2..txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/file2...txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file2..txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file2...txt")); - - testTransferObjOpenSingle(hTest, lstRoots, "file1.txt", VINF_SUCCESS); - testTransferObjOpenSingle(hTest, lstRoots, "file2..txt", VINF_SUCCESS); - testTransferObjOpenSingle(hTest, lstRoots, "file2...txt", VINF_SUCCESS); - testTransferObjOpenSingle(hTest, lstRoots, "dir2/sub1/file2...txt", VINF_SUCCESS); - testTransferObjOpenSingle(hTest, lstRoots, "does-not-exist.txt", VERR_PATH_NOT_FOUND); - testTransferObjOpenSingle(hTest, lstRoots, "dir1/does-not-exist.txt", VERR_PATH_NOT_FOUND); - testTransferObjOpenSingle(hTest, lstRoots, "../must-not-access-this.txt", VERR_INVALID_PARAMETER); - testTransferObjOpenSingle(hTest, lstRoots, "dir1/../../must-not-access-this.txt", VERR_INVALID_PARAMETER); -} - -int main(int argc, char *argv[]) -{ - /* - * Init the runtime, test and say hello. - */ - const char *pcszExecName; - NOREF(argc); - pcszExecName = strrchr(argv[0], '/'); - pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - RTTestBanner(hTest); - - /* For negative stuff that may assert: */ - bool const fMayPanic = RTAssertSetMayPanic(false); - bool const fQuiet = RTAssertSetQuiet(true); - - testPathSanitize(); - testEvents(); - testTransferBasics(); - testTransferValidation(); - testTransferObjDataChunkDupZeroLength(); - testTransferContextIdentity(); - testTransferResetClosesObjectHandles(hTest); - testTransferRootsSet(hTest); - testTransferObjOpenPrefixBoundary(hTest); - testTransferObjReadWriteValidation(); - testTransferListOpenEmptyPath(hTest); - testTransferListSingleFileRoot(hTest); - testTransferSymlinkRejection(hTest); - testTransferObjOpen(hTest); - - int rc = testRemoveTempDir(hTest); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - RTAssertSetMayPanic(fMayPanic); - RTAssertSetQuiet(fQuiet); - - /* - * Summary - */ - return RTTestSummaryAndDestroy(hTest); -} diff --git a/src/VBox/HostServices/testcase/TstHGCMMock.cpp b/src/VBox/HostServices/testcase/TstHGCMMock.cpp index d06417b0171f..8fade5e16874 100644 --- a/src/VBox/HostServices/testcase/TstHGCMMock.cpp +++ b/src/VBox/HostServices/testcase/TstHGCMMock.cpp @@ -1,4 +1,4 @@ -/* $Id: TstHGCMMock.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ +/* $Id: TstHGCMMock.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ /** @file * TstHGCMMock.cpp - Mocking framework for testing HGCM-based host services. * @@ -53,12 +53,6 @@ #include #include -#ifdef VBOX_WITH_SHARED_CLIPBOARD -#include -#include -#include -#endif - /********************************************************************************************************************************* * Global Variables * @@ -289,122 +283,6 @@ static DECLCALLBACK(int) tstHgcmMockSvcCallComplete(VBOXHGCMCALLHANDLE callHandl return VERR_NOT_FOUND; } -#ifdef VBOX_WITH_SHARED_CLIPBOARD -DECLCALLBACK(int) tstHgcmMockSvcDispatcher(void *pvExtension, uint32_t u32Function, - void *pvParms, uint32_t cbParms) -{ - RT_NOREF(pvExtension, cbParms); - int rc = VINF_SUCCESS; - - PSHCLEXTPARMS pParms = (PSHCLEXTPARMS)pvParms; /* pParms might be NULL, depending on the message. */ - - LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); - - switch (u32Function) - { - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: // via VBOX_SHCL_GUEST_FN_REPORT_FORMATS in the guest - { - PSHCLCLIENT pClient = pParms->u.ReportFormats.pClient; - SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; - - rc = ShClBackendReportFormats(pClient->pBackend, pClient, fFormats); - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Reporting guest clipboard formats to the host failed with %Rrc\n", rc)); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - { - PSHCLCLIENT pClient = pParms->u.ReportFormats.pClient; - SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; - - rc = ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: // via VBOX_SHCL_GUEST_FN_DATA_READ in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - SHCLCLIENTCMDCTX cmdCtx; - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - rc = ShClBackendReadData(pClient->pBackend, pClient, &cmdCtx, fFormats, pvData, cbData, - &pParms->u.ReadWriteData.cbActual); - if (RT_SUCCESS(rc)) - LogRel2(("Shared Clipboard: Read host clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, - pParms->u.ReadWriteData.cbActual)); - else - LogRel(("Shared Clipboard: Reading host clipboard data failed with %Rrc\n", rc)); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: // via VBOX_SHCL_GUEST_FN_DATA_WRITE in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - PSHCLCLIENTCMDCTX pCmdCtx = pParms->u.ReadWriteData.pCmdCtx; - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - rc = ShClBackendWriteData(pClient->pBackend, pClient, pCmdCtx, fFormats, pvData, cbData); - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Writing guest clipboard data to the host failed with %Rrc\n", rc)); - /* Complete any pending events. */ - int rc2 = ShClSvcGuestDataSignal(pClient, pCmdCtx, fFormats, pvData, cbData); - if (RT_FAILURE(rc2)) - LogRel(("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", rc2)); - AssertRC(rc2); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - VBOXHGCMSVCFNTABLE *pTable = pParms->u.ReadWriteData.pTable; - rc = ShClBackendInit(pBackend, pTable); - break; - } - - // via VbglR3HGCMDisconnect()->...HGCMService::DisconnectClient()->...HGCMService::instanceDestroy()->...svcUnload() - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - ShClBackendDestroy(pBackend); - rc = VINF_SUCCESS; - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: // via VbglR3ClipboardConnect()->VbglR3HGCMConnect() in the guest - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = ShClBackendConnect(pBackend, pClient); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: // via VbglR3ClipboardDisconnect()->VbglR3HGCMDisconnect() in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = ShClBackendDisconnect(pClient->pBackend, pClient); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = ShClBackendSync(pBackend, pClient); - break; - } - - default: - break; - } - - return rc; -} -#endif - /** * Main thread of HGCM mock service. * @@ -430,9 +308,6 @@ static DECLCALLBACK(int) tstHgcmMockSvcThread(RTTHREAD hThread, void *pvUser) if (RT_FAILURE(rc)) return rc; -#ifdef VBOX_WITH_SHARED_CLIPBOARD - rc = pSvc->fnTable.pfnRegisterExtension(pSvc->fnTable.pvService, tstHgcmMockSvcDispatcher, NULL); -#endif if (RT_SUCCESS(rc)) { RTThreadUserSignal(hThread); diff --git a/src/VBox/Main/include/ClipboardSessionImpl.h b/src/VBox/Main/include/ClipboardSessionImpl.h index dbe6c7893707..6c8ea9fbd3eb 100644 --- a/src/VBox/Main/include/ClipboardSessionImpl.h +++ b/src/VBox/Main/include/ClipboardSessionImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardSessionImpl.h 114560 2026-06-29 08:32:23Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardSessionImpl.h 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard session API object. */ @@ -55,6 +55,16 @@ class ATL_NO_VTABLE ClipboardSession : void FinalRelease(); HRESULT init(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFlags, Clipboard *aParent); +#ifdef UNIT_TEST + /** + * Initializes a parentless session for testing the public session object's own state. + * + * @returns COM status code. + * @param aClientId Main clipboard client ID represented by the session. + * @param fFlags IClipboardSessionFlag mask. + */ + HRESULT initForTesting(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFlags); +#endif void uninit(); HRESULT i_onEventSourceChanged(const ComPtr &aListener, BOOL fAdded); diff --git a/src/VBox/Main/src-client/ClipboardSessionImpl.cpp b/src/VBox/Main/src-client/ClipboardSessionImpl.cpp index 6c110182c683..a7b0f6ce91e6 100644 --- a/src/VBox/Main/src-client/ClipboardSessionImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardSessionImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardSessionImpl.cpp 114560 2026-06-29 08:32:23Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardSessionImpl.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard session API object. */ @@ -188,6 +188,37 @@ HRESULT ClipboardSession::init(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFlags, } +#ifdef UNIT_TEST +/** + * Initializes a parentless clipboard session for unit testing. + * + * @returns COM status code. + * @param aClientId Main clipboard client ID this session represents. + * @param fFlags IClipboardSessionFlag mask. + */ +HRESULT ClipboardSession::initForTesting(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFlags) +{ + AssertReturn(aClientId != VBOX_SHCL_MAIN_CLIENT_NONE, E_INVALIDARG); + + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + mData.mClientId = aClientId; + mData.mfFlags = fFlags; + mData.mParent = NULL; + mData.mfInitialStateDelivered = false; + + HRESULT hrc = mData.mEventSource.createObject(); + AssertComRCReturnRC(hrc); + hrc = mData.mEventSource->init(); + AssertComRCReturnRC(hrc); + + autoInitSpan.setSucceeded(); + return S_OK; +} +#endif + + /** * Uninitializes a clipboard session object. */ @@ -509,4 +540,3 @@ HRESULT ClipboardSession::close() ptrEventSource->uninit(); return S_OK; } - diff --git a/src/VBox/Main/testcase/Makefile.kmk b/src/VBox/Main/testcase/Makefile.kmk index 880bdcd8542a..f4d69bf27e75 100644 --- a/src/VBox/Main/testcase/Makefile.kmk +++ b/src/VBox/Main/testcase/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the VBox API testcases. # @@ -48,7 +48,9 @@ ifndef VBOX_ONLY_SDK $(if $(VBOX_WITH_GUEST_CONTROL),tstGuestCtrlPaths,) \ tstMediumLock \ tstSafeArray \ - tstClipboard \ + tstClipboardAPI \ + tstClipboardMain \ + tstClipboardMain2HostSvc \ tstRecording \ tstSettings \ tstBstr \ @@ -327,17 +329,132 @@ tstSafeArray_SOURCES = tstSafeArray.cpp # -# tstClipboard +# tstClipboardAPI # -tstClipboard_TEMPLATE = VBoxMainClientTstExe -tstClipboard_INCS = \ +tstClipboardAPI_TEMPLATE = VBoxMainTstExe +tstClipboardAPI_INCS = \ ../include \ - $(PATH_OUT)/obj/VBoxAPIWrap \ - $(PATH_OUT)/obj/Main -tstClipboard_SOURCES = \ - tstClipboard.cpp + $(PATH_OUT)/obj/Main \ + $(VBOX_MAIN_APIWRAPPER_INCS) +tstClipboardAPI_DEFS = UNIT_TEST VBOX_COM_INPROC VBOX_WITH_SHARED_CLIPBOARD +tstClipboardAPI_SOURCES = \ + ../src-all/AutoCaller.cpp \ + ../src-all/EventImpl.cpp \ + ../src-all/GlobalStatusConversion.cpp \ + ../src-all/ObjectsTracker.cpp \ + ../src-all/VirtualBoxBase.cpp \ + ../src-all/VirtualBoxErrorInfoImpl.cpp \ + ../src-client/ClipboardFormatImpl.cpp \ + ../src-client/ClipboardItemImpl.cpp \ + ../src-client/ClipboardSessionImpl.cpp \ + ../src-client/HostClipboardImpl.cpp \ + $(PATH_OUT)/obj/Main/VBoxEvents.cpp \ + tstClipboardAPI.cpp +tstClipboardAPI_LIBS = \ + $(PATH_OUT)/lib/VBoxAPIWrap.a +ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardAPI_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardAPI_SOURCES += \ + ../src-all/ProgressImpl.cpp \ + ../src-client/ClipboardTransferDataImpl.cpp \ + ../src-client/ClipboardTransferDirectoryImpl.cpp \ + ../src-client/ClipboardTransferFileImpl.cpp \ + ../src-client/ClipboardTransferFsObjInfoImpl.cpp \ + ../src-client/ClipboardTransferImpl.cpp \ + ../src-client/ClipboardTransferManagerImpl.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp +endif + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_SDK) + tstClipboardAPI_CLEAN = $(tstClipboardAPI_0_OUTDIR)/tstClipboardAPI.run + + $$(tstClipboardAPI_0_OUTDIR)/tstClipboardAPI.run: $$(tstClipboardAPI_1_STAGE_TARGET) | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstClipboardAPI_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardAPI_0_OUTDIR)/tstClipboardAPI.run + endif +endif + + +# +# tstClipboardMain +# +tstClipboardMain_TEMPLATE = VBoxMainTstExe +tstClipboardMain_INCS = \ + ../include \ + ../src-client +tstClipboardMain_DEFS = VBOX_WITH_SHARED_CLIPBOARD +tstClipboardMain_SOURCES = \ + ../src-client/GuestShClBackend.cpp \ + ../src-client/GuestShClConn.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ + tstClipboardMain.cpp ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - tstClipboard_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardMain_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +endif + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_SDK) + tstClipboardMain_CLEAN = $(tstClipboardMain_0_OUTDIR)/tstClipboardMain.run + + $$(tstClipboardMain_0_OUTDIR)/tstClipboardMain.run: $$(tstClipboardMain_1_STAGE_TARGET) | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstClipboardMain_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardMain_0_OUTDIR)/tstClipboardMain.run + endif +endif + + +# +# tstClipboardMain2HostSvc +# +tstClipboardMain2HostSvc_TEMPLATE = VBoxMainTstExe +tstClipboardMain2HostSvc_INCS = \ + ../include \ + ../src-client \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard +tstClipboardMain2HostSvc_DEFS = \ + UNIT_TEST \ + VBOX_WITH_HGCM \ + VBOX_WITH_SHARED_CLIPBOARD \ + VBOX_WITH_SHARED_CLIPBOARD_HOST +tstClipboardMain2HostSvc_SOURCES = \ + ../src-client/GuestShClBackend.cpp \ + ../src-client/GuestShClConn.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ + tstClipboardMain2HostSvc.cpp +ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardMain2HostSvc_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardMain2HostSvc_SOURCES += \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +endif + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_SDK) + tstClipboardMain2HostSvc_CLEAN = $(tstClipboardMain2HostSvc_0_OUTDIR)/tstClipboardMain2HostSvc.run + + $$(tstClipboardMain2HostSvc_0_OUTDIR)/tstClipboardMain2HostSvc.run: $$(tstClipboardMain2HostSvc_1_STAGE_TARGET) | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstClipboardMain2HostSvc_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardMain2HostSvc_0_OUTDIR)/tstClipboardMain2HostSvc.run + endif endif diff --git a/src/VBox/Main/testcase/tstClipboard.cpp b/src/VBox/Main/testcase/tstClipboard.cpp deleted file mode 100644 index 954a9bc47d52..000000000000 --- a/src/VBox/Main/testcase/tstClipboard.cpp +++ /dev/null @@ -1,3868 +0,0 @@ -/* $Id: tstClipboard.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ -/** @file - * Main API Testcase - Clipboard. - */ - -/* - * Copyright (C) 2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#include -#include -#include -#include -#include -#include -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# include -#endif -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace com; - - -/********************************************************************************************************************************* -* Global Variables * -*********************************************************************************************************************************/ -/** The testcase logger. */ -static PRTLOGGER g_pLogger; - - -/********************************************************************************************************************************* -* Internal Functions * -*********************************************************************************************************************************/ -/** - * Creates a clipboard format object through the public Main API. - * - * @returns COM status code. - * @param pClipboard Clipboard API object to use. - * @param pszMimeType MIME type for the format object. - * @param ptrFormat Where to return the created format object. - */ -static HRESULT tstCreateFormat(IClipboard *pClipboard, const char *pszMimeType, ComPtr &ptrFormat) -{ - AssertPtrReturn(pClipboard, E_POINTER); - return pClipboard->CreateFormat(Bstr(pszMimeType).raw(), ptrFormat.asOutParam()); -} - - -/** - * Creates a clipboard item object through the public Main API. - * - * @returns COM status code. - * @param pClipboard Clipboard API object to use. - * @param enmSource Clipboard source. - * @param ptrFormat Clipboard format object. - * @param abData Payload bytes. - * @param ptrItem Where to return the created item object. - */ -static HRESULT tstCreateItem(IClipboard *pClipboard, ClipboardSource_T enmSource, const ComPtr &ptrFormat, - const std::vector &abData, ComPtr &ptrItem) -{ - AssertPtrReturn(pClipboard, E_POINTER); - - SafeArray aBuffer; - if (!abData.empty()) - { - HRESULT hrc = aBuffer.initFrom(&abData[0], abData.size()); - if (FAILED(hrc)) - return hrc; - } - - return pClipboard->CreateItem(enmSource, ptrFormat, ComSafeArrayAsInParam(aBuffer), ptrItem.asOutParam()); -} - - - -/** - * Checks whether a byte SafeArray contains the expected bytes. - * - * @returns true if the buffers match, false otherwise. - * @param aBuffer Buffer to check. - * @param abExpected Expected payload bytes. - */ -static bool tstByteArrayEquals(const SafeArray &aBuffer, const std::vector &abExpected) -{ - if (aBuffer.size() != abExpected.size()) - return false; - if (abExpected.empty()) - return true; - return !memcmp(aBuffer.raw(), &abExpected[0], abExpected.size()); -} - - -/** - * Creates deterministic byte test data from a NUL-terminated string, excluding the terminator. - * - * @returns Byte vector containing the string bytes. - * @param pszData Test data string. - */ -static std::vector tstBytesFromString(const char *pszData) -{ - AssertPtrReturn(pszData, std::vector()); - - size_t const cbData = strlen(pszData); - std::vector abData(cbData); - if (cbData) - memcpy(&abData[0], pszData, cbData); - return abData; -} - - -/** - * Initializes a byte SafeArray from a vector. - * - * @returns COM status code. - * @param abData Source bytes. - * @param aData SafeArray to initialize. - */ -static HRESULT tstSafeArrayFromBytes(const std::vector &abData, SafeArray &aData) -{ - if (abData.empty()) - return S_OK; - return aData.initFrom(&abData[0], abData.size()); -} - - -/** - * Reads raw clipboard data and checks the source, MIME type, and payload. - * - * @returns true if the read matched the expected data, false otherwise. - * @param pClipboard Clipboard API object to use. - * @param pszWhat Description used in failure messages. - * @param enmExpectedSource Expected clipboard source. - * @param pszExpectedMimeType Expected MIME type. - * @param abExpected Expected payload bytes. - */ -static bool tstReadDataRawEquals(IClipboard *pClipboard, const char *pszWhat, ClipboardSource_T enmExpectedSource, - const char *pszExpectedMimeType, const std::vector &abExpected) -{ - AssertPtrReturn(pClipboard, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(pszExpectedMimeType, false); - - ClipboardSource_T enmReadSource = ClipboardSource_Custom; - Bstr bstrRequestedMimeType(""); - Bstr bstrReadMimeType; - SafeArray aReadBuffer; - HRESULT hrc = pClipboard->ReadDataRaw(ClipboardAction_Copy, bstrRequestedMimeType.raw(), &enmReadSource, - bstrReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aReadBuffer)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: ReadDataRaw failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - bool fRc = true; - if (enmReadSource != enmExpectedSource) - { - RTTestIFailed("%s: ReadDataRaw source %d, expected %d\n", pszWhat, enmReadSource, enmExpectedSource); - fRc = false; - } - - Utf8Str strReadMimeType(bstrReadMimeType); - if (RTStrCmp(strReadMimeType.c_str(), pszExpectedMimeType)) - { - RTTestIFailed("%s: ReadDataRaw MIME '%s', expected '%s'\n", pszWhat, strReadMimeType.c_str(), pszExpectedMimeType); - fRc = false; - } - - if (!tstByteArrayEquals(aReadBuffer, abExpected)) - { - RTTestIFailed("%s: ReadDataRaw returned %zu bytes, expected %zu\n", pszWhat, aReadBuffer.size(), abExpected.size()); - fRc = false; - } - - return fRc; -} - - -/** - * Creates a clipboard session through the public Main API. - */ -static HRESULT tstCreateSession(IClipboard *pClipboard, const IClipboardSessionFlag_T *paFlags, size_t cFlags, - ComPtr &ptrSession) -{ - AssertPtrReturn(pClipboard, E_POINTER); - if (cFlags && !paFlags) - return E_POINTER; - - SafeArray aFlags; - for (size_t i = 0; i < cFlags; i++) - if (!aFlags.push_back(paFlags[i])) - return E_OUTOFMEMORY; - - return pClipboard->CreateSession(ComSafeArrayAsInParam(aFlags), ptrSession.asOutParam()); -} - - - -/** - * Registers a passive listener for the specified event types. - */ -static HRESULT tstRegisterClipboardListener(IEventSource *pEventSource, const VBoxEventType_T *paEventTypes, size_t cEventTypes, - ComPtr &ptrListener) -{ - AssertPtrReturn(pEventSource, E_POINTER); - AssertPtrReturn(paEventTypes, E_POINTER); - AssertReturn(cEventTypes > 0, E_INVALIDARG); - - ptrListener.setNull(); - HRESULT hrc = pEventSource->CreateListener(ptrListener.asOutParam()); - if (FAILED(hrc)) - return hrc; - - SafeArray aEventTypes; - for (size_t i = 0; i < cEventTypes; i++) - if (!aEventTypes.push_back(paEventTypes[i])) - return E_OUTOFMEMORY; - - hrc = pEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aEventTypes), FALSE /* aActive */); - if (FAILED(hrc)) - ptrListener.setNull(); - return hrc; -} - - -/** - * Marks a waitable event as processed. Passive clipboard listeners should normally see non-waitable events. - */ -static void tstClipboardMaybeProcessEvent(IEventSource *pEventSource, IEventListener *pListener, IEvent *pEvent) -{ - AssertPtrReturnVoid(pEventSource); - AssertPtrReturnVoid(pListener); - AssertPtrReturnVoid(pEvent); - - BOOL fWaitable = FALSE; - HRESULT hrc = pEvent->COMGETTER(Waitable)(&fWaitable); - if (SUCCEEDED(hrc) && fWaitable) - pEventSource->EventProcessed(pListener, pEvent); -} - - -static bool tstClipboardIsEventTypeExpected(VBoxEventType_T enmType, const VBoxEventType_T *paExpectedTypes, size_t cExpectedTypes) -{ - for (size_t i = 0; i < cExpectedTypes; i++) - if (paExpectedTypes[i] == enmType) - return true; - return false; -} - - -/** - * Waits for the next event and verifies that it has one of the expected types. - */ -static bool tstClipboardWaitForAnyEvent(IEventSource *pEventSource, IEventListener *pListener, - const VBoxEventType_T *paExpectedTypes, size_t cExpectedTypes, - uint32_t cMsTimeout, const char *pszWhat, - ComPtr &ptrEvent, VBoxEventType_T *penmType) -{ - AssertPtrReturn(pEventSource, false); - AssertPtrReturn(pListener, false); - AssertPtrReturn(paExpectedTypes, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(penmType, false); - - ptrEvent.setNull(); - *penmType = VBoxEventType_Invalid; - - HRESULT hrc = pEventSource->GetEvent(pListener, cMsTimeout, ptrEvent.asOutParam()); - if (FAILED(hrc)) - { - RTTestIFailed("%s: GetEvent failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - if (ptrEvent.isNull()) - { - RTTestIFailed("%s: GetEvent returned no event\n", pszWhat); - return false; - } - - hrc = ptrEvent->COMGETTER(Type)(penmType); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Type) failed, hrc=%Rhrc\n", pszWhat, hrc); - tstClipboardMaybeProcessEvent(pEventSource, pListener, ptrEvent); - return false; - } - - if (!tstClipboardIsEventTypeExpected(*penmType, paExpectedTypes, cExpectedTypes)) - { - RTTestIFailed("%s: GetEvent returned type %d\n", pszWhat, *penmType); - tstClipboardMaybeProcessEvent(pEventSource, pListener, ptrEvent); - return false; - } - - return true; -} - - -/** - * Verifies that a listener has no queued event. - */ -static bool tstClipboardExpectNoEvent(IEventSource *pEventSource, IEventListener *pListener, uint32_t cMsTimeout, - const char *pszWhat) -{ - AssertPtrReturn(pEventSource, false); - AssertPtrReturn(pListener, false); - AssertPtrReturn(pszWhat, false); - - ComPtr ptrEvent; - HRESULT hrc = pEventSource->GetEvent(pListener, cMsTimeout, ptrEvent.asOutParam()); - if ( hrc == VBOX_E_OBJECT_NOT_FOUND - || (SUCCEEDED(hrc) && ptrEvent.isNull())) - return true; - - VBoxEventType_T enmType = VBoxEventType_Invalid; - if (SUCCEEDED(hrc)) - ptrEvent->COMGETTER(Type)(&enmType); - RTTestIFailed("%s: unexpected event, hrc=%Rhrc, type=%d\n", pszWhat, hrc, enmType); - if (ptrEvent.isNotNull()) - tstClipboardMaybeProcessEvent(pEventSource, pListener, ptrEvent); - return false; -} - - -/** - * Verifies the metadata common to clipboard event interfaces. - */ -template -static bool tstClipboardCheckEventMetadata(EventT *pEvent, const char *pszWhat, ULONG idExpectedClient, - LONG64 *pi64Revision = NULL) -{ - AssertPtrReturn(pEvent, false); - AssertPtrReturn(pszWhat, false); - - bool fRc = true; - LONG64 i64Revision = 0; - HRESULT hrc = pEvent->COMGETTER(Revision)(&i64Revision); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Revision) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (i64Revision <= 0) - { - RTTestIFailed("%s: revision is %RI64\n", pszWhat, i64Revision); - fRc = false; - } - - ULONG idClient = UINT32_MAX; - hrc = pEvent->COMGETTER(ClientId)(&idClient); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(ClientId) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (idClient != idExpectedClient) - { - RTTestIFailed("%s: client ID %RU32, expected %RU32\n", pszWhat, (uint32_t)idClient, (uint32_t)idExpectedClient); - fRc = false; - } - - if (pi64Revision) - *pi64Revision = i64Revision; - return fRc; -} - - -template -static bool tstClipboardCheckEventMetadata(const ComPtr &ptrEvent, const char *pszWhat, ULONG idExpectedClient, - LONG64 *pi64Revision = NULL) -{ - return tstClipboardCheckEventMetadata((EventT *)ptrEvent, pszWhat, idExpectedClient, pi64Revision); -} - - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** - * Verifies a service-originated clipboard transfer event. - */ -static bool tstClipboardCheckTransferEvent(IEvent *pEvent, const char *pszWhat, - ClipboardTransferState_T enmExpectedState, - ClipboardTransferDirection_T enmExpectedDirection, - ClipboardSource_T enmExpectedSource, - ULONG idExpectedTransfer, - ComPtr &ptrTransfer, - ClipboardError_T enmExpectedError = ClipboardError_None) -{ - AssertPtrReturn(pEvent, false); - AssertPtrReturn(pszWhat, false); - - ptrTransfer.setNull(); - ComPtr ptrTransferEvent(pEvent); - if (ptrTransferEvent.isNull()) - { - RTTestIFailed("%s: event does not implement IClipboardTransferEvent\n", pszWhat); - return false; - } - - bool fRc = tstClipboardCheckEventMetadata(ptrTransferEvent, pszWhat, VBOX_SHCL_MAIN_CLIENT_NONE); - - ClipboardTransferState_T enmState = ClipboardTransferState_Removed; - HRESULT hrc = ptrTransferEvent->COMGETTER(State)(&enmState); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(State) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmState != enmExpectedState) - { - RTTestIFailed("%s: state %d, expected %d\n", pszWhat, enmState, enmExpectedState); - fRc = false; - } - - ClipboardError_T enmError = ClipboardError_OperationFailed; - hrc = ptrTransferEvent->COMGETTER(Error)(&enmError); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Error) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmError != enmExpectedError) - { - RTTestIFailed("%s: error %d, expected %d\n", pszWhat, enmError, enmExpectedError); - fRc = false; - } - - hrc = ptrTransferEvent->COMGETTER(Transfer)(ptrTransfer.asOutParam()); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Transfer) failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - if (ptrTransfer.isNull()) - { - RTTestIFailed("%s: transfer event has no transfer\n", pszWhat); - return false; - } - - ULONG idTransfer = 0; - hrc = ptrTransfer->COMGETTER(Id)(&idTransfer); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardTransfer::COMGETTER(Id) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (idTransfer != idExpectedTransfer) - { - RTTestIFailed("%s: transfer ID %RU32, expected %RU32\n", pszWhat, idTransfer, idExpectedTransfer); - fRc = false; - } - - ClipboardTransferDirection_T enmDirection = ClipboardTransferDirection_Any; - hrc = ptrTransfer->COMGETTER(Direction)(&enmDirection); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardTransfer::COMGETTER(Direction) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmDirection != enmExpectedDirection) - { - RTTestIFailed("%s: transfer direction %d, expected %d\n", pszWhat, enmDirection, enmExpectedDirection); - fRc = false; - } - - ClipboardSource_T enmSource = ClipboardSource_Custom; - hrc = ptrTransfer->COMGETTER(Source)(&enmSource); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardTransfer::COMGETTER(Source) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmSource != enmExpectedSource) - { - RTTestIFailed("%s: transfer source %d, expected %d\n", pszWhat, enmSource, enmExpectedSource); - fRc = false; - } - - ClipboardTransferState_T enmTransferState = ClipboardTransferState_Removed; - hrc = ptrTransfer->COMGETTER(State)(&enmTransferState); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardTransfer::COMGETTER(State) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmTransferState != enmExpectedState) - { - RTTestIFailed("%s: transfer state %d, expected %d\n", pszWhat, enmTransferState, enmExpectedState); - fRc = false; - } - - ClipboardError_T enmTransferError = ClipboardError_OperationFailed; - hrc = ptrTransfer->COMGETTER(Error)(&enmTransferError); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardTransfer::COMGETTER(Error) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmTransferError != enmExpectedError) - { - RTTestIFailed("%s: transfer error %d, expected %d\n", pszWhat, enmTransferError, enmExpectedError); - fRc = false; - } - - return fRc; -} -#endif - - -/** - * Checks whether a format array contains exactly the expected MIME type. - */ -static bool tstClipboardCheckSingleFormat(SafeIfaceArray &aFormats, const char *pszExpectedMimeType, - const char *pszWhat) -{ - AssertPtrReturn(pszExpectedMimeType, false); - AssertPtrReturn(pszWhat, false); - - if (aFormats.size() != 1) - { - RTTestIFailed("%s: got %zu formats, expected 1\n", pszWhat, aFormats.size()); - return false; - } - - Bstr bstrMimeType; - HRESULT hrc = aFormats[0]->COMGETTER(MimeType)(bstrMimeType.asOutParam()); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(MimeType) failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - Utf8Str strMimeType(bstrMimeType); - if (!RTStrCmp(strMimeType.c_str(), pszExpectedMimeType)) - return true; - - RTTestIFailed("%s: format MIME '%s', expected '%s'\n", pszWhat, strMimeType.c_str(), pszExpectedMimeType); - return false; -} - - -/** - * Verifies a clipboard item payload. - */ -static bool tstClipboardCheckItemPayload(IClipboardItem *pItem, const char *pszWhat, ClipboardSource_T enmExpectedSource, - const char *pszExpectedMimeType, const std::vector &abExpected) -{ - AssertPtrReturn(pItem, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(pszExpectedMimeType, false); - - bool fRc = true; - ClipboardSource_T enmSource = ClipboardSource_Custom; - HRESULT hrc = pItem->COMGETTER(Source)(&enmSource); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardItem::COMGETTER(Source) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmSource != enmExpectedSource) - { - RTTestIFailed("%s: item source %d, expected %d\n", pszWhat, enmSource, enmExpectedSource); - fRc = false; - } - - ComPtr ptrFormat; - hrc = pItem->COMGETTER(Format)(ptrFormat.asOutParam()); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardItem::COMGETTER(Format) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (ptrFormat.isNull()) - { - RTTestIFailed("%s: item has no format\n", pszWhat); - fRc = false; - } - else - { - Bstr bstrMimeType; - hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardFormat::COMGETTER(MimeType) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else - { - Utf8Str strMimeType(bstrMimeType); - if (RTStrCmp(strMimeType.c_str(), pszExpectedMimeType)) - { - RTTestIFailed("%s: item MIME '%s', expected '%s'\n", pszWhat, strMimeType.c_str(), pszExpectedMimeType); - fRc = false; - } - } - } - - SafeArray aBuffer; - hrc = pItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aBuffer)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardItem::COMGETTER(Buffer) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (!tstByteArrayEquals(aBuffer, abExpected)) - { - RTTestIFailed("%s: item payload has %zu bytes, expected %zu\n", pszWhat, aBuffer.size(), abExpected.size()); - fRc = false; - } - - return fRc; -} - - -/** - * Reads the possibly-null IClipboardEvent::item payload from a concrete clipboard event. - */ -static bool tstClipboardGetEventItem(IEvent *pEvent, const char *pszWhat, ComPtr &ptrItem) -{ - AssertPtrReturn(pEvent, false); - AssertPtrReturn(pszWhat, false); - - ptrItem.setNull(); - ComPtr ptrClipboardEvent(pEvent); - if (ptrClipboardEvent.isNull()) - { - RTTestIFailed("%s: event does not implement IClipboardEvent\n", pszWhat); - return false; - } - - HRESULT hrc = ptrClipboardEvent->COMGETTER(Item)(ptrItem.asOutParam()); - if (SUCCEEDED(hrc)) - return true; - - RTTestIFailed("%s: IClipboardEvent::COMGETTER(Item) failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; -} - - -/** - * Verifies an OnClipboardDataChanged event and optionally returns its inherited item. - */ -static bool tstClipboardCheckDataChangedEvent(IEvent *pEvent, const char *pszWhat, ULONG idExpectedClient, - ClipboardAction_T enmExpectedAction, ComPtr *pptrItem, - LONG64 *pi64Revision = NULL) -{ - AssertPtrReturn(pEvent, false); - AssertPtrReturn(pszWhat, false); - - ComPtr ptrDataEvent(pEvent); - if (ptrDataEvent.isNull()) - { - RTTestIFailed("%s: event does not implement IClipboardDataChangedEvent\n", pszWhat); - return false; - } - - bool fRc = tstClipboardCheckEventMetadata(ptrDataEvent, pszWhat, idExpectedClient, pi64Revision); - - ClipboardAction_T enmAction = ClipboardAction_Custom; - HRESULT hrc = ptrDataEvent->COMGETTER(Action)(&enmAction); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Action) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmAction != enmExpectedAction) - { - RTTestIFailed("%s: action %d, expected %d\n", pszWhat, enmAction, enmExpectedAction); - fRc = false; - } - - if (pptrItem && !tstClipboardGetEventItem(pEvent, pszWhat, *pptrItem)) - fRc = false; - return fRc; -} - - -/** - * Verifies an OnClipboardFormatChanged event. - */ -static bool tstClipboardCheckFormatChangedEvent(IEvent *pEvent, const char *pszWhat, ULONG idExpectedClient, - ClipboardSource_T enmExpectedSource, const char *pszExpectedMimeType, - LONG64 *pi64Revision = NULL) -{ - AssertPtrReturn(pEvent, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(pszExpectedMimeType, false); - - ComPtr ptrFormatEvent(pEvent); - if (ptrFormatEvent.isNull()) - { - RTTestIFailed("%s: event does not implement IClipboardFormatChangedEvent\n", pszWhat); - return false; - } - - bool fRc = tstClipboardCheckEventMetadata(ptrFormatEvent, pszWhat, idExpectedClient, pi64Revision); - - ClipboardSource_T enmSource = ClipboardSource_Custom; - HRESULT hrc = ptrFormatEvent->COMGETTER(ClipboardSource)(&enmSource); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(ClipboardSource) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmSource != enmExpectedSource) - { - RTTestIFailed("%s: source %d, expected %d\n", pszWhat, enmSource, enmExpectedSource); - fRc = false; - } - - SafeIfaceArray aFormats; - hrc = ptrFormatEvent->COMGETTER(Formats)(ComSafeArrayAsOutParam(aFormats)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Formats) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (!tstClipboardCheckSingleFormat(aFormats, pszExpectedMimeType, pszWhat)) - fRc = false; - - return fRc; -} - - - -/** - * Verifies a clipboard item payload. - * Reads raw clipboard data through a clipboard session and checks the source, MIME type, and payload. - */ -static bool tstReadSessionDataRawEquals(IClipboardSession *pSession, const char *pszWhat, - ClipboardSource_T enmExpectedSource, const char *pszExpectedMimeType, - const std::vector &abExpected) -{ - AssertPtrReturn(pSession, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(pszExpectedMimeType, false); - - ClipboardSource_T enmReadSource = ClipboardSource_Custom; - Bstr bstrRequestedMimeType(""); - Bstr bstrReadMimeType; - SafeArray aReadBuffer; - HRESULT hrc = pSession->ReadDataRaw(ClipboardAction_Copy, bstrRequestedMimeType.raw(), &enmReadSource, - bstrReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aReadBuffer)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardSession::ReadDataRaw failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - bool fRc = true; - if (enmReadSource != enmExpectedSource) - { - RTTestIFailed("%s: session ReadDataRaw source %d, expected %d\n", pszWhat, enmReadSource, enmExpectedSource); - fRc = false; - } - - Utf8Str strReadMimeType(bstrReadMimeType); - if (RTStrCmp(strReadMimeType.c_str(), pszExpectedMimeType)) - { - RTTestIFailed("%s: session ReadDataRaw MIME '%s', expected '%s'\n", pszWhat, strReadMimeType.c_str(), - pszExpectedMimeType); - fRc = false; - } - - if (!tstByteArrayEquals(aReadBuffer, abExpected)) - { - RTTestIFailed("%s: session ReadDataRaw returned %zu bytes, expected %zu\n", pszWhat, aReadBuffer.size(), - abExpected.size()); - fRc = false; - } - - return fRc; -} - - -/** - * Closes and releases a clipboard session. - */ -static void tstClipboardCloseSession(ComPtr &ptrSession, const char *pszWhat) -{ - AssertPtrReturnVoid(pszWhat); - - if (ptrSession.isNull()) - return; - - HRESULT hrc = ptrSession->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("%s: IClipboardSession::Close failed, hrc=%Rhrc\n", pszWhat, hrc)); - ptrSession.setNull(); -} - - - -/** - * Writes data through IHostClipboard::SetData and verifies regular Main state is unchanged. - * - * @returns true if all readbacks matched, false otherwise. - * @param pClipboard Clipboard API object to use for readback. - * @param pHostClipboard Host clipboard endpoint to test. - * @param pszMimeType MIME type to publish to the native host clipboard. - * @param abData Deterministic payload bytes to publish. - * @param enmExpectedSource Expected regular Main clipboard source after publication. - * @param pszExpectedMimeType Expected regular Main clipboard MIME type after publication. - * @param abExpected Expected regular Main clipboard payload after publication. - * @param cReads Number of repeated readbacks to perform. - * @param pszWhat Description used in failure messages. - */ -static bool tstHostClipboardSetDataAndKeepReadBack(IClipboard *pClipboard, IHostClipboard *pHostClipboard, - const char *pszMimeType, const std::vector &abData, - ClipboardSource_T enmExpectedSource, const char *pszExpectedMimeType, - const std::vector &abExpected, unsigned cReads, - const char *pszWhat) -{ - AssertPtrReturn(pClipboard, false); - AssertPtrReturn(pHostClipboard, false); - AssertPtrReturn(pszMimeType, false); - AssertPtrReturn(pszExpectedMimeType, false); - AssertPtrReturn(pszWhat, false); - - SafeArray aData; - HRESULT hrc = tstSafeArrayFromBytes(abData, aData); - if (FAILED(hrc)) - { - RTTestIFailed("%s: SafeArray initFrom failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - hrc = pHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, Bstr(pszMimeType).raw(), - ComSafeArrayAsInParam(aData)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IHostClipboard::SetData failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - bool fRc = true; - for (unsigned i = 0; i < cReads; i++) - { - char szWhat[128]; - RTStrPrintf(szWhat, sizeof(szWhat), "%s preserved readback %u", pszWhat, i); - if (!tstReadDataRawEquals(pClipboard, szWhat, enmExpectedSource, pszExpectedMimeType, abExpected)) - fRc = false; - } - return fRc; -} - - -/** - * Tries to observe a native host-source readback after IHostClipboard publication. - * - * This is intentionally guarded: headless or no-backend environments may never - * reflect the native clipboard back into Main as ClipboardSource_Host. - * - * @param hTest Test handle. - * @param pClipboard Clipboard API object to use. - * @param pszMimeType Expected MIME type. - * @param abExpected Expected payload bytes. - */ -static void tstHostClipboardTryNativeReadBack(RTTEST hTest, IClipboard *pClipboard, const char *pszMimeType, - const std::vector &abExpected) -{ - RTTestSub(hTest, "IHostClipboard native host clipboard observation"); - - for (unsigned i = 0; i < 20; i++) - { - ClipboardSource_T enmReadSource = ClipboardSource_Custom; - Bstr bstrRequestedMimeType(""); - Bstr bstrReadMimeType; - SafeArray aReadBuffer; - HRESULT hrc = pClipboard->ReadDataRaw(ClipboardAction_Copy, bstrRequestedMimeType.raw(), &enmReadSource, - bstrReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aReadBuffer)); - if (SUCCEEDED(hrc) && enmReadSource == ClipboardSource_Host) - { - Utf8Str strReadMimeType(bstrReadMimeType); - if ( !RTStrCmp(strReadMimeType.c_str(), pszMimeType) - && tstByteArrayEquals(aReadBuffer, abExpected)) - return; - } - RTThreadSleep(100); - } - - RTTestSkipped(hTest, "Native host clipboard did not become observable as host-owned data; this is expected on " - "headless/no-backend or lazy/no-op backend runs"); -} - - -/** - * Tests the sole host clipboard endpoint implementation exposed by IClipboard. - * - * @param hTest Test handle. - * @param pClipboard Clipboard API object to use. - * @param pClipboardSettings Clipboard settings for mode validation. - * @param pEventSource Clipboard event source for data request events. - * @param ptrTextFormat Supported text format object. - * @param bstrGuestReadMimeType MIME type from the regular readback path. - * @param aGuestReadBuffer Payload from the regular readback path. - * @param abRoundTripBuffer Expected regular readback payload bytes. - */ -static void tstHostClipboard(RTTEST hTest, IClipboard *pClipboard, IClipboardSettings *pClipboardSettings, - IEventSource *pEventSource, const ComPtr &ptrTextFormat, - const Bstr &bstrGuestReadMimeType, const SafeArray &aGuestReadBuffer, - const std::vector &abRoundTripBuffer) -{ - RTTestSub(hTest, "IHostClipboard public endpoint"); - - HRESULT hrc = S_OK; - bool fListenerRegistered = false; - ComPtr ptrListener; - ComPtr ptrHostClipboard; - - do - { - /* Verify the endpoint object is reachable from the public clipboard API. */ - hrc = pClipboard->COMGETTER(HostClipboard)(ptrHostClipboard.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(HostClipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrHostClipboard.isNull(), ("COMGETTER(HostClipboard) returned NULL\n")); - - /* Verify the native host endpoint can be explicitly reset. */ - hrc = ptrHostClipboard->Clear(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard Clear failed, hrc=%Rhrc\n", hrc)); - - std::vector > vecHostFormats; - vecHostFormats.push_back(ptrTextFormat); - SafeIfaceArray aHostTextFormats(vecHostFormats); - - /* Verify guest-origin offers are blocked when mode disallows guest-to-host publication. */ - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_HostToGuest); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMSETTER(Mode) HostToGuest before IHostClipboard failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aHostTextFormats)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ACCESS_DENIED, - ("HostClipboard ReportFormats in HostToGuest mode returned hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Host, - ComSafeArrayAsInParam(aHostTextFormats)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ACCESS_DENIED, - ("HostClipboard ReportFormats with host source returned hrc=%Rhrc\n", hrc)); - - ComPtr ptrOctetStreamFormat; - hrc = tstCreateFormat(pClipboard, "application/octet-stream", ptrOctetStreamFormat); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateFormat(octet-stream) failed, hrc=%Rhrc\n", hrc)); - vecHostFormats.clear(); - vecHostFormats.push_back(ptrOctetStreamFormat); - SafeIfaceArray aHostOctetFormats(vecHostFormats); - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aHostOctetFormats)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("HostClipboard ReportFormats with application/octet-stream returned hrc=%Rhrc\n", hrc)); - - /* Restore bidirectional mode before exercising successful IHostClipboard guest publication. */ - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMSETTER(Mode) Bidirectional before IHostClipboard failed, hrc=%Rhrc\n", hrc)); - - /* Verify guest format offers and early ProvideData rejection paths. */ - vecHostFormats.clear(); - vecHostFormats.push_back(ptrTextFormat); - SafeIfaceArray aHostGuestTextFormats(vecHostFormats); - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aHostGuestTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard ReportFormats(guest) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ProvideData(0 /* requestId */, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ERROR, - ("HostClipboard ProvideData with zero request ID returned hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ProvideData(1 /* requestId */, ClipboardAction_Copy, ClipboardSource_Guest, - Bstr("application/octet-stream").raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("HostClipboard ProvideData with application/octet-stream returned hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ProvideData(1 /* requestId */, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ERROR, - ("HostClipboard ProvideData without pending request returned hrc=%Rhrc\n", hrc)); - - /* Validate ProvideData against a real pending request ID generated by Main. */ - hrc = pEventSource->CreateListener(ptrListener.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateListener(data requested) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrListener.isNull()); - SafeArray aRequestEventTypes; - aRequestEventTypes.push_back(VBoxEventType_OnClipboardDataRequested); - hrc = pEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aRequestEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(data requested) failed, hrc=%Rhrc\n", hrc)); - fListenerRegistered = SUCCEEDED(hrc); - - ComPtr ptrInternalClipboardControl(pClipboard); - RTTESTI_CHECK_MSG_BREAK(!ptrInternalClipboardControl.isNull(), ("Query IInternalClipboardControl returned NULL\n")); - - ULONG idRequest = 0; - hrc = ptrInternalClipboardControl->RequestData(bstrGuestReadMimeType.raw(), &idRequest); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IInternalClipboardControl::RequestData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idRequest != 0, ("IInternalClipboardControl::RequestData returned zero request ID\n")); - - ComPtr ptrRequestEvent; - hrc = pEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrRequestEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent(data requested) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrRequestEvent.isNull(), ("GetEvent(data requested) returned no event\n")); - - VBoxEventType_T enmRequestEventType = VBoxEventType_Invalid; - hrc = ptrRequestEvent->COMGETTER(Type)(&enmRequestEventType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)(data requested) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmRequestEventType == VBoxEventType_OnClipboardDataRequested, - ("GetEvent(data requested) returned type %d, expected %d\n", - enmRequestEventType, VBoxEventType_OnClipboardDataRequested)); - - BOOL fRequestEventWaitable = TRUE; - hrc = ptrRequestEvent->COMGETTER(Waitable)(&fRequestEventWaitable); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Waitable)(data requested) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(!fRequestEventWaitable, ("OnClipboardDataRequested unexpectedly became waitable\n")); - - ComPtr ptrDataRequestedEvent = ptrRequestEvent; - RTTESTI_CHECK(!ptrDataRequestedEvent.isNull()); - if (ptrDataRequestedEvent.isNotNull()) - { - RTTESTI_CHECK(tstClipboardCheckEventMetadata(ptrDataRequestedEvent, "data requested event", - VBOX_SHCL_MAIN_CLIENT_NONE)); - - ULONG idEventRequest = 0; - hrc = ptrDataRequestedEvent->COMGETTER(RequestId)(&idEventRequest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(RequestId) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idEventRequest == idRequest, - ("Request event ID %RU32, expected %RU32\n", (uint32_t)idEventRequest, (uint32_t)idRequest)); - - ClipboardAction_T enmRequestAction = ClipboardAction_Custom; - hrc = ptrDataRequestedEvent->COMGETTER(Action)(&enmRequestAction); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Action) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmRequestAction == ClipboardAction_Copy); - - ClipboardSource_T enmRequestSource = ClipboardSource_Custom; - hrc = ptrDataRequestedEvent->COMGETTER(ClipboardSource)(&enmRequestSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardSource) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmRequestSource == ClipboardSource_Guest); - - ComPtr ptrRequestFormat; - hrc = ptrDataRequestedEvent->COMGETTER(Format)(ptrRequestFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrRequestFormat.isNull()); - if (ptrRequestFormat.isNotNull()) - { - Bstr bstrRequestMimeType; - hrc = ptrRequestFormat->COMGETTER(MimeType)(bstrRequestMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(MimeType)(request format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrRequestMimeType).c_str(), "text/plain;charset=utf-8")); - } - } - - hrc = ptrHostClipboard->ProvideData(idRequest, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard ProvideData with pending request failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ProvideData(idRequest, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ERROR, - ("HostClipboard ProvideData with consumed request ID returned hrc=%Rhrc\n", hrc)); - - if (ptrListener.isNotNull()) - pEventSource->UnregisterListener(ptrListener); - fListenerRegistered = false; - ptrListener.setNull(); - - /* Ensure data supplied for the pending request became the guest-owned clipboard content. */ - ClipboardSource_T enmProvidedReadSource = ClipboardSource_Custom; - Bstr bstrProvidedRequestedMimeType(""); - Bstr bstrProvidedReadMimeType; - SafeArray aProvidedReadBuffer; - hrc = pClipboard->ReadDataRaw(ClipboardAction_Copy, bstrProvidedRequestedMimeType.raw(), &enmProvidedReadSource, - bstrProvidedReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aProvidedReadBuffer)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadDataRaw after HostClipboard ProvideData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmProvidedReadSource == ClipboardSource_Guest); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrProvidedReadMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aProvidedReadBuffer, abRoundTripBuffer), - ("ReadDataRaw after HostClipboard ProvideData returned %zu bytes, expected %zu\n", - aProvidedReadBuffer.size(), abRoundTripBuffer.size())); - - /* Seed host-owned Main state before lazy guest publication. */ - static const char s_szHostStateText[] = "tstClipboard host state preserved across IHostClipboard"; - std::vector abHostStateData = tstBytesFromString(s_szHostStateText); - SafeArray aHostStateData; - hrc = tstSafeArrayFromBytes(abHostStateData, aHostStateData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(host state data) failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmHostStateWrittenSource = ClipboardSource_Custom; - Bstr bstrHostStateWrittenMimeType; - SafeArray aHostStateWrittenBuffer; - hrc = pClipboard->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, bstrGuestReadMimeType.raw(), - ComSafeArrayAsInParam(aHostStateData), &enmHostStateWrittenSource, - bstrHostStateWrittenMimeType.asOutParam(), - ComSafeArrayAsOutParam(aHostStateWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteDataRaw(host state before IHostClipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmHostStateWrittenSource == ClipboardSource_Host); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aHostStateWrittenBuffer, abHostStateData), - ("WriteDataRaw(host state before IHostClipboard) returned %zu bytes, expected %zu\n", - aHostStateWrittenBuffer.size(), abHostStateData.size())); - Utf8Str strHostStateMimeType(bstrHostStateWrittenMimeType); - tstReadDataRawEquals(pClipboard, "host state before IHostClipboard publication", ClipboardSource_Host, - strHostStateMimeType.c_str(), abHostStateData); - - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aHostGuestTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard ReportFormats(guest) over host state failed, hrc=%Rhrc\n", hrc)); - - hrc = pEventSource->CreateListener(ptrListener.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateListener(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrListener.isNull()); - hrc = pEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aRequestEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(lazy host request) failed, hrc=%Rhrc\n", hrc)); - fListenerRegistered = SUCCEEDED(hrc); - - ULONG idLazyRequest = 0; - hrc = ptrInternalClipboardControl->RequestData(bstrGuestReadMimeType.raw(), &idLazyRequest); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IInternalClipboardControl::RequestData after HostClipboard ReportFormats failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idLazyRequest != 0, ("IInternalClipboardControl::RequestData after HostClipboard ReportFormats returned zero request ID\n")); - - ComPtr ptrLazyRequestEvent; - hrc = pEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrLazyRequestEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrLazyRequestEvent.isNull(), ("GetEvent(lazy host request) returned no event\n")); - - VBoxEventType_T enmLazyRequestEventType = VBoxEventType_Invalid; - hrc = ptrLazyRequestEvent->COMGETTER(Type)(&enmLazyRequestEventType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmLazyRequestEventType == VBoxEventType_OnClipboardDataRequested, - ("GetEvent(lazy host request) returned type %d, expected %d\n", - enmLazyRequestEventType, VBoxEventType_OnClipboardDataRequested)); - - ComPtr ptrLazyDataRequestedEvent = ptrLazyRequestEvent; - RTTESTI_CHECK(!ptrLazyDataRequestedEvent.isNull()); - if (ptrLazyDataRequestedEvent.isNotNull()) - { - RTTESTI_CHECK(tstClipboardCheckEventMetadata(ptrLazyDataRequestedEvent, "lazy data requested event", - VBOX_SHCL_MAIN_CLIENT_NONE)); - - ULONG idLazyEventRequest = 0; - hrc = ptrLazyDataRequestedEvent->COMGETTER(RequestId)(&idLazyEventRequest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(RequestId)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idLazyEventRequest == idLazyRequest, - ("Lazy request event ID %RU32, expected %RU32\n", - (uint32_t)idLazyEventRequest, (uint32_t)idLazyRequest)); - - ClipboardAction_T enmLazyRequestAction = ClipboardAction_Custom; - hrc = ptrLazyDataRequestedEvent->COMGETTER(Action)(&enmLazyRequestAction); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Action)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmLazyRequestAction == ClipboardAction_Copy); - - ClipboardSource_T enmLazyRequestSource = ClipboardSource_Custom; - hrc = ptrLazyDataRequestedEvent->COMGETTER(ClipboardSource)(&enmLazyRequestSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardSource)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmLazyRequestSource == ClipboardSource_Guest); - - ComPtr ptrLazyRequestFormat; - hrc = ptrLazyDataRequestedEvent->COMGETTER(Format)(ptrLazyRequestFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Format)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrLazyRequestFormat.isNull()); - if (ptrLazyRequestFormat.isNotNull()) - { - Bstr bstrLazyRequestMimeType; - hrc = ptrLazyRequestFormat->COMGETTER(MimeType)(bstrLazyRequestMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(MimeType)(lazy host request format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrLazyRequestMimeType).c_str(), "text/plain;charset=utf-8")); - } - } - - hrc = ptrHostClipboard->ProvideData(idLazyRequest, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard ProvideData for lazy host request failed, hrc=%Rhrc\n", hrc)); - - if (ptrListener.isNotNull()) - pEventSource->UnregisterListener(ptrListener); - fListenerRegistered = false; - ptrListener.setNull(); - - tstReadDataRawEquals(pClipboard, "IHostClipboard ReportFormats lazy guest data over host state", ClipboardSource_Guest, - "text/plain;charset=utf-8", abRoundTripBuffer); - - /* Reseed host-owned Main state before verifying SetData does not replace it. */ - aHostStateWrittenBuffer.setNull(); - hrc = pClipboard->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, bstrGuestReadMimeType.raw(), - ComSafeArrayAsInParam(aHostStateData), &enmHostStateWrittenSource, - bstrHostStateWrittenMimeType.asOutParam(), - ComSafeArrayAsOutParam(aHostStateWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteDataRaw(host state after lazy IHostClipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmHostStateWrittenSource == ClipboardSource_Host); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aHostStateWrittenBuffer, abHostStateData), - ("WriteDataRaw(host state after lazy IHostClipboard) returned %zu bytes, expected %zu\n", - aHostStateWrittenBuffer.size(), abHostStateData.size())); - tstReadDataRawEquals(pClipboard, "host state after lazy IHostClipboard publication", ClipboardSource_Host, - strHostStateMimeType.c_str(), abHostStateData); - - RTTestSub(hTest, "IHostClipboard SetData preserves Main state"); - /* Verify deterministic SetData payloads do not overwrite host-owned Main state. */ - struct TSTHOSTCLIPBOARDSETDATA - { - const char *pszMimeType; - const char *pszData; - const char *pszWhat; - }; - static const TSTHOSTCLIPBOARDSETDATA s_aHostClipboardSetData[] = - { - { "text/plain;charset=utf-8", "tstClipboard IHostClipboard text publish #1\n", "text/plain #1" }, - { "text/html", "

tstClipboard IHostClipboard html publish

", "text/html" }, - { "text/plain;charset=utf-8", "tstClipboard IHostClipboard text publish #2\n", "text/plain #2" } - }; - - bool fHaveLastHostClipboardSetData = false; - const char *pszLastHostClipboardMimeType = NULL; - std::vector abLastHostClipboardData; - for (unsigned i = 0; i < RT_ELEMENTS(s_aHostClipboardSetData); i++) - { - std::vector abSetData = tstBytesFromString(s_aHostClipboardSetData[i].pszData); - char szWhat[128]; - RTStrPrintf(szWhat, sizeof(szWhat), "IHostClipboard SetData %s", s_aHostClipboardSetData[i].pszWhat); - if (tstHostClipboardSetDataAndKeepReadBack(pClipboard, ptrHostClipboard, - s_aHostClipboardSetData[i].pszMimeType, abSetData, - ClipboardSource_Host, strHostStateMimeType.c_str(), - abHostStateData, 3 /* cReads */, szWhat)) - { - fHaveLastHostClipboardSetData = true; - pszLastHostClipboardMimeType = s_aHostClipboardSetData[i].pszMimeType; - abLastHostClipboardData = abSetData; - } - } - - if (fHaveLastHostClipboardSetData) - { - SafeArray aLastHostClipboardData; - hrc = tstSafeArrayFromBytes(abLastHostClipboardData, aLastHostClipboardData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(last host clipboard data) failed, hrc=%Rhrc\n", hrc)); - - /* Verify SetData rejects wrong source, empty data, unsupported format, and disallowed mode. */ - hrc = ptrHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Host, Bstr(pszLastHostClipboardMimeType).raw(), - ComSafeArrayAsInParam(aLastHostClipboardData)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ACCESS_DENIED, - ("HostClipboard SetData with host source returned hrc=%Rhrc\n", hrc)); - - SafeArray aEmptyHostClipboardData; - hrc = ptrHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, Bstr(pszLastHostClipboardMimeType).raw(), - ComSafeArrayAsInParam(aEmptyHostClipboardData)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_NO_DATA, - ("HostClipboard SetData with empty data returned hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, Bstr("application/octet-stream").raw(), - ComSafeArrayAsInParam(aLastHostClipboardData)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("HostClipboard SetData with application/octet-stream returned hrc=%Rhrc\n", hrc)); - - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_HostToGuest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("COMSETTER(Mode) HostToGuest before HostClipboard SetData denial failed, hrc=%Rhrc\n", hrc)); - if (SUCCEEDED(hrc)) - { - hrc = ptrHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, - Bstr(pszLastHostClipboardMimeType).raw(), - ComSafeArrayAsInParam(aLastHostClipboardData)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ACCESS_DENIED, - ("HostClipboard SetData in HostToGuest mode returned hrc=%Rhrc\n", hrc)); - - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), - ("COMSETTER(Mode) Bidirectional after HostClipboard SetData denial failed, " - "hrc=%Rhrc\n", hrc)); - } - - /* Ensure rejection paths did not disturb the preserved host-owned Main payload. */ - tstReadDataRawEquals(pClipboard, "IHostClipboard SetData after failure cases", ClipboardSource_Host, - strHostStateMimeType.c_str(), abHostStateData); - tstHostClipboardTryNativeReadBack(hTest, pClipboard, pszLastHostClipboardMimeType, abLastHostClipboardData); - } - } while (0); - - if (fListenerRegistered && pEventSource && ptrListener.isNotNull()) - pEventSource->UnregisterListener(ptrListener); - ptrListener.setNull(); - - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMSETTER(Mode) Bidirectional after IHostClipboard failed, hrc=%Rhrc\n", hrc)); -} - - -/** - * Configures Shared Clipboard logging to go to stdout. - */ -static void tstInitLogging(void) -{ - RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG; -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) - fFlags |= RTLOGFLAGS_USECRLF; -#endif - static const char * const s_apszLogGroups[] = VBOX_LOGGROUP_NAMES; - int vrc = RTLogCreate(&g_pLogger, fFlags, "all.e.l.f", "TST_CLIPBOARD_LOG", - RT_ELEMENTS(s_apszLogGroups), s_apszLogGroups, RTLOGDEST_STDOUT, NULL); - if (RT_SUCCESS(vrc)) - { - vrc = RTLogGroupSettings(g_pLogger, "main.e.l+shared_clipboard.e.l.l2.f"); - if (RT_SUCCESS(vrc)) - RTLogRelSetDefaultInstance(g_pLogger); - } - else - RTMsgWarning("Failed to create shared clipboard logger: %Rrc", vrc); -} - - -/** - * Logs COM error information for a testcase failure. - * - * @param hTest Test handle. - * @param pszWhat Operation that failed. - * @param errorInfo Error information to log. - */ -static void tstLogErrorInfo(RTTEST hTest, const char *pszWhat, const ErrorInfo &errorInfo) -{ - if (!errorInfo.isBasicAvailable()) - { - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: no error info available\n", pszWhat); - return; - } - - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: hrc=%Rhrc, text='%ls'\n", - pszWhat, errorInfo.getResultCode(), errorInfo.getText().raw()); - if (errorInfo.getComponent().raw()) - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: component='%ls'\n", pszWhat, errorInfo.getComponent().raw()); - if (errorInfo.getInterfaceName().raw()) - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: interface='%ls'\n", pszWhat, errorInfo.getInterfaceName().raw()); -} - - -/** - * Waits for a temporary testcase machine to become unlocked before unregistering it. - * - * @returns COM status code from the last SessionState query. - * @param hTest Test handle. - * @param pMachine Machine to query. - */ -static HRESULT tstWaitMachineUnlocked(RTTEST hTest, IMachine *pMachine) -{ - AssertPtrReturn(pMachine, E_POINTER); - - HRESULT hrc = S_OK; - SessionState_T enmSessionState = SessionState_Unlocked; - for (uint32_t i = 0; i < 100; ++i) - { - hrc = pMachine->COMGETTER(SessionState)(&enmSessionState); - if (FAILED(hrc)) - return hrc; - if (enmSessionState == SessionState_Unlocked) - return S_OK; - RTThreadSleep(100); - } - - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Machine still not unlocked; SessionState=%d\n", enmSessionState); - return VBOX_E_INVALID_OBJECT_STATE; -} - - -/** - * Tests clipboard object factory methods and the returned public objects. - * - * @param hTest Test handle. - * @param pClipboard Clipboard API object to use. - */ -static void tstClipboardPublicObjects(RTTEST hTest, IClipboard *pClipboard) -{ - RTTestSub(hTest, "Clipboard public objects"); - - ComPtr ptrFormat; - HRESULT hrc = tstCreateFormat(pClipboard, "text/plain;charset=utf-8", ptrFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat failed, hrc=%Rhrc\n", hrc)); - - Bstr bstrMimeType; - hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardFormat::COMGETTER(MimeType) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrMimeType).c_str(), "text/plain;charset=utf-8")); - - hrc = ptrFormat->COMSETTER(MimeType)(Bstr("text/html").raw()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardFormat::COMSETTER(MimeType) failed, hrc=%Rhrc\n", hrc)); - bstrMimeType.setNull(); - hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardFormat::COMGETTER(MimeType) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrMimeType).c_str(), "text/html")); - - ComPtr ptrTextFormat; - hrc = tstCreateFormat(pClipboard, "text/plain;charset=utf-8", ptrTextFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat(text) failed, hrc=%Rhrc\n", hrc)); - - static const char s_szText[] = "Hello from the Main clipboard testcase"; - std::vector abText(sizeof(s_szText)); - memcpy(&abText[0], s_szText, sizeof(s_szText)); - - ComPtr ptrItem; - hrc = tstCreateItem(pClipboard, ClipboardSource_Host, ptrTextFormat, abText, ptrItem); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateItem failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmSource = ClipboardSource_Guest; - hrc = ptrItem->COMGETTER(Source)(&enmSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Source) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmSource == ClipboardSource_Host); - - ComPtr ptrItemFormat; - hrc = ptrItem->COMGETTER(Format)(ptrItemFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrItemFormat.isNull()); - if (ptrItemFormat.isNotNull()) - { - Bstr bstrItemMimeType; - hrc = ptrItemFormat->COMGETTER(MimeType)(bstrItemMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::Format::COMGETTER(MimeType) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrItemMimeType).c_str(), "text/plain;charset=utf-8")); - } - - SafeArray aReadText; - hrc = ptrItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aReadText)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Buffer) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aReadText, abText), - ("IClipboardItem::COMGETTER(Buffer) returned %zu bytes, expected %zu\n", aReadText.size(), abText.size())); - - ULONG cbItem = 0; - hrc = ptrItem->COMGETTER(Size)(&cbItem); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Size) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cbItem == sizeof(s_szText)); - - ComPtr ptrHtmlFormat; - hrc = tstCreateFormat(pClipboard, "text/html", ptrHtmlFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat(html) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrItem->COMSETTER(Format)(ptrHtmlFormat); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMSETTER(Format) failed, hrc=%Rhrc\n", hrc)); - ptrItemFormat.setNull(); - hrc = ptrItem->COMGETTER(Format)(ptrItemFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Format) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrItemFormat.isNull()); - if (ptrItemFormat.isNotNull()) - { - Bstr bstrItemMimeType; - hrc = ptrItemFormat->COMGETTER(MimeType)(bstrItemMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::Format::COMGETTER(MimeType) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrItemMimeType).c_str(), "text/html")); - } - - static const char s_szHtml[] = "

Hello from the Main clipboard testcase

"; - SafeArray aHtml; - hrc = aHtml.initFrom(reinterpret_cast(s_szHtml), sizeof(s_szHtml)); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("SafeArray initFrom failed, hrc=%Rhrc\n", hrc)); - hrc = ptrItem->COMSETTER(Buffer)(ComSafeArrayAsInParam(aHtml)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMSETTER(Buffer) failed, hrc=%Rhrc\n", hrc)); - - SafeArray aReadHtml; - hrc = ptrItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aReadHtml)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Buffer) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadHtml.size() == sizeof(s_szHtml)); - if (aReadHtml.size() == sizeof(s_szHtml)) - RTTESTI_CHECK(!memcmp(aReadHtml.raw(), s_szHtml, sizeof(s_szHtml))); - - hrc = ptrItem->COMGETTER(Size)(&cbItem); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Size) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cbItem == sizeof(s_szHtml)); -} - - - -/** - * Tests public clipboard session creation, event routing and close semantics. - * Tests the public clipboard session API. - * - * @param hTest Test handle. - * @param pClipboard Live console clipboard object. - * @param pClipboardSettings Live clipboard settings object. - * @param pEventSource Live console clipboard event source. - */ -static void tstClipboardPublicSessionApi(RTTEST hTest, IClipboard *pClipboard, IClipboardSettings *pClipboardSettings, - IEventSource *pEventSource) -{ - RTTestSub(hTest, "Clipboard public session API"); - - RTTESTI_CHECK_RETV(pClipboard != NULL); - RTTESTI_CHECK_RETV(pClipboardSettings != NULL); - RTTESTI_CHECK_RETV(pEventSource != NULL); - - RTTESTI_CHECK_MSG(VBOX_SHCL_MAIN_CLIENT_NONE == 0, - ("VBOX_SHCL_MAIN_CLIENT_NONE is %RU32, expected 0\n", (uint32_t)VBOX_SHCL_MAIN_CLIENT_NONE)); - - HRESULT hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("COMSETTER(Mode)(Bidirectional) before sessions failed, hrc=%Rhrc\n", hrc)); - - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("Reset before sessions failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrTextFormat; - hrc = tstCreateFormat(pClipboard, "text/plain;charset=utf-8", ptrTextFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat(session text) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrHtmlFormat; - hrc = tstCreateFormat(pClipboard, "text/html", ptrHtmlFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat(session html) failed, hrc=%Rhrc\n", hrc)); - - std::vector > vecTextFormats; - vecTextFormats.push_back(ptrTextFormat); - SafeIfaceArray aTextFormats(vecTextFormats); - - std::vector > vecHtmlFormats; - vecHtmlFormats.push_back(ptrHtmlFormat); - SafeIfaceArray aHtmlFormats(vecHtmlFormats); - - - /* Basic session construction, identity, accessors, raw data, format offer, and close. */ - { - ComPtr ptrSessionA; - ComPtr ptrSessionB; - ComPtr ptrObserverEventSource; - ComPtr ptrObserverListener; - bool fObserverListenerRegistered = false; - do - { - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrSessionA); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(empty A) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrSessionA.isNull(), ("CreateSession(empty A) returned NULL\n")); - - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrSessionB); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(empty B) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrSessionB.isNull(), ("CreateSession(empty B) returned NULL\n")); - - ULONG idSessionA = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSessionA->COMGETTER(Id)(&idSessionA); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(Id)(A) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSessionA != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Session A returned anonymous/zero client ID\n")); - - ULONG idSessionB = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSessionB->COMGETTER(Id)(&idSessionB); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(Id)(B) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSessionB != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Session B returned anonymous/zero client ID\n")); - RTTESTI_CHECK_MSG(idSessionA != idSessionB, - ("Session IDs are not distinct: %RU32\n", (uint32_t)idSessionA)); - - ComPtr ptrSessionEventSource; - hrc = ptrSessionA->COMGETTER(EventSource)(ptrSessionEventSource.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(EventSource) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrSessionEventSource.isNull()); - - ComPtr ptrSessionHostClipboard; - hrc = ptrSessionA->COMGETTER(HostClipboard)(ptrSessionHostClipboard.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(HostClipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrSessionHostClipboard.isNull()); - - hrc = ptrSessionB->COMGETTER(EventSource)(ptrObserverEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(EventSource)(observer) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrObserverEventSource.isNull(), ("IClipboardSession::COMGETTER(EventSource)(observer) returned NULL\n")); - - static VBoxEventType_T const s_aBasicEventTypes[] = - { - VBoxEventType_OnClipboardFormatChanged, - VBoxEventType_OnClipboardDataChanged - }; - hrc = tstRegisterClipboardListener(ptrObserverEventSource, s_aBasicEventTypes, RT_ELEMENTS(s_aBasicEventTypes), - ptrObserverListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(session observer) failed, hrc=%Rhrc\n", hrc)); - fObserverListenerRegistered = true; - - SafeIfaceArray aReadFormats; - hrc = ptrSessionA->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::ReadFormats(empty) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadFormats.size() == 0); - - hrc = ptrSessionA->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::WriteFormats failed, hrc=%Rhrc\n", hrc)); - - LONG64 i64FormatRevision = 0; - ComPtr ptrFormatEvent; - VBoxEventType_T enmFormatEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aBasicEventTypes, - RT_ELEMENTS(s_aBasicEventTypes), 1000 /* cMsTimeout */, - "session observer format", ptrFormatEvent, &enmFormatEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - RTTESTI_CHECK(enmFormatEventType == VBoxEventType_OnClipboardFormatChanged); - if (enmFormatEventType == VBoxEventType_OnClipboardFormatChanged) - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrFormatEvent, "session observer format event", - idSessionA, ClipboardSource_Host, - "text/plain;charset=utf-8", &i64FormatRevision)); - } - - aReadFormats.setNull(); - hrc = ptrSessionA->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::ReadFormats(after WriteFormats) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardCheckSingleFormat(aReadFormats, "text/plain;charset=utf-8", - "session ReadFormats after WriteFormats")); - - static const char s_szSessionRawText[] = "tstClipboard session raw data"; - std::vector abSessionRawData = tstBytesFromString(s_szSessionRawText); - SafeArray aSessionRawData; - hrc = tstSafeArrayFromBytes(abSessionRawData, aSessionRawData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(session raw data) failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmWrittenSource = ClipboardSource_Custom; - Bstr bstrWrittenMimeType; - SafeArray aWrittenBuffer; - hrc = ptrSessionA->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aSessionRawData), &enmWrittenSource, - bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IClipboardSession::WriteDataRaw failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmWrittenSource == ClipboardSource_Host); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrWrittenMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK(tstByteArrayEquals(aWrittenBuffer, abSessionRawData)); - - LONG64 i64DataRevision = 0; - { - ComPtr ptrDataEvent; - VBoxEventType_T enmDataEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aBasicEventTypes, - RT_ELEMENTS(s_aBasicEventTypes), 1000 /* cMsTimeout */, - "session observer data", ptrDataEvent, &enmDataEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - RTTESTI_CHECK(enmDataEventType == VBoxEventType_OnClipboardDataChanged); - if (enmDataEventType == VBoxEventType_OnClipboardDataChanged) - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrDataEvent, "session observer data event", - idSessionA, ClipboardAction_Copy, - NULL /* pptrItem */, &i64DataRevision)); - } - } - if (i64FormatRevision > 0 && i64DataRevision > 0) - RTTESTI_CHECK_MSG(i64DataRevision > i64FormatRevision, - ("session data revision %RI64, expected greater than format revision %RI64\n", - i64DataRevision, i64FormatRevision)); - RTTESTI_CHECK(tstReadSessionDataRawEquals(ptrSessionA, "session raw round-trip", ClipboardSource_Host, - "text/plain;charset=utf-8", abSessionRawData)); - - hrc = ptrSessionA->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::Close(basic session A) failed, hrc=%Rhrc\n", hrc)); - ULONG idSessionAAfterClose = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSessionA->COMGETTER(Id)(&idSessionAAfterClose); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Id)(basic session A after Close) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSessionAAfterClose == idSessionA, - ("Closed session ID changed from %RU32 to %RU32\n", - (uint32_t)idSessionA, (uint32_t)idSessionAAfterClose)); - - ComPtr ptrClosedEventSource; - hrc = ptrSessionA->COMGETTER(EventSource)(ptrClosedEventSource.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("COMGETTER(EventSource)(closed session A) unexpectedly succeeded\n")); - - ComPtr ptrClosedHostClipboard; - hrc = ptrSessionA->COMGETTER(HostClipboard)(ptrClosedHostClipboard.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("COMGETTER(HostClipboard)(closed session A) unexpectedly succeeded\n")); - - SafeIfaceArray aClosedFormats; - hrc = ptrSessionA->ReadFormats(ComSafeArrayAsOutParam(aClosedFormats)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("ReadFormats(closed session A) unexpectedly succeeded\n")); - - ClipboardSource_T enmClosedReadSource = ClipboardSource_Custom; - Bstr bstrClosedRequestedMimeType(""); - Bstr bstrClosedReadMimeType; - SafeArray aClosedReadBuffer; - hrc = ptrSessionA->ReadDataRaw(ClipboardAction_Copy, bstrClosedRequestedMimeType.raw(), &enmClosedReadSource, - bstrClosedReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aClosedReadBuffer)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("ReadDataRaw(closed session A) unexpectedly succeeded\n")); - - ClipboardSource_T enmClosedWrittenSource = ClipboardSource_Custom; - Bstr bstrClosedWrittenMimeType; - SafeArray aClosedWrittenBuffer; - hrc = ptrSessionA->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, - Bstr("text/plain;charset=utf-8").raw(), ComSafeArrayAsInParam(aSessionRawData), - &enmClosedWrittenSource, bstrClosedWrittenMimeType.asOutParam(), - ComSafeArrayAsOutParam(aClosedWrittenBuffer)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("WriteDataRaw(closed session A) unexpectedly succeeded\n")); - - hrc = ptrSessionA->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("WriteFormats(closed session A) unexpectedly succeeded\n")); - - hrc = ptrSessionA->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::Close(closed session A) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrSessionHostClipboard->Clear(); - RTTESTI_CHECK_MSG(FAILED(hrc), ("HostClipboard::Clear(closed session A endpoint) unexpectedly succeeded\n")); - hrc = ptrSessionHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("HostClipboard::ReportFormats(closed session A endpoint) unexpectedly succeeded\n")); - hrc = ptrSessionHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aSessionRawData)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("HostClipboard::SetData(closed session A endpoint) unexpectedly succeeded\n")); - hrc = ptrSessionHostClipboard->ProvideData(1 /* aRequestId */, ClipboardAction_Copy, ClipboardSource_Guest, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aSessionRawData)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("HostClipboard::ProvideData(closed session A endpoint) unexpectedly succeeded\n")); - - SafeIfaceArray aOwnerClearedFormats; - hrc = pClipboard->ReadFormats(ComSafeArrayAsOutParam(aOwnerClearedFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadFormats after closing owning session failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(aOwnerClearedFormats.size() == 0, - ("Closing owning session left %zu clipboard formats advertised\n", aOwnerClearedFormats.size())); - } while (0); - - if (fObserverListenerRegistered && ptrObserverEventSource.isNotNull() && ptrObserverListener.isNotNull()) - ptrObserverEventSource->UnregisterListener(ptrObserverListener); - ptrObserverListener.setNull(); - - tstClipboardCloseSession(ptrSessionA, "basic session A"); - tstClipboardCloseSession(ptrSessionB, "basic session B"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after basic session test failed, hrc=%Rhrc\n", hrc)); - } - - - /* Session host clipboard endpoints tag lazy native-host requests with the session client ID. */ - { - ComPtr ptrHostSession; - ComPtr ptrSessionHostClipboard; - ComPtr ptrRequestListener; - bool fRequestListenerRegistered = false; - do - { - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrHostSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(host clipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrHostSession.isNull(), ("CreateSession(host clipboard) returned NULL\n")); - - ULONG idHostSession = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrHostSession->COMGETTER(Id)(&idHostSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(host clipboard session) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idHostSession != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Host clipboard session returned zero client ID\n")); - - hrc = ptrHostSession->COMGETTER(HostClipboard)(ptrSessionHostClipboard.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(HostClipboard)(session) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrSessionHostClipboard.isNull(), ("COMGETTER(HostClipboard)(session) returned NULL\n")); - - hrc = ptrSessionHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Session HostClipboard::ReportFormats failed, hrc=%Rhrc\n", hrc)); - - hrc = pEventSource->CreateListener(ptrRequestListener.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateListener(session host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrRequestListener.isNull(), ("CreateListener(session host request) returned NULL\n")); - SafeArray aRequestEventTypes; - aRequestEventTypes.push_back(VBoxEventType_OnClipboardDataRequested); - hrc = pEventSource->RegisterListener(ptrRequestListener, ComSafeArrayAsInParam(aRequestEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(session host request) failed, hrc=%Rhrc\n", hrc)); - fRequestListenerRegistered = true; - - ComPtr ptrInternalClipboardControl(pClipboard); - RTTESTI_CHECK_MSG_BREAK(!ptrInternalClipboardControl.isNull(), ("Query IInternalClipboardControl(session host) returned NULL\n")); - - ULONG idRequest = 0; - Bstr bstrMimeType("text/plain;charset=utf-8"); - hrc = ptrInternalClipboardControl->RequestData(bstrMimeType.raw(), &idRequest); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IInternalClipboardControl::RequestData(session host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idRequest != 0, ("IInternalClipboardControl::RequestData(session host) returned zero request ID\n")); - - ComPtr ptrRequestEvent; - hrc = pEventSource->GetEvent(ptrRequestListener, 1000 /* aTimeout */, ptrRequestEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent(session host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrRequestEvent.isNull(), ("GetEvent(session host request) returned no event\n")); - - ComPtr ptrDataRequestedEvent = ptrRequestEvent; - RTTESTI_CHECK(!ptrDataRequestedEvent.isNull()); - if (ptrDataRequestedEvent.isNotNull()) - { - RTTESTI_CHECK(tstClipboardCheckEventMetadata(ptrDataRequestedEvent, "session host data requested event", - idHostSession)); - - ULONG idEventRequest = 0; - hrc = ptrDataRequestedEvent->COMGETTER(RequestId)(&idEventRequest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(RequestId)(session host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idEventRequest == idRequest, - ("Session host request event ID %RU32, expected %RU32\n", - (uint32_t)idEventRequest, (uint32_t)idRequest)); - - ClipboardSource_T enmRequestSource = ClipboardSource_Custom; - hrc = ptrDataRequestedEvent->COMGETTER(ClipboardSource)(&enmRequestSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardSource)(session host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmRequestSource == ClipboardSource_Guest); - } - - static const char s_szSessionHostText[] = "tstClipboard session host clipboard data"; - std::vector abSessionHostData = tstBytesFromString(s_szSessionHostText); - SafeArray aSessionHostData; - hrc = tstSafeArrayFromBytes(abSessionHostData, aSessionHostData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(session host data) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrSessionHostClipboard->ProvideData(idRequest, ClipboardAction_Copy, ClipboardSource_Guest, - bstrMimeType.raw(), ComSafeArrayAsInParam(aSessionHostData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Session HostClipboard::ProvideData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstReadSessionDataRawEquals(ptrHostSession, "session HostClipboard ProvideData readback", - ClipboardSource_Guest, "text/plain;charset=utf-8", abSessionHostData)); - } while (0); - - if (fRequestListenerRegistered && ptrRequestListener.isNotNull()) - pEventSource->UnregisterListener(ptrRequestListener); - ptrRequestListener.setNull(); - - tstClipboardCloseSession(ptrHostSession, "session HostClipboard session"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after session HostClipboard test failed, hrc=%Rhrc\n", hrc)); - } - - - /* IncludeInitialState replays the state current when a session listener registers. */ - { - ComPtr ptrInitialSession; - ComPtr ptrInitialEventSource; - ComPtr ptrInitialListener; - bool fInitialListenerRegistered = false; - do - { - hrc = pClipboard->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteFormats(seed initial state) failed, hrc=%Rhrc\n", hrc)); - - static IClipboardSessionFlag_T const s_aInitialFlags[] = - { - IClipboardSessionFlag_IncludeInitialState - }; - hrc = tstCreateSession(pClipboard, s_aInitialFlags, RT_ELEMENTS(s_aInitialFlags), ptrInitialSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(IncludeInitialState) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrInitialSession.isNull(), ("CreateSession(IncludeInitialState) returned NULL\n")); - - hrc = ptrInitialSession->COMGETTER(EventSource)(ptrInitialEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(initial) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrInitialEventSource.isNull(), ("COMGETTER(EventSource)(initial) returned NULL\n")); - - hrc = pClipboard->WriteFormats(ComSafeArrayAsInParam(aHtmlFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteFormats(update initial state before listener) failed, hrc=%Rhrc\n", hrc)); - - static VBoxEventType_T const s_aInitialEventTypes[] = - { - VBoxEventType_OnClipboardFormatChanged - }; - hrc = tstRegisterClipboardListener(ptrInitialEventSource, s_aInitialEventTypes, RT_ELEMENTS(s_aInitialEventTypes), - ptrInitialListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(initial state) failed, hrc=%Rhrc\n", hrc)); - fInitialListenerRegistered = true; - - ComPtr ptrInitialEvent; - VBoxEventType_T enmInitialEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrInitialEventSource, ptrInitialListener, s_aInitialEventTypes, - RT_ELEMENTS(s_aInitialEventTypes), 1000 /* cMsTimeout */, - "IncludeInitialState", ptrInitialEvent, &enmInitialEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - RTTESTI_CHECK(enmInitialEventType == VBoxEventType_OnClipboardFormatChanged); - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrInitialEvent, "IncludeInitialState format event", - VBOX_SHCL_MAIN_CLIENT_NONE, ClipboardSource_Host, - "text/html")); - } - - SafeIfaceArray aInitialReadFormats; - hrc = ptrInitialSession->ReadFormats(ComSafeArrayAsOutParam(aInitialReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::ReadFormats(IncludeInitialState) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardCheckSingleFormat(aInitialReadFormats, "text/html", - "IncludeInitialState current formats")); - } while (0); - - if (fInitialListenerRegistered && ptrInitialEventSource.isNotNull() && ptrInitialListener.isNotNull()) - ptrInitialEventSource->UnregisterListener(ptrInitialListener); - ptrInitialListener.setNull(); - - tstClipboardCloseSession(ptrInitialSession, "IncludeInitialState session"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after IncludeInitialState test failed, hrc=%Rhrc\n", hrc)); - } - - - /* ExcludeOwnChanges suppresses events for the writing session while other sessions see its client ID. */ - { - ComPtr ptrSessionA; - ComPtr ptrSessionB; - ComPtr ptrEventSourceA; - ComPtr ptrEventSourceB; - ComPtr ptrListenerA; - ComPtr ptrListenerB; - bool fListenerARegistered = false; - bool fListenerBRegistered = false; - do - { - static IClipboardSessionFlag_T const s_aExcludeOwnFlags[] = - { - IClipboardSessionFlag_ExcludeOwnChanges - }; - hrc = tstCreateSession(pClipboard, s_aExcludeOwnFlags, RT_ELEMENTS(s_aExcludeOwnFlags), ptrSessionA); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(ExcludeOwnChanges) failed, hrc=%Rhrc\n", hrc)); - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrSessionB); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(observer) failed, hrc=%Rhrc\n", hrc)); - - ULONG idSessionA = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSessionA->COMGETTER(Id)(&idSessionA); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(ExcludeOwnChanges) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSessionA != VBOX_SHCL_MAIN_CLIENT_NONE, - ("ExcludeOwnChanges session returned zero client ID\n")); - - hrc = ptrSessionA->COMGETTER(EventSource)(ptrEventSourceA.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(A) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrSessionB->COMGETTER(EventSource)(ptrEventSourceB.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(B) failed, hrc=%Rhrc\n", hrc)); - - static VBoxEventType_T const s_aOwnEventTypes[] = - { - VBoxEventType_OnClipboardFormatChanged, - VBoxEventType_OnClipboardDataChanged - }; - hrc = tstRegisterClipboardListener(ptrEventSourceA, s_aOwnEventTypes, RT_ELEMENTS(s_aOwnEventTypes), ptrListenerA); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(A own changes) failed, hrc=%Rhrc\n", hrc)); - fListenerARegistered = true; - - static VBoxEventType_T const s_aDataEventTypes[] = - { - VBoxEventType_OnClipboardDataChanged - }; - hrc = tstRegisterClipboardListener(ptrEventSourceB, s_aDataEventTypes, RT_ELEMENTS(s_aDataEventTypes), ptrListenerB); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(B observer) failed, hrc=%Rhrc\n", hrc)); - fListenerBRegistered = true; - - static const char s_szSessionAText[] = "tstClipboard session A writes"; - std::vector abSessionAData = tstBytesFromString(s_szSessionAText); - SafeArray aSessionAData; - hrc = tstSafeArrayFromBytes(abSessionAData, aSessionAData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(session A data) failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmWrittenSource = ClipboardSource_Custom; - Bstr bstrWrittenMimeType; - SafeArray aWrittenBuffer; - hrc = ptrSessionA->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aSessionAData), &enmWrittenSource, - bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Session A WriteDataRaw failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstReadSessionDataRawEquals(ptrSessionA, "ExcludeOwnChanges committed state", - ClipboardSource_Host, "text/plain;charset=utf-8", abSessionAData)); - - ComPtr ptrObservedEvent; - VBoxEventType_T enmObservedEventType = VBoxEventType_Invalid; - bool const fRc = tstClipboardWaitForAnyEvent(ptrEventSourceB, ptrListenerB, s_aDataEventTypes, - RT_ELEMENTS(s_aDataEventTypes), 1000 /* cMsTimeout */, - "ExcludeOwnChanges observer", ptrObservedEvent, - &enmObservedEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrObservedEvent, - "ExcludeOwnChanges observer data event", - idSessionA, ClipboardAction_Copy, - NULL /* pptrItem */)); - } - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSourceA, ptrListenerA, 250 /* cMsTimeout */, - "ExcludeOwnChanges writer")); - } while (0); - - if (fListenerARegistered && ptrEventSourceA.isNotNull() && ptrListenerA.isNotNull()) - ptrEventSourceA->UnregisterListener(ptrListenerA); - if (fListenerBRegistered && ptrEventSourceB.isNotNull() && ptrListenerB.isNotNull()) - ptrEventSourceB->UnregisterListener(ptrListenerB); - ptrListenerA.setNull(); - ptrListenerB.setNull(); - - tstClipboardCloseSession(ptrSessionA, "ExcludeOwnChanges session A"); - tstClipboardCloseSession(ptrSessionB, "ExcludeOwnChanges session B"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after ExcludeOwnChanges test failed, hrc=%Rhrc\n", hrc)); - } - - - /* IncludePayload attaches the item payload to data events for sessions that request it. */ - { - ComPtr ptrWriterSession; - ComPtr ptrPayloadSession; - ComPtr ptrNoPayloadSession; - ComPtr ptrPayloadEventSource; - ComPtr ptrNoPayloadEventSource; - ComPtr ptrPayloadListener; - ComPtr ptrNoPayloadListener; - bool fPayloadListenerRegistered = false; - bool fNoPayloadListenerRegistered = false; - do - { - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrWriterSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(payload writer) failed, hrc=%Rhrc\n", hrc)); - ULONG idWriterSession = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrWriterSession->COMGETTER(Id)(&idWriterSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(payload writer) failed, hrc=%Rhrc\n", hrc)); - - static IClipboardSessionFlag_T const s_aPayloadFlags[] = - { - IClipboardSessionFlag_IncludePayload - }; - hrc = tstCreateSession(pClipboard, s_aPayloadFlags, RT_ELEMENTS(s_aPayloadFlags), ptrPayloadSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(IncludePayload) failed, hrc=%Rhrc\n", hrc)); - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrNoPayloadSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(no payload observer) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrPayloadSession->COMGETTER(EventSource)(ptrPayloadEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(IncludePayload) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrNoPayloadSession->COMGETTER(EventSource)(ptrNoPayloadEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(no payload) failed, hrc=%Rhrc\n", hrc)); - - static VBoxEventType_T const s_aPayloadEventTypes[] = - { - VBoxEventType_OnClipboardDataChanged, - VBoxEventType_OnClipboardDataRequested - }; - hrc = tstRegisterClipboardListener(ptrPayloadEventSource, s_aPayloadEventTypes, - RT_ELEMENTS(s_aPayloadEventTypes), - ptrPayloadListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(IncludePayload) failed, hrc=%Rhrc\n", hrc)); - fPayloadListenerRegistered = true; - hrc = tstRegisterClipboardListener(ptrNoPayloadEventSource, s_aPayloadEventTypes, - RT_ELEMENTS(s_aPayloadEventTypes), - ptrNoPayloadListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(no payload) failed, hrc=%Rhrc\n", hrc)); - fNoPayloadListenerRegistered = true; - - static const char s_szPayloadText[] = "tstClipboard session payload data"; - std::vector abPayloadData = tstBytesFromString(s_szPayloadText); - SafeArray aPayloadData; - hrc = tstSafeArrayFromBytes(abPayloadData, aPayloadData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(payload data) failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmWrittenSource = ClipboardSource_Custom; - Bstr bstrWrittenMimeType; - SafeArray aWrittenBuffer; - hrc = ptrWriterSession->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aPayloadData), &enmWrittenSource, - bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Payload writer WriteDataRaw failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstReadSessionDataRawEquals(ptrWriterSession, "IncludePayload committed state", - ClipboardSource_Host, "text/plain;charset=utf-8", abPayloadData)); - - ComPtr ptrPayloadEvent; - VBoxEventType_T enmPayloadEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrPayloadEventSource, ptrPayloadListener, s_aPayloadEventTypes, - RT_ELEMENTS(s_aPayloadEventTypes), 1000 /* cMsTimeout */, - "IncludePayload listener", ptrPayloadEvent, &enmPayloadEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - ComPtr ptrPayloadItem; - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrPayloadEvent, "IncludePayload data event", - idWriterSession, ClipboardAction_Copy, - &ptrPayloadItem)); - RTTESTI_CHECK_MSG(!ptrPayloadItem.isNull(), ("IncludePayload data event did not include an item\n")); - if (ptrPayloadItem.isNotNull()) - RTTESTI_CHECK(tstClipboardCheckItemPayload(ptrPayloadItem, "IncludePayload event item", - ClipboardSource_Host, "text/plain;charset=utf-8", - abPayloadData)); - } - - ComPtr ptrNoPayloadEvent; - VBoxEventType_T enmNoPayloadEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrNoPayloadEventSource, ptrNoPayloadListener, s_aPayloadEventTypes, - RT_ELEMENTS(s_aPayloadEventTypes), 1000 /* cMsTimeout */, - "No IncludePayload listener", ptrNoPayloadEvent, - &enmNoPayloadEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - ComPtr ptrNoPayloadItem; - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrNoPayloadEvent, - "No IncludePayload data event", - idWriterSession, ClipboardAction_Copy, - &ptrNoPayloadItem)); - RTTESTI_CHECK_MSG(ptrNoPayloadItem.isNull(), - ("No IncludePayload data event unexpectedly included an item\n")); - } - - ComPtr ptrInternalClipboardControl(pClipboard); - RTTESTI_CHECK_MSG_BREAK(!ptrInternalClipboardControl.isNull(), - ("Query IInternalClipboardControl(IncludePayload) returned NULL\n")); - ULONG idRequest = 0; - hrc = ptrInternalClipboardControl->RequestData(Bstr("text/plain;charset=utf-8").raw(), &idRequest); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), - ("IInternalClipboardControl::RequestData(IncludePayload) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idRequest != 0, ("IncludePayload request returned zero request ID\n")); - - ComPtr ptrPayloadRequestEvent; - fRc = tstClipboardWaitForAnyEvent(ptrPayloadEventSource, ptrPayloadListener, s_aPayloadEventTypes, - RT_ELEMENTS(s_aPayloadEventTypes), 1000 /* cMsTimeout */, - "IncludePayload request listener", ptrPayloadRequestEvent, - &enmPayloadEventType); - RTTESTI_CHECK(fRc); - RTTESTI_CHECK(enmPayloadEventType == VBoxEventType_OnClipboardDataRequested); - if (fRc && enmPayloadEventType == VBoxEventType_OnClipboardDataRequested) - { - ComPtr ptrPayloadRequest = ptrPayloadRequestEvent; - RTTESTI_CHECK(!ptrPayloadRequest.isNull()); - ComPtr ptrPayloadRequestItem; - if (ptrPayloadRequest.isNotNull()) - hrc = ptrPayloadRequest->COMGETTER(Item)(ptrPayloadRequestItem.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("COMGETTER(Item)(IncludePayload request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(!ptrPayloadRequestItem.isNull(), - ("IncludePayload request event did not include an item\n")); - if (ptrPayloadRequestItem.isNotNull()) - RTTESTI_CHECK(tstClipboardCheckItemPayload(ptrPayloadRequestItem, - "IncludePayload request item", - ClipboardSource_Host, - "text/plain;charset=utf-8", abPayloadData)); - } - - ComPtr ptrNoPayloadRequestEvent; - fRc = tstClipboardWaitForAnyEvent(ptrNoPayloadEventSource, ptrNoPayloadListener, s_aPayloadEventTypes, - RT_ELEMENTS(s_aPayloadEventTypes), 1000 /* cMsTimeout */, - "No IncludePayload request listener", ptrNoPayloadRequestEvent, - &enmNoPayloadEventType); - RTTESTI_CHECK(fRc); - RTTESTI_CHECK(enmNoPayloadEventType == VBoxEventType_OnClipboardDataRequested); - if (fRc && enmNoPayloadEventType == VBoxEventType_OnClipboardDataRequested) - { - ComPtr ptrNoPayloadRequest = ptrNoPayloadRequestEvent; - RTTESTI_CHECK(!ptrNoPayloadRequest.isNull()); - ComPtr ptrNoPayloadRequestItem; - if (ptrNoPayloadRequest.isNotNull()) - hrc = ptrNoPayloadRequest->COMGETTER(Item)(ptrNoPayloadRequestItem.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("COMGETTER(Item)(no payload request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(ptrNoPayloadRequestItem.isNull(), - ("No IncludePayload request event unexpectedly included an item\n")); - } - } while (0); - - if (fPayloadListenerRegistered && ptrPayloadEventSource.isNotNull() && ptrPayloadListener.isNotNull()) - ptrPayloadEventSource->UnregisterListener(ptrPayloadListener); - if (fNoPayloadListenerRegistered && ptrNoPayloadEventSource.isNotNull() && ptrNoPayloadListener.isNotNull()) - ptrNoPayloadEventSource->UnregisterListener(ptrNoPayloadListener); - ptrPayloadListener.setNull(); - ptrNoPayloadListener.setNull(); - - tstClipboardCloseSession(ptrWriterSession, "IncludePayload writer session"); - tstClipboardCloseSession(ptrPayloadSession, "IncludePayload observer session"); - tstClipboardCloseSession(ptrNoPayloadSession, "No IncludePayload observer session"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after IncludePayload test failed, hrc=%Rhrc\n", hrc)); - } - - - /* ExcludeReflections suppresses matching anonymous format reflections, not direct session events. */ - { - ComPtr ptrReflectSession; - ComPtr ptrObserverSession; - ComPtr ptrReflectEventSource; - ComPtr ptrObserverEventSource; - ComPtr ptrReflectListener; - ComPtr ptrObserverListener; - bool fReflectListenerRegistered = false; - bool fObserverListenerRegistered = false; - do - { - static IClipboardSessionFlag_T const s_aExcludeReflectionFlags[] = - { - IClipboardSessionFlag_ExcludeReflections - }; - hrc = tstCreateSession(pClipboard, s_aExcludeReflectionFlags, RT_ELEMENTS(s_aExcludeReflectionFlags), - ptrReflectSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(ExcludeReflections) failed, hrc=%Rhrc\n", hrc)); - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrObserverSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(reflection observer) failed, hrc=%Rhrc\n", hrc)); - - ULONG idReflectSession = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrReflectSession->COMGETTER(Id)(&idReflectSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(ExcludeReflections) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idReflectSession != VBOX_SHCL_MAIN_CLIENT_NONE, - ("ExcludeReflections session returned zero client ID\n")); - - hrc = ptrReflectSession->COMGETTER(EventSource)(ptrReflectEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(ExcludeReflections) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrObserverSession->COMGETTER(EventSource)(ptrObserverEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(reflection observer) failed, hrc=%Rhrc\n", hrc)); - - static VBoxEventType_T const s_aFormatEventTypes[] = - { - VBoxEventType_OnClipboardFormatChanged - }; - hrc = tstRegisterClipboardListener(ptrReflectEventSource, s_aFormatEventTypes, RT_ELEMENTS(s_aFormatEventTypes), - ptrReflectListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(reflection writer) failed, hrc=%Rhrc\n", hrc)); - fReflectListenerRegistered = true; - hrc = tstRegisterClipboardListener(ptrObserverEventSource, s_aFormatEventTypes, RT_ELEMENTS(s_aFormatEventTypes), - ptrObserverListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(reflection observer) failed, hrc=%Rhrc\n", hrc)); - fObserverListenerRegistered = true; - - hrc = ptrReflectSession->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Session WriteFormats(ExcludeReflections) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrReflectEvent; - VBoxEventType_T enmReflectEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrReflectEventSource, ptrReflectListener, s_aFormatEventTypes, - RT_ELEMENTS(s_aFormatEventTypes), 1000 /* cMsTimeout */, - "ExcludeReflections direct writer", ptrReflectEvent, &enmReflectEventType); - RTTESTI_CHECK(fRc); - if (fRc) - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrReflectEvent, "ExcludeReflections direct writer format event", - idReflectSession, ClipboardSource_Host, - "text/plain;charset=utf-8")); - - ComPtr ptrObserverEvent; - VBoxEventType_T enmObserverEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aFormatEventTypes, - RT_ELEMENTS(s_aFormatEventTypes), 1000 /* cMsTimeout */, - "ExcludeReflections observer", ptrObserverEvent, &enmObserverEventType); - RTTESTI_CHECK(fRc); - if (fRc) - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrObserverEvent, "ExcludeReflections observer format event", - idReflectSession, ClipboardSource_Host, - "text/plain;charset=utf-8")); - - hrc = pClipboard->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Anonymous WriteFormats(ExcludeReflections echo) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrObserverEchoEvent; - VBoxEventType_T enmObserverEchoEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aFormatEventTypes, - RT_ELEMENTS(s_aFormatEventTypes), 1000 /* cMsTimeout */, - "ExcludeReflections observer echo", ptrObserverEchoEvent, &enmObserverEchoEventType); - RTTESTI_CHECK(fRc); - if (fRc) - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrObserverEchoEvent, - "ExcludeReflections observer anonymous echo event", - VBOX_SHCL_MAIN_CLIENT_NONE, ClipboardSource_Host, - "text/plain;charset=utf-8")); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrReflectEventSource, ptrReflectListener, 250 /* cMsTimeout */, - "ExcludeReflections anonymous echo")); - } while (0); - - if (fReflectListenerRegistered && ptrReflectEventSource.isNotNull() && ptrReflectListener.isNotNull()) - ptrReflectEventSource->UnregisterListener(ptrReflectListener); - if (fObserverListenerRegistered && ptrObserverEventSource.isNotNull() && ptrObserverListener.isNotNull()) - ptrObserverEventSource->UnregisterListener(ptrObserverListener); - ptrReflectListener.setNull(); - ptrObserverListener.setNull(); - - tstClipboardCloseSession(ptrReflectSession, "ExcludeReflections session"); - tstClipboardCloseSession(ptrObserverSession, "ExcludeReflections observer session"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after ExcludeReflections test failed, hrc=%Rhrc\n", hrc)); - } - - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMSETTER(Mode)(Bidirectional) after sessions failed, hrc=%Rhrc\n", hrc)); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after session API subtest failed, hrc=%Rhrc\n", hrc)); -} - - - -/** - * Tests the public console clipboard API. - * - * @param hTest Test handle. - */ -static void tstClipboardPublicApi(RTTEST hTest) -{ - RTTestSub(hTest, "Clipboard public API"); - - HRESULT hrc = S_OK; - bool fMachineRegistered = false; - bool fMachineLocked = false; - bool fMachinePoweredOn = false; - bool fListenerRegistered = false; - ComPtr ptrVirtualBoxClient; - ComPtr ptrVirtualBox; - ComPtr ptrSession; - ComPtr ptrMachine; - ComPtr ptrConsole; - ComPtr ptrClipboard; - ComPtr ptrEventSource; - ComPtr ptrListener; - ComPtr ptrSurvivingSession; - ULONG idSurvivingSession = VBOX_SHCL_MAIN_CLIENT_NONE; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - char szFile1[RTPATH_MAX] = ""; - char szDir1[RTPATH_MAX] = ""; - char szDirFile1[RTPATH_MAX] = ""; - bool fFile1Created = false; - bool fDir1Created = false; - bool fDirFile1Created = false; -#endif - - do - { - /* Create the frontend objects and a throwaway VM used for live console clipboard testing. */ - hrc = ptrVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("createInprocObject(CLSID_VirtualBoxClient) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrVirtualBoxClient->COMGETTER(VirtualBox)(ptrVirtualBox.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(VirtualBox) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrHost; - hrc = ptrVirtualBox->COMGETTER(Host)(ptrHost.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Host) failed, hrc=%Rhrc\n", hrc)); - PlatformArchitecture_T enmPlatformArch = PlatformArchitecture_x86; - hrc = ptrHost->COMGETTER(Architecture)(&enmPlatformArch); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Architecture) failed, hrc=%Rhrc\n", hrc)); - - RTUUID uuid; - int vrc = RTUuidCreate(&uuid); - RTTESTI_CHECK_RC_BREAK(vrc, VINF_SUCCESS); - char szMachineName[64]; - RTStrPrintf(szMachineName, sizeof(szMachineName), "tstClipboard-%RTuuid", &uuid); - - SafeArray aGroups; - hrc = ptrVirtualBox->CreateMachine(NULL, /* Settings file */ - Bstr(szMachineName).raw(), /* Name */ - enmPlatformArch, - ComSafeArrayAsInParam(aGroups), /* Groups */ - NULL, /* OS type */ - NULL, /* Flags */ - NULL, /* Cipher */ - NULL, /* Password ID */ - NULL, /* Password */ - ptrMachine.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateMachine failed, hrc=%Rhrc\n", hrc)); - - /* Verify default clipboard settings before enabling the directions this testcase exercises. */ - ComPtr ptrClipboardSettings; - hrc = ptrMachine->COMGETTER(Clipboard)(ptrClipboardSettings.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(IMachine::Clipboard) failed, hrc=%Rhrc\n", hrc)); - - ClipboardMode_T enmInitialMode = ClipboardMode_Bidirectional; - hrc = ptrClipboardSettings->COMGETTER(Mode)(&enmInitialMode); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(IClipboardSettings::Mode) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmInitialMode == ClipboardMode_Disabled); - - BOOL fFileTransfersEnabled = TRUE; - hrc = ptrClipboardSettings->COMGETTER(FileTransfersEnabled)(&fFileTransfersEnabled); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(IClipboardSettings::FileTransfersEnabled) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!fFileTransfersEnabled); - - hrc = ptrClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMSETTER(Mode) failed, hrc=%Rhrc\n", hrc)); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - hrc = ptrClipboardSettings->COMSETTER(FileTransfersEnabled)(TRUE); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMSETTER(FileTransfersEnabled) failed, hrc=%Rhrc\n", hrc)); -#endif - - /* Register and launch the VM so the console-scoped clipboard service is available. */ - hrc = ptrVirtualBox->RegisterMachine(ptrMachine); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterMachine failed, hrc=%Rhrc\n", hrc)); - fMachineRegistered = true; - - hrc = ptrSession.createInprocObject(CLSID_Session); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("createInprocObject(CLSID_Session) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrLaunchProgress; - hrc = ptrMachine->LaunchVMProcess(ptrSession, Bstr("headless").raw(), - ComSafeArrayNullInParam(), ptrLaunchProgress.asOutParam()); - if (FAILED(hrc)) - { - tstLogErrorInfo(hTest, "LaunchVMProcess", ErrorInfo(ptrMachine, COM_IIDOF(IMachine))); - RTTestSkipped(hTest, "LaunchVMProcess failed, hrc=%Rhrc", hrc); - break; - } - RTTESTI_CHECK_MSG_BREAK(!ptrLaunchProgress.isNull(), ("LaunchVMProcess returned no progress object\n")); - hrc = ptrLaunchProgress->WaitForCompletion(-1); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WaitForCompletion(LaunchVMProcess) failed, hrc=%Rhrc\n", hrc)); - LONG lrcLaunchResult = S_OK; - hrc = ptrLaunchProgress->COMGETTER(ResultCode)(&lrcLaunchResult); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(ResultCode) failed, hrc=%Rhrc\n", hrc)); - if (FAILED((HRESULT)lrcLaunchResult)) - { - tstLogErrorInfo(hTest, "LaunchVMProcess progress", ProgressErrorInfo(ptrLaunchProgress)); - RTTestSkipped(hTest, "LaunchVMProcess result code is %Rhrc", lrcLaunchResult); - break; - } - fMachineLocked = true; - fMachinePoweredOn = true; - - /* Resolve the live console and clipboard objects under test. */ - hrc = ptrSession->COMGETTER(Console)(ptrConsole.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Console) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrConsole.isNull(), ("COMGETTER(Console) returned NULL\n")); - - ComPtr ptrSessionMachine; - hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Machine) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrSessionMachine->COMGETTER(Clipboard)(ptrClipboardSettings.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(IInternalSession::Machine::Clipboard) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrConsole->COMGETTER(Clipboard)(ptrClipboard.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Clipboard) failed, hrc=%Rhrc\n", hrc)); - - /* Verify object construction helpers before exercising live clipboard state. */ - tstClipboardPublicObjects(hTest, ptrClipboard); - RTTestSub(hTest, "Clipboard public API setup"); - - /* Verify transfer source-path storage and the auxiliary transfer/event-source getters. */ -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - ComPtr ptrTransfers; - hrc = ptrClipboard->COMGETTER(Transfers)(ptrTransfers.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Transfers) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrTransfers.isNull(), ("COMGETTER(Transfers) returned NULL\n")); - - SafeIfaceArray aTransfers; - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any initial) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aTransfers.size() == 0); - aTransfers.setNull(); - hrc = ptrTransfers->GetTransfers((ClipboardTransferDirection_T)999, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("GetTransfers(invalid direction) returned hrc=%Rhrc, expected E_INVALIDARG\n", hrc)); - - char szTmpDir[RTPATH_MAX]; - vrc = RTPathTemp(szTmpDir, sizeof(szTmpDir)); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTPathTemp failed, vrc=%Rrc\n", vrc)); - char szTmpName[64]; - RTStrPrintf(szTmpName, sizeof(szTmpName), "tstClipboard-%RU64-1.txt", RTTimeNanoTS()); - vrc = RTPathJoin(szFile1, sizeof(szFile1), szTmpDir, szTmpName); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTPathJoin(%s, %s) failed, vrc=%Rrc\n", szTmpDir, szTmpName, vrc)); - - static const char s_szFile1Data[] = "clipboard transfer data one"; - RTFILE hFile = NIL_RTFILE; - vrc = RTFileOpen(&hFile, szFile1, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTFileOpen(%s) failed, vrc=%Rrc\n", szFile1, vrc)); - fFile1Created = true; - vrc = RTFileWrite(hFile, s_szFile1Data, sizeof(s_szFile1Data) - 1, NULL /* pcbWritten */); - RTTESTI_CHECK_MSG(RT_SUCCESS(vrc), ("RTFileWrite(%s) failed, vrc=%Rrc\n", szFile1, vrc)); - RTFileClose(hFile); - - ComPtr ptrMainTransferEventSource; - hrc = ptrClipboard->COMGETTER(EventSource)(ptrMainTransferEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc) && !ptrMainTransferEventSource.isNull(), - ("COMGETTER(EventSource for Create) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrMainTransferListener; - static VBoxEventType_T const s_aMainTransferEventTypes[] = - { - VBoxEventType_OnClipboardTransfer - }; - hrc = tstRegisterClipboardListener(ptrMainTransferEventSource, s_aMainTransferEventTypes, - RT_ELEMENTS(s_aMainTransferEventTypes), ptrMainTransferListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc) && !ptrMainTransferListener.isNull(), - ("RegisterListener(Create) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrTransfer; - hrc = ptrTransfers->Create(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, - ClipboardAction_Copy, ptrTransfer.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Create(ToGuest, Host, Copy) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrTransfer.isNull(), ("Create(ToGuest, Host, Copy) returned NULL transfer\n")); - - ULONG idMainTransfer = 0; - hrc = ptrTransfer->COMGETTER(Id)(&idMainTransfer); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id Main-created transfer) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrMainAddedEvent; - VBoxEventType_T enmMainAddedEventType = VBoxEventType_Invalid; - bool fMainTransferEvent = tstClipboardWaitForAnyEvent(ptrMainTransferEventSource, ptrMainTransferListener, - s_aMainTransferEventTypes, - RT_ELEMENTS(s_aMainTransferEventTypes), - 1000 /* cMsTimeout */, "Main-created transfer added", - ptrMainAddedEvent, &enmMainAddedEventType); - RTTESTI_CHECK(fMainTransferEvent); - if (fMainTransferEvent) - { - ComPtr ptrAddedTransfer; - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrMainAddedEvent, "Main-created transfer added", - ClipboardTransferState_Added, - ClipboardTransferDirection_ToGuest, - ClipboardSource_Host, idMainTransfer, ptrAddedTransfer)); - RTTESTI_CHECK(ptrAddedTransfer == ptrTransfer); - } - - aTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after Create) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aTransfers.size() == 1); - if (aTransfers.size() == 1) - RTTESTI_CHECK(aTransfers[0] == ptrTransfer); - SafeIfaceArray aGuestTransfers; - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToGuest, 0, - ComSafeArrayAsOutParam(aGuestTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToGuest after Create) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aGuestTransfers.size() == 1); - SafeIfaceArray aHostTransfers; - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToHost, 0, - ComSafeArrayAsOutParam(aHostTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToHost after Create) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aHostTransfers.size() == 0); - - SafeArray aSourcePaths; - hrc = ptrTransfer->GetSourcePaths(ComSafeArrayAsOutParam(aSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSourcePaths initial failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSourcePaths.size() == 0); - - SafeArray aNewSourcePaths; - RTTESTI_CHECK(aNewSourcePaths.push_back(Bstr(szFile1).raw())); - hrc = ptrTransfer->SetSourcePaths(ComSafeArrayAsInParam(aNewSourcePaths)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SetSourcePaths failed, hrc=%Rhrc\n", hrc)); - aSourcePaths.setNull(); - hrc = ptrTransfer->GetSourcePaths(ComSafeArrayAsOutParam(aSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSourcePaths after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSourcePaths.size() == 1); - if (aSourcePaths.size() == 1) - RTTESTI_CHECK(!RTUtf16Cmp(aSourcePaths[0], Bstr(szFile1).raw())); - - hrc = ptrTransfers->Pause(ptrTransfer); - RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Pause returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); - hrc = ptrTransfers->Resume(ptrTransfer); - RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Resume returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); - hrc = ptrTransfers->Approve(ptrTransfer, 0 /* aFlags */); - RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Approve returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); - hrc = ptrTransfers->Deny(ptrTransfer, Bstr("").raw()); - RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Deny returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); - hrc = ptrTransfers->Respond(ptrTransfer, ClipboardTransferInteraction_Approval, Bstr("").raw(), - ClipboardTransferResponse_Accept, Bstr("").raw(), 0 /* aFlags */); - RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Respond returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); - - aTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after SourcePaths) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aTransfers.size() == 1); - if (aTransfers.size() == 1) - RTTESTI_CHECK(aTransfers[0] == ptrTransfer); - aGuestTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToGuest, 0, ComSafeArrayAsOutParam(aGuestTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToGuest after SourcePaths) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aGuestTransfers.size() == 1); - aHostTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToHost, 0, ComSafeArrayAsOutParam(aHostTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToHost after SourcePaths) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aHostTransfers.size() == 0); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrMainTransferEventSource, ptrMainTransferListener, - 100 /* cMsTimeout */, "Main-created transfer source-path update")); - if (ptrTransfer.isNotNull()) - { - SafeIfaceArray aRootNodes; - hrc = ptrTransfer->Roots(ComSafeArrayAsOutParam(aRootNodes)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::Roots failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aRootNodes.size() == 1); - if (aRootNodes.size() == 1) - { - Bstr bstrPath; - hrc = aRootNodes[0]->COMGETTER(Path)(bstrPath.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFsObjInfo::COMGETTER(Path) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrPath).c_str(), RTPathFilename(szFile1))); - } - - ComPtr ptrInvalidNode; - hrc = ptrTransfer->Query(Bstr("../host-file").raw(), ptrInvalidNode.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::Query('../host-file') unexpectedly succeeded\n")); - hrc = ptrTransfer->Query(Bstr("host-file/").raw(), ptrInvalidNode.asOutParam()); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("IClipboardTransfer::Query('host-file/') returned hrc=%Rhrc\n", hrc)); - SafeIfaceArray aInvalidNodes; - hrc = ptrTransfer->List(Bstr("/absolute").raw(), ClipboardTransferListFlag_None, ComSafeArrayAsOutParam(aInvalidNodes)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::List('/absolute') unexpectedly succeeded\n")); - - ComPtr ptrUnsupportedFile; - hrc = ptrTransfer->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadWrite, - FileOpenAction_OpenExisting, FileSharingMode_Read, 0 /* creationMode */, - ptrUnsupportedFile.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::OpenFile(ReadWrite) unexpectedly succeeded\n")); - hrc = ptrTransfer->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadOnly, - FileOpenAction_CreateOrReplace, FileSharingMode_Read, 0 /* creationMode */, - ptrUnsupportedFile.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::OpenFile(CreateOrReplace) unexpectedly succeeded\n")); - - ComPtr ptrFile; - hrc = ptrTransfer->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadOnly, - FileOpenAction_OpenExisting, FileSharingMode_Read, 0 /* creationMode */, - ptrFile.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::OpenFile failed, hrc=%Rhrc\n", hrc)); - if (SUCCEEDED(hrc) && ptrFile.isNotNull()) - { - LONG64 cbInitial = -1; - hrc = ptrFile->COMGETTER(InitialSize)(&cbInitial); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::COMGETTER(InitialSize) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cbInitial == (LONG64)sizeof(s_szFile1Data) - 1); - LONG64 cbSize = -1; - hrc = ptrFile->QuerySize(&cbSize); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::QuerySize failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cbSize == (LONG64)sizeof(s_szFile1Data) - 1); - ComPtr ptrFileInfo; - hrc = ptrFile->QueryInfo(ptrFileInfo.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::QueryInfo failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrFileInfo.isNull()); - SafeArray aWriteData; - RTTESTI_CHECK(aWriteData.push_back('x')); - ULONG cbWritten = 0; - hrc = ptrFile->Write(ComSafeArrayAsInParam(aWriteData), 0 /* timeoutMS */, &cbWritten); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferFile::Write unexpectedly succeeded\n")); - - SafeArray aOversizedFileData; - hrc = ptrFile->Read(SHCL_TRANSFER_DEFAULT_MAX_CHUNK_SIZE + 1, 0 /* timeoutMS */, - ComSafeArrayAsOutParam(aOversizedFileData)); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("IClipboardTransferFile::Read(oversized) returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aOversizedFileData.size() == 0); - - SafeArray aFileData; - hrc = ptrFile->Read(sizeof(s_szFile1Data) - 1, 0 /* timeoutMS */, ComSafeArrayAsOutParam(aFileData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::Read failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aFileData.size() == sizeof(s_szFile1Data) - 1); - if (aFileData.size() == sizeof(s_szFile1Data) - 1) - RTTESTI_CHECK(!memcmp(aFileData.raw(), s_szFile1Data, sizeof(s_szFile1Data) - 1)); - LONG64 offNew = -1; - hrc = ptrFile->Seek(INT64_MAX, FileSeekOrigin_Current, &offNew); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("IClipboardTransferFile::Seek(overflow) returned hrc=%Rhrc\n", hrc)); - hrc = ptrFile->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::Close failed, hrc=%Rhrc\n", hrc)); - } - - ComPtr ptrTransferData; - hrc = ptrTransfer->COMGETTER(Data)(ptrTransferData.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IClipboardTransfer::COMGETTER(Data) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrTransferData.isNull(), - ("IClipboardTransfer::COMGETTER(Data) returned NULL\n")); - - LONG64 cRoots = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_RootList, Bstr("").raw(), Bstr("").raw(), - 0 /* aFlags */, &cRoots); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Open(RootList) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cRoots == 1); - - Bstr bstrRootName; - ULONG fInfo = 0; - SafeArray aInfo; - SafeArray aData; - hrc = ptrTransferData->Read(ClipboardTransferDataType_RootList, 0 /* aHandle */, 0 /* aSize */, - 0 /* aFlags */, bstrRootName.asOutParam(), &fInfo, - ComSafeArrayAsOutParam(aInfo), ComSafeArrayAsOutParam(aData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Read(RootList) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(fInfo & VBOX_SHCL_INFO_F_FSOBJINFO); - RTTESTI_CHECK(aInfo.size() == sizeof(SHCLFSOBJINFO)); - - LONG64 hInvalid = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_List, Bstr("../parent").raw(), Bstr("").raw(), - 0 /* aFlags */, &hInvalid); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("IClipboardTransferData::Open(List '../parent') returned hrc=%Rhrc\n", hrc)); - hrc = ptrTransferData->Open(ClipboardTransferDataType_List, Bstr("/absolute").raw(), Bstr("").raw(), - 0 /* aFlags */, &hInvalid); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("IClipboardTransferData::Open(List '/absolute') returned hrc=%Rhrc\n", hrc)); - hrc = ptrTransferData->Open(ClipboardTransferDataType_Object, Bstr("dir\\file").raw(), Bstr("").raw(), - SHCL_OBJ_CF_ACCESS_READ | SHCL_OBJ_CF_ACCESS_DENYWRITE, &hInvalid); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferData::Open(Object 'dir\\file') unexpectedly succeeded\n")); - hrc = ptrTransferData->Open(ClipboardTransferDataType_Object, Bstr("C:file").raw(), Bstr("").raw(), - SHCL_OBJ_CF_ACCESS_READ | SHCL_OBJ_CF_ACCESS_DENYWRITE, &hInvalid); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferData::Open(Object 'C:file') unexpectedly succeeded\n")); - - LONG64 hObj = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_Object, bstrRootName.raw(), Bstr("").raw(), - SHCL_OBJ_CF_ACCESS_READ | SHCL_OBJ_CF_ACCESS_DENYWRITE, &hObj); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Open(Object) failed, hrc=%Rhrc\n", hrc)); - SafeArray aOversizedObjData; - Bstr bstrOversizedObjName; - ULONG fOversizedObjInfo = 0; - SafeArray aOversizedObjInfo; - hrc = ptrTransferData->Read(ClipboardTransferDataType_Object, hObj, - SHCL_TRANSFER_DEFAULT_MAX_CHUNK_SIZE + 1, 0 /* aFlags */, - bstrOversizedObjName.asOutParam(), &fOversizedObjInfo, - ComSafeArrayAsOutParam(aOversizedObjInfo), - ComSafeArrayAsOutParam(aOversizedObjData)); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("IClipboardTransferData::Read(Object oversized) returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aOversizedObjData.size() == 0); - SafeArray aObjData; - Bstr bstrObjName; - ULONG fObjInfo = 0; - SafeArray aObjInfo; - hrc = ptrTransferData->Read(ClipboardTransferDataType_Object, hObj, sizeof(s_szFile1Data) - 1, - 0 /* aFlags */, bstrObjName.asOutParam(), &fObjInfo, - ComSafeArrayAsOutParam(aObjInfo), ComSafeArrayAsOutParam(aObjData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Read(Object) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aObjData.size() == sizeof(s_szFile1Data) - 1); - if (aObjData.size() == sizeof(s_szFile1Data) - 1) - RTTESTI_CHECK(!memcmp(aObjData.raw(), s_szFile1Data, sizeof(s_szFile1Data) - 1)); - ULONG cbObjWritten = 0; - hrc = ptrTransferData->Write(ClipboardTransferDataType_Object, hObj, Bstr("").raw(), 0 /* aInfoFlags */, - ComSafeArrayAsInParam(aObjInfo), ComSafeArrayAsInParam(aObjData), - 1 /* aFlags */, &cbObjWritten); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferData::Write(Object with flags) unexpectedly succeeded\n")); - RTTESTI_CHECK(cbObjWritten == 0); - hrc = ptrTransferData->Close(ClipboardTransferDataType_Object, hObj); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Close(Object) failed, hrc=%Rhrc\n", hrc)); - } - - RTStrPrintf(szTmpName, sizeof(szTmpName), "tstClipboard-%RU64-dir", RTTimeNanoTS()); - vrc = RTPathJoin(szDir1, sizeof(szDir1), szTmpDir, szTmpName); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTPathJoin(%s, %s) failed, vrc=%Rrc\n", szTmpDir, szTmpName, vrc)); - vrc = RTDirCreate(szDir1, 0700 /* fMode */, 0 /* fCreate */); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTDirCreate(%s) failed, vrc=%Rrc\n", szDir1, vrc)); - fDir1Created = true; - vrc = RTPathJoin(szDirFile1, sizeof(szDirFile1), szDir1, "tstClipboard-list-entry.txt"); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTPathJoin(%s, tstClipboard-list-entry.txt) failed, vrc=%Rrc\n", - szDir1, vrc)); - hFile = NIL_RTFILE; - vrc = RTFileOpen(&hFile, szDirFile1, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTFileOpen(%s) failed, vrc=%Rrc\n", szDirFile1, vrc)); - fDirFile1Created = true; - static const char s_szDirFile1Data[] = "clipboard transfer list data"; - vrc = RTFileWrite(hFile, s_szDirFile1Data, sizeof(s_szDirFile1Data) - 1, NULL /* pcbWritten */); - RTTESTI_CHECK_MSG(RT_SUCCESS(vrc), ("RTFileWrite(%s) failed, vrc=%Rrc\n", szDirFile1, vrc)); - RTFileClose(hFile); - - SafeArray aDirSourcePaths; - RTTESTI_CHECK(aDirSourcePaths.push_back(Bstr(szDir1).raw())); - hrc = ptrTransfer->SetSourcePaths(ComSafeArrayAsInParam(aDirSourcePaths)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SetSourcePaths directory failed, hrc=%Rhrc\n", hrc)); - aSourcePaths.setNull(); - hrc = ptrTransfer->GetSourcePaths(ComSafeArrayAsOutParam(aSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSourcePaths directory failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSourcePaths.size() == 1); - if (aSourcePaths.size() == 1) - RTTESTI_CHECK(!RTUtf16Cmp(aSourcePaths[0], Bstr(szDir1).raw())); - aTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after directory SourcePaths) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aTransfers.size() == 1); - if (aTransfers.size() == 1) - RTTESTI_CHECK(aTransfers[0] == ptrTransfer); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrMainTransferEventSource, ptrMainTransferListener, - 100 /* cMsTimeout */, "Main-created transfer directory update")); - if (ptrTransfer.isNotNull()) - { - SafeIfaceArray aDirNodes; - hrc = ptrTransfer->List(Bstr("").raw(), ClipboardTransferListFlag_None, ComSafeArrayAsOutParam(aDirNodes)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::List(recursive) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aDirNodes.size() == 2); - SafeIfaceArray aDirRootOnly; - hrc = ptrTransfer->List(Bstr("").raw(), ClipboardTransferListFlag_NoRecursion, ComSafeArrayAsOutParam(aDirRootOnly)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::List(NoRecursion) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aDirRootOnly.size() == 1); - - ComPtr ptrInvalidDirectory; - hrc = ptrTransfer->OpenDirectory(Bstr("").raw(), ClipboardTransferListFlag_NoRecursion, - ptrInvalidDirectory.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::OpenDirectory(empty path) unexpectedly succeeded\n")); - - ComPtr ptrDirectory; - hrc = ptrTransfer->OpenDirectory(Bstr(RTPathFilename(szDir1)).raw(), ClipboardTransferListFlag_NoRecursion, - ptrDirectory.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::OpenDirectory failed, hrc=%Rhrc\n", hrc)); - if (SUCCEEDED(hrc) && ptrDirectory.isNotNull()) - { - SafeIfaceArray aChildren; - hrc = ptrDirectory->ListEx(16, ClipboardTransferListFlag_NoRecursion, ComSafeArrayAsOutParam(aChildren)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::ListEx failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aChildren.size() == 1); - hrc = ptrDirectory->Rewind(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::Rewind failed, hrc=%Rhrc\n", hrc)); - SafeIfaceArray aRootAndChildren; - hrc = ptrDirectory->ListEx(16, - ClipboardTransferListFlag_NoRecursion - | ClipboardTransferListFlag_IncludeRoot, - ComSafeArrayAsOutParam(aRootAndChildren)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IClipboardTransferDirectory::ListEx(IncludeRoot, NoRecursion) failed, hrc=%Rhrc\n", - hrc)); - RTTESTI_CHECK(aRootAndChildren.size() == 2); - hrc = ptrDirectory->Rewind(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IClipboardTransferDirectory::Rewind after IncludeRoot failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrChild; - hrc = ptrDirectory->Read(ptrChild.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::Read after rewind failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrChild.isNull()); - hrc = ptrDirectory->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::Close failed, hrc=%Rhrc\n", hrc)); - } - - ComPtr ptrTransferData; - hrc = ptrTransfer->COMGETTER(Data)(ptrTransferData.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), - ("IClipboardTransfer::COMGETTER(Data directory) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrTransferData.isNull(), - ("IClipboardTransfer::COMGETTER(Data directory) returned NULL\n")); - - LONG64 cRoots = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_RootList, Bstr("").raw(), Bstr("").raw(), - 0 /* aFlags */, &cRoots); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Open(directory RootList) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cRoots == 1); - - Bstr bstrDirRootName; - ULONG fDirRootInfo = 0; - SafeArray aDirRootInfo; - SafeArray aDirRootData; - hrc = ptrTransferData->Read(ClipboardTransferDataType_RootList, 0 /* aHandle */, 0 /* aSize */, - 0 /* aFlags */, bstrDirRootName.asOutParam(), &fDirRootInfo, - ComSafeArrayAsOutParam(aDirRootInfo), ComSafeArrayAsOutParam(aDirRootData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Read(directory RootList) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(fDirRootInfo & VBOX_SHCL_INFO_F_FSOBJINFO); - RTTESTI_CHECK(aDirRootInfo.size() == sizeof(SHCLFSOBJINFO)); - - LONG64 hList = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_List, bstrDirRootName.raw(), Bstr("").raw(), - 0 /* aFlags */, &hList); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Open(List) failed, hrc=%Rhrc\n", hrc)); - - Bstr bstrListName; - ULONG fListInfo = 0; - SafeArray aListInfo; - SafeArray aListData; - hrc = ptrTransferData->Read(ClipboardTransferDataType_List, hList, 0 /* aSize */, 0 /* aFlags */, - bstrListName.asOutParam(), &fListInfo, - ComSafeArrayAsOutParam(aListInfo), ComSafeArrayAsOutParam(aListData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Read(List) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrListName).c_str(), RTPathFilename(szDirFile1))); - RTTESTI_CHECK(fListInfo & VBOX_SHCL_INFO_F_FSOBJINFO); - RTTESTI_CHECK(aListInfo.size() == sizeof(SHCLFSOBJINFO)); - - ULONG cbListWritten = 0; - hrc = ptrTransferData->Write(ClipboardTransferDataType_List, hList, Bstr("invalid-none").raw(), - VBOX_SHCL_INFO_F_NONE, ComSafeArrayAsInParam(aListInfo), - ComSafeArrayAsInParam(aListData), 0 /* aFlags */, &cbListWritten); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("IClipboardTransferData::Write(List mismatched NONE info) returned hrc=%Rhrc\n", hrc)); - SafeArray aEmptyListInfo; - hrc = ptrTransferData->Write(ClipboardTransferDataType_List, hList, Bstr("invalid-fs-info").raw(), - VBOX_SHCL_INFO_F_FSOBJINFO, ComSafeArrayAsInParam(aEmptyListInfo), - ComSafeArrayAsInParam(aListData), 0 /* aFlags */, &cbListWritten); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("IClipboardTransferData::Write(List missing FS info) returned hrc=%Rhrc\n", hrc)); - hrc = ptrTransferData->Write(ClipboardTransferDataType_List, hList, Bstr("new-entry").raw(), - VBOX_SHCL_INFO_F_FSOBJINFO, ComSafeArrayAsInParam(aListInfo), - ComSafeArrayAsInParam(aListData), 0 /* aFlags */, &cbListWritten); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferData::Write(List) unexpectedly succeeded\n")); - RTTESTI_CHECK(cbListWritten == 0); - - hrc = ptrTransferData->Close(ClipboardTransferDataType_List, hList); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Close(List) failed, hrc=%Rhrc\n", hrc)); - } - - hrc = ptrTransfers->Remove(ptrTransfer); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferManager::Remove(source-path transfer) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrMainRemovedEvent; - VBoxEventType_T enmMainRemovedEventType = VBoxEventType_Invalid; - fMainTransferEvent = tstClipboardWaitForAnyEvent(ptrMainTransferEventSource, ptrMainTransferListener, - s_aMainTransferEventTypes, - RT_ELEMENTS(s_aMainTransferEventTypes), - 1000 /* cMsTimeout */, "Main-created transfer removed", - ptrMainRemovedEvent, &enmMainRemovedEventType); - RTTESTI_CHECK(fMainTransferEvent); - if (fMainTransferEvent) - { - ComPtr ptrRemovedTransfer; - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrMainRemovedEvent, "Main-created transfer removed", - ClipboardTransferState_Removed, - ClipboardTransferDirection_ToGuest, - ClipboardSource_Host, idMainTransfer, ptrRemovedTransfer)); - RTTESTI_CHECK(ptrRemovedTransfer == ptrTransfer); - } - aTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after source-path transfer remove) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aTransfers.size() == 0); - hrc = ptrTransfers->Remove(ptrTransfer); - RTTESTI_CHECK_MSG(hrc == VBOX_E_OBJECT_NOT_FOUND, - ("Repeated IClipboardTransferManager::Remove returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrMainTransferEventSource, ptrMainTransferListener, - 100 /* cMsTimeout */, "stale Main-created transfer remove")); - hrc = ptrMainTransferEventSource->UnregisterListener(ptrMainTransferListener); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("UnregisterListener(Main transfer) failed, hrc=%Rhrc\n", hrc)); -#endif - - hrc = ptrClipboard->COMGETTER(EventSource)(ptrEventSource.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(EventSource) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrEventSource.isNull()); - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RTTestSub(hTest, "Clipboard service transfer lifecycle"); - ComPtr ptrTransferListener; - static VBoxEventType_T const s_aTransferEventTypes[] = - { - VBoxEventType_OnClipboardTransfer - }; - hrc = tstRegisterClipboardListener(ptrEventSource, s_aTransferEventTypes, RT_ELEMENTS(s_aTransferEventTypes), - ptrTransferListener); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("RegisterListener(service transfer) failed, hrc=%Rhrc\n", hrc)); - if (SUCCEEDED(hrc)) - { - ComPtr ptrInternalClipboardControl(ptrClipboard); - RTTESTI_CHECK_MSG(!ptrInternalClipboardControl.isNull(), - ("Query IInternalClipboardControl(service transfer) returned NULL\n")); - if (ptrInternalClipboardControl.isNotNull()) - { - hrc = ptrInternalClipboardControl->SetTransferStatus(0 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus accepted a zero service session, hrc=%Rhrc\n", hrc)); - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - 0xfeed /* aStatus */, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus accepted an invalid status, hrc=%Rhrc\n", hrc)); - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_INITIALIZED, VERR_ACCESS_DENIED); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus accepted a non-error status with a failing result, hrc=%Rhrc\n", - hrc)); - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_ERROR, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus accepted ERROR with a successful result, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "status/result-inconsistent service transfer")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(REQUESTED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrRequestedEvent; - VBoxEventType_T enmRequestedEventType = VBoxEventType_Invalid; - bool fRequestedRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "requested service transfer", ptrRequestedEvent, - &enmRequestedEventType); - RTTESTI_CHECK(fRequestedRc); - if (fRequestedRc) - { - ComPtr ptrRequestedTransfer; - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrRequestedEvent, "requested service transfer", - ClipboardTransferState_Added, - ClipboardTransferDirection_ToHost, - ClipboardSource_Guest, 76, ptrRequestedTransfer)); - } - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus accepted REQUESTED to COMPLETED, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "requested-to-completed service transfer")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 76 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_ERROR, VERR_ACCESS_DENIED); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(requested ERROR) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrRequestedErrorEvent; - VBoxEventType_T enmRequestedErrorEventType = VBoxEventType_Invalid; - fRequestedRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "requested service transfer error", ptrRequestedErrorEvent, - &enmRequestedErrorEventType); - RTTESTI_CHECK(fRequestedRc); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(INITIALIZED) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrAddedEvent; - VBoxEventType_T enmAddedEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "service transfer added", ptrAddedEvent, &enmAddedEventType); - RTTESTI_CHECK(fRc); - ComPtr ptrStatusTransfer; - if (fRc) - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrAddedEvent, "service transfer added", - ClipboardTransferState_Added, - ClipboardTransferDirection_ToHost, - ClipboardSource_Guest, 77, ptrStatusTransfer)); - - ComPtr ptrStatusProgress; - if (ptrStatusTransfer.isNotNull()) - { - hrc = ptrStatusTransfer->COMGETTER(Progress)(ptrStatusProgress.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IClipboardTransfer::COMGETTER(Progress) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrStatusProgress.isNull()); - } - if (ptrStatusProgress.isNotNull()) - { - BOOL fCompleted = TRUE; - hrc = ptrStatusProgress->COMGETTER(Completed)(&fCompleted); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IProgress::COMGETTER(Completed) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!fCompleted); - } - - SafeIfaceArray aStatusTransfers; - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToHost, 0, - ComSafeArrayAsOutParam(aStatusTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToHost service) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aStatusTransfers.size() == 1); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus(backward REQUESTED) returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "backward service transfer status")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Host, - SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus(source-mismatched STARTED) returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "source-mismatched service transfer status")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(STARTED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrStartedEvent; - VBoxEventType_T enmStartedEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "service transfer started", ptrStartedEvent, &enmStartedEventType); - RTTESTI_CHECK(fRc); - ComPtr ptrStartedTransfer; - if (fRc) - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrStartedEvent, "service transfer started", - ClipboardTransferState_InProgress, - ClipboardTransferDirection_ToHost, - ClipboardSource_Guest, 77, ptrStartedTransfer)); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus(backward INITIALIZED) returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "backward initialized service transfer status")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(duplicate STARTED) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "duplicate service transfer status")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(COMPLETED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrCompletedEvent; - VBoxEventType_T enmCompletedEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "service transfer completed", ptrCompletedEvent, &enmCompletedEventType); - RTTESTI_CHECK(fRc); - ComPtr ptrCompletedTransfer; - if (fRc) - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrCompletedEvent, "service transfer completed", - ClipboardTransferState_Completed, - ClipboardTransferDirection_ToHost, - ClipboardSource_Guest, 77, ptrCompletedTransfer)); - if (ptrStatusProgress.isNotNull()) - { - BOOL fCompleted = FALSE; - hrc = ptrStatusProgress->COMGETTER(Completed)(&fCompleted); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IProgress::COMGETTER(Completed after completion) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(fCompleted); - LONG hrcResult = E_FAIL; - hrc = ptrStatusProgress->COMGETTER(ResultCode)(&hrcResult); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IProgress::COMGETTER(ResultCode after completion) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(hrcResult == S_OK); - } - aStatusTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, - ComSafeArrayAsOutParam(aStatusTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers after service completion failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aStatusTransfers.size() == 0); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* stale generation */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus(stale STARTED) returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "stale service transfer generation")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 2 /* aGeneration */, ClipboardSource_Host, - SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("SetTransferStatus(second INITIALIZED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrSecondAddedEvent; - VBoxEventType_T enmSecondAddedEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "second service transfer added", ptrSecondAddedEvent, - &enmSecondAddedEventType); - RTTESTI_CHECK(fRc); - ComPtr ptrFailedTransfer; - if (fRc) - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrSecondAddedEvent, "second service transfer added", - ClipboardTransferState_Added, - ClipboardTransferDirection_ToGuest, - ClipboardSource_Host, 77, ptrFailedTransfer)); - ComPtr ptrFailedProgress; - if (ptrFailedTransfer.isNotNull()) - { - hrc = ptrFailedTransfer->COMGETTER(Progress)(ptrFailedProgress.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IClipboardTransfer::COMGETTER(Progress failed transfer) failed, hrc=%Rhrc\n", hrc)); - } - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 77 /* aTransferId */, - 2 /* aGeneration */, ClipboardSource_Host, - SHCLTRANSFERSTATUS_ERROR, VERR_ACCESS_DENIED); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetTransferStatus(ERROR) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrFailedEvent; - VBoxEventType_T enmFailedEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "service transfer failed", ptrFailedEvent, &enmFailedEventType); - RTTESTI_CHECK(fRc); - ComPtr ptrFailedEventTransfer; - if (fRc) - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrFailedEvent, "service transfer failed", - ClipboardTransferState_Failed, - ClipboardTransferDirection_ToGuest, - ClipboardSource_Host, 77, ptrFailedEventTransfer, - ClipboardError_AccessDenied)); - if (ptrFailedProgress.isNotNull()) - { - BOOL fCompleted = FALSE; - hrc = ptrFailedProgress->COMGETTER(Completed)(&fCompleted); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IProgress::COMGETTER(Completed after failure) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(fCompleted); - LONG hrcResult = S_OK; - hrc = ptrFailedProgress->COMGETTER(ResultCode)(&hrcResult); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IProgress::COMGETTER(ResultCode after failure) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(hrcResult == (LONG)VBOX_E_SHCL_ACCESS_DENIED); - ComPtr ptrProgressErrorInfo; - hrc = ptrFailedProgress->COMGETTER(ErrorInfo)(ptrProgressErrorInfo.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IProgress::COMGETTER(ErrorInfo after failure) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrProgressErrorInfo.isNull()); - if (ptrProgressErrorInfo.isNotNull()) - { - LONG hrcErrorInfo = S_OK; - hrc = ptrProgressErrorInfo->COMGETTER(ResultCode)(&hrcErrorInfo); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IVirtualBoxErrorInfo::COMGETTER(ResultCode) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(hrcErrorInfo == (LONG)VBOX_E_SHCL_ACCESS_DENIED); - LONG vrcErrorInfo = VINF_SUCCESS; - hrc = ptrProgressErrorInfo->COMGETTER(ResultDetail)(&vrcErrorInfo); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("IVirtualBoxErrorInfo::COMGETTER(ResultDetail) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(vrcErrorInfo == VERR_ACCESS_DENIED); - } - } - - /* A replacement service client owns a new, independently numbered generation sequence. */ - hrc = ptrInternalClipboardControl->SetTransferStatus(10 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Host, - SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("SetTransferStatus(new-session INITIALIZED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrNewSessionAddedEvent; - VBoxEventType_T enmNewSessionAddedEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "new-session service transfer added", ptrNewSessionAddedEvent, - &enmNewSessionAddedEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - ComPtr ptrNewSessionTransfer; - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrNewSessionAddedEvent, - "new-session service transfer added", - ClipboardTransferState_Added, - ClipboardTransferDirection_ToGuest, - ClipboardSource_Host, 77, ptrNewSessionTransfer)); - } - - hrc = ptrInternalClipboardControl->SetTransferStatus(10 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Host, - SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("SetTransferStatus(new-session COMPLETED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrNewSessionCompletedEvent; - VBoxEventType_T enmNewSessionCompletedEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "new-session service transfer completed", - ptrNewSessionCompletedEvent, &enmNewSessionCompletedEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - ComPtr ptrNewSessionCompletedTransfer; - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrNewSessionCompletedEvent, - "new-session service transfer completed", - ClipboardTransferState_Completed, - ClipboardTransferDirection_ToGuest, - ClipboardSource_Host, 77, - ptrNewSessionCompletedTransfer)); - } - hrc = ptrInternalClipboardControl->SetTransferStatus(10 /* aServiceSessionId */, 77 /* aTransferId */, - 1 /* stale generation */, ClipboardSource_Host, - SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus(new-session stale STARTED) returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "new-session stale transfer generation")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, - 1 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("SetTransferStatus(unknown COMPLETED) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "unknown terminal service transfer")); - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, - 1 /* stale generation */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus(STARTED after unknown terminal) returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "status after unknown terminal generation")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, - 2 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("SetTransferStatus(pre-reset INITIALIZED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrPreResetEvent; - VBoxEventType_T enmPreResetEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "pre-reset service transfer added", ptrPreResetEvent, - &enmPreResetEventType); - RTTESTI_CHECK(fRc); - ComPtr ptrPreResetTransfer; - if (fRc) - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrPreResetEvent, "pre-reset service transfer added", - ClipboardTransferState_Added, - ClipboardTransferDirection_ToHost, - ClipboardSource_Guest, 78, ptrPreResetTransfer)); - - hrc = ptrTransfers->Reset(); - RTTESTI_CHECK_MSG(hrc == VBOX_E_OBJECT_IN_USE, - ("IClipboardTransferManager::Reset with an active service transfer returned hrc=%Rhrc\n", - hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "rejected service transfer reset")); - aStatusTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, - ComSafeArrayAsOutParam(aStatusTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers after rejected reset failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aStatusTransfers.size() == 1); - - if (ptrPreResetTransfer.isNotNull()) - { - hrc = ptrTransfers->Remove(ptrPreResetTransfer); - RTTESTI_CHECK_MSG(hrc == VBOX_E_OBJECT_IN_USE, - ("IClipboardTransferManager::Remove(active service transfer) returned hrc=%Rhrc\n", hrc)); - } - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "rejected service transfer remove")); - aStatusTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, - ComSafeArrayAsOutParam(aStatusTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers after rejected remove failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aStatusTransfers.size() == 1); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, - 2 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_CANCELED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus accepted CANCELED with a successful result, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "status/result-inconsistent cancellation")); - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, - 2 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("SetTransferStatus(pre-reset CANCELED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrResetEvent; - VBoxEventType_T enmResetEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "service transfer canceled", ptrResetEvent, &enmResetEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - ComPtr ptrResetTransfer; - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrResetEvent, "service transfer canceled", - ClipboardTransferState_Canceled, - ClipboardTransferDirection_ToHost, - ClipboardSource_Guest, 78, ptrResetTransfer)); - } - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, - 2 /* reset generation */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, - ("SetTransferStatus(STARTED after terminal status) returned hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "status after terminal service transfer")); - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, - 3 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_INITIALIZED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("SetTransferStatus(next-generation INITIALIZED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrPostResetEvent; - VBoxEventType_T enmPostResetEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "next-generation service transfer added", ptrPostResetEvent, - &enmPostResetEventType); - RTTESTI_CHECK(fRc); - ComPtr ptrPostResetTransfer; - if (fRc) - RTTESTI_CHECK(tstClipboardCheckTransferEvent(ptrPostResetEvent, "next-generation service transfer added", - ClipboardTransferState_Added, - ClipboardTransferDirection_ToHost, - ClipboardSource_Guest, 78, ptrPostResetTransfer)); - - if (ptrPostResetTransfer.isNotNull()) - { - hrc = ptrTransfers->Cancel(ptrPostResetTransfer); - RTTESTI_CHECK_MSG(FAILED(hrc), - ("IClipboardTransferManager::Cancel(synthetic service transfer) unexpectedly succeeded\n")); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSource, ptrTransferListener, 100 /* cMsTimeout */, - "failed service transfer cancel")); - aStatusTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, - ComSafeArrayAsOutParam(aStatusTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("GetTransfers after service cancel failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aStatusTransfers.size() == 1); - } - - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, - 3 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_STARTED, VINF_SUCCESS); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("SetTransferStatus(STARTED after failed service cancel) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrPostResetStartedEvent; - VBoxEventType_T enmPostResetStartedEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "service transfer started after failed cancel", - ptrPostResetStartedEvent, &enmPostResetStartedEventType); - RTTESTI_CHECK(fRc); - hrc = ptrInternalClipboardControl->SetTransferStatus(9 /* aServiceSessionId */, 78 /* aTransferId */, - 3 /* aGeneration */, ClipboardSource_Guest, - SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("SetTransferStatus(next-generation CANCELED) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrPostResetCanceledEvent; - VBoxEventType_T enmPostResetCanceledEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrEventSource, ptrTransferListener, s_aTransferEventTypes, - RT_ELEMENTS(s_aTransferEventTypes), 1000 /* cMsTimeout */, - "next-generation service transfer canceled", - ptrPostResetCanceledEvent, &enmPostResetCanceledEventType); - RTTESTI_CHECK(fRc); - } - - hrc = ptrEventSource->UnregisterListener(ptrTransferListener); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("UnregisterListener(service transfer) failed, hrc=%Rhrc\n", hrc)); - } -#endif - - tstClipboardPublicSessionApi(hTest, ptrClipboard, ptrClipboardSettings, ptrEventSource); - RTTestSub(hTest, "Clipboard public API operations"); - - /* Register a broad passive listener for mode changes and explicit host-origin writes. */ - hrc = ptrEventSource->CreateListener(ptrListener.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("CreateListener failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrListener.isNull()); - SafeArray aEventTypes; - aEventTypes.push_back(VBoxEventType_OnClipboardModeChanged); - aEventTypes.push_back(VBoxEventType_OnClipboardSourceChanged); - aEventTypes.push_back(VBoxEventType_OnClipboardFormatChanged); - aEventTypes.push_back(VBoxEventType_OnClipboardDataChanged); - aEventTypes.push_back(VBoxEventType_OnClipboardDataRequested); - hrc = ptrEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("RegisterListener failed, hrc=%Rhrc\n", hrc)); - fListenerRegistered = SUCCEEDED(hrc); - - /* Check mode-change events before any data writes alter clipboard ownership. */ - hrc = ptrClipboardSettings->COMSETTER(Mode)(ClipboardMode_HostToGuest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMSETTER(Mode) live change failed, hrc=%Rhrc\n", hrc)); - if (SUCCEEDED(hrc)) - { - ComPtr ptrEvent; - hrc = ptrEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrEvent.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetEvent(mode) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(!ptrEvent.isNull(), ("GetEvent(mode) returned no event\n")); - if (ptrEvent.isNotNull()) - { - VBoxEventType_T enmType = VBoxEventType_Invalid; - hrc = ptrEvent->COMGETTER(Type)(&enmType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)(mode) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmType == VBoxEventType_OnClipboardModeChanged, - ("GetEvent(mode) returned type %d, expected %d\n", - enmType, VBoxEventType_OnClipboardModeChanged)); - - ComPtr ptrModeEvent = ptrEvent; - RTTESTI_CHECK(!ptrModeEvent.isNull()); - if (ptrModeEvent.isNotNull()) - { - ClipboardMode_T enmMode = ClipboardMode_Disabled; - hrc = ptrModeEvent->COMGETTER(ClipboardMode)(&enmMode); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardMode) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmMode == ClipboardMode_HostToGuest, - ("GetEvent(mode) returned mode %d, expected %d\n", - enmMode, ClipboardMode_HostToGuest)); - } - } - } - - /* Verify the initial live clipboard has no formats, data, or queued data events. */ - SafeIfaceArray aReadFormats; - hrc = ptrClipboard->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadFormats failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadFormats.size() == 0); - - ComPtr ptrTextFormat; - hrc = tstCreateFormat(ptrClipboard, "text/plain;charset=utf-8", ptrTextFormat); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateFormat(text) failed, hrc=%Rhrc\n", hrc)); - - BOOL fAvailable = TRUE; - hrc = ptrClipboard->IsFormatAvailable(ClipboardSource_Host, ptrTextFormat, &fAvailable); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IsFormatAvailable failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!fAvailable); - - SafeIfaceArray aSupportedFormats; - hrc = ptrClipboard->GetSupportedFormats(ClipboardSource_Host, ComSafeArrayAsOutParam(aSupportedFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSupportedFormats failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSupportedFormats.size() == 0); - - ComPtr ptrReadItem; - hrc = ptrClipboard->ReadData(ClipboardAction_Copy, ptrReadItem.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("ReadData without available formats unexpectedly succeeded\n")); - - ComPtr ptrUnexpectedEvent; - hrc = ptrEventSource->GetEvent(ptrListener, 0 /* aTimeout */, ptrUnexpectedEvent.asOutParam()); - RTTESTI_CHECK_MSG( hrc == VBOX_E_OBJECT_NOT_FOUND - || ptrUnexpectedEvent.isNull(), - ("Unexpected clipboard event before explicit API write, hrc=%Rhrc\n", hrc)); - - /* Exercise validation failures for empty payloads and unsupported MIME types. */ - std::vector abBuffer; - ComPtr ptrEmptyItem; - hrc = tstCreateItem(ptrClipboard, ClipboardSource_Host, ptrTextFormat, abBuffer, ptrEmptyItem); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateItem(empty) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrWrittenItem; - hrc = ptrClipboard->WriteData(ClipboardAction_Copy, ptrEmptyItem, ptrWrittenItem.asOutParam()); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_NO_DATA, ("WriteData with empty buffer returned hrc=%Rhrc\n", hrc)); - - ComPtr ptrUnsupportedFormat; - hrc = tstCreateFormat(ptrClipboard, "application/x-unsupported", ptrUnsupportedFormat); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateFormat(unsupported) failed, hrc=%Rhrc\n", hrc)); - abBuffer.push_back('x'); - ComPtr ptrUnsupportedItem; - hrc = tstCreateItem(ptrClipboard, ClipboardSource_Host, ptrUnsupportedFormat, abBuffer, ptrUnsupportedItem); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateItem(unsupported) failed, hrc=%Rhrc\n", hrc)); - ptrWrittenItem.setNull(); - hrc = ptrClipboard->WriteData(ClipboardAction_Copy, ptrUnsupportedItem, ptrWrittenItem.asOutParam()); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("WriteData with unsupported MIME type returned hrc=%Rhrc\n", hrc)); - - /* Write host-origin data and verify the expected source/format/data events. */ - static const char s_szRoundTripText[] = "tstClipboard host to guest round-trip data"; - std::vector abRoundTripBuffer(sizeof(s_szRoundTripText)); - memcpy(&abRoundTripBuffer[0], s_szRoundTripText, sizeof(s_szRoundTripText)); - - ComPtr ptrTextItem; - hrc = tstCreateItem(ptrClipboard, ClipboardSource_Host, ptrTextFormat, abRoundTripBuffer, ptrTextItem); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateItem(text) failed, hrc=%Rhrc\n", hrc)); - ptrWrittenItem.setNull(); - hrc = ptrClipboard->WriteData(ClipboardAction_Copy, ptrTextItem, ptrWrittenItem.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrWrittenItem.isNull()); - - static VBoxEventType_T const s_aExpectedEvents[] = - { - VBoxEventType_OnClipboardSourceChanged, - VBoxEventType_OnClipboardFormatChanged, - VBoxEventType_OnClipboardDataChanged - }; - for (size_t i = 0; i < RT_ELEMENTS(s_aExpectedEvents); i++) - { - ComPtr ptrEvent; - hrc = ptrEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent[%zu] failed, hrc=%Rhrc\n", i, hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrEvent.isNull(), ("GetEvent[%zu] returned no event\n", i)); - - VBoxEventType_T enmType = VBoxEventType_Invalid; - hrc = ptrEvent->COMGETTER(Type)(&enmType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)[%zu] failed, hrc=%Rhrc\n", i, hrc)); - RTTESTI_CHECK_MSG(enmType == s_aExpectedEvents[i], - ("GetEvent[%zu] returned type %d, expected %d\n", i, enmType, s_aExpectedEvents[i])); - RTTESTI_CHECK_MSG(enmType != VBoxEventType_OnClipboardDataRequested, - ("Explicit API write unexpectedly emitted OnClipboardDataRequested\n")); - if (enmType == VBoxEventType_OnClipboardFormatChanged) - { - ComPtr ptrFormatEvent = ptrEvent; - RTTESTI_CHECK(!ptrFormatEvent.isNull()); - if (ptrFormatEvent.isNotNull()) - { - RTTESTI_CHECK(tstClipboardCheckEventMetadata(ptrFormatEvent, "public API format event", - VBOX_SHCL_MAIN_CLIENT_NONE)); - - ClipboardSource_T enmSource = ClipboardSource_Custom; - hrc = ptrFormatEvent->COMGETTER(ClipboardSource)(&enmSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardSource)(format event) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmSource == ClipboardSource_Host, - ("Format event source %d, expected %d\n", enmSource, ClipboardSource_Host)); - - SafeIfaceArray aEventFormats; - hrc = ptrFormatEvent->COMGETTER(Formats)(ComSafeArrayAsOutParam(aEventFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Formats)(format event) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(aEventFormats.size() == 1, - ("Format event returned %zu formats, expected 1\n", aEventFormats.size())); - if (aEventFormats.size() == 1) - { - Bstr bstrEventMimeType; - hrc = aEventFormats[0]->COMGETTER(MimeType)(bstrEventMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(MimeType)(format event) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrEventMimeType).c_str(), "text/plain;charset=utf-8")); - } - } - } - else if (enmType == VBoxEventType_OnClipboardDataChanged) - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrEvent, "public API data event", - VBOX_SHCL_MAIN_CLIENT_NONE, ClipboardAction_Copy, - NULL /* pptrItem */)); - } - ComPtr ptrUnexpectedAfterWrite; - hrc = ptrEventSource->GetEvent(ptrListener, 0 /* aTimeout */, ptrUnexpectedAfterWrite.asOutParam()); - RTTESTI_CHECK_MSG( hrc == VBOX_E_OBJECT_NOT_FOUND - || ptrUnexpectedAfterWrite.isNull(), - ("Unexpected clipboard event after explicit API write, hrc=%Rhrc\n", hrc)); - if (ptrUnexpectedAfterWrite.isNotNull()) - { - BOOL fWaitable = FALSE; - hrc = ptrUnexpectedAfterWrite->COMGETTER(Waitable)(&fWaitable); - if (SUCCEEDED(hrc) && fWaitable) - ptrEventSource->EventProcessed(ptrListener, ptrUnexpectedAfterWrite); - } - - if (ptrListener.isNotNull()) - ptrEventSource->UnregisterListener(ptrListener); - fListenerRegistered = false; - ptrListener.setNull(); - - /* Confirm the host-origin write is visible through query and raw read APIs. */ - aReadFormats.setNull(); - hrc = ptrClipboard->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadFormats after WriteData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadFormats.size() == 1); - - fAvailable = FALSE; - hrc = ptrClipboard->IsFormatAvailable(ClipboardSource_Host, ptrTextFormat, &fAvailable); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IsFormatAvailable after WriteData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(fAvailable); - - aSupportedFormats.setNull(); - hrc = ptrClipboard->GetSupportedFormats(ClipboardSource_Host, ComSafeArrayAsOutParam(aSupportedFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSupportedFormats after WriteData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSupportedFormats.size() == 1); - - ClipboardSource_T enmGuestReadSource = ClipboardSource_Custom; - Bstr bstrGuestRequestedMimeType(""); - Bstr bstrGuestReadMimeType; - SafeArray aGuestReadBuffer; - hrc = ptrClipboard->ReadDataRaw(ClipboardAction_Copy, bstrGuestRequestedMimeType.raw(), &enmGuestReadSource, - bstrGuestReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("ReadDataRaw(host -> guest) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmGuestReadSource == ClipboardSource_Host); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrGuestReadMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aGuestReadBuffer, abRoundTripBuffer), - ("ReadDataRaw(host -> guest) returned %zu bytes, expected %zu\n", - aGuestReadBuffer.size(), abRoundTripBuffer.size())); - - ClipboardSource_T enmTextPlainReadSource = ClipboardSource_Custom; - Bstr bstrTextPlainRequestedMimeType("text/plain"); - Bstr bstrTextPlainReadMimeType; - SafeArray aTextPlainReadBuffer; - hrc = ptrClipboard->ReadDataRaw(ClipboardAction_Copy, bstrTextPlainRequestedMimeType.raw(), &enmTextPlainReadSource, - bstrTextPlainReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aTextPlainReadBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("ReadDataRaw(text/plain, host -> guest) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmTextPlainReadSource == ClipboardSource_Host); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrTextPlainReadMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aTextPlainReadBuffer, abRoundTripBuffer), - ("ReadDataRaw(text/plain, host -> guest) returned %zu bytes, expected %zu\n", - aTextPlainReadBuffer.size(), abRoundTripBuffer.size())); - - /* Switch to bidirectional mode and verify raw guest-origin writes. */ - hrc = ptrClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), - ("COMSETTER(Mode) bidirectional before guest write failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmWrittenSource = ClipboardSource_Custom; - Bstr bstrWrittenMimeType; - SafeArray aWrittenBuffer; - hrc = ptrClipboard->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Guest, bstrGuestReadMimeType.raw(), - ComSafeArrayAsInParam(aGuestReadBuffer), &enmWrittenSource, - bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteDataRaw(guest -> host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmWrittenSource == ClipboardSource_Guest); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrWrittenMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aWrittenBuffer, abRoundTripBuffer), - ("WriteDataRaw(guest -> host) returned %zu bytes, expected %zu\n", - aWrittenBuffer.size(), abRoundTripBuffer.size())); - - enmTextPlainReadSource = ClipboardSource_Custom; - bstrTextPlainReadMimeType.setNull(); - aTextPlainReadBuffer.setNull(); - hrc = ptrClipboard->ReadDataRaw(ClipboardAction_Copy, bstrTextPlainRequestedMimeType.raw(), &enmTextPlainReadSource, - bstrTextPlainReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aTextPlainReadBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("ReadDataRaw(text/plain, guest -> host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmTextPlainReadSource == ClipboardSource_Guest); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrTextPlainReadMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aTextPlainReadBuffer, abRoundTripBuffer), - ("ReadDataRaw(text/plain, guest -> host) returned %zu bytes, expected %zu\n", - aTextPlainReadBuffer.size(), abRoundTripBuffer.size())); - - /* Verify the object read API sees the same guest-origin payload. */ - ptrReadItem.setNull(); - hrc = ptrClipboard->ReadData(ClipboardAction_Copy, ptrReadItem.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("ReadData after guest write failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrReadItem.isNull()); - if (ptrReadItem.isNotNull()) - { - ClipboardSource_T enmRoundTripSource = ClipboardSource_Custom; - hrc = ptrReadItem->COMGETTER(Source)(&enmRoundTripSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Source)(round-trip) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmRoundTripSource == ClipboardSource_Guest); - - ComPtr ptrRoundTripFormat; - hrc = ptrReadItem->COMGETTER(Format)(ptrRoundTripFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Format)(round-trip) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrRoundTripFormat.isNull()); - if (ptrRoundTripFormat.isNotNull()) - { - Bstr bstrRoundTripMimeType; - hrc = ptrRoundTripFormat->COMGETTER(MimeType)(bstrRoundTripMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(MimeType)(round-trip) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrRoundTripMimeType).c_str(), "text/plain;charset=utf-8")); - } - - SafeArray aRoundTripBuffer; - hrc = ptrReadItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aRoundTripBuffer)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Buffer)(round-trip) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aRoundTripBuffer, abRoundTripBuffer), - ("ReadData(round-trip) returned %zu bytes, expected %zu\n", - aRoundTripBuffer.size(), abRoundTripBuffer.size())); - } - - /* Exercise the explicit IHostClipboard endpoint after normal IClipboard paths are known-good. */ - tstHostClipboard(hTest, ptrClipboard, ptrClipboardSettings, ptrEventSource, ptrTextFormat, - bstrGuestReadMimeType, aGuestReadBuffer, abRoundTripBuffer); - - /* Verify unsupported format offers fail before checking repeat format notifications. */ - std::vector > vecFormats; - vecFormats.push_back(ptrTextFormat); - vecFormats.push_back(ptrUnsupportedFormat); - SafeIfaceArray aWriteFormats(vecFormats); - hrc = ptrClipboard->WriteFormats(ComSafeArrayAsInParam(aWriteFormats)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("WriteFormats with unsupported MIME type returned hrc=%Rhrc\n", hrc)); - - /* Listen only for format changes to prove repeated offers still notify observers. */ - hrc = ptrEventSource->CreateListener(ptrListener.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("CreateListener(format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrListener.isNull()); - SafeArray aFormatEventTypes; - aFormatEventTypes.push_back(VBoxEventType_OnClipboardFormatChanged); - hrc = ptrEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aFormatEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("RegisterListener(format) failed, hrc=%Rhrc\n", hrc)); - fListenerRegistered = SUCCEEDED(hrc); - - vecFormats.clear(); - vecFormats.push_back(ptrTextFormat); - SafeIfaceArray aWriteTextFormats(vecFormats); - hrc = ptrClipboard->WriteFormats(ComSafeArrayAsInParam(aWriteTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("WriteFormats failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrClipboard->WriteFormats(ComSafeArrayAsInParam(aWriteTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Repeated WriteFormats failed, hrc=%Rhrc\n", hrc)); - - /* Repeated explicit host offers must remain visible to observers such as VBoxManage clipboard listen. */ - for (unsigned i = 0; i < 2; i++) - { - ComPtr ptrEvent; - hrc = ptrEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent(format write %u) failed, hrc=%Rhrc\n", i, hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrEvent.isNull(), ("GetEvent(format write %u) returned no event\n", i)); - VBoxEventType_T enmType = VBoxEventType_Invalid; - hrc = ptrEvent->COMGETTER(Type)(&enmType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)(format write %u) failed, hrc=%Rhrc\n", i, hrc)); - RTTESTI_CHECK_MSG(enmType == VBoxEventType_OnClipboardFormatChanged, - ("GetEvent(format write %u) returned type %d, expected %d\n", - i, enmType, VBoxEventType_OnClipboardFormatChanged)); - } - - if (ptrListener.isNotNull()) - ptrEventSource->UnregisterListener(ptrListener); - fListenerRegistered = false; - ptrListener.setNull(); - - /* Reset clears transient clipboard transfer and format state. */ - hrc = ptrClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset failed, hrc=%Rhrc\n", hrc)); - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - SafeIfaceArray aResetTransfers; - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aResetTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after Reset) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aResetTransfers.size() == 0); -#endif - - aReadFormats.setNull(); - hrc = ptrClipboard->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadFormats after Reset failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadFormats.size() == 0); - - RTTestSub(hTest, "Clipboard session survives console teardown setup"); - hrc = tstCreateSession(ptrClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrSurvivingSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(surviving) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrSurvivingSession.isNull(), ("CreateSession(surviving) returned NULL\n")); - hrc = ptrSurvivingSession->COMGETTER(Id)(&idSurvivingSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(surviving) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSurvivingSession != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Surviving session returned anonymous/zero client ID before teardown\n")); - } while (0); - - /* Clean up listeners and VM state regardless of which subtest exited early. */ -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - if (fDirFile1Created) - RTFileDelete(szDirFile1); - if (fDir1Created) - RTDirRemove(szDir1); - if (fFile1Created) - RTFileDelete(szFile1); -#endif - if (fListenerRegistered && ptrEventSource.isNotNull() && ptrListener.isNotNull()) - ptrEventSource->UnregisterListener(ptrListener); - ptrListener.setNull(); - ptrEventSource.setNull(); - ptrClipboard.setNull(); - - if (fMachinePoweredOn && !ptrConsole.isNull()) - { - ComPtr ptrPowerDownProgress; - HRESULT hrcPowerDown = ptrConsole->PowerDown(ptrPowerDownProgress.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcPowerDown), ("PowerDown failed, hrc=%Rhrc\n", hrcPowerDown)); - if (SUCCEEDED(hrcPowerDown) && !ptrPowerDownProgress.isNull()) - { - hrcPowerDown = ptrPowerDownProgress->WaitForCompletion(-1); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcPowerDown), ("WaitForCompletion(PowerDown) failed, hrc=%Rhrc\n", hrcPowerDown)); - } - } - - ptrConsole.setNull(); - - if (fMachineLocked) - { - HRESULT hrcUnlock = ptrSession->UnlockMachine(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcUnlock), ("UnlockMachine failed, hrc=%Rhrc\n", hrcUnlock)); - } - ptrSession.setNull(); - - if (fMachineRegistered) - { - HRESULT hrcWait = tstWaitMachineUnlocked(hTest, ptrMachine); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcWait), ("Waiting for machine unlock failed, hrc=%Rhrc\n", hrcWait)); - - SafeIfaceArray aMedia; - HRESULT hrcCleanup = ptrMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcCleanup), ("Unregister failed, hrc=%Rhrc\n", hrcCleanup)); - if (SUCCEEDED(hrcCleanup)) - { - ComPtr ptrProgress; - hrcCleanup = ptrMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), ptrProgress.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcCleanup), ("DeleteConfig failed, hrc=%Rhrc\n", hrcCleanup)); - if (SUCCEEDED(hrcCleanup) && !ptrProgress.isNull()) - { - hrcCleanup = ptrProgress->WaitForCompletion(-1); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcCleanup), ("WaitForCompletion(DeleteConfig) failed, hrc=%Rhrc\n", hrcCleanup)); - } - } - } - - if (ptrSurvivingSession.isNotNull()) - { - RTTestSub(hTest, "Clipboard session after console teardown"); - - ULONG idAfterTeardown = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSurvivingSession->COMGETTER(Id)(&idAfterTeardown); - RTTESTI_CHECK_MSG( FAILED(hrc) - || idAfterTeardown == idSurvivingSession, - ("Surviving session ID changed from %RU32 to %RU32, hrc=%Rhrc\n", - (uint32_t)idSurvivingSession, (uint32_t)idAfterTeardown, hrc)); - RTTESTI_CHECK_MSG( FAILED(hrc) - || idAfterTeardown != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Surviving session returned anonymous/zero client ID after teardown\n")); - - SafeIfaceArray aFormats; - hrc = ptrSurvivingSession->ReadFormats(ComSafeArrayAsOutParam(aFormats)); - RTTESTI_CHECK_MSG(FAILED(hrc), - ("ReadFormats on surviving session after console teardown unexpectedly succeeded\n")); - - ptrSurvivingSession.setNull(); - } -} - - -int main() -{ - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboard", &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - - tstInitLogging(); - - RTTestBanner(hTest); - -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RTTestSkipped(hTest, "Shared Clipboard transfers are not available on this platform"); - return RTTestSummaryAndDestroy(hTest); -#endif - - HRESULT hrc = Initialize(); - if (FAILED(hrc)) - { - RTTestFailed(hTest, "Failed to initialize COM, hrc=%Rhrc", hrc); - return RTTestSummaryAndDestroy(hTest); - } - - tstClipboardPublicApi(hTest); - - Shutdown(); - return RTTestSummaryAndDestroy(hTest); -} diff --git a/src/VBox/Main/testcase/tstClipboardAPI.cpp b/src/VBox/Main/testcase/tstClipboardAPI.cpp new file mode 100644 index 000000000000..a8e4d89e21b8 --- /dev/null +++ b/src/VBox/Main/testcase/tstClipboardAPI.cpp @@ -0,0 +1,441 @@ +/* $Id: tstClipboardAPI.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Public API object testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include "ClipboardFormatImpl.h" +#include "ClipboardItemImpl.h" +#include "ClipboardImpl.h" +#include "ClipboardSessionImpl.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include "ClipboardTransferImpl.h" +# include "ClipboardTransferManagerImpl.h" +#endif + +#include +#include + + +/** @name Parent Clipboard stubs for unexercised session delegation paths. + * @{ */ +/** Test stub for Clipboard::i_registerSession(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_registerSession(VBOXSHCLMAINCLIENTID, ClipboardSession *, uint32_t, const ComPtr &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_unregisterSession(). */ +void Clipboard::i_unregisterSession(VBOXSHCLMAINCLIENTID) +{ +} + +/** Test stub for Clipboard::i_fireSessionInitialState(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_fireSessionInitialState(VBOXSHCLMAINCLIENTID) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_readFormatObjects(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_readFormatObjects(std::vector > &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_readDataRaw(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_readDataRaw(ClipboardAction_T, const com::Utf8Str &, ClipboardSource_T *, com::Utf8Str &, + std::vector &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_writeDataRaw(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_writeDataRaw(VBOXSHCLMAINCLIENTID, ClipboardAction_T, ClipboardSource_T, const com::Utf8Str &, + const std::vector &, ClipboardSource_T *, com::Utf8Str &, std::vector &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_writeFormatObjects(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_writeFormatObjects(VBOXSHCLMAINCLIENTID, const std::vector > &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_hostClipboardReportFormats(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_hostClipboardReportFormats(VBOXSHCLMAINCLIENTID, ClipboardAction_T, ClipboardSource_T, + const std::vector > &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_hostClipboardProvideData(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_hostClipboardProvideData(VBOXSHCLMAINCLIENTID, ULONG, ClipboardAction_T, ClipboardSource_T, + const com::Utf8Str &, const std::vector &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_hostClipboardSetData(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_hostClipboardSetData(VBOXSHCLMAINCLIENTID, ClipboardAction_T, ClipboardSource_T, + const com::Utf8Str &, const std::vector &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_hostClipboardClear(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_hostClipboardClear(VBOXSHCLMAINCLIENTID) +{ + return E_NOTIMPL; +} +/** @} */ + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Validates an action for the test-local transfer objects. + * + * @returns true if @a enmAction is a public ClipboardAction_T value. + * @param enmAction Action to validate. + */ +bool ShClMainIsValidAction(ClipboardAction_T enmAction) +{ + return enmAction == ClipboardAction_Copy + || enmAction == ClipboardAction_Cut + || enmAction == ClipboardAction_Paste + || enmAction == ClipboardAction_Custom; +} + +/** + * Validates a source for the test-local transfer objects. + * + * @returns true if @a enmSource is a public ClipboardSource_T value. + * @param enmSource Source to validate. + */ +bool ShClMainIsValidSource(ClipboardSource_T enmSource) +{ + return enmSource == ClipboardSource_Host + || enmSource == ClipboardSource_Guest + || enmSource == ClipboardSource_Remote + || enmSource == ClipboardSource_Custom; +} + +/** Test stub for the unexercised parent transfer-cancel callback. */ +HRESULT Clipboard::i_transferCancel(SHCLSESSIONID, SHCLTRANSFERID, SHCLTRANSFERGEN) +{ + return E_NOTIMPL; +} + +/** Test stub for the unexercised parent transfer-event callback. */ +void Clipboard::i_fireClipboardTransferEvent(VBOXSHCLMAINCLIENTID, IClipboardTransfer *, ClipboardTransferState_T, + ClipboardTransferInteraction_T, const com::Utf8Str &, + const com::Utf8Str &, ClipboardError_T) +{ +} +#endif + + +/** + * @page pg_tstClipboardAPI Main Shared Clipboard public API testcase + * + * Tests the state and ownership semantics of the small public Main clipboard + * objects without constructing a VM, HGCM service or native clipboard backend. + */ + + +/** Tests IClipboardFormat and IClipboardItem value-object semantics. */ +static void tstClipboardValues(void) +{ + RTTestISub("Formats and items"); + + ComObjPtr ptrFormatObj; + HRESULT hrc = ptrFormatObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + hrc = ptrFormatObj->init(com::Utf8Str("text/plain;charset=utf-8")); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrFormat; + hrc = ptrFormatObj.queryInterfaceTo(ptrFormat.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + com::Bstr bstrMimeType; + hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(!RTStrCmp(com::Utf8Str(bstrMimeType).c_str(), "text/plain;charset=utf-8")); + + hrc = ptrFormat->COMSETTER(MimeType)(com::Bstr("text/html").raw()); + RTTESTI_CHECK_RC(hrc, S_OK); + bstrMimeType.setNull(); + hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(!RTStrCmp(com::Utf8Str(bstrMimeType).c_str(), "text/html")); + + static uint8_t const s_abPayload[] = { 0, 1, 2, 0xff }; + std::vector abPayload(s_abPayload, s_abPayload + RT_ELEMENTS(s_abPayload)); + ComObjPtr ptrItemObj; + hrc = ptrItemObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + hrc = ptrItemObj->init(7, ClipboardSource_Host, ptrFormat, abPayload); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrItem; + hrc = ptrItemObj.queryInterfaceTo(ptrItem.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ULONG idItem = 0; + ClipboardSource_T enmSource = ClipboardSource_Custom; + ULONG cbItem = 0; + hrc = ptrItem->COMGETTER(Id)(&idItem); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrItem->COMGETTER(Source)(&enmSource); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrItem->COMGETTER(Size)(&cbItem); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(idItem == 7); + RTTESTI_CHECK(enmSource == ClipboardSource_Host); + RTTESTI_CHECK(cbItem == sizeof(s_abPayload)); + + com::SafeArray aRead; + hrc = ptrItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aRead)); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(aRead.size() == sizeof(s_abPayload)); + if (aRead.size() == sizeof(s_abPayload)) + RTTESTI_CHECK(!memcmp(aRead.raw(), s_abPayload, sizeof(s_abPayload))); + + static uint8_t const s_abReplacement[] = { 9, 8, 7 }; + com::SafeArray aReplacement; + hrc = aReplacement.initFrom(s_abReplacement, RT_ELEMENTS(s_abReplacement)); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrItem->COMSETTER(Buffer)(ComSafeArrayAsInParam(aReplacement)); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrItem->COMGETTER(Size)(&cbItem); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(cbItem == sizeof(s_abReplacement)); +} + + +/** Tests IClipboardSession identity, endpoint state and idempotent close. */ +static void tstClipboardSession(void) +{ + RTTestISub("Session state"); + + ComObjPtr ptrSessionObj; + HRESULT hrc = ptrSessionObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + hrc = ptrSessionObj->initForTesting(17, 0); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrSession; + hrc = ptrSessionObj.queryInterfaceTo(ptrSession.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ULONG idSession = 0; + hrc = ptrSession->COMGETTER(Id)(&idSession); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(idSession == 17); + + ComPtr ptrEventSource; + hrc = ptrSession->COMGETTER(EventSource)(ptrEventSource.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(ptrEventSource.isNotNull()); + + hrc = ptrSession->Close(); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrSession->Close(); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrSession->COMGETTER(Id)(&idSession); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(idSession == 17); + + ptrEventSource.setNull(); + RTTESTI_CHECK_RC(RTTestIDisableAssertions(), VINF_SUCCESS); + hrc = ptrSession->COMGETTER(EventSource)(ptrEventSource.asOutParam()); + RTTESTI_CHECK_RC(RTTestIRestoreAssertions(), VINF_SUCCESS); + RTTESTI_CHECK(FAILED(hrc)); + RTTESTI_CHECK(ptrEventSource.isNull()); +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Tests IClipboardTransfer metadata without attaching a data-plane backend. */ +static void tstClipboardTransfer(void) +{ + RTTestISub("Transfer metadata"); + + ComObjPtr ptrTransferObj; + HRESULT hrc = ptrTransferObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + ComPtr ptrItem; + ComPtr ptrProgress; + hrc = ptrTransferObj->init(23, ClipboardTransferDirection_ToGuest, ClipboardSource_Host, + ClipboardAction_Copy, ptrItem, ptrProgress); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrTransfer; + hrc = ptrTransferObj.queryInterfaceTo(ptrTransfer.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ULONG idTransfer = 0; + ClipboardTransferDirection_T enmDirection = ClipboardTransferDirection_Any; + ClipboardSource_T enmSource = ClipboardSource_Custom; + ClipboardAction_T enmAction = ClipboardAction_Invalid; + ClipboardTransferState_T enmState = ClipboardTransferState_Removed; + hrc = ptrTransfer->COMGETTER(Id)(&idTransfer); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Direction)(&enmDirection); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Source)(&enmSource); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Action)(&enmAction); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(State)(&enmState); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(idTransfer == 23); + RTTESTI_CHECK(enmDirection == ClipboardTransferDirection_ToGuest); + RTTESTI_CHECK(enmSource == ClipboardSource_Host); + RTTESTI_CHECK(enmAction == ClipboardAction_Copy); + RTTESTI_CHECK(enmState == ClipboardTransferState_Added); + + ptrTransferObj->i_setState(ClipboardTransferState_Failed, com::Utf8Str("failed"), ClipboardError_OperationFailed); + com::Bstr bstrMessage; + ClipboardError_T enmError = ClipboardError_None; + hrc = ptrTransfer->COMGETTER(State)(&enmState); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Message)(bstrMessage.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Error)(&enmError); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(enmState == ClipboardTransferState_Failed); + RTTESTI_CHECK(!RTStrCmp(com::Utf8Str(bstrMessage).c_str(), "failed")); + RTTESTI_CHECK(enmError == ClipboardError_OperationFailed); + + ComPtr ptrData; + RTTESTI_CHECK_RC(RTTestIDisableAssertions(), VINF_SUCCESS); + hrc = ptrTransfer->COMGETTER(Data)(ptrData.asOutParam()); + RTTESTI_CHECK_RC(RTTestIRestoreAssertions(), VINF_SUCCESS); + RTTESTI_CHECK(FAILED(hrc)); + RTTESTI_CHECK(ptrData.isNull()); +} + + +/** Tests IClipboardTransferManager ownership, filtering and removal. */ +static void tstClipboardTransferManager(void) +{ + RTTestISub("Transfer manager"); + + ComObjPtr ptrManagerObj; + HRESULT hrc = ptrManagerObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + hrc = ptrManagerObj->init(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrManager; + hrc = ptrManagerObj.queryInterfaceTo(ptrManager.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + com::SafeIfaceArray aTransfers; + hrc = ptrManager->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(aTransfers.size() == 0); + + ComPtr ptrTransfer; + hrc = ptrManager->Create(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, ClipboardAction_Copy, + ptrTransfer.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(ptrTransfer.isNotNull()); + + aTransfers.setNull(); + hrc = ptrManager->GetTransfers(ClipboardTransferDirection_ToGuest, 0, ComSafeArrayAsOutParam(aTransfers)); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(aTransfers.size() == 1); + if (aTransfers.size() == 1) + RTTESTI_CHECK(aTransfers[0] == ptrTransfer); + + hrc = ptrManager->Remove(ptrTransfer); + RTTESTI_CHECK_RC(hrc, S_OK); + aTransfers.setNull(); + hrc = ptrManager->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(aTransfers.size() == 0); + + RTTESTI_CHECK_RC(RTTestIDisableAssertions(), VINF_SUCCESS); + hrc = ptrManager->Remove(ptrTransfer); + RTTESTI_CHECK_RC(RTTestIRestoreAssertions(), VINF_SUCCESS); + RTTESTI_CHECK(FAILED(hrc)); +} +#endif + + +int main(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardAPI", &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + tstClipboardValues(); + tstClipboardSession(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardTransfer(); + tstClipboardTransferManager(); +#endif + return RTTestSummaryAndDestroy(hTest); +} diff --git a/src/VBox/Main/testcase/tstClipboardMain.cpp b/src/VBox/Main/testcase/tstClipboardMain.cpp new file mode 100644 index 000000000000..1a5958988b9c --- /dev/null +++ b/src/VBox/Main/testcase/tstClipboardMain.cpp @@ -0,0 +1,913 @@ +/* $Id: tstClipboardMain.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Connection and service-extension testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include "GuestShClBackendPrivate.h" +#include "GuestShClConn.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +/** + * @page pg_tstClipboardMain Main Shared Clipboard connection testcase + * + * This is the Main-side unit test for the Shared Clipboard connection boundary. + * It compiles the production GuestShClConn and ShClBackend dispatcher against + * two small fakes: one implements the service operation table, the other + * implements the native backend operation table. + * + * The test checks backend lifetime, opaque transport identity, operation + * forwarding and connection pinning while a guest-data token is outstanding. + * With transfers enabled it also checks retained lookups and complete + * session/ID/generation keys; transfer contents are not involved. + * + * No VM, HGCM service, native clipboard, filesystem provider or HTTP transport + * is constructed here. Those belong to their respective component tests. + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Test service session ID. */ +#define TST_SHCL_SESSION_ID UINT16_C(42) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Test transfer ID. */ +# define TST_SHCL_TRANSFER_ID UINT16_C(7) +/** Test transfer generation. */ +# define TST_SHCL_TRANSFER_GEN UINT64_C(0x1122334455667788) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Fake opaque service client. */ +struct SHCLCLIENTOPAQUE +{ + /** Identity marker. */ + uint32_t uMagic; +}; + +/** Fake retained guest-data token. */ +struct SHCLGUESTDATATOKENOPAQUE +{ + /** Identity marker. */ + uint32_t uMagic; +}; + +/** Complete definition of the otherwise opaque service command context. */ +struct _SHCLCLIENTCMDCTX +{ + /** Guest context ID identifying the pending reply. */ + uint64_t uContextID; +}; + +/** Fake native backend connection context. */ +struct SHCLCONTEXT +{ + /** Main connection owning this context. */ + GuestShClConn *pConn; +}; + +/** State shared by the service and native-backend fakes. */ +typedef struct TSTSHCLSTATE +{ + /** Fake service client. */ + SHCLCLIENTOPAQUE Client; + /** Fake backend context. */ + SHCLCONTEXT BackendCtx; + /** Fake guest-data token. */ + SHCLGUESTDATATOKENOPAQUE Token; + /** Configured backend initialization result. */ + int vrcBackendInit; + /** Configured backend connection result. */ + int vrcBackendConnect; + /** Configured backend synchronization result. */ + int vrcBackendSync; + /** Configured service guest-data-begin result. */ + int vrcGuestDataBegin; + /** Whether guest-data begin returns success without a token. */ + bool fGuestDataNullToken; + + /** Backend initialization calls. */ + uint32_t cBackendInit; + /** Backend destruction calls. */ + uint32_t cBackendDestroy; + /** Backend connection calls. */ + uint32_t cBackendConnect; + /** Backend disconnection calls. */ + uint32_t cBackendDisconnect; + /** Backend format-report calls. */ + uint32_t cBackendReportFormats; + /** Backend read calls. */ + uint32_t cBackendRead; + /** Backend write calls. */ + uint32_t cBackendWrite; + /** Backend synchronization calls. */ + uint32_t cBackendSync; + /** Last backend format value. */ + SHCLFORMATS fBackendFormats; + /** Last backend format/data type. */ + SHCLFORMAT uBackendFormat; + /** Last backend buffer. */ + void *pvBackendData; + /** Last backend buffer size. */ + uint32_t cbBackendData; + /** Most recently installed backend callback table. */ + PSHCLCALLBACKS pBackendCallbacks; + + /** Service filter calls. */ + uint32_t cSvcFilter; + /** Service guest-format report calls. */ + uint32_t cSvcReportFormats; + /** Asynchronous service read calls. */ + uint32_t cSvcReadAsync; + /** Synchronous service read calls. */ + uint32_t cSvcRead; + /** Guest-data begin calls. */ + uint32_t cGuestDataBegin; + /** Guest-data completion calls. */ + uint32_t cGuestDataComplete; + /** Guest-data cancellation calls. */ + uint32_t cGuestDataCancel; + /** Last service format value. */ + SHCLFORMATS fSvcFormats; + /** Last guest-data format. */ + SHCLFORMAT uGuestDataFormat; + /** Last completed guest-data buffer. */ + void const *pvGuestData; + /** Last completed guest-data size. */ + uint32_t cbGuestData; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Fake transfer known to the service. */ + SHCLTRANSFER Transfer; + /** References held on the fake transfer. */ + uint32_t cTransferRefs; + /** Transfer callback-table requests made to the backend. */ + uint32_t cBackendTransferCallbacks; + /** Transfer status reports made to the backend. */ + uint32_t cBackendTransferStatus; + /** Transfer lookup-by-ID calls. */ + uint32_t cSvcTransferGetById; + /** Transfer lookup-by-key calls. */ + uint32_t cSvcTransferGetByKey; + /** Transfer create calls. */ + uint32_t cSvcTransferCreate; + /** Transfer initialization calls. */ + uint32_t cSvcTransferInit; + /** Transfer destroy-by-ID calls. */ + uint32_t cSvcTransferDestroyById; + /** Transfer destroy-all calls. */ + uint32_t cSvcTransferDestroyAll; + /** Provider initialization calls. */ + uint32_t cSvcProviderInit; + /** Last transfer direction. */ + SHCLTRANSFERDIR enmTransferDir; + /** Last transfer source. */ + SHCLSOURCE enmTransferSource; + /** Last transfer status. */ + SHCLTRANSFERSTATUS enmTransferStatus; + /** Last transfer status result. */ + int vrcTransferStatus; +#endif +} TSTSHCLSTATE; + +/** Disconnect worker arguments. */ +typedef struct TSTDISCONNECTARGS +{ + /** Connection to disconnect. */ + GuestShClConn *pConn; + /** Service transport identifying the connection. */ + SHCLTRANSPORT Transport; + /** Event signalled before entering disconnect. */ + RTSEMEVENT hStarted; + /** Result returned by disconnect. */ + int vrc; +} TSTDISCONNECTARGS; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Test framework handle. */ +static RTTEST g_hTest; +/** Shared fake state. */ +static TSTSHCLSTATE g_State; + + +/********************************************************************************************************************************* +* Fake native backend * +*********************************************************************************************************************************/ +/** @copydoc SHCLBACKENDOPS::pfnInit */ +static int tstBackendInit(void) +{ + g_State.cBackendInit++; + return g_State.vrcBackendInit; +} + +/** @copydoc SHCLBACKENDOPS::pfnDestroy */ +static void tstBackendDestroy(void) +{ + g_State.cBackendDestroy++; +} + +/** @copydoc SHCLBACKENDOPS::pfnSetCallbacks */ +static void tstBackendSetCallbacks(PSHCLCALLBACKS pCallbacks) +{ + g_State.pBackendCallbacks = pCallbacks; +} + +/** @copydoc SHCLBACKENDOPS::pfnConnect */ +static int tstBackendConnect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) +{ + g_State.cBackendConnect++; + if (RT_FAILURE(g_State.vrcBackendConnect)) + return g_State.vrcBackendConnect; + g_State.BackendCtx.pConn = pConn; + *ppCtx = &g_State.BackendCtx; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnDisconnect */ +static int tstBackendDisconnect(PSHCLCONTEXT pCtx) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + pCtx->pConn->transferDestroyAll(); +#endif + g_State.cBackendDisconnect++; + g_State.BackendCtx.pConn = NULL; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnReportFormats */ +static int tstBackendReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendReportFormats++; + g_State.fBackendFormats = fFormats; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnReadData */ +static int tstBackendReadData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, + uint32_t *pcbActual) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendRead++; + g_State.uBackendFormat = uFormat; + g_State.pvBackendData = pvData; + g_State.cbBackendData = cbData; + if (pcbActual) + *pcbActual = cbData; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnWriteData */ +static int tstBackendWriteData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendWrite++; + g_State.uBackendFormat = uFormat; + g_State.pvBackendData = pvData; + g_State.cbBackendData = cbData; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnSync */ +static int tstBackendSync(PSHCLCONTEXT pCtx) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendSync++; + return g_State.vrcBackendSync; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @copydoc SHCLBACKENDOPS::pfnTransferGetCallbacks */ +static void tstBackendTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendTransferCallbacks++; + RT_ZERO(*pCallbacks); + pCallbacks->pvUser = &g_State; + pCallbacks->cbUser = sizeof(g_State); +} + +/** @copydoc SHCLBACKENDOPS::pfnTransferHandleStatusReply */ +static int tstBackendTransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, + SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int vrcStatus) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + g_State.cBackendTransferStatus++; + g_State.enmTransferSource = enmSource; + g_State.enmTransferStatus = enmStatus; + g_State.vrcTransferStatus = vrcStatus; + return VINF_SUCCESS; +} +#endif + +/** Fake backend operation table selected by ShClBackend. */ +static SHCLBACKENDOPS const g_BackendOps = +{ + tstBackendInit, + tstBackendDestroy, + tstBackendSetCallbacks, + tstBackendConnect, + tstBackendDisconnect, + tstBackendReportFormats, + tstBackendReadData, + tstBackendWriteData, + tstBackendSync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstBackendTransferGetCallbacks, + tstBackendTransferHandleStatusReply, +#endif +}; + +/** + * Supplies the fake backend to ShClBackend. + * + * @returns Immutable fake backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &g_BackendOps; +} + + +/********************************************************************************************************************************* +* Fake service endpoint * +*********************************************************************************************************************************/ +/** Checks that an operation was made against the fake service client. */ +static void tstSvcCheckClient(SHCLCLIENTHANDLE hClient) +{ + RTTESTI_CHECK(hClient == &g_State.Client); +} + +/** @copydoc SHCLSVCOPS::pfnFilterFormats */ +static DECLCALLBACK(int) tstSvcFilterFormats(SHCLCLIENTHANDLE hClient, bool fHostToGuest, + SHCLFORMATS fFormats, SHCLFORMATS *pfFiltered) +{ + tstSvcCheckClient(hClient); + g_State.cSvcFilter++; + g_State.fSvcFormats = fFormats; + *pfFiltered = fHostToGuest ? fFormats : fFormats & ~VBOX_SHCL_FMT_HTML; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnReportFormatsToGuest */ +static DECLCALLBACK(int) tstSvcReportFormatsToGuest(SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + SHCLFORMATS *pfReported) +{ + tstSvcCheckClient(hClient); + g_State.cSvcReportFormats++; + g_State.fSvcFormats = fFormats; + if (pfReported) + *pfReported = fFormats; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnReadDataFromGuestAsync */ +static DECLCALLBACK(int) tstSvcReadDataFromGuestAsync(SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + PSHCLEVENT *ppEvent) +{ + tstSvcCheckClient(hClient); + g_State.cSvcReadAsync++; + g_State.fSvcFormats = fFormats; + RTTESTI_CHECK(ppEvent == NULL); + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnReadDataFromGuest */ +static DECLCALLBACK(int) tstSvcReadDataFromGuest(SHCLCLIENTHANDLE hClient, SHCLFORMAT uFormat, + void **ppvData, uint32_t *pcbData) +{ + static char const s_achData[] = "guest"; + tstSvcCheckClient(hClient); + g_State.cSvcRead++; + g_State.uGuestDataFormat = uFormat; + *ppvData = RTMemDup(s_achData, sizeof(s_achData)); + if (!*ppvData) + return VERR_NO_MEMORY; + *pcbData = sizeof(s_achData); + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnGuestDataBegin */ +static DECLCALLBACK(int) tstSvcGuestDataBegin(SHCLCLIENTHANDLE hClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(pCmdCtx != NULL); + g_State.cGuestDataBegin++; + g_State.uGuestDataFormat = uFormat; + if (RT_SUCCESS(g_State.vrcGuestDataBegin) && !g_State.fGuestDataNullToken) + *phToken = &g_State.Token; + return g_State.vrcGuestDataBegin; +} + +/** @copydoc SHCLSVCOPS::pfnGuestDataComplete */ +static DECLCALLBACK(int) tstSvcGuestDataComplete(SHCLCLIENTHANDLE hClient, SHCLGUESTDATATOKEN hToken, + void const *pvData, uint32_t cbData) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(hToken == &g_State.Token); + g_State.cGuestDataComplete++; + g_State.pvGuestData = pvData; + g_State.cbGuestData = cbData; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnGuestDataCancel */ +static DECLCALLBACK(void) tstSvcGuestDataCancel(SHCLCLIENTHANDLE hClient, SHCLGUESTDATATOKEN hToken) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(hToken == &g_State.Token); + g_State.cGuestDataCancel++; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Retains the fake transfer without depending on the GuestHost transfer data plane. */ +static void tstTransferRetain(void) +{ + ASMAtomicIncU32(&g_State.cTransferRefs); +} + +/** @copydoc SHCLSVCOPS::pfnTransferGetByIdRetained */ +static DECLCALLBACK(PSHCLTRANSFER) tstSvcTransferGetByIdRetained(SHCLCLIENTHANDLE hClient, + SHCLTRANSFERID idTransfer) +{ + tstSvcCheckClient(hClient); + g_State.cSvcTransferGetById++; + if (idTransfer != TST_SHCL_TRANSFER_ID) + return NULL; + tstTransferRetain(); + return &g_State.Transfer; +} + +/** @copydoc SHCLSVCOPS::pfnTransferGetByKeyRetained */ +static DECLCALLBACK(PSHCLTRANSFER) tstSvcTransferGetByKeyRetained(SHCLCLIENTHANDLE hClient, + SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration) +{ + tstSvcCheckClient(hClient); + g_State.cSvcTransferGetByKey++; + if ( idSession != TST_SHCL_SESSION_ID + || idTransfer != TST_SHCL_TRANSFER_ID + || uGeneration != TST_SHCL_TRANSFER_GEN) + return NULL; + tstTransferRetain(); + return &g_State.Transfer; +} + +/** @copydoc SHCLSVCOPS::pfnTransferCreate */ +static DECLCALLBACK(int) tstSvcTransferCreate(SHCLCLIENTHANDLE hClient, SHCLTRANSFERDIR enmDir, + SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, + SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(pCallbacks != NULL); + RTTESTI_CHECK(idTransfer == TST_SHCL_TRANSFER_ID); + g_State.cSvcTransferCreate++; + g_State.enmTransferDir = enmDir; + g_State.enmTransferSource = enmSource; + tstTransferRetain(); + *ppTransfer = &g_State.Transfer; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnTransferInit */ +static DECLCALLBACK(int) tstSvcTransferInit(SHCLCLIENTHANDLE hClient, PSHCLTRANSFER pTransfer) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + g_State.cSvcTransferInit++; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnTransferDestroyById */ +static DECLCALLBACK(void) tstSvcTransferDestroyById(SHCLCLIENTHANDLE hClient, SHCLTRANSFERID idTransfer) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(idTransfer == TST_SHCL_TRANSFER_ID); + g_State.cSvcTransferDestroyById++; +} + +/** @copydoc SHCLSVCOPS::pfnTransferDestroyAll */ +static DECLCALLBACK(void) tstSvcTransferDestroyAll(SHCLCLIENTHANDLE hClient) +{ + tstSvcCheckClient(hClient); + g_State.cSvcTransferDestroyAll++; +} + +/** @copydoc SHCLSVCOPS::pfnTransferProviderInitGuest */ +static DECLCALLBACK(int) tstSvcTransferProviderInitGuest(SHCLCLIENTHANDLE hClient, PSHCLTXPROVIDER pProvider) +{ + tstSvcCheckClient(hClient); + g_State.cSvcProviderInit++; + RT_ZERO(*pProvider); + pProvider->enmSource = SHCLSOURCE_REMOTE; + pProvider->pvUser = &g_State; + pProvider->cbUser = sizeof(g_State); + return VINF_SUCCESS; +} +#endif + +/** Fake service operation table. */ +static SHCLSVCOPS const g_SvcOps = +{ + sizeof(SHCLSVCOPS), + tstSvcFilterFormats, + tstSvcReportFormatsToGuest, + tstSvcReadDataFromGuestAsync, + tstSvcReadDataFromGuest, + tstSvcGuestDataBegin, + tstSvcGuestDataComplete, + tstSvcGuestDataCancel, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstSvcTransferGetByIdRetained, + tstSvcTransferGetByKeyRetained, + tstSvcTransferCreate, + tstSvcTransferInit, + tstSvcTransferDestroyById, + tstSvcTransferDestroyAll, + tstSvcTransferProviderInitGuest, +#endif +}; + +/** Returns the fake service transport. */ +static SHCLTRANSPORT tstTransport(void) +{ + SHCLTRANSPORT Transport; + Transport.hClient = &g_State.Client; + Transport.pOps = &g_SvcOps; + return Transport; +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + + +/********************************************************************************************************************************* +* Minimal transfer metadata implementation * +*********************************************************************************************************************************/ +/* These functions model the transfer metadata contract used by Main. The + * testcase deliberately does not link the transfer data-plane implementation. */ +bool ShClTransferStatusResultIsValid(SHCLTRANSFERSTATUS enmStatus, int vrcTransfer) +{ + switch (enmStatus) + { + case SHCLTRANSFERSTATUS_REQUESTED: + case SHCLTRANSFERSTATUS_INITIALIZED: + case SHCLTRANSFERSTATUS_UNINITIALIZED: + case SHCLTRANSFERSTATUS_STARTED: + case SHCLTRANSFERSTATUS_COMPLETED: + return RT_SUCCESS(vrcTransfer); + + case SHCLTRANSFERSTATUS_CANCELED: + return vrcTransfer == VERR_CANCELLED; + + case SHCLTRANSFERSTATUS_KILLED: + case SHCLTRANSFERSTATUS_ERROR: + return RT_FAILURE(vrcTransfer); + + default: + return false; + } +} + +uint32_t ShClTransferRelease(PSHCLTRANSFER pTransfer) +{ + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + uint32_t const cRefs = ASMAtomicDecU32(&g_State.cTransferRefs); + RTTESTI_CHECK(cRefs < UINT32_MAX); + return cRefs; +} + +SHCLTRANSFERID ShClTransferGetID(PSHCLTRANSFER pTransfer) +{ + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + return TST_SHCL_TRANSFER_ID; +} + +SHCLSESSIONID ShClTransferGetSessionId(PSHCLTRANSFER pTransfer) +{ + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + return TST_SHCL_SESSION_ID; +} + +SHCLTRANSFERGEN ShClTransferGetGeneration(PSHCLTRANSFER pTransfer) +{ + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + return TST_SHCL_TRANSFER_GEN; +} +#endif + + +/********************************************************************************************************************************* +* Helpers * +*********************************************************************************************************************************/ +/** Resets the fake state to successful defaults. */ +static void tstStateReset(void) +{ + RT_ZERO(g_State); + g_State.Client.uMagic = UINT32_C(0x434c4950); + g_State.Token.uMagic = UINT32_C(0x544f4b4e); + g_State.vrcBackendInit = VINF_SUCCESS; + g_State.vrcBackendConnect = VINF_SUCCESS; + g_State.vrcBackendSync = VINF_SUCCESS; + g_State.vrcGuestDataBegin = VINF_SUCCESS; +} + +/** Initializes and connects a test connection. */ +static void tstConnect(GuestShClConn &Conn, SHCLTRANSPORT *pTransport) +{ + *pTransport = tstTransport(); + RTTESTI_CHECK_RC(Conn.initBackend(), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.connect(pTransport), VINF_SUCCESS); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Disconnects and destroys a test connection. */ +static void tstDisconnect(GuestShClConn &Conn, PCSHCLTRANSPORT pTransport) +{ + if (Conn.isConnected()) + RTTESTI_CHECK_RC(Conn.disconnect(pTransport), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); +} +#endif + +/** Executes disconnect on a worker thread. */ +static DECLCALLBACK(int) tstDisconnectThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + TSTDISCONNECTARGS *pArgs = (TSTDISCONNECTARGS *)pvUser; + RTSemEventSignal(pArgs->hStarted); + pArgs->vrc = pArgs->pConn->disconnect(&pArgs->Transport); + return pArgs->vrc; +} + + +/********************************************************************************************************************************* +* Testcases * +*********************************************************************************************************************************/ +/** Tests backend initialization and destruction ownership. */ +static void tstBackendLifecycle(void) +{ + RTTestISub("Backend lifecycle"); + tstStateReset(); + + GuestShClConn Conn(NULL); + g_State.vrcBackendInit = VERR_NOT_SUPPORTED; + RTTESTI_CHECK_RC(Conn.initBackend(), VERR_NOT_SUPPORTED); + RTTESTI_CHECK(g_State.cBackendInit == 1); + + g_State.vrcBackendInit = VINF_SUCCESS; + RTTESTI_CHECK_RC(Conn.initBackend(), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.initBackend(), VINF_SUCCESS); + RTTESTI_CHECK(g_State.cBackendInit == 2); + + SHCLCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Conn.setBackendCallbacks(&Callbacks); + RTTESTI_CHECK(g_State.pBackendCallbacks == &Callbacks); + + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); + RTTESTI_CHECK(g_State.cBackendDestroy == 1); +} + +/** Tests transport identity and service/backend forwarding. */ +static void tstConnectionAndForwarding(void) +{ + RTTestISub("Connection and forwarding"); + tstStateReset(); + + GuestShClConn Conn(NULL); + SHCLTRANSPORT Transport = tstTransport(); + SHCLTRANSPORT Invalid = Transport; + Invalid.pOps = NULL; + RTTESTI_CHECK_RC(RTTestIDisableAssertions(), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.connect(&Invalid), VERR_INVALID_HANDLE); + RTTESTI_CHECK_RC(RTTestIRestoreAssertions(), VINF_SUCCESS); + + RTTESTI_CHECK_RC(Conn.initBackend(), VINF_SUCCESS); + g_State.vrcBackendConnect = VERR_NOT_AVAILABLE; + RTTESTI_CHECK_RC(Conn.connect(&Transport), VERR_NOT_AVAILABLE); + RTTESTI_CHECK(!Conn.isConnected()); + g_State.vrcBackendConnect = VINF_SUCCESS; + RTTESTI_CHECK_RC(Conn.connect(&Transport), VINF_SUCCESS); + RTTESTI_CHECK(Conn.matches(&Transport)); + RTTESTI_CHECK_RC(Conn.connect(&Transport), VERR_RESOURCE_BUSY); + + SHCLTRANSPORT Wrong = Transport; + SHCLCLIENTOPAQUE OtherClient = { UINT32_C(0xdeadbeef) }; + Wrong.hClient = &OtherClient; + RTTESTI_CHECK_RC(Conn.disconnect(&Wrong), VERR_INVALID_HANDLE); + + SHCLFORMATS fReported = 0; + RTTESTI_CHECK_RC(Conn.reportFormatsToGuest(VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML, &fReported), + VINF_SUCCESS); + RTTESTI_CHECK(fReported == (VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML)); + RTTESTI_CHECK(g_State.cSvcReportFormats == 1); + + RTTESTI_CHECK_RC(Conn.readDataFromGuestAsync(VBOX_SHCL_FMT_UNICODETEXT, NULL), VINF_SUCCESS); + + void *pvData = NULL; + uint32_t cbData = 0; + RTTESTI_CHECK_RC(Conn.readDataFromGuest(VBOX_SHCL_FMT_UNICODETEXT, &pvData, &cbData), VINF_SUCCESS); + RTTESTI_CHECK(cbData == sizeof("guest")); + RTTESTI_CHECK(pvData && !memcmp(pvData, "guest", sizeof("guest"))); + RTMemFree(pvData); + + RTTESTI_CHECK_RC(Conn.reportFormatsToBackend(VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML), VINF_SUCCESS); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RTTESTI_CHECK(g_State.cSvcFilter == 1); + RTTESTI_CHECK(g_State.fBackendFormats == VBOX_SHCL_FMT_UNICODETEXT); +#else + RTTESTI_CHECK(g_State.fBackendFormats == (VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML)); +#endif + + uint8_t abData[8]; + uint32_t cbActual = 0; + RTTESTI_CHECK_RC(Conn.readDataFromBackend(VBOX_SHCL_FMT_UNICODETEXT, abData, sizeof(abData), &cbActual), + VINF_SUCCESS); + RTTESTI_CHECK(cbActual == sizeof(abData)); + RTTESTI_CHECK_RC(Conn.writeDataToBackend(VBOX_SHCL_FMT_UNICODETEXT, abData, sizeof(abData)), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.syncBackend(), VINF_SUCCESS); + + RTTESTI_CHECK_RC(Conn.disconnect(&Transport), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.reportFormatsToBackend(VBOX_SHCL_FMT_UNICODETEXT), VERR_SHCLPB_NO_DATA); + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); +} + +/** Tests retained guest-data tokens and disconnect draining. */ +static void tstGuestDataTokens(void) +{ + RTTestISub("Guest-data token ownership"); + tstStateReset(); + + GuestShClConn Conn(NULL); + SHCLTRANSPORT Transport; + tstConnect(Conn, &Transport); + SHCLCLIENTCMDCTX CmdCtx = { UINT64_C(0x1234) }; + + SHCLGUESTDATATOKEN hToken = NULL; + g_State.fGuestDataNullToken = true; + RTTESTI_CHECK_RC(Conn.guestDataBegin(&CmdCtx, VBOX_SHCL_FMT_UNICODETEXT, &hToken), VINF_SUCCESS); + RTTESTI_CHECK(hToken == NULL); + + g_State.fGuestDataNullToken = false; + g_State.vrcGuestDataBegin = VERR_INVALID_CONTEXT; + RTTESTI_CHECK_RC(Conn.guestDataBegin(&CmdCtx, VBOX_SHCL_FMT_UNICODETEXT, &hToken), VERR_INVALID_CONTEXT); + + g_State.vrcGuestDataBegin = VINF_SUCCESS; + RTTESTI_CHECK_RC(Conn.guestDataBegin(&CmdCtx, VBOX_SHCL_FMT_UNICODETEXT, &hToken), VINF_SUCCESS); + RTTESTI_CHECK(hToken == &g_State.Token); + Conn.guestDataCancel(hToken); + RTTESTI_CHECK(g_State.cGuestDataCancel == 1); + + hToken = NULL; + RTTESTI_CHECK_RC(Conn.guestDataBegin(&CmdCtx, VBOX_SHCL_FMT_HTML, &hToken), VINF_SUCCESS); + + TSTDISCONNECTARGS Args; + Args.pConn = &Conn; + Args.Transport = Transport; + Args.hStarted = NIL_RTSEMEVENT; + Args.vrc = VERR_IPE_UNINITIALIZED_STATUS; + RTTESTI_CHECK_RC(RTSemEventCreate(&Args.hStarted), VINF_SUCCESS); + RTTHREAD hThread = NIL_RTTHREAD; + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstDisconnectThread, &Args, 0, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "shcl-discon"), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTSemEventWait(Args.hStarted, RT_MS_5SEC), VINF_SUCCESS); + + for (uint32_t i = 0; i < 5000 && Conn.isConnected(); i++) + RTThreadSleep(1); + RTTESTI_CHECK(!Conn.isConnected()); + RTTESTI_CHECK(g_State.cBackendDisconnect == 0); + + static char const s_achReply[] = "reply"; + RTTESTI_CHECK_RC(Conn.guestDataComplete(hToken, s_achReply, sizeof(s_achReply)), VINF_SUCCESS); + int vrcThread = VERR_IPE_UNINITIALIZED_STATUS; + RTTESTI_CHECK_RC(RTThreadWait(hThread, RT_MS_5SEC, &vrcThread), VINF_SUCCESS); + RTTESTI_CHECK_RC(vrcThread, VINF_SUCCESS); + RTTESTI_CHECK(g_State.cBackendDisconnect == 1); + RTTESTI_CHECK(g_State.cGuestDataComplete == 1); + RTTESTI_CHECK(g_State.pvGuestData == s_achReply); + RTTESTI_CHECK_RC(RTSemEventDestroy(Args.hStarted), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Tests Main's transfer metadata and full-key forwarding. */ +static void tstTransfers(void) +{ + RTTestISub("Transfer metadata forwarding"); + tstStateReset(); + + GuestShClConn Conn(NULL); + SHCLTRANSPORT Transport; + tstConnect(Conn, &Transport); + + PSHCLTRANSFER pTransfer = Conn.transferGetByIdRetained(TST_SHCL_TRANSFER_ID); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + if (pTransfer) + ShClTransferRelease(pTransfer); + RTTESTI_CHECK(Conn.transferGetByIdRetained(TST_SHCL_TRANSFER_ID + 1) == NULL); + + pTransfer = Conn.transferGetByKeyRetained(TST_SHCL_SESSION_ID, TST_SHCL_TRANSFER_ID, + TST_SHCL_TRANSFER_GEN + 1); + RTTESTI_CHECK(pTransfer == NULL); + pTransfer = Conn.transferGetByKeyRetained(TST_SHCL_SESSION_ID, TST_SHCL_TRANSFER_ID, + TST_SHCL_TRANSFER_GEN); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + if (pTransfer) + ShClTransferRelease(pTransfer); + + SHCLTRANSFERCALLBACKS Callbacks; + RT_ZERO(Callbacks); + RTTESTI_CHECK_RC(Conn.transferGetCallbacks(&Callbacks), VINF_SUCCESS); + RTTESTI_CHECK(Callbacks.pvUser == &g_State); + + pTransfer = NULL; + RTTESTI_CHECK_RC(Conn.transferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, &Callbacks, + TST_SHCL_TRANSFER_ID, &pTransfer), VINF_SUCCESS); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + if (pTransfer) + ShClTransferRelease(pTransfer); + RTTESTI_CHECK_RC(Conn.transferInit(&g_State.Transfer), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.transferHandleStatusReply(&g_State.Transfer, SHCLSOURCE_REMOTE, + SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS), VINF_SUCCESS); + RTTESTI_CHECK(g_State.enmTransferStatus == SHCLTRANSFERSTATUS_REQUESTED); + + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); + RTTESTI_CHECK_RC(Conn.transferProviderInitGuest(&Provider), VINF_SUCCESS); + RTTESTI_CHECK(Provider.enmSource == SHCLSOURCE_REMOTE); + Conn.transferDestroyById(TST_SHCL_TRANSFER_ID); + + RTTESTI_CHECK(g_State.cTransferRefs == 0); + tstDisconnect(Conn, &Transport); + RTTESTI_CHECK(g_State.cSvcTransferDestroyAll == 1); +} +#endif + + +/** Testcase entry point. */ +int main(void) +{ + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardMain", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + tstBackendLifecycle(); + tstConnectionAndForwarding(); + tstGuestDataTokens(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstTransfers(); +#endif + + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp b/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp new file mode 100644 index 000000000000..91aa16402682 --- /dev/null +++ b/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp @@ -0,0 +1,759 @@ +/* $Id: tstClipboardMain2HostSvc.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Host Service integration testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include "GuestShClBackendPrivate.h" +#include "GuestShClConn.h" + +#include +#include + +#include +#include +#include + + +/** + * @page pg_tstClipboardMain2HostSvc Main to Shared Clipboard Host Service integration testcase + * + * Links the production HGCM service to Main's production connection, backend + * dispatcher. A small adapter replaces only Console-facing notifications, + * while a fake backend replaces the native operating-system clipboard. + * + * The test covers registration, connect/sync/disconnect, the real service + * transport, messages in both directions, guest-data reply ownership and one + * transfer handshake, including initialization and destruction callbacks. VM + * construction, public Main API objects, native clipboard contents and transfer + * data providers are outside its scope. + */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** State attached to a synthetic HGCM call. */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Whether the Host Service completed the call. */ + bool fCompleted; + /** Completion status supplied by the Host Service. */ + int32_t rc; +}; + +/** Fake native backend context. */ +struct SHCLCONTEXT +{ + /** Main connection owning the context. */ + GuestShClConn *pConn; +}; + +/** Integration-test state. */ +typedef struct TSTSHCLSTATE +{ + /** Whether the Host Service was loaded successfully. */ + bool fServiceLoaded; + /** Whether Main's test extension was registered successfully. */ + bool fExtensionRegistered; + /** Fake native backend context. */ + SHCLCONTEXT BackendCtx; + /** Number of native backend initializations. */ + uint32_t cBackendInit; + /** Number of native backend destructions. */ + uint32_t cBackendDestroy; + /** Number of native backend connections. */ + uint32_t cBackendConnect; + /** Number of native backend disconnections. */ + uint32_t cBackendDisconnect; + /** Number of native backend synchronizations. */ + uint32_t cBackendSync; + /** Number of guest format notifications crossing into Main. */ + uint32_t cGuestFormats; + /** Last guest format mask received by Main. */ + SHCLFORMATS fGuestFormats; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Number of transfer callback-table requests reaching the backend. */ + uint32_t cTransferCallbacks; + /** Number of transfer status notifications reaching the backend. */ + uint32_t cTransferStatuses; + /** Session of the last transfer notification. */ + SHCLSESSIONID idTransferSession; + /** ID of the last transfer notification. */ + SHCLTRANSFERID idTransfer; + /** Generation of the last transfer notification. */ + SHCLTRANSFERGEN uTransferGeneration; + /** Last transfer status. */ + SHCLTRANSFERSTATUS enmTransferStatus; +#endif +} TSTSHCLSTATE; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** State recorded while checking the post-initialization callback contract. */ +typedef struct TSTTRANSFERINITIALIZED +{ + /** Number of post-initialization callback invocations. */ + uint32_t cInitialized; + /** Number of destruction callback invocations. */ + uint32_t cDestroyed; + /** Transfer status observed by the post-initialization callback. */ + SHCLTRANSFERSTATUS enmStatus; + /** Whether the transfer lock was owned by the callback thread. */ + bool fLockOwned; +} TSTTRANSFERINITIALIZED; +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Test framework handle. */ +static RTTEST g_hTest; +/** Loaded HGCM service table. */ +static VBOXHGCMSVCFNTABLE g_Table; +/** Helpers supplied to the HGCM service. */ +static VBOXHGCMSVCHELPERS g_Helpers; +/** Shared integration state. */ +static TSTSHCLSTATE g_State; + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable); + + +/********************************************************************************************************************************* +* Fake Native Backend * +*********************************************************************************************************************************/ +/** @copydoc SHCLBACKENDOPS::pfnInit */ +static int tstBackendInit(void) +{ + g_State.cBackendInit++; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnDestroy */ +static void tstBackendDestroy(void) +{ + g_State.cBackendDestroy++; +} + +/** @copydoc SHCLBACKENDOPS::pfnSetCallbacks */ +static void tstBackendSetCallbacks(PSHCLCALLBACKS pCallbacks) +{ + RT_NOREF(pCallbacks); +} + +/** @copydoc SHCLBACKENDOPS::pfnConnect */ +static int tstBackendConnect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) +{ + g_State.cBackendConnect++; + g_State.BackendCtx.pConn = pConn; + *ppCtx = &g_State.BackendCtx; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnDisconnect */ +static int tstBackendDisconnect(PSHCLCONTEXT pCtx) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + pCtx->pConn->transferDestroyAll(); +#endif + g_State.cBackendDisconnect++; + g_State.BackendCtx.pConn = NULL; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnReportFormats */ +static int tstBackendReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cGuestFormats++; + g_State.fGuestFormats = fFormats; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnReadData */ +static int tstBackendReadData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, + uint32_t *pcbActual) +{ + RT_NOREF(pCtx, uFormat, pvData, cbData); + *pcbActual = 0; + return VERR_NO_DATA; +} + +/** @copydoc SHCLBACKENDOPS::pfnWriteData */ +static int tstBackendWriteData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pCtx, uFormat, pvData, cbData); + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnSync */ +static int tstBackendSync(PSHCLCONTEXT pCtx) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendSync++; + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Records the state observed by the post-initialization callback. */ +static DECLCALLBACK(void) tstTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + TSTTRANSFERINITIALIZED *pState = (TSTTRANSFERINITIALIZED *)pCbCtx->pvUser; + RTTESTI_CHECK_RETV(pState != NULL); + RTTESTI_CHECK_RETV(pCbCtx->cbUser == sizeof(*pState)); + + pState->cInitialized++; + pState->fLockOwned = RTCritSectIsOwner(&pCbCtx->pTransfer->CritSect); + pState->enmStatus = ShClTransferGetStatus(pCbCtx->pTransfer); +} + +/** Records destruction of the transfer used by the callback contract test. */ +static DECLCALLBACK(void) tstTransferDestroyedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + TSTTRANSFERINITIALIZED *pState = (TSTTRANSFERINITIALIZED *)pCbCtx->pvUser; + RTTESTI_CHECK(pState != NULL); + if (pState) + pState->cDestroyed++; +} + +/** @copydoc SHCLBACKENDOPS::pfnTransferGetCallbacks */ +static void tstBackendTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cTransferCallbacks++; + RT_ZERO(*pCallbacks); +} + +/** @copydoc SHCLBACKENDOPS::pfnTransferHandleStatusReply */ +static int tstBackendTransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, + SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus) +{ + RT_NOREF(enmSource, rcStatus); + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cTransferStatuses++; + g_State.idTransferSession = ShClTransferGetSessionId(pTransfer); + g_State.idTransfer = ShClTransferGetID(pTransfer); + g_State.uTransferGeneration = ShClTransferGetGeneration(pTransfer); + g_State.enmTransferStatus = enmStatus; + return VINF_SUCCESS; +} +#endif + +/** Backend operation table selected by Main's backend dispatcher. */ +static SHCLBACKENDOPS const g_BackendOps = +{ + tstBackendInit, + tstBackendDestroy, + tstBackendSetCallbacks, + tstBackendConnect, + tstBackendDisconnect, + tstBackendReportFormats, + tstBackendReadData, + tstBackendWriteData, + tstBackendSync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstBackendTransferGetCallbacks, + tstBackendTransferHandleStatusReply, +#endif +}; + +/** + * Returns the fake native backend to production Main code. + * + * @returns Immutable fake backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &g_BackendOps; +} + + +/********************************************************************************************************************************* +* Main Service Extension Adapter * +*********************************************************************************************************************************/ +/** + * Dispatches the connection-owned portion of Main's service extension. + * + * Console-facing notifications are reduced to deterministic sinks. Connection + * ownership and backend dispatch use production Main code; all parameters here + * are produced by the real Host Service. + * + * @returns VBox status code. + * @param pvExtension GuestShClConn instance receiving the request. + * @param uFunction VBOX_CLIPBOARD_EXT_FN_XXX function number. + * @param pvParms Service-extension parameters. + * @param cbParms Size of @a pvParms in bytes. + */ +static DECLCALLBACK(int) tstMainExtension(void *pvExtension, uint32_t uFunction, void *pvParms, uint32_t cbParms) +{ + GuestShClConn * const pConn = (GuestShClConn *)pvExtension; + RTTESTI_CHECK_RET(cbParms == sizeof(SHCLEXTPARMS), VERR_INVALID_PARAMETER); + PSHCLEXTPARMS const pParms = (PSHCLEXTPARMS)pvParms; + int vrc; + switch (uFunction) + { + case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: + return pConn->initBackend(); + case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: + return pConn->destroyBackend(); + case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + return pConn->connect(&Transport); + } + case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + return pConn->disconnect(&Transport); + } + case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: + return pConn->syncBackend(); + case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: + return pConn->reportFormatsToBackend(pParms->u.ReportFormats.uFormats); + case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: + { + SHCLGUESTDATATOKEN hToken = NULL; + vrc = pConn->guestDataBegin(pParms->u.ReadWriteData.pCmdCtx, + pParms->u.ReadWriteData.uFormat, &hToken); + if (RT_SUCCESS(vrc)) + vrc = pConn->guestDataComplete(hToken, pParms->u.ReadWriteData.pvData, + pParms->u.ReadWriteData.cbData); + return vrc; + } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: + return pConn->transferGetCallbacks(pParms->u.TransferCallbacks.pCallbacks); + case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: + return pConn->transferHandleStatusReply(pParms->u.FileTransferData.pTransfer, + pParms->u.FileTransferData.enmShClSource, + pParms->u.FileTransferData.pReply->u.TransferStatus.uStatus, + (int)pParms->u.FileTransferData.pReply->rc); +#endif + default: + return VERR_NOT_SUPPORTED; + } +} + + +/********************************************************************************************************************************* +* Test Helpers * +*********************************************************************************************************************************/ +/** + * Completes one synthetic guest call. + * + * @returns VINF_SUCCESS. + * @param hCall Synthetic call handle to complete. + * @param rc Guest-call result. + */ +static DECLCALLBACK(int) tstCallComplete(VBOXHGCMCALLHANDLE hCall, int32_t rc) +{ + hCall->fCompleted = true; + hCall->rc = rc; + return VINF_SUCCESS; +} + +/** + * Executes a guest call expected to complete synchronously. + * + * @returns Guest-call result. + * @param pvClient HGCM client state. + * @param uFunction Guest function number. + * @param cParms Number of HGCM parameters. + * @param paParms HGCM parameters. Optional if @a cParms is zero. + */ +static int tstGuestCall(void *pvClient, uint32_t uFunction, uint32_t cParms, VBOXHGCMSVCPARM *paParms) +{ + VBOXHGCMCALLHANDLE_TYPEDEF Call; + Call.fCompleted = false; + Call.rc = VERR_IPE_UNINITIALIZED_STATUS; + g_Table.pfnCall(g_Table.pvService, &Call, 1 /* idClient */, pvClient, + uFunction, cParms, paParms, 0 /* tsArrival */); + RTTESTI_CHECK_MSG_RET(Call.fCompleted, ("Guest function %RU32 did not complete\n", uFunction), + VERR_INTERNAL_ERROR); + return Call.rc; +} + +/** + * Loads and registers the real Host Service with Main's test adapter. + * + * @returns VBox status code. + * @param pConn Main connection registered as the extension target. + */ +static int tstLoad(GuestShClConn *pConn) +{ + RT_ZERO(g_Table); + RT_ZERO(g_Helpers); + g_Helpers.pfnCallComplete = tstCallComplete; + g_Table.cbSize = sizeof(g_Table); + g_Table.u32Version = VBOX_HGCM_SVC_VERSION; + g_Table.pHelpers = &g_Helpers; + + int vrc = VBoxHGCMSvcLoad(&g_Table); + if (RT_SUCCESS(vrc)) + { + g_State.fServiceLoaded = true; + vrc = g_Table.pfnRegisterExtension(g_Table.pvService, tstMainExtension, pConn); + if (RT_SUCCESS(vrc)) + g_State.fExtensionRegistered = true; + } + return vrc; +} + +/** + * Enables bidirectional clipboard traffic and reports modern guest features. + * + * @param pvClient Connected HGCM client state. + */ +static void tstNegotiate(void *pvClient) +{ + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, VBOX_SHCL_MODE_BIDIRECTIONAL); + int vrc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_MODE, 1, &Parm); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + vrc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); +#endif + + VBOXHGCMSVCPARM aFeatures[2]; + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_GF_0_TRANSFERS +#endif + ); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RTTESTI_CHECK(g_State.cBackendSync == 1); + + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(g_State.cBackendSync == 1); +#endif +} + + +/********************************************************************************************************************************* +* Test Cases * +*********************************************************************************************************************************/ +/** + * Checks service registration and the complete connection lifecycle. + * + * @param pConn Main connection used by the extension adapter. + * @param ppvClient Where to return the connected HGCM client state. + */ +static void tstLifecycle(GuestShClConn *pConn, void **ppvClient) +{ + RTTestISub("Service and Main lifecycle"); + int vrc = tstLoad(pConn); + RTTESTI_CHECK_RC_RETV(vrc, VINF_SUCCESS); + + void *pvClient = RTMemAllocZ(g_Table.cbClient); + RTTESTI_CHECK_RETV(pvClient != NULL); + vrc = g_Table.pfnConnect(g_Table.pvService, 1, pvClient, 0 /* fRequestor */, false /* fRestoring */); + if (RT_FAILURE(vrc)) + { + RTTestIFailed("Connecting the HGCM client failed: %Rrc", vrc); + RTMemFree(pvClient); + return; + } + + RTTESTI_CHECK(pConn->isConnected()); + RTTESTI_CHECK(g_State.cBackendInit == 1); + RTTESTI_CHECK(g_State.cBackendConnect == 1); + RTTESTI_CHECK(g_State.cBackendSync == 1); + *ppvClient = pvClient; +} + +/** + * Checks messages flowing from Main through the real Host Service to the guest. + * + * @param pvClient Connected HGCM client state. + * @param pConn Connected Main service connection. + */ +static void tstFormats(void *pvClient, GuestShClConn *pConn) +{ + RTTestISub("Format messages in both directions"); + SHCLFORMATS fReported = VBOX_SHCL_FMT_NONE; + int vrc = pConn->reportFormatsToGuest(VBOX_SHCL_FMT_UNICODETEXT, &fReported); + RTTESTI_CHECK_RC_OK(vrc); + RTTESTI_CHECK(fReported == VBOX_SHCL_FMT_UNICODETEXT); + + VBOXHGCMSVCPARM aGet[2]; + HGCMSvcSetU32(&aGet[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + HGCMSvcSetU32(&aGet[1], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aGet), aGet); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(aGet[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, VBOX_SHCL_FMT_HTML); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(g_State.cGuestFormats == 1); + RTTESTI_CHECK(g_State.fGuestFormats == VBOX_SHCL_FMT_HTML); +} + +/** + * Checks a service-owned reply context across the real extension boundary. + * + * @param pvClient Connected HGCM client state. + * @param pConn Connected Main service connection. + */ +static void tstGuestData(void *pvClient, GuestShClConn *pConn) +{ + RTTestISub("Guest data reply ownership"); + PSHCLEVENT pEvent = NULL; + int vrc = pConn->readDataFromGuestAsync(VBOX_SHCL_FMT_UNICODETEXT, &pEvent); + RTTESTI_CHECK_RC_RETV(vrc, VINF_SUCCESS); + RTTESTI_CHECK_RETV(pEvent != NULL); + + VBOXHGCMSVCPARM aGet[2]; + HGCMSvcSetU64(&aGet[0], VBOX_SHCL_HOST_MSG_READ_DATA_CID); + HGCMSvcSetU32(&aGet[1], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aGet), aGet); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + uint64_t const uContext = aGet[0].u.uint64; + RTTESTI_CHECK(aGet[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + + static uint8_t const s_abData[] = { 'm', 'a', 'i', 'n', '\0' }; + VBOXHGCMSVCPARM aWrite[VBOX_SHCL_CPARMS_DATA_WRITE]; + HGCMSvcSetU64(&aWrite[0], uContext); + HGCMSvcSetU32(&aWrite[1], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetPv(&aWrite[2], (void *)s_abData, sizeof(s_abData)); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWrite), aWrite); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + + PSHCLEVENTPAYLOAD pPayload = NULL; + vrc = ShClEventWait(pEvent, RT_MS_1SEC, &pPayload); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + if (pPayload) + { + RTTESTI_CHECK(pPayload->cbData == sizeof(s_abData)); + RTTESTI_CHECK(memcmp(pPayload->pvData, s_abData, sizeof(s_abData)) == 0); + ShClPayloadDestroy(pPayload); + } + RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Checks transfer initialization and destruction across Main and the Host Service. + * + * @param pvClient Connected HGCM client state. + * @param pConn Connected Main service connection. + */ +static void tstTransferInitialized(void *pvClient, GuestShClConn *pConn) +{ + RTTestISub("Transfer initialization"); + + TSTTRANSFERINITIALIZED State; + RT_ZERO(State); + + SHCLTRANSFERCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pfnOnInitialized = tstTransferInitializedCallback; + Callbacks.pfnOnDestroy = tstTransferDestroyedCallback; + Callbacks.pvUser = &State; + Callbacks.cbUser = sizeof(State); + + PSHCLTRANSFER pTransfer = NULL; + int vrc = pConn->transferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, &Callbacks, + NIL_SHCLTRANSFERID, &pTransfer); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + if (RT_FAILURE(vrc)) + return; + + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + + vrc = pConn->transferInit(pTransfer); + RTTESTI_CHECK_RC_OK(vrc); + RTTESTI_CHECK(State.cInitialized == 1); + RTTESTI_CHECK(State.enmStatus == SHCLTRANSFERSTATUS_INITIALIZED); + RTTESTI_CHECK(!State.fLockOwned); + + VBOXHGCMSVCPARM aStatus[VBOX_SHCL_CPARMS_TRANSFER_STATUS]; + HGCMSvcSetU64(&aStatus[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aStatus[1], 0); + HGCMSvcSetU32(&aStatus[2], 0); + HGCMSvcSetU32(&aStatus[3], 0); + HGCMSvcSetU32(&aStatus[4], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatus), aStatus); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(aStatus[0].u.uint64) == idSession); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(aStatus[0].u.uint64) == idTransfer); + RTTESTI_CHECK(aStatus[1].u.uint32 == SHCLTRANSFERDIR_FROM_REMOTE); + RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_INITIALIZED); + RTTESTI_CHECK((int32_t)aStatus[3].u.uint32 == VINF_SUCCESS); + + ShClTransferRelease(pTransfer); + pConn->transferDestroyById(idTransfer); + RTTESTI_CHECK(State.cDestroyed == 1); + + HGCMSvcSetU64(&aStatus[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aStatus[1], 0); + HGCMSvcSetU32(&aStatus[2], 0); + HGCMSvcSetU32(&aStatus[3], 0); + HGCMSvcSetU32(&aStatus[4], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatus), aStatus); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(aStatus[0].u.uint64) == idSession); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(aStatus[0].u.uint64) == idTransfer); + RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_UNINITIALIZED); +} + + +/** + * Checks one guest-requested transfer reaching Main and the native backend. + * + * @param pvClient Connected HGCM client state. + */ +static void tstTransfer(void *pvClient) +{ + RTTestISub("Transfer control-plane handshake"); + VBOXHGCMSVCPARM aReply[VBOX_SHCL_CPARMS_REPLY_MIN + 1]; + HGCMSvcSetU64(&aReply[0], 0); + HGCMSvcSetU32(&aReply[1], VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS); + HGCMSvcSetU32(&aReply[2], VINF_SUCCESS); + HGCMSvcSetPv(&aReply[3], NULL, 0); + HGCMSvcSetU32(&aReply[4], SHCLTRANSFERSTATUS_REQUESTED); + int vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPLY, RT_ELEMENTS(aReply), aReply); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(g_State.cTransferCallbacks == 1); + RTTESTI_CHECK(g_State.cTransferStatuses == 1); + RTTESTI_CHECK(g_State.idTransferSession != NIL_SHCLSESSIONID); + RTTESTI_CHECK(g_State.idTransfer != NIL_SHCLTRANSFERID); + RTTESTI_CHECK(g_State.uTransferGeneration != NIL_SHCLTRANSFERGEN); + RTTESTI_CHECK(g_State.enmTransferStatus == SHCLTRANSFERSTATUS_REQUESTED); + + VBOXHGCMSVCPARM aStatus[VBOX_SHCL_CPARMS_TRANSFER_STATUS]; + HGCMSvcSetU64(&aStatus[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aStatus[1], 0); + HGCMSvcSetU32(&aStatus[2], 0); + HGCMSvcSetU32(&aStatus[3], 0); + HGCMSvcSetU32(&aStatus[4], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatus), aStatus); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(aStatus[0].u.uint64) == g_State.idTransferSession); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(aStatus[0].u.uint64) == g_State.idTransfer); + RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_REQUESTED); + + VBOXHGCMSVCPARM aCancel[2]; + HGCMSvcSetU64(&aCancel[0], VBOX_SHCL_CONTEXTID_MAKE(g_State.idTransferSession, g_State.idTransfer, 0)); + HGCMSvcSetU64(&aCancel[1], g_State.uTransferGeneration); + vrc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_CANCEL, RT_ELEMENTS(aCancel), aCancel); + RTTESTI_CHECK_RC_OK(vrc); + + HGCMSvcSetU64(&aStatus[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aStatus[1], 0); + HGCMSvcSetU32(&aStatus[2], 0); + HGCMSvcSetU32(&aStatus[3], 0); + HGCMSvcSetU32(&aStatus[4], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatus), aStatus); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_CANCELED); +} +#endif + +/** + * Disconnects Main, unregisters its extension and unloads the Host Service. + * + * @param pvClient Connected HGCM client state. May be NULL. + * @param pConn Main service connection. + */ +static void tstShutdown(void *pvClient, GuestShClConn *pConn) +{ + RTTestISub("Orderly shutdown"); + if (pvClient) + { + int const vrc = g_Table.pfnDisconnect(g_Table.pvService, 1, pvClient); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTMemFree(pvClient); + } + RTTESTI_CHECK(!pConn->isConnected()); + RTTESTI_CHECK(g_State.cBackendDisconnect == 1); + + if (g_State.fExtensionRegistered) + { + int const vrc = g_Table.pfnRegisterExtension(g_Table.pvService, NULL, NULL); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + g_State.fExtensionRegistered = false; + RTTESTI_CHECK(g_State.cBackendDestroy == 1); + } + if (g_State.fServiceLoaded) + { + int const vrc = g_Table.pfnUnload(g_Table.pvService); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + g_State.fServiceLoaded = false; + } +} + +/** Testcase entry point. */ +int main(void) +{ + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardMain2HostSvc", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + RT_ZERO(g_State); + try + { + GuestShClConn Conn(NULL); + void *pvClient = NULL; + tstLifecycle(&Conn, &pvClient); + if (pvClient) + { + tstNegotiate(pvClient); + tstFormats(pvClient, &Conn); + tstGuestData(pvClient, &Conn); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstTransferInitialized(pvClient, &Conn); + tstTransfer(pvClient); +#endif + } + tstShutdown(pvClient, &Conn); + } + catch (int vrc) + { + RTTestIFailed("Constructing Main's clipboard connection failed: %Rrc", vrc); + } + + return RTTestSummaryAndDestroy(g_hTest); +} From 2ed54b4ead02a206b07f3b128b40d433b85ea673 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 16:48:01 +0000 Subject: [PATCH 150/176] Shared Clipboard: Propagated post-initialization errors. bugref:4697 svn:sync-xref-src-repo-rev: r174898 --- .../GuestHost/SharedClipboard-transfers.h | 10 +++--- .../VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp | 4 ++- .../Additions/win/VBoxTray/VBoxClipboard.cpp | 7 +++-- .../x11/VBoxClient/clipboard-x11.cpp | 31 ++++++++++++------- .../SharedClipboard/clipboard-transfers.cpp | 4 +-- .../src-client/win/ClipboardBackendWin.cpp | 5 +-- .../testcase/tstClipboardMain2HostSvc.cpp | 30 ++++++++++-------- 7 files changed, 55 insertions(+), 36 deletions(-) diff --git a/include/VBox/GuestHost/SharedClipboard-transfers.h b/include/VBox/GuestHost/SharedClipboard-transfers.h index 8a7b650e30b2..4d6bff9b826d 100644 --- a/include/VBox/GuestHost/SharedClipboard-transfers.h +++ b/include/VBox/GuestHost/SharedClipboard-transfers.h @@ -1,4 +1,4 @@ -/* $Id: SharedClipboard-transfers.h 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: SharedClipboard-transfers.h 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Shared transfer functions between host and guest. */ @@ -824,16 +824,18 @@ typedef struct _SHCLTRANSFERCALLBACKS /** * Called when the transfer gets initialized. * - * @return VBox status code. On error the intialization will will be treated as failed. + * @return VBox status code. On error the initialization is treated as failed. * @param pCbCtx Pointer to callback context to use. */ DECLCALLBACKMEMBER(int, pfnOnInitialize,(PSHCLTRANSFERCALLBACKCTX pCbCtx)); /** - * Called after the transfer got initialized. + * Called after the transfer entered the initialized state. * + * @return VBox status code propagated by ShClTransferInit(). On failure + * the lifecycle owner must abort or destroy the initialized transfer. * @param pCbCtx Pointer to callback context to use. */ - DECLCALLBACKMEMBER(void, pfnOnInitialized,(PSHCLTRANSFERCALLBACKCTX pCbCtx)); + DECLCALLBACKMEMBER(int, pfnOnInitialized,(PSHCLTRANSFERCALLBACKCTX pCbCtx)); /** * Called before the transfer gets destroyed. * diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp index 6f224808b2b4..245904b0a17c 100644 --- a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxGuestR3LibClipboard.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxGuestR3LibClipboard.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ /** @file * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared Clipboard. */ @@ -2479,6 +2479,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, int rc2 = VbglR3ClipboardTransferSendStatus(pCmdCtx, pTransfer, SHCLTRANSFERSTATUS_ERROR, rc); AssertRC(rc2); + if (RT_SUCCESS(rc2)) + fErrorSent = true; } } else diff --git a/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp b/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp index af75fb7bc93e..9a5f422c8880 100644 --- a/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp +++ b/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxClipboard.cpp 115049 2026-08-17 15:12:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxClipboard.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ /** @file * VBoxClipboard - Shared clipboard, Windows Guest Implementation. */ @@ -97,7 +97,7 @@ static DECLCALLBACK(void) vbtrShClTransferCreatedCallback(PSHCLTRANSFERCALLBACKC static DECLCALLBACK(void) vbtrShClTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx); static DECLCALLBACK(void) vbtrShClTransferDestroyCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); -static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); +static DECLCALLBACK(int) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); static DECLCALLBACK(void) vbtrShClTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rc); #endif @@ -234,7 +234,7 @@ static DECLCALLBACK(int) vbtrShClTransferInitializeCallback(PSHCLTRANSFERCALLBAC * * @thread Clipboard main thread. */ -static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +static DECLCALLBACK(int) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { LogFlowFuncEnter(); @@ -262,6 +262,7 @@ static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLB } LogFlowFuncLeaveRC(rc); + return rc; } /** diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index e3fd4cb1ad2a..813ffbf0c372 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-x11.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -174,16 +174,18 @@ static int vbclX11TransferStateStart(PSHCLCONTEXT pCtx) * If the host clipboard changed while a transfer was being initialized, a new * serialized request is started for the latest offer after the old transfer * has been canceled. + * + * @returns VBox status code for preparing and publishing the URI list. */ -static void vbclX11TransferStateComplete(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, - const char *pszUriList, size_t cbUriList, int rcPreparation) +static int vbclX11TransferStateComplete(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, + const char *pszUriList, size_t cbUriList, int rcPreparation) { PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; if (!vbclX11TransferStateMatches(pCtx, pTransfer)) { LogRel2(("Shared Clipboard: Ignoring URI-list preparation completion for unbound transfer %RU16/%RU64\n", ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); - return; + return VINF_SUCCESS; } uint64_t const uPreparingOfferGeneration = pX11TransferState->uPreparingOfferGeneration; @@ -227,7 +229,8 @@ static void vbclX11TransferStateComplete(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTrans ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); vbclX11TransferUnregister(pCtx, pTransfer); - if (ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_INITIALIZED) + if ( RT_SUCCESS(rcPreparation) + && ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_INITIALIZED) { int rc2 = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pTransfer, SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); @@ -244,6 +247,8 @@ static void vbclX11TransferStateComplete(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTrans if (RT_FAILURE(rc2)) LogRel(("Shared Clipboard: Restarting X11 transfer preparation failed with %Rrc\n", rc2)); } + + return rcPreparation; } /** @@ -327,13 +332,15 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA * * @thread Clipboard main thread. */ -static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +static DECLCALLBACK(int) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; - AssertPtr(pCtx); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; - AssertPtr(pTransfer); + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) { @@ -346,14 +353,14 @@ static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALL if (RT_FAILURE(rc2)) LogRel(("Shared Clipboard: Canceling unbound transfer %RU16/%RU64 failed with %Rrc\n", ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer), rc2)); - return; + return VINF_SUCCESS; } /* The remote provider rejects root-list reads until ShClTransferInit() * has changed the transfer state to INITIALIZED. Registering the HTTP * transfer from pfnOnInitialize therefore races ahead of that state * transition and leaves URI-list conversion waiting forever. */ - int rc = ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx); + rc = ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx); if (RT_SUCCESS(rc)) rc = ShClTransferRootListRead(pTransfer); if (RT_SUCCESS(rc)) @@ -370,9 +377,11 @@ static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALL rc = ShClTransferHttpConvertToStringList(&pCtx->X11.HttpCtx.HttpServer, pTransfer, &pszUriList, &cbUriList); - vbclX11TransferStateComplete(pCtx, pTransfer, pszUriList, cbUriList, rc); + rc = vbclX11TransferStateComplete(pCtx, pTransfer, pszUriList, cbUriList, rc); RTStrFree(pszUriList); } + + return rc; } /** diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index ddc54adf874a..56e0da8a3907 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -5201,7 +5201,7 @@ int ShClTransferInit(PSHCLTRANSFER pTransfer) /* Note: Callback will be called after we unlocked the transfer, as the caller might access the transfer right away. */ if ( RT_SUCCESS(rc) && pTransfer->Callbacks.pfnOnInitialized) - pTransfer->Callbacks.pfnOnInitialized(&pTransfer->CallbackCtx); + rc = pTransfer->Callbacks.pfnOnInitialized(&pTransfer->CallbackCtx); if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Initialization of transfer failed with %Rrc\n", rc)); diff --git a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp index 8306d028cc6e..a32e1a8c53c4 100644 --- a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp +++ b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendWin.cpp 115053 2026-08-17 15:54:26Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendWin.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Win32 host. */ @@ -330,7 +330,7 @@ static DECLCALLBACK(int) shClSvcWinTransferOnInitializeCallback(PSHCLTRANSFERCAL * * @thread Clipboard main thread. */ -static DECLCALLBACK(void) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +static DECLCALLBACK(int) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { LogFlowFuncEnter(); @@ -358,6 +358,7 @@ static DECLCALLBACK(void) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERC } LogFlowFuncLeaveRC(vrc); + return vrc; } /** diff --git a/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp b/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp index 91aa16402682..8718bca288e1 100644 --- a/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp +++ b/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardMain2HostSvc.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardMain2HostSvc.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ /** @file * Main Shared Clipboard - Host Service integration testcase. */ @@ -50,7 +50,7 @@ * * The test covers registration, connect/sync/disconnect, the real service * transport, messages in both directions, guest-data reply ownership and one - * transfer handshake, including initialization and destruction callbacks. VM + * transfer handshake, including post-initialization error propagation. VM * construction, public Main API objects, native clipboard contents and transfer * data providers are outside its scope. */ @@ -118,6 +118,8 @@ typedef struct TSTSHCLSTATE /** State recorded while checking the post-initialization callback contract. */ typedef struct TSTTRANSFERINITIALIZED { + /** Result returned by the post-initialization callback. */ + int vrcCallback; /** Number of post-initialization callback invocations. */ uint32_t cInitialized; /** Number of destruction callback invocations. */ @@ -226,16 +228,17 @@ static int tstBackendSync(PSHCLCONTEXT pCtx) } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** Records the state observed by the post-initialization callback. */ -static DECLCALLBACK(void) tstTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +/** Records and returns the configured post-initialization result. */ +static DECLCALLBACK(int) tstTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { TSTTRANSFERINITIALIZED *pState = (TSTTRANSFERINITIALIZED *)pCbCtx->pvUser; - RTTESTI_CHECK_RETV(pState != NULL); - RTTESTI_CHECK_RETV(pCbCtx->cbUser == sizeof(*pState)); + RTTESTI_CHECK_RET(pState != NULL, VERR_INVALID_POINTER); + RTTESTI_CHECK_RET(pCbCtx->cbUser == sizeof(*pState), VERR_INVALID_PARAMETER); pState->cInitialized++; pState->fLockOwned = RTCritSectIsOwner(&pCbCtx->pTransfer->CritSect); pState->enmStatus = ShClTransferGetStatus(pCbCtx->pTransfer); + return pState->vrcCallback; } /** Records destruction of the transfer used by the callback contract test. */ @@ -573,17 +576,18 @@ static void tstGuestData(void *pvClient, GuestShClConn *pConn) #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** - * Checks transfer initialization and destruction across Main and the Host Service. + * Checks post-initialization callback failure propagation across Main and the Host Service. * * @param pvClient Connected HGCM client state. * @param pConn Connected Main service connection. */ -static void tstTransferInitialized(void *pvClient, GuestShClConn *pConn) +static void tstTransferInitializedResult(void *pvClient, GuestShClConn *pConn) { - RTTestISub("Transfer initialization"); + RTTestISub("Transfer initialized callback result"); TSTTRANSFERINITIALIZED State; RT_ZERO(State); + State.vrcCallback = VERR_SHCLPB_NO_DATA; SHCLTRANSFERCALLBACKS Callbacks; RT_ZERO(Callbacks); @@ -603,7 +607,7 @@ static void tstTransferInitialized(void *pvClient, GuestShClConn *pConn) SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); vrc = pConn->transferInit(pTransfer); - RTTESTI_CHECK_RC_OK(vrc); + RTTESTI_CHECK_RC(vrc, State.vrcCallback); RTTESTI_CHECK(State.cInitialized == 1); RTTESTI_CHECK(State.enmStatus == SHCLTRANSFERSTATUS_INITIALIZED); RTTESTI_CHECK(!State.fLockOwned); @@ -619,8 +623,8 @@ static void tstTransferInitialized(void *pvClient, GuestShClConn *pConn) RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(aStatus[0].u.uint64) == idSession); RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(aStatus[0].u.uint64) == idTransfer); RTTESTI_CHECK(aStatus[1].u.uint32 == SHCLTRANSFERDIR_FROM_REMOTE); - RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_INITIALIZED); - RTTESTI_CHECK((int32_t)aStatus[3].u.uint32 == VINF_SUCCESS); + RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_ERROR); + RTTESTI_CHECK((int32_t)aStatus[3].u.uint32 == State.vrcCallback); ShClTransferRelease(pTransfer); pConn->transferDestroyById(idTransfer); @@ -744,7 +748,7 @@ int main(void) tstFormats(pvClient, &Conn); tstGuestData(pvClient, &Conn); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - tstTransferInitialized(pvClient, &Conn); + tstTransferInitializedResult(pvClient, &Conn); tstTransfer(pvClient); #endif } From 28ca41f88270dd8724c9a649b462f67782405a53 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 16:58:23 +0000 Subject: [PATCH 151/176] Shared Clipboard: Propagated post-initialization errors [build fix]. bugref:4697 svn:sync-xref-src-repo-rev: r174899 --- src/VBox/Main/testcase/Makefile.kmk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VBox/Main/testcase/Makefile.kmk b/src/VBox/Main/testcase/Makefile.kmk index f4d69bf27e75..40093e90f412 100644 --- a/src/VBox/Main/testcase/Makefile.kmk +++ b/src/VBox/Main/testcase/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115058 2026-08-17 16:58:23Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the VBox API testcases. # @@ -351,7 +351,7 @@ tstClipboardAPI_SOURCES = \ $(PATH_OUT)/obj/Main/VBoxEvents.cpp \ tstClipboardAPI.cpp tstClipboardAPI_LIBS = \ - $(PATH_OUT)/lib/VBoxAPIWrap.a + $(PATH_STAGE_LIB)/VBoxAPIWrap$(VBOX_SUFF_LIB) ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS tstClipboardAPI_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS tstClipboardAPI_SOURCES += \ From f16ef99452ba88d3596eb6634d3a793375191ea0 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Mon, 17 Aug 2026 17:02:20 +0000 Subject: [PATCH 152/176] Devices/Graphics: cleanup resource view helpers. svn:sync-xref-src-repo-rev: r174900 --- .../Graphics/DevVGA-SVGA3d-dx-dx11.cpp | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp index 549e23d60396..55d2393ec2c3 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 115042 2026-08-15 14:51:14Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 115059 2026-08-17 17:02:20Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -1631,9 +1631,12 @@ static int dxViewDestroy(DXVIEW *pDXView) pDXView->cid, pDXView->sid, pDXView->viewId, pDXView->enmViewType)); if (pDXView->u.pView) { + pDXView->cid = SVGA3D_INVALID_ID; + pDXView->sid = SVGA3D_INVALID_ID; + pDXView->viewId = SVGA3D_INVALID_ID; + pDXView->enmViewType = VMSVGA3D_VIEWTYPE_NONE; D3D_RELEASE(pDXView->u.pView); RTListNodeRemove(&pDXView->nodeSurfaceView); - RT_ZERO(*pDXView); } return VINF_SUCCESS; @@ -1652,17 +1655,30 @@ static int dxViewInit(DXVIEW *pDXView, PVMSVGA3DSURFACE pSurface, VMSVGA3DDXCONT LogFunc(("cid = %u, sid = %u, viewId = %u, type = %u\n", pDXView->cid, pDXView->sid, pDXView->viewId, pDXView->enmViewType)); -DXVIEW *pIter, *pNext; -RTListForEachSafe(&pSurface->pBackendSurface->listView, pIter, pNext, DXVIEW, nodeSurfaceView) -{ - AssertPtr(pNext); - LogFunc(("pIter=%p, pNext=%p\n", pIter, pNext)); -} +#ifdef LOG_ENABLED + DXVIEW *pIter, *pNext; + RTListForEachSafe(&pSurface->pBackendSurface->listView, pIter, pNext, DXVIEW, nodeSurfaceView) + { + AssertPtr(pNext); + LogFunc(("pIter=%p, pNext=%p\n", pIter, pNext)); + } +#endif return VINF_SUCCESS; } +static void dxViewInit(DXVIEW *pDXView) +{ + pDXView->cid = SVGA3D_INVALID_ID; + pDXView->sid = SVGA3D_INVALID_ID; + pDXView->viewId = SVGA3D_INVALID_ID; + pDXView->enmViewType = VMSVGA3D_VIEWTYPE_NONE; + pDXView->u.pView = NULL; + RT_ZERO(pDXView->nodeSurfaceView); +} + + static DXDEVICE *dxDeviceGet(PVMSVGA3DSTATE p3dState) { DXDEVICE *pDXDevice = &p3dState->pBackend->dxDevice; @@ -11239,11 +11255,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D sizeof(pBackendDXContext->paRenderTargetView[0]), pDXContext->cot.cRTView, cValidEntries); AssertRCBreak(rc); - for (uint32_t i = 0; i < cValidEntries; ++i) + for (uint32_t i = 0; i < pBackendDXContext->cRenderTargetView; ++i) { DXVIEW *pDXView = &pBackendDXContext->paRenderTargetView[i]; - if (pDXView->u.pView) + if (i < cValidEntries && pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); } #endif break; @@ -11298,11 +11316,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D sizeof(pBackendDXContext->paDepthStencilView[0]), pDXContext->cot.cDSView, cValidEntries); AssertRCBreak(rc); - for (uint32_t i = 0; i < cValidEntries; ++i) + for (uint32_t i = 0; i < pBackendDXContext->cDepthStencilView; ++i) { DXVIEW *pDXView = &pBackendDXContext->paDepthStencilView[i]; - if (pDXView->u.pView) + if (i < cValidEntries && pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); } #endif break; @@ -11357,11 +11377,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D sizeof(pBackendDXContext->paShaderResourceView[0]), pDXContext->cot.cSRView, cValidEntries); AssertRCBreak(rc); - for (uint32_t i = 0; i < cValidEntries; ++i) + for (uint32_t i = 0; i < pBackendDXContext->cShaderResourceView; ++i) { DXVIEW *pDXView = &pBackendDXContext->paShaderResourceView[i]; - if (pDXView->u.pView) + if (i < cValidEntries && pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); } #endif break; @@ -11695,11 +11717,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D sizeof(pBackendDXContext->paUnorderedAccessView[0]), pDXContext->cot.cUAView, cValidEntries); AssertRCBreak(rc); - for (uint32_t i = 0; i < cValidEntries; ++i) + for (uint32_t i = 0; i < pBackendDXContext->cUnorderedAccessView; ++i) { DXVIEW *pDXView = &pBackendDXContext->paUnorderedAccessView[i]; - if (pDXView->u.pView) + if (i < cValidEntries && pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); } #endif break; @@ -12856,7 +12880,7 @@ static int dxCreateVideoDecoderOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEX return VERR_INVALID_PARAMETER; DXVIEW *pView = &pDXContext->pBackendDXContext->paVideoDecoderOutputView[videoDecoderOutputViewId]; - Assert(pView->u.pView == NULL); + AssertStmt(pView->u.pView == NULL, dxViewDestroy(pView)); D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC Desc; RT_ZERO(Desc); @@ -12887,7 +12911,7 @@ static int dxCreateVideoProcessorInputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTE return VERR_INVALID_PARAMETER; DXVIEW *pView = &pDXContext->pBackendDXContext->paVideoProcessorInputView[videoProcessorInputViewId]; - Assert(pView->u.pView == NULL); + AssertStmt(pView->u.pView == NULL, dxViewDestroy(pView)); D3D11_VIDEO_PROCESSOR_CONTENT_DESC ContentDesc; RT_ZERO(ContentDesc); @@ -12937,7 +12961,7 @@ static int dxCreateVideoProcessorOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONT return VERR_INVALID_PARAMETER; DXVIEW *pView = &pDXContext->pBackendDXContext->paVideoProcessorOutputView[videoProcessorOutputViewId]; - Assert(pView->u.pView == NULL); + AssertStmt(pView->u.pView == NULL, dxViewDestroy(pView)); D3D11_VIDEO_PROCESSOR_CONTENT_DESC ContentDesc; RT_ZERO(ContentDesc); From 25f60ac5735850a3ceb77be2ab77e4e3c58241e4 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 17:28:06 +0000 Subject: [PATCH 153/176] Shared Clipboard: Made transfer directions consistently describe guest-to-host or host-to-guest transfers. bugref:4697 svn:sync-xref-src-repo-rev: r174901 --- .../GuestHost/SharedClipboard-transfers.h | 4 +-- include/VBox/GuestHost/SharedClipboard.h | 14 +++++---- include/VBox/HostServices/VBoxClipboardSvc.h | 2 +- .../VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp | 29 +++++++------------ .../Additions/win/VBoxTray/VBoxClipboard.cpp | 14 ++++----- .../x11/VBoxClient/clipboard-x11.cpp | 12 ++++---- .../SharedClipboard/clipboard-common.cpp | 7 ++--- .../SharedClipboard/clipboard-transfers.cpp | 10 +++---- .../testcase/tstClipboardHttpServer.cpp | 8 ++--- .../VBoxSharedClipboardSvc-transfers.cpp | 10 +++---- .../testcase/tstClipboardHostService.cpp | 10 +++---- .../Main/src-client/ClipboardTransferImpl.cpp | 10 +++---- .../ClipboardTransferManagerImpl.cpp | 18 ++++++------ .../darwin/ClipboardBackendDarwin.cpp | 6 ++-- .../src-client/linux/ClipboardBackendX11.cpp | 18 ++++++------ .../src-client/win/ClipboardBackendWin.cpp | 20 ++++++------- src/VBox/Main/testcase/tstClipboardMain.cpp | 4 +-- .../testcase/tstClipboardMain2HostSvc.cpp | 6 ++-- 18 files changed, 98 insertions(+), 104 deletions(-) diff --git a/include/VBox/GuestHost/SharedClipboard-transfers.h b/include/VBox/GuestHost/SharedClipboard-transfers.h index 4d6bff9b826d..a37721f54cec 100644 --- a/include/VBox/GuestHost/SharedClipboard-transfers.h +++ b/include/VBox/GuestHost/SharedClipboard-transfers.h @@ -1,4 +1,4 @@ -/* $Id: SharedClipboard-transfers.h 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ +/* $Id: SharedClipboard-transfers.h 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Shared transfer functions between host and guest. */ @@ -612,7 +612,7 @@ typedef struct _SHCLTRANSFERSTATE SHCLTRANSFERGEN uGeneration; /** The transfer's current status. */ SHCLTRANSFERSTATUS enmStatus; - /** The transfer's direction, seen from the perspective who created the transfer. */ + /** The transfer's absolute guest/host direction. */ SHCLTRANSFERDIR enmDir; /** The transfer's source, seen from the perspective who created the transfer. */ SHCLSOURCE enmSource; diff --git a/include/VBox/GuestHost/SharedClipboard.h b/include/VBox/GuestHost/SharedClipboard.h index 481a24058019..438da6b2cb14 100644 --- a/include/VBox/GuestHost/SharedClipboard.h +++ b/include/VBox/GuestHost/SharedClipboard.h @@ -163,15 +163,18 @@ enum /** * Shared Clipboard transfer direction. + * + * The direction is absolute and therefore has the same meaning on the host and + * guest. The explicit values are part of the HGCM protocol and saved state. */ typedef enum SHCLTRANSFERDIR { - /** Unknown transfer directory. */ + /** Unknown transfer direction. */ SHCLTRANSFERDIR_UNKNOWN = 0, - /** Read transfer (from source). */ - SHCLTRANSFERDIR_FROM_REMOTE, - /** Write transfer (to target). */ - SHCLTRANSFERDIR_TO_REMOTE, + /** Guest-to-host transfer. */ + SHCLTRANSFERDIR_GUEST_TO_HOST = 1, + /** Host-to-guest transfer. */ + SHCLTRANSFERDIR_HOST_TO_GUEST = 2, /** The usual 32-bit hack. */ SHCLTRANSFERDIR_32BIT_HACK = 0x7fffffff } SHCLTRANSFERDIR; @@ -489,4 +492,3 @@ typedef struct SHCLCALLBACKS typedef SHCLCALLBACKS *PSHCLCALLBACKS; #endif /* !VBOX_INCLUDED_GuestHost_SharedClipboard_h */ - diff --git a/include/VBox/HostServices/VBoxClipboardSvc.h b/include/VBox/HostServices/VBoxClipboardSvc.h index 47fe964656c4..a2acba67fe73 100644 --- a/include/VBox/HostServices/VBoxClipboardSvc.h +++ b/include/VBox/HostServices/VBoxClipboardSvc.h @@ -884,7 +884,7 @@ typedef struct _VBoxShClTransferStatusMsg /** uint64_t, out: Context ID. */ HGCMFunctionParameter uContext; - /** uint32_t, out: Direction of transfer; of type SHCLTRANSFERDIR_. */ + /** uint32_t, out: Absolute guest/host transfer direction; of type SHCLTRANSFERDIR_. */ HGCMFunctionParameter enmDir; /** uint32_t, out: Status to report; of type SHCLTRANSFERSTATUS_. */ HGCMFunctionParameter enmStatus; diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp index 245904b0a17c..0c2ca029af9f 100644 --- a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxGuestR3LibClipboard.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxGuestR3LibClipboard.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared Clipboard. */ @@ -2240,9 +2240,8 @@ static int vbglR3ClipboardTransferInit(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFER /* Assign local provider first and overwrite interface methods below if needed. */ ShClTransferProviderLocalQueryInterface(&Provider); - /* If this is a read transfer (reading data from host), set the interface to use - * our VbglR3 routines here. */ - if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) /* Host -> Guest */ + /* Host-to-guest transfers use the remote HGCM provider. */ + if (enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST) { Provider.Interface.pfnRootListRead = vbglR3ClipboardTransferIfaceHGRootListRead; @@ -2255,7 +2254,7 @@ static int vbglR3ClipboardTransferInit(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFER Provider.Interface.pfnObjClose = vbglR3ClipboardTransferIfaceHGObjClose; Provider.Interface.pfnObjRead = vbglR3ClipboardTransferIfaceHGObjRead; } - else if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) /* Guest -> Host */ + else if (enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST) { /* Uses the local provider assigned above. */ } @@ -2275,7 +2274,7 @@ static int vbglR3ClipboardTransferInit(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFER if (RT_SUCCESS(rc)) { LogRel2(("Shared Clipboard: Transfer %RU32 (%s) successfully initialized\n", - idTransfer, enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "host -> guest" : "guest -> host")); + idTransfer, enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST ? "host -> guest" : "guest -> host")); } else LogRel(("Shared Clipboard: Unable to initialize transfer %RU16, rc=%Rrc\n", idTransfer, rc)); @@ -2425,7 +2424,7 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, { case SHCLTRANSFERSTATUS_REQUESTED: /* Only used for H->G transfers. */ { - enmDir = SHCLTRANSFERDIR_FROM_REMOTE; + enmDir = SHCLTRANSFERDIR_HOST_TO_GUEST; enmSource = SHCLSOURCE_REMOTE; /* The host acknowledged our request to create a new transfer. @@ -2449,21 +2448,15 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, case SHCLTRANSFERSTATUS_INITIALIZED: { - /* The host announces the transfer direction from its point of view, so inverse the direction here. */ - if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) /* H -> G */ - { - enmDir = SHCLTRANSFERDIR_FROM_REMOTE; + /* Directions are absolute on the wire; only the source is endpoint-relative. */ + if (enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST) enmSource = SHCLSOURCE_REMOTE; - } - else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) /* G -> H */ - { - enmDir = SHCLTRANSFERDIR_TO_REMOTE; + else if (enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST) enmSource = SHCLSOURCE_LOCAL; - } else AssertFailedBreakStmt(rc = VERR_INVALID_PARAMETER); - if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */ + if (enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST) { /* The host reported INITIALIZED for the transfer. * So init our local transfer as well now. */ @@ -2486,7 +2479,7 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, else rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; } - else if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) /* G->H */ + else if (enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST) { /* The host reported the INITIALIZED status together with the transfer ID. * So create a local transfer here with that ID. */ diff --git a/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp b/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp index 9a5f422c8880..12a8ea603e08 100644 --- a/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp +++ b/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxClipboard.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxClipboard.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * VBoxClipboard - Shared clipboard, Windows Guest Implementation. */ @@ -205,13 +205,13 @@ static DECLCALLBACK(int) vbtrShClTransferInitializeCallback(PSHCLTRANSFERCALLBAC switch(ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { rc = ShClWinTransferInitialize(&pCtx->Win, pTransfer); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { rc = ShClWinTransferGetRootsFromClipboard(&pCtx->Win, pTransfer); break; @@ -248,13 +248,13 @@ static DECLCALLBACK(int) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBA switch(ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { rc = ShClWinTransferStart(&pCtx->Win, pTransfer); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: break; default: @@ -325,11 +325,11 @@ static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTRANSFERCALLBACKC int rc = VINF_SUCCESS; /* The guest wants to transfer data to the host. */ - if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) /* G->H */ + if (enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST) { rc = ShClWinTransferGetRootsFromClipboard(&pCtx->Win, pTransfer); } - else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */ + else if (enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST) { /* Nothing to do here. */ } diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index 813ffbf0c372..599b301ff125 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-x11.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -298,7 +298,7 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA * will start reading those as soon as we report the INITIALIZED status. */ switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { void *pvData; uint32_t cbData; @@ -312,7 +312,7 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA break; } - case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: break; default: @@ -342,7 +342,7 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALLB int rc = VINF_SUCCESS; - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST) { if (!vbclX11TransferStateMatches(pCtx, pTransfer)) { @@ -406,7 +406,7 @@ static DECLCALLBACK(void) vbclX11OnTransferRegisteredCallback(PSHCLTRANSFERCALLB AssertPtr(pTransfer); /* We only need to start the HTTP server when we actually receive data from the remote (host). */ - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */ + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST) { PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; /* H->G requests are serialized by fPreparing. The first H->G @@ -444,7 +444,7 @@ static DECLCALLBACK(void) vbclX11OnTransferRegisteredCallback(PSHCLTRANSFERCALLB */ static void vbclX11TransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer) { - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST) { if (ShClTransferHttpServerIsInitialized(&pCtx->X11.HttpCtx.HttpServer)) { diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp index 86c8b83e6ff5..df8232be4f48 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-common.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-common.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common helper objects. */ @@ -814,8 +814,8 @@ VBGH_DECL(bool) ShClFormatsAreValid(SHCLFORMATS fFormats) */ VBGH_DECL(bool) ShClTransferDirIsValid(SHCLTRANSFERDIR enmDir) { - return enmDir == SHCLTRANSFERDIR_FROM_REMOTE - || enmDir == SHCLTRANSFERDIR_TO_REMOTE; + return enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST + || enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST; } @@ -1175,4 +1175,3 @@ VBGH_DECL(int) ShClCacheTransferAll(PSHCLCACHE pCache, PSHCLCACHE pOtherCache) } return VINF_SUCCESS; } - diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index 56e0da8a3907..67e377f66aa3 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -2198,10 +2198,10 @@ int ShClTransferRootsSetFromStringListEx(PSHCLTRANSFER pTransfer, const char *ps PSHCLFSOBJINFO pFsObjInfo = (PSHCLFSOBJINFO)RTMemAllocZ(sizeof(SHCLFSOBJINFO)); if (pFsObjInfo) { - if (pTransfer->State.enmDir == SHCLTRANSFERDIR_TO_REMOTE) + if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL) rc = ShClFsObjInfoQueryLocal(pszPathCur, pFsObjInfo); if ( RT_SUCCESS(rc) - && pTransfer->State.enmDir == SHCLTRANSFERDIR_TO_REMOTE) + && pTransfer->State.enmSource == SHCLSOURCE_LOCAL) { if ( !RTFS_IS_DIRECTORY(pFsObjInfo->Attr.fMode) && !RTFS_IS_FILE(pFsObjInfo->Attr.fMode)) @@ -5189,7 +5189,7 @@ int ShClTransferInit(PSHCLTRANSFER pTransfer) { /* Sanity: Make sure that the transfer we're gonna report as INITIALIZED * actually has some root entries set, as the other side can query for those at any time then. */ - if (pTransfer->State.enmDir == SHCLTRANSFERDIR_TO_REMOTE) + if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL) AssertMsgStmt(ShClTransferRootsCount(pTransfer), ("Transfer has no root entries set (yet)\n"), rc = VERR_WRONG_ORDER); if (RT_SUCCESS(rc)) @@ -5240,7 +5240,7 @@ int ShClSvcTransferInit(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) SHCLTRANSFERDIR const enmDir = ShClTransferGetDir(pTransfer); LogRel2(("Shared Clipboard: Initializing %s transfer ...\n", - enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "guest -> host" : "host -> guest")); + enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST ? "guest -> host" : "host -> guest")); rc = ShClTransferInit(pTransfer); } diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp index 413b5e0982cd..92946f68d7fa 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardHttpServer.cpp 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardHttpServer.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard HTTP server test case. */ @@ -120,7 +120,7 @@ static int tstCreateTransferSingle(RTTEST hTest, PSHCLTRANSFERCTX pTransferCtx, do { - RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, + RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferCreate(SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTx)); RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferSetProvider(pTx, pProvider)); RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferRootsSetFromPath(pTx, pszPath)); @@ -215,7 +215,7 @@ static void tstDuplicateTransferRegistration(RTTEST hTest, PSHCLTRANSFERCTX pTra do { - RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, + RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferCreate(SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTx)); RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferSetProvider(pTx, pProvider)); RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferRootsSetFromPath(pTx, pszPath)); @@ -444,7 +444,7 @@ static int tstCreateRegisteredTransfer(PSHCLTRANSFERCTX pTransferCtx, PSHCLHTTPS bool fCtxRegistered = false; bool fHttpRegistered = false; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); + int rc = ShClTransferCreate(SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); if (RT_SUCCESS(rc)) rc = ShClTransferSetProvider(pTransfer, pProvider); if (RT_SUCCESS(rc)) diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp index e458ac09a335..38417c7d8ab4 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal code for transfer (list) handling. */ @@ -740,7 +740,7 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra rc = VINF_SUCCESS; } if (RT_SUCCESS(rc)) - rc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, + rc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, &Callbacks, NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); @@ -773,11 +773,11 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra { switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: /* Already done locally when creating the transfer. */ break; - case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { /* Initialize the transfer on the host side. */ rc = ShClSvcTransferInit(pClient, pTransfer); @@ -795,7 +795,7 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra { /* We only need to start for H->G transfers here. * For G->H transfers we start this as soon as the host clipboard requests data. */ - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_TO_REMOTE) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST) { /* Start the transfer on the host side. */ rc = ShClSvcTransferStart(pClient, pTransfer); diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp index e42b9546e007..5e3316265cde 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardHostService.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardHostService.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Host Service testcase. */ @@ -744,7 +744,7 @@ static void tstTransferStatusGet(void *pvClient, SHCLSESSIONID idSession, SHCLTR RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(uContext) == idTransfer); RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != 0); RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != NIL_SHCLEVENTID); - RTTESTI_CHECK(aParms[1].u.uint32 == SHCLTRANSFERDIR_TO_REMOTE); + RTTESTI_CHECK(aParms[1].u.uint32 == SHCLTRANSFERDIR_HOST_TO_GUEST); RTTESTI_CHECK(aParms[2].u.uint32 == enmStatus); RTTESTI_CHECK((int32_t)aParms[3].u.uint32 == rcTransfer); RTTESTI_CHECK(aParms[4].u.uint32 == 0); @@ -776,7 +776,7 @@ static void tstTransfers(void *pvClient) rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); RTTESTI_CHECK_RC(rc, VINF_SUCCESS); PSHCLTRANSFER pDeniedTransfer = (PSHCLTRANSFER)(uintptr_t)1; - rc = g_Ext.Transport.pOps->pfnTransferCreate(g_Ext.Transport.hClient, SHCLTRANSFERDIR_FROM_REMOTE, + rc = g_Ext.Transport.pOps->pfnTransferCreate(g_Ext.Transport.hClient, SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, NULL, NIL_SHCLTRANSFERID, &pDeniedTransfer); RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); RTTESTI_CHECK(pDeniedTransfer == NULL); @@ -794,7 +794,7 @@ static void tstTransfers(void *pvClient) rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); RTTESTI_CHECK_RC(rc, VINF_SUCCESS); pDeniedTransfer = (PSHCLTRANSFER)(uintptr_t)1; - rc = g_Ext.Transport.pOps->pfnTransferCreate(g_Ext.Transport.hClient, SHCLTRANSFERDIR_FROM_REMOTE, + rc = g_Ext.Transport.pOps->pfnTransferCreate(g_Ext.Transport.hClient, SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, NULL, NIL_SHCLTRANSFERID, &pDeniedTransfer); RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); RTTESTI_CHECK(pDeniedTransfer == NULL); @@ -835,7 +835,7 @@ static void tstTransfers(void *pvClient) RTTESTI_CHECK(idSession != NIL_SHCLSESSIONID); RTTESTI_CHECK(g_Ext.idTransfer != NIL_SHCLTRANSFERID); RTTESTI_CHECK(g_Ext.uTransferGeneration != NIL_SHCLTRANSFERGEN); - RTTESTI_CHECK(g_Ext.enmTransferDir == SHCLTRANSFERDIR_TO_REMOTE); + RTTESTI_CHECK(g_Ext.enmTransferDir == SHCLTRANSFERDIR_HOST_TO_GUEST); RTTESTI_CHECK(g_Ext.enmTransferSource == SHCLSOURCE_REMOTE); RTTESTI_CHECK(g_Ext.enmTransferStatus == SHCLTRANSFERSTATUS_REQUESTED); RTTESTI_CHECK(g_Ext.rcTransfer == VINF_SUCCESS); diff --git a/src/VBox/Main/src-client/ClipboardTransferImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferImpl.cpp index e02216c7b952..da6fab345400 100644 --- a/src/VBox/Main/src-client/ClipboardTransferImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferImpl.cpp 114975 2026-08-10 18:04:13Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferImpl.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer object. */ @@ -101,9 +101,9 @@ static ClipboardTransferDirection_T clipboardTransferDirectionFromShCl(SHCLTRANS { switch (enmDir) { - case SHCLTRANSFERDIR_TO_REMOTE: return ClipboardTransferDirection_ToGuest; - case SHCLTRANSFERDIR_FROM_REMOTE: return ClipboardTransferDirection_ToHost; - default: return ClipboardTransferDirection_Any; + case SHCLTRANSFERDIR_HOST_TO_GUEST: return ClipboardTransferDirection_ToGuest; + case SHCLTRANSFERDIR_GUEST_TO_HOST: return ClipboardTransferDirection_ToHost; + default: return ClipboardTransferDirection_Any; } } @@ -229,7 +229,7 @@ static HRESULT clipboardTransferCreateLocalProviderBackend(const std::vectormDirection == SHCLTRANSFERDIR_FROM_REMOTE + = it->mDirection == SHCLTRANSFERDIR_GUEST_TO_HOST ? ClipboardTransferDirection_ToHost : ClipboardTransferDirection_ToGuest; if (enmDirection != aDirection) continue; @@ -376,7 +376,7 @@ HRESULT ClipboardTransferManager::create(ClipboardTransferDirection_T aDirection Data::TransferRecord Record; Record.mTransferId = idTransfer; Record.mDirection = aDirection == ClipboardTransferDirection_ToHost - ? SHCLTRANSFERDIR_FROM_REMOTE : SHCLTRANSFERDIR_TO_REMOTE; + ? SHCLTRANSFERDIR_GUEST_TO_HOST : SHCLTRANSFERDIR_HOST_TO_GUEST; Record.mSource = aSource == ClipboardSource_Host ? SHCLSOURCE_LOCAL : aSource == ClipboardSource_Guest ? SHCLSOURCE_REMOTE : SHCLSOURCE_INVALID; @@ -924,17 +924,17 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS else { enmTransferDirection = enmShClSource == SHCLSOURCE_REMOTE - ? SHCLTRANSFERDIR_FROM_REMOTE : SHCLTRANSFERDIR_TO_REMOTE; + ? SHCLTRANSFERDIR_GUEST_TO_HOST : SHCLTRANSFERDIR_HOST_TO_GUEST; enmTransferSource = enmShClSource; } - if ( ( enmTransferDirection != SHCLTRANSFERDIR_FROM_REMOTE - && enmTransferDirection != SHCLTRANSFERDIR_TO_REMOTE) + if ( ( enmTransferDirection != SHCLTRANSFERDIR_GUEST_TO_HOST + && enmTransferDirection != SHCLTRANSFERDIR_HOST_TO_GUEST) || ( enmTransferSource != SHCLSOURCE_LOCAL && enmTransferSource != SHCLSOURCE_REMOTE) || ( enmTransferSource == SHCLSOURCE_LOCAL - && enmTransferDirection != SHCLTRANSFERDIR_TO_REMOTE) + && enmTransferDirection != SHCLTRANSFERDIR_HOST_TO_GUEST) || ( enmTransferSource == SHCLSOURCE_REMOTE - && enmTransferDirection != SHCLTRANSFERDIR_FROM_REMOTE)) + && enmTransferDirection != SHCLTRANSFERDIR_GUEST_TO_HOST)) return E_INVALIDARG; if (enmStatus == SHCLTRANSFERSTATUS_NONE) return S_OK; @@ -998,7 +998,7 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS if (FAILED(hrc)) return hrc; - ClipboardTransferDirection_T const enmDirection = enmTransferDirection == SHCLTRANSFERDIR_FROM_REMOTE + ClipboardTransferDirection_T const enmDirection = enmTransferDirection == SHCLTRANSFERDIR_GUEST_TO_HOST ? ClipboardTransferDirection_ToHost : ClipboardTransferDirection_ToGuest; ClipboardSource_T const enmSource = enmTransferSource == SHCLSOURCE_REMOTE diff --git a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp index c79dcb755087..44b9370ec0bf 100644 --- a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp +++ b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendDarwin.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendDarwin.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host. */ @@ -119,7 +119,7 @@ static DECLCALLBACK(void) shClSvcDarwinTransferOnCreatedCallback(PSHCLTRANSFERCA PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtrReturnVoid(pTransfer); - if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_TO_REMOTE + if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST && ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL) { SHCLTXPROVIDER Provider; @@ -142,7 +142,7 @@ static DECLCALLBACK(int) shClSvcDarwinTransferOnInitializeCallback(PSHCLTRANSFER PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); - if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_TO_REMOTE + if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST && ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL) return ShClTransferRootListRead(pTransfer); return VERR_NOT_SUPPORTED; diff --git a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp index 647ae0aa71cd..f4a37b7d06d5 100644 --- a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp +++ b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendX11.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendX11.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - X11 backend. */ @@ -713,7 +713,7 @@ static int shClSvcX11TransferPrepare(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats, ui { SHCLTRANSFERCALLBACKS Callbacks; shClBackendX11TransferGetCallbacks(pCtx, &Callbacks); - vrc = pCtx->pConn->transferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, &Callbacks, + vrc = pCtx->pConn->transferCreate(SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, &Callbacks, NIL_SHCLTRANSFERID, &pTransfer); } @@ -935,13 +935,13 @@ static DECLCALLBACK(void) shClSvcX11TransferOnCreatedCallback(PSHCLTRANSFERCALLB switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* Guest -> Host. */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { vrc = pCtx->pConn->transferProviderInitGuest(&Provider); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* Host -> Guest. */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { ShClTransferProviderLocalQueryInterface(&Provider); Provider.Interface.pfnRootListRead = shClSvcX11TransferIfaceHGRootListRead; @@ -986,7 +986,7 @@ static DECLCALLBACK(int) shClSvcX11TransferOnInitCallback(PSHCLTRANSFERCALLBACKC switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { # ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP /* We only need to start the HTTP server when we actually receive data from the remote (host). */ @@ -995,7 +995,7 @@ static DECLCALLBACK(int) shClSvcX11TransferOnInitCallback(PSHCLTRANSFERCALLBACKC break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { vrc = ShClTransferRootListRead(pTransfer); /* Calls shClSvcX11TransferIfaceHGRootListRead(). */ break; @@ -1028,7 +1028,7 @@ static DECLCALLBACK(void) shClSvcX11TransferOnDestroyCallback(PSHCLTRANSFERCALLB PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_GUEST_TO_HOST) ShClTransferHttpServerMaybeStop(&pCtx->X11.HttpCtx); # else RT_NOREF(pCbCtx); @@ -1049,7 +1049,7 @@ static DECLCALLBACK(void) shClSvcX11TransferOnDestroyCallback(PSHCLTRANSFERCALLB */ static void shClSvcX11HttpTransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer) { - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_GUEST_TO_HOST) { # ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP if (ShClTransferHttpServerIsInitialized(&pCtx->X11.HttpCtx.HttpServer)) @@ -1161,7 +1161,7 @@ static int shClBackendX11TransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANS AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); RT_NOREF(enmSource, rcStatus); - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) /* Guest -> Host */ + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_GUEST_TO_HOST) { switch (enmStatus) { diff --git a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp index a32e1a8c53c4..b886d4bdd976 100644 --- a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp +++ b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendWin.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendWin.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Win32 host. */ @@ -247,13 +247,13 @@ static DECLCALLBACK(void) shClSvcWinTransferOnCreatedCallback(PSHCLTRANSFERCALLB switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { vrc = pCtx->pConn->transferProviderInitGuest(&Provider); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { ShClTransferProviderLocalQueryInterface(&Provider); Provider.Interface.pfnRootListRead = shClSvcWinTransferIfaceHGRootListRead; @@ -300,13 +300,13 @@ static DECLCALLBACK(int) shClSvcWinTransferOnInitializeCallback(PSHCLTRANSFERCAL switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { vrc = ShClWinTransferInitialize(&pCtx->Win, pTransfer); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { vrc = ShClTransferRootListRead(pTransfer); /* Calls shClSvcWinTransferIfaceHGRootListRead(). */ break; @@ -325,8 +325,8 @@ static DECLCALLBACK(int) shClSvcWinTransferOnInitializeCallback(PSHCLTRANSFERCAL * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialized * * Called by ShClTransferInit via VbglR3. - * For H->G: Called on transfer intialization to start the data transfer for the "in-flight" IDataObject. - * For G->H: Nothing to do here. + * For G->H: Starts the data transfer for the "in-flight" IDataObject. + * For H->G: Nothing to do here. * * @thread Clipboard main thread. */ @@ -344,13 +344,13 @@ static DECLCALLBACK(int) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERCA switch(ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { vrc = ShClWinTransferStart(&pCtx->Win, pTransfer); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: break; default: @@ -417,7 +417,7 @@ static DECLCALLBACK(int) shClSvcWinDataObjectTransferBeginCallback(ShClWinDataOb shClBackendWinTransferGetCallbacks(pCtx, &Callbacks); PSHCLTRANSFER pTransfer; - int vrc = pCtx->pConn->transferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, &Callbacks, + int vrc = pCtx->pConn->transferCreate(SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, &Callbacks, NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); if (RT_SUCCESS(vrc)) { diff --git a/src/VBox/Main/testcase/tstClipboardMain.cpp b/src/VBox/Main/testcase/tstClipboardMain.cpp index 1a5958988b9c..4777dd55f88b 100644 --- a/src/VBox/Main/testcase/tstClipboardMain.cpp +++ b/src/VBox/Main/testcase/tstClipboardMain.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardMain.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardMain.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Main Shared Clipboard - Connection and service-extension testcase. */ @@ -871,7 +871,7 @@ static void tstTransfers(void) RTTESTI_CHECK(Callbacks.pvUser == &g_State); pTransfer = NULL; - RTTESTI_CHECK_RC(Conn.transferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, &Callbacks, + RTTESTI_CHECK_RC(Conn.transferCreate(SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, &Callbacks, TST_SHCL_TRANSFER_ID, &pTransfer), VINF_SUCCESS); RTTESTI_CHECK(pTransfer == &g_State.Transfer); if (pTransfer) diff --git a/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp b/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp index 8718bca288e1..9fef9c091f47 100644 --- a/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp +++ b/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardMain2HostSvc.cpp 115057 2026-08-17 16:48:01Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardMain2HostSvc.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Main Shared Clipboard - Host Service integration testcase. */ @@ -597,7 +597,7 @@ static void tstTransferInitializedResult(void *pvClient, GuestShClConn *pConn) Callbacks.cbUser = sizeof(State); PSHCLTRANSFER pTransfer = NULL; - int vrc = pConn->transferCreate(SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, &Callbacks, + int vrc = pConn->transferCreate(SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, &Callbacks, NIL_SHCLTRANSFERID, &pTransfer); RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); if (RT_FAILURE(vrc)) @@ -622,7 +622,7 @@ static void tstTransferInitializedResult(void *pvClient, GuestShClConn *pConn) RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(aStatus[0].u.uint64) == idSession); RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(aStatus[0].u.uint64) == idTransfer); - RTTESTI_CHECK(aStatus[1].u.uint32 == SHCLTRANSFERDIR_FROM_REMOTE); + RTTESTI_CHECK(aStatus[1].u.uint32 == SHCLTRANSFERDIR_GUEST_TO_HOST); RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_ERROR); RTTESTI_CHECK((int32_t)aStatus[3].u.uint32 == State.vrcCallback); From ef26ee773b12be5d4ac20d065661fb82692c8b03 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Mon, 17 Aug 2026 17:42:59 +0000 Subject: [PATCH 154/176] =?UTF-8?q?Shared=20Clipboard:=20VRDE=20clipboard?= =?UTF-8?q?=20traffic=20routing=20now=20goes=20directly=20through=20Main?= =?UTF-8?q?=20instead=20to=20the=20not=20required=20host=20service=20detou?= =?UTF-8?q?r=20anymore=20[build=20fix].=20=E2=80=8Bbugref:4697?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn:sync-xref-src-repo-rev: r174902 --- src/VBox/Main/src-client/ConsoleVRDPServer.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/VBox/Main/src-client/ConsoleVRDPServer.cpp b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp index 948108b120b7..a25d76caef22 100644 --- a/src/VBox/Main/src-client/ConsoleVRDPServer.cpp +++ b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp @@ -1,4 +1,4 @@ -/* $Id: ConsoleVRDPServer.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ +/* $Id: ConsoleVRDPServer.cpp 115061 2026-08-17 17:42:59Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console VRDP helper class. */ @@ -3302,9 +3302,9 @@ void ConsoleVRDPServer::unlockConsoleVRDPServer(void) * * @returns VBox status code. * @param pvCallback ConsoleVRDPServer instance receiving the request. - * @param u32ClientId Remote client ID. - * @param u32Function VRDE_CLIPBOARD_FUNCTION_XXX request number. - * @param u32Format Clipboard format associated with the request. + * @param u32ClientId Remote client ID. + * @param u32Function VRDE_CLIPBOARD_FUNCTION_XXX request number. + * @param u32Format Clipboard format associated with the request. * @param pvData Request data. Optional if @a cbData is zero. * @param cbData Request data size in bytes. * @@ -3317,6 +3317,8 @@ DECLCALLBACK(int) ConsoleVRDPServer::ClipboardCallback(void *pvCallback, const void *pvData, uint32_t cbData) { + RT_NOREF(u32Format); + LogFlowFunc(("pvCallback = %p, u32ClientId = %d, u32Function = %d, u32Format = 0x%08X, pvData = %p, cbData = %d\n", pvCallback, u32ClientId, u32Function, u32Format, pvData, cbData)); From c961b33c1a122c687268046346e8c2d58914cd5b Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Tue, 18 Aug 2026 07:52:42 +0000 Subject: [PATCH 155/176] ValidationKit/bootsectors/bs3kit: Add Bs3SelSetup32BitData() in order to set up a 32-bit GDT entry svn:sync-xref-src-repo-rev: r174903 --- .../bootsectors/bs3kit/Makefile.kmk | 3 +- .../bs3kit/bs3-cmn-SelSetup32BitData.c | 62 +++++++++++++++++++ .../bs3kit/bs3kit-mangling-code-define.h | 3 +- .../bs3kit/bs3kit-mangling-code-undef.h | 3 +- .../ValidationKit/bootsectors/bs3kit/bs3kit.h | 14 ++++- 5 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitData.c diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk b/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk index b5c1078703ec..9d1f52db6e4a 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ ## @file # VirtualBox Validation Kit - Bootsector Kit v3 # @@ -204,6 +204,7 @@ VBOX_BS3KIT_COMMON_SOURCES = \ bs3-cmn-SelLnkPtrToFlat.c \ bs3-cmn-SelSetup16BitData.c \ bs3-cmn-SelSetup16BitCode.c \ + bs3-cmn-SelSetup32BitData.c \ bs3-cmn-SelSetup32BitCode.c \ bs3-cmn-SelSetupGate.c \ bs3-cmn-SelSetupGate64.c \ diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitData.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitData.c new file mode 100644 index 000000000000..14833c8beed2 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitData.c @@ -0,0 +1,62 @@ +/* $Id: bs3-cmn-SelSetup32BitData.c 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ */ +/** @file + * BS3Kit - Bs3SelSetup32BitData + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + + +#undef Bs3SelSetup32BitData +BS3_CMN_DEF(void, Bs3SelSetup32BitData,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint32_t uLimit, uint8_t bDpl)) +{ + uint8_t const cLimitShift = uLimit <= UINT32_C(0xfffff) ? 0 : 12; + pDesc->Gen.u16LimitLow = (uint16_t)(uLimit >> cLimitShift); + pDesc->Gen.u16BaseLow = (uint16_t)uBaseAddr; + pDesc->Gen.u8BaseHigh1 = (uint8_t)(uBaseAddr >> 16); + pDesc->Gen.u4Type = X86_SEL_TYPE_RW_ACC; + pDesc->Gen.u1DescType = 1; /* data/code */ + pDesc->Gen.u2Dpl = bDpl & 3; + pDesc->Gen.u1Present = 1; + pDesc->Gen.u4LimitHigh = (unsigned)(uLimit >> (16 + cLimitShift)); + pDesc->Gen.u1Available = 0; + pDesc->Gen.u1Long = 0; + pDesc->Gen.u1DefBig = 1; + pDesc->Gen.u1Granularity = cLimitShift != 0; + pDesc->Gen.u8BaseHigh2 = (uint8_t)(uBaseAddr >> 24); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h index c0b3e1757670..3bdbdc390859 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h @@ -1,4 +1,4 @@ -/* $Id: bs3kit-mangling-code-define.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: bs3kit-mangling-code-define.h 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - Function needing mangling - generated by the bs3kit-mangling-code-define.h makefile rule. */ @@ -175,6 +175,7 @@ #define Bs3SelRealModeDataToProtFar16 BS3_CMN_MANGLER(Bs3SelRealModeDataToProtFar16) #define Bs3SelSetup16BitCode BS3_CMN_MANGLER(Bs3SelSetup16BitCode) #define Bs3SelSetup16BitData BS3_CMN_MANGLER(Bs3SelSetup16BitData) +#define Bs3SelSetup32BitData BS3_CMN_MANGLER(Bs3SelSetup32BitData) #define Bs3SelSetup32BitCode BS3_CMN_MANGLER(Bs3SelSetup32BitCode) #define Bs3SelSetupGate BS3_CMN_MANGLER(Bs3SelSetupGate) #define Bs3SelSetupGate64 BS3_CMN_MANGLER(Bs3SelSetupGate64) diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h index 2ef988101fcd..a61619b3be03 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h @@ -1,4 +1,4 @@ -/* $Id: bs3kit-mangling-code-undef.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: bs3kit-mangling-code-undef.h 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - Undefining function mangling - automatically generated by the bs3kit-mangling-code-undef.h makefile rule. */ @@ -175,6 +175,7 @@ #undef Bs3SelRealModeDataToProtFar16 #undef Bs3SelSetup16BitCode #undef Bs3SelSetup16BitData +#undef Bs3SelSetup32BitData #undef Bs3SelSetup32BitCode #undef Bs3SelSetupGate #undef Bs3SelSetupGate64 diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h index 1290843e2a62..22b8c3e235cf 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h @@ -1,4 +1,4 @@ -/* $Id: bs3kit.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: bs3kit.h 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - structures, symbols, macros and stuff. */ @@ -2099,6 +2099,18 @@ BS3_CMN_PROTO_STUB(void, Bs3SelSetup16BitData,(X86DESC BS3_FAR *pDesc, uint32_t */ BS3_CMN_PROTO_STUB(void, Bs3SelSetup16BitCode,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint8_t bDpl)); +/** + * Sets up a 32-bit read-write selector with a user specified limit. + * + * @param pDesc Pointer to the descriptor table entry. + * @param uBaseAddr The base address of the descriptor. + * @param uLimit The limit. (This is included here and not in the 16-bit + * functions because we're more likely to want to set it + * than for 16-bit selectors.) + * @param bDpl The descriptor privilege level. + */ +BS3_CMN_PROTO_STUB(void, Bs3SelSetup32BitData,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint32_t uLimit, uint8_t bDpl)); + /** * Sets up a 32-bit execute-read selector with a user specified limit. * From ae91c269cffd56a12dd7da75c62f0be8c97e42d4 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Tue, 18 Aug 2026 07:54:16 +0000 Subject: [PATCH 156/176] ValidationKit/bootsectors/bs3-apic-1: Optimize the interrupt handling by using the gs segment selector to get at the per CPU data instead of reading a bunch of APIC registers. This should reflect real guests workloads better for benchmarking svn:sync-xref-src-repo-rev: r174904 --- .../bootsectors/bs3-apic-1-32.c32 | 27 +++++++++++++++++-- .../bootsectors/bs3-apic-1-asm.asm | 25 ++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 index c135b461ac0a..5b0936aebad1 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 +++ b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 @@ -1,4 +1,4 @@ -/* $Id: bs3-apic-1-32.c32 114765 2026-07-24 08:34:04Z alexander.eichner@oracle.com $ */ +/* $Id: bs3-apic-1-32.c32 115063 2026-08-18 07:54:16Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - bs3-apic-1, 32-bit C code. */ @@ -90,6 +90,8 @@ typedef enum APICCPUREQ */ typedef struct APICCPU { + /** Pointer to this CPU. */ + struct APICCPU *pThis; /** The stack for this CPU. */ uint8_t abStack[CPU_STACK_SIZE]; /** The MMIO base for the APIC when in xAPIC mode. */ @@ -105,13 +107,15 @@ typedef struct APICCPU /** Flag whether the APIC is in x2Apic mode. */ bool fX2Apic; /** Padding. */ - bool afAlignment[11]; + bool afAlignment[7]; } APICCPU; typedef APICCPU *PAPICCPU; AssertCompileSizeAlignment(APICCPU, 16); BS3_DECL_CALLBACK(void) bs3ApicApTrampoline(void); BS3_DECL_CALLBACK(void) bs3ApicApTrampoline_EndProc(void); +BS3_DECL(void) bs3ApicApSetGs(uint16_t u16Gs); +BS3_DECL(PAPICCPU) bs3ApicApGetCpu(void); /** The AP lock protecting against concurrent access to some APIs and the startup code (lives in the DATA16 segment). */ #define g_fApLock BS3_DATA_NM(g_fApLock) @@ -223,6 +227,7 @@ static void bs3ApicTestFailedF(const char BS3_FAR *pszFormat, ...) static PAPICCPU bs3ApicGetCpu(void) { +#if 0 uint64_t uApicBase; uApicBase = ASMRdMsr(MSR_IA32_APICBASE); @@ -242,6 +247,9 @@ static PAPICCPU bs3ApicGetCpu(void) } return NULL; +#else + return bs3ApicApGetCpu(); +#endif } @@ -354,6 +362,10 @@ BS3_DECL(uint32_t) bs3ApicApStartup_pe32(void) uint32_t const idApic = pau32Apic[XAPIC_OFF_ID / sizeof(uint32_t)] >> 24; PAPICCPU pApicCpu = &g_paCpus[idApic]; + /* Set the GS selector. */ + bs3ApicApSetGs(BS3_SEL_FREE_PART4 + idApic * 8); + + pApicCpu->pThis = pApicCpu; pApicCpu->idCpu = idApic; pApicCpu->pau32Apic = pau32Apic; pApicCpu->enmWaitMethod = kApicCpuWaitMethod_StiHltCli; @@ -420,11 +432,20 @@ static bool bs3ApicStartAllAps(uint32_t BS3_FAR volatile * const pau32Apic) PAPICCPU pApicCpu = &g_paCpus[0]; void BS3_FAR *pv = Bs3MemAllocZ(BS3MEMKIND_REAL, _4K); uintptr_t const uPtr = (uintptr_t)pv; + uint8_t i; + pApicCpu->pThis = pApicCpu; pApicCpu->idCpu = 0; pApicCpu->fX2Apic = false; pApicCpu->pau32Apic = pau32Apic; + /* Setup the GDT entries for the per vCPU data. */ + for (i = 0; i < CPUS_MAX; i++) + Bs3SelSetup32BitData(&Bs3GdteFreePart4[i], (uint32_t)(uintptr_t)&g_paCpus[i], sizeof(g_paCpus[0]), 0 /*bDpl*/); + + /* Set the GS selector for CPU0. */ + bs3ApicApSetGs(BS3_SEL_FREE_PART4); + if ( !(uPtr & X86_PAGE_OFFSET_MASK) && (uPtr >> X86_PAGE_SHIFT) < UINT8_MAX) { @@ -495,6 +516,8 @@ static void bs3ApicTestWaitForResponses(uint32_t cCpusToRespond, uint64_t bmCpus static void bs3ApicTestSelfIpi(PAPICCPU pApicCpu) { + /* Set the GS selector for CPU0. */ + bs3ApicApSetGs(BS3_SEL_FREE_PART4); bs3ApicTestSetIntrHandlerReq(pApicCpu, kApicCpuReq_Pong); ASMIntEnable(); diff --git a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-asm.asm index e26f40923053..a31c86cd11a8 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-asm.asm +++ b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-asm.asm @@ -1,4 +1,4 @@ -; $Id: bs3-apic-1-asm.asm 114752 2026-07-22 16:52:58Z alexander.eichner@oracle.com $ +; $Id: bs3-apic-1-asm.asm 115063 2026-08-18 07:54:16Z alexander.eichner@oracle.com $ ;; @file ; BS3Kit - bs3-apic-1 ; @@ -121,3 +121,26 @@ BS3_GLOBAL_LOCAL_LABEL .ap_lck_busy int 3 BS3_PROC_END NAME(bs3ApicApTrampoline) +BS3_BEGIN_TEXT32 + +;; +; @cproto BS3_DECL(void) bs3ApicApSetGs(uint16_t u16Gs); +; +BS3_PROC_BEGIN NAME(bs3ApicApSetGs) + push edi + + mov di, [esp + 8] ; u16Gs + mov gs, di + pop edi + ret +BS3_PROC_END NAME(bs3ApicApSetGs) + + +;; +; @cproto BS3_DECL(PAPICCPU) bs3ApicApGetCpu(void); +; +BS3_PROC_BEGIN NAME(bs3ApicApGetCpu) + mov eax, [gs:0] + ret +BS3_PROC_END NAME(bs3ApicApGetCpu) + From fd8512d445f0cc8a915f40f5d9ef2c3bfc158648 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Tue, 18 Aug 2026 09:09:59 +0000 Subject: [PATCH 157/176] ValidationKit/bootsectors/bs3-apic-1: Very basic test to check that nested interrupt processing is working svn:sync-xref-src-repo-rev: r174905 --- .../bootsectors/bs3-apic-1-32.c32 | 66 ++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 index 5b0936aebad1..8d5aa8a9c284 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 +++ b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 @@ -1,4 +1,4 @@ -/* $Id: bs3-apic-1-32.c32 115063 2026-08-18 07:54:16Z alexander.eichner@oracle.com $ */ +/* $Id: bs3-apic-1-32.c32 115064 2026-08-18 09:09:59Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - bs3-apic-1, 32-bit C code. */ @@ -48,6 +48,7 @@ #define CPUS_MAX 32 #define CPU_STACK_SIZE _4K +#define APIC_TIMER_VECTOR 0x6f #define APIC_VECTOR 0x70 #define APIC_X2APIC_VECTOR 0x71 @@ -129,7 +130,7 @@ static volatile uint32_t g_cCpus = 1; static volatile uint32_t g_cCpusResponded = 0; static volatile uint64_t g_bmCpusValid = 0; static volatile uint64_t g_bmCpusResponded = 0; - +static volatile bool g_fTimerDone = false; static void bs3ApicBusyWait(uint64_t u64Usec) { @@ -527,6 +528,64 @@ static void bs3ApicTestSelfIpi(PAPICCPU pApicCpu) } +BS3_DECL_NEAR_CALLBACK(void) BS3_CMN_NM(bs3ApicTimerHandler)(PBS3TRAPFRAME pTrapFrame) +{ + PAPICCPU pApicCpu = bs3ApicGetCpu(); + if (pApicCpu) + { + /* Cause an IPI with a higher priority to interrupt this handler. */ + ASMIntEnable(); + bs3ApicTestReset(); + bs3ApicTestSetIntrHandlerReq(pApicCpu, kApicCpuReq_Pong); + apicSelfIpi(pApicCpu, APIC_VECTOR); + bs3ApicTestWaitForResponses(1, RT_BIT_64(pApicCpu->idCpu)); + + g_fTimerDone = true; + + /* Finish servicing this interrupt. */ + if (pApicCpu->fX2Apic) + ASMWrMsr(MSR_IA32_X2APIC_EOI, 0); + else + pApicCpu->pau32Apic[XAPIC_OFF_EOI / sizeof(uint32_t)] = 0; + } + else + bs3ApicTestFailedF("APIC not enabled!"); + + RT_NOREF(pTrapFrame); +} + + +static void bs3ApicTestNestedIrqs(PAPICCPU pApicCpu) +{ + /* Set the GS selector for CPU0. */ + bs3ApicApSetGs(BS3_SEL_FREE_PART4); + Bs3TrapSetHandler(APIC_TIMER_VECTOR, bs3ApicTimerHandler_c32); + g_fTimerDone = false; + + /* Program the timer. */ + if (pApicCpu->fX2Apic) + { + ASMWrMsr(MSR_IA32_X2APIC_TIMER_DCR, 0); + ASMWrMsr(MSR_IA32_X2APIC_LVT_TIMER, APIC_TIMER_VECTOR); + ASMIntEnable(); + ASMWrMsr(MSR_IA32_X2APIC_TIMER_ICR, 0xffff); + } + else + { + uint32_t BS3_FAR volatile * const pau32Apic = pApicCpu->pau32Apic; + pau32Apic[XAPIC_OFF_TIMER_DCR / sizeof(uint32_t)] = 0; + pau32Apic[XAPIC_OFF_LVT_TIMER / sizeof(uint32_t)] = (uint32_t)APIC_TIMER_VECTOR; + + ASMIntEnable(); + pau32Apic[XAPIC_OFF_TIMER_ICR / sizeof(uint32_t)] = 0xffff; + } + + while (!g_fTimerDone) + ASMNopPause(); + ASMIntDisable(); +} + + static void bs3ApicTestIpiStress(PAPICCPU pApicCpu) { /* Hammer the APIC with pings to all CPUs. */ @@ -593,6 +652,9 @@ static void bs3ApicRunTests(PAPICCPU pApicCpu) Bs3TestSubSub("Self IPI"); bs3ApicTestSelfIpi(pApicCpu); + Bs3TestSubSub("Nested Interrupts"); + bs3ApicTestNestedIrqs(pApicCpu); + if (g_cCpus > 1) { Bs3TestSubSub("Pinging all APs"); From b9a74faf6a82b7cd9505c67f81f3194078ea0bd1 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Tue, 18 Aug 2026 12:57:16 +0000 Subject: [PATCH 158/176] ValidationKit/bootsectors/bs3-apic-1: Also test that servicing a lowerp priority interrupt works after it arrived while servicing a higher priority one svn:sync-xref-src-repo-rev: r174906 --- .../bootsectors/bs3-apic-1-32.c32 | 90 ++++++++++++++----- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 index 8d5aa8a9c284..50e4cc026191 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 +++ b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 @@ -1,4 +1,4 @@ -/* $Id: bs3-apic-1-32.c32 115064 2026-08-18 09:09:59Z alexander.eichner@oracle.com $ */ +/* $Id: bs3-apic-1-32.c32 115065 2026-08-18 12:57:16Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - bs3-apic-1, 32-bit C code. */ @@ -81,6 +81,8 @@ typedef enum APICCPUREQ kApicCpuReq_Pong, /** A simple pong to a ping but don't clear the request. */ kApicCpuReq_PongNoClear, + /** Starts the APIC timer and waits for it to complete. */ + kApicCpuReq_ApicTimerStartAndWaitToComplete, /** 32-bit hack. */ kApicCpuReq_32Bit_Hack = 0x7fffffff } APICCPUREQ; @@ -131,6 +133,7 @@ static volatile uint32_t g_cCpusResponded = 0; static volatile uint64_t g_bmCpusValid = 0; static volatile uint64_t g_bmCpusResponded = 0; static volatile bool g_fTimerDone = false; +static volatile bool g_fTimerSetIpi = false; static void bs3ApicBusyWait(uint64_t u64Usec) { @@ -254,6 +257,40 @@ static PAPICCPU bs3ApicGetCpu(void) } +static void bs3ApicTimerStart(PAPICCPU pApicCpu, uint8_t bVector, uint32_t u32Icr) +{ + /* Program the timer. */ + if (pApicCpu->fX2Apic) + { + ASMWrMsr(MSR_IA32_X2APIC_TIMER_DCR, 0); + ASMWrMsr(MSR_IA32_X2APIC_LVT_TIMER, bVector); + ASMIntEnable(); + ASMWrMsr(MSR_IA32_X2APIC_TIMER_ICR, u32Icr); + } + else + { + uint32_t BS3_FAR volatile * const pau32Apic = pApicCpu->pau32Apic; + pau32Apic[XAPIC_OFF_TIMER_DCR / sizeof(uint32_t)] = 0; + pau32Apic[XAPIC_OFF_LVT_TIMER / sizeof(uint32_t)] = (uint32_t)bVector; + + ASMIntEnable(); + pau32Apic[XAPIC_OFF_TIMER_ICR / sizeof(uint32_t)] = u32Icr; + } +} + + +static uint32_t bs3ApicTimerGetCurrent(PAPICCPU pApicCpu) +{ + if (pApicCpu->fX2Apic) + return (uint32_t)ASMRdMsr(MSR_IA32_X2APIC_TIMER_CCR); + else + { + uint32_t BS3_FAR volatile * const pau32Apic = pApicCpu->pau32Apic; + return pau32Apic[XAPIC_OFF_TIMER_CCR / sizeof(uint32_t)]; + } +} + + BS3_DECL_NEAR_CALLBACK(void) BS3_CMN_NM(bs3ApicIpiHandler)(PBS3TRAPFRAME pTrapFrame) { PAPICCPU pApicCpu = bs3ApicGetCpu(); @@ -276,6 +313,13 @@ BS3_DECL_NEAR_CALLBACK(void) BS3_CMN_NM(bs3ApicIpiHandler)(PBS3TRAPFRAME pTrapFr case kApicCpuReq_Pong: bs3ApicTestFinish(pApicCpu->idCpu); break; + case kApicCpuReq_ApicTimerStartAndWaitToComplete: + { + bs3ApicTimerStart(pApicCpu, APIC_TIMER_VECTOR, 0x1); + while (bs3ApicTimerGetCurrent(pApicCpu) > 0) + ASMNopPause(); + break; + } default: /* Invalid but can't print anything here. */ break; @@ -533,12 +577,15 @@ BS3_DECL_NEAR_CALLBACK(void) BS3_CMN_NM(bs3ApicTimerHandler)(PBS3TRAPFRAME pTrap PAPICCPU pApicCpu = bs3ApicGetCpu(); if (pApicCpu) { - /* Cause an IPI with a higher priority to interrupt this handler. */ - ASMIntEnable(); - bs3ApicTestReset(); - bs3ApicTestSetIntrHandlerReq(pApicCpu, kApicCpuReq_Pong); - apicSelfIpi(pApicCpu, APIC_VECTOR); - bs3ApicTestWaitForResponses(1, RT_BIT_64(pApicCpu->idCpu)); + if (g_fTimerSetIpi) + { + /* Cause an IPI with a higher priority to interrupt this handler. */ + ASMIntEnable(); + bs3ApicTestReset(); + bs3ApicTestSetIntrHandlerReq(pApicCpu, kApicCpuReq_Pong); + apicSelfIpi(pApicCpu, APIC_VECTOR); + bs3ApicTestWaitForResponses(1, RT_BIT_64(pApicCpu->idCpu)); + } g_fTimerDone = true; @@ -561,24 +608,21 @@ static void bs3ApicTestNestedIrqs(PAPICCPU pApicCpu) bs3ApicApSetGs(BS3_SEL_FREE_PART4); Bs3TrapSetHandler(APIC_TIMER_VECTOR, bs3ApicTimerHandler_c32); g_fTimerDone = false; + g_fTimerSetIpi = true; - /* Program the timer. */ - if (pApicCpu->fX2Apic) - { - ASMWrMsr(MSR_IA32_X2APIC_TIMER_DCR, 0); - ASMWrMsr(MSR_IA32_X2APIC_LVT_TIMER, APIC_TIMER_VECTOR); - ASMIntEnable(); - ASMWrMsr(MSR_IA32_X2APIC_TIMER_ICR, 0xffff); - } - else - { - uint32_t BS3_FAR volatile * const pau32Apic = pApicCpu->pau32Apic; - pau32Apic[XAPIC_OFF_TIMER_DCR / sizeof(uint32_t)] = 0; - pau32Apic[XAPIC_OFF_LVT_TIMER / sizeof(uint32_t)] = (uint32_t)APIC_TIMER_VECTOR; + bs3ApicTimerStart(pApicCpu, APIC_TIMER_VECTOR, 0xffff); - ASMIntEnable(); - pau32Apic[XAPIC_OFF_TIMER_ICR / sizeof(uint32_t)] = 0xffff; - } + while (!g_fTimerDone) + ASMNopPause(); + + /* + * Test that a lower priority interrupt is serviced when it arrives while a + * higher priiority interrupt is serviced when it finished. + */ + g_fTimerDone = false; + g_fTimerSetIpi = false; + bs3ApicTestSetIntrHandlerReq(pApicCpu, kApicCpuReq_ApicTimerStartAndWaitToComplete); + apicSelfIpi(pApicCpu, APIC_VECTOR); while (!g_fTimerDone) ASMNopPause(); From f5b79cd3a8d75f65002cb2946bcf9d6402a91a31 Mon Sep 17 00:00:00 2001 From: Vitali Pelenjow Date: Tue, 18 Aug 2026 13:31:58 +0000 Subject: [PATCH 159/176] Devices/Graphics: streamlined dxEnsureVideo*View helpers. bugref:10934 svn:sync-xref-src-repo-rev: r174907 --- .../Graphics/DevVGA-SVGA3d-dx-dx11.cpp | 221 ++++++++++++------ 1 file changed, 156 insertions(+), 65 deletions(-) diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp index 55d2393ec2c3..94c0b62b298c 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 115059 2026-08-17 17:02:20Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 115066 2026-08-18 13:31:58Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -11752,6 +11752,7 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D } break; case VBSVGA_COTABLE_VDOV: +#ifndef DX_STATE_TRACKER if (pBackendDXContext->paVideoDecoderOutputView) { /* Destroy the no longer used entries. */ @@ -11779,6 +11780,36 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D if (pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); } +#else + if (!fGrow) + cValidEntries = 0; + + if (pBackendDXContext->paVideoDecoderOutputView) + { + /* Destroy the no longer used entries. */ + for (uint32_t i = 0; i < pBackendDXContext->cVideoDecoderOutputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoDecoderOutputView[i]; + if (i < cValidEntries) + dxViewRemoveFromList(pDXView); /* Remove from list because DXVIEW array will be reallocated. */ + else + dxViewDestroy(pDXView); + } + } + + rc = dxCOTableRealloc((void **)&pBackendDXContext->paVideoDecoderOutputView, &pBackendDXContext->cVideoDecoderOutputView, + sizeof(pBackendDXContext->paVideoDecoderOutputView[0]), pDXContext->cot.cVideoDecoderOutputView, cValidEntries); + AssertRCBreak(rc); + + for (uint32_t i = 0; i < pBackendDXContext->cVideoDecoderOutputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoDecoderOutputView[i]; + if (i < cValidEntries && pDXView->u.pView) + dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); + } +#endif break; case VBSVGA_COTABLE_VIDEODECODER: if (pBackendDXContext->paVideoDecoder) @@ -11804,6 +11835,7 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D } break; case VBSVGA_COTABLE_VPIV: +#ifndef DX_STATE_TRACKER if (pBackendDXContext->paVideoProcessorInputView) { /* Destroy the no longer used entries. */ @@ -11831,8 +11863,39 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D if (pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); } +#else + if (!fGrow) + cValidEntries = 0; + + if (pBackendDXContext->paVideoProcessorInputView) + { + /* Destroy the no longer used entries. */ + for (uint32_t i = 0; i < pBackendDXContext->cVideoProcessorInputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoProcessorInputView[i]; + if (i < cValidEntries) + dxViewRemoveFromList(pDXView); /* Remove from list because DXVIEW array will be reallocated. */ + else + dxViewDestroy(pDXView); + } + } + + rc = dxCOTableRealloc((void **)&pBackendDXContext->paVideoProcessorInputView, &pBackendDXContext->cVideoProcessorInputView, + sizeof(pBackendDXContext->paVideoProcessorInputView[0]), pDXContext->cot.cVideoProcessorInputView, cValidEntries); + AssertRCBreak(rc); + + for (uint32_t i = 0; i < pBackendDXContext->cVideoProcessorInputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoProcessorInputView[i]; + if (i < cValidEntries && pDXView->u.pView) + dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); + } +#endif break; case VBSVGA_COTABLE_VPOV: +#ifndef DX_STATE_TRACKER if (pBackendDXContext->paVideoProcessorOutputView) { /* Destroy the no longer used entries. */ @@ -11860,6 +11923,36 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D if (pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); } +#else + if (!fGrow) + cValidEntries = 0; + + if (pBackendDXContext->paVideoProcessorOutputView) + { + /* Destroy the no longer used entries. */ + for (uint32_t i = 0; i < pBackendDXContext->cVideoProcessorOutputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoProcessorOutputView[i]; + if (i < cValidEntries) + dxViewRemoveFromList(pDXView); /* Remove from list because DXVIEW array will be reallocated. */ + else + dxViewDestroy(pDXView); + } + } + + rc = dxCOTableRealloc((void **)&pBackendDXContext->paVideoProcessorOutputView, &pBackendDXContext->cVideoProcessorOutputView, + sizeof(pBackendDXContext->paVideoProcessorOutputView[0]), pDXContext->cot.cVideoProcessorOutputView, cValidEntries); + AssertRCBreak(rc); + + for (uint32_t i = 0; i < pBackendDXContext->cVideoProcessorOutputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoProcessorOutputView[i]; + if (i < cValidEntries && pDXView->u.pView) + dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); + } +#endif break; case VBSVGA_COTABLE_MAX: break; /* Compiler warning */ #ifndef DEBUG_sunlover @@ -12865,54 +12958,30 @@ static void dxVideoProcessorSetStreamRotation(DXDEVICE *pDXDevice, DXVIDEOPROCES } -static int dxCreateVideoDecoderOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, VBSVGACOTableDXVideoDecoderOutputViewEntry const *pEntry) +static HRESULT dxCreateVideoDecoderOutputView(PVGASTATECC pThisCC, VBSVGACOTableDXVideoDecoderOutputViewEntry const *pEntry, + ID3D11Resource *pResource, ID3D11VideoDecoderOutputView **ppVDOView) { DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturn(pDXDevice->pVideoDevice, VERR_INVALID_STATE); - PVMSVGA3DSURFACE pSurface; - ID3D11Resource *pResource; - int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); - AssertRCReturn(rc, rc); - - videoDecoderOutputViewId = dxVideoDecoderOutputViewId(pDXContext, videoDecoderOutputViewId); - if (videoDecoderOutputViewId == SVGA3D_INVALID_ID) - return VERR_INVALID_PARAMETER; - - DXVIEW *pView = &pDXContext->pBackendDXContext->paVideoDecoderOutputView[videoDecoderOutputViewId]; - AssertStmt(pView->u.pView == NULL, dxViewDestroy(pView)); - D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC Desc; RT_ZERO(Desc); memcpy(&Desc.DecodeProfile, &pEntry->desc.DecodeProfile, sizeof(GUID)); Desc.ViewDimension = dxVDOVDimension(pEntry->desc.ViewDimension); Desc.Texture2D.ArraySlice = pEntry->desc.Texture2D.ArraySlice; - ID3D11VideoDecoderOutputView *pVDOView; - HRESULT hr = pDXDevice->pVideoDevice->CreateVideoDecoderOutputView(pResource, &Desc, &pVDOView); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); - - return dxViewInit(pView, pSurface, pDXContext, videoDecoderOutputViewId, VMSVGA3D_VIEWTYPE_VIDEODECODEROUTPUT, pVDOView); + HRESULT hr = pDXDevice->pVideoDevice->CreateVideoDecoderOutputView(pResource, &Desc, ppVDOView); + Assert(SUCCEEDED(hr)); + return hr; } -static int dxCreateVideoProcessorInputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId, VBSVGACOTableDXVideoProcessorInputViewEntry const *pEntry) +static HRESULT dxCreateVideoProcessorInputView(PVGASTATECC pThisCC, VBSVGACOTableDXVideoProcessorInputViewEntry const *pEntry, + ID3D11Resource *pResource, ID3D11VideoProcessorInputView **ppVPIView) { DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturn(pDXDevice->pVideoDevice, VERR_INVALID_STATE); - PVMSVGA3DSURFACE pSurface; - ID3D11Resource *pResource; - int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); - AssertRCReturn(rc, rc); - - videoProcessorInputViewId = dxVideoProcessorInputViewId(pDXContext, videoProcessorInputViewId); - if (videoProcessorInputViewId == SVGA3D_INVALID_ID) - return VERR_INVALID_PARAMETER; - - DXVIEW *pView = &pDXContext->pBackendDXContext->paVideoProcessorInputView[videoProcessorInputViewId]; - AssertStmt(pView->u.pView == NULL, dxViewDestroy(pView)); - D3D11_VIDEO_PROCESSOR_CONTENT_DESC ContentDesc; RT_ZERO(ContentDesc); ContentDesc.InputFrameFormat = dxVideoFrameFormat(pEntry->contentDesc.InputFrameFormat); @@ -12928,7 +12997,7 @@ static int dxCreateVideoProcessorInputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTE ID3D11VideoProcessorEnumerator *pEnum; HRESULT hr = pDXDevice->pVideoDevice->CreateVideoProcessorEnumerator(&ContentDesc, &pEnum); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); + AssertReturn(SUCCEEDED(hr), hr); D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC Desc; RT_ZERO(Desc); @@ -12937,32 +13006,19 @@ static int dxCreateVideoProcessorInputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTE Desc.Texture2D.MipSlice = pEntry->desc.Texture2D.MipSlice; Desc.Texture2D.ArraySlice = pEntry->desc.Texture2D.ArraySlice; - ID3D11VideoProcessorInputView *pVPIView; - hr = pDXDevice->pVideoDevice->CreateVideoProcessorInputView(pResource, pEnum, &Desc, &pVPIView); + hr = pDXDevice->pVideoDevice->CreateVideoProcessorInputView(pResource, pEnum, &Desc, ppVPIView); D3D_RELEASE(pEnum); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); - - return dxViewInit(pView, pSurface, pDXContext, videoProcessorInputViewId, VMSVGA3D_VIEWTYPE_VIDEOPROCESSORINPUT, pVPIView); + Assert(SUCCEEDED(hr)); + return hr; } -static int dxCreateVideoProcessorOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, VBSVGACOTableDXVideoProcessorOutputViewEntry const *pEntry) +static HRESULT dxCreateVideoProcessorOutputView(PVGASTATECC pThisCC, VBSVGACOTableDXVideoProcessorOutputViewEntry const *pEntry, + ID3D11Resource *pResource, ID3D11VideoProcessorOutputView **ppVPOView) { DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturn(pDXDevice->pVideoDevice, VERR_INVALID_STATE); - PVMSVGA3DSURFACE pSurface; - ID3D11Resource *pResource; - int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); - AssertRCReturn(rc, rc); - - videoProcessorOutputViewId = dxVideoProcessorOutputViewId(pDXContext, videoProcessorOutputViewId); - if (videoProcessorOutputViewId == SVGA3D_INVALID_ID) - return VERR_INVALID_PARAMETER; - - DXVIEW *pView = &pDXContext->pBackendDXContext->paVideoProcessorOutputView[videoProcessorOutputViewId]; - AssertStmt(pView->u.pView == NULL, dxViewDestroy(pView)); - D3D11_VIDEO_PROCESSOR_CONTENT_DESC ContentDesc; RT_ZERO(ContentDesc); ContentDesc.InputFrameFormat = dxVideoFrameFormat(pEntry->contentDesc.InputFrameFormat); @@ -12978,7 +13034,7 @@ static int dxCreateVideoProcessorOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONT ID3D11VideoProcessorEnumerator *pEnum; HRESULT hr = pDXDevice->pVideoDevice->CreateVideoProcessorEnumerator(&ContentDesc, &pEnum); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); + AssertReturn(SUCCEEDED(hr), hr); D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC Desc; RT_ZERO(Desc); @@ -12994,31 +13050,40 @@ static int dxCreateVideoProcessorOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONT Desc.Texture2DArray.ArraySize = pEntry->desc.Texture2DArray.ArraySize; } - ID3D11VideoProcessorOutputView *pVPOView; - hr = pDXDevice->pVideoDevice->CreateVideoProcessorOutputView(pResource, pEnum, &Desc, &pVPOView); + hr = pDXDevice->pVideoDevice->CreateVideoProcessorOutputView(pResource, pEnum, &Desc, ppVPOView); D3D_RELEASE(pEnum); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); - - return dxViewInit(pView, pSurface, pDXContext, videoProcessorOutputViewId, VMSVGA3D_VIEWTYPE_VIDEOPROCESSOROUTPUT, pVPOView); + Assert(SUCCEEDED(hr)); + return hr; } static int dxEnsureVideoDecoderOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, VBSVGA3dVideoDecoderOutputViewId viewId, DXVIEW **ppResult) { - ASSERT_GUEST_RETURN(viewId < pDXContext->cot.cVideoDecoderOutputView, VERR_INVALID_PARAMETER); - viewId = dxVideoDecoderOutputViewId(pDXContext, viewId); viewId = svgaVideoDecoderOutputViewId(pDXContext, viewId); if (viewId == SVGA3D_INVALID_ID) return VERR_INVALID_PARAMETER; + VBSVGACOTableDXVideoDecoderOutputViewEntry const *pEntry = &pDXContext->cot.paVideoDecoderOutputView[viewId]; DXVIEW *pDXView = &pDXContext->pBackendDXContext->paVideoDecoderOutputView[viewId]; + + /* dxEnsureResource must be always called because it makes sure that resource is up to date. */ + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); + AssertRCReturn(rc, rc); + if (!pDXView->u.pView) { - VBSVGACOTableDXVideoDecoderOutputViewEntry const *pEntry = &pDXContext->cot.paVideoDecoderOutputView[viewId]; - int rc = dxCreateVideoDecoderOutputView(pThisCC, pDXContext, viewId, pEntry); + ID3D11VideoDecoderOutputView *pVDOView; + HRESULT hr = dxCreateVideoDecoderOutputView(pThisCC, pEntry, pResource, &pVDOView); + if (SUCCEEDED(hr)) + rc = dxViewInit(pDXView, pSurface, pDXContext, viewId, VMSVGA3D_VIEWTYPE_VIDEODECODEROUTPUT, pVDOView); + else + rc = VERR_INVALID_STATE; AssertRCReturn(rc, rc); } + *ppResult = pDXView; return VINF_SUCCESS; } @@ -13031,13 +13096,26 @@ static int dxEnsureVideoProcessorInputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTE if (viewId == SVGA3D_INVALID_ID) return VERR_INVALID_PARAMETER; + VBSVGACOTableDXVideoProcessorInputViewEntry const *pEntry = &pDXContext->cot.paVideoProcessorInputView[viewId]; DXVIEW *pDXView = &pDXContext->pBackendDXContext->paVideoProcessorInputView[viewId]; + + /* dxEnsureResource must be always called because it makes sure that resource is up to date. */ + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); + AssertRCReturn(rc, rc); + if (!pDXView->u.pView) { - VBSVGACOTableDXVideoProcessorInputViewEntry const *pEntry = &pDXContext->cot.paVideoProcessorInputView[viewId]; - int rc = dxCreateVideoProcessorInputView(pThisCC, pDXContext, viewId, pEntry); + ID3D11VideoProcessorInputView *pVPIView; + HRESULT hr = dxCreateVideoProcessorInputView(pThisCC, pEntry, pResource, &pVPIView); + if (SUCCEEDED(hr)) + rc = dxViewInit(pDXView, pSurface, pDXContext, viewId, VMSVGA3D_VIEWTYPE_VIDEOPROCESSORINPUT, pVPIView); + else + rc = VERR_INVALID_STATE; AssertRCReturn(rc, rc); } + *ppResult = pDXView; return VINF_SUCCESS; } @@ -13050,13 +13128,26 @@ static int dxEnsureVideoProcessorOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONT if (viewId == SVGA3D_INVALID_ID) return VERR_INVALID_PARAMETER; + VBSVGACOTableDXVideoProcessorOutputViewEntry const *pEntry = &pDXContext->cot.paVideoProcessorOutputView[viewId]; DXVIEW *pDXView = &pDXContext->pBackendDXContext->paVideoProcessorOutputView[viewId]; + + /* dxEnsureResource must be always called because it makes sure that resource is up to date. */ + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); + AssertRCReturn(rc, rc); + if (!pDXView->u.pView) { - VBSVGACOTableDXVideoProcessorOutputViewEntry const *pEntry = &pDXContext->cot.paVideoProcessorOutputView[viewId]; - int rc = dxCreateVideoProcessorOutputView(pThisCC, pDXContext, viewId, pEntry); + ID3D11VideoProcessorOutputView *pVPOView; + HRESULT hr = dxCreateVideoProcessorOutputView(pThisCC, pEntry, pResource, &pVPOView); + if (SUCCEEDED(hr)) + rc = dxViewInit(pDXView, pSurface, pDXContext, viewId, VMSVGA3D_VIEWTYPE_VIDEOPROCESSOROUTPUT, pVPOView); + else + rc = VERR_INVALID_STATE; AssertRCReturn(rc, rc); } + *ppResult = pDXView; return VINF_SUCCESS; } From 3daf205f656be4d547a9bb08f63f445a20c65f11 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 18 Aug 2026 14:37:14 +0000 Subject: [PATCH 160/176] Shared Clipboard/win: Logging fixes. bugref:4697 svn:sync-xref-src-repo-rev: r174908 --- src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp index 07dc83a10093..ffd76b4296c8 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-win.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-win.cpp 115067 2026-08-18 14:37:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Windows-specific functions for clipboard handling. */ @@ -483,13 +483,15 @@ SHCLFORMAT ShClWinClipboardFormatToVBox(UINT uFormat) else if ( (RTUtf16Cmp(szFormatName, RT_LSTR("FileGroupDescriptor")) == 0) || (RTUtf16Cmp(szFormatName, RT_LSTR("FileGroupDescriptorW")) == 0) || (RTUtf16Cmp(szFormatName, RT_LSTR("FileContents")) == 0)) + LogRelMax(16, ("Shared Clipboard: Windows virtual-file clipboard format '%ls' is not yet supported " + "for file transfers from the local Windows clipboard\n", szFormatName)); # else else if ( (RTStrCmp(szFormatName, CFSTR_FILEDESCRIPTORA) == 0) || (RTStrCmp(szFormatName, "FileGroupDescriptorW") == 0) || (RTStrCmp(szFormatName, CFSTR_FILECONTENTS) == 0)) + LogRelMax(16, ("Shared Clipboard: Windows virtual-file clipboard format '%s' is not yet supported " + "for file transfers from the local Windows clipboard\n", szFormatName)); # endif - LogRelMax(16, ("Shared Clipboard: Windows virtual-file clipboard format '%s' is not supported as a host file-transfer source yet\n", - szFormatName)); #endif } } From 483eda03ac9b05b45aeed8aceca32e56928ebf50 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Tue, 18 Aug 2026 14:45:28 +0000 Subject: [PATCH 161/176] Shared Clipboard/Win: Handle different transfer chunk sizes more gracefully. Added a new testcase for this. svn:sync-xref-src-repo-rev: r174909 --- .../ClipboardStreamImpl-win.cpp | 22 +- .../SharedClipboard/testcase/Makefile.kmk | 29 +- .../testcase/tstClipboardWinStream.cpp | 504 ++++++++++++++++++ 3 files changed, 550 insertions(+), 5 deletions(-) create mode 100644 src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp index 3960603f5221..b6c87aa4d201 100644 --- a/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardStreamImpl-win.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardStreamImpl-win.cpp 115068 2026-08-18 14:45:28Z andreas.loeffler@oracle.com $ */ /** @file * ClipboardStreamImpl-win.cpp - Shared Clipboard IStream object implementation (guest and host side). */ @@ -257,11 +257,25 @@ STDMETHODIMP ShClWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG * { if (cbToRead) { - rc = ShClTransferObjRead(m_pTransfer, m_hObj, pvBuffer, cbToRead, 0 /* fFlags */, &cbRead); - if (RT_SUCCESS(rc)) + /* Windows treats a short IStream read as EOF, so satisfy it using + * as many transfer chunks as necessary. */ + while (cbRead < cbToRead) { - m_cbProcessed += cbRead; + uint32_t const cbToReadChunk = RT_MIN(cbToRead - cbRead, m_pTransfer->cbMaxChunkSize); + uint32_t cbReadChunk = 0; + rc = ShClTransferObjRead(m_pTransfer, m_hObj, (uint8_t *)pvBuffer + cbRead, cbToReadChunk, + 0 /* fFlags */, &cbReadChunk); + if (RT_FAILURE(rc)) + break; + + AssertBreakStmt(cbReadChunk <= cbToReadChunk, rc = VERR_TOO_MUCH_DATA); + + cbRead += cbReadChunk; + m_cbProcessed += cbReadChunk; Assert(m_cbProcessed <= cbSize); + + if (cbReadChunk < cbToReadChunk) + break; } } diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk b/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk index 8390a36122ff..c4e2c6213fb6 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk +++ b/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114650 2026-07-08 09:14:39Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115068 2026-08-18 14:45:28Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the Shared Clipboard Guest/Host testcases. # @@ -89,6 +89,33 @@ if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBO $(tstClipboardMimeConv_1_STAGE_TARGET) quiet $(QUIET)$(APPEND) -t "$@" "done" + ifeq ($(KBUILD_TARGET),win) + # + # Shared Clipboard Windows stream testcase. + # + PROGRAMS += tstClipboardWinStream + tstClipboardWinStream_TEMPLATE = VBoxR3TstExe + tstClipboardWinStream_DEFS = VBOX_WITH_SHARED_CLIPBOARD VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardWinStream_SOURCES = \ + tstClipboardWinStream.cpp \ + ../ClipboardDataObjectImpl-win.cpp \ + ../ClipboardEnumFormatEtcImpl-win.cpp \ + ../ClipboardStreamImpl-win.cpp \ + ../clipboard-common.cpp \ + ../clipboard-helper.cpp \ + ../clipboard-path.cpp \ + ../clipboard-transfers.cpp + tstClipboardWinStream_CLEAN = $(tstClipboardWinStream_0_OUTDIR)/tstClipboardWinStream.run + + $$(tstClipboardWinStream_0_OUTDIR)/tstClipboardWinStream.run: $$(tstClipboardWinStream_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstClipboardWinStream_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardWinStream_0_OUTDIR)/tstClipboardWinStream.run + endif + endif + if defined(VBOX_WITH_LIBCURL) && defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) PROGRAMS += tstClipboardHttpServer tstClipboardHttpServer_TEMPLATE = VBoxR3TstExe diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp new file mode 100644 index 000000000000..67cf00b1d1d0 --- /dev/null +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp @@ -0,0 +1,504 @@ +/* $Id: tstClipboardWinStream.cpp 115068 2026-08-18 14:45:28Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard Windows stream testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include + + +/** @page pg_tstClipboardWinStream Shared Clipboard Windows stream testcase + * + * This testcase exercises the Windows IStream adapter with a mock transfer + * provider. It verifies exact boundary cases and deterministic randomized + * reads crossing the default 64 KiB transfer-chunk boundary. + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Size of the original boundary regression read. */ +#define TST_WIN_STREAM_BOUNDARY_READ_SIZE (_64K + 1) +/** Amount of data reserved for the final read past EOF. */ +#define TST_WIN_STREAM_FINAL_DATA_SIZE (_64K + 37) +/** Maximum randomized IStream read size. */ +#define TST_WIN_STREAM_MAX_READ_SIZE (_128K + 17) +/** Number of deterministic randomized IStream reads. */ +#define TST_WIN_STREAM_RANDOM_READ_COUNT 32 +/** Seed for the deterministic randomized reads. */ +#define TST_WIN_STREAM_RANDOM_SEED UINT64_C(0x4f1bbcdc676f2c35) +/** Maximum number of mock-provider reads recorded. */ +#define TST_WIN_STREAM_MAX_PROVIDER_READS 256 +/** Value used to detect writes beyond the returned data. */ +#define TST_WIN_STREAM_BUFFER_FILL UINT8_C(0xa5) +/** Object handle returned by the mock provider. */ +#define TST_WIN_STREAM_OBJ_HANDLE UINT64_C(1) + +/** Explicit boundary read sizes, performed from unaligned stream offsets. */ +static uint32_t const g_acbBoundaryReads[] = +{ + 17, + _64K - 1, + _64K, + _64K + 1, + _128K - 1, + _128K, + _128K + 1 +}; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** A recorded mock-provider read. */ +typedef struct TSTWINSTREAMPROVIDERREAD +{ + /** Source offset before the read. */ + uint32_t offData; + /** Requested byte count. */ + uint32_t cbRequested; + /** Returned byte count. */ + uint32_t cbRead; +} TSTWINSTREAMPROVIDERREAD; + +/** State for the mock transfer provider. */ +typedef struct TSTWINSTREAMPROVIDER +{ + /** Deterministic source data. */ + uint8_t *pbData; + /** Size of the source data. */ + uint32_t cbData; + /** Current source offset. */ + uint32_t offData; + /** Maximum transfer chunk size. */ + uint32_t cbMaxChunkSize; + /** Recorded object reads. */ + TSTWINSTREAMPROVIDERREAD aReads[TST_WIN_STREAM_MAX_PROVIDER_READS]; + /** Number of object opens. */ + uint32_t cOpens; + /** Number of object reads. */ + uint32_t cReads; + /** Number of object closes. */ + uint32_t cCloses; + /** Whether the read trace overflowed. */ + bool fReadLogOverflow; +} TSTWINSTREAMPROVIDER; +/** Pointer to mock transfer provider state. */ +typedef TSTWINSTREAMPROVIDER *PTSTWINSTREAMPROVIDER; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** @copydoc SHCLTXPROVIDERIFACE::pfnRootListRead */ +static DECLCALLBACK(int) tstWinStreamProviderRootListRead(PSHCLTXPROVIDERCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + return VINF_SUCCESS; +} + + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjOpen */ +static DECLCALLBACK(int) tstWinStreamProviderObjOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCreateParms, VERR_INVALID_POINTER); + AssertPtrReturn(phObj, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTWINSTREAMPROVIDER), VERR_INVALID_PARAMETER); + + PTSTWINSTREAMPROVIDER const pThis = (PTSTWINSTREAMPROVIDER)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + pThis->offData = 0; + pThis->cOpens++; + *phObj = TST_WIN_STREAM_OBJ_HANDLE; + return VINF_SUCCESS; +} + + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjRead */ +static DECLCALLBACK(int) tstWinStreamProviderObjRead(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, void *pvData, + uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTWINSTREAMPROVIDER), VERR_INVALID_PARAMETER); + AssertReturn(hObj == TST_WIN_STREAM_OBJ_HANDLE, VERR_INVALID_HANDLE); + AssertReturn(fFlags == 0, VERR_INVALID_FLAGS); + + PTSTWINSTREAMPROVIDER const pThis = (PTSTWINSTREAMPROVIDER)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pThis->pbData, VERR_INVALID_POINTER); + AssertReturn(pThis->offData <= pThis->cbData, VERR_OUT_OF_RANGE); + AssertReturn(cbData <= pThis->cbMaxChunkSize, VERR_BUFFER_OVERFLOW); + + uint32_t const offData = pThis->offData; + uint32_t const cbRemaining = pThis->cbData - offData; + uint32_t const cbRead = RT_MIN(cbData, cbRemaining); + + uint32_t const iRead = pThis->cReads++; + if (iRead < RT_ELEMENTS(pThis->aReads)) + { + pThis->aReads[iRead].offData = offData; + pThis->aReads[iRead].cbRequested = cbData; + pThis->aReads[iRead].cbRead = cbRead; + } + else + pThis->fReadLogOverflow = true; + + memcpy(pvData, &pThis->pbData[offData], cbRead); + pThis->offData += cbRead; + + if (pcbRead) + *pcbRead = cbRead; + return VINF_SUCCESS; +} + + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjClose */ +static DECLCALLBACK(int) tstWinStreamProviderObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTWINSTREAMPROVIDER), VERR_INVALID_PARAMETER); + AssertReturn(hObj == TST_WIN_STREAM_OBJ_HANDLE, VERR_INVALID_HANDLE); + + PTSTWINSTREAMPROVIDER const pThis = (PTSTWINSTREAMPROVIDER)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + pThis->cCloses++; + return VINF_SUCCESS; +} + + +/** + * Performs and validates one non-empty IStream read. + */ +static bool tstWinStreamReadAndCheck(IStream *pStream, PTSTWINSTREAMPROVIDER pProvider, uint8_t *pbBuffer, + uint32_t cbBuffer, uint32_t *poffExpected, uint32_t cbRequest, uint32_t iStreamRead) +{ + AssertPtrReturn(pStream, false); + AssertPtrReturn(pProvider, false); + AssertPtrReturn(pbBuffer, false); + AssertPtrReturn(poffExpected, false); + AssertReturn(cbRequest > 0, false); + AssertReturn(cbBuffer > cbRequest, false); + AssertReturn(*poffExpected <= pProvider->cbData, false); + + uint32_t const offExpected = *poffExpected; + uint32_t const cbExpected = RT_MIN(cbRequest, pProvider->cbData - offExpected); + HRESULT const hrcExpected = cbExpected == cbRequest ? S_OK : S_FALSE; + uint32_t const iProviderReadFirst = pProvider->cReads; + + memset(pbBuffer, TST_WIN_STREAM_BUFFER_FILL, cbRequest + 1); + + ULONG cbRead = UINT32_MAX; + HRESULT const hrc = pStream->Read(pbBuffer, cbRequest, &cbRead); + RTTESTI_CHECK_MSG(hrc == hrcExpected, + ("read=%RU32 off=%RU32 cbRequest=%RU32: hrc=%Rhrc, expected %Rhrc\n", + iStreamRead, offExpected, cbRequest, hrc, hrcExpected)); + RTTESTI_CHECK_MSG(cbRead == cbExpected, + ("read=%RU32 off=%RU32 cbRequest=%RU32: cbRead=%RU32, expected %RU32\n", + iStreamRead, offExpected, cbRequest, cbRead, cbExpected)); + RTTESTI_CHECK_MSG(memcmp(pbBuffer, &pProvider->pbData[offExpected], cbExpected) == 0, + ("read=%RU32 off=%RU32 cbRequest=%RU32: data mismatch\n", + iStreamRead, offExpected, cbRequest)); + + bool fTailUntouched = true; + for (uint32_t off = cbExpected; off <= cbRequest; off++) + if (pbBuffer[off] != TST_WIN_STREAM_BUFFER_FILL) + { + fTailUntouched = false; + break; + } + RTTESTI_CHECK_MSG(fTailUntouched, + ("read=%RU32 off=%RU32 cbRequest=%RU32: buffer overwritten past returned data\n", + iStreamRead, offExpected, cbRequest)); + + uint32_t const cProviderReadsExpected = cbExpected + ? (cbExpected + pProvider->cbMaxChunkSize - 1) / pProvider->cbMaxChunkSize + : 0; + uint32_t const cProviderReadsActual = pProvider->cReads - iProviderReadFirst; + RTTESTI_CHECK_MSG(cProviderReadsActual == cProviderReadsExpected, + ("read=%RU32 off=%RU32 cbRequest=%RU32: provider reads=%RU32, expected %RU32\n", + iStreamRead, offExpected, cbRequest, cProviderReadsActual, cProviderReadsExpected)); + RTTESTI_CHECK_MSG(!pProvider->fReadLogOverflow, + ("read=%RU32: provider read trace overflowed\n", iStreamRead)); + + if ( !pProvider->fReadLogOverflow + && cProviderReadsActual == cProviderReadsExpected + && pProvider->cReads <= RT_ELEMENTS(pProvider->aReads)) + { + uint32_t offTrace = offExpected; + uint32_t cbLeft = cbExpected; + for (uint32_t i = 0; i < cProviderReadsExpected; i++) + { + uint32_t const cbChunkExpected = RT_MIN(cbLeft, pProvider->cbMaxChunkSize); + TSTWINSTREAMPROVIDERREAD const *pRead = &pProvider->aReads[iProviderReadFirst + i]; + RTTESTI_CHECK_MSG(pRead->offData == offTrace, + ("read=%RU32 chunk=%RU32: offData=%RU32, expected %RU32\n", + iStreamRead, i, pRead->offData, offTrace)); + RTTESTI_CHECK_MSG(pRead->cbRequested == cbChunkExpected, + ("read=%RU32 chunk=%RU32: cbRequested=%RU32, expected %RU32\n", + iStreamRead, i, pRead->cbRequested, cbChunkExpected)); + RTTESTI_CHECK_MSG(pRead->cbRead == cbChunkExpected, + ("read=%RU32 chunk=%RU32: cbRead=%RU32, expected %RU32\n", + iStreamRead, i, pRead->cbRead, cbChunkExpected)); + offTrace += cbChunkExpected; + cbLeft -= cbChunkExpected; + } + } + + bool const fSucceeded = hrc == hrcExpected && cbRead == cbExpected; + if (fSucceeded) + *poffExpected += cbExpected; + return fSucceeded; +} + + +/** + * Tests varied IStream reads which cross the default chunk boundary. + */ +static void tstWinStreamReadsAcrossChunkBoundaries(void) +{ + RTTestISub("64 KiB + 1 byte IStream read"); + + RTRAND hRand = NIL_RTRAND; + uint32_t acbRandomReads[TST_WIN_STREAM_RANDOM_READ_COUNT]; + RT_ZERO(acbRandomReads); + + int rc = RTRandAdvCreateParkMiller(&hRand); + RTTESTI_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + rc = RTRandAdvSeed(hRand, TST_WIN_STREAM_RANDOM_SEED); + RTTESTI_CHECK_RC_OK(rc); + } + + uint64_t cbData = TST_WIN_STREAM_BOUNDARY_READ_SIZE + TST_WIN_STREAM_FINAL_DATA_SIZE; + for (size_t i = 0; i < RT_ELEMENTS(g_acbBoundaryReads); i++) + cbData += g_acbBoundaryReads[i]; + if (RT_SUCCESS(rc)) + { + for (size_t i = 0; i < RT_ELEMENTS(acbRandomReads); i++) + { + acbRandomReads[i] = RTRandAdvU32Ex(hRand, 1, TST_WIN_STREAM_MAX_READ_SIZE); + cbData += acbRandomReads[i]; + } + } + RTTESTI_CHECK_MSG(cbData <= UINT32_MAX, ("cbData=%RU64\n", cbData)); + if (cbData > UINT32_MAX) + rc = VERR_OUT_OF_RANGE; + + TSTWINSTREAMPROVIDER ProviderCtx; + RT_ZERO(ProviderCtx); + ProviderCtx.cbData = (uint32_t)cbData; + + uint8_t *pbBuffer = NULL; + if (RT_SUCCESS(rc)) + { + ProviderCtx.pbData = (uint8_t *)RTMemAlloc(ProviderCtx.cbData); + pbBuffer = (uint8_t *)RTMemAlloc(TST_WIN_STREAM_MAX_READ_SIZE + 1); + RTTESTI_CHECK(ProviderCtx.pbData != NULL); + RTTESTI_CHECK(pbBuffer != NULL); + if (!ProviderCtx.pbData || !pbBuffer) + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + RTRandAdvBytes(hRand, ProviderCtx.pbData, ProviderCtx.cbData); + + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); + Provider.Interface.pfnRootListRead = tstWinStreamProviderRootListRead; + Provider.Interface.pfnObjOpen = tstWinStreamProviderObjOpen; + Provider.Interface.pfnObjRead = tstWinStreamProviderObjRead; + Provider.Interface.pfnObjClose = tstWinStreamProviderObjClose; + Provider.pvUser = &ProviderCtx; + Provider.cbUser = sizeof(ProviderCtx); + + PSHCLTRANSFER pTransfer = NULL; + ShClWinDataObject *pParent = NULL; + IStream *pStream = NULL; + uint8_t bFrontendCtx = 0; + + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCreate(SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, NULL /* pCallbacks */, &pTransfer); + RTTESTI_CHECK_RC_OK(rc); + } + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK(pTransfer->cbMaxChunkSize == _64K); + ProviderCtx.cbMaxChunkSize = pTransfer->cbMaxChunkSize; + rc = ShClTransferSetProvider(pTransfer, &Provider); + RTTESTI_CHECK_RC_OK(rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferRootListRead(pTransfer); + RTTESTI_CHECK_RC_OK(rc); + } + if (RT_SUCCESS(rc)) + { + pParent = new ShClWinDataObject(); + RTTESTI_CHECK(pParent != NULL); + if (pParent) + { + pParent->AddRef(); /* Testcase reference. */ + + ShClWinDataObject::CALLBACKS Callbacks; + RT_ZERO(Callbacks); + rc = pParent->Init((PSHCLCONTEXT)&bFrontendCtx, &Callbacks); + RTTESTI_CHECK_RC_OK(rc); + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + SHCLFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); + ObjInfo.cbObject = ProviderCtx.cbData; + + HRESULT const hrc = ShClWinStreamImpl::Create(pParent, pTransfer, Utf8Str("random-boundaries.bin"), &ObjInfo, &pStream); + RTTESTI_CHECK_MSG(hrc == S_OK, ("hrc=%Rhrc\n", hrc)); + if (FAILED(hrc)) + rc = VERR_GENERAL_FAILURE; + } + if (RT_SUCCESS(rc)) + { + ULONG cbRead = UINT32_MAX; + HRESULT const hrc = pStream->Read(pbBuffer, 0, &cbRead); + RTTESTI_CHECK_MSG(hrc == S_OK, ("zero-byte read hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG(cbRead == 0, ("zero-byte read cbRead=%RU32\n", cbRead)); + RTTESTI_CHECK_MSG(ProviderCtx.cOpens == 0, ("zero-byte read cOpens=%RU32\n", ProviderCtx.cOpens)); + RTTESTI_CHECK_MSG(ProviderCtx.cReads == 0, ("zero-byte read cReads=%RU32\n", ProviderCtx.cReads)); + } + + uint32_t offExpected = 0; + uint32_t iStreamRead = 0; + bool fCanContinue = RT_SUCCESS(rc); + if (fCanContinue) + { + fCanContinue = tstWinStreamReadAndCheck(pStream, &ProviderCtx, pbBuffer, TST_WIN_STREAM_MAX_READ_SIZE + 1, + &offExpected, TST_WIN_STREAM_BOUNDARY_READ_SIZE, iStreamRead++); + RTTESTI_CHECK_MSG(ProviderCtx.cReads == 2, ("cReads=%RU32\n", ProviderCtx.cReads)); + RTTESTI_CHECK_MSG(ProviderCtx.cOpens == 1, ("cOpens=%RU32\n", ProviderCtx.cOpens)); + RTTESTI_CHECK_MSG(ProviderCtx.cCloses == 0, ("cCloses=%RU32\n", ProviderCtx.cCloses)); + } + + RTTestISubF("%u randomized boundary reads (seed %#RX64)", + TST_WIN_STREAM_RANDOM_READ_COUNT, TST_WIN_STREAM_RANDOM_SEED); + + for (size_t i = 0; fCanContinue && i < RT_ELEMENTS(g_acbBoundaryReads); i++) + fCanContinue = tstWinStreamReadAndCheck(pStream, &ProviderCtx, pbBuffer, TST_WIN_STREAM_MAX_READ_SIZE + 1, + &offExpected, g_acbBoundaryReads[i], iStreamRead++); + + uint32_t cRandomMultiChunkReads = 0; + uint32_t cRandomFileBoundaryReads = 0; + for (size_t i = 0; fCanContinue && i < RT_ELEMENTS(acbRandomReads); i++) + { + uint32_t const cbRequest = acbRandomReads[i]; + if (cbRequest > ProviderCtx.cbMaxChunkSize) + cRandomMultiChunkReads++; + if (offExpected / ProviderCtx.cbMaxChunkSize != (offExpected + cbRequest - 1) / ProviderCtx.cbMaxChunkSize) + cRandomFileBoundaryReads++; + + fCanContinue = tstWinStreamReadAndCheck(pStream, &ProviderCtx, pbBuffer, TST_WIN_STREAM_MAX_READ_SIZE + 1, + &offExpected, cbRequest, iStreamRead++); + } + + if (fCanContinue) + { + RTTESTI_CHECK_MSG(cRandomMultiChunkReads > 0, + ("No randomized read exceeded the transfer chunk size\n")); + RTTESTI_CHECK_MSG(cRandomFileBoundaryReads > 0, + ("No randomized read crossed a file chunk boundary\n")); + RTTESTI_CHECK_MSG(offExpected == ProviderCtx.cbData - TST_WIN_STREAM_FINAL_DATA_SIZE, + ("offExpected=%RU32, expected %RU32\n", + offExpected, ProviderCtx.cbData - TST_WIN_STREAM_FINAL_DATA_SIZE)); + RTTESTI_CHECK_MSG(ProviderCtx.cCloses == 0, ("pre-EOF cCloses=%RU32\n", ProviderCtx.cCloses)); + + fCanContinue = tstWinStreamReadAndCheck(pStream, &ProviderCtx, pbBuffer, TST_WIN_STREAM_MAX_READ_SIZE + 1, + &offExpected, TST_WIN_STREAM_FINAL_DATA_SIZE + 1, iStreamRead++); + } + + if (fCanContinue) + { + RTTESTI_CHECK_MSG(offExpected == ProviderCtx.cbData, + ("offExpected=%RU32, expected %RU32\n", offExpected, ProviderCtx.cbData)); + RTTESTI_CHECK_MSG(ProviderCtx.offData == ProviderCtx.cbData, + ("provider offData=%RU32, expected %RU32\n", ProviderCtx.offData, ProviderCtx.cbData)); + RTTESTI_CHECK_MSG(ProviderCtx.cOpens == 1, ("cOpens=%RU32\n", ProviderCtx.cOpens)); + RTTESTI_CHECK_MSG(ProviderCtx.cCloses == 1, ("cCloses=%RU32\n", ProviderCtx.cCloses)); + RTTESTI_CHECK_MSG(!ProviderCtx.fReadLogOverflow, ("Provider read trace overflowed\n")); + + uint32_t const cProviderReadsBefore = ProviderCtx.cReads; + memset(pbBuffer, TST_WIN_STREAM_BUFFER_FILL, 2); + ULONG cbRead = UINT32_MAX; + HRESULT const hrc = pStream->Read(pbBuffer, 1, &cbRead); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("post-EOF read hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG(cbRead == 0, ("post-EOF read cbRead=%RU32\n", cbRead)); + RTTESTI_CHECK_MSG(ProviderCtx.cReads == cProviderReadsBefore, + ("post-EOF cReads=%RU32, expected %RU32\n", ProviderCtx.cReads, cProviderReadsBefore)); + RTTESTI_CHECK_MSG(pbBuffer[0] == TST_WIN_STREAM_BUFFER_FILL, + ("post-EOF read modified the buffer\n")); + } + + if (pStream) + pStream->Release(); + if (pParent) + pParent->Release(); + if (pTransfer) + RTTESTI_CHECK_RC_OK(ShClTransferDestroy(pTransfer)); + RTMemFree(pbBuffer); + RTMemFree(ProviderCtx.pbData); + if (hRand != NIL_RTRAND) + RTTESTI_CHECK_RC_OK(RTRandAdvDestroy(hRand)); +} + + +/** Testcase entry point. */ +int main(void) +{ + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardWinStream", &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + tstWinStreamReadsAcrossChunkBoundaries(); + + return RTTestSummaryAndDestroy(hTest); +} From 826f2351e051dfc52b8668ba1e85fe48a28bfee0 Mon Sep 17 00:00:00 2001 From: Aleksey Ilyushin Date: Tue, 18 Aug 2026 18:39:48 +0000 Subject: [PATCH 162/176] git: ISSUE_TEMPLATE: Add 7.2.16 svn:sync-xref-src-repo-rev: r174912 --- .github/ISSUE_TEMPLATE/10-bug-report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/10-bug-report.yml b/.github/ISSUE_TEMPLATE/10-bug-report.yml index 599c07615104..9d8c2873ee6a 100644 --- a/.github/ISSUE_TEMPLATE/10-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/10-bug-report.yml @@ -45,6 +45,7 @@ body: label: Version options: - trunk/main + - 7.2.16 - 7.2.14 - 7.2.12 - 7.2.10 From 6b739e15ff3d88921129282269a33bb7d46b8be5 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Wed, 19 Aug 2026 07:32:45 +0000 Subject: [PATCH 163/176] Dhcpd/testcase: SVN property tweaks to fix OSE build errors. svn:sync-xref-src-repo-rev: r174914 --- .../Dhcpd/testcase/VBoxIntNetSwitchInProc.cpp | 317 ++++++++++++++++++ .../Dhcpd/testcase/VBoxNetDhcpdInProc.cpp | 268 +++++++++++++++ 2 files changed, 585 insertions(+) create mode 100644 src/VBox/NetworkServices/Dhcpd/testcase/VBoxIntNetSwitchInProc.cpp create mode 100644 src/VBox/NetworkServices/Dhcpd/testcase/VBoxNetDhcpdInProc.cpp diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/VBoxIntNetSwitchInProc.cpp b/src/VBox/NetworkServices/Dhcpd/testcase/VBoxIntNetSwitchInProc.cpp new file mode 100644 index 000000000000..2086e69b30a3 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/testcase/VBoxIntNetSwitchInProc.cpp @@ -0,0 +1,317 @@ +/* $Id: VBoxIntNetSwitchInProc.cpp 115072 2026-08-19 07:32:45Z andreas.loeffler@oracle.com $ */ +/** @file + * In-process local IPC IntNet switch for tstVBoxNetDhcpd. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH +# define VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH +#endif +#ifndef VBOX_INTNET_TESTCASE_LOCALIPC +# define VBOX_INTNET_TESTCASE_LOCALIPC +#endif + +#include "../../IntNetSwitch/VBoxIntNetSwitch.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Valid embedded switch handle magic. */ +#define VBOXINTNETSWITCHTEST_MAGIC UINT32_C(0x49505357) +/** Invalidated embedded switch handle magic. */ +#define VBOXINTNETSWITCHTEST_MAGIC_DEAD UINT32_C(0x69707377) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Embedded production IntNet local IPC service state. */ +typedef struct VBOXINTNETSWITCHTEST +{ + /** Magic value (VBOXINTNETSWITCHTEST_MAGIC). */ + uint32_t u32Magic; + /** Local IPC server. */ + RTLOCALIPCSERVER hServer; + /** Waitable acceptor thread. */ + RTTHREAD hAcceptor; + /** Set when the acceptor should stop. */ + bool volatile fStop; + /** Whether the R3-built IntNet switching core was initialized. */ + bool fIntNetInited; + /** Unique service name selected through VBOX_INTNET_R3_SVC_NAME. */ + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; +} VBOXINTNETSWITCHTEST; +typedef VBOXINTNETSWITCHTEST *PVBOXINTNETSWITCHTEST; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** + * Accepts local IPC clients and starts the session worker. + * + * @returns VBox status code. + * @param hThreadSelf Thread handle, unused. + * @param pvUser Embedded switch state. + */ +static DECLCALLBACK(int) vboxIntNetSwitchTestAcceptThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + PVBOXINTNETSWITCHTEST pTest = (PVBOXINTNETSWITCHTEST)pvUser; + AssertPtrReturn(pTest, VERR_INVALID_POINTER); + AssertReturn(pTest->u32Magic == VBOXINTNETSWITCHTEST_MAGIC, VERR_INVALID_HANDLE); + + for (;;) + { + RTLOCALIPCSESSION hClient = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcServerListen(pTest->hServer, &hClient); +#ifdef RT_OS_WINDOWS + if (rc == VERR_TRY_AGAIN) + { + RTThreadSleep(10); + continue; + } +#endif + if (RT_FAILURE(rc)) + break; + if (ASMAtomicReadBool(&pTest->fStop)) + { + RTLocalIpcSessionClose(hClient); + break; + } + if (!intnetR3TryReserveSlot(&g_DevExt.cRefs, g_DevExt.cMaxConnections)) + { + RTLocalIpcSessionClose(hClient); + RTThreadSleep(10); + continue; + } + + PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession)); + if (pSession == NULL) + { + RTLocalIpcSessionClose(hClient); + intnetR3ReleaseConnectionSlot(&g_DevExt); + continue; + } + + pSession->pDevExt = &g_DevExt; + pSession->hIpcSession = hClient; + pSession->hThread = NIL_RTTHREAD; + pSession->hIpcPokeThread = NIL_RTTHREAD; + pSession->hIpcPokeEvt = NIL_RTSEMEVENT; + pSession->fIpcPokeStopping = false; + pSession->hIpcIoMtx = NIL_RTSEMMUTEX; + int rcThread = RTSemMutexCreate(&pSession->hIpcIoMtx); + if (RT_SUCCESS(rcThread)) + rcThread = RTSemEventCreate(&pSession->hIpcPokeEvt); + if (RT_FAILURE(rcThread)) + { + intnetR3SessionDestroy(pSession); + continue; + } + + if (!intnetR3TryReserveSlots(&g_DevExt.cThreads, g_DevExt.cMaxThreads, + INTNETR3_THREADS_PER_LOCALIPC_SESSION)) + { + intnetR3SessionDestroy(pSession); + RTThreadSleep(10); + continue; + } + + rcThread = RTThreadCreate(&pSession->hIpcPokeThread, intnetR3LocalIpcPokeThread, pSession, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "DhcpPoke"); + if (RT_FAILURE(rcThread)) + { + intnetR3SessionDestroy(pSession); + ASMAtomicSubU32(&g_DevExt.cThreads, INTNETR3_THREADS_PER_LOCALIPC_SESSION); + continue; + } + rcThread = RTThreadCreate(&pSession->hThread, intnetR3LocalIpcSessionThread, pSession, 0 /*cbStack*/, + RTTHREADTYPE_IO, 0 /*fFlags*/, "DhcpIntNet"); + if (RT_FAILURE(rcThread)) + { + intnetR3SessionDestroy(pSession); + ASMAtomicDecU32(&g_DevExt.cThreads); /* Reserved request worker was never started. */ + } + } + return VINF_SUCCESS; +} + + +/** + * Releases an embedded switch after all service threads have stopped. + * + * @param pTest Embedded switch state. + */ +static void vboxIntNetSwitchTestFree(PVBOXINTNETSWITCHTEST pTest) +{ + AssertPtrReturnVoid(pTest); + Assert(pTest->hServer == NIL_RTLOCALIPCSERVER); + Assert(pTest->hAcceptor == NIL_RTTHREAD); + Assert(ASMAtomicReadU32(&g_DevExt.cRefs) == 0); + + if (pTest->fIntNetInited) + { + IntNetR0Term(); + pTest->fIntNetInited = false; + } + RTCritSectDelete(&g_DevExt.CritSect); + RTEnvUnset("VBOX_INTNET_R3_SVC_NAME"); + + pTest->u32Magic = VBOXINTNETSWITCHTEST_MAGIC_DEAD; + RTMemFree(pTest); +} + + +/** + * Starts the IntNet switch using a unique local IPC service name. + * + * @returns VBox status code. + * @param ppvHandle Where to return the opaque embedded switch handle. + */ +extern "C" int VBoxIntNetSwitchTestStart(void **ppvHandle) +{ + AssertPtrReturn(ppvHandle, VERR_INVALID_POINTER); + *ppvHandle = NULL; + + PVBOXINTNETSWITCHTEST pTest = (PVBOXINTNETSWITCHTEST)RTMemAllocZ(sizeof(*pTest)); + if (pTest == NULL) + return VERR_NO_MEMORY; + pTest->u32Magic = VBOXINTNETSWITCHTEST_MAGIC; + pTest->hServer = NIL_RTLOCALIPCSERVER; + pTest->hAcceptor = NIL_RTTHREAD; + + int rc = RTCritSectInit(&g_DevExt.CritSect); + if (RT_FAILURE(rc)) + { + RTMemFree(pTest); + return rc; + } + g_DevExt.pObjs = NULL; + intnetR3InitLimits(&g_DevExt); + + rc = IntNetR0Init(); + if (RT_SUCCESS(rc)) + pTest->fIntNetInited = true; + if (RT_SUCCESS(rc)) + { + RTUUID Uuid; + char szUuid[RTUUID_STR_LENGTH]; + rc = RTUuidCreate(&Uuid); + if (RT_SUCCESS(rc)) + rc = RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pTest->szService, sizeof(pTest->szService), "tst-vboxnetdhcp-%s", szUuid); + if (cch <= 0 || (size_t)cch >= sizeof(pTest->szService)) + rc = VERR_BUFFER_OVERFLOW; + } + } + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SVC_NAME", pTest->szService); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcServerCreate(&pTest->hServer, pTest->szService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + rc = RTThreadCreate(&pTest->hAcceptor, vboxIntNetSwitchTestAcceptThread, pTest, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "DhcpIntNet0"); + if (RT_SUCCESS(rc)) + { + *ppvHandle = pTest; + return VINF_SUCCESS; + } + + if (pTest->hServer != NIL_RTLOCALIPCSERVER) + { + RTLocalIpcServerDestroy(pTest->hServer); + pTest->hServer = NIL_RTLOCALIPCSERVER; + } + vboxIntNetSwitchTestFree(pTest); + return rc; +} + + +/** + * Stops and destroys the embedded production IntNet switch. + * + * @returns VBox status code. + * @param pvHandle Opaque handle returned by VBoxIntNetSwitchTestStart. + */ +extern "C" int VBoxIntNetSwitchTestStop(void *pvHandle) +{ + PVBOXINTNETSWITCHTEST pTest = (PVBOXINTNETSWITCHTEST)pvHandle; + AssertPtrReturn(pTest, VERR_INVALID_HANDLE); + AssertReturn(pTest->u32Magic == VBOXINTNETSWITCHTEST_MAGIC, VERR_INVALID_HANDLE); + + int rc = VINF_SUCCESS; + if (pTest->hServer != NIL_RTLOCALIPCSERVER) + { + ASMAtomicWriteBool(&pTest->fStop, true); + + /* Wake the blocking server listen with a local dummy connection. */ + RTLOCALIPCSESSION hTmp = NIL_RTLOCALIPCSESSION; + int rc2 = RTLocalIpcSessionConnect(&hTmp, pTest->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc2)) + RTLocalIpcSessionClose(hTmp); + + rc2 = RTLocalIpcServerDestroy(pTest->hServer); + if (RT_FAILURE(rc2)) + rc = rc2; + pTest->hServer = NIL_RTLOCALIPCSERVER; + } + + if (pTest->hAcceptor != NIL_RTTHREAD) + { + int rcThread = VINF_SUCCESS; + int rc2 = RTThreadWait(pTest->hAcceptor, RT_MS_5SEC, &rcThread); + if (RT_FAILURE(rc2)) + return rc2; + pTest->hAcceptor = NIL_RTTHREAD; + if (RT_FAILURE(rcThread) && RT_SUCCESS(rc)) + rc = rcThread; + } + + uint64_t const msStart = RTTimeMilliTS(); + while ( ( ASMAtomicReadU32(&g_DevExt.cRefs) != 0 + || ASMAtomicReadU32(&g_DevExt.cThreads) != 0) + && RTTimeMilliTS() - msStart < RT_MS_5SEC) + RTThreadSleep(1); + if ( ASMAtomicReadU32(&g_DevExt.cRefs) != 0 + || ASMAtomicReadU32(&g_DevExt.cThreads) != 0) + return VERR_TIMEOUT; + + vboxIntNetSwitchTestFree(pTest); + return rc; +} diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/VBoxNetDhcpdInProc.cpp b/src/VBox/NetworkServices/Dhcpd/testcase/VBoxNetDhcpdInProc.cpp new file mode 100644 index 000000000000..2afaeea28bbe --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/testcase/VBoxNetDhcpdInProc.cpp @@ -0,0 +1,268 @@ +/* $Id: VBoxNetDhcpdInProc.cpp 115072 2026-08-19 07:32:45Z andreas.loeffler@oracle.com $ */ +/** @file + * In-process VBoxNetDHCP source shim for tstVBoxNetDhcpd. + * + * This guarantees that VBoxNetDhcpd.cpp is compiled with the testcase hooks, + * independent of target-global preprocessor definitions on a specific kBuild + * host/toolchain combination. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOXNETDHCPD_INPROC_TESTING +# define VBOXNETDHCPD_INPROC_TESTING 1 +#endif + +#include "../VBoxNetDhcpd.cpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Valid in-process DHCP daemon handle magic. */ +#define VBOXNETDHCPDTEST_MAGIC UINT32_C(0x44484350) +/** Invalidated in-process DHCP daemon handle magic. */ +#define VBOXNETDHCPDTEST_MAGIC_DEAD UINT32_C(0x64686370) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** In-process VBoxNetDHCP testcase context. */ +typedef struct VBOXNETDHCPDTEST +{ + /** Magic value (VBOXNETDHCPDTEST_MAGIC). */ + uint32_t u32Magic; + /** Daemon instance. */ + VBoxNetDhcpd *pDhcpd; + /** Waitable daemon thread. */ + RTTHREAD hThread; + /** Daemon startup completion event. */ + RTSEMEVENT hEvtStartup; + /** Duplicated argument count. */ + int cArgs; + /** Duplicated argument vector. */ + char **papszArgs; +} VBOXNETDHCPDTEST; +typedef VBOXNETDHCPDTEST *PVBOXNETDHCPDTEST; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** + * Releases an in-process daemon context after its thread has stopped. + * + * @param pTest In-process daemon context. + */ +static void vboxNetDhcpdTestFree(PVBOXNETDHCPDTEST pTest) +{ + AssertPtrReturnVoid(pTest); + Assert(pTest->hThread == NIL_RTTHREAD); + + Config *pConfig = pTest->pDhcpd != NULL ? pTest->pDhcpd->testTakeConfig() : NULL; + delete pTest->pDhcpd; + pTest->pDhcpd = NULL; + delete pConfig; + + if (pTest->hEvtStartup != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pTest->hEvtStartup); + pTest->hEvtStartup = NIL_RTSEMEVENT; + } + + if (pTest->papszArgs != NULL) + { + for (int i = 0; i < pTest->cArgs; i++) + RTStrFree(pTest->papszArgs[i]); + RTMemFree(pTest->papszArgs); + pTest->papszArgs = NULL; + } + + pTest->u32Magic = VBOXNETDHCPDTEST_MAGIC_DEAD; + RTMemFree(pTest); +} + + +/** + * Runs the production VBoxNetDHCP daemon in-process. + * + * @returns VBox status code. + * @param hThreadSelf Thread handle, unused. + * @param pvUser In-process daemon context. + */ +static DECLCALLBACK(int) vboxNetDhcpdTestThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + PVBOXNETDHCPDTEST pTest = (PVBOXNETDHCPDTEST)pvUser; + AssertPtrReturn(pTest, VERR_INVALID_POINTER); + AssertReturn(pTest->u32Magic == VBOXNETDHCPDTEST_MAGIC, VERR_INVALID_HANDLE); + + int rc = pTest->pDhcpd->main(pTest->cArgs, pTest->papszArgs); + if (rc == VERR_SEM_DESTROYED) + rc = VINF_SUCCESS; + return rc; +} + + +/** + * Duplicates an argument vector for the daemon thread. + * + * @returns VBox status code. + * @param pTest In-process daemon context. + * @param argc Argument count. + * @param argv Argument vector. + */ +static int vboxNetDhcpdTestDupArgv(PVBOXNETDHCPDTEST pTest, int argc, char **argv) +{ + pTest->papszArgs = (char **)RTMemAllocZ((argc + 1) * sizeof(pTest->papszArgs[0])); + if (pTest->papszArgs == NULL) + return VERR_NO_MEMORY; + + for (int i = 0; i < argc; i++) + { + AssertPtrReturn(argv[i], VERR_INVALID_POINTER); + pTest->papszArgs[i] = RTStrDup(argv[i]); + if (pTest->papszArgs[i] == NULL) + return VERR_NO_MEMORY; + pTest->cArgs = i + 1; + } + return VINF_SUCCESS; +} + + +/** + * Starts VBoxNetDHCP in-process and waits for its IntNet/lwIP initialization. + * + * @returns VBox status code. + * @param argc Argument count for VBoxNetDHCP. + * @param argv Argument vector for VBoxNetDHCP. + * @param ppvHandle Where to return the opaque daemon handle. + */ +extern "C" int VBoxNetDhcpdTestStart(int argc, char **argv, void **ppvHandle) +{ + AssertPtrReturn(ppvHandle, VERR_INVALID_POINTER); + *ppvHandle = NULL; + AssertReturn(argc > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(argv, VERR_INVALID_POINTER); + + PVBOXNETDHCPDTEST pTest = (PVBOXNETDHCPDTEST)RTMemAllocZ(sizeof(*pTest)); + if (pTest == NULL) + return VERR_NO_MEMORY; + pTest->u32Magic = VBOXNETDHCPDTEST_MAGIC; + pTest->hThread = NIL_RTTHREAD; + pTest->hEvtStartup = NIL_RTSEMEVENT; + + int rc = vboxNetDhcpdTestDupArgv(pTest, argc, argv); + if (RT_SUCCESS(rc)) + rc = RTSemEventCreate(&pTest->hEvtStartup); + if (RT_SUCCESS(rc)) + { + pTest->pDhcpd = new (std::nothrow) VBoxNetDhcpd(); + if (pTest->pDhcpd == NULL) + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + pTest->pDhcpd->testSetStartupEvent(pTest->hEvtStartup); + rc = RTThreadCreate(&pTest->hThread, vboxNetDhcpdTestThread, pTest, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "DhcpdTest"); + } + if (RT_FAILURE(rc)) + { + vboxNetDhcpdTestFree(pTest); + return rc; + } + + rc = RTSemEventWait(pTest->hEvtStartup, RT_MS_30SEC); + if (RT_SUCCESS(rc)) + rc = pTest->pDhcpd->testQueryStartupStatus(); + if (RT_SUCCESS(rc)) + { + *ppvHandle = pTest; + return VINF_SUCCESS; + } + + int rcThread = VINF_SUCCESS; + int rcWait = RTThreadWait(pTest->hThread, RT_MS_30SEC, &rcThread); + if (RT_SUCCESS(rcWait)) + { + pTest->hThread = NIL_RTTHREAD; + vboxNetDhcpdTestFree(pTest); + } + else + *ppvHandle = pTest; + return rc; +} + + +/** + * Stops and destroys an in-process VBoxNetDHCP daemon. + * + * @returns VBox status code. + * @param pvHandle Opaque daemon handle returned by VBoxNetDhcpdTestStart. + */ +extern "C" int VBoxNetDhcpdTestStop(void *pvHandle) +{ + PVBOXNETDHCPDTEST pTest = (PVBOXNETDHCPDTEST)pvHandle; + AssertPtrReturn(pTest, VERR_INVALID_HANDLE); + AssertReturn(pTest->u32Magic == VBOXNETDHCPDTEST_MAGIC, VERR_INVALID_HANDLE); + + int rc = pTest->pDhcpd->testStop(); + int rcThread = VINF_SUCCESS; + int rcWait = RTThreadWait(pTest->hThread, RT_MS_30SEC, &rcThread); + if (RT_FAILURE(rcWait)) + return rcWait; + + pTest->hThread = NIL_RTTHREAD; + vboxNetDhcpdTestFree(pTest); + if (RT_FAILURE(rc)) + return rc; + return rcThread; +} + + +/** + * Checks whether an in-process VBoxNetDHCP daemon is running. + * + * @returns true if the daemon is running, false otherwise. + * @param pvHandle Opaque daemon handle returned by VBoxNetDhcpdTestStart. + */ +extern "C" bool VBoxNetDhcpdTestIsRunning(void *pvHandle) +{ + PVBOXNETDHCPDTEST pTest = (PVBOXNETDHCPDTEST)pvHandle; + if ( pTest == NULL + || pTest->u32Magic != VBOXNETDHCPDTEST_MAGIC) + return false; + return pTest->pDhcpd->testIsRunning(); +} From 0cf6efb6b7eb2d95d757eacec29aaa4056f84339 Mon Sep 17 00:00:00 2001 From: Teknomancer Date: Wed, 19 Aug 2026 10:00:47 +0000 Subject: [PATCH 164/176] VMM: Merge GitHub PR 811: AVIC work-in-progress, github:gh-811 github:gh-808 Includes commits: * VMM: SVM AVIC work-in-progress, continuing work done by Alexander Eichner on the SVM AVIC implementation. * VMM/target-x86: SVM AVIC, include the PDMHasApic check in fUseAvic. Logging nits. * VMM: SVM AVIC work-in-progress. Ubuntu 26 live-CD now boots with single VCPU using the AVIC. * VMM/target-x86: Fix APIC interrupt delivery when SVM AVIC is used. Fixed a typo (% vs &) in hmR0SvmExitAvicNoAccel. Ubuntu 26 VM single-VCPU now boots fully without manually triggering an interrupt, like for example pressing a key. * VMM/target-x86: Fix incorrect force-flag clearing when retreiving the interrupt from the APIC when AMD AVIC is enabled. This fixes the SMP issue while booting Ubuntu 26 VMs with AVIC. * VMM/HM: Don't yet enable AVIC by default, needs more testing. * VMM/APIC: Fix error handling when an illegal vector is sent via an IPI. This is for the upcoming fix in the SVM AVIC incomplete IPI #VMEXIT handler. Tested this change booting Ubuntu 10.04-3 amd64 live CD to ensure it doesn't cause a regression there * VMM/APIC: Comment typo. * VMM/HM: SVM AVIC: Incomplete IPI #VMEXIT nits. Handle invalid target similar to invalid interrupt type and only kick VCPUs if the failure reason is 'target not running', fail other cases. * VMM/target-x86: Added missing stub functions for pfnUpdateApicAfterWrite and pfnSetEoiFast for the NEM Windows and Linux x86 backends. * VMM/target-x86: APIC: Removed temporary additional debug logging that wasn't meant to be committed. github-merge-author: Teknomancer svn:sync-xref-src-repo-rev: r174915 --- include/VBox/err.h | 4 + include/VBox/vmm/hm.h | 1 + include/VBox/vmm/hm_svm.h | 19 +- include/VBox/vmm/pdmapic.h | 49 +- include/iprt/x86.h | 2 + src/VBox/VMM/VMMAll/PDMAll.cpp | 15 +- .../VMM/VMMAll/target-x86/APICAll-x86.cpp | 220 ++++++--- .../target-x86/IEMAllCImplSvmInstr-x86.cpp | 14 +- .../VMM/VMMAll/target-x86/PDMAllApic-x86.cpp | 37 +- src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp | 3 +- src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp | 435 ++++++++++++++++-- src/VBox/VMM/VMMR3/EMR3.cpp | 7 +- src/VBox/VMM/VMMR3/EMR3HM.cpp | 4 +- src/VBox/VMM/VMMR3/TRPMR3.cpp | 5 +- src/VBox/VMM/VMMR3/target-x86/APICR3-x86.cpp | 8 +- .../VMMR3/target-x86/APICR3Nem-linux-x86.cpp | 26 +- .../VMMR3/target-x86/APICR3Nem-win-x86.cpp | 26 +- src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp | 37 +- src/VBox/VMM/include/APICInternal.h | 8 +- src/VBox/VMM/include/EMHandleRCTmpl.h | 6 +- src/VBox/VMM/include/HMInternal.h | 36 +- src/VBox/VMM/include/HMInternal.mac | 5 +- 22 files changed, 853 insertions(+), 114 deletions(-) diff --git a/include/VBox/err.h b/include/VBox/err.h index 069dd188ab8b..b5d0bbadf086 100644 --- a/include/VBox/err.h +++ b/include/VBox/err.h @@ -3143,6 +3143,10 @@ #define VERR_APIC_IPE_1 (-6706) /** APIC internal error \#2. */ #define VERR_APIC_IPE_2 (-6707) +/** Update the APIC state in R3 after an unaccelerated write (AVIC/APICv). */ +#define VINF_APIC_R3_UPDATE_STATE 6708 +/** Pending interrupt deferred for delivery by hardware (AVIC/APICv). */ +#define VERR_APIC_INTR_DEFER (-6709) /** @} */ /** @name NEM Status Codes diff --git a/include/VBox/vmm/hm.h b/include/VBox/vmm/hm.h index 6bd25b889b29..36a546a83c07 100644 --- a/include/VBox/vmm/hm.h +++ b/include/VBox/vmm/hm.h @@ -300,6 +300,7 @@ VMMR3DECL(bool) HMR3IsEnabled(PUVM pUVM); VMMR3DECL(bool) HMR3IsNestedPagingActive(PUVM pUVM); VMMR3DECL(bool) HMR3AreVirtApicRegsEnabled(PUVM pUVM); VMMR3DECL(bool) HMR3IsPostedIntrsEnabled(PUVM pUVM); +VMMR3DECL(bool) HMR3IsAvicEnabled(PUVM pUVM); VMMR3DECL(bool) HMR3IsVpidActive(PUVM pUVM); VMMR3DECL(bool) HMR3IsUXActive(PUVM pUVM); VMMR3DECL(bool) HMR3IsSvmEnabled(PUVM pUVM); diff --git a/include/VBox/vmm/hm_svm.h b/include/VBox/vmm/hm_svm.h index 95d5bf59689e..e7a0c404425c 100644 --- a/include/VBox/vmm/hm_svm.h +++ b/include/VBox/vmm/hm_svm.h @@ -65,6 +65,8 @@ #define SVM_MSRPM_PAGES 2 /** Number of pages required for the IO permission bitmap. */ #define SVM_IOPM_PAGES 3 +/** Number of pages required for the AVIC per-VM (APIC access, logical, physical tables). */ +#define SVM_AVIC_PAGES 3 /** @} */ /* @@ -337,6 +339,20 @@ #endif /* !IN_REM_R3*/ +/** @name SVMVMCB.u64ExitInfo2 for AVIC Incomplete IPI. + * @{ + */ +#define SVM_EXIT2_INC_IPI_INDEX_MASK UINT64_C(0xfff) +#define SVM_EXIT2_INC_IPI_ID_SHIFT 32 +#define SVM_EXIT2_INC_IPI_INDEX_INVALID_INTR_TYPE 0 +#define SVM_EXIT2_INC_IPI_INDEX_TARGET_NOT_RUNNING 1 +#define SVM_EXIT2_INC_IPI_INDEX_INVALID_TARGET 2 +#define SVM_EXIT2_INC_IPI_INDEX_INVALID_PTR 3 +#define SVM_EXIT2_INC_IPI_INDEX_INVALID_IPI_VECTOR 4 +#define SVM_EXIT2_INC_IPI_INDEX_UNACCEL_IPI 5 +/** @} */ + + /** @name SVMVMCB.u64ExitInfo2 for task switches * @{ */ @@ -627,7 +643,8 @@ typedef union uint32_t u3Reserved : 3; uint32_t u1VIntrMasking : 1; /* V_INTR_MASKING */ uint32_t u1VGifEnable : 1; /* VGIF enable */ - uint32_t u5Reserved : 5; + uint32_t u4Reserved : 4; + uint32_t u1X2AvicEnable : 1; /* X2AVIC enable */ uint32_t u1AvicEnable : 1; /* AVIC enable */ uint32_t u8VIntrVector : 8; /* V_INTR_VECTOR */ uint32_t u24Reserved : 24; diff --git a/include/VBox/vmm/pdmapic.h b/include/VBox/vmm/pdmapic.h index feacab90e3b0..3c23fe29dfeb 100644 --- a/include/VBox/vmm/pdmapic.h +++ b/include/VBox/vmm/pdmapic.h @@ -362,6 +362,26 @@ typedef struct PDMAPICBACKENDR3 */ DECLR3CALLBACKMEMBER(VBOXSTRICTRC, pfnExportState, (PVMCPUCC pVCpu)); + /** + * Updates the APIC state after a write to the APIC page by hardware. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * + * @note This is a helper for AVIC/APICv when used on AMD or Intel. + */ + DECLR3CALLBACKMEMBER(VBOXSTRICTRC, pfnUpdateStateAfterWrite, (PVMCPUCC pVCpu, uint16_t offApicReg)); + + /** + * Sets the End-Of-Interrupt (EOI) register when the vector corresponding + * to the EOI is given. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector for the attempted EOI. + */ + DECLR3CALLBACKMEMBER(VBOXSTRICTRC, pfnSetEoiFast, (PVMCPUCC pVCpu, uint8_t uVector)); + /** @name Reserved for future (MBZ). * @{ */ DECLR3CALLBACKMEMBER(int, pfnReserved0, (void)); @@ -370,8 +390,6 @@ typedef struct PDMAPICBACKENDR3 DECLR3CALLBACKMEMBER(int, pfnReserved3, (void)); DECLR3CALLBACKMEMBER(int, pfnReserved4, (void)); DECLR3CALLBACKMEMBER(int, pfnReserved5, (void)); - DECLR3CALLBACKMEMBER(int, pfnReserved6, (void)); - DECLR3CALLBACKMEMBER(int, pfnReserved7, (void)); /** @} */ } PDMAPICBACKENDR3; /** Pointer to ring-3 APIC backend. */ @@ -624,10 +642,31 @@ typedef struct PDMAPICBACKENDR0 * Exports the APIC state. * * @returns Strict VBox status code. - * @param pVCpu The cross context virtual CPU structure. + * @param pVCpu The cross context virtual CPU structure. + * @param offApicReg The APIC register offset which was updated. */ DECLR0CALLBACKMEMBER(VBOXSTRICTRC, pfnExportState, (PVMCPUCC pVCpu)); + /** + * Updates the APIC state after a write to the APIC page by hardware. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * + * @note This is a helper for AVIC/APICv when used on AMD or Intel. + */ + DECLR0CALLBACKMEMBER(VBOXSTRICTRC, pfnUpdateStateAfterWrite, (PVMCPUCC pVCpu, uint16_t offApicReg)); + + /** + * Sets the End-Of-Interrupt (EOI) register when the vector corresponding + * to the EOI is given. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector for the attempted EOI. + */ + DECLR0CALLBACKMEMBER(VBOXSTRICTRC, pfnSetEoiFast, (PVMCPUCC pVCpu, uint8_t uVector)); + /** @name Reserved for future (MBZ). * @{ */ DECLR0CALLBACKMEMBER(int, pfnReserved0, (void)); @@ -636,8 +675,6 @@ typedef struct PDMAPICBACKENDR0 DECLR0CALLBACKMEMBER(int, pfnReserved3, (void)); DECLR0CALLBACKMEMBER(int, pfnReserved4, (void)); DECLR0CALLBACKMEMBER(int, pfnReserved5, (void)); - DECLR0CALLBACKMEMBER(int, pfnReserved6, (void)); - DECLR0CALLBACKMEMBER(int, pfnReserved7, (void)); /** @} */ } PDMAPICBACKENDR0; /** Pointer to ring-0 APIC backend. */ @@ -946,6 +983,8 @@ VMM_INT_DECL(int) PDMApicSetBaseMsr(PVMCPUCC pVCpu, uint64_t u64BaseMs VMM_INT_DECL(int) PDMApicGetInterrupt(PVMCPUCC pVCpu, uint8_t *pu8Vector, uint32_t *puSrcTag); VMM_INT_DECL(int) PDMApicBusDeliver(PVMCC pVM, uint8_t uDest, uint8_t uDestMode, uint8_t uDeliveryMode, uint8_t uVector, uint8_t uPolarity, uint8_t uTriggerMode, uint8_t uIoApicPin, uint32_t uTagSrc); +VMM_INT_DECL(VBOXSTRICTRC) PDMApicUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg); +VMM_INT_DECL(VBOXSTRICTRC) PDMApicSetEoiFast(PVMCPUCC pVCpu, uint8_t uIsrVector); #ifdef IN_RING0 VMM_INT_DECL(int) PDMR0ApicGetApicPageForCpu(PCVMCPUCC pVCpu, PRTHCPHYS pHCPhys, PRTR0PTR pR0Ptr, PRTR3PTR pR3Ptr); #endif diff --git a/include/iprt/x86.h b/include/iprt/x86.h index 07885368277d..8f657106d280 100644 --- a/include/iprt/x86.h +++ b/include/iprt/x86.h @@ -2395,6 +2395,8 @@ typedef const X86MTRRVAR *PCX86MTRRVAR; /** SVM - VM_HSAVE_PA - Physical address for saving and restoring * host state during world switch. */ #define MSR_K8_VM_HSAVE_PA UINT32_C(0xc0010117) +/** SVM - AVIC doorbell register. */ +#define MSR_AMD_AVIC_DOORBELL UINT32_C(0xc001011b) /** Virtualized speculation control for AMD processors. * diff --git a/src/VBox/VMM/VMMAll/PDMAll.cpp b/src/VBox/VMM/VMMAll/PDMAll.cpp index a4dcd6bcf04f..744e35c1b747 100644 --- a/src/VBox/VMM/VMMAll/PDMAll.cpp +++ b/src/VBox/VMM/VMMAll/PDMAll.cpp @@ -1,4 +1,4 @@ -/* $Id: PDMAll.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: PDMAll.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * PDM Critical Sections */ @@ -72,18 +72,23 @@ VMMDECL(int) PDMGetInterrupt(PVMCPUCC pVCpu, uint8_t *pu8Interrupt) int rc = VERR_NO_DATA; if (VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC)) { - VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_INTERRUPT_APIC); - uint32_t uTagSrc; rc = PDMApicGetInterrupt(pVCpu, pu8Interrupt, &uTagSrc); if (RT_SUCCESS(rc)) { VBOXVMM_PDM_IRQ_GET(pVCpu, RT_LOWORD(uTagSrc), RT_HIWORD(uTagSrc), *pu8Interrupt); Log8(("PDMGetInterrupt: irq=%#x tag=%#x (apic)\n", *pu8Interrupt, uTagSrc)); + VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_INTERRUPT_APIC); return VINF_SUCCESS; } - /* else if it's masked by TPR/PPR/whatever, go ahead checking the PIC. Such masked - interrupts shouldn't prevent ExtINT from being delivered. */ + + /* + * If it's masked by TPR/PPR/whatever, go ahead checking the PIC. Such masked + * interrupts shouldn't prevent ExtINT from being delivered. If the interrupt is + * deferred for delivery by the hardware, do -not- clear the force-flag here. + */ + if (rc != VERR_APIC_INTR_DEFER) + VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_INTERRUPT_APIC); } PVMCC pVM = pVCpu->CTX_SUFF(pVM); diff --git a/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp b/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp index 28140537f65e..723df8a3cc1e 100644 --- a/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp +++ b/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: APICAll-x86.cpp 114733 2026-07-21 06:02:36Z alexander.eichner@oracle.com $ */ +/* $Id: APICAll-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller - All Contexts. */ @@ -608,14 +608,19 @@ static VBOXSTRICTRC apicSendIntr(PVMCC pVM, PVMCPUCC pVCpu, uint8_t uVector, XAP && pVCpu) { /* - * Flag only errors when the delivery mode is fixed and not others. + * Flag only errors when the delivery mode is fixed or lowest-priority and not + * others. This applies to ICR and self-IPI in both xAPIC and x2APIC modes. + * - Intel: Documented under "Error Status Register (ESR)" in the + * Intel spec. "13.5.3 Error Handling". + * - AMD: Documented under "APICx280 [Error Status] (ErrorStatus)" in the CPU + * specific manual (e.g. "Processor Programming Reference (PPR) for + * AMD Family 17h Model 01h, Revision B1 Processors". * * Ubuntu 10.04-3 amd64 live CD with 2 VCPUs gets upset as it sends an SIPI to the * 2nd VCPU with vector 6 and checks the ESR for no errors, see @bugref{8245#c86}. */ - /** @todo The spec says this for LVT, but not explcitly for ICR-lo - * but it probably is true. */ - if (enmDeliveryMode == XAPICDELIVERYMODE_FIXED) + if ( enmDeliveryMode == XAPICDELIVERYMODE_FIXED + || enmDeliveryMode == XAPICDELIVERYMODE_LOWEST_PRIO) { if (RT_UNLIKELY(uVector <= XAPIC_ILLEGAL_VECTOR_END)) apicSetError(pVCpu, XAPIC_ESR_SEND_ILLEGAL_VECTOR); @@ -765,6 +770,7 @@ static VBOXSTRICTRC apicSetIcrLo(PVMCPUCC pVCpu, uint32_t uIcrLo, int rcRZ, bool STAM_COUNTER_INC(&pVCpu->apic.s.StatIcrLoWrite); RT_NOREF(fUpdateStat); + Assert(!(pXApicPage->icr_lo.all.u32IcrLo & XAPIC_LVT_DELIVERY_STATUS)); return apicSendIpi(pVCpu, rcRZ); } @@ -905,6 +911,65 @@ static int apicSetTprEx(PVMCPUCC pVCpu, uint32_t uTpr, bool fForceX2ApicBehaviou } +/** + * Helper for processing an EOI when the vector corresponding to the EOI is + * given. + * + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector for attempted EOI. This is the highest + * in-service vector found in the ISR. + */ +static void apicProcessEoi(PVMCPUCC pVCpu, uint8_t uVector) +{ + /* + * Broadcast the EOI to the I/O APIC(s). + * + * We'll handle the EOI broadcast first as there is tiny chance we get rescheduled to + * ring-3 due to contention on the I/O APIC lock. This way we don't mess with the rest + * of the APIC state and simply restart the EOI write operation from ring-3. + */ + PXAPICPAGE pXApicPage = VMCPU_TO_XAPICPAGE(pVCpu); + bool const fLevelTriggered = apicTestVectorInReg(&pXApicPage->tmr, uVector); + if (fLevelTriggered) + { + PDMIoApicBroadcastEoi(pVCpu->CTX_SUFF(pVM), uVector); + + /* + * Clear the vector from the TMR. + * + * The broadcast to I/O APIC can re-trigger new interrupts to arrive via the bus. However, + * apicUpdatePendingInterrupts() which updates TMR can only be done from EMT which we + * currently are on, so no possibility of concurrent updates. + */ + apicClearVectorInReg(&pXApicPage->tmr, uVector); + + /* + * Clear the remote IRR bit for level-triggered, fixed mode LINT0 interrupt. + * The LINT1 pin does not support level-triggered interrupts. + * See Intel spec. 10.5.1 "Local Vector Table". + */ + uint32_t const uLvtLint0 = pXApicPage->lvt_lint0.all.u32LvtLint0; + if ( XAPIC_LVT_GET_REMOTE_IRR(uLvtLint0) + && XAPIC_LVT_GET_VECTOR(uLvtLint0) == uVector + && XAPIC_LVT_GET_DELIVERY_MODE(uLvtLint0) == XAPICDELIVERYMODE_FIXED) + { + ASMAtomicAndU32((volatile uint32_t *)&pXApicPage->lvt_lint0.all.u32LvtLint0, ~XAPIC_LVT_REMOTE_IRR); + Log2(("APIC%u: apicSetEoi: Cleared remote-IRR for LINT0. uVector=%#x\n", pVCpu->idCpu, uVector)); + } + + Log2(("APIC%u: apicSetEoi: Cleared level triggered interrupt from TMR. uVector=%#x\n", pVCpu->idCpu, uVector)); + } + + /* + * Mark interrupt as serviced, update the PPR and signal pending interrupts. + */ + Log2(("APIC%u: apicSetEoi: Clearing interrupt from ISR. uVector=%#x\n", pVCpu->idCpu, uVector)); + apicClearVectorInReg(&pXApicPage->isr, uVector); + apicUpdatePpr(pVCpu); + apicSignalNextPendingIntr(pVCpu); +} + + /** * Sets the End-Of-Interrupt (EOI) register. * @@ -930,53 +995,9 @@ static DECLCALLBACK(VBOXSTRICTRC) apicSetEoi(PVMCPUCC pVCpu, uint32_t uEoi, bool int isrv = apicGetHighestSetBitInReg(&pXApicPage->isr, -1 /* rcNotFound */); if (isrv >= 0) { - /* - * Broadcast the EOI to the I/O APIC(s). - * - * We'll handle the EOI broadcast first as there is tiny chance we get rescheduled to - * ring-3 due to contention on the I/O APIC lock. This way we don't mess with the rest - * of the APIC state and simply restart the EOI write operation from ring-3. - */ Assert(isrv <= (int)UINT8_MAX); - uint8_t const uVector = isrv; - bool const fLevelTriggered = apicTestVectorInReg(&pXApicPage->tmr, uVector); - if (fLevelTriggered) - { - PDMIoApicBroadcastEoi(pVCpu->CTX_SUFF(pVM), uVector); - - /* - * Clear the vector from the TMR. - * - * The broadcast to I/O APIC can re-trigger new interrupts to arrive via the bus. However, - * apicUpdatePendingInterrupts() which updates TMR can only be done from EMT which we - * currently are on, so no possibility of concurrent updates. - */ - apicClearVectorInReg(&pXApicPage->tmr, uVector); - - /* - * Clear the remote IRR bit for level-triggered, fixed mode LINT0 interrupt. - * The LINT1 pin does not support level-triggered interrupts. - * See Intel spec. 10.5.1 "Local Vector Table". - */ - uint32_t const uLvtLint0 = pXApicPage->lvt_lint0.all.u32LvtLint0; - if ( XAPIC_LVT_GET_REMOTE_IRR(uLvtLint0) - && XAPIC_LVT_GET_VECTOR(uLvtLint0) == uVector - && XAPIC_LVT_GET_DELIVERY_MODE(uLvtLint0) == XAPICDELIVERYMODE_FIXED) - { - ASMAtomicAndU32((volatile uint32_t *)&pXApicPage->lvt_lint0.all.u32LvtLint0, ~XAPIC_LVT_REMOTE_IRR); - Log2(("APIC%u: apicSetEoi: Cleared remote-IRR for LINT0. uVector=%#x\n", pVCpu->idCpu, uVector)); - } - - Log2(("APIC%u: apicSetEoi: Cleared level triggered interrupt from TMR. uVector=%#x\n", pVCpu->idCpu, uVector)); - } - - /* - * Mark interrupt as serviced, update the PPR and signal pending interrupts. - */ - Log2(("APIC%u: apicSetEoi: Clearing interrupt from ISR. uVector=%#x\n", pVCpu->idCpu, uVector)); - apicClearVectorInReg(&pXApicPage->isr, uVector); - apicUpdatePpr(pVCpu); - apicSignalNextPendingIntr(pVCpu); + uint8_t const uVector = isrv; + apicProcessEoi(pVCpu, uVector); } else { @@ -992,6 +1013,30 @@ static DECLCALLBACK(VBOXSTRICTRC) apicSetEoi(PVMCPUCC pVCpu, uint32_t uEoi, bool } +/** + * Sets the End-Of-Interrupt (EOI) register when the ISR is already known. + * + * This is an optimization that lets us avoid scanning the 256-bit sparse ISR + * register figuring out the highest pending in-service vector. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector for attempted EOI. This is the vector for the + * highest in-service vector. + * + * @note It it assumed the caller (hardware in the case of SVM AVIC) has + * already validated the value written to the EOI register. + */ +static DECLCALLBACK(VBOXSTRICTRC) apicSetEoiFast(PVMCPUCC pVCpu, uint8_t uVector) +{ + VMCPU_ASSERT_EMT(pVCpu); + Log2(("APIC%u: apicSetEoiFast: uEoi=%#RX32 uVector=%#x\n", pVCpu->idCpu, uVector)); + STAM_COUNTER_INC(&pVCpu->apic.s.StatEoiWriteFast); + apicProcessEoi(pVCpu, uVector); + return VINF_SUCCESS; +} + + /** * Sets the Logical Destination Register (LDR). * @@ -1554,7 +1599,7 @@ static DECLCALLBACK(VBOXSTRICTRC) apicReadMsr(PVMCPUCC pVCpu, uint32_t u32Reg, u * Validate. */ VMCPU_ASSERT_EMT(pVCpu); - Assert(u32Reg >= MSR_IA32_X2APIC_ID && u32Reg <= MSR_IA32_X2APIC_SELF_IPI); + AssertMsg(u32Reg >= MSR_IA32_X2APIC_ID && u32Reg <= MSR_IA32_X2APIC_SELF_IPI, ("u32Reg=%#x\n", u32Reg)); Assert(pu64Value); /* @@ -2388,6 +2433,20 @@ static DECLCALLBACK(int) apicGetInterrupt(PVMCPUCC pVCpu, uint8_t *pu8Vector, ui LogFlow(("APIC%u: apicGetInterrupt:\n", pVCpu->idCpu)); + /* + * When SVM AVIC is in use, we only deliver PIC-style interrupts here. + * Other interrupts are updated in the APIC page by the usual mechanism + * and picked up by the hardware without explicit event injection. + */ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + PCAPIC pApic = VM_TO_APIC(pVM); + if (pApic->fAvicEnabled) + { + *pu8Vector = 0; + *puSrcTag = 0; + return VERR_APIC_INTR_DEFER; + } + PXAPICPAGE pXApicPage = VMCPU_TO_XAPICPAGE(pVCpu); bool const fApicHwEnabled = apicIsEnabled(pVCpu); if ( fApicHwEnabled @@ -2616,7 +2675,8 @@ DECLCALLBACK(bool) apicPostInterrupt(PVMCPUCC pVCpu, uint8_t uVector, XAPICTRIGG uint32_t uSrcTag) { Assert(pVCpu); - Assert(uVector > XAPIC_ILLEGAL_VECTOR_END); + AssertMsg(uVector > XAPIC_ILLEGAL_VECTOR_END, ("uVector=%#x, IcrLo=%#RX32 IcrHi=%#RX32\n", uVector, + VMCPU_TO_CX2APICPAGE(pVCpu)->icr_lo.all.u32IcrLo, VMCPU_TO_CX2APICPAGE(pVCpu)->icr_hi.u32IcrHi)); RT_NOREF(fAutoEoi); PVMCC pVM = pVCpu->CTX_SUFF(pVM); @@ -2835,7 +2895,7 @@ static DECLCALLBACK(void) apicUpdatePendingInterrupts(PVMCPUCC pVCpu) PXAPICPAGE pXApicPage = VMCPU_TO_XAPICPAGE(pVCpu); bool fHasPendingIntrs = false; - Log3(("APIC%u: apicUpdatePendingInterrupts:\n", pVCpu->idCpu)); + Log2(("APIC%u: apicUpdatePendingInterrupts:\n", pVCpu->idCpu)); STAM_PROFILE_START(&pApicCpu->StatUpdatePendingIntrs, a); /* Update edge-triggered pending interrupts. */ @@ -2949,6 +3009,56 @@ static DECLCALLBACK(VBOXSTRICTRC) apicExportState(PVMCPUCC pVCpu) } +/** + * @interface_method_impl{PDMAPICBACKENDR0,pfnUpdateStateAfterWrite} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicVBoxUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg) +{ + AssertReturn(pVCpu, VERR_INVALID_PARAMETER); + + Assert(PDMHasApic(pVCpu->CTX_SUFF(pVM))); + + /* + * In SVM, vAPIC registers are 32-bits wide and currently the two 64-bit accesses + * (Self-IPI, and ICR) are both trap-like accesses meaning the the higher 32 bits + * are already updated. + */ + PPDMDEVINS pDevIns = VMCPU_TO_DEVINS(pVCpu); + VBOXSTRICTRC rcStrict; + if (XAPIC_IN_X2APIC_MODE(pVCpu->apic.s.uApicBaseMsr)) + { + /* This is the conversion documented by AMD in 16.11.1 "x2APIC Register Address Space". */ + uint32_t const idMsr = MSR_IA32_X2APIC_START + (offApicReg >> 4); + + /* + * We shouldn't be getting called with reading just the ICR_HI bits in x2APIC mode. + * If we do, we just forward the error to the guest, like normal x2APIC operation. + * The assert is thus debug only. + */ + Assert(offApicReg != XAPIC_OFF_ICR_HI); + + uint64_t u64Value = 0; + rcStrict = apicReadMsr(pVCpu, idMsr, &u64Value); + if (rcStrict == VINF_SUCCESS) + rcStrict = apicWriteMsr(pVCpu, idMsr, u64Value); + if ( rcStrict == VINF_CPUM_R3_MSR_READ + || rcStrict == VINF_CPUM_R3_MSR_WRITE) + rcStrict = VINF_APIC_R3_UPDATE_STATE; + } + else + { + uint32_t u32Value = 0; + rcStrict = apicReadRegister(pDevIns, pVCpu, offApicReg, &u32Value); + if (rcStrict == VINF_SUCCESS) + rcStrict = apicWriteRegister(pDevIns, pVCpu, offApicReg, u32Value); + if ( rcStrict == VINF_IOM_R3_MMIO_READ + || rcStrict == VINF_IOM_R3_MMIO_WRITE) + rcStrict = VINF_APIC_R3_UPDATE_STATE; + } + return rcStrict; +} + + #ifndef IN_RING3 /** @@ -3083,5 +3193,7 @@ const PDMAPICBACKEND g_ApicBackend = #endif /* .pfnImportState = */ apicImportState, /* .pfnExportState = */ apicExportState, + /* .pfnUpdateStateAfterWrite = */ apicVBoxUpdateStateAfterWrite, + /* .pfnSetEoiFast = */ apicSetEoiFast, }; diff --git a/src/VBox/VMM/VMMAll/target-x86/IEMAllCImplSvmInstr-x86.cpp b/src/VBox/VMM/VMMAll/target-x86/IEMAllCImplSvmInstr-x86.cpp index f23bfcc4808c..4c7c1b9ed832 100644 --- a/src/VBox/VMM/VMMAll/target-x86/IEMAllCImplSvmInstr-x86.cpp +++ b/src/VBox/VMM/VMMAll/target-x86/IEMAllCImplSvmInstr-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: IEMAllCImplSvmInstr-x86.cpp 113745 2026-04-07 10:06:17Z alexander.eichner@oracle.com $ */ +/* $Id: IEMAllCImplSvmInstr-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * IEM - AMD-V (Secure Virtual Machine) instruction implementation (x86 target). */ @@ -482,7 +482,7 @@ static VBOXSTRICTRC iemSvmVmrun(PVMCPUCC pVCpu, uint8_t cbInstr, RTGCPHYS GCPhys pVmcbCtrl->TLBCtrl.n.u24Reserved = 0; pVmcbCtrl->IntCtrl.n.u6Reserved = 0; pVmcbCtrl->IntCtrl.n.u3Reserved = 0; - pVmcbCtrl->IntCtrl.n.u5Reserved = 0; + pVmcbCtrl->IntCtrl.n.u4Reserved = 0; pVmcbCtrl->IntCtrl.n.u24Reserved = 0; pVmcbCtrl->IntShadow.n.u30Reserved = 0; pVmcbCtrl->ExitIntInfo.n.u19Reserved = 0; @@ -516,6 +516,16 @@ static VBOXSTRICTRC iemSvmVmrun(PVMCPUCC pVCpu, uint8_t cbInstr, RTGCPHYS GCPhys pVmcbCtrl->IntCtrl.n.u1AvicEnable = 0; } + /* X2AVIC. */ + if ( pVmcbCtrl->IntCtrl.n.u1X2AvicEnable + && ( !pVmcbCtrl->IntCtrl.n.u1AvicEnable + || !pVM->cpum.ro.GuestFeatures.fSvmX2Avic + || !pVM->cpum.ro.GuestFeatures.fSvmAvic)) + { + Log(("iemSvmVmrun: X2AVIC not supported or trying to be enabled without XAVIC -> #VMEXIT\n")); + return iemSvmVmexit(pVCpu, SVM_EXIT_INVALID, 0 /* uExitInfo1 */, 0 /* uExitInfo2 */); + } + /* Last branch record (LBR) virtualization. */ if ( pVmcbCtrl->LbrVirt.n.u1LbrVirt && !pVM->cpum.ro.GuestFeatures.fSvmLbrVirt) diff --git a/src/VBox/VMM/VMMAll/target-x86/PDMAllApic-x86.cpp b/src/VBox/VMM/VMMAll/target-x86/PDMAllApic-x86.cpp index 9a03588e365b..a9865ceac039 100644 --- a/src/VBox/VMM/VMMAll/target-x86/PDMAllApic-x86.cpp +++ b/src/VBox/VMM/VMMAll/target-x86/PDMAllApic-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: PDMAllApic-x86.cpp 112682 2026-01-25 17:10:52Z alexander.eichner@oracle.com $ */ +/* $Id: PDMAllApic-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * PDM - APIC (Advanced Programmable Interrupt Controller) Interface. */ @@ -464,6 +464,39 @@ VMM_INT_DECL(VBOXSTRICTRC) PDMApicExportState(PVMCPUCC pVCpu) } +/** + * Updates the APIC state after a write to the APIC page by hardware. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param offApicReg The APIC register offset which was updated. + * + * @note This is a helper for AVIC/APICv when used on AMD or Intel. + */ +VMM_INT_DECL(VBOXSTRICTRC) PDMApicUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg) +{ + AssertReturn(PDMCPU_TO_APICBACKEND(pVCpu)->pfnUpdateStateAfterWrite, VERR_INVALID_POINTER); + return PDMCPU_TO_APICBACKEND(pVCpu)->pfnUpdateStateAfterWrite(pVCpu, offApicReg); +} + + +/** + * Sets the End-Of-Interrupt (EOI) register when the ISR is already known. + * + * This is an optimization that lets us avoid scanning the 256-bit sparse ISR + * register figuring out the highest pending in-service vector. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector (highest ISR) for the attempted EOI. + */ +VMM_INT_DECL(VBOXSTRICTRC) PDMApicSetEoiFast(PVMCPUCC pVCpu, uint8_t uVector) +{ + AssertReturn(PDMCPU_TO_APICBACKEND(pVCpu)->pfnSetEoiFast, VERR_INVALID_POINTER); + return PDMCPU_TO_APICBACKEND(pVCpu)->pfnSetEoiFast(pVCpu, uVector); +} + + /** * Registers a PDM APIC backend. * @@ -506,6 +539,8 @@ VMM_INT_DECL(int) PDMApicRegisterBackend(PVMCC pVM, PDMAPICBACKENDTYPE enmBacken #elif defined(IN_RING0) AssertPtrReturn(pBackend->pfnGetApicPageForCpu, VERR_INVALID_POINTER); #endif + AssertPtrReturn(pBackend->pfnUpdateStateAfterWrite, VERR_INVALID_POINTER); + AssertPtrReturn(pBackend->pfnSetEoiFast, VERR_INVALID_POINTER); /* * Register the backend. diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp index 76ac8981ad13..c0cf3309dfb7 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0-x86.cpp 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ */ +/* $Id: HMR0-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * Hardware Assisted Virtualization Manager (HM) - Host Context Ring-0. */ @@ -861,6 +861,7 @@ static int hmR0EnableCpu(PVMCC pVM, RTCPUID idCpu) Assert(!RTThreadPreemptIsEnabled(NIL_RTTHREAD)); pHostCpu->idCpu = idCpu; + pHostCpu->idApic = ASMGetApicId(); /* Do NOT reset cTlbFlushes here, see @bugref{6255}. */ int rc; diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp index 80c250c15fe7..3f0337412e9d 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0SVM-x86.cpp 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ */ +/* $Id: HMR0SVM-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * HM SVM (AMD-V) - Host Context Ring-0. */ @@ -33,6 +33,7 @@ #define VMCPU_INCL_CPUM_GST_CTX #include #include +#include #include #include @@ -380,6 +381,8 @@ static FNSVMEXITHANDLER hmR0SvmExitSwInt; static FNSVMEXITHANDLER hmR0SvmExitTrRead; static FNSVMEXITHANDLER hmR0SvmExitTrWrite; static FNSVMEXITHANDLER hmR0SvmExitBusLock; +static FNSVMEXITHANDLER hmR0SvmExitAvicIncompleteIpi; +static FNSVMEXITHANDLER hmR0SvmExitAvicNoAccel; #ifdef VBOX_WITH_NESTED_HWVIRT_SVM static FNSVMEXITHANDLER hmR0SvmExitClgi; static FNSVMEXITHANDLER hmR0SvmExitStgi; @@ -425,6 +428,56 @@ static R0PTRTYPE(void *) g_pvIOBitmap; | HMSVM_LOG_GS \ | HMSVM_LOG_LBR) +/** A list of x2APIC MSRs we don't want to intercept when using the AVIC. */ +static const uint32_t g_aX2AvicMsrs[] = +{ + MSR_IA32_X2APIC_ID, + MSR_IA32_X2APIC_VERSION, + MSR_IA32_X2APIC_TPR, + MSR_IA32_X2APIC_PPR, + MSR_IA32_X2APIC_EOI, + MSR_IA32_X2APIC_LDR, + MSR_IA32_X2APIC_SVR, + MSR_IA32_X2APIC_ISR0, + MSR_IA32_X2APIC_ISR1, + MSR_IA32_X2APIC_ISR2, + MSR_IA32_X2APIC_ISR3, + MSR_IA32_X2APIC_ISR4, + MSR_IA32_X2APIC_ISR5, + MSR_IA32_X2APIC_ISR6, + MSR_IA32_X2APIC_ISR7, + MSR_IA32_X2APIC_TMR0, + MSR_IA32_X2APIC_TMR1, + MSR_IA32_X2APIC_TMR2, + MSR_IA32_X2APIC_TMR3, + MSR_IA32_X2APIC_TMR4, + MSR_IA32_X2APIC_TMR5, + MSR_IA32_X2APIC_TMR6, + MSR_IA32_X2APIC_TMR7, + MSR_IA32_X2APIC_IRR0, + MSR_IA32_X2APIC_IRR1, + MSR_IA32_X2APIC_IRR2, + MSR_IA32_X2APIC_IRR3, + MSR_IA32_X2APIC_IRR4, + MSR_IA32_X2APIC_IRR5, + MSR_IA32_X2APIC_IRR6, + MSR_IA32_X2APIC_IRR7, + MSR_IA32_X2APIC_ESR, + MSR_IA32_X2APIC_LVT_CMCI, + MSR_IA32_X2APIC_ICR, + MSR_IA32_X2APIC_LVT_TIMER, + MSR_IA32_X2APIC_LVT_THERMAL, + MSR_IA32_X2APIC_LVT_PERF, + MSR_IA32_X2APIC_LVT_LINT0, + MSR_IA32_X2APIC_LVT_LINT1, + MSR_IA32_X2APIC_LVT_ERROR, + MSR_IA32_X2APIC_TIMER_ICR, + MSR_IA32_X2APIC_TIMER_CCR, + MSR_IA32_X2APIC_TIMER_DCR, + MSR_IA32_X2APIC_SELF_IPI +}; + + /** * Dumps virtual CPU state and additional info. to the logger for diagnostics. * @@ -665,6 +718,14 @@ VMMR0DECL(void) SVMR0GlobalTerm(void) */ DECLINLINE(void) hmR0SvmFreeStructs(PVMCC pVM) { + if (pVM->hmr0.s.svm.hMemObjAvicHost != NIL_RTR0MEMOBJ) + { + RTR0MemObjFree(pVM->hmr0.s.svm.hMemObjAvicHost, false); + pVM->hmr0.s.svm.HCPhysAvicPhysIdTbl = 0; + pVM->hmr0.s.svm.HCPhysAvicLogicalIdTbl = 0; + pVM->hmr0.s.svm.hMemObjAvicHost = NIL_RTR0MEMOBJ; + } + for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++) { PVMCPUCC pVCpu = VMCC_GET_CPU(pVM, idCpu); @@ -794,6 +855,8 @@ VMMR0DECL(int) SVMR0InitVM(PVMCC pVM) /* * Initialize the R0 memory objects up-front so we can properly cleanup on allocation failures. */ + pVM->hmr0.s.svm.hMemObjAvicHost = NIL_RTR0MEMOBJ; + for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++) { PVMCPUCC pVCpu = VMCC_GET_CPU(pVM, idCpu); @@ -802,6 +865,23 @@ VMMR0DECL(int) SVMR0InitVM(PVMCC pVM) pVCpu->hmr0.s.svm.hMemObjMsrBitmap = NIL_RTR0MEMOBJ; } + /* + * Create the physical and logical APIC ID tables if AVIC is going to be used. + */ + void *pvAvicHost = NULL; + size_t const cbAvicPages = SVM_AVIC_PAGES << HOST_PAGE_SHIFT; + rc = RTR0MemObjAllocCont(&pVM->hmr0.s.svm.hMemObjAvicHost, cbAvicPages, + NIL_RTHCPHYS /*PhysHighest*/, false /* fExecutable */); + if (RT_FAILURE(rc)) + goto failure_cleanup; + + pvAvicHost = RTR0MemObjAddress(pVM->hmr0.s.svm.hMemObjAvicHost); + RT_BZERO(pvAvicHost, cbAvicPages); + pVM->hmr0.s.svm.HCPhysAvicPhysIdTbl = RTR0MemObjGetPagePhysAddr(pVM->hmr0.s.svm.hMemObjAvicHost, 0 /* iPage */); + pVM->hmr0.s.svm.HCPhysAvicLogicalIdTbl = RTR0MemObjGetPagePhysAddr(pVM->hmr0.s.svm.hMemObjAvicHost, 1 /* iPage */); + pVM->hmr0.s.svm.HCPhysApicAccess = RTR0MemObjGetPagePhysAddr(pVM->hmr0.s.svm.hMemObjAvicHost, 2 /* iPage */); + pVM->hmr0.s.svm.paAvicPhysIdTbl = (volatile uint64_t *)pvAvicHost; + for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++) { PVMCPUCC pVCpu = VMCC_GET_CPU(pVM, idCpu); @@ -1042,6 +1122,12 @@ VMMR0DECL(int) SVMR0SetupVM(PVMCC pVM) bool const fLbrVirt = RT_BOOL(g_fHmSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_LBR_VIRT); bool const fUseLbrVirt = fLbrVirt && pVM->hm.s.svm.fLbrVirt; /** @todo IEM implementation etc. */ + bool const fAvic = RT_BOOL(g_fHmSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_AVIC); + bool const fUseAvic = fAvic && pVM->hm.s.svm.fAvic && PDMHasApic(pVM); + + //bool const fX2Avic = RT_BOOL(g_fHmSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_X2AVIC); + //bool const fUseX2Avic = fX2Avic && pVM->hm.s.svm.fAvic; + #ifdef VBOX_WITH_NESTED_HWVIRT_SVM bool const fVirtVmsaveVmload = RT_BOOL(g_fHmSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_VIRT_VMSAVE_VMLOAD); bool const fUseVirtVmsaveVmload = fVirtVmsaveVmload && pVM->hm.s.svm.fVirtVmsaveVmload && fNestedPaging; @@ -1228,6 +1314,24 @@ VMMR0DECL(int) SVMR0SetupVM(PVMCC pVM) /* Initially all VMCB clean bits MBZ indicating that everything should be loaded from the VMCB in memory. */ Assert(pVmcbCtrl0->u32VmcbCleanBits == 0); + if (fUseAvic) + { + void *pvVirtApic = NULL; + RTHCPHYS HCPhysVirtApic = 0; + int rc = PDMR0ApicGetApicPageForCpu(pVCpu0, &HCPhysVirtApic, (PRTR0PTR)&pvVirtApic, NULL /*pR3Ptr*/); + AssertRCReturn(rc, rc); + + pVmcbCtrl0->AvicBackingPagePtr.u = HCPhysVirtApic; + pVmcbCtrl0->AvicLogicalTablePtr.u = pVM->hmr0.s.svm.HCPhysAvicLogicalIdTbl; + pVmcbCtrl0->AvicPhysicalTablePtr.u = pVM->hmr0.s.svm.HCPhysAvicPhysIdTbl | (pVM->cCpus - 1); + pVmcbCtrl0->IntCtrl.n.u1AvicEnable = 1; + + pVCpu0->hmr0.s.svm.u64PhysIdEntry = RT_BIT_64(63) | HCPhysVirtApic; + pVCpu0->hm.s.svm.fUseAvic = fUseAvic; + } + else + Assert(!pVCpu0->hm.s.svm.fUseAvic); + for (VMCPUID idCpu = 1; idCpu < pVM->cCpus; idCpu++) { PVMCPUCC pVCpuCur = VMCC_GET_CPU(pVM, idCpu); @@ -1250,6 +1354,26 @@ VMMR0DECL(int) SVMR0SetupVM(PVMCC pVM) Assert(pVCpuCur->hm.s.fGIMTrapXcptUD == pVCpu0->hm.s.fGIMTrapXcptUD); /* Same for GCM, #DE trapping should be uniform across VCPUs. */ Assert(pVCpuCur->hm.s.fGCMTrapXcptDE == pVCpu0->hm.s.fGCMTrapXcptDE); + + /* Update the per-VCPU/VMCB specific fields for the AVIC. */ + if (pVCpu0->hm.s.svm.fUseAvic) + { + void *pvVirtApic = NULL; + RTHCPHYS HCPhysVirtApic = 0; + int rc = PDMR0ApicGetApicPageForCpu(pVCpuCur, &HCPhysVirtApic, (PRTR0PTR)&pvVirtApic, NULL /*pR3Ptr*/); + AssertRCReturn(rc, rc); + + pVmcbCtrlCur->AvicBackingPagePtr.u = HCPhysVirtApic; + pVCpuCur->hmr0.s.svm.u64PhysIdEntry = RT_BIT_64(63) | HCPhysVirtApic; + pVCpuCur->hm.s.svm.fUseAvic = true; + + Assert(pVmcbCtrlCur->AvicLogicalTablePtr.u == pVmcb0->ctrl.AvicLogicalTablePtr.u); + Assert(pVmcbCtrlCur->AvicPhysicalTablePtr.u == pVmcb0->ctrl.AvicPhysicalTablePtr.u); + Assert(pVmcbCtrlCur->IntCtrl.n.u1AvicEnable == pVmcb0->ctrl.IntCtrl.n.u1AvicEnable); + Assert(pVCpuCur->hm.s.svm.fUseAvic == pVCpu0->hm.s.svm.fUseAvic); + } + else + Assert(!pVCpuCur->hm.s.svm.fUseAvic); } #ifdef VBOX_WITH_NESTED_HWVIRT_SVM @@ -2181,7 +2305,8 @@ static int hmR0SvmExportGuestApicTpr(PVMCPUCC pVCpu, PSVMVMCB pVmcb) { PVMCC pVM = pVCpu->CTX_SUFF(pVM); if ( PDMHasApic(pVM) - && PDMApicIsEnabled(pVCpu)) + && PDMApicIsEnabled(pVCpu) + && !pVCpu->hm.s.svm.fUseAvic) { bool fPendingIntr; uint8_t u8Tpr; @@ -3182,8 +3307,8 @@ static VBOXSTRICTRC hmR0SvmExitToRing3(PVMCPUCC pVCpu, VBOXSTRICTRC rcExit) /* Please, no longjumps here (any logging shouldn't flush jump back to ring-3). NO LOGGING BEFORE THIS POINT! */ VMMRZCallRing3Disable(pVCpu); - Log4Func(("rcExit=%d LocalFF=%#RX64 GlobalFF=%#RX32\n", VBOXSTRICTRC_VAL(rcExit), (uint64_t)pVCpu->fLocalForcedActions, - pVCpu->CTX_SUFF(pVM)->fGlobalForcedActions)); + Log4Func(("rcExit=%d LocalFF=%#RX64 GlobalFF=%#RX32 PicInterrupt=%RTbool\n", VBOXSTRICTRC_VAL(rcExit), (uint64_t)pVCpu->fLocalForcedActions, + pVCpu->CTX_SUFF(pVM)->fGlobalForcedActions, VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_PIC))); /* We need to do this only while truly exiting the "inner loop" back to ring-3 and -not- for any longjmp to ring3. */ if (pVCpu->hm.s.Event.fPending) @@ -3695,11 +3820,11 @@ static VBOXSTRICTRC hmR0SvmEvaluatePendingEvent(PVMCPUCC pVCpu, PCSVMTRANSIENT p * * See AMD spec. 15.21.4 "Injecting Virtual (INTR) Interrupts". */ - else if ( VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC) - && !pVCpu->hm.s.fSingleInstruction) + if ( VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC) + && !pVCpu->hm.s.fSingleInstruction) { bool const fBlockInt = !pSvmTransient->fIsNestedGuest ? !(pCtx->eflags.u & X86_EFL_IF) - : CPUMIsGuestSvmPhysIntrEnabled(pVCpu, pCtx); + : CPUMIsGuestSvmPhysIntrEnabled(pVCpu, pCtx); if ( fGif && !fBlockInt && !fIntShadow) @@ -3712,28 +3837,49 @@ static VBOXSTRICTRC hmR0SvmEvaluatePendingEvent(PVMCPUCC pVCpu, PCSVMTRANSIENT p return IEMExecSvmVmexit(pVCpu, SVM_EXIT_INTR, 0, 0); } #endif - uint8_t u8Interrupt; - int rc = PDMGetInterrupt(pVCpu, &u8Interrupt); - if (RT_SUCCESS(rc)) - { - Log4(("Setting external interrupt %#x pending for injection\n", u8Interrupt)); - SVMEVENT Event; - Event.u = 0; - Event.n.u1Valid = 1; - Event.n.u8Vector = u8Interrupt; - Event.n.u3Type = SVM_EVENT_EXTERNAL_IRQ; - hmR0SvmSetPendingEvent(pVCpu, &Event, 0 /* GCPtrFaultAddress */); - } - else if (rc == VERR_APIC_INTR_MASKED_BY_TPR) + /* With the AVIC, we still need to deliver PIC style interrupts ourselves. */ + bool fGetInterrupt = true; + if (pVCpu->hm.s.svm.fUseAvic) { + if (!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_PIC)) + fGetInterrupt = false; /* - * AMD-V has no TPR thresholding feature. TPR and the force-flag will be - * updated eventually when the TPR is written by the guest. + * We clear the interrupt flag here because we are certain that all + * conditions necessary for the AVIC hardware to deliver the interrupt + * are met. */ - STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchTprMaskedIrq); + if (VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC)) + VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_INTERRUPT_APIC); + } + if (fGetInterrupt) + { + uint8_t u8Interrupt; + int rc = PDMGetInterrupt(pVCpu, &u8Interrupt); + if (RT_SUCCESS(rc)) + { + Log4(("Setting external interrupt %#x pending for injection\n", u8Interrupt)); + SVMEVENT Event; + Event.u = 0; + Event.n.u1Valid = 1; + Event.n.u8Vector = u8Interrupt; + Event.n.u3Type = SVM_EVENT_EXTERNAL_IRQ; + hmR0SvmSetPendingEvent(pVCpu, &Event, 0 /* GCPtrFaultAddress */); + } + else if (rc == VERR_APIC_INTR_MASKED_BY_TPR) + { + /* + * AMD-V has no TPR thresholding feature. TPR and the force-flag will be + * updated eventually when the TPR is written by the guest. + */ + STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchTprMaskedIrq); + Log4(("External interrupt %#x masked by TPR\n", u8Interrupt)); + } + else + { + Log4(("PDMGetInterrupt failed. rc=%Rrc\n", rc)); + STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchGuestIrq); + } } - else - STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchGuestIrq); } else if (!fGif) hmR0SvmSetCtrlIntercept(pVmcb, SVM_CTRL_INTERCEPT_STGI); @@ -3889,7 +4035,9 @@ static void hmR0SvmReportWorldSwitchError(PVMCPUCC pVCpu, int rcVMRun) Log4(("ctrl.IntCtrl.u3Reserved %#x\n", pVmcb->ctrl.IntCtrl.n.u3Reserved)); Log4(("ctrl.IntCtrl.u1VIntrMasking %#x\n", pVmcb->ctrl.IntCtrl.n.u1VIntrMasking)); Log4(("ctrl.IntCtrl.u1VGifEnable %#x\n", pVmcb->ctrl.IntCtrl.n.u1VGifEnable)); - Log4(("ctrl.IntCtrl.u5Reserved1 %#x\n", pVmcb->ctrl.IntCtrl.n.u5Reserved)); + Log4(("ctrl.IntCtrl.u4Reserved4 %#x\n", pVmcb->ctrl.IntCtrl.n.u4Reserved)); + Log4(("ctrl.IntCtrl.u1X2AvicEnable %#x\n", pVmcb->ctrl.IntCtrl.n.u1X2AvicEnable)); + Log4(("ctrl.IntCtrl.u1AvicEnable %#x\n", pVmcb->ctrl.IntCtrl.n.u1AvicEnable)); Log4(("ctrl.IntCtrl.u8VIntrVector %#x\n", pVmcb->ctrl.IntCtrl.n.u8VIntrVector)); Log4(("ctrl.IntCtrl.u24Reserved %#x\n", pVmcb->ctrl.IntCtrl.n.u24Reserved)); @@ -4083,6 +4231,47 @@ static VBOXSTRICTRC hmR0SvmCheckForceFlags(PVMCPUCC pVCpu) } +/** + * Unmaps the APIC-access page for virtualizing APIC accesses. + * + * @returns VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param GCPhysApicBase The guest-physical address of the APIC access page. + */ +static int hmR0SvmUnmapHCApicAccessPage(PVMCPUCC pVCpu, RTGCPHYS GCPhysApicBase) +{ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + Assert(GCPhysApicBase); + + Log4Func(("Unaliasing any existing mapping to the HC APIC-access page at %#RGp\n", GCPhysApicBase)); + return PGMHandlerPhysicalReset(pVM, GCPhysApicBase); +} + + +/** + * Map the APIC-access page for virtualizing APIC accesses. + * + * This can cause a longjumps to R3 due to the acquisition of the PGM lock. Hence, + * this not done as part of exporting guest state, see @bugref{8721}. + * + * @returns VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param GCPhysApicBase The guest-physical address of the APIC access page. + */ +static int hmR0SvmMapHCApicAccessPage(PVMCPUCC pVCpu, RTGCPHYS GCPhysApicBase) +{ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + Assert(GCPhysApicBase); + + Log4Func(("Mapping HC APIC-access page at %#RGp\n", GCPhysApicBase)); + + int const rc = IOMR0MmioMapMmioHCPage(pVM, pVCpu, GCPhysApicBase, pVM->hmr0.s.svm.HCPhysApicAccess, + X86_PTE_RW | X86_PTE_P); + AssertRCReturn(rc, rc); + return VINF_SUCCESS; +} + + /** * Does the preparations before executing guest code in AMD-V. * @@ -4150,6 +4339,57 @@ static VBOXSTRICTRC hmR0SvmPreRunGuest(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransie ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_ALL_GUEST); #endif + /* + * Setup the AVIC state if enabled. + */ + if (pVCpu->hm.s.svm.fUseAvic) + { + Assert(PDMHasApic(pVM)); + + /* Get the APIC base MSR from the virtual APIC device. */ + uint64_t const uApicBaseMsr = PDMApicGetBaseMsrNoCheck(pVCpu); + Assert( MSR_IA32_APICBASE_GET_ADDR(uApicBaseMsr) == MSR_IA32_APICBASE_ADDR + || !(uApicBaseMsr & MSR_IA32_APICBASE_EN)); + if (uApicBaseMsr != pVCpu->hm.s.svm.u64GstMsrApicBase) + { + PSVMVMCB pVmcb = pVCpu->hmr0.s.svm.pVmcb; + + /* Unalias any existing mapping. */ + RTGCPHYS const GCPhysApic = uApicBaseMsr & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK; + hmR0SvmUnmapHCApicAccessPage(pVCpu, GCPhysApic); + Log4(("Unaliasing any previous AVIC backing page mappings at %#RGp\n", GCPhysApic)); + + /* XAPIC. */ + if (uApicBaseMsr & MSR_IA32_APICBASE_EN) + { + rc = hmR0SvmMapHCApicAccessPage(pVCpu, GCPhysApic); + AssertRCReturn(rc, rc); + Log4(("Mapped AVIC backing page at %#RGp\n", GCPhysApic)); + } + + /* X2APIC. */ + { + /* If enabled don't intercept X2APIC MSRs as the intercept has higher priority than the AVIC hardware. */ + bool const fX2AvicEnable = (uApicBaseMsr & (MSR_IA32_APICBASE_EN | MSR_IA32_APICBASE_EXTD)) + == (MSR_IA32_APICBASE_EN | MSR_IA32_APICBASE_EXTD); + SVMMSREXITREAD const fRdPerm = fX2AvicEnable ? SVMMSREXIT_PASSTHRU_READ : SVMMSREXIT_INTERCEPT_READ; + SVMMSREXITWRITE const fWrPerm = fX2AvicEnable ? SVMMSREXIT_PASSTHRU_WRITE : SVMMSREXIT_INTERCEPT_WRITE; + + uint8_t *pbMsrBitmap = (uint8_t *)pVCpu->hmr0.s.svm.pvMsrBitmap; + for (uint32_t i = 0; i < RT_ELEMENTS(g_aX2AvicMsrs); i++) + hmR0SvmSetMsrPermission(pVCpu, pbMsrBitmap, g_aX2AvicMsrs[i], fRdPerm, fWrPerm); + pVmcb->ctrl.IntCtrl.n.u1X2AvicEnable = fX2AvicEnable; + } + + pVmcb->ctrl.AvicBar.u = MSR_IA32_APICBASE_GET_ADDR(uApicBaseMsr); + pVmcb->ctrl.u32VmcbCleanBits &= ~HMSVM_VMCB_CLEAN_AVIC; + /** @todo Do we need to flush the TLB with SVM_TLB_FLUSH_SINGLE_CONTEXT here? */ + + /* Update the per-VCPU cache of the APIC base MSR corresponding to the mapped APIC access page. */ + pVCpu->hm.s.svm.u64GstMsrApicBase = uApicBaseMsr; + } + } + #ifdef VBOX_WITH_NESTED_HWVIRT_SVM /* * Set up the nested-guest VMCB for execution using hardware-assisted SVM. @@ -4287,6 +4527,15 @@ static void hmR0SvmPreRunGuestCommitted(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransi pSvmTransient->fWasGuestDebugStateActive = CPUMIsGuestDebugStateActive(pVCpu); pSvmTransient->fWasHyperDebugStateActive = CPUMIsHyperDebugStateActive(pVCpu); + /* Set AVIC state. */ + if ( pVCpu->hm.s.svm.fUseAvic + && PDMHasApic(pVM)) /** @todo Check where we can merge the PDMHasApic() call into fUseAvic. */ + { + ASMAtomicUoWriteU64(&pVM->hmr0.s.svm.paAvicPhysIdTbl[pVCpu->idCpu], pVCpu->hmr0.s.svm.u64PhysIdEntry /*| RT_BIT_64(62) | pHostCpu->idApic*/); + if (fMigratedHostCpu) + pVCpu->hmr0.s.fForceTLBFlush = true; /* Need to flush the TLB with SVM_TLB_FLUSH_SINGLE_CONTEXT */ + } + #ifdef VBOX_WITH_NESTED_HWVIRT_SVM uint8_t *pbMsrBitmap; if (!pSvmTransient->fIsNestedGuest) @@ -4421,6 +4670,19 @@ static void hmR0SvmPostRunGuest(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransient, VBO ASMSetFlags(pSvmTransient->fEFlags); /* Enable interrupts. */ VMMRZCallRing3Enable(pVCpu); /* It is now safe to do longjmps to ring-3!!! */ + /* Set AVIC state. */ + if ( pVCpu->hm.s.svm.fUseAvic + && PDMHasApic(pVM)) /** @todo Check where we can merge the PDMHasApic() call into fUseAvic. */ + { + ASMAtomicUoWriteU64(&pVM->hmr0.s.svm.paAvicPhysIdTbl[pVCpu->idCpu], pVCpu->hmr0.s.svm.u64PhysIdEntry); /* Clear IsRunning bit. */ + /* + * It's possible VMCPU_FF_INTERRUPT_APIC might be pending here if interrupts were seen as disabled + * (or interrupt shadow was active) in hmR0SvmEvaluatePendingEvent. There is no easy way to detect + * if the interrupt was delivered by the AVIC hardware. Leaving the force-flag pending here should + * be fine (an extra check the next time around, mostly harmless). + */ + } + /* If VMRUN failed, we can bail out early. This does -not- cover SVM_EXIT_INVALID. */ if (RT_UNLIKELY(rcVMRun != VINF_SUCCESS)) { @@ -4514,6 +4776,7 @@ static VBOXSTRICTRC hmR0SvmRunGuestCodeNormal(PVMCPUCC pVCpu, uint32_t *pcLoops) SvmTransient.fUpdateTscOffsetting = true; SvmTransient.pVmcb = pVCpu->hmr0.s.svm.pVmcb; + Log2Func(("\n")); VBOXSTRICTRC rc = VERR_INTERNAL_ERROR_5; for (;;) { @@ -5421,6 +5684,8 @@ static VBOXSTRICTRC hmR0SvmHandleExit(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransien case SVM_EXIT_XSETBV: VMEXIT_CALL_RET(0, hmR0SvmExitXsetbv(pVCpu, pSvmTransient)); case SVM_EXIT_FERR_FREEZE: VMEXIT_CALL_RET(0, hmR0SvmExitFerrFreeze(pVCpu, pSvmTransient)); case SVM_EXIT_BUSLOCK: VMEXIT_CALL_RET(0, hmR0SvmExitBusLock(pVCpu, pSvmTransient)); + case SVM_EXIT_AVIC_INCOMPLETE_IPI: VMEXIT_CALL_RET(0, hmR0SvmExitAvicIncompleteIpi(pVCpu, pSvmTransient)); + case SVM_EXIT_AVIC_NOACCEL: VMEXIT_CALL_RET(0, hmR0SvmExitAvicNoAccel(pVCpu, pSvmTransient)); default: { @@ -9043,6 +9308,124 @@ HMSVM_EXIT_DECL hmR0SvmExitBusLock(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransient) } +/** + * \#VMEXIT handler for AVIC incomplete IPI delivery operations due to a halting vCPU. + * Conditional \#VMEXIT. + */ +HMSVM_EXIT_DECL hmR0SvmExitAvicIncompleteIpi(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransient) +{ + HMSVM_VALIDATE_EXIT_HANDLER_PARAMS(pVCpu, pSvmTransient); + STAM_REL_COUNTER_INC(&pVCpu->hm.s.StatSvmExitAvicIncompleteIpi); + + uint64_t const u64ExitInfo1 = pSvmTransient->pVmcb->ctrl.u64ExitInfo1; + uint32_t const u32ApicIcrLo = RT_LO_U32(u64ExitInfo1); + uint32_t const u32ApicIcrHi = RT_HI_U32(u64ExitInfo1); + + uint64_t const u64ExitInfo2 = pSvmTransient->pVmcb->ctrl.u64ExitInfo2; + uint32_t const idApic = (uint32_t)(u64ExitInfo2 & SVM_EXIT2_INC_IPI_INDEX_MASK); + uint32_t const idFailure = (uint32_t)(u64ExitInfo2 >> SVM_EXIT2_INC_IPI_ID_SHIFT); + + Log2Func(("\n")); + Log4Func(("AVICExitIncompleteIpi/%u: ICRL=%#x ICRH=%#x idApic=%#x idFailure=%#x\n", + pVCpu->idCpu, u32ApicIcrLo, u32ApicIcrHi, idApic, idFailure)); + if ( idFailure == SVM_EXIT2_INC_IPI_INDEX_INVALID_INTR_TYPE + || idFailure == SVM_EXIT2_INC_IPI_INDEX_INVALID_TARGET) + { + pVCpu->hm.s.offApicReg = XAPIC_OFF_ICR_LO; + return PDMApicUpdateStateAfterWrite(pVCpu, XAPIC_OFF_ICR_LO); + } + + if (idFailure == SVM_EXIT2_INC_IPI_INDEX_TARGET_NOT_RUNNING) + { + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + PVMCPUCC pVCpuDst = pVM->CTX_SUFF(apCpus)[idApic]; + VMCPUID idCpu = pVCpuDst->idCpu; + if (VMMGetCpuId(pVM) != idCpu) + { + switch (VMCPU_GET_STATE(pVCpuDst)) + { + case VMCPUSTATE_STARTED_EXEC: + Log7Func(("idCpu=%u VMCPUSTATE_STARTED_EXEC\n", idCpu)); + GVMMR0SchedPokeNoGVMNoLock(pVM, idCpu); + break; + + case VMCPUSTATE_STARTED_HALTED: + Log7Func(("idCpu=%u VMCPUSTATE_STARTED_HALTED\n", idCpu)); + VMCPU_FF_SET(pVCpuDst, VMCPU_FF_UNHALT); + GVMMR0SchedWakeUpNoGVMNoLock(pVM, idCpu); + break; + + default: + Log7Func(("idCpu=%u enmState=%d\n", idCpu, pVCpu->enmState)); + break; /* nothing to do in other states. */ + } + } + return VINF_SUCCESS; + } + + AssertMsgFailed(("hmR0SvmExitAvicIncompleteIpi: Unexpected failure type %#x\n", idFailure)); + return VERR_SVM_IPE_4; +} + + +/** + * \#VMEXIT handler for AVIC no acceleration operations. + * Conditional \#VMEXIT. + */ +HMSVM_EXIT_DECL hmR0SvmExitAvicNoAccel(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransient) +{ + HMSVM_VALIDATE_EXIT_HANDLER_PARAMS(pVCpu, pSvmTransient); + STAM_REL_COUNTER_INC(&pVCpu->hm.s.StatSvmExitAvicNoAccel); + + uint16_t const offApicReg = pSvmTransient->pVmcb->ctrl.u64ExitInfo1 & 0xfff; + bool const fWr = RT_BOOL(pSvmTransient->pVmcb->ctrl.u64ExitInfo1 & RT_BIT_64(32)); + + /* + * Determine whether the access is fault or trap like. + * Trap like exits have the value updated in the APIC page already + * while fault like exits need emulating the instruction. + * + * Table 15-22 in chapter 15.29.3.1 (40332_4.09_APM_PUB.pdf) gives an overview. + */ + switch (offApicReg) + { + case XAPIC_OFF_ID: + case XAPIC_OFF_RRD: + case XAPIC_OFF_LDR: + case XAPIC_OFF_DFR: + case XAPIC_OFF_SVR: + case XAPIC_OFF_ESR: + case XAPIC_OFF_ICR_LO: + case XAPIC_OFF_LVT_TIMER: + case XAPIC_OFF_LVT_THERMAL: + case XAPIC_OFF_LVT_PERF: + case XAPIC_OFF_LVT_LINT0: + case XAPIC_OFF_LVT_LINT1: + case XAPIC_OFF_LVT_ERROR: + case XAPIC_OFF_TIMER_ICR: + case XAPIC_OFF_TIMER_DCR: + /** @todo Extended Interrupt Local Vector Table Registers when we start supporting it. */ + Assert(fWr); + break; + + case XAPIC_OFF_EOI: + { + uint8_t const uVector = pSvmTransient->pVmcb->ctrl.u64ExitInfo2 & 0xff; + return PDMApicSetEoiFast(pVCpu, uVector); + } + + default: + /* These accesses fault -> emulate completely and be done with it. */ + /** @todo Check whether we can speed things up with EMHistoryExec... */ + return hmR0SvmExitInterpretInstruction(pVCpu, pSvmTransient, HMSVM_CPUMCTX_EXTRN_ALL, HM_CHANGED_ALL_GUEST); + } + + Log2(("AVICNoAccelExit/%u: Trapping offApicReg=%#x\n", pVCpu->idCpu, offApicReg)); + pVCpu->hm.s.offApicReg = offApicReg; + return PDMApicUpdateStateAfterWrite(pVCpu, offApicReg); +} + + #ifdef VBOX_WITH_NESTED_HWVIRT_SVM /** * \#VMEXIT handler for CLGI (SVM_EXIT_CLGI). Conditional \#VMEXIT. diff --git a/src/VBox/VMM/VMMR3/EMR3.cpp b/src/VBox/VMM/VMMR3/EMR3.cpp index f36adaa26bc7..d5301417a05a 100644 --- a/src/VBox/VMM/VMMR3/EMR3.cpp +++ b/src/VBox/VMM/VMMR3/EMR3.cpp @@ -1,4 +1,4 @@ -/* $Id: EMR3.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: EMR3.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * EM - Execution Monitor / Manager. */ @@ -1970,9 +1970,9 @@ int emR3ForcedActions(PVM pVM, PVMCPU pVCpu, int rc) rc2 = emR3SvmNstGstIntrIntercept(pVCpu); else rc2 = VINF_NO_CHANGE; - if (rc2 == VINF_NO_CHANGE) { +#if 1 bool fInjected = false; CPUM_IMPORT_EXTRN_RET(pVCpu, IEM_CPUMCTX_EXTRN_XCPT_MASK); /** @todo this really isn't nice, should properly handle this */ @@ -1989,6 +1989,9 @@ int emR3ForcedActions(PVM pVM, PVMCPU pVCpu, int rc) if (fInjected) rcIrq = rc2; # endif +#else + rc2 = VINF_EM_RESCHEDULE; +#endif } UPDATE_RC(); } diff --git a/src/VBox/VMM/VMMR3/EMR3HM.cpp b/src/VBox/VMM/VMMR3/EMR3HM.cpp index dc5631a720cf..b7b5aee37481 100644 --- a/src/VBox/VMM/VMMR3/EMR3HM.cpp +++ b/src/VBox/VMM/VMMR3/EMR3HM.cpp @@ -1,4 +1,4 @@ -/* $Id: EMR3HM.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: EMR3HM.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * EM - Execution Monitor / Manager - hardware virtualization */ @@ -43,10 +43,12 @@ #include #include #include +#include #include #include #include #include "EMInternal.h" +#include "HMInternal.h" #include #include #include diff --git a/src/VBox/VMM/VMMR3/TRPMR3.cpp b/src/VBox/VMM/VMMR3/TRPMR3.cpp index ec482f610241..099c43a2348d 100644 --- a/src/VBox/VMM/VMMR3/TRPMR3.cpp +++ b/src/VBox/VMM/VMMR3/TRPMR3.cpp @@ -1,4 +1,4 @@ -/* $Id: TRPMR3.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: TRPMR3.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * TRPM - The Trap Monitor. */ @@ -439,7 +439,8 @@ VMMR3DECL(int) TRPMR3InjectEvent(PVM pVM, PVMCPU pVCpu, TRPMEVENT enmEvent, bool else { /* Can happen if the interrupt is masked by TPR or APIC is disabled. */ - AssertMsg(rc == VERR_APIC_INTR_MASKED_BY_TPR || rc == VERR_NO_DATA, ("PDMGetInterrupt failed. rc=%Rrc\n", rc)); + AssertMsg(rc == VERR_APIC_INTR_MASKED_BY_TPR || rc == VERR_NO_DATA || rc == VERR_APIC_INTR_DEFER, + ("PDMGetInterrupt failed. rc=%Rrc\n", rc)); } # if 0 /* HMR3IsActive is not reliable (esp. after restore), just return VINF_EM_RESCHEDULE. */ return HMR3IsActive(pVCpu) ? VINF_EM_RESCHEDULE_HM diff --git a/src/VBox/VMM/VMMR3/target-x86/APICR3-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/APICR3-x86.cpp index f0c29d3aaddf..5f29ef2b44f5 100644 --- a/src/VBox/VMM/VMMR3/target-x86/APICR3-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/APICR3-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: APICR3-x86.cpp 112727 2026-01-28 17:02:49Z alexander.eichner@oracle.com $ */ +/* $Id: APICR3-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller. */ @@ -912,9 +912,10 @@ DECLCALLBACK(int) apicR3InitComplete(PPDMDEVINS pDevIns) pApic->fSupportsTscDeadline = RT_BOOL(CpuLeaf.uEcx & X86_CPUID_FEATURE_ECX_TSCDEADL); pApic->fPostedIntrsEnabled = HMR3IsPostedIntrsEnabled(pVM->pUVM); pApic->fVirtApicRegsEnabled = HMR3AreVirtApicRegsEnabled(pVM->pUVM); + pApic->fAvicEnabled = HMR3IsAvicEnabled(pVM->pUVM); - LogRel(("APIC: fPostedIntrsEnabled=%RTbool fVirtApicRegsEnabled=%RTbool fSupportsTscDeadline=%RTbool\n", - pApic->fPostedIntrsEnabled, pApic->fVirtApicRegsEnabled, pApic->fSupportsTscDeadline)); + LogRel(("APIC: fPostedIntrsEnabled=%RTbool fVirtApicRegsEnabled=%RTbool fSupportsTscDeadline=%RTbool fAvicEnabled=%RTbool\n", + pApic->fPostedIntrsEnabled, pApic->fVirtApicRegsEnabled, pApic->fSupportsTscDeadline, pApic->fAvicEnabled)); return VINF_SUCCESS; } @@ -1094,6 +1095,7 @@ DECLCALLBACK(int) apicR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE p APIC_REG_COUNTER(&pApicCpu->StatTprWrite, "%u/TprWrite", "Number of TPR writes."); APIC_REG_COUNTER(&pApicCpu->StatTprRead, "%u/TprRead", "Number of TPR reads."); APIC_REG_COUNTER(&pApicCpu->StatEoiWrite, "%u/EoiWrite", "Number of EOI writes."); + APIC_REG_COUNTER(&pApicCpu->StatEoiWriteFast, "%u/EoiWriteFast", "Number of EOI writes using the fast path."); APIC_REG_COUNTER(&pApicCpu->StatMaskedByTpr, "%u/MaskedByTpr", "Number of times TPR masks an interrupt in apicGetInterrupt."); APIC_REG_COUNTER(&pApicCpu->StatMaskedByPpr, "%u/MaskedByPpr", "Number of times PPR masks an interrupt in apicGetInterrupt."); APIC_REG_COUNTER(&pApicCpu->StatTimerIcrWrite, "%u/TimerIcrWrite", "Number of times the timer ICR is written."); diff --git a/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-linux-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-linux-x86.cpp index f04c3ab558f9..0a4fe1b16217 100644 --- a/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-linux-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-linux-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: APICR3Nem-linux-x86.cpp 113859 2026-04-14 11:43:54Z alexander.eichner@oracle.com $ */ +/* $Id: APICR3Nem-linux-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller - NEM KVM backend. */ @@ -506,6 +506,28 @@ static DECLCALLBACK(VBOXSTRICTRC) apicR3KvmExportState(PVMCPUCC pVCpu) } +/** + * @interface_method_impl{PDMAPICBACKEND,pfnUpdateStateAfterWrite} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicR3KvmUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg) +{ + RT_NOREF(pVCpu, offApicReg); + AssertReleaseMsgFailed(("Unexpected interface call\n")); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{PDMAPICBACKEND,pfnSetEoiFast} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicR3KvmSetEoiFast(PVMCPUCC pVCpu, uint8_t uVector) +{ + RT_NOREF(pVCpu, uVector); + AssertReleaseMsgFailed(("Unexpected interface call\n")); + return VERR_NOT_SUPPORTED; +} + + /** * Dumps basic APIC state. * @@ -1356,6 +1378,8 @@ const PDMAPICBACKEND g_ApicNemBackend = /* .pfnSetHvCompatMode = */ apicR3KvmSetHvCompatMode, /* .pfnImportState = */ apicR3KvmImportState, /* .pfnExportState = */ apicR3KvmExportState, + /* .pfnUpdateStateAfterWrite = */ apicR3KvmUpdateStateAfterWrite, + /* .pfnSetEoiFast = */ apicR3KvmSetEoiFast, }; #endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-win-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-win-x86.cpp index c4e911a4831d..aa8d84599cc7 100644 --- a/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-win-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-win-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: APICR3Nem-win-x86.cpp 114734 2026-07-21 06:14:23Z alexander.eichner@oracle.com $ */ +/* $Id: APICR3Nem-win-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller - NEM Hyper-V backend. */ @@ -1064,6 +1064,28 @@ static DECLCALLBACK(VBOXSTRICTRC) apicR3HvExportState(PVMCPUCC pVCpu) } +/** + * @interface_method_impl{PDMAPICBACKEND,pfnUpdateStateAfterWrite} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicR3HvUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg) +{ + RT_NOREF(pVCpu, offApicReg); + AssertReleaseMsgFailed(("Unexpected interface call\n")); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{PDMAPICBACKEND,pfnSetEoiFast} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicR3HvSetEoiFast(PVMCPUCC pVCpu, uint8_t uVector) +{ + RT_NOREF(pVCpu, uVector); + AssertReleaseMsgFailed(("Unexpected interface call\n")); + return VERR_NOT_SUPPORTED; +} + + /** * Dumps basic APIC state. * @@ -1753,6 +1775,8 @@ const PDMAPICBACKEND g_ApicNemBackend = /* .pfnSetHvCompatMode = */ apicR3HvSetHvCompatMode, /* .pfnImportState = */ apicR3HvImportState, /* .pfnExportState = */ apicR3HvExportState, + /* .pfnUpdateStateAfterWrite = */ apicR3HvUpdateStateAfterWrite, + /* .pfnSetEoiFast = */ apicR3HvSetEoiFast, }; #endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp index ead02c27cd9e..fcdb59d1e9be 100644 --- a/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR3-x86.cpp 113496 2026-03-22 22:23:27Z knut.osmundsen@oracle.com $ */ +/* $Id: HMR3-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * HM - Intel/AMD VM Hardware Support Manager. */ @@ -281,6 +281,7 @@ VMMR3_INT_DECL(int) HMR3Init(PVM pVM) "|SvmPauseFilterThreshold" "|SvmVirtVmsaveVmload" "|SvmVGif" + "|SvmAvic" "|LovelyMesaDrvWorkaround" "|MissingOS2TlbFlushWorkaround" "|AlwaysInterceptVmxMovDRx" @@ -425,6 +426,12 @@ VMMR3_INT_DECL(int) HMR3Init(PVM pVM) rc = CFGMR3QueryBoolDef(pCfgHm, "SvmLbrVirt", &pVM->hm.s.svm.fLbrVirt, false); AssertRCReturn(rc, rc); + /** @cfgm{/HM/SvmAvic, bool, false} + * Whether to make use of the AVIC virtualization feature of the CPU if it's + * available. */ + rc = CFGMR3QueryBoolDef(pCfgHm, "SvmAvic", &pVM->hm.s.svm.fAvic, false); + AssertRCReturn(rc, rc); + /** @cfgm{/HM/Exclusive, bool} * Determines the init method for AMD-V and VT-x. If set to true, HM will do a * global init for each host CPU. If false, we do local init each time we wish @@ -991,6 +998,11 @@ static int hmR3InitFinalizeR3(PVM pVM) HM_REG_COUNTER(&pHmCpu->StatVmxPreemptionRecalcingDeadline, "/HM/CPU%u/PreemptTimer/RecalcingDeadline", "VMX-preemption timer arming logic recalculating the deadline (slightly expensive)"); HM_REG_COUNTER(&pHmCpu->StatVmxPreemptionRecalcingDeadlineExpired, "/HM/CPU%u/PreemptTimer/RecalcingDeadlineExpired", "VMX-preemption timer arming logic found recalculated deadline expired (ignored)"); } + else + { + HM_REG_COUNTER(&pHmCpu->StatSvmExitAvicIncompleteIpi, "/HM/CPU%u/AVIC/IncompleteIpi", "VM exit due to an incomplete IPI."); + HM_REG_COUNTER(&pHmCpu->StatSvmExitAvicNoAccel, "/HM/CPU%u/AVIC/UnAcceleratedAccess", "VM exit due to an unaccelerated APIC access"); + } #ifdef VBOX_WITH_STATISTICS /* * Guest Exit reason stats. @@ -2061,6 +2073,9 @@ static int hmR3InitFinalizeR0Amd(PVM pVM) if (pVM->hm.s.fPostedIntrs) LogRel(("HM: Enabled posted-interrupt processing support\n")); + if (pVM->apCpusR3[0]->hm.s.svm.fUseAvic) + LogRel(("HM: Enabled AVIC support\n")); + hmR3DisableRawMode(pVM); LogRel((pVM->hm.s.fTprPatchingAllowed ? "HM: Enabled TPR patching\n" @@ -2946,6 +2961,26 @@ VMMR3DECL(bool) HMR3IsPostedIntrsEnabled(PUVM pUVM) } +/** + * Checks if the SVM AVIC feature is enabled. + * + * This returns whether the APIC should only deliver PIC-style interrupts + * to the guest while all other interrupts are updated to the APIC page + * using the posted-interrupt bitmap. + * + * @returns @c true if SVM AVIC feature is enabled, @c false otherwise. + * @param pUVM The user mode VM handle. + */ +VMMR3DECL(bool) HMR3IsAvicEnabled(PUVM pUVM) +{ + UVM_ASSERT_VALID_EXT_RETURN(pUVM, false); + PVM pVM = pUVM->pVM; + VM_ASSERT_VALID_EXT_RETURN(pVM, false); + PCVMCPU pVCpu0 = pVM->apCpusR3[0]; + return pVCpu0->hm.s.svm.fUseAvic; +} + + /** * Checks if we are currently using VPID in VT-x mode. * diff --git a/src/VBox/VMM/include/APICInternal.h b/src/VBox/VMM/include/APICInternal.h index b92029da3dcf..48825382b501 100644 --- a/src/VBox/VMM/include/APICInternal.h +++ b/src/VBox/VMM/include/APICInternal.h @@ -1,4 +1,4 @@ -/* $Id: APICInternal.h 112683 2026-01-25 17:23:05Z alexander.eichner@oracle.com $ */ +/* $Id: APICInternal.h 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller, Internal header. */ @@ -195,6 +195,8 @@ typedef struct APIC bool fVirtApicRegsEnabled; /** Whether posted-interrupt processing is enabled. */ bool fPostedIntrsEnabled; + /** Whether the SVM AVIC feature is enabled. */ + bool fAvicEnabled; /** Whether TSC-deadline timer mode is supported for the guest. */ bool fSupportsTscDeadline; /** Whether this VM has an IO-APIC. */ @@ -211,6 +213,8 @@ typedef struct APIC * kernel load area and macOS kernel selector value (8), as we must not ever * apply this to the EFI code. */ bool fMacOSWorkaround; + /** Alignment padding. */ + bool afPadding[3]; /** The max supported APIC mode from CFGM. */ PDMAPICMODE enmMaxMode; /** @} */ @@ -336,6 +340,8 @@ typedef struct APICCPU STAMCOUNTER StatTprRead; /** Number of times the EOI is written. */ STAMCOUNTER StatEoiWrite; + /** Number of times the EOI is written in the fast path. */ + STAMCOUNTER StatEoiWriteFast; /** Number of times TPR masks an interrupt in apicGetInterrupt(). */ STAMCOUNTER StatMaskedByTpr; /** Number of times PPR masks an interrupt in apicGetInterrupt(). */ diff --git a/src/VBox/VMM/include/EMHandleRCTmpl.h b/src/VBox/VMM/include/EMHandleRCTmpl.h index 4bd10840ff95..41bb96a061bc 100644 --- a/src/VBox/VMM/include/EMHandleRCTmpl.h +++ b/src/VBox/VMM/include/EMHandleRCTmpl.h @@ -1,4 +1,4 @@ -/* $Id: EMHandleRCTmpl.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: EMHandleRCTmpl.h 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * EM - emR3[Raw|Hm|Nem]HandleRC template. */ @@ -151,6 +151,10 @@ int emR3NemHandleRC(PVM pVM, PVMCPU pVCpu, int rc) case VINF_EM_HM_PATCH_TPR_INSTR: rc = HMR3PatchTprInstr(pVM, pVCpu); break; + + case VINF_APIC_R3_UPDATE_STATE: + rc = VBOXSTRICTRC_TODO(PDMApicUpdateStateAfterWrite(pVCpu, pVCpu->hm.s.offApicReg)); + break; #endif case VINF_EM_RAW_GUEST_TRAP: diff --git a/src/VBox/VMM/include/HMInternal.h b/src/VBox/VMM/include/HMInternal.h index 2cc2712dbdbc..5222cc7564c3 100644 --- a/src/VBox/VMM/include/HMInternal.h +++ b/src/VBox/VMM/include/HMInternal.h @@ -1,4 +1,4 @@ -/* $Id: HMInternal.h 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ */ +/* $Id: HMInternal.h 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * HM - Internal header file. */ @@ -134,6 +134,8 @@ typedef struct HMPHYSCPU bool fVmxeAlreadyEnabled; /** In use by our code. (for power suspend) */ bool volatile fInUse; + /** The APIC ID of the physical CPU associated with this entry. */ + uint8_t idApic; #ifdef VBOX_WITH_NESTED_HWVIRT_SVM /** Nested-guest union (put data common to SVM/VMX outside the union). */ union @@ -321,7 +323,9 @@ typedef struct HM bool fVGif; /** Whether to use LBR virtualization feature. */ bool fLbrVirt; - bool afAlignment1[2]; + /** Whether to use the AVIC feature if available. */ + bool fAvic; + bool fAlignment1; /** Pause filter counter. */ uint16_t cPauseFilter; @@ -520,6 +524,16 @@ typedef struct HMR0PERVM { /** Set if erratum 170 affects the AMD cpu. */ bool fAlwaysFlushTLB; + /** Ring-0 memory object for per-VM SVM AVIC structures. */ + RTR0MEMOBJ hMemObjAvicHost; + /** Host physical address of the physical APIC ID table. */ + RTHCPHYS HCPhysAvicPhysIdTbl; + /** Host physical address of the logical APIC ID table. */ + RTHCPHYS HCPhysAvicLogicalIdTbl; + /** Host-physical address of the APIC-access page. */ + RTHCPHYS HCPhysApicAccess; + /** R0 pointer to the physical APIC ID table. */ + volatile uint64_t *paAvicPhysIdTbl; } svm; /** VT-x specific data. */ @@ -844,11 +858,16 @@ typedef struct HMCPU * long-mode and to intercept reads and writes to the SYSENTER MSRs in order to * preserve the upper 32 bits written to them (AMD will ignore and discard). */ bool fEmulateLongModeSysEnterExit; - uint8_t au8Alignment0[7]; + /** Flag whether to utilize AVIC hardware. */ + bool fUseAvic; + uint8_t au8Alignment0[6]; /** Cache of the nested-guest's VMCB fields that we modify in order to run the * nested-guest using AMD-V. This will be restored on \#VMEXIT. */ SVMNESTEDVMCBCACHE NstGstVmcbCache; + + /** Cached guest APIC-base MSR for identifying when to enable the AVIC if supported. */ + uint64_t u64GstMsrApicBase; } svm; /** Event injection state. */ @@ -857,7 +876,9 @@ typedef struct HMCPU /** Current shadow paging mode for updating CR4. * @todo move later (@bugref{9217}). */ PGMMODE enmShadowMode; - uint32_t u32TemporaryPadding; + uint16_t u16TemporaryPadding; + /** The APIC register offset from an unaccelerated write causing the return to R3. */ + uint16_t offApicReg; /** The PAE PDPEs used with Nested Paging (only valid when * VMCPU_FF_HM_UPDATE_PAE_PDPES is set). */ @@ -1016,6 +1037,9 @@ typedef struct HMCPU STAMCOUNTER StatVmxPreemptionReusingDeadline; STAMCOUNTER StatVmxPreemptionReusingDeadlineExpired; + STAMCOUNTER StatSvmExitAvicIncompleteIpi; + STAMCOUNTER StatSvmExitAvicNoAccel; + #ifdef VBOX_WITH_STATISTICS STAMCOUNTER aStatExitReason[MAX_EXITREASON_STAT]; STAMCOUNTER aStatNestedExitReason[MAX_EXITREASON_STAT]; @@ -1162,6 +1186,9 @@ typedef struct HMR0PERVCPU /** Host's TSC_AUX MSR (used when RDTSCP doesn't cause VM-exits). */ uint64_t u64HostTscAux; + /* The AVIC physical ID entry for this vCPU. */ + uint64_t u64PhysIdEntry; + /** For saving stack space, the disassembler state is allocated here * instead of on the stack. */ DISSTATE Dis; @@ -1173,7 +1200,6 @@ AssertCompileMemberAlignment(HMR0PERVCPU, cWorldSwitchExits, 4); AssertCompileMemberAlignment(HMR0PERVCPU, fForceTLBFlush, 4); AssertCompileMemberAlignment(HMR0PERVCPU, vmx.RestoreHost, 8); - /** @name HM_WSF_XXX - @bugref{9453}, @bugref{9087} * @note If you change these values don't forget to update the assembly * defines as well! diff --git a/src/VBox/VMM/include/HMInternal.mac b/src/VBox/VMM/include/HMInternal.mac index e8b3994bb1f5..8933ba48ff96 100644 --- a/src/VBox/VMM/include/HMInternal.mac +++ b/src/VBox/VMM/include/HMInternal.mac @@ -1,4 +1,4 @@ -;$Id: HMInternal.mac 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ +;$Id: HMInternal.mac 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ ;; @file ; HM - Internal header file. ; @@ -151,9 +151,11 @@ endstruc struc HMCPUSVM .fEmulateLongModeSysEnterExit resb 1 + .fUseAvic resb 1 alignb 8 .NstGstVmcbCache resb 40 + .u64GstMsrApicBase resq 1 endstruc struc HMCPU @@ -241,6 +243,7 @@ struc HMR0CPUSVM alignb 8 .pSvmTransient RTR0PTR_RES 1 .u64HostTscAux resq 1 + .u64PhysIdEntry resq 1 alignb 8 .Dis resb 0d8h From af02b1edb5029f67f00246ec8088dbf39481fe09 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Wed, 19 Aug 2026 10:06:25 +0000 Subject: [PATCH 165/176] VMM: Merge GitHub PR 811: AVIC work-in-progress, github:gh-811 github:gh-808 [build fix] svn:sync-xref-src-repo-rev: r174916 --- src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp index 3f0337412e9d..01d867ef0124 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0SVM-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ +/* $Id: HMR0SVM-x86.cpp 115074 2026-08-19 10:06:25Z alexander.eichner@oracle.com $ */ /** @file * HM SVM (AMD-V) - Host Context Ring-0. */ @@ -9318,8 +9318,8 @@ HMSVM_EXIT_DECL hmR0SvmExitAvicIncompleteIpi(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmT STAM_REL_COUNTER_INC(&pVCpu->hm.s.StatSvmExitAvicIncompleteIpi); uint64_t const u64ExitInfo1 = pSvmTransient->pVmcb->ctrl.u64ExitInfo1; - uint32_t const u32ApicIcrLo = RT_LO_U32(u64ExitInfo1); - uint32_t const u32ApicIcrHi = RT_HI_U32(u64ExitInfo1); + uint32_t const u32ApicIcrLo = RT_LO_U32(u64ExitInfo1); RT_NOREF(u32ApicIcrLo); + uint32_t const u32ApicIcrHi = RT_HI_U32(u64ExitInfo1); RT_NOREF(u32ApicIcrHi); uint64_t const u64ExitInfo2 = pSvmTransient->pVmcb->ctrl.u64ExitInfo2; uint32_t const idApic = (uint32_t)(u64ExitInfo2 & SVM_EXIT2_INC_IPI_INDEX_MASK); From 21442291ee61c82a7927024fb17314d7ecc97a8b Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Wed, 19 Aug 2026 10:07:31 +0000 Subject: [PATCH 166/176] VMM: Merge GitHub PR 811: AVIC work-in-progress, github:gh-811 github:gh-808 [build fix] svn:sync-xref-src-repo-rev: r174917 --- src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp index 01d867ef0124..aae355592b0d 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0SVM-x86.cpp 115074 2026-08-19 10:06:25Z alexander.eichner@oracle.com $ */ +/* $Id: HMR0SVM-x86.cpp 115075 2026-08-19 10:07:31Z alexander.eichner@oracle.com $ */ /** @file * HM SVM (AMD-V) - Host Context Ring-0. */ @@ -412,21 +412,6 @@ static RTHCPHYS g_HCPhysIOBitmap; /** Pointer to the IO bitmap. */ static R0PTRTYPE(void *) g_pvIOBitmap; -#ifdef VBOX_STRICT -# define HMSVM_LOG_RBP_RSP RT_BIT_32(0) -# define HMSVM_LOG_CR_REGS RT_BIT_32(1) -# define HMSVM_LOG_CS RT_BIT_32(2) -# define HMSVM_LOG_SS RT_BIT_32(3) -# define HMSVM_LOG_FS RT_BIT_32(4) -# define HMSVM_LOG_GS RT_BIT_32(5) -# define HMSVM_LOG_LBR RT_BIT_32(6) -# define HMSVM_LOG_ALL ( HMSVM_LOG_RBP_RSP \ - | HMSVM_LOG_CR_REGS \ - | HMSVM_LOG_CS \ - | HMSVM_LOG_SS \ - | HMSVM_LOG_FS \ - | HMSVM_LOG_GS \ - | HMSVM_LOG_LBR) /** A list of x2APIC MSRs we don't want to intercept when using the AVIC. */ static const uint32_t g_aX2AvicMsrs[] = @@ -478,6 +463,23 @@ static const uint32_t g_aX2AvicMsrs[] = }; +#ifdef VBOX_STRICT +# define HMSVM_LOG_RBP_RSP RT_BIT_32(0) +# define HMSVM_LOG_CR_REGS RT_BIT_32(1) +# define HMSVM_LOG_CS RT_BIT_32(2) +# define HMSVM_LOG_SS RT_BIT_32(3) +# define HMSVM_LOG_FS RT_BIT_32(4) +# define HMSVM_LOG_GS RT_BIT_32(5) +# define HMSVM_LOG_LBR RT_BIT_32(6) +# define HMSVM_LOG_ALL ( HMSVM_LOG_RBP_RSP \ + | HMSVM_LOG_CR_REGS \ + | HMSVM_LOG_CS \ + | HMSVM_LOG_SS \ + | HMSVM_LOG_FS \ + | HMSVM_LOG_GS \ + | HMSVM_LOG_LBR) + + /** * Dumps virtual CPU state and additional info. to the logger for diagnostics. * From 8263f73510f69350838b0e70856450f2abed8d0c Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Wed, 19 Aug 2026 10:08:30 +0000 Subject: [PATCH 167/176] VMM: Merge GitHub PR 811: AVIC work-in-progress, github:gh-811 github:gh-808 [build fix] svn:sync-xref-src-repo-rev: r174918 --- src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp index aae355592b0d..3d1e069f0308 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0SVM-x86.cpp 115075 2026-08-19 10:07:31Z alexander.eichner@oracle.com $ */ +/* $Id: HMR0SVM-x86.cpp 115076 2026-08-19 10:08:30Z alexander.eichner@oracle.com $ */ /** @file * HM SVM (AMD-V) - Host Context Ring-0. */ @@ -9380,7 +9380,7 @@ HMSVM_EXIT_DECL hmR0SvmExitAvicNoAccel(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransie STAM_REL_COUNTER_INC(&pVCpu->hm.s.StatSvmExitAvicNoAccel); uint16_t const offApicReg = pSvmTransient->pVmcb->ctrl.u64ExitInfo1 & 0xfff; - bool const fWr = RT_BOOL(pSvmTransient->pVmcb->ctrl.u64ExitInfo1 & RT_BIT_64(32)); + bool const fWr = RT_BOOL(pSvmTransient->pVmcb->ctrl.u64ExitInfo1 & RT_BIT_64(32)); RT_NOREF(fWr); /* * Determine whether the access is fault or trap like. From d6aca44c625b145a8aed021f977273ee2c51a86e Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Wed, 19 Aug 2026 10:14:49 +0000 Subject: [PATCH 168/176] Shared Clipboard/VBoxClient: Fixed a warning (init rc). bugref:4697 svn:sync-xref-src-repo-rev: r174919 --- src/VBox/Additions/x11/VBoxClient/clipboard.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp index 2d2ee57bd7b1..078887d6f125 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard.cpp 114748 2026-07-21 20:16:49Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard.cpp 115077 2026-08-19 10:14:49Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - Common Shared Clipboard wrapper service. */ @@ -107,7 +107,7 @@ static DECLCALLBACK(int) vbclShClWorker(bool volatile *pfShutdown) { RT_NOREF(pfShutdown); - int rc; + int rc = VINF_SUCCESS; g_fVBClWayland = false; VBGHDISPLAYSERVERTYPE const enmDispType = VBClGetDisplayServerTypeResolveAuto(); if (VBClClipboardShouldUseWayland(enmDispType)) From 22314bf3d8d8ca04b55f4e1d9888124910260560 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Wed, 19 Aug 2026 10:21:30 +0000 Subject: [PATCH 169/176] VMM: Merge GitHub PR 811: AVIC work-in-progress, github:gh-811 github:gh-808 [build fix] svn:sync-xref-src-repo-rev: r174920 --- src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp b/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp index 723df8a3cc1e..792cb15e54ff 100644 --- a/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp +++ b/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: APICAll-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ +/* $Id: APICAll-x86.cpp 115078 2026-08-19 10:21:30Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller - All Contexts. */ @@ -1030,7 +1030,7 @@ static DECLCALLBACK(VBOXSTRICTRC) apicSetEoi(PVMCPUCC pVCpu, uint32_t uEoi, bool static DECLCALLBACK(VBOXSTRICTRC) apicSetEoiFast(PVMCPUCC pVCpu, uint8_t uVector) { VMCPU_ASSERT_EMT(pVCpu); - Log2(("APIC%u: apicSetEoiFast: uEoi=%#RX32 uVector=%#x\n", pVCpu->idCpu, uVector)); + Log2(("APIC%u: apicSetEoiFast: uVector=%#x\n", pVCpu->idCpu, uVector)); STAM_COUNTER_INC(&pVCpu->apic.s.StatEoiWriteFast); apicProcessEoi(pVCpu, uVector); return VINF_SUCCESS; From d014f3688ec4adf5f7740ed49f19b312320ab57b Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Wed, 19 Aug 2026 11:54:29 +0000 Subject: [PATCH 170/176] VMM: Merge GitHub PR 811: AVIC work-in-progress, github:gh-811 github:gh-808 [build fix] svn:sync-xref-src-repo-rev: r174923 --- include/VBox/vmm/pdmapic.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/VBox/vmm/pdmapic.h b/include/VBox/vmm/pdmapic.h index 3c23fe29dfeb..25adb7fa1546 100644 --- a/include/VBox/vmm/pdmapic.h +++ b/include/VBox/vmm/pdmapic.h @@ -651,7 +651,8 @@ typedef struct PDMAPICBACKENDR0 * Updates the APIC state after a write to the APIC page by hardware. * * @returns Strict VBox status code. - * @param pVCpu The cross context virtual CPU structure. + * @param pVCpu The cross context virtual CPU structure. + * @param offApicReg The APIC register offset in the backing page which got updated. * * @note This is a helper for AVIC/APICv when used on AMD or Intel. */ From 8d8c59767a20f45862e49d0ee3ed6356207aaffa Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Wed, 19 Aug 2026 11:56:57 +0000 Subject: [PATCH 171/176] VMM: Merge GitHub PR 811: AVIC work-in-progress, github:gh-811 github:gh-808 [build fix] svn:sync-xref-src-repo-rev: r174924 --- include/VBox/vmm/pdmapic.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/VBox/vmm/pdmapic.h b/include/VBox/vmm/pdmapic.h index 25adb7fa1546..f1995e957dbd 100644 --- a/include/VBox/vmm/pdmapic.h +++ b/include/VBox/vmm/pdmapic.h @@ -366,7 +366,8 @@ typedef struct PDMAPICBACKENDR3 * Updates the APIC state after a write to the APIC page by hardware. * * @returns Strict VBox status code. - * @param pVCpu The cross context virtual CPU structure. + * @param pVCpu The cross context virtual CPU structure. + * @param offApicReg The APIC register offset in the backing page which got updated. * * @note This is a helper for AVIC/APICv when used on AMD or Intel. */ From 5042e984380c662d646b67c13c58fcf831285e56 Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Wed, 19 Aug 2026 12:04:11 +0000 Subject: [PATCH 172/176] Shared Clipboard/Main: Include the generated wrapper headers for tstClipboardAPI. bugref:4697 svn:sync-xref-src-repo-rev: r174925 --- src/VBox/Main/testcase/Makefile.kmk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VBox/Main/testcase/Makefile.kmk b/src/VBox/Main/testcase/Makefile.kmk index 40093e90f412..4c0cbeba5389 100644 --- a/src/VBox/Main/testcase/Makefile.kmk +++ b/src/VBox/Main/testcase/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 115058 2026-08-17 16:58:23Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115083 2026-08-19 12:04:11Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the VBox API testcases. # @@ -352,6 +352,7 @@ tstClipboardAPI_SOURCES = \ tstClipboardAPI.cpp tstClipboardAPI_LIBS = \ $(PATH_STAGE_LIB)/VBoxAPIWrap$(VBOX_SUFF_LIB) +tstClipboardAPI_INTERMEDIATES = $(VBOX_MAIN_APIWRAPPER_GEN_HDRS) ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS tstClipboardAPI_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS tstClipboardAPI_SOURCES += \ From cf5f86bb2f0ea72da7079a3a3d325cb375b26d4d Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Wed, 19 Aug 2026 12:52:58 +0000 Subject: [PATCH 173/176] VMM/HM: Implement support for APICv register virtualization on Intel, github:gh-808 svn:sync-xref-src-repo-rev: r174926 --- src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h | 29 ++++++- src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp | 54 +++++++++---- src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp | 31 ++++---- src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp | 76 ++++++++++++++++++- src/VBox/VMM/include/HMInternal.h | 39 +++++++++- src/VBox/VMM/include/VMXInternal.h | 4 +- 6 files changed, 194 insertions(+), 39 deletions(-) diff --git a/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h b/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h index 131e0c26602e..04f937b5eac1 100644 --- a/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h +++ b/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h @@ -1,4 +1,4 @@ -/* $Id: VMXAllTemplate.cpp.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VMXAllTemplate.cpp.h 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * HM VMX (Intel VT-x) - Code template for our own hypervisor and the NEM darwin backend using Apple's Hypervisor.framework. */ @@ -263,6 +263,7 @@ static FNVMXEXITHANDLER vmxHCExitMonitor; static FNVMXEXITHANDLER vmxHCExitPause; static FNVMXEXITHANDLERNSRC vmxHCExitTprBelowThreshold; static FNVMXEXITHANDLER vmxHCExitApicAccess; +static FNVMXEXITHANDLER vmxHCExitApicWrite; static FNVMXEXITHANDLER vmxHCExitEptViolation; static FNVMXEXITHANDLER vmxHCExitEptMisconfig; static FNVMXEXITHANDLER vmxHCExitRdtscp; @@ -671,7 +672,7 @@ static const struct CLANG11NOTHROWWEIRDNESS { PFNVMXEXITHANDLER pfn; } g_aVMExit #endif /* 54 VMX_EXIT_WBINVD */ { vmxHCExitWbinvd }, /* 55 VMX_EXIT_XSETBV */ { vmxHCExitXsetbv }, - /* 56 VMX_EXIT_APIC_WRITE */ { vmxHCExitErrUnexpected }, + /* 56 VMX_EXIT_APIC_WRITE */ { vmxHCExitApicWrite }, /* 57 VMX_EXIT_RDRAND */ { vmxHCExitErrUnexpected }, /* 58 VMX_EXIT_INVPCID */ { vmxHCExitInvpcid }, /* 59 VMX_EXIT_VMFUNC */ { vmxHCExitErrUnexpected }, @@ -5955,6 +5956,7 @@ DECLINLINE(VBOXSTRICTRC) vmxHCHandleExit(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTrans case VMX_EXIT_RDTSC: VMEXIT_CALL_RET(0, vmxHCExitRdtsc(pVCpu, pVmxTransient)); case VMX_EXIT_RDTSCP: VMEXIT_CALL_RET(0, vmxHCExitRdtscp(pVCpu, pVmxTransient)); case VMX_EXIT_APIC_ACCESS: VMEXIT_CALL_RET(0, vmxHCExitApicAccess(pVCpu, pVmxTransient)); + case VMX_EXIT_APIC_WRITE: VMEXIT_CALL_RET(0, vmxHCExitApicWrite(pVCpu, pVmxTransient)); case VMX_EXIT_XCPT_OR_NMI: VMEXIT_CALL_RET(0, vmxHCExitXcptOrNmi(pVCpu, pVmxTransient)); case VMX_EXIT_MOV_CRX: VMEXIT_CALL_RET(0, vmxHCExitMovCRx(pVCpu, pVmxTransient)); case VMX_EXIT_EXT_INT: VMEXIT_CALL_RET(0, vmxHCExitExtInt(pVCpu, pVmxTransient)); @@ -6022,7 +6024,6 @@ DECLINLINE(VBOXSTRICTRC) vmxHCHandleExit(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTrans case VMX_EXIT_VIRTUALIZED_EOI: case VMX_EXIT_GDTR_IDTR_ACCESS: case VMX_EXIT_LDTR_TR_ACCESS: - case VMX_EXIT_APIC_WRITE: case VMX_EXIT_RDRAND: case VMX_EXIT_RSM: case VMX_EXIT_VMFUNC: @@ -9339,6 +9340,28 @@ HMVMX_EXIT_DECL vmxHCExitApicAccess(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient) } +/** + * VM-exit handler for APIC access (VMX_EXIT_APIC_WRITE). Conditional VM-exit. + */ +HMVMX_EXIT_DECL vmxHCExitApicWrite(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient) +{ + HMVMX_VALIDATE_EXIT_HANDLER_PARAMS(pVCpu, pVmxTransient); + STAM_COUNTER_INC(&VCPU_2_VMXSTATS(pVCpu).StatExitApicWrite); + + vmxHCReadToTransient(pVCpu, pVmxTransient); + + /* + * APIC write exits are all trap like, so the instruction has already completed and + * the APIC page got updated. + */ + pVCpu->hm.s.offApicReg = pVmxTransient->uExitQual; + VBOXSTRICTRC rcStrict = PDMApicUpdateStateAfterWrite(pVCpu, pVmxTransient->uExitQual); + if (rcStrict != VINF_SUCCESS) + STAM_COUNTER_INC(&VCPU_2_VMXSTATS(pVCpu).StatSwitchApicWriteToR3); + return rcStrict; +} + + /** * VM-exit handler for debug-register accesses (VMX_EXIT_MOV_DRX). Conditional * VM-exit. diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp index c0cf3309dfb7..62009cef0f31 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ +/* $Id: HMR0-x86.cpp 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * Hardware Assisted Virtualization Manager (HM) - Host Context Ring-0. */ @@ -1298,19 +1298,45 @@ VMMR0_INT_DECL(int) HMR0InitVM(PVMCC pVM) /* Use the VMCS controls for swapping the EFER MSR if supported. */ pVM->hm.s.ForR3.vmx.fSupportsVmcsEfer = g_fHmVmxSupportsVmcsEfer; -#if 0 - /* Enable APIC register virtualization and virtual-interrupt delivery if supported. */ - if ( (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_APIC_REG_VIRT) - && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_INTR_DELIVERY)) - pVM->hm.s.fVirtApicRegs = true; - - /* Enable posted-interrupt processing if supported. */ - /** @todo Add and query IPRT API for host OS support for posted-interrupt IPI - * here. */ - if ( (g_HmMsrs.u.vmx.PinCtls.n.allowed1 & VMX_PIN_CTLS_POSTED_INT) - && (g_HmMsrs.u.vmx.ExitCtls.n.allowed1 & VMX_EXIT_CTLS_ACK_EXT_INT)) - pVM->hm.s.fPostedIntrs = true; -#endif + /* Configure the APICv level. */ + HMVMXAPICVLVL enmApicvLvl = pVM->hm.s.vmx.enmApicvLvl; + if ( enmApicvLvl >= kVmxApicvLvl_ApicAccessVirt + && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_APIC_ACCESS)) + { + if ( enmApicvLvl >= kVmxApicvLvl_ApicRegVirt + && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_APIC_REG_VIRT)) + { + pVM->hm.s.fVirtApicRegs = true; + + if ( enmApicvLvl >= kVmxApicvLvl_IntrDelivery + && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_INT_DELIVERY)) + { + + /* Enable posted-interrupt processing if supported. */ + /** @todo Add and query IPRT API for host OS support for posted-interrupt IPI + * here. */ + if ( enmApicvLvl >= kVmxApicvLvl_PostedIntrs + && (g_HmMsrs.u.vmx.PinCtls.n.allowed1 & VMX_PIN_CTLS_POSTED_INT) + && (g_HmMsrs.u.vmx.ExitCtls.n.allowed1 & VMX_EXIT_CTLS_ACK_EXT_INT)) + enmApicvLvl = kVmxApicvLvl_PostedIntrs; + else + enmApicvLvl = kVmxApicvLvl_IntrDelivery; + } + else + enmApicvLvl = kVmxApicvLvl_ApicRegVirt; + } + else + enmApicvLvl = kVmxApicvLvl_ApicAccessVirt; + } + else + enmApicvLvl = kVmxApicvLvl_None; + + /** @todo Update/remove as the implementation of the higher level features progresses. */ + if (enmApicvLvl > kVmxApicvLvl_ApicRegVirt) + enmApicvLvl = kVmxApicvLvl_ApicRegVirt; + + pVM->hmr0.s.vmx.enmApicvLvl = enmApicvLvl; + pVM->hm.s.ForR3.vmx.enmApicvLvl = enmApicvLvl; } else if (pVM->hm.s.svm.fSupported) { diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp index 9ffed9c228d8..393460b84e32 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0VMX-x86.cpp 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ */ +/* $Id: HMR0VMX-x86.cpp 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * HM VMX (Intel VT-x) - Host Context Ring-0. */ @@ -1061,7 +1061,7 @@ static int hmR0VmxStructsAlloc(PVMCC pVM) /* * Allocate per-VM VT-x structures. */ - bool const fVirtApicAccess = RT_BOOL(g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_APIC_ACCESS); + bool const fVirtApicAccess = pVM->hmr0.s.vmx.enmApicvLvl >= kVmxApicvLvl_ApicAccessVirt; bool const fUseVmcsShadowing = pVM->hmr0.s.vmx.fUseVmcsShadowing; VMXPAGEALLOCINFO aAllocInfo[] = { @@ -2709,28 +2709,22 @@ static int hmR0VmxSetupVmcsProcCtls2(PVMCPUCC pVCpu, PVMXVMCSINFO pVmcsInfo) if (pVM->hmr0.s.vmx.fUnrestrictedGuest) fVal |= VMX_PROC_CTLS2_UNRESTRICTED_GUEST; -#if 0 - if (pVM->hm.s.fVirtApicRegs) - { - /* Enable APIC-register virtualization. */ - Assert(g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_APIC_REG_VIRT); - fVal |= VMX_PROC_CTLS2_APIC_REG_VIRT; - - /* Enable virtual-interrupt delivery. */ - Assert(g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_INTR_DELIVERY); - fVal |= VMX_PROC_CTLS2_VIRT_INTR_DELIVERY; - } -#endif - /* Virtualize-APIC accesses if supported by the CPU. The virtual-APIC page is where the TPR shadow resides. */ /** @todo VIRT_X2APIC support, it's mutually exclusive with this. So must be * done dynamically. */ - if (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_APIC_ACCESS) + if (pVM->hmr0.s.vmx.enmApicvLvl >= kVmxApicvLvl_ApicAccessVirt) { fVal |= VMX_PROC_CTLS2_VIRT_APIC_ACCESS; hmR0VmxSetupVmcsApicAccessAddr(pVCpu); - } + } + + if (pVM->hmr0.s.vmx.enmApicvLvl >= kVmxApicvLvl_ApicRegVirt) + { + /* Enable APIC-register virtualization. */ + Assert(g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_APIC_REG_VIRT); + fVal |= VMX_PROC_CTLS2_APIC_REG_VIRT; + } /* Enable the RDTSCP instruction if we expose it to the guest and is supported by the hardware. Without this, guest executing RDTSCP would cause a #UD. */ @@ -3383,6 +3377,7 @@ VMMR0DECL(int) VMXR0SetupVM(PVMCC pVM) Log4Func(("pVCpu=%p idCpu=%RU32\n", pVCpu, pVCpu->idCpu)); pVCpu->hmr0.s.vmx.pfnStartVm = hmR0VmxStartVmSelector; + pVCpu->hmr0.s.vmx.enmApicvLvl = pVM->hmr0.s.vmx.enmApicvLvl; rc = hmR0VmxSetupVmcs(pVCpu, &pVCpu->hmr0.s.vmx.VmcsInfo, false /* fIsNstGstVmcs */); if (RT_SUCCESS(rc)) @@ -5950,7 +5945,7 @@ static VBOXSTRICTRC hmR0VmxPreRunGuest(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransie */ PVMCC pVM = pVCpu->CTX_SUFF(pVM); if ( !pVCpu->hm.s.vmx.u64GstMsrApicBase - && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_APIC_ACCESS) + && pVM->hmr0.s.vmx.enmApicvLvl >= kVmxApicvLvl_ApicAccessVirt && PDMHasApic(pVM)) { /* Get the APIC base MSR from the virtual APIC device. */ diff --git a/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp index fcdb59d1e9be..2aa70e9b844c 100644 --- a/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR3-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ +/* $Id: HMR3-x86.cpp 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * HM - Intel/AMD VM Hardware Support Manager. */ @@ -202,6 +202,61 @@ static const char *hmR3GetXcptName(uint8_t uVector) #endif /* VBOX_WITH_STATISTICS */ +/** + * Converts the given config string for the VMX APICv level to the corresponding enum. + * + * @returns VBox status code. + * @param pVM The cross context VM structure. + * @param pszVmxApicvLvl The desired VMX APICv level. + */ +static int hmR3CfgVmxApicvLvlStrToEnum(PVM pVM, const char *pszVmxApicvLvl) +{ + if (!strcmp(pszVmxApicvLvl, "None")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_None; + else if (!strcmp(pszVmxApicvLvl, "ApicAccessVirt")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_ApicAccessVirt; + else if (!strcmp(pszVmxApicvLvl, "ApicRegVirt")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_ApicRegVirt; + else if (!strcmp(pszVmxApicvLvl, "IntrDelivery")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_IntrDelivery; + else if (!strcmp(pszVmxApicvLvl, "PostedIntrs")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_PostedIntrs; + else if (!strcmp(pszVmxApicvLvl, "IpiVirt")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_IpiVirt; + else if (!strcmp(pszVmxApicvLvl, "Max")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_Max; + else + return VMSetError(pVM, VERR_INVALID_PARAMETER, RT_SRC_POS, "VmxApicLvl contains invalid value '%s'", pszVmxApicvLvl); + + return VINF_SUCCESS; +} + + +/** + * Returns the string variant of the given APICv level enum. + * + * @returns VBox status code. + * @param enmApicvLvl The APICv level to convert. + */ +static const char *hmR3CfgVmxApicvLvlEnumToStr(HMVMXAPICVLVL enmApicvLvl) +{ + switch (enmApicvLvl) + { + case kVmxApicvLvl_None: return "None"; + case kVmxApicvLvl_ApicAccessVirt: return "ApicAccessVirt"; + case kVmxApicvLvl_ApicRegVirt: return "ApicRegVirt"; + case kVmxApicvLvl_IntrDelivery: return "Virtual Interrupt Delivery"; + case kVmxApicvLvl_PostedIntrs: return "Posted Interrupts"; + case kVmxApicvLvl_IpiVirt: return "IPI Virtualization"; + case kVmxApicvLvl_Max: return "Maximum"; + default: break; + } + + AssertFailed(); + return ""; +} + + /** * Initializes the HM. * @@ -277,6 +332,7 @@ VMMR3_INT_DECL(int) HMR3Init(PVM pVM) "|VmxPleWindow" "|VmxLbr" "|UseVmxPreemptTimer" + "|VmxApicvLvl" "|SvmPauseFilter" "|SvmPauseFilterThreshold" "|SvmVirtVmsaveVmload" @@ -385,6 +441,16 @@ VMMR3_INT_DECL(int) HMR3Init(PVM pVM) rc = CFGMR3QueryBoolDef(pCfgHm, "VmxLbr", &pVM->hm.s.vmx.fLbrCfg, false); AssertRCReturn(rc, rc); + /** @cfgm{/HM/VmxApicvLvl, string, "Max"} + * The desired APIC virtualization level. Currently allows None, ApicAccessVirt, ApicRegVirt, + * IntrDelivery, PostedIntrs, IpiVirt, Max where Max is the default. + * The actually configured level depends on supported hardware. */ + char szVmxApicvLvl[16]; RT_ZERO(szVmxApicvLvl); + rc = CFGMR3QueryStringDef(pCfgHm, "VmxApicvLvl", &szVmxApicvLvl[0], sizeof(szVmxApicvLvl), "Max"); + AssertRCReturn(rc, rc); + rc = hmR3CfgVmxApicvLvlStrToEnum(pVM, &szVmxApicvLvl[0]); + AssertRCReturn(rc, rc); + /** @cfgm{/HM/SvmPauseFilterCount, uint16_t, 0} * A counter that is decrement each time a PAUSE instruction is executed by the * guest. When the counter is 0, a \#VMEXIT is triggered. @@ -911,6 +977,7 @@ static int hmR3InitFinalizeR3(PVM pVM) HM_REG_COUNTER(&pHmCpu->StatExitTprBelowThreshold, "/HM/CPU%u/Exit/TprBelowThreshold", "TPR lowered below threshold by the guest."); HM_REG_COUNTER(&pHmCpu->StatExitTaskSwitch, "/HM/CPU%u/Exit/TaskSwitch", "Task switch caused through task gate in IDT."); HM_REG_COUNTER(&pHmCpu->StatExitApicAccess, "/HM/CPU%u/Exit/ApicAccess", "APIC access. Guest attempted to access memory at a physical address on the APIC-access page."); + HM_REG_COUNTER(&pHmCpu->StatExitApicWrite, "/HM/CPU%u/Exit/ApicWrite", "APIC write emulation. Guest wrote APIC register which the hardware couldn't emulate."); HM_REG_COUNTER(&pHmCpu->StatSwitchTprMaskedIrq, "/HM/CPU%u/Switch/TprMaskedIrq", "PDMGetInterrupt() signals TPR masks pending Irq."); HM_REG_COUNTER(&pHmCpu->StatSwitchGuestIrq, "/HM/CPU%u/Switch/IrqPending", "PDMGetInterrupt() cleared behind our back!?!."); @@ -924,6 +991,7 @@ static int hmR3InitFinalizeR3(PVM pVM) HM_REG_COUNTER(&pHmCpu->StatSwitchMaxResumeLoops, "/HM/CPU%u/Switch/MaxResumeLoops", "Maximum VMRESUME inner-loop counter reached."); HM_REG_COUNTER(&pHmCpu->StatSwitchHltToR3, "/HM/CPU%u/Switch/HltToR3", "HLT causing us to go to ring-3."); HM_REG_COUNTER(&pHmCpu->StatSwitchApicAccessToR3, "/HM/CPU%u/Switch/ApicAccessToR3", "APIC access causing us to go to ring-3."); + HM_REG_COUNTER(&pHmCpu->StatSwitchApicWriteToR3, "/HM/CPU%u/Switch/ApicWriteToR3", "APIC write emulation causing us to go to ring-3."); #endif HM_REG_COUNTER(&pHmCpu->StatSwitchPreempt, "/HM/CPU%u/Switch/Preempting", "EMT has been preempted while in HM context."); #ifdef VBOX_WITH_STATISTICS @@ -1902,6 +1970,12 @@ static int hmR3InitFinalizeR0Intel(PVM pVM) else LogRel(("HM: Disabled VMX-preemption timer\n")); + if (pVM->hm.s.ForR3.vmx.enmApicvLvl != kVmxApicvLvl_None) + LogRel(("HM: Enabled APICv level: %s (configured %s)\n", hmR3CfgVmxApicvLvlEnumToStr(pVM->hm.s.ForR3.vmx.enmApicvLvl), + hmR3CfgVmxApicvLvlEnumToStr(pVM->hm.s.vmx.enmApicvLvl))); + else + LogRel(("HM: Disabled APICv support (configured %s)\n", hmR3CfgVmxApicvLvlEnumToStr(pVM->hm.s.vmx.enmApicvLvl))); + if (pVM->hm.s.fVirtApicRegs) LogRel(("HM: Enabled APIC-register virtualization support\n")); diff --git a/src/VBox/VMM/include/HMInternal.h b/src/VBox/VMM/include/HMInternal.h index 5222cc7564c3..f1271fc4c8f9 100644 --- a/src/VBox/VMM/include/HMInternal.h +++ b/src/VBox/VMM/include/HMInternal.h @@ -1,4 +1,4 @@ -/* $Id: HMInternal.h 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ +/* $Id: HMInternal.h 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * HM - Internal header file. */ @@ -77,6 +77,30 @@ RT_C_DECLS_BEGIN #define HM_VTX_TOTAL_DEVHEAP_MEM (HM_EPT_IDENTITY_PG_TABLE_SIZE + HM_VTX_TSS_SIZE) +/** + * The VT-x APICv level wanted/configured. + */ +typedef enum HMVMXAPICVLVL +{ + /** No APICv. */ + kVmxApicvLvl_None = 0, + /** APIC access virtualization. */ + kVmxApicvLvl_ApicAccessVirt, + /** APIC register virtualization. */ + kVmxApicvLvl_ApicRegVirt, + /** Virtual interrupt delivery. */ + kVmxApicvLvl_IntrDelivery, + /** Posted interrupt processing. */ + kVmxApicvLvl_PostedIntrs, + /** IPI virtualization. */ + kVmxApicvLvl_IpiVirt, + /** Max level. */ + kVmxApicvLvl_Max, + /** 32-bit hack. */ + kVmxApicvLvl_32Bit_Hack = 0x7fffffff +} HMVMXAPICVLVL; + + /** @name Macros for enabling and disabling preemption. * These are really just for hiding the RTTHREADPREEMPTSTATE and asserting that * preemption has already been disabled when there is no context hook. @@ -294,6 +318,8 @@ typedef struct HM * In the default case it is only always intercepted when setting DR6 to 0 on * the host results in a value different from X86_DR6_RA1_MASK. */ int8_t fAlwaysInterceptMovDRxCfg; + /** Desired APICv virtualization level. */ + HMVMXAPICVLVL enmApicvLvl; /** @} */ /** Pause-loop exiting (PLE) gap in ticks. */ @@ -419,6 +445,9 @@ typedef struct HM VMXTLBFLUSHEPT enmTlbFlushEpt; /** Flush type to use for INVVPID (only for ring-3 consumption). */ VMXTLBFLUSHVPID enmTlbFlushVpid; + + /** Configured APICv virtualization level (only for ring-3 consumption). */ + HMVMXAPICVLVL enmApicvLvl; } vmx; struct @@ -560,6 +589,8 @@ typedef struct HMR0PERVM VMXTLBFLUSHEPT enmTlbFlushEpt; /** Flush type to use for INVVPID. */ VMXTLBFLUSHVPID enmTlbFlushVpid; + /** The set APICv virtualization level. */ + HMVMXAPICVLVL enmApicvLvl; /** The host LBR TOS (top-of-stack) MSR id. */ uint32_t idLbrTosMsr; @@ -968,6 +999,7 @@ typedef struct HMCPU STAMCOUNTER StatExitTprBelowThreshold; STAMCOUNTER StatExitTaskSwitch; STAMCOUNTER StatExitApicAccess; + STAMCOUNTER StatExitApicWrite; STAMCOUNTER StatExitReasonNpf; STAMCOUNTER StatNestedExitReasonNpf; @@ -1001,6 +1033,7 @@ typedef struct HMCPU STAMCOUNTER StatSwitchMaxResumeLoops; STAMCOUNTER StatSwitchHltToR3; STAMCOUNTER StatSwitchApicAccessToR3; + STAMCOUNTER StatSwitchApicWriteToR3; STAMCOUNTER StatSwitchPreempt; STAMCOUNTER StatSwitchNstGstVmexit; @@ -1120,7 +1153,9 @@ typedef struct HMR0PERVCPU /* Whether the nested-guest VMCS was the last current VMCS (authoritative copy). * @see HMCPU::vmx.fSwitchedToNstGstVmcsCopyForRing3 */ bool fSwitchedToNstGstVmcs; - bool afAlignment0[7]; + bool afAlignment0[3]; + /** Active APICv virtualization level (to avoid going through pVM in hot paths). */ + HMVMXAPICVLVL enmApicvLvl; /** Pointer to the VMX transient info during VM-exit. */ PVMXTRANSIENT pVmxTransient; /** @} */ diff --git a/src/VBox/VMM/include/VMXInternal.h b/src/VBox/VMM/include/VMXInternal.h index ed45a18a72cf..769ca418dc35 100644 --- a/src/VBox/VMM/include/VMXInternal.h +++ b/src/VBox/VMM/include/VMXInternal.h @@ -1,4 +1,4 @@ -/* $Id: VMXInternal.h 113496 2026-03-22 22:23:27Z knut.osmundsen@oracle.com $ */ +/* $Id: VMXInternal.h 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * VMX - Internal header file for the VMX code template. */ @@ -245,6 +245,7 @@ typedef struct VMXSTATISTICS STAMCOUNTER StatExitTprBelowThreshold; STAMCOUNTER StatExitTaskSwitch; STAMCOUNTER StatExitApicAccess; + STAMCOUNTER StatExitApicWrite; STAMCOUNTER StatExitReasonNpf; STAMCOUNTER StatNestedExitReasonNpf; @@ -278,6 +279,7 @@ typedef struct VMXSTATISTICS STAMCOUNTER StatSwitchMaxResumeLoops; STAMCOUNTER StatSwitchHltToR3; STAMCOUNTER StatSwitchApicAccessToR3; + STAMCOUNTER StatSwitchApicWriteToR3; STAMCOUNTER StatSwitchPreempt; STAMCOUNTER StatSwitchNstGstVmexit; From c977c4bb19addee671803543051bc377e16cccb5 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Wed, 19 Aug 2026 13:04:31 +0000 Subject: [PATCH 174/176] VMM/HM: Implement support for APICv register virtualization on Intel, github:gh-808 [scm] svn:sync-xref-src-repo-rev: r174927 --- src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp index 2aa70e9b844c..2447e6f30379 100644 --- a/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR3-x86.cpp 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ +/* $Id: HMR3-x86.cpp 115085 2026-08-19 13:04:31Z alexander.eichner@oracle.com $ */ /** @file * HM - Intel/AMD VM Hardware Support Manager. */ @@ -443,7 +443,7 @@ VMMR3_INT_DECL(int) HMR3Init(PVM pVM) /** @cfgm{/HM/VmxApicvLvl, string, "Max"} * The desired APIC virtualization level. Currently allows None, ApicAccessVirt, ApicRegVirt, - * IntrDelivery, PostedIntrs, IpiVirt, Max where Max is the default. + * IntrDelivery, PostedIntrs, IpiVirt, Max where Max is the default. * The actually configured level depends on supported hardware. */ char szVmxApicvLvl[16]; RT_ZERO(szVmxApicvLvl); rc = CFGMR3QueryStringDef(pCfgHm, "VmxApicvLvl", &szVmxApicvLvl[0], sizeof(szVmxApicvLvl), "Max"); From 835ffe9f0853b2abff2c8e261d729d4dabecdc4f Mon Sep 17 00:00:00 2001 From: Andreas Loeffler Date: Wed, 19 Aug 2026 13:09:34 +0000 Subject: [PATCH 175/176] Shared Clipboard/Transfers: Added some more specs to the Windows path validation + added some more testcases for it. svn:sync-xref-src-repo-rev: r174928 --- .../GuestHost/SharedClipboard-transfers.h | 5 +- .../SharedClipboard/clipboard-path.cpp | 74 +++++++++-- .../SharedClipboard/clipboard-transfers.cpp | 74 +++++++---- .../testcase/tstClipboardWinStream.cpp | 125 +++++++++++++++++- 4 files changed, 238 insertions(+), 40 deletions(-) diff --git a/include/VBox/GuestHost/SharedClipboard-transfers.h b/include/VBox/GuestHost/SharedClipboard-transfers.h index a37721f54cec..d8aa108871a3 100644 --- a/include/VBox/GuestHost/SharedClipboard-transfers.h +++ b/include/VBox/GuestHost/SharedClipboard-transfers.h @@ -1,4 +1,4 @@ -/* $Id: SharedClipboard-transfers.h 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ +/* $Id: SharedClipboard-transfers.h 115086 2026-08-19 13:09:34Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Shared transfer functions between host and guest. */ @@ -475,7 +475,7 @@ typedef SHCLLISTENTRY *PSHCLLISTENTRY; /** Pointer to a const Shared Clipboard list entry. */ typedef SHCLLISTENTRY *PCSHCLLISTENTRY; -/** Maximum length (in UTF-8 characters) of a list entry name. Includes terminator. */ +/** Maximum size (in bytes) of a UTF-8 list entry name. Includes terminator. */ #define SHCLLISTENTRY_MAX_NAME 4096 /** @@ -1283,6 +1283,7 @@ int ShClPathSanitize(char *pszPath, size_t cbPath); const char *ShClTransferStatusToStr(SHCLTRANSFERSTATUS enmStatus); int ShClTransferTransformPath(char *pszPath, size_t cbPath); int ShClTransferValidatePath(const char *pcszPath, bool fMustExist); +int ShClTransferValidatePathEx(const char *pcszPath, size_t cbPath, bool fMustExist); int ShClTransferResolvePathAbs(PSHCLTRANSFER pTransfer, const char *pszPath, uint32_t fFlags, char **ppszResolved); int ShClTransferConvertFileCreateFlags(uint32_t fShClFlags, uint64_t *pfOpen); int ShClFsObjInfoQueryLocal(const char *pszPath, PSHCLFSOBJINFO pObjInfo); diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp index 64be9ab36e28..9fdfc3ee01d0 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-path.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-path.cpp 115086 2026-08-19 13:09:34Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Path handling. */ @@ -37,11 +37,52 @@ #include +#ifdef RT_OS_WINDOWS +/** + * Returns whether a filename uses a reserved Windows device name. + * + * Matching is case-insensitive and only considers the part before the first + * dot, as reserved device names remain reserved when followed by an extension. + * + * @returns Whether @a pszName is a reserved Windows device filename. + * @param pszName Zero-terminated UTF-8 filename to inspect. + */ +static bool shClPathIsWindowsReservedFilename(const char *pszName) +{ + size_t const cchBase = strcspn(pszName, "."); + if (cchBase == 3) + { + if ( RTStrNICmp(pszName, "CON", 3) == 0 + || RTStrNICmp(pszName, "PRN", 3) == 0 + || RTStrNICmp(pszName, "AUX", 3) == 0 + || RTStrNICmp(pszName, "NUL", 3) == 0) + return true; + } + else if ( cchBase >= 4 + && ( RTStrNICmp(pszName, "COM", 3) == 0 + || RTStrNICmp(pszName, "LPT", 3) == 0)) + { + size_t const cbSuffix = cchBase - 3; + if ( ( cbSuffix == 1 + && pszName[3] >= '1' + && pszName[3] <= '9') + || ( cbSuffix == 2 + && ( !memcmp(&pszName[3], "\xc2\xb9", 2) /* SUPERSCRIPT ONE */ + || !memcmp(&pszName[3], "\xc2\xb2", 2) /* SUPERSCRIPT TWO */ + || !memcmp(&pszName[3], "\xc2\xb3", 2) /* SUPERSCRIPT THREE */))) + return true; + } + + return false; +} +#endif /* RT_OS_WINDOWS */ + + /** - * Sanitizes the file name component so that unsupported characters - * will be replaced by an underscore ("_"). + * Sanitizes the file name component so that unsupported characters and + * reserved Windows device names will be replaced by an underscore ("_"). * - * @return IPRT status code. + * @returns IPRT status code. * @param pszPath Path to sanitize. * @param cbPath Size (in bytes) of path to sanitize. */ @@ -50,23 +91,28 @@ int ShClPathSanitizeFilename(char *pszPath, size_t cbPath) int rc = VINF_SUCCESS; #ifdef RT_OS_WINDOWS RT_NOREF1(cbPath); - /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */ + /* Replace characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */ /** @todo Use something like RTPathSanitize() if available later some time. */ - static const RTUNICP s_uszValidRangePairs[] = + static const RTUNICP s_aValidRangePairs[] = { - ' ', ' ', - '(', ')', - '-', '.', + ' ', '!', + '#', ')', + '+', '.', '0', '9', - 'A', 'Z', - 'a', 'z', - '_', '_', - 0xa0, 0xd7af, + ';', ';', + '=', '=', + '@', '[', + ']', '{', + '}', '~', + 0x80, 0xd7ff, + 0xe000, 0x10ffff, '\0' }; - ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_uszValidRangePairs, '_' /* chReplacement */); + ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_aValidRangePairs, '_' /* chReplacement */); if (cReplaced < 0) rc = VERR_INVALID_UTF8_ENCODING; + else if (shClPathIsWindowsReservedFilename(pszPath)) + pszPath[0] = '_'; #else RT_NOREF2(pszPath, cbPath); #endif diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index 67e377f66aa3..e7e88c10e505 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 115086 2026-08-19 13:09:34Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -786,21 +786,11 @@ PSHCLLISTENTRY ShClTransferListEntryDup(PSHCLLISTENTRY pEntry) */ static bool shclTransferListEntryNameIsValid(const char *pszName, size_t cbName) { - if (!pszName) - return false; - - size_t const cchLen = RTStrNLen(pszName, cbName); - - if ( !cbName - || cchLen == cbName - || cchLen == 0 - || cchLen > SHCLLISTENTRY_MAX_NAME /* Includes zero termination */ - 1) - { + int rc = ShClTransferValidatePathEx(pszName, cbName, false /* fMustExist */); + if (RT_FAILURE(rc)) return false; - } - int rc = ShClTransferValidatePath(pszName, false /* fMustExist */); - if (RT_FAILURE(rc)) + if (*pszName == '\0') return false; if (!shClTransferPathIsRelative(pszName)) @@ -816,7 +806,7 @@ static bool shclTransferListEntryNameIsValid(const char *pszName, size_t cbName) * @param pListEntry Clipboard list entry structure to initialize. * @param fInfo Info flags (of type VBOX_SHCL_INFO_F_XXX). * @param pszName Name (e.g. filename) to use. Can be NULL if not being used. - * Up to SHCLLISTENTRY_MAX_NAME characters. + * Up to SHCLLISTENTRY_MAX_NAME bytes, including the terminator. * @param pvInfo Pointer to info data to assign. Must match \a fInfo. * The list entry takes the ownership of the data on success. * @param cbInfo Size (in bytes) of \a pvInfo data to assign. @@ -824,17 +814,24 @@ static bool shclTransferListEntryNameIsValid(const char *pszName, size_t cbName) int ShClTransferListEntryInitEx(PSHCLLISTENTRY pListEntry, uint32_t fInfo, const char *pszName, void *pvInfo, uint32_t cbInfo) { AssertPtrReturn(pListEntry, VERR_INVALID_POINTER); - AssertReturn ( pszName == NULL - || shclTransferListEntryNameIsValid(pszName, strlen(pszName) + 1), VERR_INVALID_PARAMETER); + + size_t cchName = 0; + if (pszName) + { + cchName = RTStrNLen(pszName, SHCLLISTENTRY_MAX_NAME); + if ( cchName >= SHCLLISTENTRY_MAX_NAME + || !shclTransferListEntryNameIsValid(pszName, cchName + 1)) + return VERR_INVALID_PARAMETER; + } /* pvInfo + cbInfo depend on fInfo. See below. */ RT_BZERO(pListEntry, sizeof(SHCLLISTENTRY)); if (pszName) { - pListEntry->pszName = RTStrDupN(pszName, SHCLLISTENTRY_MAX_NAME); + pListEntry->pszName = RTStrDupN(pszName, cchName); AssertPtrReturn(pListEntry->pszName, VERR_NO_MEMORY); - pListEntry->cbName = (uint32_t)strlen(pListEntry->pszName) + 1 /* Include terminator */; + pListEntry->cbName = (uint32_t)cchName + 1 /* Include terminator */; } pListEntry->pvInfo = pvInfo; @@ -3722,23 +3719,54 @@ int ShClTransferTransformPath(char *pszPath, size_t cbPath) * - Symbolic links are forbidden. * * @returns VBox status code. - * @param pcszPath Path to validate. + * @param pcszPath Zero-terminated path to validate. * @param fMustExist Whether the path to validate also must exist. */ int ShClTransferValidatePath(const char *pcszPath, bool fMustExist) { AssertPtrReturn(pcszPath, VERR_INVALID_POINTER); + size_t const cchPath = RTStrNLen(pcszPath, SHCLLISTENTRY_MAX_NAME); + if (cchPath >= SHCLLISTENTRY_MAX_NAME) + return VERR_INVALID_PARAMETER; + + return ShClTransferValidatePathEx(pcszPath, cchPath + 1, fMustExist); +} + +/** + * Validates whether a given path matches our set of rules or not, extended version. + * + * Validates the supplied size and requires the terminator to be the last byte. + * + * @returns VBox status code. + * @param pcszPath Path to validate. + * @param cbPath Size (in bytes) of @a pcszPath, including the terminator. + * Must not exceed SHCLLISTENTRY_MAX_NAME. + * @param fMustExist Whether the path to validate also must exist. + */ +int ShClTransferValidatePathEx(const char *pcszPath, size_t cbPath, bool fMustExist) +{ + if (!pcszPath) + return VERR_INVALID_POINTER; + + if ( !cbPath + || cbPath > SHCLLISTENTRY_MAX_NAME) + return VERR_INVALID_PARAMETER; + + size_t const cchPath = RTStrNLen(pcszPath, cbPath); + if (cchPath != cbPath - 1) + return VERR_INVALID_PARAMETER; + int rc = VINF_SUCCESS; - if (*pcszPath == '\0') + if (!cchPath) return rc; - char *pszSanitized = RTStrDup(pcszPath); + char *pszSanitized = RTStrDupN(pcszPath, cchPath); if (!pszSanitized) return VERR_NO_MEMORY; - rc = ShClPathSanitize(pszSanitized, strlen(pszSanitized) + 1); + rc = ShClPathSanitize(pszSanitized, cbPath); if ( RT_SUCCESS(rc) && shClTransferPathIsRelative(pcszPath) && strcmp(pszSanitized, pcszPath)) diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp index 67cf00b1d1d0..44d20430cde7 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardWinStream.cpp 115068 2026-08-18 14:45:28Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardWinStream.cpp 115086 2026-08-19 13:09:34Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Windows stream testcase. */ @@ -121,6 +121,127 @@ typedef TSTWINSTREAMPROVIDER *PTSTWINSTREAMPROVIDER; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ +/** Tests Windows filename sanitization and transfer-list name validation. */ +static void tstWinPathSanitizeFilename(void) +{ + static const char * const s_apszValid[] = + { + "test-64KiB-name-url-#%+&=,.bin", + "unicode-\xed\x9e\xb0-\xee\x80\x80-\xf0\x9f\x98\x80.bin", + "COM0.txt", + "COM10.txt", + "LPT0.txt", + "CONSOLE.txt", + "NULL.txt" + }; + static const char * const s_apszReserved[] = + { + "CON", + "con.txt", + "PRN.tar.gz", + "AUX", + "NUL.txt", + "COM1", + "com9.log", + "LPT1", + "lpt9.txt", + "COM\xc2\xb9.txt", + "LPT\xc2\xb2", + "COM\xc2\xb3.bin", + "directory/CON.txt" + }; + char szPath[128]; + + RTTestISub("Windows filename sanitization"); + for (size_t i = 0; i < RT_ELEMENTS(s_apszValid); i++) + { + RTTESTI_CHECK_RC_OK(RTStrCopy(szPath, sizeof(szPath), s_apszValid[i])); + RTTESTI_CHECK_RC_OK(ShClPathSanitize(szPath, sizeof(szPath))); + RTTESTI_CHECK_MSG(!strcmp(szPath, s_apszValid[i]), + ("Valid name '%s' was changed to '%s'\n", s_apszValid[i], szPath)); + RTTESTI_CHECK_RC_OK(ShClTransferValidatePath(s_apszValid[i], false /* fMustExist */)); + } + + for (size_t i = 0; i < RT_ELEMENTS(s_apszReserved); i++) + { + RTTESTI_CHECK_RC_OK(RTStrCopy(szPath, sizeof(szPath), s_apszReserved[i])); + RTTESTI_CHECK_RC_OK(ShClPathSanitize(szPath, sizeof(szPath))); + RTTESTI_CHECK_MSG(strcmp(szPath, s_apszReserved[i]), + ("Reserved name '%s' was not changed\n", s_apszReserved[i])); + RTTESTI_CHECK_RC(ShClTransferValidatePath(s_apszReserved[i], false /* fMustExist */), VERR_INVALID_PARAMETER); + } +} + + +/** Tests transfer-list name length and termination invariants. */ +static void tstTransferListEntryNameValidation(void) +{ + RTTestISub("Transfer-list entry name validation"); + + char szValid[] = "valid.bin"; + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(NULL, sizeof(szValid), false /* fMustExist */), VERR_INVALID_POINTER); + RTTESTI_CHECK_RC_OK(ShClTransferValidatePathEx(szValid, sizeof(szValid), false /* fMustExist */)); + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(szValid, 0, false /* fMustExist */), VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(szValid, 1, false /* fMustExist */), VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(szValid, sizeof(szValid) + 1, false /* fMustExist */), + VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(szValid, SHCLLISTENTRY_MAX_NAME + 1, false /* fMustExist */), + VERR_INVALID_PARAMETER); + + char szEmpty[] = ""; + RTTESTI_CHECK_RC_OK(ShClTransferValidatePathEx(szEmpty, sizeof(szEmpty), false /* fMustExist */)); + + SHCLLISTENTRY Entry; + RT_ZERO(Entry); + Entry.pszName = szValid; + Entry.cbName = sizeof(szValid); + RTTESTI_CHECK(ShClTransferListEntryIsValid(&Entry)); + + Entry.pszName = szEmpty; + Entry.cbName = sizeof(szEmpty); + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + Entry.pszName = szValid; + + Entry.cbName = 0; + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + Entry.cbName = 1; + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + Entry.cbName = sizeof(szValid) + 1; + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + Entry.cbName = SHCLLISTENTRY_MAX_NAME + 1; + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + char szEmbeddedNul[] = { 'n', 'a', 'm', 'e', '\0', 'x', '\0' }; + Entry.pszName = szEmbeddedNul; + Entry.cbName = sizeof(szEmbeddedNul); + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + char szNoTerminator[] = { 'n', 'a', 'm', 'e' }; + Entry.pszName = szNoTerminator; + Entry.cbName = sizeof(szNoTerminator); + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + char szTooLong[SHCLLISTENTRY_MAX_NAME]; + memset(szTooLong, 'a', sizeof(szTooLong)); + RTTESTI_CHECK_RC(ShClTransferListEntryInitEx(&Entry, VBOX_SHCL_INFO_F_NONE, szTooLong, + NULL /* pvInfo */, 0 /* cbInfo */), VERR_INVALID_PARAMETER); + + szTooLong[sizeof(szTooLong) - 1] = '\0'; + RTTESTI_CHECK_RC_OK(ShClTransferListEntryInitEx(&Entry, VBOX_SHCL_INFO_F_NONE, szTooLong, + NULL /* pvInfo */, 0 /* cbInfo */)); + RTTESTI_CHECK(Entry.cbName == SHCLLISTENTRY_MAX_NAME); + ShClTransferListEntryDestroy(&Entry); + + RTTESTI_CHECK_RC_OK(ShClTransferListEntryInitEx(&Entry, VBOX_SHCL_INFO_F_NONE, szValid, + NULL /* pvInfo */, 0 /* cbInfo */)); + RTTESTI_CHECK(Entry.cbName == sizeof(szValid)); + ShClTransferListEntryDestroy(&Entry); +} + + /** @copydoc SHCLTXPROVIDERIFACE::pfnRootListRead */ static DECLCALLBACK(int) tstWinStreamProviderRootListRead(PSHCLTXPROVIDERCTX pCtx) { @@ -498,6 +619,8 @@ int main(void) return rcExit; RTTestBanner(hTest); + tstWinPathSanitizeFilename(); + tstTransferListEntryNameValidation(); tstWinStreamReadsAcrossChunkBoundaries(); return RTTestSummaryAndDestroy(hTest); From cee420042dfb985af03afa681005b51d3eb9daa4 Mon Sep 17 00:00:00 2001 From: Alexander Eichner Date: Wed, 19 Aug 2026 13:09:55 +0000 Subject: [PATCH 176/176] VMM/HM: Implement support for APICv register virtualization on Intel, github:gh-808 [build fix] svn:sync-xref-src-repo-rev: r174929 --- src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h b/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h index 04f937b5eac1..13f7ce5e46d1 100644 --- a/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h +++ b/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h @@ -1,4 +1,4 @@ -/* $Id: VMXAllTemplate.cpp.h 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ +/* $Id: VMXAllTemplate.cpp.h 115087 2026-08-19 13:09:55Z alexander.eichner@oracle.com $ */ /** @file * HM VMX (Intel VT-x) - Code template for our own hypervisor and the NEM darwin backend using Apple's Hypervisor.framework. */ @@ -9348,6 +9348,7 @@ HMVMX_EXIT_DECL vmxHCExitApicWrite(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient) HMVMX_VALIDATE_EXIT_HANDLER_PARAMS(pVCpu, pVmxTransient); STAM_COUNTER_INC(&VCPU_2_VMXSTATS(pVCpu).StatExitApicWrite); +#ifndef IN_NEM_DARWIN vmxHCReadToTransient(pVCpu, pVmxTransient); /* @@ -9359,6 +9360,10 @@ HMVMX_EXIT_DECL vmxHCExitApicWrite(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient) if (rcStrict != VINF_SUCCESS) STAM_COUNTER_INC(&VCPU_2_VMXSTATS(pVCpu).StatSwitchApicWriteToR3); return rcStrict; +#else + AssertFailed(); /** @todo */ + return VERR_NOT_IMPLEMENTED; +#endif }