1
0
mirror of https://github.com/Kopano-dev/kopano-ol-extension.git synced 2023-10-10 13:37:40 +02:00

[KOE-124] Lots of small fixes for account share setup

This commit is contained in:
Patrick Simpson 2017-11-30 09:26:12 +02:00
parent ae076fe12f
commit 6d50679269
15 changed files with 242 additions and 52 deletions

View File

@ -97,6 +97,7 @@ namespace Acacia
public const string ZPUSH_CAPABILITY_NOTES = "notes";
public const string ZPUSH_CAPABILITY_OUT_OF_OFFICE = "oof";
public const string ZPUSH_CAPABILITY_OUT_OF_OFFICE_TIMES = "ooftime";
public const string ZPUSH_CAPABILITY_IMPERSONATE = "impersonate";
#endregion
@ -118,5 +119,6 @@ namespace Acacia
public const string PLUGIN_REGISTRY_LOGLEVEL = "LogLevel";
#endregion
}
}

View File

@ -65,6 +65,15 @@ namespace Acacia.Features.SharedFolders
set { RegistryUtil.SetConfigValue("SharedFolders", "DefaultFolderNameFormat", value, Microsoft.Win32.RegistryValueKind.String); }
}
[AcaciaOption("If enabled, the 'Impersonate' capability is added, allowing whole stores to be opened through " +
"user impersonation; if Z-Push supports it.")]
public bool AllowImpersonate
{
get { return GetOption(OPTION_ALLOW_IMPERSONATE); }
set { SetOption(OPTION_ALLOW_IMPERSONATE, value); }
}
private static readonly BoolOption OPTION_ALLOW_IMPERSONATE = new BoolOption("AllowImpersonate", false);
#endregion
public override void Startup()
@ -84,6 +93,13 @@ namespace Acacia.Features.SharedFolders
SetupHierarchyChangeSuppression();
}
override public void GetCapabilities(ZPushCapabilities caps)
{
base.GetCapabilities(caps);
if (AllowImpersonate)
caps.Add(Constants.ZPUSH_CAPABILITY_IMPERSONATE);
}
#region UI
private bool CanManageFolder(MenuItem<IFolder> b, IFolder folder)

View File

@ -290,6 +290,8 @@ namespace Acacia.Features.SharedFolders
// Restart
IRestarter restarter = ThisAddIn.Instance.Restarter();
restarter.CloseWindows = true;
foreach (StoreTreeNode node in state.stores)
restarter.OpenShare(_account, node.User);
restarter.Restart();
}
}, true)
@ -362,7 +364,7 @@ namespace Acacia.Features.SharedFolders
if (select)
{
FocusNode(node, false);
FocusNode(node, !_folders.SupportsWholeStore);
}
}
@ -392,6 +394,7 @@ namespace Acacia.Features.SharedFolders
private void WholeStoreShareChanged(KTreeNode node)
{
// TODO: check duplicate email address
StoreTreeNode storeNode = (StoreTreeNode)node;
_dirtyWholeStores[storeNode.User] = storeNode.IsWholeStoreDirty;
CheckDirty();

View File

@ -43,7 +43,7 @@ namespace Acacia.Features.SharedFolders
public bool SupportsWholeStore
{
get { return true; } // TODO: use capability
get { return _feature.AllowImpersonate; } // TODO: use capability
}
public FeatureSharedFolders Feature { get { return _feature; } }

View File

@ -65,7 +65,7 @@ namespace Acacia.Features.SharedFolders
ChildLoader = new UserFolderLoader(this, folders, user);
ChildLoader.ReloadOnCloseOpen = true;
HasCheckBox = folders.SupportsWholeStore;
HasCheckBox = !string.IsNullOrEmpty(user.EmailAddress) && folders.SupportsWholeStore;
ApplyReadOnly(this, IsReadOnly);
// TODO: better icons, better way of handling this

View File

@ -27,6 +27,8 @@ namespace Acacia.Stubs
{
AccountType AccountType { get; }
string AccountId { get; }
IStore Store { get; }
void SendReceive();

View File

@ -35,6 +35,7 @@ namespace Acacia.Stubs
IEnumerable<Feature> Features { get; }
IEnumerable<KeyValuePair<string,string>> COMAddIns { get; }
string Version { get; }
string VersionMajor { get; }
ISyncObject GetSyncObject();
#region UI

View File

@ -35,11 +35,13 @@ namespace Acacia.Stubs.OutlookWrappers
[TypeConverter(typeof(ExpandableObjectConverter))]
class AccountWrapper : ComWrapper<NSOutlook.Application>, IAccount, LogContext
{
private readonly string _accountId;
private readonly string _regPath;
private readonly IStore _store;
internal AccountWrapper(NSOutlook.Application item, string regPath, IStore store) : base(item)
{
this._accountId = System.IO.Path.GetFileName(regPath);
this._regPath = regPath;
this._store = store;
@ -81,10 +83,15 @@ namespace Acacia.Stubs.OutlookWrappers
{
get
{
return (DeviceId == null) ? AccountType.Other : AccountType.EAS;
return (UserName == null) ? AccountType.Other : AccountType.EAS;
}
}
public string AccountId
{
get { return _accountId; }
}
[Browsable(false)]
public IStore Store
{

View File

@ -304,7 +304,10 @@ namespace Acacia.Stubs.OutlookWrappers
{
get { return _app.Version; }
}
public string VersionMajor
{
get { return _app.Version.Split('.')[0]; }
}
public FeatureType GetFeature<FeatureType>()
where FeatureType : Feature

View File

@ -72,29 +72,27 @@ namespace Acacia.Stubs.OutlookWrappers
commandLine += " /cleankoe " + Util.QuoteCommandLine(path);
}
}
foreach(KeyValuePair<ZPushAccount, GABUser> share in _shares)
if (_shares.Count > 0)
{
using (RegistryKey key = OutlookRegistryUtils.OpenProfileOutlookKey(_addIn.ProfileName, RegistryKeyPermissionCheck.ReadWriteSubTree))
foreach (KeyValuePair<ZPushAccount, GABUser> share in _shares)
{
int accountId = (int)key.GetValue(OutlookConstants.REG_VAL_NEXT_ACCOUNT_ID);
using (RegistryKey accountKey = key.CreateSubKey(string.Format("{0:X8}", accountId)))
{
accountKey.SetValue(OutlookConstants.REG_VAL_ACCOUNTNAME, share.Value.UserName + " through " + share.Key.DisplayName);
accountKey.SetValue(OutlookConstants.REG_VAL_DISPLAYNAME, "Share for " + share.Value.DisplayName);
accountKey.SetValue(OutlookConstants.REG_VAL_EMAIL, "test" + share.Key.Account.SmtpAddress);
accountKey.SetValue(OutlookConstants.REG_VAL_EAS_SERVER, share.Key.Account.ServerURL);
accountKey.SetValue(OutlookConstants.REG_VAL_EAS_USERNAME, share.Key.Account.UserName + ".share." + share.Value.UserName);
accountKey.SetValue(OutlookConstants.REG_VAL_EAS_PASSWORD, share.Key.Account.EncryptedPassword);
//accountKey.SetValue(OutlookConstants.REG_VAL_EAS_DEVICEID, share.Key.Account.DeviceId);
accountKey.SetValue("clsid", "{ED475415-B0D6-11D2-8C3B-00104B2A6676}");
}
key.SetValue(OutlookConstants.REG_VAL_NEXT_ACCOUNT_ID, accountId + 1);
// TODO: escaping
commandLine += " /sharekoe " + Util.QuoteCommandLine(_addIn.ProfileName + ":" +
_addIn.VersionMajor + ":" +
share.Key.Account.AccountId + ":" +
share.Value.UserName + ":" + share.Value.EmailAddress + ":" +
share.Value.EmailAddress);
}
}
string arch = Environment.Is64BitProcess ? "x64" : "x86";
// Run that
Process process = new Process();
process.StartInfo = new ProcessStartInfo(RestarterPath, Process.GetCurrentProcess().Id + " " + commandLine);
process.StartInfo = new ProcessStartInfo(RestarterPath,
Process.GetCurrentProcess().Id + " " +
arch + " " +
commandLine);
process.Start();
// And close us and any other windows

View File

@ -212,7 +212,7 @@ namespace Acacia.UI.Outlook
string title = Properties.Resources.Ribbon_Title;
// Convert to upper case for Outlook 2013, to match other ribbons
if (ThisAddIn.Instance.Version.StartsWith("15."))
if (ThisAddIn.Instance.VersionMajor.Equals("15"))
{
title = title.ToUpper();
}

View File

@ -1,15 +1,26 @@
#include "EASAccount.h"
static const wstring R_ACCOUNT_NAME = L"Account Name";
static const wstring R_DISPLAY_NAME = L"Display Name";
static const wstring R_SERVER_URL = L"EAS Server URL";
static const wstring R_USERNAME = L"EAS User";
static const wstring R_EMAIL = L"Email";
static const wstring R_EMAIL_ORIGINAL = L"KOE Share For";
static const wstring R_PASSWORD = L"EAS Password";
struct Account
{
public:
wstring profileName;
wstring outlookVersion;
wstring accountName;
wstring displayName;
wstring email;
wstring emailOriginal;
wstring server;
wstring username;
wstring password;
vector<byte> encryptedPassword;
wstring dataFolder;
private:
wstring path;
@ -55,7 +66,50 @@ public:
void Create()
{
CheckInit();
DeterminePath();
// Set up the account
OpenProfileAdmin();
EncryptPassword();
CreateMessageService();
GetEntryId();
CreateAccount();
CommitAccountKey();
PatchMessageStore();
}
void LoadFromAccountId(const wstring &accountId)
{
OpenAccountKey(accountId);
try
{
accountName = RegReadAccountKey(R_ACCOUNT_NAME);
displayName = RegReadAccountKey(R_DISPLAY_NAME);
email = RegReadAccountKey(R_EMAIL);
server = RegReadAccountKey(R_SERVER_URL);
username = RegReadAccountKey(R_USERNAME);
encryptedPassword = RegReadAccountKeyBinary(R_PASSWORD);
}
// Clean up
catch (...)
{
if (hKeyNewAccount)
{
RegCloseKey(hKeyNewAccount);
hKeyNewAccount = nullptr;
}
throw;
}
if (hKeyNewAccount)
{
RegCloseKey(hKeyNewAccount);
hKeyNewAccount = nullptr;
}
}
private:
void DeterminePath()
{
// Determine the .ost path
if (dataFolder.empty())
{
@ -65,25 +119,27 @@ public:
}
// Somehow it only works if there's a number in between parentheses
path = dataFolder + email + L" - " + profileName + L"(1).ost";
// Set up the account
OpenProfileAdmin();
CreateMessageService();
GetEntryId();
CreateAccount();
CommitAccountKey();
PatchMessageStore();
}
private:
void EncryptPassword()
{
// TODO: handle the case password is not set in registry
if (encryptedPassword.empty())
encryptedPassword = EncryptPassword(password, L"EAS Password");
}
void CheckInit()
{
#define DoCheckInit(field) do{ if (field.empty()) throw exception("Field " #field " not initialised"); } while(0)
DoCheckInit(profileName);
DoCheckInit(outlookVersion);
DoCheckInit(accountName);
DoCheckInit(displayName);
DoCheckInit(email);
DoCheckInit(server);
DoCheckInit(username);
if (encryptedPassword.empty())
DoCheckInit(password);
#undef DoCheckInit
}
@ -137,9 +193,8 @@ private:
msprops[2].Value.lpszW = (LPWSTR)displayName.c_str();
msprops[3].ulPropTag = PR_PROFILE_SECURE_MAILBOX;
vector<byte> encPassword = EncryptPassword(password, L"S010267f0");
msprops[3].Value.bin.cb = (ULONG)encPassword.size();
msprops[3].Value.bin.lpb = &encPassword[0];
msprops[3].Value.bin.cb = (ULONG)encryptedPassword.size();
msprops[3].Value.bin.lpb = &encryptedPassword[0];
msprops[4].ulPropTag = PR_RESOURCE_FLAGS;
msprops[4].Value.l = SERVICE_NO_PRIMARY_IDENTITY | SERVICE_CREATE_WITH_STORE;
@ -182,13 +237,31 @@ private:
}
}
void AllocateAccountKey()
void OpenAccountsKey()
{
if (hKeyAccounts != nullptr)
return;
wchar_t keyPath[MAX_PATH];
// Open the accounts key
swprintf_s(keyPath, ARRAYSIZE(keyPath), KEY_ACCOUNTS, 16, profileName.c_str());
swprintf_s(keyPath, ARRAYSIZE(keyPath), KEY_ACCOUNTS, outlookVersion.c_str(), profileName.c_str());
CHECK_L(RegOpenKey(HKEY_CURRENT_USER, keyPath, &hKeyAccounts), "OpenAccountsKey");
}
void OpenAccountKey(const wstring &accountId)
{
OpenAccountsKey();
// Open the subkey
CHECK_L(RegOpenKey(hKeyAccounts, accountId.c_str(), &hKeyNewAccount), "OpenAccountKey");
}
void AllocateAccountKey()
{
OpenAccountsKey();
wchar_t keyPath[MAX_PATH];
// Get the NextAccountID value
DWORD size = sizeof(accountId);
@ -199,6 +272,23 @@ private:
CHECK_L(RegCreateKey(hKeyAccounts, keyPath, &hKeyNewAccount), "CreateAccountKey");
}
wstring RegReadAccountKey(const wstring &name)
{
wchar_t buffer[4096];
DWORD size = sizeof(buffer);
CHECK_L(RegQueryValueEx(hKeyNewAccount, name.c_str(), nullptr, nullptr, (LPBYTE)buffer, &size), "RegReadAccountKey");
return buffer;
}
vector<byte> RegReadAccountKeyBinary(const wstring &name)
{
vector<byte> buffer(4096);
DWORD size = (DWORD)buffer.size();
CHECK_L(RegQueryValueEx(hKeyNewAccount, name.c_str(), nullptr, nullptr, (LPBYTE)&buffer[0], &size), "RegReadAccountKey");
buffer.resize(size);
return buffer;
}
void WriteAccountKey(const wstring &name, const wstring &value)
{
CHECK_L(RegSetValueEx(hKeyNewAccount, name.c_str(), 0, REG_SZ, (LPBYTE)value.data(), (DWORD)value.size() * 2), "WriteAccountKey");
@ -290,15 +380,18 @@ private:
AllocateAccountKey();
// Write the values
WriteAccountKey(L"Account Name", accountName);
WriteAccountKey(L"Display Name", displayName);
WriteAccountKey(L"EAS Server URL", server);
WriteAccountKey(L"EAS User", username);
WriteAccountKey(L"Email", email);
WriteAccountKey(R_ACCOUNT_NAME, accountName);
WriteAccountKey(R_DISPLAY_NAME, displayName);
WriteAccountKey(R_SERVER_URL, server);
WriteAccountKey(R_USERNAME, username);
WriteAccountKey(R_EMAIL, email);
if (!emailOriginal.empty())
WriteAccountKey(R_EMAIL_ORIGINAL, emailOriginal);
WriteAccountKey(L"clsid", L"{ED475415-B0D6-11D2-8C3B-00104B2A6676}");
vector<byte> encrypted = EncryptPassword(password, L"EAS Password");
WriteAccountKey(L"EAS Password", &encrypted[0], encrypted.size());
WriteAccountKey(R_PASSWORD, &encryptedPassword[0], encryptedPassword.size());
WriteAccountKey(L"Service UID", &service, sizeof(service));
@ -400,6 +493,9 @@ private:
// Delete existing store
DeleteFile(path.c_str());
if (entryId.size() == 0)
throw exception("entryId not initialised");
// Open the msg store to finalise creation
CHECK_H(session->OpenMsgStore(0, (ULONG)entryId.size(), (LPENTRYID)&entryId[0], nullptr,
MDB_NO_DIALOG | MDB_WRITE | MAPI_DEFERRED_ERRORS, &msgStore), "OpenMsgStore");
@ -430,15 +526,27 @@ private:
};
int __cdecl wmain(int, wchar_t **)
int __cdecl wmain(int argc, wchar_t **argv)
{
// Parse the command line
// Usage:
Account account;
// Main
try
{
if (argc != 7)
{
fwprintf(stderr, L"EASAccount: <profile> <outlook version> <accountid> <username> <email> <display>\n");
exit(3);
}
account.profileName = argv[1];
account.outlookVersion = argv[2];
account.LoadFromAccountId(argv[3]);
account.username = account.username + L"+share+" + argv[4];
account.emailOriginal = account.email;
account.email = argv[5];
account.accountName = account.email;
account.displayName = argv[6];
// Create the account
account.Create();
}

View File

@ -38,7 +38,7 @@
using namespace std;
static const wchar_t *KEY_ACCOUNTS = L"SOFTWARE\\Microsoft\\Office\\%d.0\\Outlook\\Profiles\\%s\\9375CFF0413111d3B88A00104B2A6676";
static const wchar_t *KEY_ACCOUNTS = L"SOFTWARE\\Microsoft\\Office\\%s.0\\Outlook\\Profiles\\%s\\9375CFF0413111d3B88A00104B2A6676";
static const wchar_t *KEY_OLKMAIL = L"{ED475418-B0D6-11D2-8C3B-00104B2A6676}";
static const wchar_t *KEY_OLKADDRESSBOOK = L"{ED475419-B0D6-11D2-8C3B-00104B2A6676}";
static const wchar_t *KEY_OLKSTORE = L"{ED475420-B0D6-11D2-8C3B-00104B2A6676}";

View File

@ -77,17 +77,25 @@
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)Build\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(PlatformShortName)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)Build\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(PlatformShortName)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)Build\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(PlatformShortName)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)Build\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(PlatformShortName)</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>

View File

@ -50,13 +50,15 @@ namespace OutlookRestarter
try
{
string procPath = args[1];
List<string> procArgs = args.Skip(2).ToList();
int procId = int.Parse(args[0]);
string arch = args[1];
string procPath = args[2];
List<string> procArgs = args.Skip(3).ToList();
try
{
Logger.Instance.Debug(typeof(OutlookRestarter), "Waiting1");
// Attempt waiting for the process to finish
int procId = int.Parse(args[0]);
Logger.Instance.Debug(typeof(OutlookRestarter), "Waiting2");
Process proc = Process.GetProcessById(procId);
Logger.Instance.Debug(typeof(OutlookRestarter), "Waiting3");
@ -76,6 +78,11 @@ namespace OutlookRestarter
string path = procArgs[i];
HandleCleanKoe(path);
}
else if (procArgs[i] == "/sharekoe")
{
++i;
HandleShareKoe(arch, procArgs[i]);
}
else if (procArgs[i].StartsWith("/"))
{
useArgs.Add(procArgs[i]);
@ -87,6 +94,7 @@ namespace OutlookRestarter
}
string argsString = string.Join(" ", useArgs);
Logger.Instance.Debug(typeof(OutlookRestarter), "Parsed arguments: {0}", argsString);
// Start the process
Process process = new Process();
process.StartInfo = new ProcessStartInfo(procPath, argsString);
@ -101,6 +109,40 @@ namespace OutlookRestarter
}
}
private static void HandleShareKoe(string arch, string rawArgs)
{
string baseDir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
string path = Path.Combine(baseDir, "EASAccount-" + arch + ".exe");
string[] args = rawArgs.Split(':');
for (int i = 0; i < args.Length; ++i)
args[i] = "\"" + args[i] + "\"";
string argsString = string.Join(" ", args);
Logger.Instance.Debug(typeof(OutlookRestarter), "Request to open account: {0}: {1}", path, argsString);
Process process = new Process();
process.StartInfo = new ProcessStartInfo(path, argsString);
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.ErrorDataReceived += (s, e) =>
{
if (!string.IsNullOrEmpty(e.Data.Trim()))
Logger.Instance.Warning(typeof(OutlookRestarter), "EASAccount: {0}", e.Data.Trim());
};
process.OutputDataReceived += (s, e) =>
{
if (!string.IsNullOrEmpty(e.Data.Trim()))
Logger.Instance.Debug(typeof(OutlookRestarter), "EASAccount: {0}", e.Data.Trim());
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit(FINISH_WAIT_TIME);
Logger.Instance.Debug(typeof(OutlookRestarter), "Opened accounts: {0}", argsString);
}
private static void HandleCleanKoe(string path)
{
Logger.Instance.Debug(typeof(OutlookRestarter), "Request to remove store: {0}", path);