changeset 1119:5349e2354c48

(issue54) Merge branch runafterinstall There is now an NSIS Plugin that executes the Software after installation using COM in the shell of the current user. With the way over the shell there is no inheritance / token management required. As it is impossible to drop all privileges of a token granted by UAC and still be able to reelevate the Token again with another RunAs call later this round trip over the Shell was necessary.
author Andre Heinecke <andre.heinecke@intevation.de>
date Tue, 16 Sep 2014 19:48:22 +0200
parents 5b6203f78b4e (current diff) fd85a02d771d (diff)
children 9651fabca17d
files
diffstat 10 files changed, 488 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Sep 16 11:45:32 2014 +0200
+++ b/CMakeLists.txt	Tue Sep 16 19:48:22 2014 +0200
@@ -153,12 +153,7 @@
    add_subdirectory(manuals)
 endif()
 
-# Configure packaging script for testing
-configure_file (packaging/win-createpackage.sh.in packaging/win-createpackage.sh @ONLY)
-configure_file (packaging/create-dist-package.sh.in packaging/create-dist-package.sh @ONLY)
-configure_file (packaging/linux-createpackage.sh.in packaging/linux-createpackage.sh @ONLY)
-configure_file (packaging/linux-installer.inc.in packaging/linux-installer.inc @ONLY)
-configure_file (packaging/linux-installer.l10n-de packaging/linux-installer.l10n-de COPYONLY)
+add_subdirectory(packaging)
 
 if (ENABLE_PROFILING)
    configure_file (make-coverage.sh.in make-coverage.sh)
--- a/cinst/nssstore_win.c	Tue Sep 16 11:45:32 2014 +0200
+++ b/cinst/nssstore_win.c	Tue Sep 16 19:48:22 2014 +0200
@@ -39,7 +39,6 @@
 */
 
 #include <windows.h>
-#include <winsafer.h>
 #include <sddl.h>
 #include <stdio.h>
 #include <stdbool.h>
@@ -99,73 +98,6 @@
   xfree (item);
 }
 
-/** @brief get a restricted access token to execute nss process
-  *
-  * This function uses the Software Restriction API to obtain the
-  * access token for a process run als normal user.
-  *
-  * @returns A restricted handle or NULL on error.
-  */
-static HANDLE
-get_restricted_token()
-{
-  SAFER_LEVEL_HANDLE user_level = NULL;
-  HANDLE retval = NULL;
-  SID_IDENTIFIER_AUTHORITY medium_identifier = {SECURITY_MANDATORY_LABEL_AUTHORITY};
-  PSID medium_sid = NULL;
-  TOKEN_MANDATORY_LABEL integrity_label;
-
-  memset (&integrity_label, 0, sizeof (integrity_label));
-
-  if (!SaferCreateLevel(SAFER_SCOPEID_USER,
-                        SAFER_LEVELID_NORMALUSER,
-                        SAFER_LEVEL_OPEN, &user_level, NULL))
-    {
-      PRINTLASTERROR ("Failed to create user level.\n");
-      return NULL;
-    }
-
-  if (!SaferComputeTokenFromLevel(user_level, NULL, &retval, 0, NULL))
-    {
-      SaferCloseLevel(user_level);
-      return NULL;
-    }
-
-  SaferCloseLevel(user_level);
-
-  /* Set the SID to medium it will still be high otherwise. Even if
-  there is no high access allowed. */
-  if (!AllocateAndInitializeSid(&medium_identifier,
-                               1,
-                               SECURITY_MANDATORY_MEDIUM_RID,
-                               0,
-                               0,
-                               0,
-                               0,
-                               0,
-                               0,
-                               0,
-                               &medium_sid))
-    {
-      PRINTLASTERROR ("Failed to initialize sid.\n");
-      return NULL;
-    }
-
-  integrity_label.Label.Attributes = SE_GROUP_INTEGRITY;
-  integrity_label.Label.Sid        = medium_sid;
-
-  if (!SetTokenInformation(retval,
-                           TokenIntegrityLevel,
-                           &integrity_label,
-                           sizeof(TOKEN_MANDATORY_LABEL)))
-    {
-      PRINTLASTERROR ("Failed to set token integrity.\n");
-      return NULL;
-    }
-
-  return retval;
-}
-
 /**@brief Write strv of instructions to a handle
 *
 * Writes the null terminated list of instructions to
--- a/common/util.c	Tue Sep 16 11:45:32 2014 +0200
+++ b/common/util.c	Tue Sep 16 19:48:22 2014 +0200
@@ -16,6 +16,7 @@
 #include <grp.h>
 #include <string.h>
 #else
+#include <winsafer.h>
 #include <windows.h>
 #include <accctrl.h>
 #include <aclapi.h>
@@ -782,3 +783,72 @@
   return retval;
 }
 #endif
+
+#ifdef WIN32
+/** @brief get a restricted access token
+  *
+  * This function uses the Software Restriction API to obtain the
+  * access token for a process run als normal user.
+  *
+  * @returns A restricted handle or NULL on error.
+  */
+HANDLE
+get_restricted_token()
+{
+  SAFER_LEVEL_HANDLE user_level = NULL;
+  HANDLE retval = NULL;
+  SID_IDENTIFIER_AUTHORITY medium_identifier = {SECURITY_MANDATORY_LABEL_AUTHORITY};
+  PSID medium_sid = NULL;
+  TOKEN_MANDATORY_LABEL integrity_label;
+
+  memset (&integrity_label, 0, sizeof (integrity_label));
+
+  if (!SaferCreateLevel(SAFER_SCOPEID_USER,
+                        SAFER_LEVELID_NORMALUSER,
+                        SAFER_LEVEL_OPEN, &user_level, NULL))
+    {
+      PRINTLASTERROR ("Failed to create user level.\n");
+      return NULL;
+    }
+
+  if (!SaferComputeTokenFromLevel(user_level, NULL, &retval, 0, NULL))
+    {
+      SaferCloseLevel(user_level);
+      return NULL;
+    }
+
+  SaferCloseLevel(user_level);
+
+  /* Set the SID to medium it will still be high otherwise. Even if
+  there is no high access allowed. */
+  if (!AllocateAndInitializeSid(&medium_identifier,
+                               1,
+                               SECURITY_MANDATORY_MEDIUM_RID,
+                               0,
+                               0,
+                               0,
+                               0,
+                               0,
+                               0,
+                               0,
+                               &medium_sid))
+    {
+      PRINTLASTERROR ("Failed to initialize sid.\n");
+      return NULL;
+    }
+
+  integrity_label.Label.Attributes = SE_GROUP_INTEGRITY;
+  integrity_label.Label.Sid        = medium_sid;
+
+  if (!SetTokenInformation(retval,
+                           TokenIntegrityLevel,
+                           &integrity_label,
+                           sizeof(TOKEN_MANDATORY_LABEL)))
+    {
+      PRINTLASTERROR ("Failed to set token integrity.\n");
+      return NULL;
+    }
+
+  return retval;
+}
+#endif
--- a/common/util.h	Tue Sep 16 11:45:32 2014 +0200
+++ b/common/util.h	Tue Sep 16 19:48:22 2014 +0200
@@ -150,6 +150,23 @@
   */
 bool has_high_integrity(HANDLE hToken);
 
+/** @brief get a restricted access token to execute nss process
+  *
+  * This function uses the Software Restriction API to obtain the
+  * access token for a process run als normal user.
+  *
+  * @returns A restricted handle or NULL on error.
+  */
+HANDLE get_restricted_token();
+
+/** @brief get a normal user access token
+  *
+  * The trusted acces token is not elevated but has the normal user rights.
+  *
+  * @returns A normal user handle or NULL on error.
+  */
+HANDLE get_normal_token();
+
 #endif
 
 #ifdef __cplusplus
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/packaging/CMakeLists.txt	Tue Sep 16 19:48:22 2014 +0200
@@ -0,0 +1,23 @@
+# Configure packaging script for testing
+configure_file (win-createpackage.sh.in win-createpackage.sh @ONLY)
+configure_file (create-dist-package.sh.in create-dist-package.sh @ONLY)
+configure_file (linux-createpackage.sh.in linux-createpackage.sh @ONLY)
+configure_file (linux-installer.inc.in linux-installer.inc @ONLY)
+configure_file (linux-installer.l10n-de linux-installer.l10n-de COPYONLY)
+
+if (WIN32)
+   # Build the nss safer plugin.
+   include_directories(${CMAKE_SOURCE_DIR}/common)
+
+   add_library(desktopshellrun SHARED desktopshellrun.cpp)
+   target_link_libraries(desktopshellrun
+      trustbridge_common
+      -luserenv
+      -lshell32
+      -lcrypt32
+      -lssp
+      -lshlwapi
+      ${POLARSSL_LIBRARIES}) # trustbridge_common pulls it in.
+   set_target_properties(desktopshellrun PROPERTIES PREFIX "")
+   set_target_properties(desktopshellrun PROPERTIES OUTPUT_NAME "DesktopShellRun")
+endif()
--- a/packaging/create-dist-package.sh.in	Tue Sep 16 11:45:32 2014 +0200
+++ b/packaging/create-dist-package.sh.in	Tue Sep 16 19:48:22 2014 +0200
@@ -58,6 +58,7 @@
 # Create a temporary NSIS file for the uninstaller cration
 LC_ALL="de_DE.latin-1" makensis -Dfiles_dir=$TMPDIR/windows \
     -Dcompany="Bundesamt für Sicherheit in der Informationstechnik" \
+    -Dplugin_dir="@CMAKE_CURRENT_BINARY_DIR@" \
     -Dversion_number=@PROJECT_VERSION@ \
     -Dsetupname="$TMPDIR/TrustBridge-@PROJECT_VERSION@-uni.exe" \
     -Dproductname="TrustBridge" \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/packaging/desktopshellrun.cpp	Tue Sep 16 19:48:22 2014 +0200
@@ -0,0 +1,326 @@
+/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=2)
+ * and comes with ABSOLUTELY NO WARRANTY!
+ * See LICENSE.txt for details.
+ */
+
+/**
+ * @file tasksched_run_as.cpp
+ * @brief Run a command with the Task Scheduler API
+ *
+ * This effectively starts a program as the user regardles of the current
+ * process token.
+ */
+#include <windows.h>
+#include "logging.h"
+#include "strhelp.h"
+
+#ifndef INITGUID
+#define INITGUID
+#endif
+
+#include <initguid.h>
+#include <mstask.h>
+#include <wchar.h>
+#include <ole2.h>
+#include <shldisp.h>
+#include <shobjidl.h>
+#include <exdisp.h>
+#include <shlguid.h>
+
+/* Some declarations missing in mingw-w64 3.1.0 taken from msdn */
+
+__CRT_UUID_DECL(IShellWindows, 0x85CB6900, 0x4D95, 0x11CF,
+                0x96, 0x0C, 0x00, 0x80, 0xC7, 0xF4, 0xEE, 0x85);
+
+DEFINE_GUID(IID_IShellWindows,
+            0x85CB6900, 0x4D95, 0x11CF,
+            0x96, 0x0C, 0x00, 0x80, 0xC7, 0xF4, 0xEE, 0x85);
+DEFINE_GUID(CLSID_ShellWindows,
+            0x9BA05972, 0xF6A8, 0x11CF,
+            0xA4, 0x42, 0x00, 0xA0, 0xC9, 0x0A, 0x8F, 0x39);
+
+
+__CRT_UUID_DECL(IShellDispatch2, 0xA4C6892C, 0x3BA9, 0x11d2,
+                0x9D, 0xEA, 0x00, 0xC0, 0x4F, 0xB1, 0x61, 0x62);
+__CRT_UUID_DECL(IShellFolderViewDual,  0xe7a1af80, 0x4d96,
+                0x11cf, 0x96, 0x0c, 0x00, 0x80, 0xc7, 0xf4, 0xee, 0x85);
+
+#ifndef SWC_DESKTOP /* Will probably be addedd in future mingw */
+#define SWC_DESKTOP 0x00000008
+/* from http://msdn.microsoft.com/en-us/library/windows/desktop/cc836581%28v=vs.85%29.aspx */
+#endif
+
+#undef INITGUID
+
+#ifdef DO_RELEASE_BUILD
+bool g_debug = false;
+#else
+bool g_debug = true;
+#endif
+
+typedef struct _stack_t
+{
+  struct _stack_t *next;
+  char text[1];          /* This should be the length of string_size. */
+} stack_t;
+
+static HINSTANCE g_hInstance; /* Our Instance. */
+
+#define UNUSED(x) (void)(x)
+
+/** @brief the actual execuation call on the shell dispatcher
+ *
+ * @param[in] disp The shell dispatcher to use for shell execute.
+ * @param[in] fName The file that should be exectued.
+ *
+ * @returns true on success.
+ */
+static bool
+shellexecute(IShellDispatch2 *disp, wchar_t *fName)
+{
+  BSTR bName = NULL,
+       bParam = NULL,
+       bDir = NULL,
+       bOp = NULL;
+  VARIANT vParams[4];
+  HRESULT hr;
+
+  if (!fName || !disp)
+    {
+      ERRORPRINTF ("Invalid call to shellexecute.");
+      return false;
+    }
+
+  bName = SysAllocString(fName);
+  bParam = SysAllocString(L"");
+  bDir = SysAllocString(L"");
+  bOp = SysAllocString(L"");
+
+  if (!bName || !bParam || !bDir || !bOp)
+    {
+      /* Out of memory */
+      ERRORPRINTF ("Failed to allocate bstr values ");
+      return false;
+    }
+
+  vParams[0].vt = VT_BSTR;
+  vParams[0].bstrVal = bParam;
+  vParams[1].vt = VT_BSTR;
+  vParams[1].bstrVal = bDir;
+  vParams[2].vt = VT_BSTR;
+  vParams[2].bstrVal = bOp;
+  vParams[3].vt = VT_INT;
+  vParams[3].intVal = SW_SHOWNORMAL;
+
+  hr = disp->ShellExecute(bName, vParams[0], vParams[1], vParams[2], vParams[3]);
+
+  SysFreeString(bName);
+  SysFreeString(bParam);
+  SysFreeString(bOp);
+  SysFreeString(bDir);
+
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to execute.");
+      return false;
+    }
+  return true;
+}
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Standard entry point for DLLs. */
+int WINAPI
+DllMain (HANDLE hinst, DWORD reason, LPVOID reserved)
+{
+  UNUSED(reserved);
+  if (reason == DLL_PROCESS_ATTACH)
+    g_hInstance = (HINSTANCE)hinst;
+  return TRUE;
+}
+
+/** @brief Execute a command with the current running shell.
+ *
+ * This function is intended to be called when you want to
+ * make sure that your application is not executed with higher
+ * privileges then the normal desktop session.
+ *
+ * The code is based on the idea:
+ * http://blogs.msdn.com/b/oldnewthing/archive/2013/11/18/10468726.aspx
+ *
+ * The function signature is explained by NSIS.
+ */
+void __declspec(dllexport) __cdecl Exec(HWND hwndParent,
+                                        int string_size,
+                                        char *variables,
+                                        stack_t **stacktop)
+{
+  UNUSED(hwndParent);
+  UNUSED(string_size);
+  HRESULT hr;
+  wchar_t *wbuf = NULL;
+  IShellWindows *shellWindows = NULL;
+  IShellBrowser *shellBrowser = NULL;
+  IShellView *shellView = NULL;
+  IShellFolderViewDual *folderView = NULL;
+  IShellDispatch2 *shellDispatch = NULL;
+  IServiceProvider *serviceProv = NULL;
+  HWND hwnd;
+  IDispatch *disp = NULL,
+             *bgDisp = NULL,
+              *sDisp = NULL;
+  VARIANT vEmpty = {};
+
+
+  if (!stacktop || !*stacktop || !(*stacktop)->text)
+    {
+      ERRORPRINTF ("Invalid call to exec :");
+      return;
+    }
+
+  /* Initialize com ctx */
+  hr = CoInitialize(NULL);
+  if(FAILED(hr))
+    {
+      ERRORPRINTF ("CoInitializeEx failed. error = 0x%lx.", hr);
+      return;
+    }
+
+  /* Get the shell interface */
+  hr = CoCreateInstance(CLSID_ShellWindows,
+                        NULL, CLSCTX_LOCAL_SERVER,
+                        IID_PPV_ARGS(&shellWindows));
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to get shell interface.");
+      goto done;
+    }
+
+  /* Get the desktop shell window */
+  hr = shellWindows->FindWindowSW(&vEmpty,
+                                  &vEmpty,
+                                  SWC_DESKTOP,
+                                  (long*)&hwnd,
+                                  SWFO_NEEDDISPATCH,
+                                  &disp);
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to find the desktop dispatcher.");
+      goto done;
+    }
+
+  hr = disp->QueryInterface(IID_PPV_ARGS(&serviceProv));
+
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to get the service provider.");
+      goto done;
+    }
+
+  /* Get the shell browser */
+  hr = serviceProv->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&shellBrowser));
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to find the top level browser.");
+      goto done;
+    }
+
+  hr = shellBrowser->QueryActiveShellView(&shellView);
+
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to find the active view.");
+      goto done;
+    }
+
+  hr = shellView->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&bgDisp));
+
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to get the views background.");
+      goto done;
+    }
+
+  hr = bgDisp->QueryInterface(IID_PPV_ARGS(&folderView));
+
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to get the folder view.");
+      goto done;
+    }
+
+
+  hr = folderView->get_Application(&sDisp);
+
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to get the shell dispatch.");
+      goto done;
+    }
+
+  hr = sDisp->QueryInterface(IID_PPV_ARGS(&shellDispatch));
+
+  if (FAILED(hr))
+    {
+      ERRORPRINTF ("Failed to get the shell dispatch interface.");
+      goto done;
+    }
+
+  wbuf = utf8_to_wchar((*stacktop)->text, strlen((*stacktop)->text));
+  if (!wbuf)
+    {
+      ERRORPRINTF ("Failed to convert argument to wchar. error = 0x%lx.", hr);
+      goto done;
+    }
+
+  if (!shellexecute(shellDispatch, wbuf))
+    {
+      ERRORPRINTF ("Failed to execute.");
+    }
+  xfree (wbuf);
+done:
+  if (folderView)
+    {
+      folderView->Release();
+    }
+  if (disp)
+    {
+      disp->Release();
+    }
+  if (shellBrowser)
+    {
+      shellBrowser->Release();
+    }
+  if (shellWindows)
+    {
+      shellWindows->Release();
+    }
+  if (shellView)
+    {
+      shellView->Release();
+    }
+  if (sDisp)
+    {
+      sDisp->Release();
+    }
+  if (shellDispatch)
+    {
+      shellDispatch->Release();
+    }
+  if (serviceProv)
+    {
+      serviceProv->Release();
+    }
+  CoUninitialize();
+  return;
+}
+#ifdef __cplusplus
+}
+#endif
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/packaging/exdll.h	Tue Sep 16 19:48:22 2014 +0200
@@ -0,0 +1,39 @@
+/* exdll.h for use with TrustBrdige
+ * Copyright (C) 1999-2005 Nullsoft, Inc.
+ * 
+ * This license applies to everything in the NSIS package, except
+ * where otherwise noted.
+ * 
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any
+ * damages arising from the use of this software.
+ * 
+ * Permission is granted to anyone to use this software for any
+ * purpose, including commercial applications, and to alter it and
+ * redistribute it freely, subject to the following restrictions:
+ * 
+ * 1. The origin of this software must not be misrepresented; you must
+ *    not claim that you wrote the original software. If you use this
+ *    software in a product, an acknowledgment in the product
+ *    documentation would be appreciated but is not required.
+ * 
+ * 2. Altered source versions must be plainly marked as such, and must
+ *    not be misrepresented as being the original software.
+ * 
+ * 3. This notice may not be removed or altered from any source
+ *    distribution.
+ ************************************************************
+ * 2005-11-14 wk  Applied license text to orginal exdll.h file from
+ *                NSIS 2.0.4 and did some formatting changes.
+ * 2014-09-16 ah  Stripped out uneccesary stuff for TrustBridge plugin
+ */
+
+#ifndef _EXDLL_H_
+#define _EXDLL_H_
+
+/* only include this file from one place in your DLL.  (it is all
+   static, if you use it in two places it will fail) */
+
+/* For page showing plug-ins */
+
+#endif//_EXDLL_H_
--- a/packaging/trustbridge.nsi	Tue Sep 16 11:45:32 2014 +0200
+++ b/packaging/trustbridge.nsi	Tue Sep 16 19:48:22 2014 +0200
@@ -27,6 +27,8 @@
 !define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME ""
 !define MULTIUSER_INSTALLMODE_INSTDIR "${productname_short}"
 
+!addplugindir "${plugin_dir}"
+
 !include "MultiUser.nsh"
 !include "MUI2.nsh"
 !include "FileFunc.nsh"
@@ -93,9 +95,9 @@
 Computer installliert. $\r$\n$\r$\n\
 Klicken Sie auf 'Fertig stellen', um den Installations-Assistenten\
 zu schließen."
-;!define MUI_FINISHPAGE_RUN $INSTDIR\trustbridge.exe
-;!define MUI_FINISHPAGE_RUN_TEXT "Anwendung starten"
-;!define MUI_FINISHPAGE_RUN_FUNCTION RunAsUser
+!define MUI_FINISHPAGE_RUN $INSTDIR\trustbridge.exe
+!define MUI_FINISHPAGE_RUN_TEXT "Anwendung starten"
+!define MUI_FINISHPAGE_RUN_FUNCTION RunAsUser
 !define MUI_FINISHPAGE_LINK "Mehr unter ${info_url}"
 !define MUI_FINISHPAGE_LINK_LOCATION "${info_url}"
 
@@ -186,7 +188,7 @@
 ; the UI elevated as this could be a security
 ; problem.
 Function RunAsUser
-  ExecShell "" "$INSTDIR\trustbridge.exe"
+  DesktopShellRun::Exec "$INSTDIR\trustbridge.exe"
 FunctionEnd
 
 ;--------------------------------
@@ -258,6 +260,9 @@
 
   !insertmacro MUI_STARTMENU_WRITE_END
 done:
+  StrCmp $is_update '1' run_silent dont_run
+run_silent:
+dont_run:
 
 SectionEnd
 
--- a/packaging/win-createpackage.sh.in	Tue Sep 16 11:45:32 2014 +0200
+++ b/packaging/win-createpackage.sh.in	Tue Sep 16 19:48:22 2014 +0200
@@ -26,6 +26,7 @@
 
 makensis -Dfiles_dir=$TMPDIR \
     -Dcompany="Bundesamt für Sicherheit in der Informationstechnik" \
+    -Dplugin_dir="@CMAKE_CURRENT_BINARY_DIR@" \
     -Dversion_number=@PROJECT_VERSION@ \
     -Dsetupname="$TMPINST" \
     -Dproductname="TrustBridge" \
@@ -42,6 +43,7 @@
 
 makensis -Dfiles_dir=$TMPDIR \
     -Dcompany="Bundesamt für Sicherheit in der Informationstechnik" \
+    -Dplugin_dir="@CMAKE_CURRENT_BINARY_DIR@" \
     -Dversion_number=@PROJECT_VERSION@ \
     -Dsetupname="@CMAKE_BINARY_DIR@/TrustBridge-@PROJECT_VERSION@.exe" \
     -Dproductname="TrustBridge" \

http://wald.intevation.org/projects/trustbridge/