Updating Installer
- UI for providing a cookie at install time
- possibility generating a random cookie
- update existing conf files with fixes for CVE-2022-24706
diff --git a/bin/build_installer.ps1 b/bin/build_installer.ps1
index 2bde102..d528125 100644
--- a/bin/build_installer.ps1
+++ b/bin/build_installer.ps1
@@ -69,9 +69,10 @@
candle -arch x64 "-dCouchDir=${CouchDB}" couchdbfiles.wxs
candle -arch x64 -ext WiXUtilExtension couchdb_wixui.wxs
candle -arch x64 -ext WiXUtilExtension adminprompt.wxs
+candle -arch x64 -ext WiXUtilExtension cookieprompt.wxs
candle -arch x64 -ext WiXUtilExtension customexit.wxs
candle -arch x64 -ext WiXUtilExtension CouchInstallDirDlg.wxs
-light -sw1076 -sice:ICE17 -ext WixUIExtension -ext WiXUtilExtension "-cultures:en-us;en;neutral" adminprompt.wixobj couchdb.wixobj couchdbfiles.wixobj couchdb_wixui.wixobj customexit.wixobj CouchInstallDirDlg.wixobj -out apache-couchdb-${CouchDBVersion}.msi
+light -sw1076 -sice:ICE17 -ext WixUIExtension -ext WiXUtilExtension "-cultures:en-us;en;neutral" adminprompt.wixobj cookieprompt.wixobj couchdb.wixobj couchdbfiles.wixobj couchdb_wixui.wixobj customexit.wixobj CouchInstallDirDlg.wixobj -out apache-couchdb-${CouchDBVersion}.msi
Pop-Location
diff --git a/installer/CustomAction/CouchIniAction.cs b/installer/CustomAction/CouchIniAction.cs
index 0b7e0ff..4a82e3f 100644
--- a/installer/CustomAction/CouchIniAction.cs
+++ b/installer/CustomAction/CouchIniAction.cs
@@ -3,101 +3,194 @@
using System.IO;
using System.Security.Cryptography;
using System.Text;
+using System.Text.RegularExpressions;
using Microsoft.Deployment.WindowsInstaller;
namespace CustomAction
{
- public class CustomActions
- {
- [CustomAction] public static ActionResult WriteAdminIniFile(Session session)
- {
- try
- {
- if (!File.Exists(session.CustomActionData["ADMINCONFIGFILE"])) {
- using (StreamWriter writer = new StreamWriter(session.CustomActionData["ADMINCONFIGFILE"]))
- {
- writer.WriteLine("; CouchDB Windows installer-generated admin user");
- writer.WriteLine("[admins]");
- writer.WriteLine($"{session.CustomActionData["ADMINUSER"]} = {session.CustomActionData["ADMINPASSWORD"]}");
- }
- }
- }
- catch (Exception ex)
- {
- session.Log("ERROR in custom action WriteAdminIniFile {0}",
- ex.ToString());
- return ActionResult.Failure;
- }
-
- return ActionResult.Success;
- }
-
- [CustomAction] public static ActionResult MaybeCopyIniFiles(Session session)
- {
- try
- {
- string[] files = new string[2];
- files[0] = "vm.args";
- files[1] = "local.ini";
-
- foreach (string file in files)
+ public class CustomActions
+ {
+ [CustomAction]
+ public static ActionResult InitCookieValue(Session session)
+ {
+ try
{
- if (!File.Exists(Path.Combine(session.CustomActionData["ETCDIR"], file)))
- {
- File.Copy(
- Path.Combine(session.CustomActionData["ETCDIR"], file + ".dist"),
- Path.Combine(session.CustomActionData["ETCDIR"], file)
- );
- }
+ byte[] buffer = new byte[16];
+ RandomNumberGenerator rng = RNGCryptoServiceProvider.Create();
+ rng.GetBytes(buffer);
+ session["COOKIEVALUE"] = BitConverter.ToString(buffer).Replace("-", String.Empty);
}
- }
- catch (Exception ex)
- {
- session.Log("ERROR in custom action MaybeCopyIniFiles {0}",
- ex.ToString());
- return ActionResult.Failure;
- }
- return ActionResult.Success;
- }
-
- [CustomAction] public static ActionResult MaybeRemoveUserConfig(Session session)
- {
- try
- {
- string[] files = new string[2];
- files[0] = "vm.args";
- files[1] = "local.ini";
-
- foreach (string file in files)
+ catch (Exception ex)
{
- if (File.Exists(Path.Combine(session.CustomActionData["ETCDIR"], file)) &&
- File.Exists(Path.Combine(session.CustomActionData["ETCDIR"], file + ".dist")))
- {
- if (GetChecksum(Path.Combine(session.CustomActionData["ETCDIR"], file)) ==
- GetChecksum(Path.Combine(session.CustomActionData["ETCDIR"], file + ".dist")))
- {
- File.Delete(Path.Combine(session.CustomActionData["ETCDIR"], file));
- }
- }
+ session.Log("ERROR in custom action InitCookieValue {0}",
+ ex.ToString());
+ return ActionResult.Failure;
}
- }
- catch (Exception ex)
- {
- session.Log("ERROR in custom action MaybeRemoveUserConfig {0}",
- ex.ToString());
- return ActionResult.Failure;
- }
- return ActionResult.Success;
- }
- private static string GetChecksum(string file)
- {
- using (FileStream stream = File.OpenRead(file))
- {
- SHA256Managed sha = new SHA256Managed();
- byte[] checksum = sha.ComputeHash(stream);
- return BitConverter.ToString(checksum).Replace("-", String.Empty);
- }
- }
- }
+ return ActionResult.Success;
+ }
+ [CustomAction]
+ public static ActionResult WriteAdminIniFile(Session session)
+ {
+ try
+ {
+ if (!File.Exists(session.CustomActionData["ADMINCONFIGFILE"]))
+ {
+ using (StreamWriter writer = new StreamWriter(session.CustomActionData["ADMINCONFIGFILE"]))
+ {
+ writer.WriteLine("; CouchDB Windows installer-generated admin user");
+ writer.WriteLine("[admins]");
+ writer.WriteLine($"{session.CustomActionData["ADMINUSER"]} = {session.CustomActionData["ADMINPASSWORD"]}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ session.Log("ERROR in custom action WriteAdminIniFile {0}",
+ ex.ToString());
+ return ActionResult.Failure;
+ }
+
+ return ActionResult.Success;
+ }
+
+ [CustomAction]
+ public static ActionResult WriteCookieToVmArgs(Session session)
+ {
+ try
+ {
+ string VMARGSFILE = Path.Combine(session.CustomActionData["ETCDIR"], "vm.args");
+ if (File.Exists(VMARGSFILE))
+ {
+ session.Log("Patching erlang cookie in existing vm.args file");
+ PatchErlangCookie(session, VMARGSFILE);
+ }
+
+ string VMFile = Path.Combine(session.CustomActionData["ETCDIR"], "vm.args.dist");
+ byte[] VMBuffer = File.ReadAllBytes(VMFile);
+ string VMText = Regex.Replace(Encoding.UTF8.GetString(VMBuffer), @"# -setcookie", $"-setcookie {session.CustomActionData["COOKIEVALUE"]}");
+ File.WriteAllBytes(VMFile, Encoding.UTF8.GetBytes(VMText));
+ }
+ catch (Exception ex)
+ {
+ session.Log("ERROR in custom action WriteCookieToVmArgs {0}",
+ ex.ToString());
+ return ActionResult.Failure;
+ }
+
+ return ActionResult.Success;
+ }
+
+ private static void PatchErlangCookie(Session session, string file)
+ {
+ //Patching erlang cookie
+ byte[] VMBuffer = File.ReadAllBytes(file);
+ string VMText = Regex.Replace(Encoding.UTF8.GetString(VMBuffer), @"-setcookie \S*", $"-setcookie {session.CustomActionData["COOKIEVALUE"]}");
+ File.WriteAllBytes(file, Encoding.UTF8.GetBytes(VMText));
+ }
+
+ private static void PatchErlangInterface(Session session, string file)
+ {
+ byte[] VMBuffer = File.ReadAllBytes(file);
+
+ //Patching erlang interface
+ string pattern = @"-kernel inet_dist_use_interface";
+ string input = Encoding.UTF8.GetString(VMBuffer);
+ Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
+ if (!m.Success)
+ {
+ session.Log("Pattern \"inet_dist_use_interface\" not found, appending fix.");
+
+ using (StreamWriter sw = File.AppendText(file))
+ {
+ sw.WriteLine();
+ sw.WriteLine("# Which interfaces should the node listen on?");
+ sw.WriteLine("-kernel inet_dist_use_interface {127,0,0,1}");
+ sw.Close();
+ }
+ }
+ else
+ {
+ session.Log("Pattern \"inet_dist_use_interface\" found, skipping.");
+ }
+ }
+
+ [CustomAction]
+ public static ActionResult MaybeCopyIniFiles(Session session)
+ {
+ try
+ {
+ string[] files = new string[2];
+ files[0] = "vm.args";
+ files[1] = "local.ini";
+
+ string VMARGSFILE = Path.Combine(session.CustomActionData["ETCDIR"], files[0]);
+
+ if (File.Exists(VMARGSFILE))
+ {
+ session.Log("Patching erlang interface in existing vm.args file");
+ PatchErlangInterface(session, VMARGSFILE);
+ }
+
+ foreach (string file in files)
+ {
+ if (!File.Exists(Path.Combine(session.CustomActionData["ETCDIR"], file)))
+ {
+ File.Copy(
+ Path.Combine(session.CustomActionData["ETCDIR"], file + ".dist"),
+ Path.Combine(session.CustomActionData["ETCDIR"], file)
+ );
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ session.Log("ERROR in custom action MaybeCopyIniFiles {0}",
+ ex.ToString());
+ return ActionResult.Failure;
+ }
+ return ActionResult.Success;
+ }
+
+ [CustomAction]
+ public static ActionResult MaybeRemoveUserConfig(Session session)
+ {
+ try
+ {
+ string[] files = new string[2];
+ files[0] = "vm.args";
+ files[1] = "local.ini";
+
+ foreach (string file in files)
+ {
+ if (File.Exists(Path.Combine(session.CustomActionData["ETCDIR"], file)) &&
+ File.Exists(Path.Combine(session.CustomActionData["ETCDIR"], file + ".dist")))
+ {
+ if (GetChecksum(Path.Combine(session.CustomActionData["ETCDIR"], file)) ==
+ GetChecksum(Path.Combine(session.CustomActionData["ETCDIR"], file + ".dist")))
+ {
+ File.Delete(Path.Combine(session.CustomActionData["ETCDIR"], file));
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ session.Log("ERROR in custom action MaybeRemoveUserConfig {0}",
+ ex.ToString());
+ return ActionResult.Failure;
+ }
+ return ActionResult.Success;
+ }
+
+ private static string GetChecksum(string file)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ SHA256Managed sha = new SHA256Managed();
+ byte[] checksum = sha.ComputeHash(stream);
+ return BitConverter.ToString(checksum).Replace("-", String.Empty);
+ }
+ }
+ }
}
diff --git a/installer/CustomAction/CustomAction.config b/installer/CustomAction/CustomAction.config
index 16a542b..6bf9448 100644
--- a/installer/CustomAction/CustomAction.config
+++ b/installer/CustomAction/CustomAction.config
@@ -7,6 +7,6 @@
-->
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
- <supportedRuntime version="v2.0.50727" />
+ <supportedRuntime version="v4.0" />
</startup>
</configuration>
diff --git a/installer/CustomAction/CustomAction.csproj b/installer/CustomAction/CustomAction.csproj
index 7b6bd51..80399ef 100644
--- a/installer/CustomAction/CustomAction.csproj
+++ b/installer/CustomAction/CustomAction.csproj
@@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>CustomAction</RootNamespace>
<AssemblyName>CouchIniAction</AssemblyName>
- <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<WixCATargetsPath Condition=" '$(WixCATargetsPath)' == '' ">C:\Program Files (x86)\MSBuild\Microsoft\WiX\v3.x\wix.ca.targets</WixCATargetsPath>
</PropertyGroup>
diff --git a/installer/cookieprompt.wxs b/installer/cookieprompt.wxs
new file mode 100644
index 0000000..844ef85
--- /dev/null
+++ b/installer/cookieprompt.wxs
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+ <Property Id="COOKIEVALUE" Hidden="yes" />
+ <UI>
+ <Dialog Id="CookiePromptDlg" Width="370" Height="270" Title="Set Cookie value">
+ <Control Id="Title" Type="Text" X="20" Y="20" Width="220" Height="30" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Emphasized}Set Cookie value" />
+ <Control Id="Description" Type="Text" X="20" Y="50" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Emphasized}For security reasons, the cookie value that CouchDB instances use to communicate between each other needs to be set even in standalone mode." />
+ <Control Id="Label" Type="Text" Width="322" Height="10" X="20" Y="110" Text="Cookie value:" />
+ <Control Id="Textbox" Type="Edit" Width="200" Height="15" X="20" Y="123" Property="COOKIEVALUE" />
+ <Control Id="Validate" Type="PushButton" Width="100" Height="15" X="140" Y="180" Text="Validate Cookie" />
+ <Control Id="Random" Type="PushButton" Width="100" Height="15" X="20" Y="180" Text="Random Cookie">
+ <Publish Event="DoAction" Value="InitCookieValue">1</Publish>
+ <Publish Property="COOKIEVALUE" Value="[COOKIEVALUE]">1</Publish>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUINext)">
+ <Condition Action="disable"><![CDATA[(COOKIEVALUE = "") OR (COOKIEVALUE >< " ")]]></Condition>
+ <Condition Action="enable"><![CDATA[(COOKIEVALUE <> "") AND NOT (COOKIEVALUE >< " ")]]></Condition>
+
+ <Publish Property="COOKIEVALUE" Value="[COOKIEVALUE]">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ </Dialog>
+ </UI>
+ </Fragment>
+</Wix>
+
diff --git a/installer/couchdb.wxs.in b/installer/couchdb.wxs.in
index 390ac23..f979547 100644
--- a/installer/couchdb.wxs.in
+++ b/installer/couchdb.wxs.in
@@ -48,7 +48,7 @@
</Directory>
<DirectoryRef Id="TARGETDIR">
- <Merge Id="VCRedist141" SourceFile="C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Redist\MSVC\14.16.27012\MergeModules\Microsoft_VC141_CRT_x64.msm" DiskId="1" Language="0"/>
+ <Merge Id="VCRedist143" SourceFile="C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\14.31.31103\MergeModules\Microsoft_VC143_CRT_x64.msm" DiskId="1" Language="0"/>
</DirectoryRef>
<SetProperty Id="ARPINSTALLLOCATION" Value="[APPLICATIONFOLDER]"
@@ -105,8 +105,8 @@
<ComponentGroupRef Id="CouchDBFilesGroup" />
</Feature>
- <Feature Id="VCRedist141" Title="Visual C++ 14.1 Runtime" AllowAdvertise="no" Display="hidden" Level="1">
- <MergeRef Id="VCRedist141"/>
+ <Feature Id="VCRedist143" Title="Microsoft Visual C++ 2015-2022 Redistributable (x64)" AllowAdvertise="no" Display="hidden" Level="1">
+ <MergeRef Id="VCRedist143"/>
</Feature>
<!--Custom actions and execute sequences-->
@@ -119,6 +119,19 @@
Value="ADMINCONFIGFILE=[APPLICATIONFOLDER]etc\local.d\10-admins.ini;ADMINUSER=[ADMINUSER];ADMINPASSWORD=[ADMINPASSWORD]"
/>
<CustomAction
+ Id="SetCookieValueInVmArgsPropertyValues"
+ Property="WriteCookieToVmArgs"
+ Value="ETCDIR=[APPLICATIONFOLDER]etc;COOKIEVALUE=[COOKIEVALUE]"
+ />
+ <CustomAction
+ Id="InitCookieValue"
+ Return="check"
+ DllEntry="InitCookieValue"
+ HideTarget="yes"
+ BinaryKey="CouchIniActionDll"
+ Impersonate="no"
+ />
+ <CustomAction
Id="WriteAdminIniFile"
Return="check"
Execute="deferred"
@@ -127,6 +140,15 @@
BinaryKey="CouchIniActionDll"
Impersonate="no"
/>
+ <CustomAction
+ Id="WriteCookieToVmArgs"
+ Return="check"
+ Execute="deferred"
+ DllEntry="WriteCookieToVmArgs"
+ HideTarget="yes"
+ BinaryKey="CouchIniActionDll"
+ Impersonate="no"
+ />
<CustomAction
Id="SetMaybeCopyIniFilesValues"
@@ -252,8 +274,10 @@
<Property Id="SCHEDULEREBOOT" Value="0" />
<InstallExecuteSequence>
<Custom Action="SetAdminIniCustomActionPropertyValues" After="InstallFiles">NOT Installed AND NOT REMOVE</Custom>
- <Custom Action="WriteAdminIniFile" After="SetAdminIniCustomActionPropertyValues">NOT Installed AND NOT REMOVE</Custom>
- <Custom Action="SetMaybeCopyIniFilesValues" After="WriteAdminIniFile">NOT Installed AND NOT REMOVE</Custom>
+ <Custom Action="SetCookieValueInVmArgsPropertyValues" After="SetAdminIniCustomActionPropertyValues">NOT Installed AND NOT REMOVE</Custom>
+ <Custom Action="WriteAdminIniFile" After="SetCookieValueInVmArgsPropertyValues">NOT Installed AND NOT REMOVE</Custom>
+ <Custom Action="WriteCookieToVmArgs" After="WriteAdminIniFile">NOT Installed AND NOT REMOVE</Custom>
+ <Custom Action="SetMaybeCopyIniFilesValues" After="WriteCookieToVmArgs">NOT Installed AND NOT REMOVE</Custom>
<Custom Action="MaybeCopyIniFiles" After="SetMaybeCopyIniFilesValues">NOT Installed AND NOT REMOVE</Custom>
<Custom Action="InstallCouchDBService" After="MaybeCopyIniFiles">INSTALLSERVICE AND NOT Installed AND NOT REMOVE</Custom>
<Custom Action="StartCouchDBService" After="InstallCouchDBService">INSTALLSERVICE AND NOT Installed AND NOT REMOVE</Custom>
diff --git a/installer/couchdb_wixui.wxs b/installer/couchdb_wixui.wxs
index 453777f..336d5f3 100644
--- a/installer/couchdb_wixui.wxs
+++ b/installer/couchdb_wixui.wxs
@@ -44,9 +44,11 @@
<Publish Dialog="CouchInstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish>
<Publish Dialog="AdminPromptDlg" Control="Back" Event="NewDialog" Value="CouchInstallDirDlg">1</Publish>
- <Publish Dialog="AdminPromptDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
+ <Publish Dialog="AdminPromptDlg" Control="Next" Event="NewDialog" Value="CookiePromptDlg">1</Publish>
+ <Publish Dialog="CookiePromptDlg" Control="Back" Event="NewDialog" Value="AdminPromptDlg">1</Publish>
+ <Publish Dialog="CookiePromptDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
- <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="AdminPromptDlg" Order="1">NOT Installed</Publish>
+ <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="CookiePromptDlg" Order="1">NOT Installed</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2">Installed AND NOT PATCH</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">Installed AND PATCH</Publish>