/// Copyright 2016 Kopano b.v. /// /// This program is free software: you can redistribute it and/or modify /// it under the terms of the GNU Affero General Public License, version 3, /// as published by the Free Software Foundation. /// /// 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 Affero General Public License for more details. /// /// You should have received a copy of the GNU Affero General Public License /// along with this program.If not, see. /// /// Consult LICENSE file for details using Acacia.Features.GAB; using Acacia.Stubs; using Acacia.UI; using Acacia.Utils; using Acacia.ZPush; using Microsoft.Win32; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace Acacia.Features.FreeBusy { [AcaciaOption("Provides free/busy information on users in the Global Adress Book to schedule meetings.")] public class FeatureFreeBusy : Feature { public FeatureFreeBusy() { } public override void Startup() { Thread thread = new Thread(() => { try { Worker(); } catch (System.Exception e) { Logger.Instance.Error(this, "Unhandled exception: {0}", e); } }); thread.Name = "FreeBusy"; thread.Start(); } #region Settings public override FeatureSettings GetSettings() { return new FreeBusySettings(this); } public ZPushAccounts Accounts { get { return Watcher.Accounts; } } private const string REG_DOGABLOOKUP = "GABLookup"; private const string REG_DEFAULTACCOUNT = "Default"; public bool DoGABLookup { get { return RegistryUtil.GetConfigValue(Name, REG_DOGABLOOKUP, 1) != 0; } set { RegistryUtil.SetConfigValue(Name, REG_DOGABLOOKUP, value ? 1 : 0, RegistryValueKind.DWord); } } public ZPushAccount DefaultAccount { get { string val = RegistryUtil.GetConfigValue(Name, REG_DEFAULTACCOUNT, null); if (!string.IsNullOrEmpty(val)) { ZPushAccount account = Accounts.GetAccount(val); if (account != null) return account; } // Fall back to the first one return Accounts.GetAccounts().FirstOrDefault(); } set { RegistryUtil.SetConfigValue(Name, REG_DEFAULTACCOUNT, value == null ? "" : value.SmtpAddress, RegistryValueKind.String); } } #endregion private const string REG_KEY = @"Options\Calendar\Internet Free/Busy"; private const string REG_VALUE = @"Read URL"; internal const string URL_IDENTIFIER = "/zpush/"; private const int DEFAULT_PORT = 18632; private const string URL_PREFIX = @"http://127.0.0.1:{0}" + URL_IDENTIFIER; private const string URL = URL_PREFIX + "%NAME%@%SERVER%"; private void Worker() { Port = DEFAULT_PORT; // Register URL using (RegistryKey key = OutlookRegistryUtils.OpenOutlookKey(REG_KEY, Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree)) { if (key != null) { string oldURL = key.GetValueString(REG_VALUE); if (string.IsNullOrWhiteSpace(oldURL) || oldURL.Contains(URL_IDENTIFIER)) key.SetValue(REG_VALUE, string.Format(URL, Port)); } } FreeBusyServer server = new FreeBusyServer(this); // Run TcpListener listener = new TcpListener(IPAddress.Loopback, Port); listener.Start(); for (;;) { Interlocked.Increment(ref _iterationCount); try { for (;;) { // Wait for a connection TcpClient client = listener.AcceptTcpClient(); Interlocked.Increment(ref _requestCount); // And handle it in the UI thread to allow GAB access Tasks.Task(this, "FreeBusyHandler", () => server.HandleRequest(client)); } } catch (Exception e) { Logger.Instance.Error(this, "Error in FreeBusy server: {0}", e); } } } #region Debug public int Port { get; private set; } private long _requestCount; public long RequestCount { get { return Interlocked.Read(ref _requestCount); } } private long _iterationCount; public long IterationCount { get { return Interlocked.Read(ref _iterationCount); } } #endregion internal ZPushAccount FindZPushAccount(string username) { // Search through GABs if (DoGABLookup) { FeatureGAB gab = ThisAddIn.Instance.GetFeature(); if (gab != null) { foreach (GABHandler handler in gab.GABHandlers) { ZPushAccount account = handler.ActiveAccount; if (account != null && handler.Contacts != null) { // Look for the email address. If found, use the account associated with the GAB ISearch search = handler.Contacts.Search(); search.AddField("urn:schemas:contacts:email1").SetOperation(SearchOperation.Equal, username); using (IItem result = search.SearchOne()) { if (result != null) return account; } } } } } // Fall back to default account return DefaultAccount; } } }