diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35eceb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +## macOS +.DS_Store + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + + +# Carthage +Carthage/Build/ +Carthage/Checkouts \ No newline at end of file diff --git a/Application/Application.xcodeproj/project.pbxproj b/Application/Application.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a806cfb --- /dev/null +++ b/Application/Application.xcodeproj/project.pbxproj @@ -0,0 +1,515 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 7D16D6941F64E43300DB3161 /* Update.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D16D6901F64E43300DB3161 /* Update.m */; }; + 7D16D6951F64E43300DB3161 /* UpdateWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D16D6911F64E43300DB3161 /* UpdateWindow.xib */; }; + 7D16D6961F64E43300DB3161 /* UpdateWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D16D6931F64E43300DB3161 /* UpdateWindowController.m */; }; + 7D3B75141F13354900568828 /* StatusBarItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D3B74FD1F13354900568828 /* StatusBarItem.m */; }; + 7D5C5F521F200B9200C0E94A /* utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D5C5F511F200B9200C0E94A /* utilities.m */; }; + 7D7755E81F02E05B00D0017D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D7755E71F02E05B00D0017D /* AppDelegate.m */; }; + 7D7755EB1F02E05B00D0017D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D7755EA1F02E05B00D0017D /* main.m */; }; + 7D7755F01F02E05B00D0017D /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D7755EE1F02E05B00D0017D /* MainMenu.xib */; }; + 7DD25FF01F23B73C00277EC4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7DD25FEF1F23B73C00277EC4 /* Assets.xcassets */; }; + CD22785E20434CAC00C72C76 /* StatusBarPopover.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD22785B20434CAB00C72C76 /* StatusBarPopover.xib */; }; + CD22785F20434CAC00C72C76 /* StatusBarPopoverController.m in Sources */ = {isa = PBXBuildFile; fileRef = CD22785D20434CAB00C72C76 /* StatusBarPopoverController.m */; }; + CD2F8008244551AC009C3D77 /* NSApplicationKeyEvents.m in Sources */ = {isa = PBXBuildFile; fileRef = CD2F8007244551AC009C3D77 /* NSApplicationKeyEvents.m */; }; + CD2F800C24455333009C3D77 /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD2F800924455333009C3D77 /* AboutWindow.xib */; }; + CD2F800D24455333009C3D77 /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CD2F800B24455333009C3D77 /* AboutWindowController.m */; }; + CD2F801724468A8C009C3D77 /* patrons.txt in Resources */ = {isa = PBXBuildFile; fileRef = CD2F801624468A8C009C3D77 /* patrons.txt */; }; + CD6836682391DB6F00CF19C1 /* security.plist in Resources */ = {isa = PBXBuildFile; fileRef = CD6836672391DB6F00CF19C1 /* security.plist */; }; + CD8FD5D523BAE2D200EFE0FB /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD8FD5D323BAE2D100EFE0FB /* Preferences.xib */; }; + CD8FD5D623BAE2D200EFE0FB /* PrefsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CD8FD5D423BAE2D200EFE0FB /* PrefsWindowController.m */; }; + CD8FD5F623C05AD900EFE0FB /* RuleRow.m in Sources */ = {isa = PBXBuildFile; fileRef = CD8FD5F023C05AD800EFE0FB /* RuleRow.m */; }; + CD8FD5F723C05AD900EFE0FB /* RuleRowCell.m in Sources */ = {isa = PBXBuildFile; fileRef = CD8FD5F223C05AD800EFE0FB /* RuleRowCell.m */; }; + CD8FD5F823C05AD900EFE0FB /* RulesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CD8FD5F323C05AD800EFE0FB /* RulesWindowController.m */; }; + CD8FD5FA23C05AD900EFE0FB /* Rules.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD8FD5F523C05AD900EFE0FB /* Rules.xib */; }; + CDA88A792537CE2400C469BF /* Sentry.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CD21501B20AD2EE000CEF17B /* Sentry.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + CDC6098E263CB7EB006D1332 /* LogMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = CDC6098D263CB7EB006D1332 /* LogMonitor.m */; }; + CDC60991263CBD36006D1332 /* AVMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = CDC60990263CBD36006D1332 /* AVMonitor.m */; }; + CDC60994263CBEE7006D1332 /* Client.m in Sources */ = {isa = PBXBuildFile; fileRef = CDC60993263CBEE7006D1332 /* Client.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + CDA88A782537CE1A00C469BF /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + CDA88A792537CE2400C469BF /* Sentry.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 7D16D68F1F64E43300DB3161 /* Update.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Update.h; sourceTree = ""; }; + 7D16D6901F64E43300DB3161 /* Update.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Update.m; sourceTree = ""; }; + 7D16D6911F64E43300DB3161 /* UpdateWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UpdateWindow.xib; sourceTree = ""; }; + 7D16D6921F64E43300DB3161 /* UpdateWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UpdateWindowController.h; sourceTree = ""; }; + 7D16D6931F64E43300DB3161 /* UpdateWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UpdateWindowController.m; sourceTree = ""; }; + 7D3B74FD1F13354900568828 /* StatusBarItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarItem.m; sourceTree = ""; }; + 7D3B74FF1F13354900568828 /* StatusBarItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarItem.h; sourceTree = ""; }; + 7D3B75021F13354900568828 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = MainMenu.xib; sourceTree = ""; }; + 7D3B75221F13357D00568828 /* consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = consts.h; path = ../Shared/consts.h; sourceTree = ""; }; + 7D5C5F501F200B9200C0E94A /* utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = utilities.h; path = ../Shared/utilities.h; sourceTree = ""; }; + 7D5C5F511F200B9200C0E94A /* utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = utilities.m; path = ../Shared/utilities.m; sourceTree = ""; }; + 7D7755E31F02E05B00D0017D /* OverSight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OverSight.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7D7755E61F02E05B00D0017D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7D7755E71F02E05B00D0017D /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 7D7755EA1F02E05B00D0017D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 7D7755EF1F02E05B00D0017D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 7D7755F11F02E05B00D0017D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7DD25FEF1F23B73C00277EC4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + CD21501B20AD2EE000CEF17B /* Sentry.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sentry.framework; path = ../Carthage/Build/Mac/Sentry.framework; sourceTree = ""; }; + CD22785B20434CAB00C72C76 /* StatusBarPopover.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StatusBarPopover.xib; sourceTree = ""; }; + CD22785C20434CAB00C72C76 /* StatusBarPopoverController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarPopoverController.h; sourceTree = ""; }; + CD22785D20434CAB00C72C76 /* StatusBarPopoverController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarPopoverController.m; sourceTree = ""; }; + CD2F8006244551AB009C3D77 /* NSApplicationKeyEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSApplicationKeyEvents.h; sourceTree = ""; }; + CD2F8007244551AC009C3D77 /* NSApplicationKeyEvents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSApplicationKeyEvents.m; sourceTree = ""; }; + CD2F800924455333009C3D77 /* AboutWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutWindow.xib; sourceTree = ""; }; + CD2F800A24455333009C3D77 /* AboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutWindowController.h; sourceTree = ""; }; + CD2F800B24455333009C3D77 /* AboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutWindowController.m; sourceTree = ""; }; + CD2F801624468A8C009C3D77 /* patrons.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = patrons.txt; path = ../Shared/patrons.txt; sourceTree = ""; }; + CD6836672391DB6F00CF19C1 /* security.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = security.plist; sourceTree = ""; }; + CD8FD5D223BAE2D100EFE0FB /* PrefsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrefsWindowController.h; sourceTree = ""; }; + CD8FD5D323BAE2D100EFE0FB /* Preferences.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = ""; }; + CD8FD5D423BAE2D200EFE0FB /* PrefsWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrefsWindowController.m; sourceTree = ""; }; + CD8FD5ED23C05AD800EFE0FB /* RuleRowCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuleRowCell.h; sourceTree = ""; }; + CD8FD5EE23C05AD800EFE0FB /* RulesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RulesWindowController.h; sourceTree = ""; }; + CD8FD5F023C05AD800EFE0FB /* RuleRow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuleRow.m; sourceTree = ""; }; + CD8FD5F123C05AD800EFE0FB /* RuleRow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuleRow.h; sourceTree = ""; }; + CD8FD5F223C05AD800EFE0FB /* RuleRowCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuleRowCell.m; sourceTree = ""; }; + CD8FD5F323C05AD800EFE0FB /* RulesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RulesWindowController.m; sourceTree = ""; }; + CD8FD5F523C05AD900EFE0FB /* Rules.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Rules.xib; sourceTree = ""; }; + CDC6098C263CB7EB006D1332 /* LogMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LogMonitor.h; sourceTree = ""; }; + CDC6098D263CB7EB006D1332 /* LogMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LogMonitor.m; sourceTree = ""; }; + CDC6098F263CBD36006D1332 /* AVMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AVMonitor.h; sourceTree = ""; }; + CDC60990263CBD36006D1332 /* AVMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AVMonitor.m; sourceTree = ""; }; + CDC60992263CBEE7006D1332 /* Client.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Client.h; sourceTree = ""; }; + CDC60993263CBEE7006D1332 /* Client.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Client.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7D7755E01F02E05B00D0017D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 7D3B75001F13354900568828 /* Base.lproj */ = { + isa = PBXGroup; + children = ( + 7D3B75011F13354900568828 /* MainMenu.xib */, + ); + path = Base.lproj; + sourceTree = ""; + }; + 7D3B75201F13356900568828 /* Shared */ = { + isa = PBXGroup; + children = ( + CD2F801624468A8C009C3D77 /* patrons.txt */, + 7D3B75221F13357D00568828 /* consts.h */, + 7D5C5F501F200B9200C0E94A /* utilities.h */, + 7D5C5F511F200B9200C0E94A /* utilities.m */, + ); + name = Shared; + sourceTree = ""; + }; + 7D7755DA1F02E05B00D0017D = { + isa = PBXGroup; + children = ( + 7D3B75201F13356900568828 /* Shared */, + 7D7755E51F02E05B00D0017D /* Source */, + CDAFF434203E55F300F27635 /* Frameworks */, + 7D7755E41F02E05B00D0017D /* Products */, + ); + sourceTree = ""; + }; + 7D7755E41F02E05B00D0017D /* Products */ = { + isa = PBXGroup; + children = ( + 7D7755E31F02E05B00D0017D /* OverSight.app */, + ); + name = Products; + sourceTree = ""; + }; + 7D7755E51F02E05B00D0017D /* Source */ = { + isa = PBXGroup; + children = ( + CD2F800924455333009C3D77 /* AboutWindow.xib */, + CD2F800A24455333009C3D77 /* AboutWindowController.h */, + CD2F800B24455333009C3D77 /* AboutWindowController.m */, + 7D7755E61F02E05B00D0017D /* AppDelegate.h */, + 7D7755E71F02E05B00D0017D /* AppDelegate.m */, + 7DD25FEF1F23B73C00277EC4 /* Assets.xcassets */, + CDC6098F263CBD36006D1332 /* AVMonitor.h */, + CDC60990263CBD36006D1332 /* AVMonitor.m */, + 7D3B75001F13354900568828 /* Base.lproj */, + CDC60992263CBEE7006D1332 /* Client.h */, + CDC60993263CBEE7006D1332 /* Client.m */, + 7D7755F11F02E05B00D0017D /* Info.plist */, + CDC6098C263CB7EB006D1332 /* LogMonitor.h */, + CDC6098D263CB7EB006D1332 /* LogMonitor.m */, + 7D7755EE1F02E05B00D0017D /* MainMenu.xib */, + CD2F8006244551AB009C3D77 /* NSApplicationKeyEvents.h */, + CD2F8007244551AC009C3D77 /* NSApplicationKeyEvents.m */, + CD8FD5D323BAE2D100EFE0FB /* Preferences.xib */, + CD8FD5D223BAE2D100EFE0FB /* PrefsWindowController.h */, + CD8FD5D423BAE2D200EFE0FB /* PrefsWindowController.m */, + CD8FD5F123C05AD800EFE0FB /* RuleRow.h */, + CD8FD5F023C05AD800EFE0FB /* RuleRow.m */, + CD8FD5ED23C05AD800EFE0FB /* RuleRowCell.h */, + CD8FD5F223C05AD800EFE0FB /* RuleRowCell.m */, + CD8FD5F523C05AD900EFE0FB /* Rules.xib */, + CD8FD5EE23C05AD800EFE0FB /* RulesWindowController.h */, + CD8FD5F323C05AD800EFE0FB /* RulesWindowController.m */, + 7D3B74FF1F13354900568828 /* StatusBarItem.h */, + 7D3B74FD1F13354900568828 /* StatusBarItem.m */, + CD22785B20434CAB00C72C76 /* StatusBarPopover.xib */, + CD22785C20434CAB00C72C76 /* StatusBarPopoverController.h */, + CD22785D20434CAB00C72C76 /* StatusBarPopoverController.m */, + 7D7755E91F02E05B00D0017D /* Supporting Files */, + 7D16D68F1F64E43300DB3161 /* Update.h */, + 7D16D6901F64E43300DB3161 /* Update.m */, + 7D16D6911F64E43300DB3161 /* UpdateWindow.xib */, + 7D16D6921F64E43300DB3161 /* UpdateWindowController.h */, + 7D16D6931F64E43300DB3161 /* UpdateWindowController.m */, + ); + name = Source; + path = Application; + sourceTree = ""; + }; + 7D7755E91F02E05B00D0017D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + CD6836672391DB6F00CF19C1 /* security.plist */, + 7D7755EA1F02E05B00D0017D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + CDAFF434203E55F300F27635 /* Frameworks */ = { + isa = PBXGroup; + children = ( + CD21501B20AD2EE000CEF17B /* Sentry.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7D7755E21F02E05B00D0017D /* Application */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7D7755F41F02E05B00D0017D /* Build configuration list for PBXNativeTarget "Application" */; + buildPhases = ( + 7D7755DF1F02E05B00D0017D /* Sources */, + 7D7755E01F02E05B00D0017D /* Frameworks */, + 7D7755E11F02E05B00D0017D /* Resources */, + CDA88A782537CE1A00C469BF /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Application; + productName = loginItem; + productReference = 7D7755E31F02E05B00D0017D /* OverSight.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7D7755DB1F02E05B00D0017D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1000; + ORGANIZATIONNAME = "Objective-See"; + TargetAttributes = { + 7D7755E21F02E05B00D0017D = { + CreatedOnToolsVersion = 8.2.1; + DevelopmentTeam = VBG97UB4TA; + ProvisioningStyle = Manual; + SystemCapabilities = { + com.apple.ApplicationGroups.Mac = { + enabled = 0; + }; + com.apple.HardenedRuntime = { + enabled = 1; + }; + com.apple.Sandbox = { + enabled = 0; + }; + }; + }; + }; + }; + buildConfigurationList = 7D7755DE1F02E05B00D0017D /* Build configuration list for PBXProject "Application" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + Base, + ); + mainGroup = 7D7755DA1F02E05B00D0017D; + productRefGroup = 7D7755E41F02E05B00D0017D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7D7755E21F02E05B00D0017D /* Application */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7D7755E11F02E05B00D0017D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CD8FD5D523BAE2D200EFE0FB /* Preferences.xib in Resources */, + CD6836682391DB6F00CF19C1 /* security.plist in Resources */, + CD2F800C24455333009C3D77 /* AboutWindow.xib in Resources */, + 7D16D6951F64E43300DB3161 /* UpdateWindow.xib in Resources */, + CD22785E20434CAC00C72C76 /* StatusBarPopover.xib in Resources */, + CD2F801724468A8C009C3D77 /* patrons.txt in Resources */, + CD8FD5FA23C05AD900EFE0FB /* Rules.xib in Resources */, + 7D7755F01F02E05B00D0017D /* MainMenu.xib in Resources */, + 7DD25FF01F23B73C00277EC4 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7D7755DF1F02E05B00D0017D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7D16D6961F64E43300DB3161 /* UpdateWindowController.m in Sources */, + 7D3B75141F13354900568828 /* StatusBarItem.m in Sources */, + 7D7755EB1F02E05B00D0017D /* main.m in Sources */, + CD2F8008244551AC009C3D77 /* NSApplicationKeyEvents.m in Sources */, + 7D5C5F521F200B9200C0E94A /* utilities.m in Sources */, + CD8FD5D623BAE2D200EFE0FB /* PrefsWindowController.m in Sources */, + CDC6098E263CB7EB006D1332 /* LogMonitor.m in Sources */, + CD8FD5F823C05AD900EFE0FB /* RulesWindowController.m in Sources */, + 7D7755E81F02E05B00D0017D /* AppDelegate.m in Sources */, + CDC60994263CBEE7006D1332 /* Client.m in Sources */, + CD8FD5F723C05AD900EFE0FB /* RuleRowCell.m in Sources */, + CD22785F20434CAC00C72C76 /* StatusBarPopoverController.m in Sources */, + CD8FD5F623C05AD900EFE0FB /* RuleRow.m in Sources */, + CDC60991263CBD36006D1332 /* AVMonitor.m in Sources */, + CD2F800D24455333009C3D77 /* AboutWindowController.m in Sources */, + 7D16D6941F64E43300DB3161 /* Update.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 7D3B75011F13354900568828 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 7D3B75021F13354900568828 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; + 7D7755EE1F02E05B00D0017D /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 7D7755EF1F02E05B00D0017D /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 7D7755F21F02E05B00D0017D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = NO; + SDKROOT = macosx; + }; + name = Debug; + }; + 7D7755F31F02E05B00D0017D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 7D7755F51F02E05B00D0017D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Developer ID Application"; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 2.0.0; + DEVELOPMENT_TEAM = VBG97UB4TA; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; + INFOPLIST_FILE = Application/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST)) @executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 2.0.0; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight"; + PRODUCT_NAME = OverSight; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 7D7755F61F02E05B00D0017D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Developer ID Application"; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 2.0.0; + DEVELOPMENT_TEAM = VBG97UB4TA; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; + INFOPLIST_FILE = Application/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST)) @executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 2.0.0; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight"; + PRODUCT_NAME = OverSight; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7D7755DE1F02E05B00D0017D /* Build configuration list for PBXProject "Application" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7D7755F21F02E05B00D0017D /* Debug */, + 7D7755F31F02E05B00D0017D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7D7755F41F02E05B00D0017D /* Build configuration list for PBXNativeTarget "Application" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7D7755F51F02E05B00D0017D /* Debug */, + 7D7755F61F02E05B00D0017D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7D7755DB1F02E05B00D0017D /* Project object */; +} diff --git a/Application/Application.xcodeproj/xcshareddata/xcschemes/Application.xcscheme b/Application/Application.xcodeproj/xcshareddata/xcschemes/Application.xcscheme new file mode 100644 index 0000000..7e9f4d1 --- /dev/null +++ b/Application/Application.xcodeproj/xcshareddata/xcschemes/Application.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Application/Application/AVMonitor.h b/Application/Application/AVMonitor.h new file mode 100644 index 0000000..f1ddd70 --- /dev/null +++ b/Application/Application/AVMonitor.h @@ -0,0 +1,38 @@ +// +// AVMonitor.h +// Application +// +// Created by Patrick Wardle on 4/30/21. +// Copyright © 2021 Objective-See. All rights reserved. +// + +@import Cocoa; +@import Foundation; +@import UserNotifications; + +#import "LogMonitor.h" + +@interface AVMonitor : NSObject + +//log monitor +@property(nonatomic, retain)LogMonitor* logMonitor; + +//clients +@property(nonatomic, retain)NSMutableArray* clients; + +//camera state +@property NSControlStateValue cameraState; + +//microphone state +@property NSControlStateValue microphoneState; + +/* METHODS */ + +//start +-(void)start; + +//stop +-(void)stop; + +@end + diff --git a/Application/Application/AVMonitor.m b/Application/Application/AVMonitor.m new file mode 100644 index 0000000..8f2fee0 --- /dev/null +++ b/Application/Application/AVMonitor.m @@ -0,0 +1,887 @@ +// +// AVMonitor.m +// Application +// +// Created by Patrick Wardle on 4/30/21. +// Copyright © 2021 Objective-See. All rights reserved. +// + +@import OSLog; +@import AVFoundation; + +#import "consts.h" +#import "Client.h" +#import "AVMonitor.h" +#import "utilities.h" + +#import +#import +#import +#import + + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +@implementation AVMonitor + +@synthesize clients; + +//init +// create XPC connection & set remote obj interface +-(id)init +{ + //action: allow + UNNotificationAction *allow = nil; + + //action: allow + UNNotificationAction *allowAlways = nil; + + //action: block + UNNotificationAction *block = nil; + + //category + UNNotificationCategory* category = nil; + + //super + self = [super init]; + if(nil != self) + { + //init log monitor + self.logMonitor = [[LogMonitor alloc] init]; + + //init clients + self.clients = [NSMutableArray array]; + + //set up delegate + UNUserNotificationCenter.currentNotificationCenter.delegate = self; + + //ask for notificaitons + [UNUserNotificationCenter.currentNotificationCenter requestAuthorizationWithOptions:(UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) + { + //dbg msg + os_log_debug(logHandle, "permission to display notifications granted? %d (error: %@)", granted, error); + + //not granted/error + if( (nil != error) && + (YES != granted) ) + { + //main thread? + if(YES == NSThread.isMainThread) + { + //show an error alert + showAlert(@"ERROR: permisssion not granted to display notifications", [NSString stringWithFormat:@"system error information: %@", error]); + } + //bg thread + // show alert on main thread + else + { + dispatch_async(dispatch_get_main_queue(), + ^{ + //show an error alert + showAlert(@"ERROR: permisssion not granted to display notifications", [NSString stringWithFormat:@"system error information: %@", error]); + }); + } + } + }]; + + //init allow action + allow = [UNNotificationAction actionWithIdentifier:@"Allow" title:@"Allow (Once)" options:UNNotificationActionOptionNone]; + + //init allow action + allowAlways = [UNNotificationAction actionWithIdentifier:@"AllowAlways" title:@"Allow (Always)" options:UNNotificationActionOptionNone]; + + //init block action + block = [UNNotificationAction actionWithIdentifier:@"Block" title:@"Block" options:UNNotificationActionOptionNone]; + + //init category + category = [UNNotificationCategory categoryWithIdentifier:@BUNDLE_ID actions:@[allow, allowAlways, block] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction]; + + //set categories + [UNUserNotificationCenter.currentNotificationCenter setNotificationCategories:[NSSet setWithObject:category]]; + + //any active cameras + // only call on intel, since broken on M1 + if(YES != AppleSilicon()) + { + self.cameraState = [self isACameraOn]; + } + } + + return self; +} + +//monitor AV +// also generate alerts as needed +-(void)start +{ + //dbg msg + os_log_debug(logHandle, "starting AV monitoring"); + + //invoke appropriate architecute monitoring logic + (YES == AppleSilicon()) ? [self monitorM1] : [self monitorIntel]; + + return; +} + +//on M1 systems +// monitor for video events via 'appleh13camerad' +-(void)monitorM1 +{ + //dbg msg + os_log_debug(logHandle, "CPU architecuture: M1, will leverage 'appleh13camerad'"); + + //start logging + [self.logMonitor start:[NSPredicate predicateWithFormat:@"process == 'appleh13camerad'"] callback:^(OSLogEvent* event) { + + //new client + // add to list + if( (YES == [event.composedMessage hasPrefix:@"TCC access already allowed for pid"]) || + (YES == [event.composedMessage hasPrefix:@"TCC preflight access returned allowed for pid"]) ) + { + //client + Client* client = nil; + + //pid + NSNumber* pid = nil; + + //dbg msg + os_log_debug(logHandle, "new client msg: %{public}@", event.composedMessage); + + //extract pid + pid = @([event.composedMessage componentsSeparatedByString:@" "].lastObject.intValue); + if(nil != pid) + { + //init client + client = [[Client alloc] init]; + client.pid = pid; + client.path = getProcessPath(pid.intValue); + client.name = getProcessName(client.path); + + //dbg msg + os_log_debug(logHandle, "new client: %{public}@", client); + + //camera already on? + // show notifcation for new client + if(NSControlStateValueOn == self.cameraState) + { + //show notification + [self generateNotification:Device_Camera state:NSControlStateValueOn client:client]; + + //execute user-specified action? + if(YES == [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION]) + { + //execute action + [self executeUserAction:Device_Camera state:NSControlStateValueOff client:nil]; + } + } + + //will handle when "on" camera msg is delivered + else + { + //add client + [self.clients addObject:client]; + } + } + } + + //camera on + // show alert! + else if(YES == [event.composedMessage isEqualToString:@"StartStream : StartStream: Powering ON camera"]) + { + //client + Client* client = nil; + + //dbg msg + os_log_debug(logHandle, "camera on msg: %{public}@", event.composedMessage); + + //set state + self.cameraState = NSControlStateValueOn; + + //last client should be responsible one + client = self.clients.lastObject; + + //show notification + [self generateNotification:Device_Camera state:NSControlStateValueOn client:client]; + + //remove + [self.clients removeLastObject]; + + //execute user-specified action? + if(YES == [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION]) + { + //execute action + [self executeUserAction:Device_Camera state:NSControlStateValueOn client:client]; + } + } + + //dead client + // remove from list + else if(YES == [event.composedMessage hasPrefix:@"Removing client: pid"]) + { + //pid + NSNumber* pid = 0; + + //dbg msg + os_log_debug(logHandle, "removed client msg: %{public}@", event.composedMessage); + + //extract pid + pid = @([event.composedMessage componentsSeparatedByString:@" "].lastObject.intValue); + if(nil != pid) + { + //sync + @synchronized (self) { + + //find and remove client + for (NSInteger i = self.clients.count - 1; i >= 0; i--) + { + if(pid != ((Client*)self.clients[i]).pid) continue; + + //remove + [self.clients removeObjectAtIndex:i]; + + //dbg msg + os_log_debug(logHandle, "removed client at index %ld", (long)i); + } + + }//sync + } + } + + //camera off + // show inactive notification + else if(YES == [event.composedMessage hasPrefix:@"StopStream : Powering OFF camera"]) + { + //dbg msg + os_log_debug(logHandle, "camera off msg: %{public}@", event.composedMessage); + + //set state + self.cameraState = NSControlStateValueOff; + + //show inactive notifcations? + if(YES != [NSUserDefaults.standardUserDefaults boolForKey:PREF_DISABLE_INACTIVE]) + { + //show notification + [self generateNotification:Device_Camera state:NSControlStateValueOff client:nil]; + + //execute user-specified action? + if(YES == [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION]) + { + //execute action + [self executeUserAction:Device_Camera state:NSControlStateValueOff client:nil]; + } + } + else + { + //dbg msg + os_log_debug(logHandle, "user has set preference to ingore 'inactive' notifications"); + } + } + + }]; + + return; +} + + +//on Intel systems +// monitor for video events via 'VDCAssistant' +-(void)monitorIntel +{ + //dbg msg + os_log_debug(logHandle, "CPU architecuture: Intel ...will leverage 'VDCAssistant'"); + + //msg count + // used to validate client pid to client id + __block unsigned long long msgCount = 0; + + //start logging + [self.logMonitor start:[NSPredicate predicateWithFormat:@"process == 'VDCAssistant'"] callback:^(OSLogEvent* event) { + + //inc + msgCount++; + + //new client + // add to list + if(YES == [event.composedMessage hasPrefix:@"Client Connect for PID"]) + { + //client + Client* client = nil; + + //pid + NSNumber* pid = nil; + + //dbg msg + os_log_debug(logHandle, "new client msg: %{public}@", event.composedMessage); + + //extract pid + pid = @([event.composedMessage componentsSeparatedByString:@" "].lastObject.intValue); + if(nil != pid) + { + //init client + client = [[Client alloc] init]; + client.msgCount = msgCount; + client.pid = pid; + client.path = getProcessPath(pid.intValue); + client.name = getProcessName(client.path); + + //dbg msg + os_log_debug(logHandle, "new client: %{public}@", client); + + //add client + [self.clients addObject:client]; + } + } + //client w/ id msg + // update (last) client, with client id + else if(YES == [event.composedMessage containsString:@"GetDevicesState for client"]) + { + //client + Client* client = nil; + + //client id + NSNumber* clientID = nil; + + //dbg msg + os_log_debug(logHandle, "new client id msg : %{public}@", event.composedMessage); + + //extract client id + clientID = @([event.composedMessage componentsSeparatedByString:@" "].lastObject.intValue); + if(0 != clientID) + { + //get last client + // check that it the one in the *last* msg + client = self.clients.lastObject; + if(client.msgCount == msgCount-1) + { + //add id + client.clientID = clientID; + } + } + } + + //camera on (for client) + // show notification + else if(YES == [event.composedMessage containsString:@"StartStream for client"]) + { + //client + Client* client = nil; + + //client id + NSNumber* clientID = nil; + + //dbg msg + os_log_debug(logHandle, "camera on msg: %{public}@", event.composedMessage); + + //set state + self.cameraState = NSControlStateValueOn; + + //extract client id + clientID = @([event.composedMessage componentsSeparatedByString:@" "].lastObject.intValue); + if(0 != clientID) + { + //find client w/ matching id + for(Client* candidateClient in self.clients) + { + //match? + if(candidateClient.clientID == clientID) + { + //save + client = candidateClient; + + //done + break; + } + } + + //nil, but last client is FaceTime? + // use that, as FaceTime is "special" + if(nil == client) + { + //facetime check? + if( (YES == [((Client*)self.clients.lastObject).path isEqualToString:FACE_TIME]) && + (YES == [NSWorkspace.sharedWorkspace.frontmostApplication.executableURL.path isEqualToString:FACE_TIME]) ) + { + //set + client = self.clients.lastObject; + } + } + } + + //show notification + // ok if client is (still) nil... + [self generateNotification:Device_Camera state:NSControlStateValueOn client:client]; + + //execute user-specified action? + if(YES == [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION]) + { + //execute action + [self executeUserAction:Device_Camera state:NSControlStateValueOn client:client]; + } + } + + //dead client + // remove from list + // e.x. "ClientDied 1111 [PID: 2222]" + else if( (YES == [event.composedMessage hasPrefix:@"ClientDied "]) && + (YES == [event.composedMessage hasSuffix:@"]"]) ) + { + //message (trimmed) + NSString* message = nil; + + //pid + NSNumber* pid = 0; + + //dbg msg + os_log_debug(logHandle, "dead client msg: %{public}@", event.composedMessage); + + //init message + // trim off last ']' + message = [event.composedMessage substringToIndex:event.composedMessage.length - 1]; + + //extract pid + pid = @([message componentsSeparatedByString:@" "].lastObject.intValue); + if(nil != pid) + { + //sync + @synchronized (self) { + + for(NSInteger i = self.clients.count - 1; i >= 0; i--) + { + //no match? + if(pid != ((Client*)self.clients[i]).pid) + { + //skip + continue; + } + + //remove + [self.clients removeObjectAtIndex:i]; + + //dbg msg + os_log_debug(logHandle, "removed client at index %ld", (long)i); + } + + } //sync + } + } + + //camera off + else if(YES == [event.composedMessage containsString:@"Post event kCameraStreamStop"]) + { + //dbg msg + os_log_debug(logHandle, "camera off msg: %{public}@", event.composedMessage); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + + //all camera's off? + if(YES != [self isACameraOn]) + { + //set state + self.cameraState = NSControlStateValueOff; + + //show inactive notifcations? + if(YES != [NSUserDefaults.standardUserDefaults boolForKey:PREF_DISABLE_INACTIVE]) + { + //show notification + [self generateNotification:Device_Camera state:NSControlStateValueOff client:nil]; + + //execute user-specified action? + if(YES == [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION]) + { + //execute action + [self executeUserAction:Device_Camera state:NSControlStateValueOff client:nil]; + } + } + else + { + //dbg msg + os_log_debug(logHandle, "user has set preference to ingore 'inactive' notifications"); + } + } + + + }); + + + + } + + }]; + + return; + +} + +//is (any) camera on? +-(BOOL)isACameraOn +{ + //flag + BOOL cameraOn = NO; + + //selector for getting device id + SEL methodSelector = nil; + + //device's connection id + unsigned int connectionID = 0; + + //dbg msg + os_log_debug(logHandle, "checking if any camera is active"); + + //init selector + methodSelector = NSSelectorFromString(@"connectionID"); + + //are any cameras currently on? + for(AVCaptureDevice* currentCamera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) + { + //dbg msg + os_log_debug(logHandle, "device: %@/%@", currentCamera.manufacturer, currentCamera.localizedName); + + //sanity check + // make sure is has private 'connectionID' iVar + if(YES != [currentCamera respondsToSelector:methodSelector]) + { + //skip + continue; + } + + //ignore leak warning + // we know what we're doing via this 'performSelector' + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Warc-performSelector-leaks" + + //grab connection ID + connectionID = (unsigned int)[currentCamera performSelector:methodSelector withObject:nil]; + + //restore + #pragma clang diagnostic pop + + //get state + // is (any) camera on? + if(NSControlStateValueOn == [self getCameraStatus:connectionID]) + { + //dbg msg + os_log_debug(logHandle, "device: %@/%@, is on!", currentCamera.manufacturer, currentCamera.localizedName); + + //set + cameraOn = YES; + + //done + break; + } + } + +bail: + + return cameraOn; +} + + +//check if a specified video is active +// note: on M1 this always says 'on' (smh apple) +-(UInt32)getCameraStatus:(CMIODeviceID)deviceID +{ + //status var + OSStatus status = -1; + + //running flag + UInt32 isRunning = -1; + + //size of query flag + UInt32 propertySize = 0; + + //property address struct + CMIOObjectPropertyAddress propertyStruct = {0}; + + //init size + propertySize = sizeof(isRunning); + + //init property struct's selector + propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere; + + //init property struct's scope + propertyStruct.mScope = kCMIOObjectPropertyScopeGlobal; + + //init property struct's element + propertyStruct.mElement = 0; + + //query to get 'kAudioDevicePropertyDeviceIsRunningSomewhere' status + status = CMIOObjectGetPropertyData(deviceID, &propertyStruct, 0, NULL, sizeof(kAudioDevicePropertyDeviceIsRunningSomewhere), &propertySize, &isRunning); + if(noErr != status) + { + //err msg + os_log_error(logHandle, "ERROR: failed to get camera status (error: %#x)", status); + + //set error + isRunning = -1; + + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "isRunning: %d", isRunning); + +bail: + + return isRunning; +} + +//build and display notification +-(void)generateNotification:(AVDevice)device state:(NSControlStateValue)state client:(Client*)client +{ + //notification content + UNMutableNotificationContent* content = nil; + + //notificaito0n request + UNNotificationRequest* request = nil; + + //alloc content + content = [[UNMutableNotificationContent alloc] init]; + + //title + NSMutableString* title = nil; + + //client? + // check if allowed + if(nil != client) + { + //match is simply: device and path + for(NSDictionary* allowedItem in [NSUserDefaults.standardUserDefaults objectForKey:PREFS_ALLOWED_ITEMS]) + { + //match? + if( (allowedItem[EVENT_DEVICE] == (NSNumber*)@(device)) && + (YES == [allowedItem[EVENT_PROCESS_PATH] isEqualToString:client.path]) ) + { + //dbg msg + os_log_debug(logHandle, "%{public}@ is allowed to access %d, so no notification will be shown", client.path, device); + + //done + goto bail; + } + } + } + + //alloc title + title = [NSMutableString string]; + + //set device + (Device_Camera == device) ? [title appendString:@"Video Device"] : [title appendString:@"Audio Device"]; + + //set status + (NSControlStateValueOn == state) ? [title appendString:@" became active!"] : [title appendString:@" became inactive."]; + + //set title + content.title = title; + + //have client? + // use as body + if(nil != client) + { + //set body + content.body = [NSString stringWithFormat:@"Process: %@ (%@)", getProcessName(client.path), client.pid]; + + //set category + content.categoryIdentifier = @BUNDLE_ID; + + //set user info + content.userInfo = @{EVENT_DEVICE:@(Device_Camera), EVENT_PROCESS_ID:client.pid, EVENT_PROCESS_PATH:client.path}; + } + + //init request + request = [UNNotificationRequest requestWithIdentifier:NSUUID.UUID.UUIDString content:content trigger:NULL]; + + //send notification + [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError *_Nullable error) + { + //error? + if(nil != error) + { + //err msg + os_log_error(logHandle, "ERROR failed to deliver notification (error: %@)", error); + } + + }]; + +bail: + + return; +} + +//execute user action +-(BOOL)executeUserAction:(AVDevice)device state:(NSControlStateValue)state client:(Client*)client +{ + //flag + BOOL wasExecuted = NO; + + //path to action + NSString* action = nil; + + //args + NSMutableArray* args = nil; + + //dbg msg + os_log_debug(logHandle, "executing user action"); + + //grab action + action = [NSUserDefaults.standardUserDefaults objectForKey:PREF_EXECUTE_PATH]; + if(YES != [NSFileManager.defaultManager fileExistsAtPath:action]) + { + //err msg + os_log_error(logHandle, "ERROR: %{public}@ is not a valid action", action); + + //bail + goto bail; + } + + //pass args? + if(YES == [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION_ARGS]) + { + //alloc + args = [NSMutableArray array]; + + //add device + [args addObject:@"-device"]; + (Device_Camera == device) ? [args addObject:@"camera"] : [args addObject:@"microphone"]; + + //add event + [args addObject:@"-event"]; + (NSControlStateValueOn == state) ? [args addObject:@"on"] : [args addObject:@"off"]; + + //add process + if(nil != client) + { + //add + [args addObject:@"-process"]; + [args addObject:client.pid.stringValue]; + } + } + + //exec user specified action + execTask(action, args, NO, NO); + +bail: + + return wasExecuted; +} + +//stop monitor +-(void)stop +{ + //stop logging + [self.logMonitor stop]; + + return; +} + +# pragma mark UNNotificationCenter Delegate Methods + +//handle user response to notification +- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { + + //allowed items + NSMutableArray* allowedItems = nil; + + //device + NSNumber* device = nil; + + //process path + NSString* processPath = nil; + + //process name + NSString* processName = nil; + + //process id + NSNumber* processID = nil; + + //error + int error = 0; + + //dbg msg + os_log_debug(logHandle, "user response to notification: %{public}@", response); + + //extract device + device = response.notification.request.content.userInfo[EVENT_DEVICE]; + + //extact process path + processPath = response.notification.request.content.userInfo[EVENT_PROCESS_PATH]; + + //extract process id + processID = response.notification.request.content.userInfo[EVENT_PROCESS_ID]; + + //get process name + processName = getProcessName(processPath); + + //allow? + // really nothing to do + if(YES == [response.actionIdentifier isEqualToString:@"Allow"]) + { + //dbg msg + os_log_debug(logHandle, "user clicked 'Allow'"); + } + + //always allow? + // added to 'allowed' items + else if(YES == [response.actionIdentifier isEqualToString:@"AllowAlways"]) + { + //dbg msg + os_log_debug(logHandle, "user clicked 'Allow Always'"); + + //load allowed items + allowedItems = [[NSUserDefaults.standardUserDefaults objectForKey:PREFS_ALLOWED_ITEMS] mutableCopy]; + if(nil == allowedItems) + { + //alloc + allowedItems = [NSMutableArray array]; + } + + //add item + [allowedItems addObject:@{EVENT_PROCESS_PATH:processPath, EVENT_DEVICE:device}]; + + //save & sync + [NSUserDefaults.standardUserDefaults setObject:allowedItems forKey:PREFS_ALLOWED_ITEMS]; + [NSUserDefaults.standardUserDefaults synchronize]; + + //dbg msg + os_log_debug(logHandle, "added %{public}@ to list of allowed items", processPath); + + //broadcast + [[NSNotificationCenter defaultCenter] postNotificationName:RULES_CHANGED object:nil userInfo:nil]; + } + + //block? + // kill process + else if(YES == [response.actionIdentifier isEqualToString:@"Block"]) + { + //dbg msg + os_log_debug(logHandle, "user clicked 'Block'"); + + //kill + error = kill(processID.intValue, SIGKILL); + if(0 != error) + { + //err msg + os_log_error(logHandle, "ERROR: failed to kill %@ (%@)", processName, processID); + + //show an alert + showAlert([NSString stringWithFormat:@"ERROR: failed to block %@ (%@)", processName, processID], [NSString stringWithFormat:@"system error code: %d", error]); + + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "killed %@ (%@)", processName, processID); + } + +bail: + + //gotta call + completionHandler(); + + return; +} + +@end diff --git a/Shared/AboutWindow.xib b/Application/Application/AboutWindow.xib similarity index 81% rename from Shared/AboutWindow.xib rename to Application/Application/AboutWindow.xib index 110bc05..54f3aea 100644 --- a/Shared/AboutWindow.xib +++ b/Application/Application/AboutWindow.xib @@ -1,7 +1,8 @@ - + - + + @@ -14,23 +15,18 @@ - - + + - + - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OverSight will pass in the following arguments:
-d <camera|microphone> -event <on|off> -process <pid>

Note: -process is only specified on activation events. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Application/Application/PrefsWindowController.h b/Application/Application/PrefsWindowController.h new file mode 100644 index 0000000..0b2492d --- /dev/null +++ b/Application/Application/PrefsWindowController.h @@ -0,0 +1,76 @@ +// +// file: PrefsWindowController.h +// project: OverSight (main app) +// description: preferences window controller (header) +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +@import Cocoa; + +#import "UpdateWindowController.h" + +/* CONSTS */ + +//modes view +#define TOOLBAR_MODES 0 + +//action view +#define TOOLBAR_ACTION 1 + +//update view +#define TOOLBAR_UPDATE 2 + +//to select, need string ID +#define TOOLBAR_MODES_ID @"mode" + +@interface PrefsWindowController : NSWindowController + +/* PROPERTIES */ + +//preferences +@property(nonatomic, retain)NSDictionary* preferences; + +//toolbar +@property (weak) IBOutlet NSToolbar *toolbar; + +//rules prefs view +@property (weak) IBOutlet NSView *rulesView; + +//modes view +@property (strong) IBOutlet NSView *modesView; + +//action view +@property (strong) IBOutlet NSView *actionView; + +//path to action +@property (weak) IBOutlet NSTextField *executePath; + +//execute args button +@property (weak) IBOutlet NSButton *executeArgsButton; + +//update view +@property (weak) IBOutlet NSView *updateView; + +//update button +@property (weak) IBOutlet NSButton *updateButton; + +//update indicator (spinner) +@property (weak) IBOutlet NSProgressIndicator *updateIndicator; + +//update label +@property (weak) IBOutlet NSTextField *updateLabel; + +//update window controller +@property(nonatomic, retain)UpdateWindowController* updateWindowController; + +/* METHODS */ + +//toolbar button handler +-(IBAction)toolbarButtonHandler:(id)sender; + +//button handler for all preference buttons +-(IBAction)togglePreference:(id)sender; + +@end diff --git a/Application/Application/PrefsWindowController.m b/Application/Application/PrefsWindowController.m new file mode 100644 index 0000000..c40418d --- /dev/null +++ b/Application/Application/PrefsWindowController.m @@ -0,0 +1,394 @@ +// +// file: PrefsWindowController.h +// project: OverSight (main app) +// description: preferences window controller (header) +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +#import "consts.h" +#import "Update.h" +#import "utilities.h" +#import "AppDelegate.h" +#import "PrefsWindowController.h" +#import "UpdateWindowController.h" + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +@implementation PrefsWindowController + +@synthesize toolbar; +@synthesize modesView; +@synthesize rulesView; +@synthesize actionView; +@synthesize updateView; +@synthesize updateWindowController; + +//start at login button +#define BUTTON_AUTOSTART_MODE 1 + +//'no-icon mode' button +#define BUTTON_NO_ICON_MODE 2 + +//'disable inactive' button +#define BUTTON_DISABLE_INACTIVE_MODE 3 + +//action +#define BUTTON_EXECUTE_ACTION 4 + +//args for action +#define BUTTON_EXECUTE_ACTION_ARGS 5 + +//'update mode' button +#define BUTTON_NO_UPDATE_MODE 6 + +//init 'general' view +// add it, and make it selected +-(void)awakeFromNib +{ + //set title + self.window.title = [NSString stringWithFormat:@"%@ v%@", PRODUCT_NAME, getAppVersion()]; + + //set rules prefs as default + [self toolbarButtonHandler:nil]; + + //set rules prefs as default + [self.toolbar setSelectedItemIdentifier:TOOLBAR_MODES_ID]; + + return; +} + +//toolbar view handler +// toggle view based on user selection +-(IBAction)toolbarButtonHandler:(id)sender +{ + //view + NSView* view = nil; + + //when we've prev added a view + // remove the prev view cuz adding a new one + if(nil != sender) + { + //remove + [[[self.window.contentView subviews] lastObject] removeFromSuperview]; + } + + //assign view + switch(((NSToolbarItem*)sender).tag) + { + //modes + case TOOLBAR_MODES: + + //set view + view = self.modesView; + + //start at login + ((NSButton*)[view viewWithTag:BUTTON_AUTOSTART_MODE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_AUTOSTART_MODE]; + + //no icon + ((NSButton*)[view viewWithTag:BUTTON_NO_ICON_MODE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_NO_ICON_MODE]; + + //disable inactive alerts + ((NSButton*)[view viewWithTag:BUTTON_DISABLE_INACTIVE_MODE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_DISABLE_INACTIVE]; + + break; + + //modes + case TOOLBAR_ACTION: + + //set view + view = self.actionView; + + //action + ((NSButton*)[view viewWithTag:BUTTON_EXECUTE_ACTION]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION]; + + //set 'execute action' path + if(0 != [NSUserDefaults.standardUserDefaults objectForKey:PREF_EXECUTE_PATH]) + { + //set + self.executePath.stringValue = [NSUserDefaults.standardUserDefaults objectForKey:PREF_EXECUTE_PATH]; + } + + //set state of 'execute action' to match + self.executePath.enabled = [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION]; + + //set action + args + ((NSButton*)[view viewWithTag:BUTTON_EXECUTE_ACTION_ARGS]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION_ARGS]; + + //set state of 'execute action' to match + self.executeArgsButton.enabled = [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION]; + + break; + + + //update + case TOOLBAR_UPDATE: + + //set view + view = self.updateView; + + //set 'update' button state + ((NSButton*)[view viewWithTag:BUTTON_NO_UPDATE_MODE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_NO_UPDATE_MODE]; + + break; + + default: + + //bail + goto bail; + } + + //set frame rect + view.frame = CGRectMake(0, 75, self.window.contentView.frame.size.width, self.window.contentView.frame.size.height-75); + + //add to window + [self.window.contentView addSubview:view]; + +bail: + + return; +} + +//automatically called when 'enter' is hit +// save values that were entered in text field +-(void)controlTextDidEndEditing:(NSNotification *)notification +{ + //execute path? + if([notification object] != self.executePath) + { + //bail + goto bail; + } + + //save & sync + [NSUserDefaults.standardUserDefaults setObject:self.executePath.stringValue forKey:PREF_EXECUTE_PATH]; + [NSUserDefaults.standardUserDefaults synchronize]; + +bail: + + return; +} + +//invoked when user toggles button +// update preferences for that button +-(IBAction)togglePreference:(id)sender +{ + //preferences + NSMutableDictionary* updatedPreferences = nil; + + //button state + BOOL state = NO; + + //init + updatedPreferences = [NSMutableDictionary dictionary]; + + //get button state + state = ((NSButton*)sender).state; + + //set appropriate preference + switch(((NSButton*)sender).tag) + { + //autostart + case BUTTON_AUTOSTART_MODE: + { + //toggle login item + toggleLoginItem([NSURL fileURLWithPath:NSBundle.mainBundle.bundlePath], state); + + //set + [NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_PASSIVE_MODE]; + + break; + } + + //no icon mode + case BUTTON_NO_ICON_MODE: + { + //toggle + [((AppDelegate*)[[NSApplication sharedApplication] delegate]) toggleIcon:!state]; + + //set + [NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_NO_ICON_MODE]; + + break; + } + + //disable inactive mode + case BUTTON_DISABLE_INACTIVE_MODE: + { + //set + [NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_DISABLE_INACTIVE]; + break; + } + + //execute action + // also toggle state of path + case BUTTON_EXECUTE_ACTION: + { + //set + [NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_EXECUTE_ACTION]; + + //set path field state to match + self.executePath.enabled = state; + + //set path field state to match + self.executeArgsButton.enabled = state; + + break; + } + + //execute action + // also toggle state of path + case BUTTON_EXECUTE_ACTION_ARGS: + { + //set + [NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_EXECUTE_ACTION_ARGS]; + break; + } + + //no update mode + case BUTTON_NO_UPDATE_MODE: + { + //set + [NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_NO_UPDATE_MODE]; + break; + } + + default: + break; + } + + //sync + [NSUserDefaults.standardUserDefaults synchronize]; + + return; +} + +//'view rules' button handler +// call helper method to show rule's window +-(IBAction)viewRules:(id)sender +{ + //call into app delegate to show app rules + [((AppDelegate*)[[NSApplication sharedApplication] delegate]) showRules:nil]; + + return; +} + +//'check for update' button handler +-(IBAction)check4Update:(id)sender +{ + //update obj + Update* update = nil; + + //disable button + self.updateButton.enabled = NO; + + //reset + self.updateLabel.stringValue = @""; + + //show/start spinner + [self.updateIndicator startAnimation:self]; + + //init update obj + update = [[Update alloc] init]; + + //check for update + // 'updateResponse newVersion:' method will be called when check is done + [update checkForUpdate:^(NSUInteger result, NSString* newVersion) { + + //process response + [self updateResponse:result newVersion:newVersion]; + + }]; + + return; +} + +//process update response +// error, no update, update/new version +-(void)updateResponse:(NSInteger)result newVersion:(NSString*)newVersion +{ + //re-enable button + self.updateButton.enabled = YES; + + //stop/hide spinner + [self.updateIndicator stopAnimation:self]; + + switch(result) + { + //error + case -1: + + //set label + self.updateLabel.stringValue = @"error: update check failed"; + + break; + + //no updates + case 0: + + //dbg msg + os_log_debug(logHandle, "no updates available"); + + //set label + self.updateLabel.stringValue = [NSString stringWithFormat:@"Installed version (%@),\r\nis the latest.", getAppVersion()]; + + break; + + + //new version + case 1: + + //dbg msg + os_log_debug(logHandle, "a new version (%@) is available", newVersion); + + //alloc update window + updateWindowController = [[UpdateWindowController alloc] initWithWindowNibName:@"UpdateWindow"]; + + //configure + [self.updateWindowController configure:[NSString stringWithFormat:@"a new version (%@) is available!", newVersion] buttonTitle:@"Update"]; + + //center window + [[self.updateWindowController window] center]; + + //show it + [self.updateWindowController showWindow:self]; + + //invoke function in background that will make window modal + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + //make modal + makeModal(self.updateWindowController); + + }); + + break; + } + + + return; +} + +//on window close +// set activation policy +-(void)windowWillClose:(NSNotification *)notification +{ + //wait a bit, then set activation policy + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + //on main thread + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + + //set activation policy + [((AppDelegate*)[[NSApplication sharedApplication] delegate]) setActivationPolicy]; + + }); + }); + + return; +} + +@end diff --git a/Application/Application/RuleRow.h b/Application/Application/RuleRow.h new file mode 100755 index 0000000..2911f3d --- /dev/null +++ b/Application/Application/RuleRow.h @@ -0,0 +1,14 @@ +// +// file: RuleRow.h +// project: OverSight (main app) +// description: row for 'rules' table (header) +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +@import Cocoa; + +@interface RuleRow : NSTableRowView + +@end diff --git a/MainApp/RuleRow.m b/Application/Application/RuleRow.m similarity index 51% rename from MainApp/RuleRow.m rename to Application/Application/RuleRow.m index 54eae05..46b334c 100755 --- a/MainApp/RuleRow.m +++ b/Application/Application/RuleRow.m @@ -1,9 +1,10 @@ // -// RuleRow.m -// OverSight +// file: RuleRow.m +// project: OverSight (main app) +// description: row for 'rules' table // -// Created by Patrick Wardle on 4/4/15. -// Copyright (c) 2017 Objective-See. All rights reserved. +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. // #import "RuleRow.h" @@ -17,7 +18,10 @@ NSRect selectionRect = {0}; //selection path - NSBezierPath *selectionPath = nil; + NSBezierPath* selectionPath = nil; + + //selection color + NSColor* selectionColor = nil; //highlight selected rows if(self.selectionHighlightStyle != NSTableViewSelectionHighlightStyleNone) @@ -25,14 +29,25 @@ //make selection rect selectionRect = NSInsetRect(self.bounds, 2.5, 2.5); + //init color + selectionColor = [NSColor systemGrayColor]; + + //on 10.14+ + // set to transparent + if (@available(macOS 10.14, *)) { + + //set color + selectionColor = [NSColor unemphasizedSelectedContentBackgroundColor]; + } + //set stroke - [[NSColor colorWithCalibratedWhite:.65 alpha:1.0] setStroke]; + [selectionColor setStroke]; //set fill - [[NSColor colorWithCalibratedWhite:.82 alpha:1.0] setFill]; + [selectionColor setFill]; //create selection path - // ->with rounded corners + // with rounded corners (5x5) selectionPath = [NSBezierPath bezierPathWithRoundedRect:selectionRect xRadius:5 yRadius:5]; //fill diff --git a/Application/Application/RuleRowCell.h b/Application/Application/RuleRowCell.h new file mode 100755 index 0000000..2c874ef --- /dev/null +++ b/Application/Application/RuleRowCell.h @@ -0,0 +1,14 @@ +// +// file: RuleRowCell.h +// project: OverSight (main app) +// description: cell for 'rules' table (header) +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +@import Cocoa; + +@interface RuleRowCell : NSTableCellView + +@end diff --git a/Application/Application/RuleRowCell.m b/Application/Application/RuleRowCell.m new file mode 100755 index 0000000..e28a63c --- /dev/null +++ b/Application/Application/RuleRowCell.m @@ -0,0 +1,23 @@ +// +// file: RuleRowCell.m +// project: OverSight (main app) +// description: cell for 'rules' table +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +#import "RuleRowCell.h" + +@implementation RuleRowCell + +//set background style +-(void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle +{ + //light + [super setBackgroundStyle: NSBackgroundStyleRaised]; + + return; +} + +@end diff --git a/Application/Application/Rules.xib b/Application/Application/Rules.xib new file mode 100644 index 0000000..61e6f22 --- /dev/null +++ b/Application/Application/Rules.xib @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Application/Application/RulesWindowController.h b/Application/Application/RulesWindowController.h new file mode 100644 index 0000000..7066621 --- /dev/null +++ b/Application/Application/RulesWindowController.h @@ -0,0 +1,76 @@ +// +// file: RulesWindowController.h +// project: OverSight (main app) +// description: window controller for 'rules' table (header) +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +@import Cocoa; + +/* CONSTS */ + +//id (tag) for detailed text in category table +#define TABLE_ROW_NAME_TAG 100 + +//id (tag) for detailed text (file) +#define TABLE_ROW_SUB_TEXT_FILE 101 + +//id (tag) for detailed text (item) +#define TABLE_ROW_SUB_TEXT_ITEM 102 + +//id (tag) for delete button +#define TABLE_ROW_DELETE_TAG 110 + +//menu item for block +#define MENU_ITEM_BLOCK 0 + +//menu item for allow +#define MENU_ITEM_ALLOW 1 + +//menu item for delete +#define MENU_ITEM_DELETE 2 + +/* INTERFACE */ + +@interface RulesWindowController : NSWindowController +{ + +} + +/* PROPERTIES */ + +//overlay +@property (weak) IBOutlet NSView *overlay; + +//observer for rules changed +@property(nonatomic, retain)id rulesObserver; + +//table items +// all of the rules +@property(nonatomic, retain)NSMutableArray* rules; + + +//top level view +@property (weak) IBOutlet NSView *view; + +//table view +@property (weak) IBOutlet NSTableView *tableView; + +//panel for 'add rule' +@property (weak) IBOutlet NSView *addRulePanel; + +/* METHODS */ + +//configure (UI) +-(void)configure; + +//get rules from daemon +// then, re-load rules table +-(void)loadRules; + +//delete a rule +-(IBAction)deleteRule:(id)sender; + +@end diff --git a/Application/Application/RulesWindowController.m b/Application/Application/RulesWindowController.m new file mode 100644 index 0000000..3db0942 --- /dev/null +++ b/Application/Application/RulesWindowController.m @@ -0,0 +1,276 @@ +// +// file: RulesWindowController.m +// project: OverSight (main app) +// description: window controller for 'rules' table +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +#import "consts.h" +#import "RuleRow.h" +#import "utilities.h" +#import "AppDelegate.h" +#import "RulesWindowController.h" + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +@implementation RulesWindowController + +@synthesize rules; + +//alloc/init +// get rules and listen for new ones +-(void)windowDidLoad +{ + //setup observer for new rules + self.rulesObserver = [[NSNotificationCenter defaultCenter] addObserverForName:RULES_CHANGED object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) + { + //get new rules + [self loadRules]; + }]; + + return; +} + +//configure (UI) +-(void)configure +{ + //load rules + [self loadRules]; + + //center window + [self.window center]; + + //show window + [self showWindow:self]; + + //make it key window + [self.window makeKeyAndOrderFront:self]; + + return; +} + + +//get rules from daemon +// then, re-load rules table +-(void)loadRules +{ + //dbg msg + os_log_debug(logHandle, "loading rules..."); + + //in background get rules + // ...then load rule table table + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + //get rules + self.rules = [[NSUserDefaults.standardUserDefaults objectForKey:PREFS_ALLOWED_ITEMS] mutableCopy]; + + //dbg msg + os_log_debug(logHandle, "loaded %lu allowed items", (unsigned long)self.rules.count); + + //sort + // case insenstive on name + self.rules = [[self.rules sortedArrayUsingComparator:^NSComparisonResult(NSDictionary* a, NSDictionary* b) + { + return [getProcessName(a[EVENT_PROCESS_PATH]) caseInsensitiveCompare: getProcessName(b[EVENT_PROCESS_PATH])]; + + }] mutableCopy]; + + //show rules in UI + // ...gotta do this on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + + //reload table + [self.tableView reloadData]; + + //select first row + [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; + + //set overlay vibility + self.overlay.hidden = !(0 == self.rules.count); + + }); + + }); + + return; +} + +//delete an allowed item +-(IBAction)deleteRule:(id)sender +{ + //index of row + // either clicked or selected row + NSInteger row = 0; + + //allowed items + NSMutableArray* allowedItems = nil; + + //item to delete + NSDictionary* item = nil; + + //dbg msg + os_log_debug(logHandle, "deleting allowed item"); + + //get selected row + row = [self.tableView rowForView:sender]; + if(-1 == row) goto bail; + + //get item + item = self.rules[row]; + + //dbg msg + os_log_debug(logHandle, "allowed item: %{public}@ (device: %@)", item[EVENT_PROCESS_PATH], item[EVENT_DEVICE]); + + //(re)load items + allowedItems = [[NSUserDefaults.standardUserDefaults objectForKey:PREFS_ALLOWED_ITEMS] mutableCopy]; + + //find/remove item + for (NSInteger i = allowedItems.count - 1; i >= 0; i--) + { + if( (item[EVENT_DEVICE] != allowedItems[i][EVENT_DEVICE]) || + (item[EVENT_PROCESS_PATH] != allowedItems[i][EVENT_PROCESS_PATH]) ) + { + //no match + continue; + } + + //remove + [allowedItems removeObjectAtIndex:i]; + } + + //save & sync + [NSUserDefaults.standardUserDefaults setObject:allowedItems forKey:PREFS_ALLOWED_ITEMS]; + [NSUserDefaults.standardUserDefaults synchronize]; + + //reload rules + [self loadRules]; + +bail: + + return; +} + +#pragma mark - +#pragma mark table delegate methods + +//number of rows +-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + //row's count + return self.rules.count; +} + +//cell for table column +-(NSView*)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + //cell + NSTableCellView *tableCell = nil; + + //item + NSDictionary* allowedItem = nil; + + //process path + NSString* processPath = nil; + + //grab item + allowedItem = self.rules[row]; + + //column: 'process' + // set process icon, name and path + if(tableColumn == tableView.tableColumns[0]) + { + //init table cell + tableCell = [tableView makeViewWithIdentifier:@"processCell" owner:self]; + if(nil == tableCell) + { + //bail + goto bail; + } + + //extract path + processPath = allowedItem[EVENT_PROCESS_PATH]; + + //set icon + tableCell.imageView.image = getIconForProcess(processPath); + + //set process name + tableCell.textField.stringValue = getProcessName(processPath); + + //set sub text (path) + ((NSTextField*)[tableCell viewWithTag:TABLE_ROW_SUB_TEXT_FILE]).stringValue = processPath; + + //set text color to gray + ((NSTextField*)[tableCell viewWithTag:TABLE_ROW_SUB_TEXT_FILE]).textColor = [NSColor secondaryLabelColor]; + } + + //column: 'rule' + // set icon and rule action + else + { + //init table cell + tableCell = [tableView makeViewWithIdentifier:@"ruleCell" owner:self]; + if(nil == tableCell) + { + //bail + goto bail; + } + + tableCell.textField.stringValue = (Device_Camera == [allowedItem[EVENT_DEVICE] intValue]) ? @"camera" : @"microphone"; + } + +bail: + + return tableCell; +} + +//row for view +-(NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row +{ + //row view + RuleRow* rowView = nil; + + //row ID + static NSString* const kRowIdentifier = @"RowView"; + + //try grab existing row view + rowView = [tableView makeViewWithIdentifier:kRowIdentifier owner:self]; + + //make new if needed + if(nil == rowView) + { + //create new + // ->size doesn't matter + rowView = [[RuleRow alloc] initWithFrame:NSZeroRect]; + + //set row ID + rowView.identifier = kRowIdentifier; + } + + return rowView; +} + +//on window close +// set activation policy +-(void)windowWillClose:(NSNotification *)notification +{ + //wait a bit, then set activation policy + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + //on main thread + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + + //set activation policy + [((AppDelegate*)[[NSApplication sharedApplication] delegate]) setActivationPolicy]; + + }); + }); + + return; +} + +@end diff --git a/MainApp/Rules.xib b/Application/Application/Rules_old.xib similarity index 52% rename from MainApp/Rules.xib rename to Application/Application/Rules_old.xib index 03e7488..948449c 100644 --- a/MainApp/Rules.xib +++ b/Application/Application/Rules_old.xib @@ -1,15 +1,15 @@ - + - + + - - - + + @@ -17,33 +17,71 @@ - + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - + + + - - - + + @@ -52,66 +90,70 @@ - + - - + + - - + + - - - - + + + + - - - - - + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + @@ -128,73 +170,45 @@ - - - - - - - - - + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/Application/Application/StatusBarItem.h b/Application/Application/StatusBarItem.h new file mode 100644 index 0000000..1b39a58 --- /dev/null +++ b/Application/Application/StatusBarItem.h @@ -0,0 +1,35 @@ +// +// file: StatusBarMenu.h +// project: OverSight (login item) +// description: menu handler for status bar icon (header) +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + + +@import Cocoa; + +@interface StatusBarItem : NSObject +{ + +} + +//status item +@property(nonatomic, strong, readwrite)NSStatusItem *statusItem; + +//popover +@property(retain, nonatomic)NSPopover *popover; + +//disabled flag +@property BOOL isDisabled; + +/* METHODS */ + +//init +-(id)init:(NSMenu*)menu; + +//remove status item +-(void)removeStatusItem; + +@end diff --git a/Application/Application/StatusBarItem.m b/Application/Application/StatusBarItem.m new file mode 100644 index 0000000..80c9f8a --- /dev/null +++ b/Application/Application/StatusBarItem.m @@ -0,0 +1,285 @@ +// +// file: StatusBarMenu.m +// project: OverSight (login item) +// description: menu handler for status bar icon +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +#import "consts.h" +#import "utilities.h" +#import "AppDelegate.h" +#import "StatusBarItem.h" +#import "StatusBarPopoverController.h" + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +//menu items +enum menuItems +{ + status = 100, + toggle, + rules, + prefs, + quit, + end +}; + +@implementation StatusBarItem + +@synthesize isDisabled; +@synthesize statusItem; + +//init method +// set some intial flags +-(id)init:(NSMenu*)menu +{ + //token + static dispatch_once_t onceToken = 0; + + //super + self = [super init]; + if(self != nil) + { + //create item + [self createStatusItem:menu]; + + //only once + // show popover + dispatch_once(&onceToken, ^{ + + //first time? + // show popover + if(YES == [[[NSProcessInfo processInfo] arguments] containsObject:INITIAL_LAUNCH]) + { + //dbg msg + os_log_debug(logHandle, "initial launch, will show popover"); + + //show + [self showPopover]; + } + + }); + + //set state based on (existing) preferences + self.isDisabled = [NSUserDefaults.standardUserDefaults boolForKey:PREF_IS_DISABLED]; + + //set initial menu state + [self setState]; + } + + return self; +} + +//create status item +-(void)createStatusItem:(NSMenu*)menu +{ + //init status item + statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; + + //set menu + self.statusItem.menu = menu; + + //set action handler for all menu items + for(int i=toggle; i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Application/Application/StatusBarPopoverController.h b/Application/Application/StatusBarPopoverController.h new file mode 100755 index 0000000..56aede0 --- /dev/null +++ b/Application/Application/StatusBarPopoverController.h @@ -0,0 +1,14 @@ +// +// file: StatusBarPopoverController.h +// project: OverSight (login item) +// description: popover for status bar (header) +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +@import Cocoa; + +@interface StatusBarPopoverController : NSViewController + +@end diff --git a/Application/Application/StatusBarPopoverController.m b/Application/Application/StatusBarPopoverController.m new file mode 100755 index 0000000..c310937 --- /dev/null +++ b/Application/Application/StatusBarPopoverController.m @@ -0,0 +1,25 @@ +// +// file: StatusBarPopoverController.m +// project: OverSight (login item) +// description: popover for status bar +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +#import "AppDelegate.h" +#import "StatusBarPopoverController.h" + +@implementation StatusBarPopoverController + +//'close' button handler +// simply dismiss/close popover +-(IBAction)closePopover:(NSControl *)sender +{ + //close + [[[self view] window] close]; + + return; +} + +@end diff --git a/Application/Application/Update.h b/Application/Application/Update.h new file mode 100644 index 0000000..d120f22 --- /dev/null +++ b/Application/Application/Update.h @@ -0,0 +1,24 @@ +// +// file: Update.h +// project: OverSight (shared) +// description: checks for new versions (header) +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +#ifndef Update_h +#define Update_h + +@import Cocoa; +@import Foundation; + +@interface Update : NSObject + +//check for an update +// will invoke app delegate method to update UI when check completes +-(void)checkForUpdate:(void (^)(NSUInteger result, NSString* latestVersion))completionHandler; + +@end + +#endif diff --git a/Application/Application/Update.m b/Application/Application/Update.m new file mode 100644 index 0000000..75792ae --- /dev/null +++ b/Application/Application/Update.m @@ -0,0 +1,105 @@ +// +// file: Update.m +// project: OverSight (shared) +// description: checks for new versions +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +@import OSLog; + +#import "consts.h" +#import "Update.h" +#import "utilities.h" +#import "AppDelegate.h" + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +@implementation Update + +//check for an update +// ->will invoke app delegate method to update UI when check completes +-(void)checkForUpdate:(void (^)(NSUInteger result, NSString* latestVersion))completionHandler +{ + //latest version + __block NSString* latestVersion = nil; + + //result + __block NSInteger result = -1; + + //get latest version in background + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + //grab latest version + latestVersion = [self getLatestVersion]; + if(nil != latestVersion) + { + //check + result = (NSOrderedAscending == [getAppVersion() compare:latestVersion options:NSNumericSearch]); + } + + //invoke app delegate method + // ->will update UI/show popup if necessart + dispatch_async(dispatch_get_main_queue(), + ^{ + completionHandler(result, latestVersion); + }); + }); + + return; +} + +//query interwebz to get latest version +-(NSString*)getLatestVersion +{ + //product version(s) data + NSData* productsVersionData = nil; + + //version dictionary + NSDictionary* productsVersionDictionary = nil; + + //latest version + NSString* latestVersion = nil; + + //get version from remote URL + productsVersionData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:PRODUCT_VERSIONS_URL]]; + if(nil == productsVersionData) + { + //bail + goto bail; + } + + //convert JSON to dictionary + // ->wrap as may throw exception + @try + { + //convert + productsVersionDictionary = [NSJSONSerialization JSONObjectWithData:productsVersionData options:0 error:nil]; + if(nil == productsVersionDictionary) + { + //bail + goto bail; + } + } + @catch(NSException* exception) + { + //bail + goto bail; + } + + //extract latest version + latestVersion = [[productsVersionDictionary objectForKey:@"OverSight"] objectForKey:@"version"]; + + //dbg msg + os_log_debug(logHandle, "latest version: %@", latestVersion); + +bail: + + return latestVersion; +} + +@end diff --git a/Shared/InfoWindow.xib b/Application/Application/UpdateWindow.xib similarity index 66% rename from Shared/InfoWindow.xib rename to Application/Application/UpdateWindow.xib index d3210ab..b6233b8 100644 --- a/Shared/InfoWindow.xib +++ b/Application/Application/UpdateWindow.xib @@ -1,14 +1,14 @@ - - + + - + + - @@ -17,24 +17,27 @@ - - - - + + + + - + - - + + + - - + + + - - + + + @@ -42,7 +45,8 @@ - @@ -74,11 +70,11 @@ - + - - + + diff --git a/Shared/InfoWindowController.h b/Application/Application/UpdateWindowController.h similarity index 72% rename from Shared/InfoWindowController.h rename to Application/Application/UpdateWindowController.h index 512c8a0..f735c1a 100644 --- a/Shared/InfoWindowController.h +++ b/Application/Application/UpdateWindowController.h @@ -1,14 +1,15 @@ // -// PrefsWindowController.h -// DHS +// file: UpdateWindowController.m +// project: OverSight (shared) +// description: window handler for update window/popup (header) // -// Created by Patrick Wardle on 2/6/15. -// Copyright (c) 2015 Objective-See, LLC. All rights reserved. +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. // -#import +@import Cocoa; -@interface InfoWindowController : NSWindowController +@interface UpdateWindowController : NSWindowController { } diff --git a/Application/Application/UpdateWindowController.m b/Application/Application/UpdateWindowController.m new file mode 100644 index 0000000..e54f1ae --- /dev/null +++ b/Application/Application/UpdateWindowController.m @@ -0,0 +1,121 @@ +// +// file: UpdateWindowController.m +// project: OverSight (shared) +// description: window handler for update window/popup +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +#import "consts.h" +#import "utilities.h" +#import "AppDelegate.h" +#import "UpdateWindowController.h" + + +@implementation UpdateWindowController + +@synthesize infoLabel; +@synthesize overlayView; +@synthesize firstButton; +@synthesize actionButton; +@synthesize infoLabelString; +@synthesize actionButtonTitle; +@synthesize progressIndicator; + +//automatically called when nib is loaded +// ->center window +-(void)awakeFromNib +{ + //center + [self.window center]; + + return; +} + +//automatically invoked when window is loaded +// ->set to white +-(void)windowDidLoad +{ + //super + [super windowDidLoad]; + + //not in dark mode? + // make window white + if(YES != isDarkMode()) + { + //make white + self.window.backgroundColor = NSColor.whiteColor; + } + + //indicated title bar is tranparent (too) + self.window.titlebarAppearsTransparent = YES; + + //set main label + [self.infoLabel setStringValue:self.infoLabelString]; + + //set button text + self.actionButton.title = self.actionButtonTitle; + + //hide 1st button on 'update' + // ...don't need update check button + if(YES == [self.actionButton.title isEqualToString:@"Update"]) + { + //hide + self.firstButton.hidden = YES; + + //then make action button first responder + [self.window makeFirstResponder:self.actionButton]; + } + + //make it key window + [self.window makeKeyAndOrderFront:self]; + + //make window front + [NSApp activateIgnoringOtherApps:YES]; + + return; +} + +//automatically invoked when window is closing +// ->make ourselves unmodal +-(void)windowWillClose:(NSNotification *)notification +{ + //make un-modal + [[NSApplication sharedApplication] stopModal]; + + return; +} + +//save the main label's & button title's text +// ->invoked before window is loaded (and thus buttons, etc are nil) +-(void)configure:(NSString*)label buttonTitle:(NSString*)buttonTitle +{ + //save label's string + self.infoLabelString = label; + + //save button's title + self.actionButtonTitle = buttonTitle; + + return; +} + +//invoked when user clicks button +// trigger action such as opening product website, updating, etc +-(IBAction)buttonHandler:(id)sender +{ + //handle 'update' / 'more info', etc + // ->open OverSight's webpage, if they *didn't* click 'close' + if(YES != [((NSButton*)sender).title isEqualToString:@"close"]) + { + //open URL + // ->invokes user's default browser + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:PRODUCT_URL]]; + } + + //always close window + [[self window] close]; + + return; +} +@end diff --git a/Application/Application/main.m b/Application/Application/main.m new file mode 100644 index 0000000..11d5483 --- /dev/null +++ b/Application/Application/main.m @@ -0,0 +1,100 @@ +// +// file: main.m +// project: OverSight (login item) +// description: main; 'nuff said +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +//FOR LOGGING: +// % log stream --level debug --predicate="subsystem='com.objective-see.oversight'" + +@import Cocoa; +@import OSLog; + +@import Sentry; + +#import "consts.h" +#import "utilities.h" + +/* GLOBALS */ + +//log handle +os_log_t logHandle = nil; + +//main interface +// sanity checks, then kick off app +int main(int argc, const char * argv[]) +{ + //status + int status = 0; + + //(v1.0) allowed items + NSArray* allowedItems = nil; + + //init log + logHandle = os_log_create(BUNDLE_ID, "application"); + + //dbg msg(s) + os_log_debug(logHandle, "started: %{public}@ (pid: %d / uid: %d)", NSProcessInfo.processInfo.arguments.firstObject, getpid(), getuid()); + os_log_debug(logHandle, "arguments: %{public}@", NSProcessInfo.processInfo.arguments); + + //init crash reporting + [SentrySDK startWithConfigureOptions:^(SentryOptions *options) { + options.dsn = SENTRY_DSN; + options.debug = YES; + }]; + + //upgrade allowed items? + // convert into 'NSUserDefaults' and then exit + if(YES == [NSProcessInfo.processInfo.arguments containsObject:CMD_UPGRADE]) + { + //dbg msg + os_log_debug(logHandle, "upgrading allowed items (from: %@)", NSProcessInfo.processInfo.arguments.lastObject); + + //load rules + allowedItems = [NSArray arrayWithContentsOfFile:NSProcessInfo.processInfo.arguments.lastObject]; + if(0 != allowedItems.count) + { + //set & snyc + [NSUserDefaults.standardUserDefaults setValue:allowedItems forKey:PREFS_ALLOWED_ITEMS]; + [NSUserDefaults.standardUserDefaults synchronize]; + } + + //done + goto bail; + } + + //initial launch? + // set defaults/handle login item persistence + if(YES == [NSProcessInfo.processInfo.arguments containsObject:INITIAL_LAUNCH]) + { + // autostart mode + // not specified? set to true + if(nil == [NSUserDefaults.standardUserDefaults objectForKey:PREF_AUTOSTART_MODE]) + { + //set & snyc + [NSUserDefaults.standardUserDefaults setBool:YES forKey:PREF_AUTOSTART_MODE]; + [NSUserDefaults.standardUserDefaults synchronize]; + } + + //autostart mode enabled? + // since this is initial launch, check that login item is set + if(YES == [NSUserDefaults.standardUserDefaults boolForKey:PREF_AUTOSTART_MODE]) + { + //dbg msg + os_log_debug(logHandle, "first launch + auto-start is set ...will ensure app is persisted as login item"); + + //persist + toggleLoginItem([NSURL fileURLWithPath:NSBundle.mainBundle.bundlePath], NSControlStateValueOn); + } + } + + //launch app normally + status = NSApplicationMain(argc, argv); + +bail: + + return status; +} diff --git a/Application/Application/security.plist b/Application/Application/security.plist new file mode 100644 index 0000000..394d01b --- /dev/null +++ b/Application/Application/security.plist @@ -0,0 +1,10 @@ + + + + + Contact + security@objective-see.com + Preferred-Languages + en + + diff --git a/Cartfile b/Cartfile new file mode 100644 index 0000000..02bfc38 --- /dev/null +++ b/Cartfile @@ -0,0 +1 @@ +github "getsentry/sentry-cocoa" "6.0.10" diff --git a/Cartfile.resolved b/Cartfile.resolved new file mode 100644 index 0000000..b50bfc9 --- /dev/null +++ b/Cartfile.resolved @@ -0,0 +1 @@ +github "getsentry/sentry-cocoa" "6.0.0" diff --git a/Installer.xcodeproj/project.pbxproj b/Installer.xcodeproj/project.pbxproj deleted file mode 100644 index a7934df..0000000 --- a/Installer.xcodeproj/project.pbxproj +++ /dev/null @@ -1,428 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 7D17C4E51D658D9F0066232A /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C4D81D658D9F0066232A /* AboutWindow.xib */; }; - 7D17C4E61D658D9F0066232A /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C4DA1D658D9F0066232A /* AboutWindowController.m */; }; - 7D17C4E71D658D9F0066232A /* Exception.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C4DD1D658D9F0066232A /* Exception.m */; }; - 7D17C4E81D658D9F0066232A /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C4DF1D658D9F0066232A /* icon.png */; }; - 7D17C4E91D658D9F0066232A /* overSight.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C4E01D658D9F0066232A /* overSight.png */; }; - 7D17C4EA1D658D9F0066232A /* Logging.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C4E21D658D9F0066232A /* Logging.m */; }; - 7D17C4EB1D658D9F0066232A /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C4E41D658D9F0066232A /* Utilities.m */; }; - 7D17C4F91D658DB90066232A /* Configure.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C4EC1D658DB90066232A /* Configure.m */; }; - 7D17C4FA1D658DB90066232A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C4ED1D658DB90066232A /* AppDelegate.m */; }; - 7D17C4FB1D658DB90066232A /* ConfigureWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C4EE1D658DB90066232A /* ConfigureWindowController.m */; }; - 7D17C4FC1D658DB90066232A /* ConfigureWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C4EF1D658DB90066232A /* ConfigureWindowController.xib */; }; - 7D17C4FD1D658DB90066232A /* ErrorWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C4F31D658DB90066232A /* ErrorWindowController.m */; }; - 7D17C4FE1D658DB90066232A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C4F41D658DB90066232A /* main.m */; }; - 7D17C5011D658DB90066232A /* ErrorWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C4F81D658DB90066232A /* ErrorWindowController.xib */; }; - 7D17C5041D658DEC0066232A /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C5021D658DEC0066232A /* MainMenu.xib */; }; - 7D6245831D87C3D700870565 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D6245821D87C3D700870565 /* Images.xcassets */; }; - 7DEABC481E907DF10024C6AB /* objectiveSee.png in Resources */ = {isa = PBXBuildFile; fileRef = 7DEABC471E907DF10024C6AB /* objectiveSee.png */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 7D17C4D81D658D9F0066232A /* AboutWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutWindow.xib; sourceTree = ""; }; - 7D17C4D91D658D9F0066232A /* AboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutWindowController.h; sourceTree = ""; }; - 7D17C4DA1D658D9F0066232A /* AboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutWindowController.m; sourceTree = ""; }; - 7D17C4DB1D658D9F0066232A /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = ""; }; - 7D17C4DC1D658D9F0066232A /* Exception.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Exception.h; sourceTree = ""; }; - 7D17C4DD1D658D9F0066232A /* Exception.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Exception.m; sourceTree = ""; }; - 7D17C4DF1D658D9F0066232A /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; - 7D17C4E01D658D9F0066232A /* overSight.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = overSight.png; sourceTree = ""; }; - 7D17C4E11D658D9F0066232A /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = ""; }; - 7D17C4E21D658D9F0066232A /* Logging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Logging.m; sourceTree = ""; }; - 7D17C4E31D658D9F0066232A /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; - 7D17C4E41D658D9F0066232A /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = ""; }; - 7D17C4EC1D658DB90066232A /* Configure.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Configure.m; path = Installer/Configure.m; sourceTree = SOURCE_ROOT; }; - 7D17C4ED1D658DB90066232A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Installer/AppDelegate.m; sourceTree = SOURCE_ROOT; }; - 7D17C4EE1D658DB90066232A /* ConfigureWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ConfigureWindowController.m; path = Installer/ConfigureWindowController.m; sourceTree = SOURCE_ROOT; }; - 7D17C4EF1D658DB90066232A /* ConfigureWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ConfigureWindowController.xib; path = Installer/ConfigureWindowController.xib; sourceTree = SOURCE_ROOT; }; - 7D17C4F01D658DB90066232A /* Configure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Configure.h; path = Installer/Configure.h; sourceTree = SOURCE_ROOT; }; - 7D17C4F11D658DB90066232A /* ConfigureWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ConfigureWindowController.h; path = Installer/ConfigureWindowController.h; sourceTree = SOURCE_ROOT; }; - 7D17C4F21D658DB90066232A /* ErrorWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ErrorWindowController.h; path = Installer/ErrorWindowController.h; sourceTree = SOURCE_ROOT; }; - 7D17C4F31D658DB90066232A /* ErrorWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ErrorWindowController.m; path = Installer/ErrorWindowController.m; sourceTree = SOURCE_ROOT; }; - 7D17C4F41D658DB90066232A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Installer/main.m; sourceTree = SOURCE_ROOT; }; - 7D17C4F51D658DB90066232A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Installer/Info.plist; sourceTree = SOURCE_ROOT; }; - 7D17C4F61D658DB90066232A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Installer/AppDelegate.h; sourceTree = SOURCE_ROOT; }; - 7D17C4F81D658DB90066232A /* ErrorWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ErrorWindowController.xib; path = Installer/ErrorWindowController.xib; sourceTree = SOURCE_ROOT; }; - 7D17C5031D658DEC0066232A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Installer/Base.lproj/MainMenu.xib; sourceTree = SOURCE_ROOT; }; - 7D24C8651D2CDEA7009932EE /* OverSight_Installer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OverSight_Installer.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 7D6245821D87C3D700870565 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Images/Images.xcassets; sourceTree = ""; }; - 7D9A7DE91D8A8BDA0091C1AF /* main.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = main.h; path = Installer/main.h; sourceTree = SOURCE_ROOT; }; - 7DEABC471E907DF10024C6AB /* objectiveSee.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = objectiveSee.png; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 7D24C8621D2CDEA7009932EE /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 7D17C4D71D658D9F0066232A /* Shared */ = { - isa = PBXGroup; - children = ( - 7D6245821D87C3D700870565 /* Images.xcassets */, - 7D17C4D81D658D9F0066232A /* AboutWindow.xib */, - 7D17C4D91D658D9F0066232A /* AboutWindowController.h */, - 7D17C4DA1D658D9F0066232A /* AboutWindowController.m */, - 7D17C4DB1D658D9F0066232A /* Consts.h */, - 7D17C4DC1D658D9F0066232A /* Exception.h */, - 7D17C4DD1D658D9F0066232A /* Exception.m */, - 7D17C4DE1D658D9F0066232A /* Images */, - 7D17C4E11D658D9F0066232A /* Logging.h */, - 7D17C4E21D658D9F0066232A /* Logging.m */, - 7D17C4E31D658D9F0066232A /* Utilities.h */, - 7D17C4E41D658D9F0066232A /* Utilities.m */, - ); - path = Shared; - sourceTree = ""; - }; - 7D17C4DE1D658D9F0066232A /* Images */ = { - isa = PBXGroup; - children = ( - 7D17C4DF1D658D9F0066232A /* icon.png */, - 7DEABC471E907DF10024C6AB /* objectiveSee.png */, - 7D17C4E01D658D9F0066232A /* overSight.png */, - ); - path = Images; - sourceTree = ""; - }; - 7D24C85C1D2CDEA7009932EE = { - isa = PBXGroup; - children = ( - 7D17C4D71D658D9F0066232A /* Shared */, - 7D24C8671D2CDEA7009932EE /* Source */, - 7D24C8661D2CDEA7009932EE /* Products */, - ); - sourceTree = ""; - }; - 7D24C8661D2CDEA7009932EE /* Products */ = { - isa = PBXGroup; - children = ( - 7D24C8651D2CDEA7009932EE /* OverSight_Installer.app */, - ); - name = Products; - sourceTree = ""; - }; - 7D24C8671D2CDEA7009932EE /* Source */ = { - isa = PBXGroup; - children = ( - 7D17C5021D658DEC0066232A /* MainMenu.xib */, - 7D17C4F61D658DB90066232A /* AppDelegate.h */, - 7D17C4ED1D658DB90066232A /* AppDelegate.m */, - 7D17C4F11D658DB90066232A /* ConfigureWindowController.h */, - 7D17C4EE1D658DB90066232A /* ConfigureWindowController.m */, - 7D17C4EF1D658DB90066232A /* ConfigureWindowController.xib */, - 7D17C4F01D658DB90066232A /* Configure.h */, - 7D17C4EC1D658DB90066232A /* Configure.m */, - 7D17C4F21D658DB90066232A /* ErrorWindowController.h */, - 7D17C4F31D658DB90066232A /* ErrorWindowController.m */, - 7D17C4F81D658DB90066232A /* ErrorWindowController.xib */, - 7D24C86B1D2CDEA7009932EE /* Supporting Files */, - ); - name = Source; - path = WhatsYourSign; - sourceTree = ""; - }; - 7D24C86B1D2CDEA7009932EE /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 7D17C4F51D658DB90066232A /* Info.plist */, - 7D17C4F41D658DB90066232A /* main.m */, - 7D9A7DE91D8A8BDA0091C1AF /* main.h */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 7D24C8641D2CDEA7009932EE /* Installer */ = { - isa = PBXNativeTarget; - buildConfigurationList = 7D24C8761D2CDEA7009932EE /* Build configuration list for PBXNativeTarget "Installer" */; - buildPhases = ( - 7D24C8611D2CDEA7009932EE /* Sources */, - 7D24C8621D2CDEA7009932EE /* Frameworks */, - 7D24C8631D2CDEA7009932EE /* Resources */, - 7D1A49C71D502D4A000E2BEB /* ShellScript */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Installer; - productName = WhatsYourSign; - productReference = 7D24C8651D2CDEA7009932EE /* OverSight_Installer.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 7D24C85D1D2CDEA7009932EE /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0900; - ORGANIZATIONNAME = "Objective-See"; - TargetAttributes = { - 7D24C8641D2CDEA7009932EE = { - CreatedOnToolsVersion = 7.3; - DevelopmentTeam = VBG97UB4TA; - }; - }; - }; - buildConfigurationList = 7D24C8601D2CDEA7009932EE /* Build configuration list for PBXProject "Installer" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 7D24C85C1D2CDEA7009932EE; - productRefGroup = 7D24C8661D2CDEA7009932EE /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 7D24C8641D2CDEA7009932EE /* Installer */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 7D24C8631D2CDEA7009932EE /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D6245831D87C3D700870565 /* Images.xcassets in Resources */, - 7D17C4E91D658D9F0066232A /* overSight.png in Resources */, - 7D17C5011D658DB90066232A /* ErrorWindowController.xib in Resources */, - 7DEABC481E907DF10024C6AB /* objectiveSee.png in Resources */, - 7D17C4E81D658D9F0066232A /* icon.png in Resources */, - 7D17C4FC1D658DB90066232A /* ConfigureWindowController.xib in Resources */, - 7D17C4E51D658D9F0066232A /* AboutWindow.xib in Resources */, - 7D17C5041D658DEC0066232A /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 7D1A49C71D502D4A000E2BEB /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "#for normal builds\n# ->just copy in app\n#cp -R -f $BUILT_PRODUCTS_DIR/OverSight.app $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources/\n\n#for achieving\n# ->first build OverSight in release mode (don't archive it)\n# then archive (this) installer app\n#cp -R -f ~/objective-see/OverSight/DerivedData/OverSight/Build/Products/Release/OverSight.app ~/objective-see/OverSight/DerivedData/OverSight/Build/Intermediates/ArchiveIntermediates/Installer/BuildProductsPath/Release/OverSight_Installer.app/Contents/Resources/\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 7D24C8611D2CDEA7009932EE /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D17C4FB1D658DB90066232A /* ConfigureWindowController.m in Sources */, - 7D17C4FA1D658DB90066232A /* AppDelegate.m in Sources */, - 7D17C4E71D658D9F0066232A /* Exception.m in Sources */, - 7D17C4EB1D658D9F0066232A /* Utilities.m in Sources */, - 7D17C4F91D658DB90066232A /* Configure.m in Sources */, - 7D17C4EA1D658D9F0066232A /* Logging.m in Sources */, - 7D17C4FD1D658DB90066232A /* ErrorWindowController.m in Sources */, - 7D17C4FE1D658DB90066232A /* main.m in Sources */, - 7D17C4E61D658D9F0066232A /* AboutWindowController.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 7D17C5021D658DEC0066232A /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 7D17C5031D658DEC0066232A /* Base */, - ); - name = MainMenu.xib; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 7D24C8741D2CDEA7009932EE /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - }; - name = Debug; - }; - 7D24C8751D2CDEA7009932EE /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - }; - name = Release; - }; - 7D24C8771D2CDEA7009932EE /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = VBG97UB4TA; - GCC_PREPROCESSOR_DEFINITIONS = ( - "IS_INSTALLER_APP=1", - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; - INFOPLIST_FILE = Installer/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = "com.objectiveSee.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = OverSight_Installer; - USER_HEADER_SEARCH_PATHS = ../Shared; - }; - name = Debug; - }; - 7D24C8781D2CDEA7009932EE /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = VBG97UB4TA; - GCC_PREPROCESSOR_DEFINITIONS = "IS_INSTALLER_APP=1"; - GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; - INFOPLIST_FILE = Installer/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = "com.objectiveSee.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = OverSight_Installer; - USER_HEADER_SEARCH_PATHS = ../Shared; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 7D24C8601D2CDEA7009932EE /* Build configuration list for PBXProject "Installer" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7D24C8741D2CDEA7009932EE /* Debug */, - 7D24C8751D2CDEA7009932EE /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 7D24C8761D2CDEA7009932EE /* Build configuration list for PBXNativeTarget "Installer" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7D24C8771D2CDEA7009932EE /* Debug */, - 7D24C8781D2CDEA7009932EE /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 7D24C85D1D2CDEA7009932EE /* Project object */; -} diff --git a/Installer.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Installer.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 2164585..0000000 --- a/Installer.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Installer/AppDelegate.h b/Installer/AppDelegate.h deleted file mode 100644 index a56d0dd..0000000 --- a/Installer/AppDelegate.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// AppDelegate.h -// OverSight -// -// Created by Patrick Wardle on 9/01/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -#import "AboutWindowController.h" -#import "ErrorWindowController.h" -#import "ConfigureWindowController.h" - -@interface AppDelegate : NSObject -{ - -} - -/* PROPERTIES */ - -//about window controller -@property(nonatomic, retain)AboutWindowController* aboutWindowController; - -//configure window controller -@property(nonatomic, retain)ConfigureWindowController* configureWindowController; - -//error window controller -@property(nonatomic, retain)ErrorWindowController* errorWindowController; - - -/* METHODS */ - -//display configuration window w/ 'install' || 'uninstall' button --(void)displayConfigureWindow:(BOOL)isInstalled; - -//display error window --(void)displayErrorWindow:(NSDictionary*)errorInfo; - -@end - - diff --git a/Installer/AppDelegate.m b/Installer/AppDelegate.m deleted file mode 100644 index 5f2951f..0000000 --- a/Installer/AppDelegate.m +++ /dev/null @@ -1,140 +0,0 @@ -// -// AppDelegate.m -// OverSight -// -// Created by Patrick Wardle on 9/01/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Configure.h" -#import "Exception.h" -#import "Utilities.h" -#import "AppDelegate.h" - -@interface AppDelegate () - -@property (weak) IBOutlet NSWindow *window; -@end - -@implementation AppDelegate - -@synthesize aboutWindowController; -@synthesize errorWindowController; -@synthesize configureWindowController; - -//main app interface --(void)applicationDidFinishLaunching:(NSNotification *)aNotification -{ - //config object - Configure* configureObj = nil; - - //first thing... - // ->install exception handlers - installExceptionHandlers(); - - //alloc/init Config obj - configureObj = [[Configure alloc] init]; - - //show window - [self displayConfigureWindow:[configureObj isInstalled]]; - -//bail -bail: - - return; - -} - -//automatically invoked when user clicks 'About/Info' -// ->show about window --(IBAction)about:(id)sender -{ - //alloc/init settings window - if(nil == self.aboutWindowController) - { - //alloc/init - aboutWindowController = [[AboutWindowController alloc] initWithWindowNibName:@"AboutWindow"]; - } - - //center window - [[self.aboutWindowController window] center]; - - //show it - [self.aboutWindowController showWindow:self]; - - return; -} - -//display configuration window w/ 'install' || 'uninstall' button --(void)displayConfigureWindow:(BOOL)isInstalled -{ - //alloc/init - configureWindowController = [[ConfigureWindowController alloc] initWithWindowNibName:@"ConfigureWindowController"]; - - //indicated title bar is tranparent (too) - self.configureWindowController.window.titlebarAppearsTransparent = YES; - - //display it - // ->call this first to so that outlets are connected - [self.configureWindowController display]; - - //configure it - [self.configureWindowController configure:isInstalled]; - - return; -} - -//display error window --(void)displayErrorWindow:(NSDictionary*)errorInfo -{ - //alloc error window - errorWindowController = [[ErrorWindowController alloc] initWithWindowNibName:@"ErrorWindowController"]; - - //main thread - // ->just show UI alert, unless its fatal (then load URL) - if(YES == [NSThread isMainThread]) - { - //non-fatal errors - // ->show error popup - if(YES != [errorInfo[KEY_ERROR_URL] isEqualToString:FATAL_ERROR_URL]) - { - //display it - // ->call this first to so that outlets are connected - [self.errorWindowController display]; - - //configure it - [self.errorWindowController configure:errorInfo]; - } - //fatal error - // ->launch browser to go to fatal error page, then exit - else - { - //launch browser - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:errorInfo[KEY_ERROR_URL]]]; - - //then exit - [NSApp terminate:self]; - } - } - //background thread - // ->have to show error window on main thread - else - { - //show alert - // ->in main UI thread - dispatch_sync(dispatch_get_main_queue(), ^{ - - //display it - // ->call this first to so that outlets are connected - [self.errorWindowController display]; - - //configure it - [self.errorWindowController configure:errorInfo]; - - }); - } - - return; -} -@end diff --git a/Installer/Base.lproj/MainMenu.xib b/Installer/Base.lproj/MainMenu.xib deleted file mode 100644 index 72c4560..0000000 --- a/Installer/Base.lproj/MainMenu.xib +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Installer/Configure.h b/Installer/Configure.h deleted file mode 100644 index 4599bdf..0000000 --- a/Installer/Configure.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// Configure.h -// OverSight -// -// Created by Patrick Wardle on 9/01/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#ifndef __WYS_Configure_h -#define __WYS_Configure_h - -#import - -@interface Configure : NSObject -{ - -} - - -/* METHODS */ - -//determine if extension is installed --(BOOL)isInstalled; - -//invokes appropriate install || uninstall logic --(BOOL)configure:(NSUInteger)parameter; - -//install --(BOOL)install; - -//uninstall --(BOOL)uninstall:(NSUInteger)type; - -//build path to logged in user's app support directory + '/Objective-See' --(NSString*)appSupportPath:(NSString*)user; - -@end - -#endif diff --git a/Installer/Configure.m b/Installer/Configure.m deleted file mode 100644 index fd58996..0000000 --- a/Installer/Configure.m +++ /dev/null @@ -1,567 +0,0 @@ -// -// Configure.m -// OverSight -// -// Created by Patrick Wardle on 9/01/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import -#import - -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" -#import "Configure.h" - -@implementation Configure - -//invokes appropriate install || uninstall logic --(BOOL)configure:(NSUInteger)parameter -{ - //return var - BOOL wasConfigured = NO; - - //install - // ->starts on success - if(ACTION_INSTALL_FLAG == parameter) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"installing..."); - #endif - - //if already installed though - // ->uninstall everything first - if(YES == [self isInstalled]) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"already installed, so stopping/uninstalling..."); - #endif - - //stop - // ->kill main app/login item/XPC service - [self stop]; - - //uninstall - // ->but do partial (leave whitelist) - if(YES != [self uninstall:UNINSTALL_PARIAL]) - { - //bail - goto bail; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"uninstalled"); - #endif - } - - //install - if(YES != [self install]) - { - //err msg - logMsg(LOG_ERR, @"installation failed"); - - //bail - goto bail; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"installed, now will start"); - #endif - - //start login item - if(YES != [self start]) - { - //err msg - logMsg(LOG_ERR, @"starting failed"); - - //bail - goto bail; - } - } - //uninstall - // ->stops login item (also w/ stop XPC service) - else if(ACTION_UNINSTALL_FLAG == parameter) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"stopping login item"); - #endif - - //stop - // ->kill login item/XPC service - [self stop]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"uninstalling..."); - #endif - - //uninstall - if(YES != [self uninstall:UNINSTALL_FULL]) - { - //bail - goto bail; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"uninstalled!"); - #endif - } - - //no errors - wasConfigured = YES; - -bail: - - return wasConfigured; -} - -//determine if installed -// ->simply checks if application exists in /Applications --(BOOL)isInstalled -{ - //check if extension exists - return [[NSFileManager defaultManager] fileExistsAtPath:[APPS_FOLDER stringByAppendingPathComponent:APP_NAME]]; -} - -//install -// a) copy to /Applications -// b) chown/chmod XPC component --(BOOL)install -{ - //return/status var - BOOL wasInstalled = NO; - - //error - NSError* error = nil; - - //path to app (src) - NSString* appPathSrc = nil; - - //path to app (dest) - NSString* appPathDest = nil; - - //path to XPC service - NSString* xpcServicePath = nil; - - //path to login item - NSString* loginItem = nil; - - //logged in user info - NSMutableDictionary* userInfo = nil; - - //white list - NSString* whiteList = nil; - - //set src path - // ->orginally stored in installer app's /Resource bundle - appPathSrc = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:APP_NAME]; - - //set dest path - appPathDest = [APPS_FOLDER stringByAppendingPathComponent:APP_NAME]; - - //move app into /Applications - if(YES != [[NSFileManager defaultManager] copyItemAtPath:appPathSrc toPath:appPathDest error:&error]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to copy %@ -> %@ (%@)", appPathSrc, appPathDest, error]); - - //bail - goto bail; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"copied %@ -> %@", appPathSrc, appPathDest]); - #endif - - //remove xattrs - // ->otherwise app translocation causes issues - execTask(XATTR, @[@"-cr", appPathDest], NO); - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"removed xattr"); - #endif - - //init path to login item - loginItem = [appPathDest stringByAppendingPathComponent:@"Contents/Library/LoginItems/OverSight Helper.app/Contents/MacOS/OverSight Helper"]; - - //get user info - userInfo = loggedinUser(); - if(nil == userInfo[@"user"]) - { - //err msg - logMsg(LOG_ERR, @"failed to determine logged-in user"); - - //bail - goto bail; - } - - //create app support directory - if(YES != [self createAppSupport:userInfo[@"user"]]) - { - //err msg - logMsg(LOG_ERR, @"failed to create app support directory for current user"); - - //bail - goto bail; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"created app support directory"); - #endif - - //init path to whitelist - whiteList = [[NSString pathWithComponents:@[@"/Users/", userInfo[@"user"], APP_SUPPORT_DIRECTORY]] stringByAppendingPathComponent:FILE_WHITELIST]; - - //if whitelist exists - // ->make sure it's owned by root - if(YES == [[NSFileManager defaultManager] fileExistsAtPath:whiteList]) - { - //set owner, root - setFileOwner(whiteList, @0, @0, NO); - } - - //call into login item to install itself - execTask(loginItem, @[[NSString stringWithUTF8String:CMD_INSTALL]], NO); - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"persisted %@", loginItem]); - #endif - - //init path to XPC service - xpcServicePath = [appPathDest stringByAppendingPathComponent:@"Contents/Library/LoginItems/OverSight Helper.app/Contents/XPCServices/OverSightXPC.xpc"]; - - //set XPC service to be owned; root:wheel - if(YES != setFileOwner(xpcServicePath, @0, @0, YES)) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set file owner to root:wheel on %@", xpcServicePath]); - - //bail - goto bail; - } - - //set XPC service binary to setuid - if(YES != setFilePermissions(xpcServicePath, 06755, YES)) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set file permissions to 06755 on %@", xpcServicePath]); - - //bail - goto bail; - } - - //no error - wasInstalled = YES; - -bail: - - return wasInstalled; -} - -//start login item -// ->exec as logged in user, since might be called via 'sudo' (cmdline install) --(BOOL)start -{ - //flag - BOOL bStarted = NO; - - //path to login item - NSString* loginItem = nil; - - //init path - loginItem = [[APPS_FOLDER stringByAppendingPathComponent:APP_NAME] stringByAppendingPathComponent:@"Contents/Library/LoginItems/OverSight Helper.app/Contents/MacOS/OverSight Helper"]; - - //start it! - // ->don't wait, as it won't exit - execTask(loginItem, nil, NO); - - //happy - bStarted = YES; - -bail: - - return bStarted; -} - -//stop --(void)stop -{ - //kill main app - execTask(PKILL, @[[APP_NAME stringByDeletingPathExtension]], YES); - - //kill helper app - execTask(PKILL, @[APP_HELPER], YES); - - //kill xpc - execTask(PKILL, @[APP_HELPER_XPC], YES); - - return; -} - -//uninstall -// ->delete app, remove login item, etc --(BOOL)uninstall:(NSUInteger)type -{ - //return/status var - BOOL wasUninstalled = NO; - - //status var - // ->since want to try (most) uninstall steps, but record if any fail - BOOL bAnyErrors = NO; - - //path to login item - NSString* loginItem = nil; - - //installed version - NSString* installedVersion = nil; - - //path to installed app - NSString* installedAppPath = nil; - - //error - NSError* error = nil; - - //logged in user info - NSMutableDictionary* userInfo = nil; - - //uninstall command - // ->changed between v1.0 and 1.1+ - NSString* uninstallCmd = nil; - - //init path to login item - loginItem = [[APPS_FOLDER stringByAppendingPathComponent:APP_NAME] stringByAppendingPathComponent:@"Contents/Library/LoginItems/OverSight Helper.app/Contents/MacOS/OverSight Helper"]; - - //init path to installed app - installedAppPath = [APPS_FOLDER stringByAppendingPathComponent:APP_NAME]; - - //get installed app version - installedVersion = [[NSBundle bundleWithPath:installedAppPath] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey]; - - //set uninstall command for version 1.0.0 - if(YES == [installedVersion isEqualToString:@"1.0.0"]) - { - //set command - uninstallCmd = ACTION_UNINSTALL; - } - //set uninstall command for version 1.1.+ - else - { - //set command - uninstallCmd = [NSString stringWithUTF8String:CMD_UNINSTALL]; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"uninstalling login item, with command: '%@'", uninstallCmd]); - #endif - - //get user - userInfo = loggedinUser(); - if(nil == userInfo[@"user"]) - { - //err msg - logMsg(LOG_ERR, @"failed to determine logged-in user"); - - //bail since lots else depends on this - goto bail; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"telling login item %@, to uninstall itself", loginItem]); - #endif - - //call into login item to uninstall itself - execTask(loginItem, @[uninstallCmd], YES); - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"unpersisted %@", loginItem]); - #endif - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"deleting app"); - #endif - - //delete folder - if(YES != [[NSFileManager defaultManager] removeItemAtPath:installedAppPath error:&error]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete app %@ (%@)", installedAppPath, error]); - - //set flag - bAnyErrors = YES; - - //keep uninstalling... - } - - //full uninstall? - // ->remove app support directory too - if(UNINSTALL_FULL == type) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"full uninstall, so also deleting app support directory"); - #endif - - //delete app's app support folder - if(YES == [[NSFileManager defaultManager] fileExistsAtPath:[self appSupportPath:userInfo[@"user"]]]) - { - //delete - if(YES != [self removeAppSupport:userInfo[@"user"]]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete app support directory %@", [self appSupportPath:userInfo[@"user"]]]); - - //set flag - bAnyErrors = YES; - - //keep uninstalling... - } - - //dbg msg - #ifdef DEBUG - else - { - //dbg msg - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"removed app support directory %@", [self appSupportPath:userInfo[@"user"]]]); - } - #endif - - } - } - - //only success when there were no errors - if(YES != bAnyErrors) - { - //happy - wasUninstalled = YES; - } - -bail: - - return wasUninstalled; -} - -//build path to logged in user's app support directory + '/Objective-See/OverSight' -// ->do this manually, since installer might be run via sudo, etc, so can just expand '~' --(NSString*)appSupportPath:(NSString*)user -{ - //build path - return [NSString pathWithComponents:@[@"/Users/", user, APP_SUPPORT_DIRECTORY]]; -} - -//create directory app support -// ->store whitelist file, log file, etc --(BOOL)createAppSupport:(NSString*)user -{ - //flag - BOOL createdDirectory = NO; - - //directory - NSString* appSupportDirectory = nil; - - //user's directory permissions - // ->used to match any created directories - NSDictionary* userDirAttributes = nil; - - //build path - appSupportDirectory = [self appSupportPath:user]; - - //create if not present - if(YES != [[NSFileManager defaultManager] fileExistsAtPath:appSupportDirectory]) - { - //create it - if(YES != [[NSFileManager defaultManager] createDirectoryAtPath:appSupportDirectory withIntermediateDirectories:YES attributes:nil error:NULL]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to create app support directory (%@)", appSupportDirectory]); - - //bail - goto bail; - } - } - - //get permissions of one directory up - // -> ~/Library - userDirAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[@"/Users/" stringByAppendingPathComponent:user] error:nil]; - - //assuming required attributes were found - // ->make sure ~/Library/Application Support/Objective-See is owned by user - if( (nil != userDirAttributes) && - (nil != userDirAttributes[@"NSFileGroupOwnerAccountID"]) && - (nil != userDirAttributes[@"NSFileOwnerAccountID"]) ) - { - //match newly created directory w/ user - setFileOwner([appSupportDirectory stringByDeletingLastPathComponent], userDirAttributes[@"NSFileGroupOwnerAccountID"], userDirAttributes[@"NSFileOwnerAccountID"], YES); - } - - //happy - createdDirectory = YES; - -bail: - - return createdDirectory; -} - -//remove ~/Library/Application Support/Objective-See/OverSight -// and also ~/Library/Application Support/Objective-See/ if nothing else is in there (no other products) --(BOOL)removeAppSupport:(NSString*)user -{ - //flag - BOOL removedDirectory = NO; - - //directory - NSString* appSupportDirectory = nil; - - //error - NSError* error = nil; - - //build path - appSupportDirectory = [self appSupportPath:user]; - - //delete OverSight's app support directory - if(YES != [[NSFileManager defaultManager] removeItemAtPath:appSupportDirectory error:&error]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete OverSight's app support directory %@ (%@)", appSupportDirectory, error]); - - //bail - goto bail; - } - - //anything left in ~/Library/Application Support/Objective-See/? - // ->nope: delete it - if(0 == [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[appSupportDirectory stringByDeletingLastPathComponent] error:nil] count]) - { - if(YES != [[NSFileManager defaultManager] removeItemAtPath:[appSupportDirectory stringByDeletingLastPathComponent] error:&error]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete Objective-See's app support directory %@ (%@)", [appSupportDirectory stringByDeletingLastPathComponent], error]); - - //bail - goto bail; - } - } - - //happy - removedDirectory = YES; - -bail: - - return removedDirectory; -} - -@end diff --git a/Installer/ConfigureWindowController.h b/Installer/ConfigureWindowController.h deleted file mode 100644 index 2ceb18e..0000000 --- a/Installer/ConfigureWindowController.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// ConfigureWindowController.h -// OverSight -// -// Created by Patrick Wardle on 9/01/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -@interface ConfigureWindowController : NSWindowController -{ - -} - -/* PROPERTIES */ - -@property (weak) IBOutlet NSProgressIndicator *activityIndicator; -@property (weak) IBOutlet NSTextField *statusMsg; -@property (weak) IBOutlet NSButton *installButton; -@property (weak) IBOutlet NSButton *uninstallButton; -@property (weak) IBOutlet NSButton *moreInfoButton; -@property (weak) IBOutlet NSButton *supportButton; -@property (strong) IBOutlet NSView *supportView; - - - - - -/* METHODS */ - -//install/uninstall button handler --(IBAction)buttonHandler:(id)sender; - -//(more) info button handler --(IBAction)info:(id)sender; - -//configure window/buttons -// ->also brings to front --(void)configure:(BOOL)isInstalled; - -//display (show) window --(void)display; - -@end diff --git a/Installer/ConfigureWindowController.m b/Installer/ConfigureWindowController.m deleted file mode 100644 index 875d943..0000000 --- a/Installer/ConfigureWindowController.m +++ /dev/null @@ -1,474 +0,0 @@ -// -// ConfigureWindowController.m -// OverSight -// -// Created by Patrick Wardle on 9/01/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "Configure.h" -#import "Utilities.h" -#import "ConfigureWindowController.h" - -#import - -@implementation ConfigureWindowController - -@synthesize statusMsg; -@synthesize moreInfoButton; - -//automatically called when nib is loaded -// ->just center window --(void)awakeFromNib -{ - //center - [self.window center]; - - return; -} - -//configure window/buttons -// ->also brings window to front --(void)configure:(BOOL)isInstalled -{ - //set window title - [self window].title = [NSString stringWithFormat:@"version %@", getAppVersion()]; - - //yosemite doesn't support emojis :P - if(getVersion(gestaltSystemVersionMinor) <= OS_MINOR_VERSION_YOSEMITE) - { - //init status msg - [self.statusMsg setStringValue:@"monitor audio & video access"]; - } - //el capitan supports emojis - else - { - //init status msg - [self.statusMsg setStringValue:@"monitor 🎤 and 📸 access"]; - } - - //enable 'uninstall' button when app is installed already - if(YES == isInstalled) - { - //enable - self.uninstallButton.enabled = YES; - } - //otherwise disable - else - { - //disable - self.uninstallButton.enabled = NO; - } - - //make 'install' have focus - // ->more likely they'll be upgrading, but do after a delay so it sticks... - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.25 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - - //set responder - [self.window makeFirstResponder:self.installButton]; - - }); - - //set delegate - [self.window setDelegate:self]; - - return; -} - -//display (show) window -// ->center, make front, set bg to white, etc --(void)display -{ - //indicated title bar is tranparent (too) - self.window.titlebarAppearsTransparent = YES; - - //center window - [[self window] center]; - - //show (now configured) windows - [self showWindow:self]; - - //make it key window - [self.window makeKeyAndOrderFront:self]; - - //make window front - [NSApp activateIgnoringOtherApps:YES]; - - //make white - [self.window setBackgroundColor: NSColor.whiteColor]; - - return; -} - -//button handler for uninstall/install --(IBAction)buttonHandler:(id)sender -{ - //button title - NSString* buttonTitle = nil; - - //extact button title - buttonTitle = ((NSButton*)sender).title; - - //action - NSUInteger action = 0; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"handling action click: %@", buttonTitle]); - #endif - - //Close/No? - // ->just exit - if( (YES == [buttonTitle isEqualToString:ACTION_CLOSE]) || - (YES == [buttonTitle isEqualToString:ACTION_NO]) ) - { - //close - [self.window close]; - - //bail - goto bail; - } - - //Next >>? - // ->show 'support us' view - if(YES == [buttonTitle isEqualToString:ACTION_NEXT]) - { - //frame - NSRect frame = {0}; - - //unset window title - self.window.title = @""; - - //get main window's frame - frame = self.window.contentView.frame; - - //set origin to 0/0 - frame.origin = CGPointZero; - - //increase y offset - frame.origin.y += 5; - - //reduce height - frame.size.height -= 5; - - //pre-req - [self.supportView setWantsLayer:YES]; - - //update overlay to take up entire window - self.supportView.frame = frame; - - //set overlay's view color to white - self.supportView.layer.backgroundColor = [NSColor whiteColor].CGColor; - - //nap for UI purposes - [NSThread sleepForTimeInterval:0.10f]; - - //add to main window - [self.window.contentView addSubview:self.supportView]; - - //show - self.supportView.hidden = NO; - - //make 'yes!' button active - [self.window makeFirstResponder:self.supportButton]; - - //bail - goto bail; - } - - //'Yes' for support - // ->load supprt in URL - if(YES == [buttonTitle isEqualToString:ACTION_YES]) - { - //open URL - // ->invokes user's default browser - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:PATREON_URL]]; - - //close - [self.window close]; - - //bail - goto bail; - } - - //install/uninstall logic handlers - else - { - //hide 'get more info' button - self.moreInfoButton.hidden = YES; - - //set action - // ->install daemon - if(YES == [buttonTitle isEqualToString:ACTION_INSTALL]) - { - //set - action = ACTION_INSTALL_FLAG; - } - //set action - // ->uninstall daemon - else - { - //set - action = ACTION_UNINSTALL_FLAG; - } - - //disable 'x' button - // ->don't want user killing app during install/upgrade - [[self.window standardWindowButton:NSWindowCloseButton] setEnabled:NO]; - - //clear status msg - [self.statusMsg setStringValue:@""]; - - //force redraw of status msg - // ->sometime doesn't refresh (e.g. slow VM) - [self.statusMsg setNeedsDisplay:YES]; - - //invoke logic to install/uninstall - // ->do in background so UI doesn't block - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - //install/uninstall - [self lifeCycleEvent:action]; - }); - } - -//bail -bail: - - return; -} - -//button handler for '?' button (on an error) -// ->load objective-see's documentation for error(s) in default browser --(IBAction)info:(id)sender -{ - //url - NSURL *helpURL = nil; - - //build help URL - helpURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@#errors", PRODUCT_URL]]; - - //open URL - // ->invokes user's default browser - [[NSWorkspace sharedWorkspace] openURL:helpURL]; - - return; -} - -//perform install | uninstall via Control obj -// ->invoked on background thread so that UI doesn't block --(void)lifeCycleEvent:(NSUInteger)event -{ - //status var - BOOL status = NO; - - //configure object - Configure* configureObj = nil; - - //alloc control object - configureObj = [[Configure alloc] init]; - - //begin event - // ->updates ui on main thread - dispatch_sync(dispatch_get_main_queue(), - ^{ - //complete - [self beginEvent:event]; - }); - - //sleep - // ->allow 'install' || 'uninstall' msg to show up - [NSThread sleepForTimeInterval:1.0f]; - - //perform action (install | uninstall) - // ->perform background actions - if(YES == [configureObj configure:event]) - { - //set flag - status = YES; - } - - //error occurred - else - { - //err msg - //logMsg(LOG_ERR, @"ERROR: failed to perform life cycle event"); - - //set flag - status = NO; - } - - //complet event - // ->updates ui on main thread - dispatch_async(dispatch_get_main_queue(), - ^{ - //complete - [self completeEvent:status event:event]; - }); - - return; -} - -//begin event -// ->basically just update UI --(void)beginEvent:(NSUInteger)event -{ - //align text left - [self.statusMsg setAlignment:NSLeftTextAlignment]; - - //install msg - if(ACTION_INSTALL_FLAG == event) - { - //update status msg - // ->with space to avoid spinner - [self.statusMsg setStringValue:@"\t Installing..."]; - } - //uninstall msg - else - { - //update status msg - // ->with space to avoid spinner - [self.statusMsg setStringValue:@"\t Uninstalling..."]; - } - - //disable action button - self.uninstallButton.enabled = NO; - - //disable cancel button - self.installButton.enabled = NO; - - //show spinner - [self.activityIndicator setHidden:NO]; - - //start spinner - [self.activityIndicator startAnimation:nil]; - - return; -} - -//complete event -// ->update UI after background event has finished --(void)completeEvent:(BOOL)success event:(NSUInteger)event -{ - //action - NSString* action = nil; - - //result msg - NSMutableString* resultMsg = nil; - - //msg font - NSColor* resultMsgColor = nil; - - //generally want centered text - [self.statusMsg setAlignment:NSCenterTextAlignment]; - - //set action msg for install - if(ACTION_INSTALL_FLAG == event) - { - //set msg - action = @"install"; - } - //set action msg for uninstall - else - { - //set msg - action = @"uninstall"; - } - - //success - if(YES == success) - { - //set result msg - resultMsg = [NSMutableString stringWithFormat:@"OverSight %@ed!", action]; - - //add extra info when installed - if(ACTION_INSTALL_FLAG == event) - { - //append - [resultMsg appendString:@"\nconfig via ☔️ in status bar"]; - } - - //set font to black - resultMsgColor = [NSColor blackColor]; - } - //failure - else - { - //set result msg - resultMsg = [NSMutableString stringWithFormat:@"error: %@ failed", action]; - - //set font to red - resultMsgColor = [NSColor redColor]; - - //show 'get more info' button - self.moreInfoButton.hidden = NO; - } - - //stop/hide spinner - [self.activityIndicator stopAnimation:nil]; - - //hide spinner - [self.activityIndicator setHidden:YES]; - - //set font to bold - [self.statusMsg setFont:[NSFont fontWithName:@"Menlo-Bold" size:13]]; - - //set msg color - [self.statusMsg setTextColor:resultMsgColor]; - - //set status msg - [self.statusMsg setStringValue:resultMsg]; - - //update button - // ->after install change butter to 'Next' - if(ACTION_INSTALL_FLAG == event) - { - //set button title to 'close' - self.installButton.title = ACTION_NEXT; - - //enable - self.installButton.enabled = YES; - - //make it active - [self.window makeFirstResponder:self.installButton]; - } - //update button - // ->after uninstall change butter to 'close' - else - { - //set button title to 'close' - self.uninstallButton.title = ACTION_CLOSE; - - //enable - self.uninstallButton.enabled = YES; - - //make it active - [self.window makeFirstResponder:self.uninstallButton]; - } - - //ok to re-enable 'x' button - [[self.window standardWindowButton:NSWindowCloseButton] setEnabled:YES]; - - //(re)make window window key - [self.window makeKeyAndOrderFront:self]; - - //(re)make window front - [NSApp activateIgnoringOtherApps:YES]; - - return; -} - -//automatically invoked when window is closing -// ->just exit application --(void)windowWillClose:(NSNotification *)notification -{ - //exit - [NSApp terminate:self]; - - return; -} - - -@end diff --git a/Installer/ConfigureWindowController.xib b/Installer/ConfigureWindowController.xib deleted file mode 100644 index ce86fdb..0000000 --- a/Installer/ConfigureWindowController.xib +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Installer/ErrorWindowController.h b/Installer/ErrorWindowController.h deleted file mode 100644 index 0633b8b..0000000 --- a/Installer/ErrorWindowController.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// ErrorWindowController.h -// OverSight -// -// Created by Patrick Wardle on 7/15/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -@interface ErrorWindowController : NSWindowController -{ - -} - -//main msg in window -@property (weak) IBOutlet NSTextField *errMsg; - -//sub msg in window -@property (weak) IBOutlet NSTextField *errSubMsg; - -//info/help/fix button -@property (weak) IBOutlet NSButton *infoButton; - -//close button -@property (weak) IBOutlet NSButton *closeButton; - -//(optional) url for 'Info' button -@property(nonatomic, retain) NSURL* errorURL; - -//flag indicating close button should exit app -@property BOOL shouldExit; - -/* METHODS */ - -//configure the object/window --(void)configure:(NSDictionary*)errorInfo; - -//display (show) window --(void)display; - -@end diff --git a/Installer/ErrorWindowController.m b/Installer/ErrorWindowController.m deleted file mode 100644 index 652bb39..0000000 --- a/Installer/ErrorWindowController.m +++ /dev/null @@ -1,153 +0,0 @@ -// -// ErrorWindowController.m -// OverSight -// -// Created by Patrick Wardle on 7/15/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "ErrorWindowController.h" - -@interface ErrorWindowController () - -@end - -@implementation ErrorWindowController - -@synthesize errorURL; -@synthesize shouldExit; -@synthesize closeButton; - -//automatically called when nib is loaded -// ->center window --(void)awakeFromNib -{ - //center - [self.window center]; - - return; -} - -//configure the object/window --(void)configure:(NSDictionary*)errorInfo -{ - //set error msg - self.errMsg.stringValue = errorInfo[KEY_ERROR_MSG]; - - //set error sub msg - self.errSubMsg.stringValue = errorInfo[KEY_ERROR_SUB_MSG]; - - //save exit - self.shouldExit = [errorInfo[KEY_ERROR_SHOULD_EXIT] boolValue]; - - //grab optional error url - if(nil != errorInfo[KEY_ERROR_URL]) - { - //extract/convert - self.errorURL = [NSURL URLWithString:errorInfo[KEY_ERROR_URL]]; - } - - //when exiting - // ->change 'close' to 'exit' - if(YES == self.shouldExit) - { - //change title - self.closeButton.title = @"Exit"; - } - - //for fatal errors - // ->change 'Info' to 'help fix' - if(YES == [[self.errorURL absoluteString] isEqualToString:FATAL_ERROR_URL]) - { - //change title - self.infoButton.title = @"Help Fix"; - } - - //set delegate - [self.window setDelegate:self]; - - return; -} - -//display (show) window --(void)display -{ - //show (now configured), alert - [self showWindow:self]; - - //make it key window - [self.window makeKeyAndOrderFront:self]; - - //make window front - [NSApp activateIgnoringOtherApps:YES]; - - //make 'close' have focus - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.25 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - - //make close button active - [self.window makeFirstResponder:closeButton]; - - }); - - //make white - [self.window setBackgroundColor: NSColor.whiteColor]; - - return; -} - -//invoked when user clicks '?' (help button) -// ->open url with more info about the error(s) --(IBAction)help:(id)sender -{ - //if a url was specified - // ->use that one - if(nil != self.errorURL) - { - //open URL - // ->invokes user's default browser - [[NSWorkspace sharedWorkspace] openURL:self.errorURL]; - } - //use default URL - else - { - //open URL - // ->invokes user's default browser - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ERRORS_URL]]; - } - - return; -} - -//invoked when user clicks 'close' -// ->just close window --(IBAction)close:(id)sender -{ - //close - [self.window close]; - - return; -} - -//automatically invoked when window is closing -// ->exit the app if specified... --(void)windowWillClose:(NSNotification *)notification -{ - //check if should exit process - // ->e.g. an error during install, etc - if(YES == self.shouldExit) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"exiting application"); - #endif - - //exit - [NSApp terminate:self]; - } - - return; -} - -@end diff --git a/Installer/ErrorWindowController.xib b/Installer/ErrorWindowController.xib deleted file mode 100644 index 543be10..0000000 --- a/Installer/ErrorWindowController.xib +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Installer/Helper/Helper.m b/Installer/Helper/Helper.m new file mode 100644 index 0000000..ecdd175 --- /dev/null +++ b/Installer/Helper/Helper.m @@ -0,0 +1,69 @@ +// +// file: Helper.m +// project: (open-source) installer +// description: main/entry point of daemon +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import OSLog; +@import Foundation; + +#import "consts.h" +#import "XPCProtocol.h" +#import "HelperListener.h" +#import "HelperInterface.h" + +/* GLOBALS */ + +//log handle +os_log_t logHandle = nil; + +//helper daemon entry point +// create XPC listener object and then just wait +int main(int argc, const char * argv[]) +{ + //pragmas + #pragma unused(argc) + #pragma unused(argv) + + //status + int status = -1; + + //init log + logHandle = os_log_create(BUNDLE_ID, "installer (helper)"); + + //pool + @autoreleasepool + { + //helper listener (XPC) obj + HelperListener* helperListener = nil; + + //alloc/init helper listener XPC obj + helperListener = [[HelperListener alloc] init]; + if(nil == helperListener) + { + //err msg + os_log_error(logHandle, "ERROR: failed to initialize user comms XPC listener"); + + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "listening for client XPC connections..."); + + //run loop + [[NSRunLoop currentRunLoop] run]; + + } //pool + + //happy + // though not sure how we'll ever get here? + status = 0; + +bail: + + return status; +} diff --git a/Installer/Helper/HelperInterface.h b/Installer/Helper/HelperInterface.h new file mode 100644 index 0000000..75c3b0b --- /dev/null +++ b/Installer/Helper/HelperInterface.h @@ -0,0 +1,20 @@ +// +// file: HelperInterface.h +// project: (open-source) installer +// description: interface for app installer comms (header) +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import Foundation; + +#import "XPCProtocol.h" +#import "HelperInterface.h" + +@interface HelperInterface : NSObject +{ + +} + +@end diff --git a/Installer/Helper/HelperInterface.m b/Installer/Helper/HelperInterface.m new file mode 100644 index 0000000..4fc8e5c --- /dev/null +++ b/Installer/Helper/HelperInterface.m @@ -0,0 +1,352 @@ +// +// file: HelperInterface.m +// project: (open-source) installer +// description: interface for app installer comms +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import OSLog; + +#import "consts.h" +#import "utilities.h" +#import "HelperInterface.h" + +#import + +//script name +#define CONF_SCRIPT @"configure.sh" + +//signing auth +#define SIGNING_AUTH @"Developer ID Application: Objective-See, LLC (VBG97UB4TA)" + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +//dispatch source for SIGTERM +dispatch_source_t dispatchSource = nil; + +@implementation HelperInterface + +//uninstall +// do uninstall logic and return result +-(void)uninstall:(NSString*)app prefs:(NSString*)prefs reply:(void (^)(NSNumber*))reply; +{ + //response + BOOL response = NO; + + //dbg msg + os_log_debug(logHandle, "XPC-request: uninstall"); + + //configure + // pass in 'uninstall' flag + if(0 == [self configure:app arguments:@[CMD_UNINSTALL, prefs]]) + { + //happy + response = YES; + } + + //reply to client + reply([NSNumber numberWithBool:response]); + + return; +} + +//configure +// install or uninstall +-(int)configure:(NSString*)app arguments:(NSArray*)args +{ + //result + int result = -1; + + //valdiated (copy) of app + NSString* validatedApp = nil; + + //validate app + validatedApp = [self validateApp:app]; + if(nil == validatedApp) + { + //err msg + os_log_error(logHandle, "ERROR: failed to validate copy of app"); + + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "validated %@", app); + + //exec script + result = [self execScript:validatedApp arguments:args]; + if(noErr != result) + { + //err msg + os_log_error(logHandle, "ERROR: failed to execute config script %@ (%d)", CONF_SCRIPT, result); + + //bail + goto bail; + } + + //happy + result = 0; + +bail: + + //always try to remove validated app + if(YES != [[NSFileManager defaultManager] removeItemAtPath:validatedApp error:nil]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to remove validated app %@", validatedApp); + + //set err + result = -1; + } + + return result; +} + +//cleanup by removing self +// since system install/launches us as root, client can't directly remove us +-(void)cleanup:(void (^)(NSNumber*))reply +{ + //response + __block BOOL response = NO; + + //flag + __block BOOL noErrors = YES; + + //helper plist + __block NSString* helperPlist = nil; + + //binary + __block NSString* helperBinary = nil; + + //error + __block NSError* error = nil; + + //dbg msg + os_log_debug(logHandle, "XPC-request: cleanup (removing self)"); + + //ignore sigterm + // handling it via GCD dispatch + signal(SIGTERM, SIG_IGN); + + //init dispatch source for SIGTERM + dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, dispatch_get_main_queue()); + + //set handler + // deletes plist and self + dispatch_source_set_event_handler(dispatchSource, ^{ + + //dbg msg + os_log_debug(logHandle, "XPC: got SIGTERM, deleting plist & self!"); + + //init path to plist + helperPlist = [@"/Library/LaunchDaemons" stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.plist", CONFIG_HELPER_ID]]; + + //delete plist + if(YES != [[NSFileManager defaultManager] removeItemAtPath:helperPlist error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to delete %@ (%@)", helperPlist, error.description); + + //set error + noErrors = NO; + } + + //init path to binary + helperBinary = [@"/Library/PrivilegedHelperTools" stringByAppendingPathComponent:CONFIG_HELPER_ID]; + + //delete self + if(YES != [[NSFileManager defaultManager] removeItemAtPath:helperBinary error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to delete %@ (%@)", helperBinary, error.description); + + //set error + noErrors = NO; + } + + //no errors? + // display dbg msg + if(YES == noErrors) + { + //happy + response = YES; + + //dbg msg + os_log_debug(logHandle, "removed %@ and %@", helperPlist, helperBinary); + } + + //reply to client + reply([NSNumber numberWithBool:response]); + + //bye! + exit(SIGTERM); + + }); + + //resume + dispatch_resume(dispatchSource); + + //init path to plist + helperPlist = [@"/Library/LaunchDaemons" stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.plist", CONFIG_HELPER_ID]]; + + //unload + // will trigger sigterm + execTask(LAUNCHCTL, @[@"unload", helperPlist], YES, NO); + + return; +} + +//make copy of app and validate +-(NSString*)validateApp:(NSString*)app +{ + //copy of app + NSString* appCopy = nil; + + //file manager + NSFileManager* defaultManager = nil; + + //path to (now) validated app + NSString* validatedApp = nil; + + //error + NSError* error = nil; + + //grab default file manager + defaultManager = [NSFileManager defaultManager]; + + //init path to app copy + // *root-owned* tmp directory + appCopy = [NSTemporaryDirectory() stringByAppendingPathComponent:app.lastPathComponent]; + + //dbg msg + os_log_debug(logHandle, "validating %@", appCopy); + + //delete if old copy is there + if(YES == [defaultManager fileExistsAtPath:appCopy]) + { + //delete + if(YES != [defaultManager removeItemAtPath:appCopy error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to delete %@ (error: %@)", appCopy, error.description); + } + } + + //copy app bundle to *root-owned* directory + if(YES != [defaultManager copyItemAtPath:app toPath:appCopy error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to copy %@ to %@ (error: %@)", app, appCopy, error.description); + + //bail + goto bail; + } + + //set group/owner to root/wheel + if(YES != setFileOwner(appCopy, @0, @0, YES)) + { + //err msg + os_log_error(logHandle, "ERROR: failed to set %@ to be owned by root", appCopy); + + //bail + goto bail; + } + + //verify app + // make sure it's signed, and by our signing auth + if(noErr != verifyApp(appCopy, SIGNING_AUTH)) + { + //err msg + os_log_error(logHandle, "ERROR: failed to validate %@", appCopy); + + //bail + goto bail; + } + + //happy + validatedApp = appCopy; + +bail: + + return validatedApp; +} + +//execute config script +-(int)execScript:(NSString*)validatedApp arguments:(NSArray*)arguments +{ + //result + int result = -1; + + //results + NSDictionary* results = nil; + + //script + NSString* script = nil; + + //app bundle + NSBundle* appBundle = nil; + + //file manager + NSFileManager* fileManager = nil; + + //current working directory + NSString* currentWorkingDir = nil; + + //init file manager + fileManager = [NSFileManager defaultManager]; + + //load app bundle + appBundle = [NSBundle bundleWithPath:validatedApp]; + if(nil == appBundle) + { + //err msg + os_log_error(logHandle, "ERROR: failed to load app bundle for %@", validatedApp); + + //bail + goto bail; + } + + //get path to config script + script = [[appBundle resourcePath] stringByAppendingPathComponent:CONF_SCRIPT]; + if(nil == script) + { + //err msg + os_log_error(logHandle, "ERROR: failed to find config script %@", CONF_SCRIPT); + + //bail + goto bail; + } + + //get current working directory + currentWorkingDir = [fileManager currentDirectoryPath]; + + //change working directory + // set it to (validated) app path's resources + [fileManager changeCurrentDirectoryPath:[NSString stringWithFormat:@"%@/Contents/Resources/", validatedApp]]; + + //exec script + // wait, but don't grab output + results = execTask(script, arguments, YES, NO); + + //exit code? + if(nil != results[EXIT_CODE]) + { + //grab + result = [results[EXIT_CODE] intValue]; + } + + //(re)set current working directory + [fileManager changeCurrentDirectoryPath:currentWorkingDir]; + +bail: + + return result; +} + +@end diff --git a/Installer/Helper/HelperListener.h b/Installer/Helper/HelperListener.h new file mode 100644 index 0000000..db96167 --- /dev/null +++ b/Installer/Helper/HelperListener.h @@ -0,0 +1,37 @@ +// +// file: HelperListener.h +// project: (open-source) installer +// description: XPC listener for connections for user components (header) +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import Foundation; + +#import "HelperInterface.h" + +//function def +OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement); + +@interface HelperListener : NSObject +{ + +} + +/* PROPERTIES */ + +//XPC listener obj +@property(nonatomic, retain)NSXPCListener* listener; + +/* METHODS */ + +//setup XPC listener +-(BOOL)initListener; + +//automatically invoked +// allows NSXPCListener to configure/accept/resume a new incoming NSXPCConnection +// note: we only allow binaries signed by Objective-See to talk to this to be extra safe :) +-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection; + +@end diff --git a/Installer/Helper/HelperListener.m b/Installer/Helper/HelperListener.m new file mode 100644 index 0000000..ca8a1c8 --- /dev/null +++ b/Installer/Helper/HelperListener.m @@ -0,0 +1,271 @@ +// +// file: HelperListener.m +// project: (open-source) installer +// description: XPC listener for connections for user components +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import OSLog; +@import Foundation; + +#import "consts.h" +#import "XPCProtocol.h" +#import "HelperListener.h" +#import "HelperInterface.h" + +#import +#import +#import + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +//interface for 'extension' to NSXPCConnection +// allows us to access the 'private' auditToken iVar +@interface ExtendedNSXPCConnection : NSXPCConnection + +//private iVar +@property (nonatomic) audit_token_t auditToken; + +@end + +//implementation for 'extension' to NSXPCConnection +// allows us to access the 'private' auditToken iVar +@implementation ExtendedNSXPCConnection + +//private iVar +@synthesize auditToken; + +@end + + + +@implementation HelperListener + + +@synthesize listener; + +//init +// create XPC listener +-(id)init +{ + //init super + self = [super init]; + if(nil != self) + { + //setup XPC listener + if(YES != [self initListener]) + { + //unset + self = nil; + + //bail + goto bail; + } + } + +bail: + + return self; +} + +//setup XPC listener +-(BOOL)initListener +{ + //result + BOOL result = NO; + + //init listener + listener = [[NSXPCListener alloc] initWithMachServiceName:CONFIG_HELPER_ID]; + if(nil == self.listener) + { + //err msg + os_log_error(logHandle, "ERROR: failed to create mach service %@", CONFIG_HELPER_ID); + + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "created mach service %@", CONFIG_HELPER_ID); + + //set delegate + self.listener.delegate = self; + + //ready to accept connections + [self.listener resume]; + + //happy + result = YES; + +bail: + + return result; +} + +#pragma mark - +#pragma mark NSXPCConnection method overrides + +//automatically invoked +// allows NSXPCListener to configure/accept/resume a new incoming NSXPCConnection +// shoutout to: https://blog.obdev.at/what-we-have-learned-from-a-vulnerability/ +-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection +{ + //pragma + #pragma unused(listener) + + //flag + BOOL shouldAccept = NO; + + //status + OSStatus status = !errSecSuccess; + + //audit token + audit_token_t auditToken = {0}; + + //task ref + SecTaskRef taskRef = 0; + + //code ref + SecCodeRef codeRef = NULL; + + //code signing info + CFDictionaryRef csInfo = NULL; + + //cs flags + uint32_t csFlags = 0; + + //signing req string (main app) + NSString* requirement = nil; + + //extract audit token + auditToken = ((ExtendedNSXPCConnection*)newConnection).auditToken; + + //dbg msg + os_log_debug(logHandle, "received request to connect to XPC interface"); + + //obtain dynamic code ref + status = SecCodeCopyGuestWithAttributes(NULL, (__bridge CFDictionaryRef _Nullable)(@{(__bridge NSString *)kSecGuestAttributeAudit : [NSData dataWithBytes:&auditToken length:sizeof(audit_token_t)]}), kSecCSDefaultFlags, &codeRef); + if(errSecSuccess != status) + { + //err msg + os_log_error(logHandle, "ERROR: 'SecCodeCopyGuestWithAttributes' failed with': %#x", status); + + //bail + goto bail; + } + + //validate code + status = SecCodeCheckValidity(codeRef, kSecCSDefaultFlags, NULL); + if(errSecSuccess != status) + { + //err msg + os_log_error(logHandle, "ERROR: 'SecCodeCheckValidity' failed with': %#x", status); + + //bail + goto bail; + } + + //get code signing info + status = SecCodeCopySigningInformation(codeRef, kSecCSDynamicInformation, &csInfo); + if(errSecSuccess != status) + { + //err msg + os_log_error(logHandle, "ERROR: 'SecCodeCopySigningInformation' failed with': %#x", status); + + //bail + goto bail; + } + + //extract flags + csFlags = [((__bridge NSDictionary *)csInfo)[(__bridge NSString *)kSecCodeInfoStatus] unsignedIntValue]; + + //dbg msg + os_log_debug(logHandle, "code signing flags: %#x", csFlags); + + //gotta have hardened runtime + if( !(CS_VALID & csFlags) && + !(CS_RUNTIME & csFlags) ) + { + //err msg + os_log_error(logHandle, "ERROR: invalid code signing flags: %#x", csFlags); + + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "code signing flags, ok ('CS_RUNTIME' is set)"); + + //init signing req + requirement = [NSString stringWithFormat:@"anchor apple generic and identifier \"%@\" and certificate leaf [subject.CN] = \"%@\"", INSTALLER_ID, SIGNING_AUTH]; + + //step 1: create task ref + // uses NSXPCConnection's (private) 'auditToken' iVar + taskRef = SecTaskCreateWithAuditToken(NULL, ((ExtendedNSXPCConnection*)newConnection).auditToken); + if(NULL == taskRef) + { + //bail + goto bail; + } + + //step 2: validate + // check that client is signed with Objective-See's dev cert and it's the BB's installer + if(0 != SecTaskValidateForRequirement(taskRef, (__bridge CFStringRef)(requirement))) + { + //err msg + os_log_error(logHandle, "ERROR: failed to validate against %@", requirement); + + //bail + goto bail; + } + + //set the interface that the exported object implements + newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; + + //set object exported by connection + newConnection.exportedObject = [[HelperInterface alloc] init]; + + //resume + [newConnection resume]; + + //dbg msg + os_log_debug(logHandle, "allowed XPC connection: %@", newConnection); + + //happy + shouldAccept = YES; + +bail: + + //release task ref object + if(NULL != taskRef) + { + //release + CFRelease(taskRef); + taskRef = NULL; + } + + //free cs info + if(NULL != csInfo) + { + //free + CFRelease(csInfo); + csInfo = NULL; + } + + //free code ref + if(NULL != codeRef) + { + //free + CFRelease(codeRef); + codeRef = NULL; + } + + return shouldAccept; +} + +@end diff --git a/Installer/Helper/Info.plist b/Installer/Helper/Info.plist new file mode 100644 index 0000000..4157cc1 --- /dev/null +++ b/Installer/Helper/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleIdentifier + com.objective-see.oversight.uninstallHelper + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + helper + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright (c) 2020 Objective-See. All rights reserved. + SMAuthorizedClients + + anchor apple generic and identifier "com.objective-see.oversight.installer" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = VBG97UB4TA) + + + diff --git a/Installer/Helper/Launchd.plist b/Installer/Helper/Launchd.plist new file mode 100644 index 0000000..120826f --- /dev/null +++ b/Installer/Helper/Launchd.plist @@ -0,0 +1,15 @@ + + + + + Label + com.objective-see.oversight.uninstallHelper + MachServices + + com.objective-see.oversight.uninstallHelper + + + EnableTransactions + + + diff --git a/Installer/Info.plist b/Installer/Info.plist deleted file mode 100644 index d30ce7b..0000000 --- a/Installer/Info.plist +++ /dev/null @@ -1,34 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(PRODUCT_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.2.0 - CFBundleSignature - ???? - CFBundleVersion - 1.2.0 - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - Copyright (c) 2017 Objective-See. All rights reserved. - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/Installer/Installer.xcodeproj/project.pbxproj b/Installer/Installer.xcodeproj/project.pbxproj new file mode 100644 index 0000000..84d6be1 --- /dev/null +++ b/Installer/Installer.xcodeproj/project.pbxproj @@ -0,0 +1,636 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 4BE4905110445D49006BE471 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4904D10445D49006BE471 /* main.m */; }; + 4BE4906110445E13006BE471 /* Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4905510445DDD006BE471 /* Helper.m */; }; + 4BE4906410445F2F006BE471 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BE4906310445F2F006BE471 /* Security.framework */; }; + 4BE49092104463A0006BE471 /* com.objective-see.oversight.uninstallHelper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4BE4905D10445E0A006BE471 /* com.objective-see.oversight.uninstallHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; + BF04235611C0531400431286 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BF04235511C0531400431286 /* AppDelegate.m */; }; + BF65C19111B985C0007C20AB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF65C19011B985C0007C20AB /* MainMenu.xib */; }; + CD17D54E20104EE700F798D7 /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD17D54420104EE700F798D7 /* AboutWindow.xib */; }; + CD17D54F20104EE700F798D7 /* Configure.m in Sources */ = {isa = PBXBuildFile; fileRef = CD17D54720104EE700F798D7 /* Configure.m */; }; + CD17D55020104EE700F798D7 /* ConfigureWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CD17D54820104EE700F798D7 /* ConfigureWindowController.m */; }; + CD17D55120104EE700F798D7 /* ConfigureWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD17D54920104EE700F798D7 /* ConfigureWindowController.xib */; }; + CD17D55320104EE700F798D7 /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CD17D54B20104EE700F798D7 /* AboutWindowController.m */; }; + CD2F801124465F68009C3D77 /* patrons.txt in Resources */ = {isa = PBXBuildFile; fileRef = CD2F801024465F68009C3D77 /* patrons.txt */; }; + CD3307AE203567D500F10D71 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD3307AD203567D400F10D71 /* Assets.xcassets */; }; + CD73DA912004629D001FFC84 /* HelperInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CD73DA902004629D001FFC84 /* HelperInterface.m */; }; + CD73DA942004633A001FFC84 /* HelperListener.m in Sources */ = {isa = PBXBuildFile; fileRef = CD73DA9220046339001FFC84 /* HelperListener.m */; }; + CD73DA9520046548001FFC84 /* HelperInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CD73DA902004629D001FFC84 /* HelperInterface.m */; }; + CD73DA9620046548001FFC84 /* HelperListener.m in Sources */ = {isa = PBXBuildFile; fileRef = CD73DA9220046339001FFC84 /* HelperListener.m */; }; + CD73DA99200490B4001FFC84 /* HelperComms.m in Sources */ = {isa = PBXBuildFile; fileRef = CD73DA97200490B4001FFC84 /* HelperComms.m */; }; + CD86B6EA23A5BBE7003F6BA4 /* configure.sh in Resources */ = {isa = PBXBuildFile; fileRef = CD86B6E923A5BBE7003F6BA4 /* configure.sh */; }; + CDA88A752537CDE100C469BF /* Sentry.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CD6B484220AD323E00A9BE71 /* Sentry.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + CDAAC241202255580032F2E6 /* utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CDAAC23F202255580032F2E6 /* utilities.m */; }; + CDAAC243202257D70032F2E6 /* utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CDAAC23F202255580032F2E6 /* utilities.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 4BE4907E1044624F006BE471 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4BE4905C10445E0A006BE471; + remoteInfo = SMJobBlessHelper; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 4BE49098104463C5006BE471 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = Contents/Library/LaunchServices; + dstSubfolderSpec = 1; + files = ( + 4BE49092104463A0006BE471 /* com.objective-see.oversight.uninstallHelper in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CDA88A722537CD7700C469BF /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + CDA88A752537CDE100C469BF /* Sentry.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; + 4BE4904C10445D49006BE471 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4BE4904D10445D49006BE471 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 4BE4905310445DDD006BE471 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4BE4905410445DDD006BE471 /* Launchd.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Launchd.plist; sourceTree = ""; }; + 4BE4905510445DDD006BE471 /* Helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Helper.m; sourceTree = ""; }; + 4BE4905D10445E0A006BE471 /* com.objective-see.oversight.uninstallHelper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "com.objective-see.oversight.uninstallHelper"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4BE4906310445F2F006BE471 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 4BE4906710445F36006BE471 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; + 8D1107320486CEB800E47090 /* OverSight Installer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "OverSight Installer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + BF04235411C0531400431286 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + BF04235511C0531400431286 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + BF65C19011B985C0007C20AB /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; + CD17D54220104EE600F798D7 /* AboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutWindowController.h; sourceTree = ""; }; + CD17D54420104EE700F798D7 /* AboutWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutWindow.xib; sourceTree = ""; }; + CD17D54520104EE700F798D7 /* Configure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Configure.h; sourceTree = ""; }; + CD17D54620104EE700F798D7 /* ConfigureWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConfigureWindowController.h; sourceTree = ""; }; + CD17D54720104EE700F798D7 /* Configure.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Configure.m; sourceTree = ""; }; + CD17D54820104EE700F798D7 /* ConfigureWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConfigureWindowController.m; sourceTree = ""; }; + CD17D54920104EE700F798D7 /* ConfigureWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ConfigureWindowController.xib; sourceTree = ""; }; + CD17D54B20104EE700F798D7 /* AboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutWindowController.m; sourceTree = ""; }; + CD18D64C200DA301005609F9 /* XPCProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XPCProtocol.h; path = Shared/XPCProtocol.h; sourceTree = SOURCE_ROOT; }; + CD2F801024465F68009C3D77 /* patrons.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = patrons.txt; path = ../../Shared/patrons.txt; sourceTree = ""; }; + CD2F8012244670B9009C3D77 /* libbsm.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbsm.tbd; path = usr/lib/libbsm.tbd; sourceTree = SDKROOT; }; + CD3307AD203567D400F10D71 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + CD6B484220AD323E00A9BE71 /* Sentry.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sentry.framework; path = ../Carthage/Build/Mac/Sentry.framework; sourceTree = ""; }; + CD6CAC9120A42D5000188B0A /* main.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = main.h; sourceTree = ""; }; + CD73DA8F2004629D001FFC84 /* HelperInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelperInterface.h; sourceTree = ""; }; + CD73DA902004629D001FFC84 /* HelperInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HelperInterface.m; sourceTree = ""; }; + CD73DA9220046339001FFC84 /* HelperListener.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HelperListener.m; sourceTree = ""; }; + CD73DA932004633A001FFC84 /* HelperListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelperListener.h; sourceTree = ""; }; + CD73DA97200490B4001FFC84 /* HelperComms.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HelperComms.m; sourceTree = ""; }; + CD73DA98200490B4001FFC84 /* HelperComms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelperComms.h; sourceTree = ""; }; + CD86B6E923A5BBE7003F6BA4 /* configure.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = configure.sh; path = Source/Script/configure.sh; sourceTree = SOURCE_ROOT; }; + CDA6E62E203B67A100C78F91 /* consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = consts.h; path = ../../Shared/consts.h; sourceTree = ""; }; + CDA6E632203B6B7800C78F91 /* objectiveSeeText.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = objectiveSeeText.png; path = ../../shared/images/objectiveSeeText.png; sourceTree = ""; }; + CDA6E634203B6B7900C78F91 /* objectiveSeeLogo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = objectiveSeeLogo.png; path = ../../shared/images/objectiveSeeLogo.png; sourceTree = ""; }; + CDAAC23F202255580032F2E6 /* utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = utilities.m; path = ../../Shared/utilities.m; sourceTree = ""; }; + CDAAC240202255580032F2E6 /* utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = utilities.h; path = ../../Shared/utilities.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8D11072E0486CEB800E47090 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, + 4BE4906410445F2F006BE471 /* Security.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8D1107320486CEB800E47090 /* OverSight Installer.app */, + 4BE4905D10445E0A006BE471 /* com.objective-see.oversight.uninstallHelper */, + ); + name = Products; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* SMJobBless */ = { + isa = PBXGroup; + children = ( + CD17D55C201148F900F798D7 /* Script */, + CD73DA9C20049158001FFC84 /* Shared */, + 4BE4904710445D49006BE471 /* Source */, + 4BE4905210445DDD006BE471 /* Helper */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = SMJobBless; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + CD2F8012244670B9009C3D77 /* libbsm.tbd */, + CD6B484220AD323E00A9BE71 /* Sentry.framework */, + 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, + 4BE4906310445F2F006BE471 /* Security.framework */, + 4BE4906710445F36006BE471 /* ServiceManagement.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 4BE4904710445D49006BE471 /* Source */ = { + isa = PBXGroup; + children = ( + CD3307AD203567D400F10D71 /* Assets.xcassets */, + CD17D54420104EE700F798D7 /* AboutWindow.xib */, + CD17D54220104EE600F798D7 /* AboutWindowController.h */, + CD17D54B20104EE700F798D7 /* AboutWindowController.m */, + CD17D54520104EE700F798D7 /* Configure.h */, + CD17D54720104EE700F798D7 /* Configure.m */, + CD17D54620104EE700F798D7 /* ConfigureWindowController.h */, + CD17D54820104EE700F798D7 /* ConfigureWindowController.m */, + CD17D54920104EE700F798D7 /* ConfigureWindowController.xib */, + CD73DA98200490B4001FFC84 /* HelperComms.h */, + CD73DA97200490B4001FFC84 /* HelperComms.m */, + 4BE4904C10445D49006BE471 /* Info.plist */, + BF65C19011B985C0007C20AB /* MainMenu.xib */, + CD6CAC9120A42D5000188B0A /* main.h */, + 4BE4904D10445D49006BE471 /* main.m */, + BF04235411C0531400431286 /* AppDelegate.h */, + BF04235511C0531400431286 /* AppDelegate.m */, + ); + path = Source; + sourceTree = ""; + }; + 4BE4905210445DDD006BE471 /* Helper */ = { + isa = PBXGroup; + children = ( + 4BE4905310445DDD006BE471 /* Info.plist */, + 4BE4905410445DDD006BE471 /* Launchd.plist */, + 4BE4905510445DDD006BE471 /* Helper.m */, + CD73DA8F2004629D001FFC84 /* HelperInterface.h */, + CD73DA902004629D001FFC84 /* HelperInterface.m */, + CD73DA932004633A001FFC84 /* HelperListener.h */, + CD73DA9220046339001FFC84 /* HelperListener.m */, + ); + path = Helper; + sourceTree = ""; + }; + CD17D55C201148F900F798D7 /* Script */ = { + isa = PBXGroup; + children = ( + CD86B6E923A5BBE7003F6BA4 /* configure.sh */, + ); + name = Script; + path = Configure/Script; + sourceTree = ""; + }; + CD73DA9C20049158001FFC84 /* Shared */ = { + isa = PBXGroup; + children = ( + CDAAC24620226D400032F2E6 /* Images */, + CDA6E62E203B67A100C78F91 /* consts.h */, + CDAAC240202255580032F2E6 /* utilities.h */, + CDAAC23F202255580032F2E6 /* utilities.m */, + CD18D64C200DA301005609F9 /* XPCProtocol.h */, + CD2F801024465F68009C3D77 /* patrons.txt */, + ); + path = Shared; + sourceTree = ""; + }; + CDAAC24620226D400032F2E6 /* Images */ = { + isa = PBXGroup; + children = ( + CDA6E634203B6B7900C78F91 /* objectiveSeeLogo.png */, + CDA6E632203B6B7800C78F91 /* objectiveSeeText.png */, + ); + name = Images; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4BE4905C10445E0A006BE471 /* helper */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4BE4906210445E31006BE471 /* Build configuration list for PBXNativeTarget "helper" */; + buildPhases = ( + 4BE4905A10445E0A006BE471 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = helper; + productName = SMJobBlessHelper; + productReference = 4BE4905D10445E0A006BE471 /* com.objective-see.oversight.uninstallHelper */; + productType = "com.apple.product-type.tool"; + }; + 8D1107260486CEB800E47090 /* Installer */ = { + isa = PBXNativeTarget; + buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Installer" */; + buildPhases = ( + 8D1107290486CEB800E47090 /* Resources */, + 8D11072C0486CEB800E47090 /* Sources */, + 8D11072E0486CEB800E47090 /* Frameworks */, + 4BE49098104463C5006BE471 /* CopyFiles */, + CD17D55F20115E5E00F798D7 /* ShellScript */, + CDA88A722537CD7700C469BF /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 4BE4907F1044624F006BE471 /* PBXTargetDependency */, + ); + name = Installer; + productInstallPath = "$(HOME)/Applications"; + productName = SMJobBless; + productReference = 8D1107320486CEB800E47090 /* OverSight Installer.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1000; + TargetAttributes = { + 4BE4905C10445E0A006BE471 = { + DevelopmentTeam = VBG97UB4TA; + ProvisioningStyle = Manual; + }; + 8D1107260486CEB800E47090 = { + DevelopmentTeam = VBG97UB4TA; + ProvisioningStyle = Manual; + SystemCapabilities = { + com.apple.HardenedRuntime = { + enabled = 1; + }; + }; + }; + }; + }; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Installer" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + en, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* SMJobBless */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8D1107260486CEB800E47090 /* Installer */, + 4BE4905C10445E0A006BE471 /* helper */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8D1107290486CEB800E47090 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CD86B6EA23A5BBE7003F6BA4 /* configure.sh in Resources */, + BF65C19111B985C0007C20AB /* MainMenu.xib in Resources */, + CD17D54E20104EE700F798D7 /* AboutWindow.xib in Resources */, + CD2F801124465F68009C3D77 /* patrons.txt in Resources */, + CD3307AE203567D500F10D71 /* Assets.xcassets in Resources */, + CD17D55120104EE700F798D7 /* ConfigureWindowController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + CD17D55F20115E5E00F798D7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [[ $BUILT_PRODUCTS_DIR = *\"ArchiveIntermediates\"* ]]; then\n cp -R -f \"$PROJECT_TEMP_ROOT/UninstalledProducts/macosx/OverSight.app\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources\"\n \n#normal build\nelse\n\nrm -rf \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources/OverSight.app\"\n\n#cp daemon/app\ncp -R -f \"$BUILT_PRODUCTS_DIR/OverSight.app\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources\"\n\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4BE4905A10445E0A006BE471 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CDAAC243202257D70032F2E6 /* utilities.m in Sources */, + CD73DA9520046548001FFC84 /* HelperInterface.m in Sources */, + CD73DA9620046548001FFC84 /* HelperListener.m in Sources */, + 4BE4906110445E13006BE471 /* Helper.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8D11072C0486CEB800E47090 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4BE4905110445D49006BE471 /* main.m in Sources */, + CD73DA942004633A001FFC84 /* HelperListener.m in Sources */, + CD17D55320104EE700F798D7 /* AboutWindowController.m in Sources */, + CD73DA99200490B4001FFC84 /* HelperComms.m in Sources */, + CDAAC241202255580032F2E6 /* utilities.m in Sources */, + CD73DA912004629D001FFC84 /* HelperInterface.m in Sources */, + BF04235611C0531400431286 /* AppDelegate.m in Sources */, + CD17D54F20104EE700F798D7 /* Configure.m in Sources */, + CD17D55020104EE700F798D7 /* ConfigureWindowController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4BE4907F1044624F006BE471 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4BE4905C10445E0A006BE471 /* helper */; + targetProxy = 4BE4907E1044624F006BE471 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 4BE4905F10445E0B006BE471 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2.0.0; + DEVELOPMENT_TEAM = VBG97UB4TA; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; + INFOPLIST_FILE = Helper/Info.plist; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 2.0.0; + ONLY_ACTIVE_ARCH = NO; + OTHER_CODE_SIGN_FLAGS = ""; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + Helper/Info.plist, + "-sectcreate", + __TEXT, + __launchd_plist, + Helper/launchd.plist, + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight.uninstallHelper"; + PRODUCT_NAME = "com.objective-see.oversight.uninstallHelper"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 4BE4906010445E0B006BE471 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2.0.0; + DEVELOPMENT_TEAM = VBG97UB4TA; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; + INFOPLIST_FILE = Helper/Info.plist; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 2.0.0; + ONLY_ACTIVE_ARCH = NO; + OTHER_CODE_SIGN_FLAGS = ""; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + Helper/Info.plist, + "-sectcreate", + __TEXT, + __launchd_plist, + Helper/launchd.plist, + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight.uninstallHelper"; + PRODUCT_NAME = "com.objective-see.oversight.uninstallHelper"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + }; + name = Release; + }; + C01FCF4B08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 2.0.0; + DEVELOPMENT_TEAM = VBG97UB4TA; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; + INFOPLIST_FILE = Source/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST)) @executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 2.0.0; + OTHER_CODE_SIGN_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight.installer"; + PRODUCT_NAME = "OverSight Installer"; + PROVISIONING_PROFILE_SPECIFIER = ""; + }; + name = Debug; + }; + C01FCF4C08A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 2.0.0; + DEVELOPMENT_TEAM = VBG97UB4TA; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; + INFOPLIST_FILE = Source/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST)) @executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 2.0.0; + OTHER_CODE_SIGN_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight.installer"; + PRODUCT_NAME = "OverSight Installer"; + PROVISIONING_PROFILE_SPECIFIER = ""; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_PARAMETER = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + ONLY_ACTIVE_ARCH = NO; + PROVISIONING_PROFILE = ""; + RUN_CLANG_STATIC_ANALYZER = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_PARAMETER = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + ONLY_ACTIVE_ARCH = NO; + PROVISIONING_PROFILE = ""; + RUN_CLANG_STATIC_ANALYZER = YES; + SDKROOT = macosx; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4BE4906210445E31006BE471 /* Build configuration list for PBXNativeTarget "helper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4BE4905F10445E0B006BE471 /* Debug */, + 4BE4906010445E0B006BE471 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Installer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4B08A954540054247B /* Debug */, + C01FCF4C08A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Installer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/Installer/Installer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Installer/Installer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Installer/Installer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Installer/Installer.xcodeproj/xcshareddata/xcschemes/Installer Helper.xcscheme b/Installer/Installer.xcodeproj/xcshareddata/xcschemes/Installer Helper.xcscheme new file mode 100644 index 0000000..a4a9566 --- /dev/null +++ b/Installer/Installer.xcodeproj/xcshareddata/xcschemes/Installer Helper.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Installer/Installer.xcodeproj/xcshareddata/xcschemes/Installer.xcscheme b/Installer/Installer.xcodeproj/xcshareddata/xcschemes/Installer.xcscheme new file mode 100644 index 0000000..810a7d7 --- /dev/null +++ b/Installer/Installer.xcodeproj/xcshareddata/xcschemes/Installer.xcscheme @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Installer/Shared/XPCProtocol.h b/Installer/Shared/XPCProtocol.h new file mode 100644 index 0000000..b68ccf2 --- /dev/null +++ b/Installer/Shared/XPCProtocol.h @@ -0,0 +1,26 @@ +// +// file: XPCProtocol.h +// project: OverSight (shared) +// description: protocol for talking to the daemon +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +#ifndef userCommsInterface_h +#define userCommsInterface_h + +@import Foundation; + +@protocol XPCProtocol + +//uninstall +-(void)uninstall:(NSString*)app prefs:(NSString*)prefs reply:(void (^)(NSNumber*))reply; + +//cleanup +// remove self +-(void)cleanup:(void (^)(NSNumber*))reply; + +@end + +#endif diff --git a/Installer/Source/AboutWindow.xib b/Installer/Source/AboutWindow.xib new file mode 100644 index 0000000..860d94a --- /dev/null +++ b/Installer/Source/AboutWindow.xib @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Installer/Source/AboutWindowController.h b/Installer/Source/AboutWindowController.h new file mode 100644 index 0000000..2325312 --- /dev/null +++ b/Installer/Source/AboutWindowController.h @@ -0,0 +1,29 @@ +// +// file: AboutWindowController.h +// project: OverSight (config) +// description: about window display/controller (header) +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import Cocoa; + +@interface AboutWindowController : NSWindowController +{ + +} + +/* PROPERTIES */ + +//version label/string +@property (weak, atomic) IBOutlet NSTextField *versionLabel; + +//patrons +@property (unsafe_unretained, atomic) IBOutlet NSTextView *patrons; + +//'support us' button +@property (weak, atomic) IBOutlet NSButton *supportUs; + + +@end diff --git a/Installer/Source/AboutWindowController.m b/Installer/Source/AboutWindowController.m new file mode 100644 index 0000000..8df4467 --- /dev/null +++ b/Installer/Source/AboutWindowController.m @@ -0,0 +1,98 @@ +// +// file: AboutWindowController.m +// project: OverSight (config) +// description: about window display/controller +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +#import "consts.h" +#import "utilities.h" +#import "AboutWindowController.h" + +@implementation AboutWindowController + +@synthesize patrons; +@synthesize supportUs; +@synthesize versionLabel; + +//automatically called when nib is loaded +// center window +-(void)awakeFromNib +{ + //center + [self.window center]; +} + +//automatically invoked when window is loaded +// set to white +-(void)windowDidLoad +{ + //super + [super windowDidLoad]; + + //not in dark mode? + // make window white + if(YES != isDarkMode()) + { + //make white + self.window.backgroundColor = NSColor.whiteColor; + } + + //set version sting + self.versionLabel.stringValue = [NSString stringWithFormat:@"Version: %@", getAppVersion()]; + + //load patrons + self.patrons.string = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"patrons" ofType:@"txt"] encoding:NSUTF8StringEncoding error:NULL]; + + //make 'support us' default + [self.supportUs setKeyEquivalent:@"\r"]; + + //make first responder + // calling this without a timeout sometimes fails :/ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (100 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + + //and make it first responder + [self.window makeFirstResponder:self.supportUs]; + + }); + + return; +} + +//automatically invoked when window is closing +// make ourselves unmodal +-(void)windowWillClose:(NSNotification *)notification +{ + #pragma unused(notification) + + //make un-modal + [[NSApplication sharedApplication] stopModal]; + + return; +} + +//automatically invoked when user clicks any of the buttons +// load patreon or products webpage in user's default browser +-(IBAction)buttonHandler:(id)sender +{ + //support us button + if(((NSButton*)sender).tag == BUTTON_SUPPORT_US) + { + //open URL + // invokes user's default browser + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:PATREON_URL]]; + } + + //more info button + else if(((NSButton*)sender).tag == BUTTON_MORE_INFO) + { + //open URL + // invokes user's default browser + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:PRODUCT_URL]]; + } + + return; +} +@end diff --git a/Installer/Source/AppDelegate.h b/Installer/Source/AppDelegate.h new file mode 100644 index 0000000..f3d238e --- /dev/null +++ b/Installer/Source/AppDelegate.h @@ -0,0 +1,43 @@ +// +// file: AppDelegate.h +// project: OverSight (config) +// description: application main/delegate (header) +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import Cocoa; + +#import "Configure.h" +#import "HelperComms.h" +#import "AboutWindowController.h" +#import "ConfigureWindowController.h" + +//block for install/uninstall +typedef void (^block)(NSNumber*); + +@interface AppDelegate : NSObject +{ + +} + +//main menu +@property (weak, nonatomic)IBOutlet NSMenu *mainMenu; + +//daemom comms object +@property(nonatomic, retain)HelperComms* xpcComms; + +//status msg +@property (nonatomic, weak)IBOutlet NSTextField *statusMsg; + +//about window controller +@property(nonatomic, retain)AboutWindowController* aboutWindowController; + +//configure window controller +@property(nonatomic, retain)ConfigureWindowController* configureWindowController; + + +/* METHODS */ + +@end diff --git a/Installer/Source/AppDelegate.m b/Installer/Source/AppDelegate.m new file mode 100644 index 0000000..b07087d --- /dev/null +++ b/Installer/Source/AppDelegate.m @@ -0,0 +1,106 @@ +// +// file: AppDelegate.m +// project: OverSight (config) +// description: application main/delegate +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +#import "consts.h" +#import "HelperComms.h" +#import "AppDelegate.h" + +#import "Configure.h" +#import "utilities.h" +#import "AppDelegate.h" + +#import +#import + +@implementation AppDelegate + +@synthesize xpcComms; +@synthesize statusMsg; +@synthesize aboutWindowController; +@synthesize configureWindowController; + +//main app interface +-(void)applicationDidFinishLaunching:(NSNotification *)notification +{ + #pragma unused(notification) + + //don't relaunch + [NSApp disableRelaunchOnLogin]; + + //center window + [[[NSApplication sharedApplication] mainWindow] center]; + + //show config window + [self displayConfigureWindow]; + + return; +} + +//handler for menu +-(IBAction)menuHandler:(id)sender +{ + //handle selection + switch(((NSButton*)sender).tag) + { + //about + case MENU_ITEM_ABOUT: + { + //show about window + [self displayAboutWindow]; + + break; + } + //quit + case MENU_ITEM_QUIT: + { + //exit + [NSApp terminate:self]; + } + } + + return; +} + +//display configuration window +// kicks off logic for un/install +-(void)displayConfigureWindow +{ + //alloc/init + configureWindowController = [[ConfigureWindowController alloc] initWithWindowNibName:@"ConfigureWindowController"]; + + //display it + // call this first to so that outlets are connected + [self.configureWindowController display]; + + //configure it + [self.configureWindowController configure]; + + return; +} + +//menu handler for 'about' +-(void)displayAboutWindow +{ + //alloc/init settings window + if(nil == self.aboutWindowController) + { + //alloc/init + aboutWindowController = [[AboutWindowController alloc] initWithWindowNibName:@"AboutWindow"]; + } + + //center window + [[self.aboutWindowController window] center]; + + //show it + [self.aboutWindowController showWindow:self]; + + return; +} + +@end diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/Contents.json b/Installer/Source/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 61% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/Contents.json rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/Contents.json index 7cd4f8e..64dc11e 100644 --- a/Shared/Images/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Installer/Source/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { "images" : [ { - "size" : "16x16", - "idiom" : "mac", "filename" : "icon_16x16.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" }, { - "size" : "16x16", - "idiom" : "mac", "filename" : "icon_16x16@2x.png", - "scale" : "2x" + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" }, { - "size" : "32x32", - "idiom" : "mac", "filename" : "icon_32x32.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" }, { - "size" : "32x32", - "idiom" : "mac", "filename" : "icon_32x32@2x.png", - "scale" : "2x" + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" }, { - "size" : "128x128", - "idiom" : "mac", "filename" : "icon_128x128.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" }, { - "size" : "128x128", - "idiom" : "mac", "filename" : "icon_128x128@2x.png", - "scale" : "2x" + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" }, { - "size" : "256x256", - "idiom" : "mac", "filename" : "icon_256x256.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" }, { - "size" : "256x256", - "idiom" : "mac", "filename" : "icon_256x256@2x.png", - "scale" : "2x" + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" }, { - "size" : "512x512", - "idiom" : "mac", "filename" : "icon_512x512.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" }, { - "size" : "512x512", - "idiom" : "mac", "filename" : "icon_512x512@2x.png", - "scale" : "2x" + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_128x128.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_128x128.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_128x128.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_128x128.png diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_16x16.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_16x16.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_16x16.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_16x16.png diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_256x256.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_256x256.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_256x256.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_256x256.png diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_32x32.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_32x32.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_32x32.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_32x32.png diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_512x512.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_512x512.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_512x512.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_512x512.png diff --git a/Shared/Images/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png similarity index 100% rename from Shared/Images/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png rename to Installer/Source/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png diff --git a/Installer/Source/Assets.xcassets/Contents.json b/Installer/Source/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Installer/Source/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Installer/Source/Assets.xcassets/Friends1Password.imageset/Contents.json b/Installer/Source/Assets.xcassets/Friends1Password.imageset/Contents.json new file mode 100644 index 0000000..28e5e0e --- /dev/null +++ b/Installer/Source/Assets.xcassets/Friends1Password.imageset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "darkMode.png" + }, + { + "idiom" : "mac", + "filename" : "lightMode.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ] + }, + { + "idiom" : "mac", + "filename" : "darkMode.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ] + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Installer/Source/Assets.xcassets/Friends1Password.imageset/darkMode.png b/Installer/Source/Assets.xcassets/Friends1Password.imageset/darkMode.png new file mode 100644 index 0000000..771e67b Binary files /dev/null and b/Installer/Source/Assets.xcassets/Friends1Password.imageset/darkMode.png differ diff --git a/Installer/Source/Assets.xcassets/Friends1Password.imageset/lightMode.png b/Installer/Source/Assets.xcassets/Friends1Password.imageset/lightMode.png new file mode 100644 index 0000000..5789fbc Binary files /dev/null and b/Installer/Source/Assets.xcassets/Friends1Password.imageset/lightMode.png differ diff --git a/Installer/Source/Assets.xcassets/FriendsJamf.imageset/Contents.json b/Installer/Source/Assets.xcassets/FriendsJamf.imageset/Contents.json new file mode 100644 index 0000000..28e5e0e --- /dev/null +++ b/Installer/Source/Assets.xcassets/FriendsJamf.imageset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "darkMode.png" + }, + { + "idiom" : "mac", + "filename" : "lightMode.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ] + }, + { + "idiom" : "mac", + "filename" : "darkMode.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ] + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Installer/Source/Assets.xcassets/FriendsJamf.imageset/darkMode.png b/Installer/Source/Assets.xcassets/FriendsJamf.imageset/darkMode.png new file mode 100644 index 0000000..c4f7cc3 Binary files /dev/null and b/Installer/Source/Assets.xcassets/FriendsJamf.imageset/darkMode.png differ diff --git a/Installer/Source/Assets.xcassets/FriendsJamf.imageset/lightMode.png b/Installer/Source/Assets.xcassets/FriendsJamf.imageset/lightMode.png new file mode 100644 index 0000000..97d543a Binary files /dev/null and b/Installer/Source/Assets.xcassets/FriendsJamf.imageset/lightMode.png differ diff --git a/Installer/Source/Assets.xcassets/FriendsMosyle.imageset/Contents.json b/Installer/Source/Assets.xcassets/FriendsMosyle.imageset/Contents.json new file mode 100644 index 0000000..28e5e0e --- /dev/null +++ b/Installer/Source/Assets.xcassets/FriendsMosyle.imageset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "darkMode.png" + }, + { + "idiom" : "mac", + "filename" : "lightMode.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ] + }, + { + "idiom" : "mac", + "filename" : "darkMode.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ] + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Installer/Source/Assets.xcassets/FriendsMosyle.imageset/darkMode.png b/Installer/Source/Assets.xcassets/FriendsMosyle.imageset/darkMode.png new file mode 100644 index 0000000..44ed7ab Binary files /dev/null and b/Installer/Source/Assets.xcassets/FriendsMosyle.imageset/darkMode.png differ diff --git a/Installer/Source/Assets.xcassets/FriendsMosyle.imageset/lightMode.png b/Installer/Source/Assets.xcassets/FriendsMosyle.imageset/lightMode.png new file mode 100644 index 0000000..44ed7ab Binary files /dev/null and b/Installer/Source/Assets.xcassets/FriendsMosyle.imageset/lightMode.png differ diff --git a/Installer/Source/Assets.xcassets/Icon.imageset/Contents.json b/Installer/Source/Assets.xcassets/Icon.imageset/Contents.json new file mode 100644 index 0000000..d57e21f --- /dev/null +++ b/Installer/Source/Assets.xcassets/Icon.imageset/Contents.json @@ -0,0 +1,34 @@ +{ + "images" : [ + { + "idiom" : "mac" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "icon.png", + "idiom" : "mac" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "icon.png", + "idiom" : "mac" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Installer/Source/Assets.xcassets/Icon.imageset/icon.png b/Installer/Source/Assets.xcassets/Icon.imageset/icon.png new file mode 100644 index 0000000..b09466e Binary files /dev/null and b/Installer/Source/Assets.xcassets/Icon.imageset/icon.png differ diff --git a/Installer/Source/Assets.xcassets/Love.imageset/Contents.json b/Installer/Source/Assets.xcassets/Love.imageset/Contents.json new file mode 100644 index 0000000..4779fb2 --- /dev/null +++ b/Installer/Source/Assets.xcassets/Love.imageset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "heart_light-1.pdf" + }, + { + "idiom" : "mac", + "filename" : "heart_light.pdf", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ] + }, + { + "idiom" : "mac", + "filename" : "heart_dark.pdf", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ] + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Installer/Source/Assets.xcassets/Love.imageset/heart_dark.pdf b/Installer/Source/Assets.xcassets/Love.imageset/heart_dark.pdf new file mode 100644 index 0000000..ea674e1 --- /dev/null +++ b/Installer/Source/Assets.xcassets/Love.imageset/heart_dark.pdf @@ -0,0 +1,992 @@ +%PDF-1.5 % +1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + heart_dark + + + Adobe Illustrator CC 22.0 (Macintosh) + 2018-08-31T14:30:33+02:00 + 2018-08-31T14:30:33+02:00 + 2018-08-31T14:30:33+02:00 + + + + 220 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAADcAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9AzTSTSF3JJJrv2wKswK 7FW+b/zH78Ku5v8AzH78Vdzf+Y/firub/wAx+/FXc3/mP34q7m/8x+/FXc3/AJj9+Ku5v/MfvxV3 N/5j9+Ku5v8AzH78Vdzf+Y/firub/wAx+/FXc3/mP34q7m/8x+/FXc3/AJj9+Ku5v/MfvxVUS7uU +zKw9qmmKo231qZSBOOa/wAw2b+mNqm0M8Uyc425L+r54VX4q7FXYq7FXYq7FWKEUJHhgV2BXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqq29zLbyc4zTxHYj3wqyC0u47mPmmxH2l 7g4VVsVdirsVdirsVYq/22+ZwK1gVMdN02O4iMspNK0UDbphVG/oey8G+/DStHRrM/zD6caVr9C2 fi/3j+mNK79C2fi/3j+mNK79C2fi/wB4/pjSu/Qtn4v94/pjSu/Qtn4v94/pjSu/Qtn4v94/pjSu /Qtn4v8AeP6Y0rv0LZ+L/eP6Y0rv0LZ+L/eP6Y0rv0LZ+L/eP6Y0rv0LZ+L/AHj+mNK2NGs/8o/T /ZjSqU+iR8SYXIbsG3BwUqUMpVirCjA0I9xirWBXYq7FVS3uJIJBJGaEdR2I8DhVkNpdx3MfNNiP tL3BwqrYq7FXYq7FWKv9tvmcCtYFT7R/94h/rHJBUbirsVdirsVdirsVdirsVdirsVdirsVdirsV dirHtTUC+lA8QfvUHAqFwK2FYqWAqF6nwrhVrArsVVLe4kgkEkZoR1HYjwOFWQ2l3Hcx802I+0vc HCqtirsVdirFX+23zOBWsCp9o/8AvEP9Y5IKjcVdirsVdirsVdirsVdirsVdirsVdirsVdirsVY/ qv8AvfL/ALH/AIiMCoTAqZaIqtJMrCqlQCD88IVT1HTmt29SPeE/8L7HFUDgV2KqlvcSQSCSM0I6 jsR4HCrILO8juY+S7MPtp3Bwqr4q7FWKv9tvmcCtYFT7R/8AeIf6xyQVG4q7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYqx/Vf8Ae+X/AGP/ABEYFQmBUz0P+9l/1R+vCFTdlVlKsKqdiDhVI9R05rdv Uj3hP/C+xwKgcCuxVUgnkgkEkZow+4jwOFWQ2t1HcxCRNj0Ze4OFVbFWKv8Abb5nArWBU90f/eIf 6xyQVHYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqx/VjW+k9gv/ERgKoTAqYaPNFHNJ6jhKrs WNB1whU2+t2n+/o/+CH9cKtNdWbKVaaMqdiCy/1xVJL63hiflBIrxt0AYEj2wKhcCuxVE2F0becM f7ttnHt4/RhVkVRSvbxwqxV/tt8zgVrAqeaL/vGf9c/qGSCo/FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FWP6r/vfL/sf+IjAqEwK7FXYq7FXYq7FXYq7FU4+tn9Dcq/HT0/xp/xHCqUP9tvmcVaw Knmi/wC8Z/1z+oZIKj8VdirsVdirsVdirsVdirsVdirsVdirsVdirsVY9qlfr0tfb/iIwKhcCuxV 2KuxV2KuxV2KuxVE8j+jePb1q/8AC4VQ7/bb5nFWsCp5ov8AvGf9c/qGSCo/FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FVOW1t5SDJGGI7nriqn+j7L/fK4q2LGz/AN8p92Kt/UbP/fKfcMVa+oWd a+iv3Yq76jZ/75T7hiqGu9IgeMtCOEg3AHQ+2ClSTArsVV+P+hcv+LKf8LhVRf7bfM4q1gVPNF/3 jP8Arn9QyQVH4q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FWL3AAnkA6B mp9+BVmBUR/0r/8Anr/xrhVQf7bfM4q1gVPNF/3jP+uf1DJBUfirsVdirsVQ97qFjYx+pdzpCnbk dz8h1P0ZDJljAXI0rG738xNOjJW0t5Lgj9piI1+j7TfhmvydpwH0i2PElkn5jakSfTtYVHblzb9R XMc9qT6AI4l0P5j3ob99ZxuvgjMp/Hlhj2pLqAniTvTvPWi3RCTFrSQ/79+x/wAGP40zLxdoY5c/ SniZCjo6h0YMrCqsDUEexzOBtLeFXYql+pa9pOmj/SrhVk7RL8Tn/Yjp9OUZdTDH9RRbHbr8x7dS Ra2buOzSME/BQ368wZ9qD+GKOJBH8xtR5ClpDx7irV++uU/ypPuCOJE2/wCZG4FzY7d2jf8A41I/ jlke1O+KeJPdN826HfkIk3oynpFN8B+g7qfvzMxa3HPrR802nOZaXYq7FXYq7FWM3QpdTDwdv1nA qlgVEf8ASv8A+ev/ABrhVQf7bfM4q1gVPNF/3jP+uf1DJBUfirsVdirEfMfndLZmtdMKyTjZ7j7S KfBezH8M1mq7Q4fTDn3sTJgtzdXN1MZriRpZW6u5qc005mRsmyxTDT/LGuXyh4bZliPSSSiLT25b n6Mvx6TJPkFpMJ/IGvRxF1MMpAr6aOeX/DKo/HLpdm5QL2KeFjssUsMjRSoUkQ0ZGFCD7g5gkEGi hbgVNNE8x6jpMg9F/UtyavbufhPjT+U+4zJwaqeI7cu5QXo2l+YdM1G19eOVYyorLHIQrJ869vfN 7h1MJxsFmCxfzJ54kdmtdKbgg2e6HU+yeA9812q7QJ9MPmgyYc7s7F3JZmNWYmpJ9zmqJti1irsV dirsVT7QfN2oaYyxSk3Fn0MTHdR/kE9Pl0zN0+tlj2O8UgvRrC/tb+1S5tX5xP8AeD3BHYjN9jyR nGxyZojJq7FXYqxm7/3rm/4yN+s4FUsCoj/pX/8APX/jXCqg/wBtvmcVawKnmi/7xn/XP6hkgqPx V2KsQ88eZGt0OmWjUmkFbmQdVU/sj3b9WavtDVcPojz6sZFg1tbT3U6W9uhkmkNEQdSc08IGRoc2 L0fy/wCTrLTkWa5C3F715EVRD4KD+s5vtNoY49zvJmAyHM5LsVYL+Y1nCk9pdqAJZgySU78KcT+N M03akACJd7GTDc1TF2KuxV2KuxV2KuxV2KuxV2Kpv5b1+bSL4OSWtJKC4i9v5h7jMrS6k4pf0eqQ XqcciSRrJGwZHAZGG4IO4IzowQRYZrsKuxVjN0a3Mx8Xb9eBVLAqI/6V/wDz1/41wqoP9tvmcVaw Knmi/wC8Z/1z+oZIKj8VUL67js7Oa6k+xChcjxoOn05DJMRiZHorx66uZbm4kuJm5SysXc+5OctO RkSTzLW9A8j6EtpYi/mX/SboVjr+zEdxT/W65u+z9Pwx4jzP3MohlGbFk7FUBe69o9kSLm7jR16x g8n/AOBWpynJqMcOZCLed+aNfOsXyvGCtrCCsCnrv1Y/OmaLV6nxZbcgxJSbMRDsVdirsVdirsVd irsVdirsVdir0HyBqpuLCSxkNZLU1jr3jbt/sTm87NzcUeE9GUSyvNkydirGLn/eiX/Xb9eBVPAq I/6V/wDz1/41wqoP9tvmcVawKnmi/wC8Z/1z+oZIKj8VY55+uTFoJjB/3olSM/IVf/jXMDtGVYq7 yiTz/TLT65qNta9ppFRiOwJ3P3ZpMUOKYj3lgHsSqqqFUUVRQAdABnVANjeKsD85earg3Mmm2TmO KI8biVTRmbuoPYDoc02u1hswjyHNiSw/NUxdirsVdirsVdirsVdirsVdirsVdirsVT3yTdGDzDAt aLOrRN9I5D/hlGZugnw5R5pD0/OhZuxVjFz/AL0S/wCu368CqeBUR/0r/wDnr/xrhVQf7bfM4q1g VPNF/wB4z/rn9QyQVH4qxP8AMYf7jLU1/wB39P8AYHNZ2p9A97GTGfJqhvMtkD0rIfujY5r9CP30 fx0QOb1LOjZuxV4rLI0sryOas7FmPuTU5yRNm2tbgV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVH6A5 XXNPI/5aIh97gfxy7TGskfeFD13OobHYqxi5/wB6Jf8AXb9eBVPAqI/6V/8Az1/41wqoP9tvmcVa wKnmi/7xn/XP6hkgqPxVjfn+EyaCHH+6Zkc/Igp/xtmB2lG8XuKJMJ8tTiDXrF22HqhCT/l/D/HN RpJVlifNgHrWdM2OxV5BrVm1nq13bEUCSNxr/KTVT9xzl88OGZHm1lBZSrsVdirsVdirsVdirsVd irsVdirsVdiqa+VoTL5hsVHaTn/wALfwzJ0cbyx96h6vnStjsVYxc/70S/67frwKp4FRH/Sv/wCe v/GuFVB/tt8zirWBU80X/eM/65/UMkFR+KoDXrM3ujXdsoqzxkoPFl+JfxGU6mHHjI8kF5Gjsjq6 GjKQVPgRuM5gGmD2PTr1L2wgu0+zMgangT1H0HbOpxZOOIl3tiIyxWDfmLpyq9tqCChf9zKfEgck P3VzT9p4txP4MZMMzUsXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FWV/l5Zepqc12R8NvHxU/wCVIaf8 RBzZdmY7mZdwTF6Dm8ZuxVjFz/vRL/rt+vAqngVEf9K//nr/AMa4VUH+23zOKtYFTzRf94z/AK5/ UMkFR+KuxV5R5o0w6drM8QFIZD6sPhwc1p9BqM5rV4uDIR0ayE+8ha9HFXSrlwoduVqx6cj1T6eo zN7O1IHoPwZRLOc3DJhn5ialbNBBpyMGnWQTSAfsgKQAfnyzU9p5RQh1u2MmDZp2LsVdirsVdirs VdirsVdirsVdirsVdir1LyhpZ0/RYg4pNcfvpQeo5D4R9C0zo9Fh4MY7zuzATrMtLsVYxc/70S/6 7frwKp4FRH/Sv/56/wDGuFVB/tt8zirWBU80X/eM/wCuf1DJBUfirsVSHzhoR1PTvUhWt3bVeIDq y/tJ9PbMLXafxIWPqCCHmO4PgRnPMEzXzNr6weiL6UJSg3+L/gvtfjmR+by1XEVtLWZmYsxLMxqW O5JOY5KtYq7FXYq7FXYq7FXYq7FXYq7FXYq7FU+8n6EdS1ESyr/olsQ8tejN+yn9fbM3Q6fxJ2fp CQHp2dCzdirsVYxc/wC9Ev8Art+vAqngVEf9K/8A56/8a4VUH+23zOKtYFTzRf8AeM/65/UMkFR+ KuxV2KsD86+WDE76pZp+6Y1uox+yT+2PY9802v0leuPLqxIYfmqYuxV2KuxV2KuxV2KuxV2KuxV2 KuxV2KovStLutTvEtbZas27Meir3ZvYZbhwyyS4QoD1bS9MttNso7S3HwJ9pj1Zj1Y+5zpMOIY48 IbAEXlquxV2KsYuf96Jf9dv14FU8Coj/AKV//PX/AI1wqoP9tvmcVawKnmi/7xn/AFz+oZIKj8Vd irsVcQCCCKg7EHFXn3m3ykbMtf2C1tDvLEP91+4/yf1Zo9bouD1R+n7mBDFM1qHYq7FXYq7FXYq7 FXYq7FXYq7FUVpumXmpXS21qnNzux6Kq92Y9hlmLFLJKorT1DQtCtNItBDCOUrUM0xG7H+AHYZ0W n08cUaHNmAmWZCXYq7FXYqxi5/3ol/12/XgVTwKiP+lf/wA9f+NcKqD/AG2+ZxVrAqeaL/vGf9c/ qGSCo/FXYq7FXYq4gEUO4OKsH80eSivO90tKru0tqOo8TH/zT92afV6D+KHy/UxIYWQQaHYjNSxd irsVdirsVdirsVdirsVTPQ/L99q8/GEcIFP724YfCvsPE+2ZGn00sp25d6gPTNJ0ey0u2EFqlK7y SHdnPixzoMOCOMVFmAjcuS7FXYq7FXYqxi5/3ol/12/XgVTwKiP+lf8A89f+NcKqD/bb5nFWsCp5 ov8AvGf9c/qGSCo/FXYq7FXYq7FXYqxvzH5OttR5XNpSC96t2SQ/5VOh98wNVoRk3jtL70EPPbuz ubOdre5jMUydVb9Y8RmjnAxNEUWCjkFdirsVdirsVdirKPLnku4vuF1fgw2nVY+jyD/jVffNjpdA Z7y2ikB6BbW0FtCkFvGI4kFFRRQDN3GAiKHJmqZJXYq7FXYq7FXYqxi5/wB6Jf8AXb9eBVPAqI/6 V/8Az1/41wqoP9tvmcVawKnmi/7xn/XP6hkgqPxV2KuxV2KuxV2KuxVAavolhqsHpXSfEP7uVdnQ +x/hlObTxyCpIIeca75av9IkJkHq2pNEuFG3yb+U5odRpJYjvuO9iQlOYqHYq7FVS2tbi6nWC3ja WVzRUUVOShAyNDcqz/y75Jt7Ljc6gFnuhusfWND/AMbHN3pdAIby3kyAZTmxZOxV2KuxV2KuxV2K uxVjFz/vRL/rt+vAqngVEf8ASv8A+ev/ABrhVQf7bfM4q1gVPNF/3jP+uf1DJBUfirsVdirsVdir sVdirsVWyRRyxtHKoeNxRkYAgjwIOAgEUVYP5i8ivHyutJBdOr2vVh/qHv8ALrmn1XZ9bw+TExYa ylSVYUYbEHqDmqYppofl2/1eWkK8IFNJLhh8I9h4n2zJ0+lllO3LvUB6Ro2hafpMHp2yVkYfvJm3 dvp7D2zfYNPHEKDMBMMvS7FXYq7FXYq7FXYq7FXYqxi5/wB6Jf8AXb9eBVPAqI/6V/8Az1/41wqo P9tvmcVawKnujD/Q/mxyQVHYq7FXYq7FXYq7FXYq7FXYq7FUp1Lyto2o3K3FxCRKPtsh48/9anX9 eY2XSY5myN0UmcEEMESwwoI4kFFRRQAZkRiIihySvwq7FXYq7FXYq7FXYq7FXYq7FWMXP+9Ev+u3 68CqeBUR/wBK/wD56/8AGuFVB/tt8zirWBU+0f8A3iH+sckFRuKuxV2KuxV2KuxV2KuxV2KuxV2K uxV2KuxV2KuxV2KuxV2KuxV2KuxVjFz/AL0S/wCu368CqeBUR/0r/wDnr/xrhVQf7bfM4q1gVMtN 1KGCExSg9SQRv1wqizrNmP5j9GG1a/TVn4P9w/rjau/TVn4P9w/rjau/TVn4P9w/rjau/TVn4P8A cP642rv01Z+D/cP642rv01Z+D/cP642rv01Z+D/cP642rv01Z+D/AHD+uNq79NWfg/3D+uNq79NW fg/3D+uNq79NWfg/3D+uNq79NWfg/wBw/rjau/TVn4P9w/rjau/TVn4P9w/rjau/TVn4P9w/rjau /TVn4P8AcP642rv01Z+D/cP642rv01Z+D/cP642rv01Z+D/cP642rv01Z+D/AHD+uNq2dZs+NRyJ 8Kf24LVJJXMkjuerksfpNcVW4FRH/Sv/AOev/GuFUOTU1PfArsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqtyP1Pj29Sv/C4VWTQyQuUdSCDT54qswK7FW+D/ AMp+7CruD/yn7sVdwf8AlP3Yq7g/8p+7FXcH/lP3Yq7g/wDKfuxV3B/5T92Ku4P/ACn7sVdwf+U/ diruD/yn7sVdwf8AlP3Yq7g/8p+7FXcH/lP3Yq7g/wDKfuxV3B/5T92Ku4P/ACn7sVdwf+U/diru D/yn7sVdwf8AlP3Yq0QR1FMVdgV2KuxVwBPTfFUV9Vn+oc+BpzrSm9KdcKv/2Q== + + + + uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 + xmp.did:9213526b-162b-466a-8595-d927b02aa86b + uuid:3523a6d4-84e3-724a-922a-dde4e0b32b5a + proof:pdf + + uuid:ec456e48-5fd4-4875-93c4-e073576b0227 + xmp.did:03811155-2e06-d540-85f9-58f10d0c940a + uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 + proof:pdf + + + + + saved + xmp.iid:9213526b-162b-466a-8595-d927b02aa86b + 2018-08-31T14:30:30+02:00 + Adobe Illustrator CC 22.0 (Macintosh) + / + + + + Basic RGB + 1 + False + False + + 256.000000 + 256.000000 + Pixels + + + + Cyan + Magenta + Yellow + Black + + + + + + Standard-Farbfeldgruppe + 0 + + + + Weiß + RGB + PROCESS + 255 + 255 + 255 + + + Schwarz + RGB + PROCESS + 0 + 0 + 0 + + + RGB Rot + RGB + PROCESS + 255 + 0 + 0 + + + RGB Gelb + RGB + PROCESS + 255 + 255 + 0 + + + RGB Grün + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blau + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Kalt + 1 + + + + C=56 M=0 Y=20 K=0 + RGB + PROCESS + 101 + 200 + 208 + + + C=51 M=43 Y=0 K=0 + RGB + PROCESS + 131 + 139 + 197 + + + C=26 M=41 Y=0 K=0 + RGB + PROCESS + 186 + 155 + 201 + + + + + + Graustufen + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + + Adobe PDF library 15.00 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 7 0 obj <>/Resources<>/Properties<>>>/Thumb 11 0 R/TrimBox[0.0 0.0 256.0 256.0]/Type/Page>> endobj 8 0 obj <>stream +HdWM5 ) t&ly V#Xp'`5H3_h4mI\v^?v=>bQ˚vo_O?Vy|v^%fu|/<]ݯ2l]q慏6-ܪjiu۵{u\{J>^mXXlaևLx^yl-[s1Kjd^v{rBx0Ι 2ݛ]wCcR(;{D!ƄTۘC{RvVrn]'y/t+:bDcҰ##k 5-̰{5E$Q +FqEX v=_'3 p3NA1Li[FcĽch\p 3FIϝY%@nt^IȾQm\|!Vơ5`i#mT΃c*k v.jBLc+\A5b̥Us*H!߶1 qzUxEQ 6O%y{t8zцdޛ +]ysќev'Td; +tMkb@)Hg]#Wn=1>*N]Yseقj^s\$|El,mL:K#n,Fo]!^E0K[1h]E|xzVHDZE&cP 8f/i4XG1M D΀ Vϔ9X _7aDPgॖ󱞶C]p,r<8._~v_w endstream endobj 11 0 obj <>stream +8;XF19+Jin#XjaAG'Ta!U7lZJN_WK[O2=$@$&jF6Y/Hn#p) +g]mdqp944kqSZ='pNHk:Nr+0&ReMlqe_)NH]CY"h0.$Vns7][kg\R'4(7tU-N;Nnt~> endstream endobj 12 0 obj [/Indexed/DeviceRGB 255 13 0 R] endobj 13 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 14 0 obj [/View/Design] endobj 15 0 obj <>>> endobj 10 0 obj <> endobj 9 0 obj <> endobj 16 0 obj <> endobj 17 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 17.0 %%AI8_CreatorVersion: 22.0.0 %%For: (Carsten Duvenhorst) () %%Title: (heart_dark.pdf) %%CreationDate: 31.08.18 14:30 %%Canvassize: 16383 %%BoundingBox: 33 -241 225 -17 %%HiResBoundingBox: 33.4615300484429 -240.495272086609 224.54759606832 -17.5783596674073 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 13.0 %AI12_BuildNumber: 244 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Passermarken]) %AI3_Cropmarks: 0 -256 256 0 %AI3_TemplateBox: 128.5 -128.5 128.5 -128.5 %AI3_TileBox: -151.5 -508 407.5 275 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI17_Begin_Content_if_version_gt:17 1 %AI9_OpenToView: -122.777777777778 12.1851851851852 2.7 1428 784 18 0 0 6 43 0 0 0 1 1 0 1 1 0 0 %AI17_Alternate_Content %AI9_OpenToView: -122.777777777778 12.1851851851852 2.7 1428 784 18 0 0 6 43 0 0 0 1 1 0 1 1 0 0 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 7 %%PageOrigin:-272 -428 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 18 0 obj <>stream +%%BoundingBox: 33 -241 225 -17 %%HiResBoundingBox: 33.4615300484429 -240.495272086609 224.54759606832 -17.5783596674073 %AI7_Thumbnail: 112 128 8 %%BeginData: 18558 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFAEFD20FFA8CFA8A8 %A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FF %A8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8 %A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8FD1EFFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD1CFFA8A8A8CFA8A8A8CFA8A8A8CF %A8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8 %A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CF %A8A8A8CFA8A8A8CFA8A8A8CFA8A8A8FD1AFFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD18FFA8A8A8FFA8A8A8FFA8A8A8FF %A8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8 %A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FF %A8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8FD18FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD16FFA8AEA8A8A8CFA8A8 %A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CF %A8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8 %A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8AEA8FD16FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD14FF %A8FFA8A8A8FFA8A8A8FFA8FFA8FFFFFFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FF %A8A8A8FD14FFA8FFA8FFA8FFA8FFA8FD3FFFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FD12FFA8A8A8CFA8A8A8CFA8FD41FFA8CFA8A8A8CF %A8A8A8CFA8A8A8CFA8A8A8CFA8A8A8FD12FFA8FFA8FFA8FFA8FD43FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD10FFA8A8A8FFA8A8A8FF %A8FD41FFA8CFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8FD10FF %A8FFA8FFA8FFA8FD43FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FD0EFFA8AEA8A8A8CFA8A8A8FD41FFA8A8A8CFA8A8A8CFA8FFFD05 %A8CFA8A8A8CFA8A8A8AEA8FD0EFFA8FFA8FFA8FFA8FD43FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD0CFFA8FFA8A8A8FFA8A8 %A8FD41FFA8A8A8FFA8A8A8FFA8FFFFFFA8A8A8FFA8A8A8FFA8A8A8FFA8A8 %A8FD0CFFA8FFA8FFA8FFA8FFAEFD41FFA8FFA8FFA8FFA8FFA8FFFFFFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FD0AFFA8A8A8CFA8A8A8CFA8FD41FFFD05 %A8CFA8A8A8FD05FFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8FD0AFFA8FFA8FF %A8FFA8FD43FFA8FFA8FFA8FFA8FFCFFD05FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FD08FFA8A8A8FFA8A8A8FFA8FD41FFA8CFA8A8A8FFA8A8A8FD07FF %A8CFA8A8A8FFA8A8A8FFA8A8A8FFA8FD08FFA8FFA8FFA8FFA8FD43FFA8FF %A8FFA8FFA8FFA8FD07FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD06FFA8AE %A8A8A8CFA8A8A8FD41FFA8A8A8CFA8A8A8CFA8FD09FFFD05A8CFA8A8A8CF %A8A8A8AEA8FD06FFA8FFA8FFA8FFA8FD43FFA8FFA8FFA8FFA8FFA8FD0BFF %A8FFA8FFA8FFA8FFA8FFA8FFA8FD04FFA8FFA8A8A8FFA8A8A8FD41FFA8A8 %A8FFA8A8A8FFA8FD0BFFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FD04FFA8FF %A8FFA8FFA8FFCFFD41FFA8FFA8FFA8FFA8FFA8FD0DFFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFFFA8A8A8CFA8A8A8CFA8FD41FFFD05A8CFA8A8A8CFA8FFA8 %FFA8FFA8FFA8FFA8FFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8FFFFA8FFA8FF %A8FFA8FFAEFD41FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFFFA8A8A8FFA8A8A8FFA8FD41FFA8CFA8 %A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8 %FFA8A8A8FFFFA8FFA8FFA8FFA8FD43FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFFFA8AEA8A8A8CFA8 %A8A8FD41FFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8 %A8A8CFA8A8A8CFA8A8A8CFA8FFFFA8FFA8FFA8FFA8FD43FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %FFA8FFA8A8A8FFA8A8A8FD41FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8 %A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8FFFFA8FFA8FFA8FFA8FF %A8FD41FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFFFA8A8A8CFA8A8A8CFA8FD41FFFD19A8AEA8A8A8 %CFA8A8A8CFA8A8A8FFFFA8FFA8FFA8FFA8FFAEFD43FFA8FFAEFFA8FFFFFF %A8FFFFFFA8FFAEFFA8FFFFFFA8FFA8FFA8FFA8FFA8FFA8FFA8FFFD04A8FF %A8A8A8FFA8FD5BFFA8A8A8FFA8A8A8FFA8A8A8FFFFA8FFA8FFA8FFA8FD5D %FFA8FFA8FFA8FFA8FFA8FFA8A8AEA8A8A8CFA8A8A8FD5DFFA8A8A8CFA8A8 %A8CFA8FFFFA8FFA8FFA8FFA8FD5FFFA8FFA8FFA8FFA8FFA8A8FFA8A8A8FF %A8A8A8FD5DFFA8A8A8FFA8A8A8FFA8FFFFA8FFA8FFA8FFA8FFA8FD5DFFA8 %FFA8FFA8FFA8FFFFA8A8A8CFA8A8A8CFA8FD5DFFFD05A8CFA8A8A8FFFFA8 %FFA8FFA8FFA8FFAEFD5DFFA8FFA8FFA8FFA8FFFFA8A8A8FFA8A8A8FFA8FD %1BFFAECFA7CFA7AEA8FD19FFAECFA8CFA8CFA8FD1BFFA8FFA8A8A8FFA8A8 %A8FFFFA8FFA8FFA8FFA8FD19FFA8A782A682827CA682827CA7A7CFCFFD11 %FFA7A782A682827CA682A682A7A7CFFD18FFA8FFA8FFA8FFA8FFA8A8AEA8 %A8A8CFA8A8A8FD15FFA8A77C827B827B827C827C827C827B827B8283FD0D %FFA8A77B827B827B827C827B827C827B827BA6A7FD15FFA8A8A8CFA8A8A8 %CFA8FFFFA8FFA8FFA8FFA8FD15FFA7A67CA682A682A682A682A682A682A6 %82A682A682AEFD0AFFA7A67CA682A682A682A682A682A682A682A67CA682 %AEFD14FFA8FFA8FFA8FFA8FFA8A8FFA8A8A8FFA8A8A8FD12FFA87C827C82 %7CA67C827B827B827CA67C827CA67C827C827BA6A8FD06FFA87B827C827C %A67C827CA67C827CA67C827CA67C827C827BA6A8FD11FFA8A8A8FFA8A8A8 %FFA8FFFFA8FFA8FFA8FFA8FFA8FD10FFA782827CA682827BA682A7A7CFA7 %A67CA682A67CA682A67CA68282A7FD04FFA77CA67CA682A67CA682A67CA6 %82A67CA682A67CA682A67CA68282A7FD11FFA8FFA8FFA8FFA8FFFFA8A8A8 %CFA8A8A8CFA8FD0FFFA8827B827C827B827CA7A8FD05FFA8A77B827C827C %827C827C827C8282FFA8A67B827C827C827C827C827C827C827C827C827C %827C827C827C827C82A7FD0FFFFD05A8CFA8A8A8FFFFA8FFA8FFA8FFA8FF %AEFD0DFFCFA67CA682A67CA6A7FD0AFFA782A682A682A682A682A682A682 %A67CA682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A6A7FD0FFFA8FFA8FFA8FFA8FFFFA8A8A8FFA8A8A8FFA8FD0DFFAEA77B82 %7CA67BA6A8FD0BFFA7827C827CA67C827CA67C827C827C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C827C82A7FD0DFFA8CF %A8A8A8FFA8A8A8FFFFA8FFA8FFA8FFA8FD0EFFA77BA682A67BA7FD0EFF7C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA682A67CA682A67CA6AEFD0DFFA8FFA8FFA8FFA8FFA8A8AE %A8A8A8CFA8A8A8FD0CFFAE7B827C827BA7FD0EFFA8827B827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827BA7FD0CFFA8A8A8CFA8A8A8CFA8FFFFA8FFA8FFA8FFA8FD %0DFF82A682A682A7FD0FFFCF7CA682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682CF %FD0CFFA8FFA8FFA8FFA8FFA8A8FFA8A8A8FFA8A8A8FD0BFFA7827C827C82 %A8FD0FFF82827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C82 %7CA67C827CA67C827CA67C827CA67C827CA67C827C8282FD0BFFA8A8A8FF %A8A8A8FFA8FFFFA8FFA8FFA8FFA8FFA8FD0AFFA77CA68282A7FD0FFF82A6 %82A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA6A8FD0BFFA8FFA8FFA8FFA8FF %FFA8A8A8CFA8A8A8CFA8FD0AFFCF7B827C827CFD0CFFCFA7A77C827C827C %827C827C827C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827C827BA7FD0AFFFD05A8CFA8A8A8FFFFA8 %FFA8FFA8FFA8FFAEFD09FFA78282A682AEFD0AFFCFA77CA682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682FD0BFFA8FFA8FFA8FFA8FFFFA8 %A8A8FFA8A8A8FFA8FD09FFCFA77C827C82A8FD09FFA8827B827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827CA67C827CA67C827CA67C827CA67C827C82A7FD09FFA8FFA8A8A8FFA8 %A8A8FFFFA8FFA8FFA8FFA8FD0BFF7CA6828282FD09FFCF827CA682A67CA6 %82A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67BADFD0AFFA8FFA8FFA8 %FFA8FFA8A8AEA8A8A8CFA8A8A8FD09FFA8827C827BA7FD09FFA77B827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C8282FD09FFA8A8 %A8CFA8A8A8CFA8FFFFA8FFA8FFA8FFA8FD0AFFCF7CA68282A7FD09FF82A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A7FD0A %FFA8FFA8FFA8FFA8FFA8A8FFA8A8A8FFA8A8A8FD09FFA8827CA67BAEFD08 %FFA7827C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827CFD09FFA8A8A8FFA8A8A8FFA8FFFFA8FFA8FFA8FFA8FFA8FD08FFCF82 %A67C82A8FD08FFCF7BA682A67CA682A67CA682A67CA682A67CA682A67CA6 %82A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A6AEFD09FFA8FFA8FFA8FFA8FFFFA8A8A8CFA8A8A8CFA8 %FD09FFA8827C827BAEFD08FFA7827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827CFD09FFFD05A8CFA8A8A8FFFFA8FFA8FFA8 %FFA8FFAEFD09FF82A682A6A7FD08FFCF7CA682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A7FD0AFFA8FFA8FFA8FFA8FFFFA8A8 %A8FFA8A8A8FFA8FD09FFA8A67C827BA7FD08FFAE827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827CA67C827CA67C827CA67C827CA67C827C8282FD09FFA8FFA8A8A8FFA8 %A8A8FFFFA8FFA8FFA8FFA8FD0BFF82A6828283FD09FFA6827CA682A67CA6 %82A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67CA7FD0AFFA8FFA8FFA8 %FFA8FFA8A8AEA8A8A8CFA8A8A8FD0AFFA77B827B82A8FD08FFAE7B827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C8283FD09FFA8A8 %A8CFA8A8A8CFA8FFFFA8FFA8FFA8FFA8FD0BFFA7A682A682FD0AFFA682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A67CCFFD0AFFA8 %FFA8FFA8FFA8FFA8A8FFA8A8A8FFA8A8A8FD0AFFAE7BA67C82A7FD09FFA7 %827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C82A8FD09 %FFA8A8A8FFA8A8A8FFA8FFFFA8FFA8FFA8FFA8FFA8FD09FFCFA67CA682A7 %FD0AFF82A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %FD0BFFA8FFA8FFA8FFA8FFFFA8A8A8CFA8A8A8CFA8FD0BFF82827C827CCF %FD08FFA8A67C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827C827C827C827C827C827C827C827C827B %A7FD0AFFFD05A8CFA8A8A8FFFFA8FFA8FFA8FFA8FFAEFD0AFFCF7CA68282 %A7FD09FF82A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %CFFD0BFFA8FFA8FFA8FFA8FFFFA8A8A8FFA8A8A8FFA8FD0BFFCF827CA67C %82A8FD08FFA77C827CA67C827CA67C827CA67C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C82 %82FD0BFFA8FFA8A8A8FFA8A8A8FFFFA8FFA8FFA8FFA8FD0DFFA7827CA682 %A7FD07FFCF7CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %CFFD0CFFA8FFA8FFA8FFA8FFA8A8AEA8A8A8CFA8A8A8FD0DFF7C827C827B %A7FD06FF7C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827BA7 %FD0CFFA8A8A8CFA8A8A8CFA8FFFFA8FFA8FFA8FFA8FD0EFFAD7CA682A67C %A7AEFFAECF82A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A6AEFD0DFFA8FFA8FFA8FFA8FFA8A8FFA8A8A8FFA8A8A8FD0DFFCF827BA6 %7C827B827C827B827C827CA67C827CA67C827CA67C827CA67C827CA67C82 %7CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA6 %7C8282FD0DFFA8A8A8FFA8A8A8FFA8FFFFA8FFA8FFA8FFA8FFA8FD0DFFA8 %827CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682FD0FFFA8FFA8FFA8FFA8FFFFA8A8A8CFA8A8A8CFA8FD0FFF8382 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7BAEFD0EFFFD05A8CFA8A8A8FFFFA8FFA8FFA8FFA8FFAEFD0FFF82A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A67CADFD10 %FFA8FFA8FFA8FFA8FFFFA8A8A8FFA8A8A8FFA8FD10FFCF7C827C827CA67C %827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67BA6CFFD0FFFA8FFA8 %A8A8FFA8A8A8FFFFA8FFA8FFA8FFA8FD12FFCF7CA682A67CA682A67CA682 %A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA6CFFD11FFA8FFA8FFA8FFA8FFA8 %A8AEA8A8A8CFA8A8A8FD12FFA87B827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827B82A8FD11FFA8A8A8CFA8A8A8CFA8FFFFA8FFA8FFA8FF %A8FD14FFCF82A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6A8 %FD13FFA8FFA8FFA8FFA8FFA8A8FFA8A8A8FFA8A8A8FD14FFAE7C827CA67C %827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827B82A8FD13FFA8A8A8FFA8A8A8FFA8 %FFFFA8FFA8FFA8FFA8FFA8FD14FFCF82827CA682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A6A8FD15FFA8FFA8FFA8FFA8FFFFA8A8A8CFA8A8A8CFA8FD16FF %AE7B827C827C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827C827C827BA6A8FD15FFFD05A8CFA8A8A8 %FFFFA8FFA8FFA8FFA8FFAEFD16FFCF82A682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A67C %A7FD18FFA8FFA8FFA8FFA8FFFFA8A8A8FFA8A8A8FFA8FD19FF7C827C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827CA67C827C827BA7FD18FFA8FFA8A8A8FFA8A8A8FFFFA8FFA8FFA8FFA8 %FD1BFF828282A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA682A67CA682827CAEFD1AFFA8FFA8FFA8FFA8FFA8A8AEA8 %A8A8CFA8A8A8FD1BFF83827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827C827C827CAEFD1AFFA8A8A8CFA8A8A8CF %A8FFFFA8FFA8FFA8FFA8FD1DFFA8A67CA682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A6A6FD1DFFA8FFA8FFA8FFA8 %FFA8A8FFA8A8A8FFA8A8A8FD1DFFA8A67B827C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C82A7FD1DFFA8A8A8FFA8A8A8FF %A8FFFFA8FFA8FFA8FFA8FFA8FD1EFFA77BA682A67CA682A67CA682A67CA6 %82A67CA682A67CA682A67CA682A67CA6A8FD1FFFA8FFA8FFA8FFA8FFFFA8 %A8A8CFA8A8A8CFA8FD20FFA87B827C827C827C827C827C827C827C827C82 %7C827C827C827C827BA7A8FD1FFFFD05A8CFA8A8A8FFFFA8FFA8FFA8FFA8 %FFAEFD21FF828282A682A682A682A682A682A682A682A682A682A682A682 %AEFD22FFA8FFA8FFA8FFA8FFFFA8A8A8FFA8A8A8FFA8FD23FFA7827BA67C %827CA67C827CA67C827CA67C827CA67C827CCFFD22FFA8FFA8A8A8FFA8A8 %A8FFFFA8FFA8FFA8FFA8FD25FFAEA67BA682A67CA682A67CA682A67CA682 %A67CA6A7FD25FFA8FFA8FFA8FFA8FFA8A8AEA8A8A8CFA8A8A8FD25FFCFA7 %7B827C827C827C827C827C827C827B82A7FD25FFA8A8A8CFA8A8A8CFA8FF %FFA8FFA8FFA8FFA8FD28FFCF82A682A682A682A682A682A682A7CFFD27FF %A8FFA8FFA8FFA8FFA8A8FFA8A8A8FFA8A8A8FD29FF82827CA67C827CA67C %827BA7FD28FFA8A8A8FFA8A8A8FFA8FFFFA8FFA8FFA8FFA8FFA8FD29FFA7 %827CA682A67CA682CFFD2AFFA8FFA8FFA8FFA8FFFFA8A8A8CFA8A8A8CFA8 %FD2BFFA8827B827C8282FD2BFFFD05A8CFA8A8A8FFFFA8FFA8FFA8FFA8FF %AEFD2CFFAD82A6A7FD2DFFA8FFA8FFA8FFA8FFFFA8A8A8FFA8A8A8FFA8FD %2EFFCFA8FD2DFFA8FFA8A8A8FFA8A8A8FFFFA8FFA8FFA8FFA8FD5FFFA8FF %A8FFA8FFA8FFA8A8AEA8A8A8CFA8A8A8FD5DFFA8A8A8CFA8A8A8CFA8FFFF %A8FFA8FFA8FFA8FD5FFFA8FFA8FFA8FFA8FFA8A8FFA8A8A8FFA8A8A8FD5D %FFA8A8A8FFA8A8A8FFA8FFFFA8FFA8FFA8FFA8FFAEFD5DFFA8FFA8FFA8FF %A8FFFFA8A8A8CFA8A8A8CFA8FD5DFFFD05A8CFA8A8A8FFFFA8FFA8FFA8FF %A8FD5FFFA8FFA8FFA8FFA8FFFFA8A8A8FFA8A8A8FFA8FD5DFFA8FFA8A8A8 %FFA8A8A8FFFFA8FFA8FFA8FFA8FFA8FD05FFCFFD55FFCFFFA8FFA8FFA8FF %A8FFA8A8AEA8A8A8CFA8A8A8AEA8A8A8AEFD5BA8CFA8A8A8CFA8FFFFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8A8FFA8A8A8FFA8A8A8FFA8 %A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8 %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8 %A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8 %FFA8A8A8FFA8A8A8FFA8FFFFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFFD04A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CF %A8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8 %A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CF %A8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8FFFFA8FFA8FFA8 %FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFFD04A8FFA8A8A8FFA8A8A8FFA8A8A8 %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8 %A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8 %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8 %A8A8FFA8A8A8FFFFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8CFA8A8 %FFA8A8A8CFA8A8A8CFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8 %A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8 %FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8A8A8FFA8 %A8A8CFA8A8A8CFA8A8A8CFA8A8A8CFA8A8A8FFA8FF %%EndData endstream endobj 19 0 obj <>stream +%AI12_CompressedDataxdqȌԜGpekV]-qFh4]5a십>8y2@CP5C'@88~ܿ_~O?O/ŇɭoC?ŏoCOo޿mw_q^ǧw/ӻ_/?я||d'~ÿ}~mzB 'x7ͿYc';wyˮH'Ьsr %m[9]%Za绒[u=EnuWZOv5g|?_k'/~^a-ooןˍ Y՛O6:_xcu?}/-n_۽?ۧ?ҎGA@pGs'{ڇyz?{[;}~߿G{~Q'X>t>}x;{;?}O/>$4?m|w_=U?"޵fkn?,MdzXosڻoo?>}xg`>t.Ϗ/z8^c¿'ʹĞ>4?;{ amG4[~w9fS/~vX;~i~_2OF0l=}|\|ؔ7ߪX_}xW?}7?rY7o=zۭM"r&>y֎'QqW?<}חwm?NOaۿa_ҿ}ٯx7ǧo}+W~Ϙ۷O>?ן}}|W<(>o~sk{5O;ܖW j +Ma2~_w>o>{䗟>>|_oOlN}Oo/|^A?ϯ{Lyl_ܳXj?|ҋo߾~_f<=ثf Gݿ-w_\ iqz"~9&?Χ~綴~͛O:O_?}&MHJB ~߷H=8t|;qW^6WSM:oo|sx?lweSL-#͎M.idz4b(r vS#&ŧ{Cnr{(\Ŕ,myf|Ŀ-~4]/鸦3޿돯gedayC)oISSc]"Q*m'O<~nS_yҋ2=-;/m̶!/c~U6*RK/<ey)jʨyYfR˭baۋ=٩n{evc 3G]]~ا^}+Lσ>//ύ8?VOߨ)&;?cdW{yagاc/6'^f>7/mJiz>iTzaE&͋`^3h :hzp0CyXC6߷z_}7?7s^h6MC66B[elfr0!=h6ˇfOB*]7z ~~هw*2'h8-j0 uҖ-fdlE}o2O6A6{/} IL0l&"}=i#62,ʪꟵ|Z'1f+{xnn6o\}w{_O3&Ƥ˔{[-S`H,] 4&E2>/m5<Ê5kAkBf.`ˁK*'`M~e}ӄ7SE3[=~ox}R$q~|?:v|Q[Bb|\Ս݅?6imZIj&vL I7CgrG>>ڭvoM2߾ZQg݅9-.4t;!ogpYgC̡ƶՑp+m~)e9mjGZ[ali#y꘮YSZMܵקJ[h$[ڊ)Ԧv5J&6 ]y 9#"n)}juAw]̾lV#jg1e4zdrlU=@tVS׫iË誓aGܙ&v?N1J\hR9>6nI[f䱐\C>Ճ ݻ_>}~>*o܃wmV<9qo +t ٣fuy._[Y1ۭӺ,\G(tNQ9QVș~vR!?Fawnuc/Kvj{~eֺ/|OVyvM][UO){R 7>S +Mݜy{{>u.3]]lY_LU/כ=m7ݻ){)uNO8K554{=H[CEYrufjh-vzb}P[̇bh?#Ѿݣᱏ^y鱯޾9tx=1{}cϏ}WO=\xNj^^si_=lLX=z'GWI~Ýr\+MdN9AX fs@HFX?9Cb7JfnW aSX,rcI6nrӠikXLdæM=&*'^v 9)hhu '֋9ysg<FdBgAڎP#m< RO/Όt=3o鹍촍obX.QsK*O<߱h0=^jZDy@_Mbwzooϖ'Ԩ0_iQzf1a}: ŭqӝޮ:g);j$`(!J ==%50.?ޏ=z7doFސ>bv؞sIΙY6O$3S̷ۘwRLm\RmhY%2Jl6ۉlocϜk蜤NHʅf1ZvlglR+#l{PB^BQ͆FQC[F_n*κmR9v 1`4j|vf{%LV6_^ "|~o3Mޔ=hlqikes>vVkd4VBƁls ]H.XO*gPlCgPrPu3i\@0;tgƓX][%fۣߧ8t=3$ ΰgI6D:~?^ۤ/%nhgmc2 jM)2C~?)ށAY!Υtw{!vUZ4f7}}oCUp/0l Fvեv]ӈ$[Cɯ`zm6 ʗI17]%CpkѮ2B&yf"mAx>5s|͘_Y0e"ᡆPPKՀKQ*vjl󬢫;&;nx drHϰ]Ng% :0_Ew,$k~BӀ [I5ڶ QHϱ3"ipD_A` Ȧw 'iaobh!Efv&+}hEVe}m*,a>*iُ#8aO73%\m6\klT)!`:20+?7IIaadl&Bldowa>&wrb މ, H +"G +&/mfU1ʖ7+u{VMVSE6KY!5ҐPb0lnv4Ƣ + lp9c ͤ9^a VK3unč)HP 7^T1 `;KfVm̓_h vL j4ۅ6ϴEƧLk lϺ&P{x~hZ ӒTۙL&X6[ :96ަܷLE4뚨qkʉ݀ tqn6SE@k0 `L!KShDM{绺X Hő F}wpMqm6Y]1p$c`lf6g<í}7ݶiT Y673J3 XV!e%Ꙕشm6jZ A1=zp%(sM0 sM:G}.]J[M%Lhnj +#&Df4`v3v6oWȾ,& GQ[ l,f) MojMlERTH ٞSrP m S3yU֎'aF:Y]7F+,5gi$5nц /!:0NbۄF؍aVȼ&$t7"5T4Ҵ.EۜH +*،+{6ًFxgjיQXo3V[;+Yvql@"ۜ-[˙Ğm2̲='x4٤NMn&e(V mi`j1: |' Wn1AV! cmw3}ED[,V޾S v*栆EpX9 TaG¤XCQx(K;kyzļ+fxW|ϰhhxk]Ib,"hI zx1$g$-1tx7F|2{`W&ʚ#5ͬqnr=$s?%~S606t.ͱƀw b M0\\Mθ7J-bPf+`sc$~oL6,ݎckvUM"|-[o# ͦA)[WZS 8o3Wft4Ah6LhW͌bݦu`ۙu`26e#Ƿ6$vh6X6[զ-%bC@=иfMנ>22F7 k0(`+kUӓS`ح z+:&룭MlqvVԏg_+u{_Y}Pi[FUcg2LTvضhUS}#Fuu]Y0,LqZR{Gux۹1׆*^['G5G`Je7mV)guYq% Hqum}z=hHMgU>m\Aٯ6i&ܟ. 1y*C\Kۣʱ.SY`vK3awnj݃Ѽ𠱭0.`2F3{"S6zP;̲m>4  k-5XW/aqd{!!1+6s ɘlF4pl`LMfOanքELJڣ49M\ͅd3+Lme*9M،jrHEb 1o1tQt?r[H4Ȧş1$/)cu76vgk1kj o q*B,TVI0y9ŦXzbxd()$w33ƶ4TO͘US-̴ȯyFuQ +lčYQ.tx[m2gL +)\Ф4u8n-m݌3e''Lv;JKN*TsK3V)f+s}:H?|ѫ@, uMJLS1(3'!P\ň"=k,— >nw]%O5b?i.պ\vЈqxŮgʉg Jdn ]nGxsB=an) 6 sx +KKX&\'3hٿ/FN0`ؿn-ӯLjEN&C:6}6"6#DMoRm6yX:qJ,X𾌩2oDk[Q&u +k+((%lh̦3)\|ejgoNhK +&!TQ7$Yۧ uӜuFK5\ ؠ3X0|ml؟dEt'EQ:ܽ6Hg;#@elo3KohrS7UpT:R\O#HdnUԷh^5_4ܼRTiQ%32`61cJxT\ǢƧ 5; x#J.u5rti^)N7i~ ?E˜ p͇ݢpH$u[/'#gRP:YO?4?!3_W'"02i2rS1)R);lB+D>qeBN3,X[5&L␴ +C1)FVAXЙ<-g 7M,Q 2OHN2"xX-ʿi Ã0X($PcHUI*vֱAWaV}G5Jȟ(TCSH1óۧbzYr!Cϓk#*^/!3?@v*UCˋ:?9 a/tg y;cSt\KTf#| +}v ):)TgT垒K^%Q<mҺZ6$g~l2Aq+FSϘLb':UjUO54i +OQ QH~2079k̓l zr:XOF*EP^H! ~=NGvtYn" q*:,B+ +a}+E5]siӶח(1L[j+FD;Dw"l"يg +mx \ 6y6 + \B-4H*<5͍dTz@]1]ϻaIP^wptOPxT3HCP[b`)&[V\=!yZʈ3I #J Z4=' lFv1Ff ݚ4/n-<ʲ>!BDd`hۛ+\X [;Mvh̐X;Xi+$2=*lLZkfiF\b83&8Q&Gtf#g& *L>7]$7W vX5uJx#t-9c0,p qvD>ľj5aJ +WkEJ`AZ mb }sb}k ,."%)cxr$ :p V@ɱv" L,vc0Qb4u4M9v܌ֺݾϐ Djl f;(<0y0*d2H;isV{&PB&a S:?d_Y#R]'U؎4!̊m +,ӒՂ(U PШ8ՈMpkL}4Ĥq6b߃80F?W5XL]u Xi^hǦyHQMÝz X4mkfC8* d$\q L6uEZh01`/'KT޽hA0.`EsP$VctBl"̯,fFHR|{ T[tzubpM+U0Mg@X `,͊K1TW_oijb`2 C=]D`dwۍm]1DI5z@%i;ux&3sJN2tΐ9N(;Hs⛳t0d=LIu4(m 8,qmUh͌ <-Nn8!NxGBfh'4f~ۺOunrlZ0;C6fbq 3riv Ί <~ХN p 6L bt/[i_bDЉC&6?!ꐒ>g4T;-֪[%l(=H7`8CZI3t0TuJo ra8,Y fȴ)_MlDe$Ά[ 140M_^HY7)+W afo`Xd@E[ʖ򰝀.qv|NAn& Q-44pZЕfC`sB^3.` mXB~El_efE;f jG^+6QA%D۠`+`N[cZ +fRTX`"t|I)-lG- Z"E%Pśm0dZ~~[ JB`m iTѪtd1:X Γ@b'7?8j  ;Ȣ OT6AHH@zoD_qfS')@V5uDž*Ldr@ ɺKJڝOC1f5Z0UW ޛ!Y"م Q8am̜܈mH up\(gnrymLH|n\22T9P~6!70E]S\",,;&+za } -6G\ȭ\ݰ!V!BLVT8֐S8H%&5ŏz}CV@J3V# GɠΚY6#l`\w,0hCոj:L\iN}:)s݆fw,L)ot %IFEκ+ +&ݵ^s״k Y 3?~] vk&I>#$owY2m 7:e[(JZy]D +f ^Ȃ"W6y&;c7^ AH=I] п:u& $1mb#9059! +4bac nˊI@&;C?Dn ]7jv6f|ZqU{kӝ*/A@Et<- v5q9|)ش~yM)֙7l&FByh#ާ5 +=6qk]7YRTLee!QVzP1^|PA YEZEGKWFNRWaQ8BM-1l% ҥ#֛Ь(Q[c EƓ lPQ̖i>ҨQ4ÐF!y+QZt*y=.A;e{k!] JCU6"H2F3aS(m3QV[46?<j) MkeA9 +8ƁԬrŶu,B+V!W8I_F4e)mt|ӮZ9@հD\P݀gx@_uA1$pLDt +7c?]z4Ktw'f"ߦE!mRA[18+Y*'Df +{ M33C6 .PG#GRt7Qn }y|LK +SJd \d~G,'܅͊/ +k; JV|/k"dWhƨM +FR&bƂ6g`*f[k3Yof-}6Jy9<9rH%2-J%&4 + ڼLJǐ`H6 ^vH@'HtBdAiE+ v( ];gVd,@"MJRYl* /b;wԱvJ0&I~3Oi"LO9Y32e)i$ѻҎ@z4j$-U +8AD( ^_$gi)&OnYj\ħIFE".?2lҏ­ByrT +ve tɪ`EGٖ&zYsov+;-DŽ^&_Tr: =P'5-c L<V0}#XIV-`훱nkqeTWfV"<9HLZ LFr 0#?2Ng:-L*op)a2[/)?jBL R&?`&38}hR +Ǩͽ$lW&Ǽ jc`} 5d=w_NUqsܝMz87eЀH(ȁ zGd[ _ulMB 鲱`6Q]HҠt~B[h'~|~Ûw_Ͼ?܋l/ ?d3F۔~#Z\7w"2f6iC$W๭_̬0+pF*ʓ"mSn,jY=A1)̪$]Lgud rFU&fէTU)L5-نgLͦ%@xQŠ7TtL$KTOQodVrW|HT[@M[6^u>UFuĦ oMn26~ n ) %E G|eJ,Tt}@P~l<QN(`+T&JD;3kKEkك^nj:E]s9U4ѽ#t[zyRl'˴΂%N򗄙cB0H:N*BKUJ03*>iPz|R+CW)n'3dhS@2354nJ+V/b7Я`Xr@S~lYd +*;ⶐP;"M,ҠZWa0o5CDJT )rlĥɇeh P~+}d ="nŒ)riEq 6,Ѵ0'2L%'?5Ln>9ԧ,G5`i%mO'@1ɷה=07hqf,ʺ4t[Kkħ&N`=@Űcnkw#06x@wL+UfHD~;IirZ˧q*Q +'2]Û*1Ʃ46.B n-QLa **,9Q +΍&]tВZ +ުLݎOJ$e^ ('UdܪYܛG:avMNk4:LEh!r 25pQ.2&aLsT $4w tt1UD1,[tޑ(e\L2%higsI#YԁDN C껊AQ?6i/bIh +SI_lg*W1+ Fh~7R fKӛLXs=gU*<6|M3,Q{1U8Br3 ]^JsPF:\?'#/%EKҽ2۱\d)"RT<Q]3_ɨ)0)`>)9e ?Zv- Ju3NAfFtg{#>1\2IAi8,/L gw<݁Hк D43ޫ+cF:#"`C!) A ,zn$ )x۪%^! Lw+;3 BgUN]͡U 4:R1F\5h: [U{f U6a&W%L]**Z,#,2AMl< "+6-n\/@f2e'v3A .i]6)dT9MҬ)Jahn Rv,UiFi%<ڠ3<#'.03[!")Om$l nkLƛ$2 j)у 7g(mB CWYsLYYE%ǝJa^9mo5`-yu%VY5TBiBLnƊ&8;6BJ=6jq7I+ĎUbP!uJ;c7J${(5D+.U!rY6')$gt'0jS.fTJ ^VYz*KNdK ' Ճ[*L(WXSXYtWΦI-K}9).Y 8cQda\\L5&ߧFRQze-hP -E,LMX5+*fg؀|%x]$rXGJ"D3ؠ eW>Z@RnQtU"ijM/j+A5FzO'2CM{'ABUTŽequJC/(]HP$r`l}j]lAo(*2*P P"$£,m'EPT`hFVSJ&p0h`m#jT+$㸐 ꇂ#=tG )3$5"Md>tފ}E +W_{@M0~)AUf?2F(S{3єkN]Z"9j!ē<Zm^ bc*;'}}rQF!A'99[ W o$Iͩ ϐE)W%QqXKC@y(ٜUXY3Tro(1=Yoh#7U{& Q^̅4&$rSYe7I!ABh+wtA=rQU +0n:>nJE)f,G3j^^RE!)4Ry6fAMVᖔP@D %֊"{&0VTh1wZ4Hq]ƗNWK7_^䄻wbൢA8u*5, Ps]iO4{*ތx]t&dcJ'Bщ֤̚dR%+V]%;TK8^)W;5Up`* a鞫nrFCW*^{({}9 )m%4UGy}OKjH0(3ݮ/\l,YPǻ ՙgRX>5/Gޔ*1~*Ұ!*Q%M.NUV UhYXq:(mT2U ȸY! +YU*PT䖮r _ h:R@xr~mu~\=*t:_)")$DPM1wCLlΣWj[e4ZU% EքӅTʵbLJԄ4iv*d1Q4xISepVѲWTaEczF +]fB'(f5D8]k܌a/!U\C6)Z#9&m] WOu.vLKonӯ9 Gr., 0p !!}B* ^sKq0X*:,#14/U*R/ƀ'y.MnpVP0yrW_}B}u؄{g m;ߠ- }i٣KVn+wê0J E){/DTU )dcl^W2{=mK7UY>*Vj~Mi R@W&VKEAż2Ja\S+UAAKi\E Msq|a +;$6p&ůCf7W\./RU6jVɪscRCФ]U UPOqJad i~AS, 95\eB0AG؀ wNIX:6ozlxFb{D(D q_T_MWSvHEkDy{)c겭שU"9[6}eY|!hLKei!3i:Ne&WMy{%3Ƚ!)\JُlV7ǑV3vT u֞dA jds +ލ1 I,Y,t/!4J3-e{~ Ъi5LUuTȞr'Fen ՂðvCPV =KIO*oɂ*UC渪y![^TPRڒ+V""uڤddڴ $>9Z叞F9eUI1ztLe$ ?@%n^S:9VB wC?HDSNTNu +{b%qu@RW)JWm`SY.p(幧Idr+-w,hPB~LЕO3l7X >a:ڲel++.iNp?4|-*I @dE̴,^i`xE@7/~\EDYdya1iџl hP>Ŧb$(Ϣ*5TaIuHQCZm-PQO|9 Y^8<p(OE'T@=bX{O)IQQs Z Vqvir(>j׋Q:5I *[PpKʫ4oәM7  +MWJq-F`(t;UqT־{߸GO'/&I*5* +`32&(xęrΠXPdn~&'/krv^#{Q5׷y̜-txu3(q~( +w^f" ur[\9LQV=V6s(^Q]q@.,K^NC +Y6 OG"0OͻO_귟o=/zzOg??C*@'݆{}z$G$|nc- +yZs|~u'/awcGvSYqB,2xU|siNW;s g&݉qN%͝HniLϧls]kxFўv̞a6myB(/ 3LW<n _'L[F\%8(ȿNި\ 5:!9 :qJ^SR Kru!cS1+*lW:-j-A$Ş~ɍ^`n&.(~icB_W誹$\ oYsvf1՝xpGԑgE Ki+#K3Xi-ɔW^_zsrM,g{߃P8(B&oհ)H| Nc2 >Y4b*X-@=fJ +/x>RÇHLa1'b+D$wbR?1I8GTw:衊EB%Qy2HD4ȑ_tH;0!_3ftqF"L@8 NV U`I"]_mu*05J;c7O>#J楆&1we_Ѣ%U+y/ueI|e[ع{ 2z!q ck;C/>NvT>#;xuszX?Ľ\픻;]ߏآCs80R sgA>3W+X7dm]d#4=5Ͳ`ѧ7"%>Y)a) ++ZGSfEZX1u?6 X C>^jKŕ?q- *8%JfK]<7HTujHShȓ +N/~Jqq +6NavE<{T骶@3~S (Sg\zhQ +$8MRma7ǔ*p*XYHwpݖ):3\iwLj۬3mgPTg %)n~ib>1{{;'Ybw=X;8u*guN2 Nkc8K,JN);%g?2qBcQG 8+.\w7wԡIt |K5_sAME '8y*!~*3L%sPkfRRIwjg;Udb]kqa X0 +L*V9/E|@uj)ȣp*̭IlyljwƲMc'bwn<$I;3%xr/4=Y<H?2},Ib]-UH6ٳß$yi$}ԒC;$-LE$#C6ۢ1l8Z: ,jH&YN B_]ֽN I8)ܤhVՀ:;ةsة!"DIsvpr8d;tRk+#&9!ē \wYDLDDR\#Έ î +aDm!U%^8. 9.*F.f ̪U$L|"TRߦDߪi#TLgDjT$90yAz(U>*=PwG&H=b_ +**{"0,*1N \ob nyZ YIg +1q*;jOp`֝R 0ޏ aժ h1@8"~TI*PR#Z#ďfnp?ez@1?^iQ+ҋbQ9>>׸UGU +Q #ݣ9Y14ʋXD,:"A;ݣso*ItIfKs=i"@e:bGD~LAݎlerF(1ٶ4vVGR3KQ e9.VGݾ +ǽX)YAQ9۟vwwGɕP([Esu}n:Qq8NJ**̍K94QتpGRF*V'[`lKd xV:-F Pw^ŊIWQeNr*E;K:UEQuw"L*ItG:U#f"oG2D28QOԇyÏj q;YnJ2t|'ablE^[Ԅ|FTHZP6h'%Kᑋc8Av@؞G"'h>gTS߬}*K+םCd+:v;'K!VY|ߪV/o7wYR+*>AiF |. uŷ7fp,jdߟY ϝĞM _h3'z"<[`p ٓٗ!דqߜȩ'o8J:sipsdvN=3*=YBYJ3WFgTz. l|9t#d?SO]z^lũ?99t(DvN=/fNI'N%Iok4.N=ǘΩ'*3*=)%l=y#\'q`&Ϩ҉A/,ByJc] zJLKYgzjd1A`Y;6/OޏpƠ'}?jMv2(w<P ޑAOΟh<ǐUN'|Az#d餶3!&צiΠ稟4#|j*A1xD57G4..=sHaSW҄)7:80Tz+*|F' +CNz$ܻtrF8S{;L0mԁgyY7Nt 8i$@,{'$^e'd[gH;yD5]w$ΣQXjfJ Hh zgl3s?cA%zT2v=51>#Sm!aތi'ڛ8&(t7c٬D w0+0y_!a'Իj82]7N=Qin'\FԜ8HӮd?]ۢP;e" t@*-@zfTz@ Ne&@> , GWR$D@NT |{vyQ.=-(Qz?J\w2=J`3LONkmLQ"DxրCX4kXHlSՋIFH͂7G4o~HUZ"K<^ +XsHJ4~ ^fF+};Ie=NiЄz33.nU)kQ Sg d&zvJ`L-KBבl0-2(L^,eGr2Ur=; G2QjQXR7s04c-v14btE*F<ΨĄ7'1*j&u@ϑ1cDS8DTA4YvUuj|` ",Չ;__P)Q=hc'S}D;wҪl]1{%+Y\E&qrEDT &H\@eXENQi}GiDUxĪrjVj;Vb*pƜ rfu߲d!(qb uqauGݞ3`9pv'j:,qwe sRZЁJE9ie`u8c"HE\Tmd6T%[=UbRuȨE"'vH%(p<cO{]M< +t5>2ah`ĞEL<^39`keg׼p +1>>Sx/uZWJUjPP)QgI?Ys%8Ghy ^' +HCQ!\z]P"&}͠Fi,LQ"xr|ki\~xmTҜOnN{Lzʗ[x6gD3߿Q (&$a nqfo3sFg manՇnIkM0Wٝf--:m*ǯJ 0޿MƖ_޿Iv^U:%`xvF^ϿEz!*a6Hu?^gNqo8Yk["5ǻ7 ^>6naջZ鿣.&~?9ݥY}Ώ1ET'ݕۖ~:jؾfCoCw~k-t,uzHU1W [tk_MT")o -Hv5|.17,К*ۇ3[Sx ߼|x|,|(/3>rFcs׿_$H; +ˋ~ۑ_sV/I9iFEWHB[w$)y~7QrKg`W %~;0Q:aMLZ32 jSϿ*g7ϒu 8W_ q +?l1c#of{Cu~8_n,GM_/&7l* ,mhӏF,l@f׽6f+Qѥ/F|{'~ͮ-.5+$6œ cNO`oAC7SX2v꾱0z(u8J9 +eF5dmG(l'v1rC:CN;Ug\wSw4Clk70xlkGJ$QEqqXy8Z?91< +bd(3@G Hԫ p +=iKL1r;8rdPƑts0|^${(JIVN sC+N#L&߲J>O21r@ˤ?t=s6854oom%ioX3B,A)~9u}鄬z_^ZDbT:_o@OWh9^eHЎQncHwGʼwA5wBB)ۦ9-Oy +D@!+'d +{-$|^m̮r8ײ٦l +)yw4z, /Ϩ/ЭL?p<.TI!z9?lTd=΋Ӎ,^ɉtM'&>qR*9rp*ZvRٟ&"x;K㈆d{`7{BxAƽ-_{@rw{!;a@١Rpq +ѼB~I3JL`n#hOfR pDLggg+pJ:pdQǾXؾǰ<6JCxt{@cTyǪNJ7؃wb*?|_qN!ա1pX2wkM gٗ;nڼ^ppfsXx\\C{9$=-qdezR4A"#SΈ =B̈=ҝeO|h9|N99^58B/(#mIoG_U<47Y{k{L&<8df +^J :)Uw@ K먉1F+@ҁWe}Pꈔ7cB`=·2K0_J`*y0_5{i`[>f7 +wvl&$Dn3K7?/ZW,;yy{PA +Bnk+=tcNhh\~c5^6ނ&(};i|M|z:Se! 0%_9G\g Π;an(ۓHfeL1Nש#$0b\g`-xtשl4oS[1%ĉRϳb@NwHzqw;Wg}󀟑+B}N~{=uHxO,(Yfz`VX!dȟ%N$wtm';%~s H#؇F8^Kǰ`1j~?t~|s!{70 +r (.p?;6O Z|` +xX[lq]}L>&ef/ϥHZb`Tl$"`b'? <,v^+i(켲s=jJ`;ܫHʫ?Ftq*d9_An\?=Xqk1\v7JTlk;R@Ȭ" I&4iQX [ gtNxyRYLWԥq u^^0IUȫ$3fa0gx߯|i~Hgԡ%τ`O^D_3nFE{'{%u:ԉXyn -V6k_DE``t>z',$?z){3=b&|#8HTox~g!*eO ~:U6JΔJi3q?dF<֙5ŀMevGIr|Z&nKV?N}bJ r){&_Gݏ/J/2έF0JyFDCrb +`ȈL +ou>*r%@,8B{R4+Ma~NFu +>d@Et(l>PɣE,5D:2ThFkTzlfdbBOJ~e7ZsgYl +y!E٫tgP.>?n*3ں|h)nb=ĀDck3)>/D'Z +-PR\*B{Dse*E;2jR" L/LH(rrJmb }G[=- >ı"&m?әCl]>\IXD`Gf:1VygmziQ:֬g|j=H'[6K׻K-YO~mG<ѵ;ӜX(:+lfцj56NLo< . A'` Na^B[~^ v=5#sc<1Eat?\>`j*A2*/;nfhqseatQ~Rb?/:B\爐V3'H"~`vIIa%\0+O^fBbiҎ23EaM_%Lb'A;bM0z$ĭ|Vr;os~%j! t\jYl{M"J͘{Gf3Zd#~f)l L^6PB;G24ƿ-O}ɎW$td;#+;?+y-JUo%ϡ/h+~:)L{!4$R+gt:Ng(7X&U?\Q4O6RIUY/gƻ%Y!X™<iEI*5𧻓P^H/jlIͻMm%Jyfvp ހ隓)#(tTY92\\sivSF Tm1?G5i;{ #/}n iTYh@AcB-.+յ]4 0Ԇ>#ԉT%B >WN3̀8SmE~z :?D`AI$D wbz 7ɳ_~-бoarЅƶ}@afVhV;{XWN_-FЉ⮟{]9UwXS*}z$Cg÷ty!>njb9WV.j{z}Y_Mr;Q+jx@V4l^2aG +kZ cMي5>>_/ ܹI|7 _DWnG.-s煺2z (k D^o f0GM#~Dl/ CTȲl3ΨNqgHw/g60 4|>:ϟQBb(yd{F-wdQx#޵hi9KFCKiY,bt!凶@OL;[/ofҫ$v<vZx K4P;JG:Qϕ}ͯPKQҌe'AƫVġE]cPzLjF|52 ;GO!CeelY`pKF`e%j(g93pMOd|+ۋ<I#"Ҙ:mI.8K.nO} %@˃ _囷?颦&H{D?EX7kO-o~`w.ɎB߹xghq>[MCF͞@߿#)ϕ{zO:@%;7>r\=[q/ 倽x!ՈF8;p@;~_8QCw4bz{i|feO\JG?Tn4m_$7u,:3HAa8:A]:eQW#rH6N0kQbn{i]V>XIQ=F8٣U։zsd!1PC3EyxNmЏgE?)=إk{3x FWџ s{TR+2i%;u?F5xM:BҒٿT+^Cw{38Mr{[eyW>^_5F݉ywh138ls~EBwę ǫ'Dh;I%@TE\N|X?܄#;\P?n DAWfa7NP2xUH9b$F4 m;׮(Xl9ܒ@m=D1׉=9{I}: ^srDô(;A6"oC&[͓%<~͡6,U|}2QluӦ,jαϢ@tW3߀.[>z0T _`MzX;90"-=W3)S2o0Bq}=*1 Ү6emM2pN // Z \3#w|`d($`*"ۣ%{z{]0 +3sbP˕+W4F?`@keO0#@*2[4f/^ș0wr&U-jbyEX+j؎5<2c^Pgk\@l?ZX\HG*34A *jδuZUZ1k dLbjn)\»u2 s!-$C+k9H`Yv2 н0R{K遥7}~EըY]z)KBڠNc&h/ýSN+Z6G1$@|\_-t~*ĕkT!RULzϾ{}S(FHrDVafZsFH)=*GdFgCP]%@-BEH X%?oV/wJF7NpvZw3'4iΡVb̥Č+)p,us9@킼&/ tSutxg<^ +4Af"xK\¨L{ݪ\:8D3z0 8x=/#Nށkf߿E\Q}mU{Ȅ7w"ʫ8IKJ ~\b!VF(ě4ќʑ&䏤;@أh&S&R}(1u?bYGx.gce* `bbP_`؁ycQDLB߯/#RChIBC-z uw oQtV :((O) +f:J__$G> Xs~{9+#>$]mPil<+D2݂F>) [SbD%}+GFPYg2e)_%S;G4;w`.*=< +K:[U%AeI9%ZNl)Wnx"$ܞZ*,@\?Q]뵟C;ֵ]l~Y V[l$u씺8Zh L*z3KSӄ%=ڿھ)LbNV-_dww+7X9T0$>u5:H2`LѾJ&3J~*#ܔu]NrN!ؒc$p:fȫsw&ks +KlƆ2}!XԧJG0(E}Aoq>4Ȥv"t)0Q%dƝ=߮ig@ּmpԆ}DIsVᒴY-r%ã``EfqsB}zM),]]n(/8^A>LpZͳ_ +8#{,(1JGRfvߓ%g#t֢|\t4=aSd⌮f{Ē +ڊXLj;.+0@kF-H:D/MmС: $O(U1 ɦEkʃx>Biދ0cx.&_-3{ݞCN#}A>%VY~D h?nT5`*M <[DSb +Gj7 +?G` qκ ^uV:p=y{)oWB>GBUPpMH/Td;N8:<?\.B +*&"=-rQA(5y<~G%]?Vhv3CLۈOԠXp'ݜ/ڷa:y>{X.OUnm#2[i^Ցگ;4Q?wUrH͂DVSv#^B!bhZtF&z~hXB1y1:L +zc+J?Q#Q#>z+}XCfv¢ώIS=V,SD{\i##^VqDa]nEz:]Jȋ8]> -U +w!$LT2+EH{t5[ T?K.LPMX)TY67T?+BMWL۝ɥrDKQB4HBU`u'KE+uGBwϙNS~}7&4t s|9T~*2bA}̋+D= {e84s !Qr?Ữ$AB=bs55kVyp6`wJ]p*E@L&>oZ[w\V(HpvduBzʥݩ{}r=/u2PΠl1jU;CQE(]I@J,;JaÍʂ ߳T=U۵#O5kDѡߚO!j5!c>iߕςРu{)`?Ayr6cu} +"e}HG~6H<*,M~K$f=($ SHa +wR3~^!/ gd \) ۄ2{LN{Y/>%裇Fe]T@A@;^)Ngԍ@Z3_qshF<:H1u,y٧NyfM +NS+L_6S#d+<'Y Wx'm-N>CB'E+hRU@C=:h{~p~йH"r\aR~`{Ԛ%#%=dZ;ù1'Vt;qƜ6!W4ʨh3[F=2\\r7JB0!";\gթ!TqAET}!{c<a-R{ЯwGjGD<# lRY4»J*.G,룇j]VJ[e˽Q#ɹ}~9R +5\ӊ|Ðj:aT#-.<^V:Fi}9fe map)=JzG6g Ӽȴ&<[̦WCJb$fȧϚKVV PDڒ#tC=^7<l$biF+Ez%瀬{"zXl(vXVqΈlKӶH鍒ϡKޠ#W$ovP{Njo޶GuGՑW⠾ 4grW H?Ӛ BY-^R^ 7SSP$YhQyS|-@-$iO)6>u.M~G5ҵ}YvT;Y%ى0aG-ߨ^%Io^Rd&q\oyzK Deq-P1,񽤙4p.t">[RqIHq5dYqsfקּPVO&NEA'.Gz:!^w J_pG3z%~\`-qQ=.;x䣉ڊu6:OԌYqZk[DXjӋZU3?U厛i(9?֨[n8 :m3e7>QbD2 F\S\/? Q|-Iۥڶ;,eKL!~](E?5[R* DVP# qH;upcaohB4Q3 /b3!rF$ (Jjh"<6|љ@|G)ȧ%O/[!\CʑP`ǝ#Iߣy[`DK@Zr%'RCx&aDq)\1h\ ZBLn:4_!rETTc@S5d(onˠQF_TvݎY[(׊~3Jbpe4e; g{fr¢c9P =Ulw2t!HG|$xŧnwW'ua{NAywu1)#z֩lC{Yh%Y6gкA# +PlA{hjφ[Њguvsdvu^[3!~>,Qk:|A*OX +Wh[*S ًs蹊`w82ِM Ǥxi6BsRrb)P=cx.m.SP{̯hTC@io9UD1w%gR<WZOAeQl#Ey:=!]$K0j\A6NA>rkLvR\.zXF# +]{nuޡ5~n->D N.CxOUJrܟGј ESwUڀ^7#^C7,뢵D +y J!Rg>tw%Gƽ9h?;@V=Jsr=$9{A}V;QXB(E &MmVo1Źte 3: 0=Q4KBeHUhH QLk^T뿂d9OiўQL8Xt+ { SʝwhCsGW<4$S\28FRGV4vCy-Z]ݴ*qJމE-yx(4J@ Q<~4 WʓuZeJ<soFا=_EEޯL$m7+/PY:PQV.*ٴZbPuD&s`Q$~uV DZKa}Q|ChsQ( ^i֊A) "B~ J|sĞ!ϗބ*8 ph/, (cܱwBj@D0C }ST+ƚw4+Y=QYW(bF!}HևB1=&c!Gyge_j#)#fu#7- `ĂS"R|p OßmЊBܬ{AʘIԺ::3cĔz0B8BX66&#DC!&QWJH<[g/3B( +)G.7UE r,[ B/!E0C-p̫sCe2táܯnA|Jfԩ+:[Au LpY+r"ɀxH~KWl|`""U4 ZQsSCVR)%|tcB+P=H5; ͭ3R*^s75:FP՟"]E{JdV`҃q蓦3IdxC}i0܀ ش/uTb$CobRSbkFDҿc3t%gҞX\fk + tҝ-tS{ Xh> "TSwj2*#8h;| |UfFwd=*X*m$ _K5L +-T(Uϙ4ޤHl{tΪAfDT1Z{e˃GZ@J)6~z+͍ȳyaJ#;h~C-o +hjI[_Z-+R_Q;!ˋ:Xgǽ^/ڿ]j^Ew %vr=oUʳTlY\7_g6o46_٪ط:tʻ8nL,y:Y.1ԧUP\ NFxZNʞ}|9g`ߏ.x(3"" d!gI;}W)hx2>GI*fyiY^ԏG B4WM10#.%lH1P(A޴+7|-`n+N.YP1+Q?J"WJ{y_*KHbV(2}!<~Ϩ]o1"r[7/[ `qpHEl͞G[%&{V+ȑ +r +) +p;])jX9Zrz5`5qC1y&j1v@9Y$kEdϧ~@%37ޱ0 ygþ&dR#SnW^E-8 H?fj=*&{Dt +:ME:ER\w K)rr/鱪#d S {k*Y׿q Rgxף=+WG3N0gÌQ\u?d$AXHCBWy'3Fjk/D1++aĞ tQϐm B']4gc&G9VH u!F +T,8e5t]ڧ +ԕgМfoV-i}<ɹH֜٨72cAX)YgL)&uQ?V1ʱDt3婏CN%{^ `(p.~.v5✎g^"B:L_l<Ÿ1uykP{YVb޺^9u-soY|'">tuLMYEdpH~OЦs%K2NO(TNʱUT=G2Ԟ5^ȝ&i!!8 HRS2pܻo6$QhmBaG΀#t%yG.G3\(F1ڭl)/(NߴҪOuVU!AP# 96VCVI`b,w(-bez(I3 T嗦<{xЏDGPk8}&LMBu!3[nH.5gPv/n2 G:hK̢QjWrC3:| |QCSlyv;+2ŐQ?H̢5P!F9oG ܫ˾^< +jQ@dN?NQQG>+4ڃeAF}dĵs"9+Co~!@#l(|Ky^މj .ޔl"6کC<605N"(.2G̓e{Mu)w(_D"YK̢r5vO ^zov#R.# qT|!2ȧbL\;gtp,g0OXݯ 3ȃ:o.fn"A udbO^T+"L3!odZ@=y3Ykf͞rn6cy_}9Y R;2YNuj^FtjdSLħ\Khӻn˛x~bsK&uC^=ӌ|_e[3tq΅F;gtA耘YCmʣ|J&L~ˉ4eOں~q gN|ߣ;vlkۣZHEG'e[?LtԜt[9.(UkM(jڲ#KtVJYM=JЧr!'pH@=<ye!ħ,Ʉ/.#?_Mu]u5 , IP.p$'쟆%pNà豑 8d2AXGY{3&HBZN0ԡxMŁGqϛ9+RamxsC/"v<[YQ$Z=sbP̣ "fV{i2* +Hf9@_We!Hz=Nܓ^a.F䑜5wr苕G~iFZ$' -|#4~a|q: +7KR,قxҠrs Ckt]!HpD.S-Þ )+DY_wN߆@ymWONr*\dK$Uș9(J2BCl@ؓ'qP)zM']ГleRdjUX}垫0WX#]#Jz\D1Jߟ (u_z8lh\u'ׂ㧎Y2ug.sf|"u prV(ּMZ6((p  W٥#@D\ :-ͣĜ7o8-Iu+HV։7^??\LKQAyCیKNwlQT[_wۿ??_?~}?oo>ZyN6]?$QQބGWSZFO4 a(w}!*,鼏l'<$ߓG,YY6:TR`~e| Rӆ&~/}GeY*P +Dɤz.5N4!r4(^qDX +\'J$󮪌̣ (I҆!i"Y>jϖT0'!FiZ"ؠ&S4GɔAVgoLRjFwڸcWSJRe3{]DcŴ6-mƬܳl,a3CUw{ ,]kȆp{o̯ZBdO6ʉ瞴=8%U4AB9J,a[׏ ׋̞IUNVwJ܋`+gA³—G}4GwXq^m{7_)\yp{y7'@!Ga]^wGR̉]ņQX]T%ĠHNxAUs'w^,^"㸢N|(6͠/%v/BҸ;;}%p_chb+#)ߞ^[cH <䯸 gqb#r-NtW|$ w~eYؓP~TٝD#?D$ ۬T~{G)s vStj?E5xïW(U`'Td9 Ȟ[}8}XS$3 ,O+bI=cyˡ,N6LS7xLvmx H\FN{ +3m>GqV<%=mrr*#HÎ x@U^xJd^:83 "n,{Rx3BhEbXàAE5(/4_lR&ԜVk Z/4}qc=zd8qU3?}҃qs(&7? 9?pCh{H +'JH*z +ɘ*S־NãI OAUY̽6?c6il5]sIAM$0VXdA#!¬TM8c^oߵQ"vx׎}O׍*>/194 2ZrW? + +4Iٹٙ{]1ƐPK˧U2{ێ"WT _;;gC)BᩒG +)q~lP Qyn%!w%U$f.HwA; 'a/ \Z@޵9xާcX%oJEs}WU@NBde޶dNh3lڡ~2$ŕ䌸 QCo {p]`;B0Кf #҄RGgiovaD+A}iCK996K`>Gu*V;?Mb/2eB8W6lij|A%=~JRԿEڏf@z0[ os= 1KCW0QւjvSQ&IY6lƗ"0f(éhZUUnPO7A=u7F"Krmȱ.i~gzp~(_`, AQgaP +'^:#k/q r` muq%֍id<E ۡPXt/rE]bD CP=曊LŶI"B)_q$^tf+śu{adca +׃Z~H;ɚ݅tu-c2gzSE{'ϪyUnMK +#`Uw5V%5.;iR \G״ WC> TǿJKCFUl3}J*)cp]ZC%5o7곡3%$w]EIsasYЇ{>m~y}ĘN?swm8r7~\Wyhh&4"Ѕ(N/Oh1.}hzS-C6~Icڣ?)|'z.50VhxZ˳$GBL^XЂI07C6\2oCBE}۽B4RTݾ3-2epiEzUJ+-{B2G2{)ԇQL.AV$f6@P>@Zd,D;4UB[22(| Lͤ)Bi0h:þFxYV)KB Kp~wNw+TX5ߒ%7Z1fNu(^rŞy*g c)wPy!a;y +TiTCER;CO;2nʵ?.w)vX{ﳱ]kP'qu߹2Y%y4dܑ\SagMͲs]z !|[Sh;]tp@W!V|<(ATY{10׊U-JD~.$YZ+(20O9\qLMr% Ndkإz+n9o6 +W`Ey K~t%G=ipR G{!_~[`1):X_W=XtvCAԬ>E8^ + #uyаiBVy` +pbIZA'AigW)Зb:!2jF=7,*^ճf}(smjOnO)7{wa}]#u9Ooi([<Uv*ibOi_gĶH$wn0ZDO19V d EvPj /D.{~OHZ Oxl :ޫnf[LN…OGAP}Ex@S;49JD W+ʳ# 1;+﹂GxיKD}*s+AЗ<XpVi̍|zlB^[pm{t mN-6wt"<ޖ)lF2m/-B{E7wr iIT JN'(C1dRjLcDMH=|{V Fq;=ѻ㎘/ {Qq-F*ߗf쮫È̍k344G GрVK=L&6\y%co;'Fllwq?]h^*O4H>B'q9Fxgo rUłopD9@qDSz PÏ)%X[4$Ȋz; +dN99A5 ];Fg,CZk@@^8.0+Y(%YMEn`6ډ#`sK5bu)DAzl38fjJ+?v' cpF@JQ.EZDwU#>Yj@KW /,\e?"O{ vO;ޭrL]d=D/'YO]»Ѡ34wD + ժnTd ټΰt;-Al29Ckeiܖ{$T4hr`4с5MkX_eV8=(3 D3j\8>LgGD#7ABѢATc `­+;tac?FwCsU@i'˞0uYsIG +ixpƅU^`~]k轀[niz˱Sp|Jnρw-sPAGNv#"X}ت5OJP*z*˒oᰠL_(@Voo;b03j8f֛PjxG9LbSj+[l9 &FtJ9B霋k8gZש"b6R4"h{BثAǧ3J0DΘ"eyR䏰lv[M3P&JKgt[0dB+h̨L̪< .z%w(|ܨ91w~̋aϋ",{W+*HWy҄]j7yڞ/(hu~~2] =99"aD%\ +vtI8KƛՊk  +(jYM[SeFz5u,<֪ʒLe\baȀ\lRC +%q8囅`p&r +7hJȳ +:(V܏"'*hc7s ڎ+-4d*/d`H8JB9V@º[*ɓZ0S$hg+H]7^N~Ap(Ǥ-'Vʌ +|!/Wx] -?UH6/h4&)V!XE0z+9܌6TMԣe߶/ `B9:!&t~"EY٠&0"A0z!bDfnG'bO;̉klB:ɲv~.g|ًƹC&W{D \rEݩkh`:HN&h=g=3f3Nti P,Fs"zO)M1)ϿHxT7 6]H{=p!eh#I5[ r*U!GWiqfLz[е~ܸXsW-`r7;0)eZޒl2碔pzEv8ߣ&D}m?A6nd?!BzPi^1 ZE^nb "ph*pzǽMøv**{x0~]^t/jr)e&4  -TaRVy8~u}$ Yc7ܩ Χ^eUK< + -Ť |$eϋ>\6Z{[xSn"vːդ6\bfe9]aeɳ7J^awpB/^lHWz ^imd<\QBt/xSsG308Շ@7y) -R啬[h0| +Z~䎝ϛsU`R7B\¥R;^ +VJRu0vJfX0H!Ϳى </J~&^3~A1Wk]Y ]4QĮL_Ȼ(-w;XX&oeJl_78zQ믞){̌tg; K\;5_Gt\IaH*/ OwQKըGt~1Gi2W38QN!&) <8iϳx½b6Gͬ;Bܟ,k5lj&pLu{}G(Bi(X]>l!SU6ѵx }82T$Ha~gY>IJS7NRn'h*f`0(kgrY,Wu]P7 ~ݹSٿO˾WC T)/ndnh_ gP20Eo2ULVSYZ|9E2z9/'H3"$FjI #J;9*?ML>հ? +-iS=Im!GC,"@ǽ"1oH)Y}ir^eA5bԫ7'{ґ};7Bʂb{ k`ge\I{rB#xBjMطzph@=A[uU[f[>{*~5TV(=O2%J8P)W[Y}ް>{b"AT4qFv51LI7ʆ(ǰϠ+4"$x1khƌ'1A}nvлͪlOy}n&Q׃WJ@.rn?m6l^N̜N#"ĺeVhޗ6bjqfTd6R[ŦҜ7UT2=/0 SV} +*TIG'>d3؎?#h^gb/F9lF Θ0L!UČsONY,,` Q1'+tdw*EQ]%yKGrgd7*_ f$(p suB42j_WKoos !_ 1bOt\֙,DhGS_Iǿ4&.7 'WΙ}8I3‚AyTNحPqpjw{[ Hg{ZلFdAo@E-\pE<,JvƮ1}kCBRaK5(rr褓1']|/EfH5-=pZ-^G ,v\`/ƩYDŎA'?"|;nXku'i I/;xiOe+ \m|QR|2Wj~a)iA>br>U韶=Iu}M _aX *xaG$.CI!+ Zk4?$t^R.%yM=]Pd-O ;Q!w|ƺ FC-0=/ղ?̲ l<"7G~ UkۗDb)r!?^8Iu6>#Rڿj=8$2q?[H}d芕װ* +\&8YTEZXݨ:\eY+Nz2B'L_3 ׷yVOk6Dwf)s80wzz!լ CJflkΝx6 /e v"L*pf dzޅL:]tρnM+j=+qH,v y$;TBЭ񈬊yp!8L\G#$ȡ~vu؋-.SU2=!BP)H7}qS(wz>#fV*A6n* ߌ2Z LG,R^uB>o۷^&Pyz)JZLnڕ\J)Oăs{/iT@ RrOy;mn 3#l4Q +'@' O]"ui.1 vU;q*z1GYnR*s$ 1NnKڻg7; -8(=V +˓rēo8XnBtGu$G]`Uޜ*%bQqFqQn0{9~@LrzAʆd7 fxD1_.8Oz 6]/@u-ޕHIȳ=3{d{G7fZcB-ܯgM{fBp།w +2Q[e62t! -(k㨹a狦O;zyb J'삍oiwEhSZ9{~.T(f\*л5ʨ@޵0OBj_({lr='XVf N􀨕V֤} I-OL07NP)hR8$P]'?kx!hU{|2j(<3# %lCP T&[Bb7Jݧ46gPTBꝦGtTaNIeƱsY+N*<)j̓VfKn^/Qw~\g%{oq;ԍ#&rl/DH6dHrv`@Q6~}/vrB-(drʕß<$gO⁁8rp@T?32Փȷxa(`YJ+hJ: eD8S[@2Dr'/D* {r {rhjF;DAy5x/))!k2G8<곍0<r@øAM{I3 x 镾 /EEDTߑrPy#8rH(raP!(vW"2JmMFi>%Ŏ-a8EI.^涧EX?W${<z8ZלpRTablI^Q I0K )6gHq9]Ax^pdC䏈66«B"\\9#r8!1XqlBB5zgCkG|!%9DI)Xg6qs0z`zd Pg B9FmpOw.@npz 9 ;hnC%șcg\ȑ8s 3 b"7‡qZ?mC¡k^ HSb3-[%NMQ.8Dm?h⸠z˜>")n4'fLQDs E$eDtr~)w˨+[ `Y*Qb#d~XA%ww="g!nH]9h"I5ff-| I`zJWp~QESP\"0Ň%'&N/qwH*5#@ x$80!1kR2CQgVc<[QS..+doFEs#"pN`1nS8&"w9ASX~b:.]*ipa91 HpDH+~M51/r!t)/Cb4 +L)^:)RMR$gPp3Wh'BTa D7! 5%V9UAf񰊓 xGߪg,bwe|8|YKHlep.fCŊDC0Y,ѭ3rZMjSjv +pm WWYJqIɸS+;s Ɂ!<iۙF1dt-td6zL_cU֐+TgbC|()q3J7fܤه?- [`lg``3z^ CЉ ),}u7N\~X!IF`?:H(UDUC?frHXx[*g!AG7@.@J#5.ވV]yH9x!x[qGJmS94!eFOb8qvbOJ⨋ȩP2*CMb1.%ݮ;X qmh[իW:/Jݒ=9*;vGH,TFZ!CTߤ[U% )Έ#0 x$6 piFL%%+[YDPwe 5 K{uF33+e{@pD𫠋 +%"qAJhMSOJvT?dr i᪼* +t: 2keGv .eAS>\<niQ2M*08f2qHRfm̰P"W>WN?_Լ9*V2)guԃ`#8yr\#  KĂ@66Db%@ܙ3qΊ‹~k.b6CNXA{7]r"B aoz@ 2=5Yg(X;iXy`\>i.%)?AFȔZ7(Z͇TR)J3@m@9xqC9sEEc|P%H[Qiy=NTzŁ{R@/N]^TAHfrԑ6K9.CbBnldC_@j4M+_& ?$J/0tA5 +(\OSpJ {-᱇:`(nZ 6 +B- _y(-؃{G;.\!D?+0(HF%.%L@XEkJuGYVxsiE,XFx&FyJ)jSM nETS +fs~"0}:tMFENԛ4B!ȵ Lnn +{ .~n>y~~~2?~spu58{_;ˋW/8:xmdz?]M@˓|`şi}d[f~q8?;M(#~}s1gɥ~cy&S5/'+kost8?.fozBWw}77>rhg /8;7{/|~ ݳg|#6y7xR6G9;;8;3;zǍU75p-}}o^PHdm 2Z3LܽUB|\ +Y_=Ⱦwut8 Nk,^. s+mvޘ gp[&`XHvj̓]$IT5`{;ֺܱ`//ߘ9BF1nFwި}Srؗ|}4#~ɶjַC[b7V0Ry%f6~wbVuXZ\ZERW[^t6&ICJ\4s,8A@f:\*rwNu)eWBIv-Zz+<2aTkzsbߛh}l[2TT#*W"Db#h &]#BŇ_~Hnژ]>$2 ~& + 5 8b:GTq܈XC4N{U<ۃkΘKUjqcu [+JoqkP H#GYuvޜ6_8f~(غ}RH9>hy8w*wʴB8Kvɨ -Ya:ni-.b#R곍IxmTp%$,P1D3(<ÑI9}׹6ٌ_hTcr9`F8ZE17裘5q 0 :(3xsu[[6A(3wQ)pGp2V~~W}丏q^A) &dwI.%md5Dx\7\?&W/lpIB_AaZ9LJ΄ĺp*41a^FU Y@ ;;e3{lWCJ +znVwd\Gfo5dAuS&ǀ1G0Zƹf= f2(ٝrՃHğwCUGRJ<4uhT?R sRVi_èf8Ƶi1M/ZA8q -,IsP֊hUnUa{HS T8C꼚̱,4°TׄzW?##)$҈$Aڈ][$ +u q3oYhM\T,F&.a˒מUYه>}"ryiabNz^YLL7 — W)r] c OH}- n\%lW}N9EF!xc +ES<N9֎H r# ʯBADnVLպϷ<Z&#PqYq%IEiWiVY6XY(.Mx9lJOS0WDH-٪Vi!LC?V8. #gI c3YHڂ0gj, G*b }mVe?ƭBQC2|3^PM j`c (.01613H΅CAB&SJfxjЬeb%a=K6 J+a2N5 19DkaCw +@d8ڛ0bZU P\CFuDY+lӛYCZX݈f"?~7E>Ўt0ԎF`:t=|QɪvӴ<ڕӫXldiQ*fW{aQKZJPE6VSeB5$ +ာՐbF-'+C`~QgY!CcTJUi.Nr 0f]փj +Z"JbU- Pg:SZ͌\wǪNVT0h1r6ɲ\uL1ifH7jZr#6Q RY'96@u'm +xAGmfePҜ&rVcRW FBCd BPpeUAbӞى'%#u]7jG.ɍϏZ>u@iQņ\n`a]j$"\ endstream endobj 20 0 obj <>stream +c].6*[s1aQ. ٬jꬱY}Y̊F[J S\=UѬ{.9]Μ4B^L-9cVki42&h BM@oƊ"mQ7ΑnP8FͺA1̑Ҝ ZjTFPjks* rNY ޵$Jy߮n'#D18Sww\o#MoTUC9h) Dm[0NjB<;nKTLvipƐ D[afe]TY8IuM.X >z$l=$Tt}sG,FwFY\Ў)kuSSHn6ئ0CoƠ6;Y3Z\mY5攺ɫEb}Iv'(l'of`"] +gKRnw7j6fv}:eCOi1~3%PQM@9l]Pm }0jb7j  3"rnE_36ġ{Ԯ ٛQ39וE}0jޘfX!$ +R?tp5%㵋3!֫fEz*s\bm3YCSI+I׺F6FLs:oLI]r6 R1Tnt򵈽ծDn7=Qrhܶ3l4NW{ P"zC V|,TEe`D2sF&EW$ovɢUSISg(`RANj\:Jm:ZSXVtAhה̽<>%$ {XH$sJ~QU' v5 [H/s16]R4c͝mB$[#f$$\{Himf6.\7/8tF5cb- ˥tybHmnE4uGNV#WGNR?6h04=l Gdö뼘zlLJLmMb5}-<^M&)Bp, +%Aq?!h66UZ}͠jT.Fu/mcBCtj84y]UO͢du}MU8`$(f͈3'csa"g6Zu5v0&3kQ~fvg'&6x۸~$B^McG/CGC a?[볂mYT A7C D[TIZٸLCҔ/Pj|KQNjcD.FD0vHcSʧlӒnvRj-إX ثCv4昻\f(u浯u00qB/n"*eZ1T}4Xj`BO5ƹ=Bjn`(v 4M2i֓mI`vUof\gƾ8Ɠl-fhl[4eMJ-G\\t̤tIA-ݺ-L=m$xgjmm(T0S8C'Yau0ؐ+:V ޙわoi&l=G6hQAj̏ԵQ입Υ`Xi6Z3tʍ-&V.ݝhϨU&AXZq !`q>+jb#aʞQ2"Oks &]'}%K悖漏n,C[Qo^ +=`$HZu*"aNؽt8(QԨVTh[yr}?umb;$â(os3'c7{JcҠgm?Fqⱈ얚b +o ND:LYR'E r4ҀazƁXFI՛sxu7IȿW&{ OآGYךk5S-ʂuh:i{hZ7{?n`CQDcJRppUFp:j ɷRu 3a̳׎E"""ں|W)>N)>F$ O@&yu?bL9e8ĻĵMuX*)ZT2ٸM Oq_'<*D;'# e_0;ɸ=|-hE{r`(ɸ.4jlr3 9Or­D5F$e1*EꛟK߳Qw +qFј+ٍaW^yu\0Q9TDEc悅:I.S~څ$1҈&96f8ݚgYг&x#dsoI$8fOG͵b_[+I<57:$z2Hl`ֺֿ=(L`etAH=iF0Ed B6-,5"oq8"[c{ݕ]jiQF긤 ɾvA堧ST&WcOΓb&EۆN%|uzjKytNݪXڛ(EJ뾾Q21@, tvܳ$>u:-YGd{AZ$cۢcI=Bm$,:0)RE֞2臒PK=3|#Ё:p?5Yr%PLxo4QUAߍ +SG V6L&M }K2C= DLz3vG CzM] +/v-d/1J#hE 6f>jeŒ?(ze%J2YsJm2_6tVx%My`* +2ꄲ)t'I T44uq] zțcfxLw4J.~EͶ(ԙl Yg8ArJlơCl01EqQ)ku>s:2.i6T45t֨{݃ 7D4:Z-bcPBc6diyVސuSeL8\1^4JeM4h:]l_!j4y c 4Щ9A4y[WEB'>ޓؙ0Ic5&y>aܯwNpY,!C :BI=,hŘs&zعEzbЎ1*PaOg2du $h\wjOJr 5H@2n̾4MAsn!{֣ JC44ˤU}Ucq4veQ.IoiM4͝Ϸݥ X#棦^ٴhi^#&CnG01C0-//~}sqcQJwj"J +^UCװg43Y8z+ o [s +nbio.`My3ټGafPg\4(4,>O-OĐ^sVi1-#㟽-^Ayz68fX$c=io5b^vژ,'oE؝ыjOK^"rդ4Ⳝe[S}K4YAT[}xyv[%ls<ųcMݢ5oE^+TU5>I'Q41)9=3_%=p$j%`~1u+= },=z_60前[{IfmMgR4Z|$#9jtjAswËV"N,I[Fŷ9=Q.YbbObe> &,F<{.T`Dkf#3Xzyc^j}Y,iA)g?(GjȹΫAg{2U50@pR ݵ1xo+7v`ewi4jK$*K6pҮ$ Y:ItR{cv6W5ZN}J"O䮆sFpҢ:2y7tfV^Ĩ$eHFmV/1{LL6S \V]n80ˊ[+zV/k.+6g徾 6➚ė[ھI@++:HmKoZ>z9?{ay-g\-Q?GIoi7̸ hu*2two}׿vZ^6]҃[׿r˼ hI0$HoW7;:sZ̿5K&Og_14oy~a7XL Fġ ;ˏc .ڸ+KV}F18fەU_~, rqK/鋿uW ?V!nmJ$\q6XpK;YgN+vfĄ7X}J&8P Iaq‚ӅeNL֏ cy냛C=j5o܆ ޱEG m]ϭis$47gWg?>{&L A}_8O~jGs/o{죿ѣg4Wˏgg7z>>9ṟ}dz>/>{ή__}wK?{>}Cz;ُZ9>CC/~"~y{R~y++X㣫kF{?j%Y^cͻ' 3O/W /'wt^<-W|qo__w[ !e_,O..H@^^|E]{s~6U/nBB5&}]P+;=rOmbE9?>ڿg"WcwW= rf~qpu8;ya }emw~NY1ℵg1i}Ӈ@hkXt!Q/-t!qcWOWLJOv_}sp|,ySط//޼^~$tsk7<_8NnKt_΁֝'_>}H/:[@].׆ޅw +']T|g<߀gwWWwǎ X16?\z~v>փ }h\;`U]@kcns._4?[MV܊K"n[Ѭc+>mE' 7O\[巙ોܓ?_ne e+ne e@>>K]$^܏up$pt|r~=ś'?͂nۚ-ڋ-~ⷍo_\/,Cqs:-b-1ܖb-b<<|=㯳pW|?mztynڋާ} 󓿟\/Գ΂SqJ`zrXoap{#qoqC!ӯ5ײ&ǂ߶[b~t􆛂R5roNԌNϏ4]˓ß>I_eؽӰ yO AkQr@ܼ+hka }O~x냣"-dKp$ <8ݫb WxW-zu}9wa|OxnS\OIhEX.7|~BK>yŤisj}~d1\VM0HO{zdOS V7V~o=meup\Z +6R٤wXڦ:mV}"jwDNO[!>Zm}M$O:\xIc#%[2[Ga7\n‘ylX氭bU=|odXkXݣ׆԰jXMDRm`\$=97{g)ޥ m۽Wc N[a#w~yς ƿ?G{4r7Zзi6z?Ps>ɕ;MJ]|Gr"{6Fbm\`l~d~x;<'Ƽw]}~|p~:?Ѥ15lGǺ\8]CG}6‰otҕkQ6媬_l#=q{;Xp`xޏ8?]];?y9Z?4Y>Z.̝㿮/drpv|zCxOA*Y}*n3[* +4ٺ䍇7?5wuQ.kMO|蟼u<ދ]t}ht?sa=ϧ3)=X;W>toJSy +O0}`7+ @-&$w CB bRkk~Ýarxm?~(mmOw,yr|) cWOWsJA\ỰER9wM9ҭzk߶a_5@o [Y?Y[ oO{nΏN)jۿ5:?0:t=5+u;lON^w}'(Nϟ"t%|Վ_?w>r_z9+/=t{e~>pJ +nTp4mp[=Vv/O JTOz|~2b~]u][ez;2QOphki~BK?7yCfTg>1e_yͻI2._lc">c4vV.N!NV9Uܼ[eVUl9[eVUF9aVe٭2Q|f;6W5$㋟ff'zmJX?#ϱBy~lsGs_W+@^7fS0SL'5oJJxw@ݔCOZbC^3~"!? yD~a||t_S+ +>.VałUl$7 +T /d*@ +d VU<&q◇wOۊ}v{pɽ?>G;/lyv}_ﱴ+Ev6?\z~v q_ોܓ?!/@pmHMў6faZs8?]mQt"M}LgD?R٤٤Vd {Tz//^^zXe6Ce;Ĩm[EL:wo7^Bmum' abbSe +[Ap+n'#?gG] DSBˋ?}CCp%gvLj_N_|ӗg{j~r03~\|fn ~I?ΞxT0ɡ+_]7?S_=c,IIW4=sԳ@BZi"-PwP[/ܭz}P5`uT[P U|0k\U]/vD旓_q|rshqJj)&_j5i4 q .QKN)r׌pL``g4F ;o| e/Wi˷F/.u'OgooWp8;xy2iv#ӕcM|;?8h«c)iNˋ?ه~0!+\:sz~ Mk3aXo! _7T|5<2X,2/pG/W -/m?ʿPAv#;/ t{ G_A3b,WHabN?)ݗYUvO?!w(3o6 +!CUԖ2݅[ڝ_Oaf`tO`,ʟgpv0dc jFZ|6ydC1_~:ܱY佅* W-c@CƒJ/b]Ken} LK-&w3&bMC ti +^w%. fMk.WH! gl/hB]r21NcoءnlkKuϛEvW8u7&t @M%؛C-h1Tnr SF ˻(D,G(S 064i&(_>n@vR2.֚8g~fT"(*,# $H'(Y%inq{O nчXD8\yC*hсĶk65eppt.cZXJj'Yf M|o|!qϠ%Yw99GXS?cP t,h"}Fw+`,a!D"A.ϖZVܯ*d +F%SNz7xw:\Ύn=$IBvppC8\DZh0Uobhv^: +Hm>yhL)j&S-Gog4$îb術Ӧ3i); +s61R/4JE/'〥 +CJ1$O4ƳCP ),wRj3 `AP= `?\ + 0ÐPt&tkp9df4DED<|U}p {_tJJOpDzxt{;`a?1&F4$Sw/, 2)_"|Rk(hb@]`XkH^J%do`G]*$xA~ H"9z#>5e+Ff+P\s5V`z?tk2}GRc -s+E14bTPkPh{%j+T%3) 4`(@b2*o<|F ]!'$ p$uLP QF|\/H pFRZδW8Ն .h;!0r%^4@V n92bI3VGS'"[_qaC&J&S@tըVu'2V k~}v4|}f>;=?O~Ŭyn?kpѶ+f?|e.f(5tFj +v)a+(0]$O3om#(PsbeMDsBN&I^TLm[mm.gۜP:BDa-b#4{@!ĐН Ġۅk я콇tw&"bWR}f[_q]DD7Iz3 BL=TX|{h1^GI_~%Po)@U0!7 t,a%jI6fvDXhf6yQ m" #k\roW/׷@JJXQ8 +´8+]R4S VI#g=XR]v>(["6<ߖџ/y- CWwJS}|o=g5\ah$ +8A^v|XP\9 aP[E^A"0y/B1a#ܽc;x+DpHa߲Ct_O~uS<+`m-o;}x#_{⢧ӇR=~5H0 ho>OѿB B's`%!{Q?[(_z%#mrw"73 +'3' Ѝ09NA*tji\Z+fqU#/(՘pPt^|bgdCʷUhL:A-LkcpyF\wuq|f <9fb49ziGg?l6^B W<篝|}睷z~dzcΗo_99E~^In(^姝($w3h ^RlK$6oՊC~2'R>̼0>ƍeM9n|ٿe!{gGW,b:ʯ~~v//:?H7.%N~Oa}a i/6?[&b'H"[%J(Duے\नnIіmIі=)ǖ6H$$7%q~(}5;>-g;,ĚwSrn) ?fo5}b.ChnP!#"*KXA)dvsͣ5#b i?VjC=k_ ۝\ffM[Eu)˓-k>|gU6BfR))1sHVA +\#sx#tړ#bAnNX/OώMHC|pR_rx{!}7:__gJRź'?,c#7eo1ӮĴ_}WEb-$k//+JKek9:i 98a'yF@s雓E5taakC[ _mDWiԐINZK$zoϯtnCgt{!g1z-t=䥾Npr!g^sOHE3wR ); F{ I])#ߥ-Zak}dw? Bv  +B6Ҁ|z6~].S/fg߯/"fP8CkEuL +@ + d!=}{zc@AMġ!@ c9'9\>SԹiH}ܿJG_<5qa/,#*[^<~6,~5 t*q̃E-l0֚nku7}v HrJ2pƀ2|vt胻'qbigYNZah)нOTdEB`dCqf|S8Sg=N6,R"*u.#ˊ-jE1x4J<:'t Z U㥡}EM݀eWʲt~Ϻ8`ع@taHVfA- 'ag_~puD+rhfBk6BR]LCx+1YL x j+tdE鑊*&̼%t6Iuqyt2ǞȄHD!D*FbNdb ?Hh}%Ev7v=BՅp(h8B3HD"Wך: tȲWDdy:q LQ@:0BY#N > +RivjodhDĈ1ɱ.`3&aܦKuKhu_R !Yn0 DaϹ$)sK- +fF V'# I 2'524ϙORƘ 4 ztBInq5S퐑x"Z?1L( +6<"iS6~a)Wb21+@=cs^AhtZAè_U tC L%byB&\;JF.)aab>*}BgdA +V=!IaXV ȈJ\BÑv::{c4ɷm&Jp(+x>bH@-3 ag޼3M[Yd@biD@f%X1I*%B͋EnlicDSu@`H\N_Ɂq*g #E叀9X 4uLddohK\R +_@[ Px( }BH|=˫ +de=6B@,-s6HKn ɑ.PT#[":nn_" = kWlӆl] +L:Z։Z]!T"t4-dYŐ]au 71*#*}9 ܐ$%ykԪbuѕ7=aHZ:xlb;W`udi1ZHzDA6ORb֔$qW$svSOߩHE3ٺ mX#nrSblrJB5 op<Ŀ ="O$C'F="\H_(7ۛ昻gI]UgLYւ:iq-MZsH(,s7a@ (`ePEnkUȉ!F,6Hk`hDu"DOb>,mKuSoHɜMšuG<35Dqfq/O._9s|zOa7FMe2QkUA)` bH0tY[^E&UkCU:HՐ;BQRoD,Xbb`.=ksi*ȟA䍜J`Y,HH ]$D<,AΨ"{tsw<夿G1ĔfIx4MOV,KkY}' nv.*0),re^MmZZML4$} BB虥.eiw贊0,ѕњ I"@UNCLZ=ۉ%%đqUE>>¤*qCd \m:vBH# D|%B tBgzb2:P`#o*_<aTN647 ByMR bg #y7> +/ Kvݍ>K9<\U;f3\l72OR7NIEG@C9I#oq{~8c8OmOFdWs׿~}@eEdFόh^j[0]b]|h)?-Y\=nq{p4{Gk:;9\4mE;q'3ݷK3z.3w-qpvsdӶoH ږ#0$ulh^bV03kF!y bn.7XLW lb00/ad Kxάjr3O` Ar$4rżQir DnGQߝ?;:aEO34:<(|xMVj34ziBK7C[~(ƛbf? Ow7ou_ڴ ەnKK>$ ԛzټ7יvkuLox3Oȵ_y/ΈopCiNj16ZEO}[ਮa/o\2k:^hݚ-vzw3R#e^IIרӞ<͔]2)+7ey9KI434yp<=WBDҮrVj0튽8^(YʮMeKlXO=4ݲme8Ok5X@'02 ^Y Ѹ3~M +YVZ^~5ƥ,pԟUQ.4\kNi}5ZGA7 @9^B;cG5H˄]EO|&:Fa;Y"Ve/f"ΐ>6-f$紜S*1)2kMqk= e锞Z3MіP=흴&fŐ !!'4.\)6mm3=)׌w>mfο. M"G@_РQP=ǒ$=ܵF7@(q[3q vGh hnq9h;*T0a`wYxSx 3Zb؛ɨ^g<ĔsUh+3mBC&{(/qnzuŒ9\ɴ7MћNpIOR|9=[:lm"[6K+ +K?9QŜfߊ1#{PU-K޸X'qؿQ҅y7a5>/'yj*-cppڶMLs"Vz4&O*PTs O%+:Cpݛ,3e< + +^+iEiiVNktʦ_ZwLk1.eio۟DlUT-FhTYOQ6d#I~k4,v DPIRﳵjVH/cgR:L*l{%HZǫ*;ū_pLks|-xWB$ZD$=rICOQ(\hY]sfjE|ԙuO/hoZjP. ]c +a٨51卷_I YwbM_38< +_ G_jFb0Fhbqlza0M~b̴N7ETЈ7<&Fcz#wl6z´eq#Y[%Dkg܎.yaFA҆;c ^aB)g5 hޢ-zA5uS{{cr+xr?!+'ϱW (+RBKx\ٹSK  Ḓ%=&7VE%ˁ<]N&]WL7̦z2J\C%ݛc7Lx8ZҁHty; I0fL'Sj8Lٷ^% A$-DYKt>..Ŷ;3#@ڶ\cHghkǤzKg4ҡ,>nV FCmG˘]X!2Jd|Ҏ⸹tjtѬf^ڜZP]M)=£cfT7O0QQӸbuKD{9OlhCڶџŶCW$Z%CmT͛jUk2j67HT"6ݚ\F9TUf5Iʎl(^#KqNFM4)4CNb6~Wi$ȯ,fEno9Y87\09il+lCɨjJ7¢h {24v=s3ߟy;ԑIChمQ ZEe٢mL.K Jw8~%ZjnƢC03[#^a%3Y׭nj:5_ԌKn5$_ƎYO:c>IbLIP3cNJB4 s˄0sl1!3o8ՙjmF{HF]E'+}k4(ίBiu$3<\CB7OעJB_6^x-4 huCyi1j9XK5ލ 1rRhƅ67uu^i!QtC^~Qp6Z=)ɃϚS`cKf#n_*JOo©De6AoF_zo&$Q>y!BھZapx@q<Q@p@0yZQ/@whS4v +O\l8$y5\ӻgܱθF B%EЗ)rϑA!z_ +i;5V|`og{mfrwirEG3;wg٬#΢t<<~Cj.9< v6)=&htT<|}*ӑ-*%n/ WV+.は70TOJmw l/P)ܿ?| )rR>R7|t4|S?q5/Qa8 +J®pw|TCo2NL/;y:?0WS0w࿐CӃK-v´IESkTE:鿝v Cq _Er?7xQiZw$ߗ $fl~9# NgwR\{fD+7gGXW +Hdž?W<9ܽ/G0]Yiv~ ûJg2x01] ~=+%Z_i*bGl9ȧHr(}:/EFv#s@E>{;tߊ+]6hǑߒB,17K?g++Vzߝyrso2"/>,qPH! O䬗|6PiMq%DcZHv^! ?Vr-9?&3JRh:-ZT{Q΅/ }uٜM_=Kŧch;ϟ k]<kg;k%CX+ + `WIӝ1S>U/U`>|< />4R7,=O;o1f LUrWĢ/J!q)`EQ9|XWWc-zJӑIVñ+SJ<%cbš +`oķH`幊JLE*GB^; +\I.b:0j0?Kpcp?岦:*픉ͦ[2t tx!1V=dCKjc!CW/Ftcp 0U%STx~L%,WU dw!3%H٪G/TΡۓ΋HH`L45W8Ep!+vVD5M"q V>&h4g4J%iNƂRkGbA*QkU¦d&E7{ռqrGW΂o]Sɀ +W/^'W,t,:e¥FJQv? ]|uyuT'=?mjὗ4uM5$x,`ɜӥ>*>9PF^)uJ!6$2yqnzJlSncj/zڄ) lvr"wy&soHzЫ'Z@KoI *Lt6B\p?X;.Rd1(kK1% KQoO@qK:> +]2GPfB- +dyü<@a 4=?U'Ci +ߙYL\oݟahn}i^(`z(O}b+u`(1K%y+kxR-bN i2A﹔:#,GaIfbq4|r+Nl6_E3ߑ>kH5&HfbiT0rVd:`ZY,\Å+I)Kk4-=QM6z M54f-RWT>ed]}rǝ՞|] c3K yt:Q'Ct3K)Q,rةIfTiyjepNA h| +)ů\|W:$% ~rt|ȧð +L4$5Z-0 +؏R>>(~ +\0ƣ\p'us0S?su/zM}w~W i_k, SMcE*\,2iN@*+yHwGQ[*f_0X CN }z0U`_ur5[qhRi|u&&4"yJ3Wx9O'6~H;|/sQ^-Lw iz~}TnŽO49X/DSd%LҊ_8ʔZ7՗Oa47*=,wWA03CbB,Br{<|T0RX.#Gk9 cX%:/s샩n^K7^8^PkŸȜx>留@MJՋSJ+Ž^ |x;NJRNIEe{_9aNWgCg&"};SVDRr3/ ,Wvq +٤ GIQ!XB23'RJ[Q ӕd~5Y0W0S$_r}Mggohew90Z}Dn!Utta( sE+Uz'OڲIXj5ahv,ׯJȢ$l(yQ?Bӂ5|1'g3ngorO@0F7"^ +ܦ0"M ;Z*J~=R/>4C.<=_^79]Ih~"OVJ&FN+S E >a9TW?e_əsba+diJGzQ-jҸƇE[_St=O!P+FN}ɑ\;(̲SqZ$>%gRwˏf.'Sͽo-7.w#{6&as}rg +|A,AT^ߧ>h\"c*ga0vn&Nn6;_qkCIvC]e8 US'$DQCR]'][)ѓ ])XFe YTr k-rQl}(9 %v/>XJ<4r80?^ xPi>AeژDotž1 ]%}0 6Є+~ a;ak~M4A<~Dn#t|}*| Q5[Y)9}!v%/BOi_>>"s㋞=|1ꌡGAf :W@Le )6jȦ8)U{=4{G'秹zzMwWݧ "ɼ,UR?:Kz/׳+sU-9y"};ƀVEU9C_p:Ss d*R_E; DbHVl D/T8v2l9 h:@FU>oTd_;ܧOߛH5iI^) HeU, DZ#}5-ZP٣M\[!g/c=v{Ho i`ɪHa^>~]#J:7ES 3U^_+cwY +iF?̑V| R\x=>ƂsGy/!b4" zZo_b &Fm FsId(v>=\)2Z>>/̑L17arR|:c +W%*7KWI˧ }3j4퟼-gSxX2O5|ǜ FWci1~hZΫϹG`Ex.J?ħ)'ESO:=0,bG0'U|uܓMD!k]d7aG1ICDzl^<%F"gQ؇֝}yM!N;#өǯ%R~gs%R0"?ӎD:HuvH5hS x +R@^V:Gj|X"%޿RSXtc}D +qHDm>C_i#`*R}Z)Ye4SMH+-ݿÅcw(_\ݏ/RZVSI f$mJ:$1NBbȦk{?tUF̬$ٲ_=K ,#P|K h"62d@߽_sمҸX~K9%%",SG%u從J :L&~^%uj>1tJۥEX,)M͐$$m(ԝ΋>yfyv%3!:6r?cŸbdX6pb ebV,tY7;87O'oLy>ǚXzZV=oea-Ulju/'S-./sڽчlџh9*hGiYm+$8ӡ=>4#cgNӎ"QifT4Kj X+ L@8"Z>CJЭ@AIJ#G2h8$ún"t׬Uq+wR󰰝]:zKGsDHtŴ`BD2o-WJ4"cWM{gVQmb [5;,OI䅹U=4L.|F1x3sK2UImuLU6!X56]Zʋk?n\:蒵C'On!ʷu,|~ +WUA~aՐ`zj^ أ} ^{nbzb&<̇0] +\ٚޱnS3 ZS}zL`KxgkRYx[-PcfK& 6Nǝ}[)f1` JT^); 8y+ZUG z4q{MG9baj~^!&%=$ |yI }6>{_Sk*D{Ͳ[p*.HqGt0zjT9]~UU͠ gX߬[V^-%Vq'o{AB/dǭ^Mnp펻 ].Ԙ5.%kȐ=x9% C4+INq_™%rЛ5 ٩4[`S->]w7 Ys}U`A8Y7uΕ-4u2Q.irJB;#m~=b+º^O^2W!9y]5~}Y#=Հ<~Nu?qswywH{ ʗ X48* 3XJПj91?ܘY;%Hfo5˶U |ROx̚󸆣$W덴&.TE(J4Bu*pV{JoLBկw ="٦"|; sk|N "鬘Q%6\h4 ITݪM^W5 +[_)RYoAieମіr"5B|Y *N7tFnlAk}.zo 1RntV/?k53+_̈́Yf؝o%%[5w?ҺH|fͰJ8q[B!y"6;/L/&`UABo &I[;׳#{,Mޯ`˟-\ՊᬭZbg^E[1Ŀ7DpZC{[H4Pg ߍ HLaIRyJ݆a OmDKʨշMrՆ[)#c?uQ8K@9=;uY7Yfo#.&qRNU`;IZ&谧Mښص ȬK+-m]0t[yURWrm]tԦR9 Z=J#vYKC'׼[mLN=bƞ:z[l6Xn0k39Բkz8n(玊qۮs<%`+p+[-׫3bV:Xɶye=P=V=wcW{"d6`7udNi]qSGb E-Tr-dѰ:!YsŎ_xisa" BhyD #MVaS1`ʘ1K1|CoZhh9 YM|,V +!K wViYm^>44}VpZc(3j"YwfV UywQD"v2p(JEv*R6*R^lTThCHl+o2 +Ť׋bh(3c.#mwlt^^ +~RO1}rR8DR +gU7™\gvVhO+|ܫDx?V;En7z!2>nEfU4.pNUnfT,ƺpAΎU~ָ>iً^.ʕ^^sy4y,*"E(}vll>™ x^V$3:bϢMX1t.MQ!y M뒤U]BNTMisal[7,n3I%USt3 Ye ]՝ vhT'âתSXw-]ZڥeD_io)=9\'>SϪK=8z''l^?+~^ w^^ItC{כֿOOmc}|mҰo }|nNoXޫU|Ɗ%n+6O)Wt{ [^?[6>Io\{\Խn^?[Vo*\\obxͭ~,뷜Jgu٪o2Xd)}ɼhn?~-["^?UqAý~d{C1z^?X"o|~[1'YvD$+a6O; 86յi~qZAh&G5aV C%.zBs]$ژa3Y9E@a=o,{#_J?; 3<|w?͍sq` VyIh̍_zIImgysGr>.rrTP/P)Uӏtp:Kݟv,~T*-D>6;rOb:{es]zYK|jr$'|qjF,B |YT4[S37&* v}]$=C};jg#RWM?xrw']5EsjU34Pkft/=Uzn<}>|'Ŷb4KHwoݟ V>3HG2>)Uw߿Gw5Ʊh7*DNTbsQ'1PDW!L~ALQhX)P/V2s5*R`䄎˝_Jd(vbQDW̅ V"} +R` +\ +yH (-Maҿs&n,یD4TⱡCbDp?5 +V܏ֵ[E<Mr9:M]SJ.kNG>IO?>dNX:Lh+qcS,avu۷ n3-kjXS# \1~ׇ?Jo`Y7.6c+:q2t:轅>|0L41rͦ8 @J:(JE˟\k[Mr~W ]l f̟Y-3:F0Wu]5*vM\X Vσ/zVEjX* Lcby /ܡ oS GtHLea>y~pa MXҖ@oC"YT  bVQa)Ş#TbBDe j=$-i5:',as Hc2Ț1CoCQHAiAyRvT=U֓"U:=SBe7JiBQI͊'pҘFE;$$ *Պ|ڝk lU+}܌5߁И%N'sKXn߁Nw"GeHq︬'5 Ye%dT?*MGօhSUTo'0xx+AbGRJsP~94hRgHo;wq;Z̮swTF+S|UDr6J>xԟP]p>ܒ=#Fv)^IUW5^6!R8y/1'w oϵct$Qwp'k[SH ^O( _ՈDF! ƻ#"Unm0c%[-7ZoۋئÈk"5AφKnQ )=~] ]ĕm1 22~PL:æ^ԵLՖxUzϽ~rXhSKxl-<ڔ–<D (ǖp6c+HW}G)) FěALIl: ʒa >8Ƥ|W֋_J"$jqˬx9Vn&t %Oғ(v"v AOZHAE 51G63%RmVe>`C5TH5oZ V7-n1t `/Q^g? '_Wwvj= +O,,a[0U@]XmK{/a[T0A[j0|󧠤7KT5ХM_?bԥi { +}iŽEUBS +AeJZ19j0?H35R2&iGmY$ HQuqx?яf"$=V>5+6g҆ZE=*AT:kD!&gR}@:)0B8#{͈㙢E:Vo){\;_k{2ZD[LY2{SA֗0Ral)Uv[RY"+vS3ns2"oZE]OZ@oD&B$"p ؛J=+ȋǙH&Bذќl^-8s9Wג#V+BdxXX + H7. 냴\D0}Y4+ 6p1vRMÛ4¬yǜ:,kɱk"zu'Y +$zIk*pCiѪ6lE0$sjHTTXzwRh͎ڋHʀ< b{%1#{vGn@VKeu@I>*~}eMHy4L_Jث!yk]A\:S\ooS)°YD\ƍBM@hbJămb`HѷH'{%<=RQRb" 0͸ +(fx/Gӡu'ddqQ{es3a o @ry$΄n|r6Yͣ}τ݁<{ 0"w31”:I>-H)Z' ndiA?S3%`n{.~t֍WTn W8lVS i~ ֿ8Q2N i5gn9q}b9)}*JKVwyco.Ji_7_v,wCrr՛Ox@Tf}ÚUxH +ukV9xVsXʁȾ:54Kne=?VG]J_nz$ Wo꡽fR^&/IިCCye=Kμ?f𧇳Iɛ/  tz(kw??< | *N'&Nyc/4X!Lf,ͥ|2H)JR d:E~T:EiN^g4އ|9??4=>R.òq8J&Si1Iǩ$-Nkֈ)6byb9bA~t2-IoLŧ`i4$ $ *Ii%F Mq"M48c1,S #y:E ) 8-ưq:&'Rid:β) MJK $| M&Ҵ>ZHi U2 +%|N F8W| p$s `I +Mqz-4AR)3ƈDXP8X0qe)BfOB< S u0$<@+* $gA4 㐧8(`nP Y`(.\ +hI$&*$!3K2Z,e?C>8",옢9 +O&: \ H81lrK (ͳ, +=(l=+ YMӂ G1DVax"AB#ygQC%As`O<06L 6mI4=R'%L,t$5 } h$9J#"IQq@,[*Ѐ\le +`X^BĀ.R8AT$&C}A&f<@%(,r)(#e/?QZp,OQ$.44jt F4 h0Rh=Xd+d +$4zK!*4 "44W M`;ۊ 3D g8t\p TJj`@0YhxNj'4e04LHb!)E9 D և 3?j!`"1( & tCyAI!6F I rc$$5t +i4R 1A?A@Ӡu0l*47(, +%L.qҠ( y*I84``.Фr !(w(gMS1@6w=Ȗ<hBQ-5Eo` +CFIH*+-,=8A)s:&'M@W M/4 +_>H NO( +.`,(4oCazM1PwJR4OLt - T'"*?4:BS C::,:v`$:)h e !-a%u [M/zI`DL[ Ph> OM8E )۴ +DY^DT/>o8,hXĵ@2b BS`H7 )x`j7RpdTJ=p\Ԁ +"Ϣ"m@π]IqDh sA*jGo +a-iMJiMB`4j\ htgp䀚 0@q+8n? +5yL#pz HPjZ6)'@:`M@ (D%* Kc`@M@H A0wa -Z$L c]hEH(`0%0xG"^$! d226#PA8V`ɠR(dI/it *@-!f4Ai?@, /`#)+iT )H 4cJ AH=A@:aLbn+ͣOf|(0Y8ؔ%s K? E!Fb1  p\J8qWilaV)]&# JF58& 8x84$į$!8PbF 0( k` $x0IB7'`R+I^숉Pdh `\ZDCrBb)f<t/Zt@*aiIt3B _fY D'w& %}-0` AI\TթsNEnrqYcq̢ۘ[)?]t#:!5E^hZKB.L*~G`<5GGAS݇pD3A#Y>Zߚ7n7~qw%C?8`]INjiq뗐.[Kc)I[ !eƢL,5-`K$vGgb4]C[SPX2%{G j!i 8m Zc>H_E~mq]g@$pxe +O؜ s G84Q鐞r'|tERj\ +쒀( 8SVǥH@ؽzGgnrʆLBF#խ>>fϟ̓=! +Ap^×.' OJd$G{-j4aIJ5XcM~>01⸐n$A Ahs55:0kOP%QOTE{ dc?䖡~G bEZdqVM+eT<*f?+8z( +Da[B%|x5z7\& w64﹐井r"ہ l*4Mv*L] 4\FiS/1281ZES;}$%V澳@#O'ݼڨkpm  +].k%o8R3uQ Ad bEqN_#fe{aۜd+@L.Sd~حXFvZ9zܜFrK6elUU'Uׄi!BBvg۹jEC/ThX@&L΁A odzRN3FV<19"@AH/8_nUIBw;rBh *BUSD@v}$[h&~X\BRU/ũF$Pu*$U:G,jFqIÂM[m\MR伍:>6KZ:N|%.ej* %'L)|S-,Q]͉zb.=pӹZlqv7ő7&6wN&"ZaCc&-& +<@*1^n};(Y; ܐơ5sd? )ZBqCuڊ1,QYyyw_?)_?|wӘy endstream endobj 6 0 obj [5 0 R] endobj 21 0 obj <> endobj xref 0 22 0000000000 65535 f +0000000016 00000 n +0000000144 00000 n +0000040127 00000 n +0000000000 00000 f +0000043273 00000 n +0000175179 00000 n +0000040178 00000 n +0000040541 00000 n +0000043572 00000 n +0000043459 00000 n +0000042372 00000 n +0000042712 00000 n +0000042760 00000 n +0000043343 00000 n +0000043374 00000 n +0000043645 00000 n +0000043850 00000 n +0000045197 00000 n +0000063992 00000 n +0000129580 00000 n +0000175202 00000 n +trailer <]>> startxref 175395 %%EOF \ No newline at end of file diff --git a/Installer/Source/Assets.xcassets/Love.imageset/heart_light-1.pdf b/Installer/Source/Assets.xcassets/Love.imageset/heart_light-1.pdf new file mode 100644 index 0000000..1648eab --- /dev/null +++ b/Installer/Source/Assets.xcassets/Love.imageset/heart_light-1.pdf @@ -0,0 +1,1033 @@ +%PDF-1.5 % +1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + heart_light + + + Adobe Illustrator CC 22.0 (Macintosh) + 2018-08-31T14:26:07+02:00 + 2018-08-31T14:26:07+02:00 + 2018-08-31T14:26:07+02:00 + + + + 220 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAADcAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A455n80a15l1m51XVrmS4 uLmRpAHcssYY1CIDsqqNgB2xVKcVdiqIGpaiBQXU1P8AjI39cVb/AEnqX/LXN/yMb+uKu/Sepf8A LXN/yMb+uKu/Sepf8tc3/Ixv64q79J6l/wAtc3/Ixv64q79J6l/y1zf8jG/rirv0nqX/AC1zf8jG /rirv0nqX/LXN/yMb+uKu/Sepf8ALXN/yMb+uKu/Sepf8tc3/Ixv64q79J6l/wAtc3/Ixv64q79J 6l/y1zf8jG/rirv0nqX/AC1zf8jG/rirv0nqX/LXN/yMb+uKu/Sepf8ALXN/yMb+uKuGp6kDUXc1 f+Mj/wBcVTnSvzG8/aU4fT/MOoQU34C4kaM9942LIfpGKvVfJH/OVvmWxlitvNlqmrWXR7y3VYbp R/NxHGKSnhRfnir6T8q+b/LvmvSU1TQrxLy0b4X47PG/dJEPxIw8CPfpiqcYq7FXYq7FXYq7FX5w 3MJguJYSeRidk5dK8TSuKqeKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVkPkf z35i8l60mq6JcGN9luLdqmGeMH7Eqdx4HqO2Kvtb8uPzH0Hz3oK6npjencR0S/sHIMtvKR0Pip/Z bv8AOoCrK8VdirsVdirsVfnNqf8Ax0rv/jNJ/wASOKobFXuX5FfkRoXnTQLjX9fuLlLcXDW1pa2z LHy9NVLyO7K9QS3EBadDir1b/oV78qf+We8/6SW/piqlN/zix+VshHAX8NOoS4Br8+aNiql/0Kn+ WH+/dS/6SI/+qWKu/wChU/yw/wB+6l/0kR/9UsVd/wBCp/lh/v3Uv+kiP/qlirv+hU/yw/37qX/S RH/1SxV3/Qqf5Yf791L/AKSI/wDqlirv+hU/yw/37qX/AEkR/wDVLFXf9Cp/lh/v3Uv+kiP/AKpY q7/oVP8ALD/fupf9JEf/AFSxV3/Qqf5Yf791L/pIj/6pYq7/AKFT/LD/AH7qX/SRH/1SxV3/AEKn +WH+/dS/6SI/+qWKr4v+cVvyuQksdQkqKANcKKHxHGNcVY95s/5xK0N9Pll8rancw6gilo7a+KSQ yED7PNEjaOvieXyxV8y3tndWN5PZXcbQ3VrI8NxC32kkjYq6n3DCmKqOKuxV2Kp75K86695O16HW dGm9OeP4ZYmqYpoifiilXup/DqN8VfbX5cfmPoPnvQV1PTG9O4jol/YOQZbeUjofFT+y3f51AVZX irsVdirsVfnNqf8Ax0rv/jNJ/wASOKobFX2R/wA4vf8Akqbf/mMuf+JDFXrWKuxV2KuxV2KuxV2K uxV2KuxV2KuxV2KuxV2KviD/AJyCtYbX84PMUcI4o0lvKR/lTWsUjn6Wc4q88xVEQaffT2txdwwP JbWnA3UqglYxIeKF6dAW2r44qh8Vdiqe+SvOuveTteh1nRpvTnj+GWJqmKaIn4opV7qfw6jfFX21 +XH5j6D570FdT0xvTuI6Jf2DkGW3lI6HxU/st3+dQFWV4q7FXYq/ObU/+Old/wDGaT/iRxVDYq+y P+cXv/JU2/8AzGXP/Ehir1rFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXxL/zkZ/5OXzD/wBG f/UDBirzfFXvP/OJtnaXus+ZrO8hS4tbiwjjngkUMjo0hDKynYgjFUi/PD8j7vyXdvrOjI9x5WuH 92e0djtHIepQnZHPyO9CyryPFXYqnvkrzrr3k7XodZ0ab054/hliapimiJ+KKVe6n8Oo3xV9q/ll +ZuheftCF/YEQ30IC6jpzGskEh+7kjU+Fu/sQRirMMVdir85tT/46V3/AMZpP+JHFUNir7I/5xe/ 8lTb/wDMZc/8SGKvWsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVfE/wDzkajL+cmvlgQHFmVJ 7j6lCKj6RirzXFXv/wDziD/yknmD/mDi/wCTuKvp28s7S9tJrO8hS4tbhGjngkUMjowoysp2IIxV 8f8A54fkfd+S7t9Z0ZHuPK1w/uz2jsdo5D1KE7I5+R3oWVeR4q7FU68n+b9c8pa7b61o05huoTR0 O8csZPxRSL+0jU/iNwMVfcH5c/mFo3nry7Fq+mn05AfTvbJiDJBMBujU6g9VbuPpGKsoxV+c2p/8 dK7/AOM0n/EjiqGxV9jf84uOrflVCFIJS9uQwHY1U0P0HFXrmKuxV2KuxV2KuxV2KuxV2KuxV2Ku xV2KuxV2Kviv/nJKZZPzg1lQCDFHaI3uTaxP/wAbYq8xxV7X/wA4t+aPL2heZdYGtajb6al1aIsE t3IsMbMklSvNyq1oelcVfSf/ACsz8uP+pr0f/uIWv/VTFVG88/8A5W3tpNZ3nmXRLi1uEaOeCS+t GR0YUZWUvQgjFXyR+b3kfyr5f1b655T1yw1bQ7xj6dtbXkNxcWrHf03VHZ2j/lf6G3oWVefYq7FW d/k1+Y0/kbzjb3sjt+iLwrb6tEKkGEnaQD+aInkPao74q+5fVj9P1ea+lx5epUceNK1r0pTFX50a n/x0rv8A4zSf8SOKobFX19/zin/5LCX/ALaVx/ybixV7JirsVdirsVdirsVdirsVdirsVdirsVdi rsVdir4l/wCcjP8AycvmH/oz/wCoGDFXm+KuxV2KuxV2KuxV2KuxV9Qf8rGuP+hWvr3rH9J+j+gu fevqej168vqvxV8cVfNGp/8AHSu/+M0n/EjiqGxV9ff84p/+Swl/7aVx/wAm4sVeyYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq+Iv+chhcf8rh8wmdeLFrYp7oLSIIf+BGKvOsVdirsVdirsVdir sVdirPv0hP8A8qH+oVHpf4n9XqeX+8FKdaca79OuKsK1P/jpXf8Axmk/4kcVQ2Kvr7/nFP8A8lhL /wBtK4/5NxYq9kxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVj/mL8v/JXmS5S61zRrW/uY14J PKg9TgDULzFGIFdgcVSn/lSf5U/9SzZ/8C3/ADViqun5QfleiBB5X04gdOUCMfpJBOKrv+VR/lh/ 1K+m/wDSNH/TFVFvya/KxpRKfLFhyBBoIqLt/kg8fwxVWP5R/lgR/wAovpv/AEjx/wBMVef/AJk/ 840+UdR0e6vPKlqdL1qFGkht43doJyor6ZRy3At0UqQK9Rir5KIIJBFCNiDirsVZZ9TP/Kp/rvLb 9Pejwp/y58q1xVjmp/8AHSu/+M0n/EjiqGxV9ff84p/+Swl/7aVx/wAm4sVeyYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX56+c4Y4fOGuwxLxjj1C6RFHZVncAYqk+Ks2/ 8or/AODJ/wBiGKsS1P8A46V3/wAZpP8AiRxVDYq+vv8AnFP/AMlhL/20rj/k3Fir2TFXYq7FXYqh 73ULGxj9S7nSFO3I7n5DqfoyGTLGAuRpWN3v5iadGStpbyXBH7TERr9H2m/DNfk7TgPpFseJLJPz G1Ik+nawqO3Lm36iuY57Un0ARxLofzHvQ376zjdfBGZT+PLDHtSXUBPEneneetFuiEmLWkh/379j /gx/GmZeLtDHLn6U8TIUdHUOjBlYVVgagj2OZwNpbwq7FUv1LXtJ00f6VcKsnaJfic/7EdPpyjLq YY/qKLY7dfmPbqSLWzdx2aRgn4KG/XmDPtQfwxRxII/mNqPIUtIePcVav31yn+VJ9wRxIm3/ADI3 AubHbu0b/wDGpH8csj2p3xTxJ7pvm3Q78hEm9GU9IpvgP0HdT9+ZmLW459aPmm05zLS7FXYq7FXY q/P38xIVg/MDzNCpJWLVr5AT1IW5cYqx/FWbf+UV/wDBk/7EMVYlqf8Ax0rv/jNJ/wASOKobFX19 /wA4p/8AksJf+2lcf8m4sVeyYq7FXYqxHzH53S2ZrXTCsk42e4+0inwXsx/DNZqu0OH0w597EyYL c3VzdTGa4kaWVuruanNNOZkbJssUw0/yxrl8oeG2ZYj0kkoi09uW5+jL8ekyT5BaTCfyBr0cRdTD KQK+mjnl/wAMqj8cul2blAvYp4WOyxSwyNFKhSRDRkYUIPuDmCQQaKFuBU00TzHqOkyD0X9S3Jq9 u5+E+NP5T7jMnBqp4jty7lBejaX5h0zUbX145VjKisschCsnzr2983uHUwnGwWYLF/MnniR2a10p uCDZ7odT7J4D3zXartAn0w+aDJhzuzsXclmY1Ziakn3Oaom2LWKuxV2KuxVPtB83ahpjLFKTcWfQ xMd1H+QT0+XTM3T62WPY7xSC9GsL+1v7VLm1fnE/3g9wR2IzfY8kZxscmaIyauxV2KvgH8zP/Jj+ a/8Atsah/wBRUmKsbxVm3/lFf/Bk/wCxDFWJan/x0rv/AIzSf8SOKobFX19/zin/AOSwl/7aVx/y bixV7JirsVYh548yNbodMtGpNIK3Mg6qp/ZHu36s1faGq4fRHn1YyLBra2nup0t7dDJNIaIg6k5p 4QMjQ5sXo/l/ydZacizXIW4vevIiqIfBQf1nN9ptDHHud5MwGQ5nJdirBfzGs4UntLtQBLMGSSnf hTifxpmm7UgARLvYyYbmqYuxV2KuxV2KuxV2KuxV2KuxVN/LevzaRfByS1pJQXEXt/MPcZlaXUnF L+j1SC9TjkSSNZI2DI4DIw3BB3BGdGCCLDNdhV2Kvz8/MGYz+fvMsxHEy6revx605XLmmKpBirNv /KK/+DJ/2IYqxLU/+Old/wDGaT/iRxVDYq+vv+cU/wDyWEv/AG0rj/k3Fir2TFVC+u47OzmupPsQ oXI8aDp9OQyTEYmR6K8eurmW5uJLiZuUsrF3PuTnLTkZEk8y1vQPI+hLaWIv5l/0m6FY6/sxHcU/ 1uubvs/T8MeI8z9zKIZRmxZOxVAXuvaPZEi5u40desYPJ/8AgVqcpyajHDmQi3nfmjXzrF8rxgra wgrAp679WPzpmi1ep8WW3IMSUmzEQ7FXYq7FXYq7FXYq7FXYq7FXYq9B8gaqbiwksZDWS1NY6942 7f7E5vOzc3FHhPRlEsrzZMnYq/Pjzz/ymvmD/tpXn/J98VSTFWbf+UV/8GT/ALEMVYlqf/HSu/8A jNJ/xI4qhsVfX3/OKf8A5LCX/tpXH/JuLFXsmKsc8/XJi0Exg/70SpGfkKv/AMa5gdoyrFXeUSef 6ZafXNRtrXtNIqMR2BO5+7NJihxTEe8sA9iVVVQqiiqKADoAM6oBsbxVgfnLzVcG5k02ycxxRHjc SqaMzd1B7AdDmm12sNmEeQ5sSWH5qmLsVdirsVdirsVdirsVdirsVdirsVdiqe+SboweYYFrRZ1a JvpHIf8ADKMzdBPhyjzSHp+dCzdir8+PPP8AymvmD/tpXn/J98VSTFWbf+UV/wDBk/7EMVYlqf8A x0rv/jNJ/wASOKobFX19/wA4p/8AksJf+2lcf8m4sVeyYqxP8xh/uMtTX/d/T/YHNZ2p9A97GTGf JqhvMtkD0rIfujY5r9CP30fx0QOb1LOjZuxV4rLI0sryOas7FmPuTU5yRNm2tbgV2KuxV2KuxV2K uxV2KuxV2KuxV2KuxVH6A5XXNPI/5aIh97gfxy7TGskfeFD13OobHYq/Pjzz/wApr5g/7aV5/wAn 3xVJMVZt/wCUV/8ABk/7EMVYlqf/AB0rv/jNJ/xI4qhsVfX3/OKf/ksJf+2lcf8AJuLFXsmKsb8/ wmTQQ4/3TMjn5EFP+NswO0o3i9xRJhPlqcQa9Yu2w9UISf8AL+H+OajSSrLE+bAPWs6ZsdiryDWr NrPVru2IoEkbjX+Umqn7jnL54cMyPNrKCylXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FU18rQmXzDYq O0nP/gAW/hmTo43lj71D1fOlbHYq/Pjzz/ymvmD/ALaV5/yffFUkxVm3/lFf/Bk/7EMVYlqf/HSu /wDjNJ/xI4qhsVfX3/OKf/ksJf8AtpXH/JuLFXsmKoDXrM3ujXdsoqzxkoPFl+JfxGU6mHHjI8kF 5Gjsjq6GjKQVPgRuM5gGmD2PTr1L2wgu0+zMgangT1H0HbOpxZOOIl3tiIyxWDfmLpyq9tqCChf9 zKfEgckP3VzT9p4txP4MZMMzUsXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FWV/l5Zepqc12R8NvHxU/ 5Uhp/wARBzZdmY7mZdwTF6Dm8ZuxV+fHnn/lNfMH/bSvP+T74qkmKs2/8or/AODJ/wBiGKsS1P8A 46V3/wAZpP8AiRxVDYq+vv8AnFP/AMlhL/20rj/k3Fir2TFXYq8o80aYdO1meICkMh9WHw4Oa0+g 1Gc1q8XBkI6NZCfeQtejirpVy4UO3K1Y9OR6p9PUZm9nakD0H4MolnObhkwz8xNStmgg05GDTrIJ pAP2QFIAPz5Zqe08ooQ63bGTBs07F2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV6l5Q0s6fosQcUmuP3 0oPUch8I+haZ0eiw8GMd53ZgJ1mWl2Kvz488/wDKa+YP+2lef8n3xVJMVZt/5RX/AMGT/sQxViWp /wDHSu/+M0n/ABI4qhsVfX3/ADin/wCSwl/7aVx/ybixV7JirsVSHzhoR1PTvUhWt3bVeIDqy/tJ 9PbMLXafxIWPqCCHmO4PgRnPMEzXzNr6weiL6UJSg3+L/gvtfjmR+by1XEVtLWZmYsxLMxqWO5JO Y5KtYq7FXYq7FXYq7FXYq7FXYq7FXYq7FU+8n6EdS1ESyr/olsQ8tejN+yn9fbM3Q6fxJ2fpCQHp 2dCzdirsVfnx55/5TXzB/wBtK8/5PviqSYqzb/yiv/gyf9iGKsS1P/jpXf8Axmk/4kcVQ2Kvr7/n FP8A8lhL/wBtK4/5NxYq9kxV2KuxVgfnXywYnfVLNP3TGt1GP2Sf2x7Hvmm1+kr1x5dWJDD81TF2 KuxV2KuxV2KuxV2KuxV2KuxV2KuxVF6Vpd1qd4lrbLVm3Zj0Ve7N7DLcOGWSXCFAeraXpltptlHa W4+BPtMerMerH3OdJhxDHHhDYAi8tV2KuxV+fHnn/lNfMH/bSvP+T74qkmKs2/8AKK/+DJ/2IYqx LU/+Old/8ZpP+JHFUNir6+/5xT/8lhL/ANtK4/5NxYq9kxV2KuxVxAIIIqDsQcVefebfKRsy1/YL W0O8sQ/3X7j/ACf1Zo9bouD1R+n7mBDFM1qHYq7FXYq7FXYq7FXYq7FXYq7FUVpumXmpXS21qnNz ux6Kq92Y9hlmLFLJKorT1DQtCtNItBDCOUrUM0xG7H+AHYZ0Wn08cUaHNmAmWZCXYq7FXYq/Pjzz /wApr5g/7aV5/wAn3xVJMVZt/wCUV/8ABk/7EMVYlqf/AB0rv/jNJ/xI4qhsVfX3/OKf/ksJf+2l cf8AJuLFXsmKuxV2KuxVxAIodwcVYP5o8lFed7paVXdpbUdR4mP/AJp+7NPq9B/FD5fqYkMLIIND sRmpYuxV2KuxV2KuxV2KuxV2Kpnofl++1efjCOECn97cMPhX2HifbMjT6aWU7cu9QHpmk6PZaXbC C1Sld5JDuznxY50GHBHGKizARuXJdirsVdirsVfnx55/5TXzB/20rz/k++KpJirNv/KK/wDgyf8A YhirEtT/AOOld/8AGaT/AIkcVQ2Kvr7/AJxT/wDJYS/9tK4/5NxYq9kxV2KuxV2KuxV2Ksb8x+Tr bUeVzaUgverdkkP+VToffMDVaEZN47S+9BDz27s7mzna3uYzFMnVW/WPEZo5wMTRFFgo5BXYq7FX Yq7FXYqyjy55LuL7hdX4MNp1WPo8g/41X3zY6XQGe8topAegW1tBbQpBbxiOJBRUUUAzdxgIihyZ qmSV2KuxV2KuxV2Kvz488/8AKa+YP+2lef8AJ98VSTFWbf8AlFf/AAZP+xDFWJan/wAdK7/4zSf8 SOKobFX19/zin/5LCX/tpXH/ACbixV7JirsVdirsVdirsVdiqA1fRLDVYPSuk+If3cq7Oh9j/DKc 2njkFSQQ8413y1f6RITIPVtSaJcKNvk38pzQ6jSSxHfcd7EhKcxUOxV2Kqlta3F1OsFvG0srmioo qclCBkaG5Vn/AJd8k29lxudQCz3Q3WPrGh/42ObvS6AQ3lvJkAynNiydirsVdirsVdirsVdir8+P PP8AymvmD/tpXn/J98VSTFWbf+UV/wDBk/7EMVYlqf8Ax0rv/jNJ/wASOKobFX19/wA4p/8AksJf +2lcf8m4sVeyYq7FXYq7FXYq7FXYq7FVskUcsbRyqHjcUZGAII8CDgIBFFWD+YvIrx8rrSQXTq9r 1Yf6h7/Lrmn1XZ9bw+TExYaylSVYUYbEHqDmqYppofl2/wBXlpCvCBTSS4YfCPYeJ9sydPpZZTty 71AekaNoWn6TB6dslZGH7yZt3b6ew9s32DTxxCgzATDL0uxV2KuxV2KuxV2KuxV2Kvz488/8pr5g /wC2lef8n3xVJMVZt/5RX/wZP+xDFWJan/x0rv8A4zSf8SOKobFX2J/zi1EqflYjLWst9cu1fEcV 2+hcVevYq7FXYq7FXYq7FXYq7FXYq7FUp1Lyto2o3K3FxCRKPtsh48/9anX9eY2XSY5myN0UmcEE MESwwoI4kFFRRQAZkRiIihySvwq7FXYq7FXYq7FXYq7FXYq7FX58eef+U18wf9tK8/5PviqSYqzb /wAor/4Mn/YhirEtT/46V3/xmk/4kcVQ2Kvsj/nF7/yVNv8A8xlz/wASGKvWsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir8+fPSsvnfzCrAhhqd4CDsQRcPiqR4qzb/AMor /wCDJ/2IYqxLU/8AjpXf/GaT/iRxVDYq95/Ij89fLPk7y1N5f8wR3Kqty9xa3UCCVOMirVGXkGBD KSKA9e2KvS5v+cpfysjUFGv5STTiltQj3+N1xVR/6Gs/LD/fWpf9I8f/AFVxV3/Q1n5Yf761L/pH j/6q4q7/AKGs/LD/AH1qX/SPH/1VxV3/AENZ+WH++tS/6R4/+quKu/6Gs/LD/fWpf9I8f/VXFXf9 DWflh/vrUv8ApHj/AOquKu/6Gs/LD/fWpf8ASPH/ANVcVd/0NZ+WH++tS/6R4/8Aqrirv+hrPyw/ 31qX/SPH/wBVcVd/0NZ+WH++tS/6R4/+quKu/wChrPyw/wB9al/0jx/9VcVd/wBDWflh/vrUv+ke P/qrirv+hrPyw/31qX/SPH/1VxV3/Q1n5Yf761L/AKR4/wDqrirv+hrPyw/31qX/AEjx/wDVXFXf 9DWflh/vrUv+keP/AKq4q7/oaz8sP99al/0jx/8AVXFXf9DWflh/vrUv+keP/qrirv8Aoaz8sP8A fWpf9I8f/VXFXf8AQ1n5Yf761L/pHj/6q4q3L/zlV+WK2rzRpqEkyg8Lb0FDMe3xF+A+/FXyfr+r S6zrupavKoSXUbqa7kQdA08jSEduhbFUBirNv/KK/wDgyf8AYhirC5ZHlkeVzV3Ysx6VJNT0xVbi rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVZV9al/wCVW/Vd vR/Tnq0pvy+qcevyxVK/M/lXXPLWr3Ol6taS209tI0fJ0ZUcAkB42IoysBVSOoxVKcVcASQAKk7A DFUT+jNS/wCWSb/kW39MVd+jNS/5ZJv+Rbf0xV36M1L/AJZJv+Rbf0xV36M1L/lkm/5Ft/TFXfoz Uv8Alkm/5Ft/TFXfozUv+WSb/kW39MVd+jNS/wCWSb/kW39MVd+jNS/5ZJv+Rbf0xV36M1L/AJZJ v+Rbf0xV36M1L/lkm/5Ft/TFXfozUv8Alkm/5Ft/TFXfozUv+WSb/kW39MVd+jNS/wCWSb/kW39M Vd+jNS/5ZJv+Rbf0xV36M1L/AJZJv+Rbf0xV36M1L/lkm/5Ft/TFXfozUv8Alkm/5Ft/TFXfozUv +WSb/kW39MVd+jNS/wCWSb/kW39MVUZreeFgs0bRMRUB1Kmn04qsxV2KuxVdFDNM/CJGkfrxUFj9 wxV6L/yr7zd/yp79L/oyf6v+lvV9P039X0Pq/D1+FK+nz+Hl/DFX/9k= + + + + uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 + xmp.did:54607f9a-caea-49b8-9eeb-f106a550c605 + uuid:4339b935-e581-5840-ac72-f3c6b52f5b9a + proof:pdf + + uuid:ec456e48-5fd4-4875-93c4-e073576b0227 + xmp.did:03811155-2e06-d540-85f9-58f10d0c940a + uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 + proof:pdf + + + + + saved + xmp.iid:54607f9a-caea-49b8-9eeb-f106a550c605 + 2018-08-31T14:26:04+02:00 + Adobe Illustrator CC 22.0 (Macintosh) + / + + + + Basic RGB + 1 + False + False + + 256.000000 + 256.000000 + Pixels + + + + Cyan + Magenta + Yellow + Black + + + + + + Standard-Farbfeldgruppe + 0 + + + + Weiß + RGB + PROCESS + 255 + 255 + 255 + + + Schwarz + RGB + PROCESS + 0 + 0 + 0 + + + RGB Rot + RGB + PROCESS + 255 + 0 + 0 + + + RGB Gelb + RGB + PROCESS + 255 + 255 + 0 + + + RGB Grün + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blau + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Kalt + 1 + + + + C=56 M=0 Y=20 K=0 + RGB + PROCESS + 101 + 200 + 208 + + + C=51 M=43 Y=0 K=0 + RGB + PROCESS + 131 + 139 + 197 + + + C=26 M=41 Y=0 K=0 + RGB + PROCESS + 186 + 155 + 201 + + + + + + Graustufen + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + + Adobe PDF library 15.00 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 7 0 obj <>/Resources<>/Properties<>>>/Thumb 11 0 R/TrimBox[0.0 0.0 256.0 256.0]/Type/Page>> endobj 8 0 obj <>stream +HdWK\7 )F#>L "$^9*80 O7)Jb^?v=>b |ϣq}󿏯]첽JRK_/.}yvv^aG6/|կǭYnV_VKiݮUlޫSqRmxb+ [>dGzck(pϋ2SFq"EZ[ݵ,)vej8[:/x熗0c䞪;y7M#d#fPޞ'Mz+6lġykhptǟ 5~yhXFKie3Ǖ~+BqtN#`IkAsO[H&P_G6V=73;yΠˈ8<m*ggeJP8_8!0=Hpx1w Yd~nGP!^r](ƺ"b8x܏¾&`P,ji` F+K4AkR$f*pƒF9gCl('%sD'hYڱ!$GpF!<,C56$G~D21I\-ɽ&]G =nFx r]dOpT`wnF)9)<-y˨3vwl8m՗vi`n!ݫpôJu.q j&@6Ҏ!\ )HQC"Kޑzu.C;RH%g(3#޳ȍ+iٗ7 +  q/܊8Tf}<<_y?6ppypLema.EMi |;;胢F >z7VU ">A6Nyj(*ɴd۳g~e< #mcG.ד.8;Bcx4 koǼB /AC32C퉽j4@d8wjb )sTbN5-BTBOTxq8NlA縄T5=r"%;`H3~;0!"JƇ9؉s+ kYC^[gR' xN9 +'j*QGg'pJ Q:2c~dc#Nf>g/ >stream +8;X.+_%hVW#Xm,P7=&-pYp%g5ZHpT#A3DJ+%MCr]dAP[t+[^nicA0mP^'j!n%QE>< +Hlfgg]Wm6H#7I^th06Pf%DQc]qPU?r!`sG@&'2C@-Cp%jgm)0aU_>Do.69_Bk)[`u +Ot.l%hcBO0r^jfmXBp-=FhW>nVCN`=VE/qMjOZD9q>9XVHjok"4FEb3?iNCU6u-$0 +!'QB7Er~> endstream endobj 12 0 obj [/Indexed/DeviceRGB 255 13 0 R] endobj 13 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 14 0 obj [/View/Design] endobj 15 0 obj <>>> endobj 10 0 obj <> endobj 9 0 obj <> endobj 16 0 obj <> endobj 17 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 17.0 %%AI8_CreatorVersion: 22.0.0 %%For: (Carsten Duvenhorst) () %%Title: (heart_light.pdf) %%CreationDate: 31.08.18 14:26 %%Canvassize: 16383 %%BoundingBox: 33 -241 225 -17 %%HiResBoundingBox: 33.4615300484429 -240.495272086609 224.54759606832 -17.5783596674073 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 13.0 %AI12_BuildNumber: 244 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Passermarken]) %AI3_Cropmarks: 0 -256 256 0 %AI3_TemplateBox: 128.5 -128.5 128.5 -128.5 %AI3_TileBox: -151.5 -508 407.5 275 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI17_Begin_Content_if_version_gt:17 1 %AI9_OpenToView: -122.777777777778 12.1851851851852 2.7 1428 784 18 0 0 6 43 0 0 0 1 1 0 1 1 0 0 %AI17_Alternate_Content %AI9_OpenToView: -122.777777777778 12.1851851851852 2.7 1428 784 18 0 0 6 43 0 0 0 1 1 0 1 1 0 0 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 7 %%PageOrigin:-272 -428 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 18 0 obj <>stream +%%BoundingBox: 33 -241 225 -17 %%HiResBoundingBox: 33.4615300484429 -240.495272086609 224.54759606832 -17.5783596674073 %AI7_Thumbnail: 112 128 8 %%BeginData: 17930 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FFFD0B275227522752275227522752275227522752275227522752 %275227522752275227522752275227522752275227522752275227522752 %27522752275227522752275252527DA8A8FD1EFF2727F827F827F827F827 %F827F827F827F827F827F827F827F827F827F827F827F827F827F827F827 %F827F827F827F827F827F827F827F827F827F827F827F827F827F827F827 %F827F827F827F827F8F8F827277DA8FD1BFFFD0527F8272727F8272727F8 %272727F8272727F8272727F8272727F8272727F8272727F8272727F82727 %27F8272727F8272727F8272727F8272727F8272727F8272727F8272727F8 %272727F8272727F827F8277DFD1AFFF827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F8F852FD19FFFD5827FD18FFF827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F8A8FD16FF522727F82727 %27F8272727F8272727F8272727F8272727F8272727F8272727F8272727F8 %272727F8272727F8272727F8272727F8272727F8272727F8272727F82727 %27F8272727F8272727F8272727F8272727F8FD0627A8FD15FF2727F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F8A8FD14FF %52FD0A27F8272727F8272727F827F827F827F827F827F827F827F827F827 %F827F827F827F827F827F827F827F827F827F827F827F827F827F827F827 %F827F827F8FD1327F8A8FD13FF2727F827F827F827F82752A87DA8A8A87D %A87D7D7DA87D7D7DA87D7D7DA87D7D7DA87D7D7DA87D7D7DA87D7D7DA87D %7D7DA87D7D7DA87D7D7DA87D7D7DA87D7D7DA87DA87DA87D52F827F827F8 %27F827F827F827F827F827F827F827F8A8FD12FF52F8272727F827272752 %FD3EFFA8FD0427F8272727F8272727F8272727F8272727F8A8FD11FF2727 %F827F827F827F87DFD3FFFA8F827F827F827F827F827F827F827F827F827 %F827F87DFD10FF52F8FD07277DFD40FF52FD1427F8A8FD0FFF2727F827F8 %27F827F87DFD40FF5227F827F827F827F827F827F827F827F827F827F827 %F87DFD0EFF522727F8272727F8277DFD40FF52F8272727F8272727F82727 %27F8272727F8FD0527F87DFD0DFF2727F827F827F827F87DFD40FF52F8F8 %27F827F827F8522727F827F827F827F827F827F827F87DFD0CFF52FD0827 %7DFD40FF52F8FD072752FF52FD0D27F87DFD0BFF2727F827F827F827F87D %FD40FF52F8F827F827F827F852FFFF52F8F827F827F827F827F827F827F8 %7DFD0AFF52F8272727F82727277DFD40FF52F827F8272727F82752FFFFFF %5227F8272727F8272727F8272727F87DFD09FF2727F827F827F827F87DFD %40FF5227F827F827F827F852FD04FF52F8F827F827F827F827F827F827F8 %52FD08FF52F8FD07277DFD40FF7DF8FD072752FD05FF52FD0D27F87DFD07 %FF2727F827F827F827F87DFD40FF5227F827F827F827F852FD06FF5227F8 %27F827F827F827F827F827F852FD06FF522727F8272727F8277DFD40FF52 %F8272727F827272752FD07FF52272727F8272727F8272727F827F87DFD05 %FF2727F827F827F827F87DFD40FF52F8F827F827F827F852FD08FF52F8F8 %27F827F827F827F827F827F87DFD04FF52FD08277DFD40FF7DF8FD072752 %FD09FF52FD0D27F8A8FFFFFF2727F827F827F827F87DFD40FF5227F827F8 %27F827F852FD0AFF52F8F827F827F827F827F827F8F827FFFFFF52F82727 %27F82727277DFD40FF7DF827F8272727F82752FD0BFF7D27F8272727F827 %2727F82727277DFFFF2727F827F827F827F87DFD40FF5227F827F827F827 %F827527D527D7D7D52FD057D2727F827F827F827F827F827F827A8FF52F8 %FD07277DFD40FF7DF8FD0927F827F827F827F827F827F8FD0D27F8A8FF27 %27F827F827F827F87DFD40FF5227F827F827F827F827F827F827F827F827 %F827F827F827F827F827F827F827F827F8F827FF522727F8272727F8277D %FD40FF52F8272727F8272727F8272727F8272727F8272727F8272727F827 %2727F8272727F8272727A82727F827F827F827F87DFD40FF52F8F827F827 %F827F827F827F827F827F827F827F827F827F827F827F827F827F827F827 %F8A852FD08277DFD40FF52F8FD23277D2727F827F827F827F87DFD40FF27 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F87D52F8272727F827272752FD40FF52F827F8272727F82727 %27F8272727F8272727F8272727F8272727F8272727F8272727F827522727 %F827F827F827F87DFD40FF52FD04F827F8F8F827F8F8F827F8F8F827F8F8 %F827F8F8F827F827F827F827F827F827F85252F8FD07277DFD41FFFD0752 %7D527D527D527D527D5252527D527D5252FD0C27522727F827F827F827F8 %7DFD5AFF5227F827F827F827F827F852522727F8272727F8277DFD5BFF52 %27F8272727F8272727522727F827F827F827F87DFD5BFFA8F827F827F827 %F827F85252FD08277DFD5CFF52F8FD0727522727F827F827F827F87DFD5C %FF2727F827F827F827F87D52F8272727F827272752FD5CFF52F827F82727 %27F827522727F827F827F827F87DFD5CFF2727F827F827F827F87D52F8FD %07277DFD5CFF52F8FD0727522727F827F827F827F87DFD1AFFAECFA7CFA7 %AEA8FD19FFAECFA8CFA8CFA8FD1BFF2727F827F827F827F87D522727F827 %2727F8277DFD17FFA8A782A682827CA682827CA7A7CFCFFD11FFA7A782A6 %82827CA682A682A7A7CFFD17FF52F8272727F8272727522727F827F827F8 %27F87DFD14FFA8A77C827B827B827C827C827C827B827B8283FD0DFFA8A7 %7B827B827B827C827B827C827B827BA6A7FD15FF52F8F827F827F827F852 %52FD08277DFD13FFA7A67CA682A682A682A682A682A682A682A682A682AE %FD0AFFA7A67CA682A682A682A682A682A682A682A67CA682AEFD13FF52F8 %FD0727522727F827F827F827F87DFD11FFA87C827C827CA67C827B827B82 %7CA67C827CA67C827C827BA6A8FD06FFA87B827C827CA67C827CA67C827C %A67C827CA67C827C827BA6A8FD11FF52F8F827F827F827F87D52F8272727 %F827272752FD10FFA782827CA682827BA682A7A7CFA7A67CA682A67CA682 %A67CA68282A7FD04FFA77CA67CA682A67CA682A67CA682A67CA682A67CA6 %82A67CA68282A7FD10FF52F827F8272727F827522727F827F827F827F87D %FD0EFFA8827B827C827B827CA7A8FD05FFA8A77B827C827C827C827C827C %8282FFA8A67B827C827C827C827C827C827C827C827C827C827C827C827C %827C82A7FD0FFF5227F827F827F827F87D52F8FD07277DFD0DFFCFA67CA6 %82A67CA6A7FD0AFFA782A682A682A682A682A682A682A67CA682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A6A7FD0EFF7DF8FD %0727522727F827F827F827F87DFD0CFFAEA77B827CA67BA6A8FD0BFFA782 %7C827CA67C827CA67C827C827C827CA67C827CA67C827CA67C827CA67C82 %7CA67C827CA67C827CA67C827C82A7FD0DFF5227F827F827F827F87D5227 %27F8272727F8277DFD0CFFA77BA682A67BA7FD0EFF7CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA6AEFD0CFF52F8272727F8272727522727F827F827F827F8 %7DFD0BFFAE7B827C827BA7FD0EFFA8827B827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7BA7FD0CFF27F8F827F827F827F85252FD08277DFD0BFF82A682A682A7FD %0FFFCF7CA682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682CFFD0BFF52F8FD072752 %2727F827F827F827F87DFD0AFFA7827C827C82A8FD0FFF82827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827CA67C827CA67C827C8282FD0BFF2727F827F827F827F87D52F8272727 %F827272752FD0AFFA77CA68282A7FD0FFF82A682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA6A8FD0AFF52F827F8272727F827522727F827F827F827F8 %7DFD09FFCF7B827C827CFD0CFFCFA7A77C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827BA7FD0AFF2727F827F827F827F87D52F8FD07277DFD09FF %A78282A682AEFD0AFFCFA77CA682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682FD0AFF52F8FD0727522727F827F827F827F87DFD08FFCFA7 %7C827C82A8FD09FFA8827B827CA67C827CA67C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C82 %7CA67C827C82A7FD09FF2727F827F827F827F87D522727F8272727F8277D %FD09FF7CA6828282FD09FFCF827CA682A67CA682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA682A67BADFD09FF52F8272727F8272727522727F827F827 %F827F87DFD08FFA8827C827BA7FD09FFA77B827C827C827C827C827C827C %827C827C827C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827C8282FD09FF27F8F827F827F827F85252 %FD08277DFD08FFCF7CA68282A7FD09FF82A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A7FD09FF52F8FD0727522727F827F8 %27F827F87DFD08FFA8827CA67BAEFD08FFA7827C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827CA67C827CA67C827CA67C827CA67C827CFD09FF2727F827F827F827F8 %7D52F8272727F827272752FD08FFCF82A67C82A8FD08FFCF7BA682A67CA6 %82A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6AEFD08FF52F8 %27F8272727F827522727F827F827F827F87DFD08FFA8827C827BAEFD08FF %A7827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7CFD09FF2727F827F827F827F87D52F8FD07277DFD09FF82A682A6A7FD08 %FFCF7CA682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A7FD09FF52F8FD0727522727F827F827F827F87DFD08FFA8A67C827BA7 %FD08FFAE827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827C8282FD09FF2727F827F827F827F87D522727F8272727F8277DFD09FF %82A6828283FD09FFA6827CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA6 %82A67CA682A67CA7FD09FF52F8272727F8272727522727F827F827F827F8 %7DFD09FFA77B827B82A8FD08FFAE7B827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C8283FD09FF27F8F827F827F827F85252FD0827 %7DFD09FFA7A682A682FD0AFFA682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A67CCFFD09FF52F8FD0727522727F827F827F827F87D %FD09FFAE7BA67C82A7FD09FFA7827CA67C827CA67C827CA67C827CA67C82 %7CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA6 %7C827CA67C827CA67C82A8FD09FF2727F827F827F827F87D52F8272727F8 %27272752FD09FFCFA67CA682A7FD0AFF82A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA6 %82A67CA682A67CA682A67CA682FD0AFF52F827F8272727F827522727F827 %F827F827F87DFD0AFF82827C827CCFFD08FFA8A67C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827BA7FD0AFF2727F827F827F827F87D52 %F8FD07277DFD0AFFCF7CA68282A7FD09FF82A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A6CFFD0AFF52F8FD0727522727F827F827F8 %27F87DFD0AFFCF827CA67C82A8FD08FFA77C827CA67C827CA67C827CA67C %827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C8282FD0BFF2727F827F827F827F87D522727F827 %2727F8277DFD0BFFA7827CA682A7FD07FFCF7CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA6 %82A67CA682A67CA682A67CA682CFFD0BFF52F8272727F8272727522727F8 %27F827F827F87DFD0CFF7C827C827BA7FD06FF7C827C827C827C827C827C %827C827C827C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827BA7FD0CFF27F8F827F827F827F85252FD %08277DFD0CFFAD7CA682A67CA7AEFFAECF82A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A6AEFD0CFF52F8FD0727522727F827F827F8 %27F87DFD0CFFCF827BA67C827B827C827B827C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C82 %7CA67C827CA67C827CA67C8282FD0DFF2727F827F827F827F87D52F82727 %27F827272752FD0DFFA8827CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682FD0EFF52F827F8272727F827522727F8 %27F827F827F87DFD0EFF83827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827BAEFD0EFF2727F827F827F827F87D52F8FD %07277DFD0FFF82A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A67CADFD0FFF52F8FD0727522727F827F827F827F87DFD0FFF %CF7C827C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67B %A6CFFD0FFF2727F827F827F827F87D522727F8272727F8277DFD10FFCF7C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA6CFFD10 %FF52F8272727F8272727522727F827F827F827F87DFD11FFA87B827C827C %827C827C827C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827C827C827C827B82A8FD11FF27F8F827F8 %27F827F85252FD08277DFD12FFCF82A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A6A8FD12FF52F8FD0727522727F827F827F827F87DFD13 %FFAE7C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C827B82A8FD13FF2727 %F827F827F827F87D52F8272727F827272752FD14FFCF82827CA682A67CA6 %82A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A6A8FD14FF52F827F8272727F827522727F827 %F827F827F87DFD15FFAE7B827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827BA6A8FD %15FF2727F827F827F827F87D52F8FD07277DFD16FFCF82A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A67CA7FD17FF52F8FD0727522727F827F827F827F87DFD18FF %7C827C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827C827BA7FD18FF2727F827F827F827F87D522727 %F8272727F8277DFD19FF828282A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682827CAEFD19FF52F8272727 %F8272727522727F827F827F827F87DFD1AFF83827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827CAEFD1A %FF27F8F827F827F827F85252FD08277DFD1BFFA8A67CA682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A6A6FD1CFF52 %F8FD0727522727F827F827F827F87DFD1CFFA8A67B827C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C82A7FD1DFF2727F827 %F827F827F87D52F8272727F827272752FD1EFFA77BA682A67CA682A67CA6 %82A67CA682A67CA682A67CA682A67CA682A67CA6A8FD1EFF52F827F82727 %27F827522727F827F827F827F87DFD1FFFA87B827C827C827C827C827C82 %7C827C827C827C827C827C827C827BA7A8FD1FFF2727F827F827F827F87D %52F8FD07277DFD21FF828282A682A682A682A682A682A682A682A682A682 %A682A682AEFD21FF52F8FD0727522727F827F827F827F87DFD22FFA7827B %A67C827CA67C827CA67C827CA67C827CA67C827CCFFD22FF2727F827F827 %F827F87D522727F8272727F8277DFD23FFAEA67BA682A67CA682A67CA682 %A67CA682A67CA6A7FD24FF52F8272727F8272727522727F827F827F827F8 %7DFD24FFCFA77B827C827C827C827C827C827C827B82A7FD25FF27F8F827 %F827F827F85252FD08277DFD26FFCF82A682A682A682A682A682A682A7CF %FD26FF52F8FD0727522727F827F827F827F87DFD28FF82827CA67C827CA6 %7C827BA7FD28FF2727F827F827F827F87D52F8272727F827272752FD29FF %A7827CA682A67CA682CFFD29FF52F827F8272727F827522727F827F827F8 %27F87DFD2AFFA8827B827C8282FD2BFF2727F827F827F827F87D52F8FD07 %277DFD2CFFAD82A6A7FD2CFF52F8FD0727522727F827F827F827F87DFD2D %FFCFA8FD2DFF2727F827F827F827F87D522727F8272727F8277DFD5CFF52 %F8272727F8272727522727F827F827F827F87DFD5CFF27F8F827F827F827 %F85252FD08277DFD5CFF52F8FD0727522727F827F827F827F87DFD5CFF27 %27F827F827F827F87D52F8272727F82727277DFD5CFF52F827F8272727F8 %27522727F827F827F827F87DFD5CFF2727F827F827F827F87D52F8FD0727 %7DFD5CFF7DF8FD0727522727F827F827F827F87DFD5CFF2727F827F827F8 %27F87D522727F8272727F827277DFD04A87DA87DA87DA87DA87DA87DA87D %A87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87D %A87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87D %A87DA87DA87DA87DA87DA8A8A85227F8272727F8272727522727F827F827 %F827F827F827F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8 %F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827 %F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8 %F827F827F827F827F827F827F827F85252FD6E2752F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F85227F8272727F8272727F8272727F8272727 %F8272727F8272727F8272727F8272727F8272727F8272727F8272727F827 %2727F8272727F8272727F8272727F8272727F8272727F8272727F8272727 %F8272727F8272727F8272727F8272727F8272727F8272727F8272727F827 %2727F82752F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F852FD6F27 %52F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F85252F8FD0527F827 %2727F8272727F8272727F8272727F8272727F8272727F8272727F8272727 %F8272727F8272727F8272727F8272727F8272727F8272727F8272727F827 %2727F8272727F8272727F8272727F8272727F8272727F8272727F8272727 %F8272727F8272727F82727F852A852F827F827F827F827F8FD5127F827F8 %27F827F827F827F827F827F827F82727FF %%EndData endstream endobj 19 0 obj <>stream +%AI12_CompressedDataxdqȌԜGpekV]-qFh4]5a십>8y2@CP5C'@88~ܿ_~O?O/ŇɭoC?ŏoCOo޿mw_q^ǧw/ӻ_/?я||d'}ů?}~m7{B 'o^PSOv7>]n?9XW'Y_Jڶs+>d_|?{ѺX94|,~v~5ӿዧ߿fh_O׿}n}śww6g?~W_|Ih~zz~Ek?6Oֲo~X췭[w9f?~|^˿׃}\O^pƌs O>s='_}xi~ZSawˆ۰ iprͦG!=O_v~d~Da&zz o_-v7_)oqϛUKo~Wo~o޼{[5H'&L(<}xOO~;yٯ/G{~Xu__>=þإ_oOo>O_C;oWS_1o}|Ͽ'ٿ?xOmQ|x/;k>w-'d훯|G&$%![vO>[}xwh?c +c7?n +B}n2)nfB|4B2Fb1kl;VzđCvdZroӇ=D +Ct!WzbJw{YNlt\W~ͲMϰ!nה7E$){}cσ }_󨿏U|'?7i)otٯ<HEz󖃝s6vfێ_1*?\JQʋ<ޔW5ǎfoemOlV0hE{փs z?Wciӣ.SS>]Ar~_=ؿ?7g_貽l趺Ϙ`yFyWc+'o\isuc+c=σ}3^K/364a}Gp`{ t4}}lz"cEU]|^xxx<,! +͈Gq}/>r ͟9/ƃY}4~ŦIz`su،m6sd39zm^f{Y_nl'[m.ok~q??_ûy^4ޖSuMĄ }iޖkekl^ڢ]m' >M&64gebZy-o?T3v=d)f8?;xk!1>.QdcdG6ykѤc]BI;qn& +T]ޤ ߛJڡI9ߣm +h~_Vm`qm? &wo_-׃ͳBs3!LcHaÔ6z#-- ۰{ʹّX:;.w Gf_6uwlrM2}=G9 :մlRt̰ԣRL; ˟S%.4CMSFV3 JX].ᡇqw^oXj/>b f=s)H(0GiklReYnf\rm^>o-R\{ܭ꼑oe/i{;ףP];Sў~d{܂SeK$Ylou,G. 6=GώqS=;ۏ$U$C0ѦMa21 +7,9'P&hWL5X<ʸXn7h[-_[vMu8onfT7{l$ŦE1l~ {֭rCi}IǃLh?c/ǾQ/.+m Sˆ6 +UK3Ū"tHMv~lQng?= |gӣ?jHmL+TXXoeWпJXF^"7շ$?cYxwua%m=_NЌ6Fgp7v/>M}r֯UH)oV0}ñ^6h;nwb1uGV2;i]+`lɞmlE 8%s?jm?i~/78 +%&ҫ+A?@$, r3AL`Yzg>xv(da"DR ; ITHJ +H ԽR +L97 +OeE."LU/_c6&p=f ٣+|$?3,|f?_|M}^楹fI(f8w='̚2>i洹9kY 9/ sI)|lZ\yuYsn]ά܊sn-HR +.& {2՜zu ,39 O|Yw,f sf!1%X3f΄Ŏ0)pT,p$79iP4ص ,q2a&^zZl/O;L]hhϔk4L4azŜ{w?g{᷷?Qzx:S/0ށJ} Uvj/J^y]3QuP1>6g*7ɞA6; ?maeovepǝFj fMT`FWjZi@Ag l~o3n?iꭽWؙguNm.1CkSz3w""[ZrKaǘmڰ>dkwZʸNoWFfu5JCA_]]vZ{UmG=`rz7\Kh#FoHq1;lώGlw,i'ymLLOֻWw&6.)f64~hwѬ^rxgD7ޱgN5tNR '$B3-;z36Vȕ g(c!DyPW֨CfDCu[#/7IރmGvyxgݶۉdemUKhI>;T]yHy//Ä]t{>?˙&oJ4ܸ5O p޿Ҳ9RF};5 ++!|g[.l +pW3L(3(e9(ʺ4l. e:HRI,SMQX:dpAgس$"e?̟dc`/^Imҗڒb73ݶ1؄y5a_Έ!dڟ@ `:軽*dOa}!J8Hytn#;RޮiD -m茡W0pgb ԤOћbg!hWl![-&nM3T)#䲍N ?SɅE&5'odKRW՟]zNC0G\NY| iMf9%u3zդ"f \ԎhŇ\SyȲPa4(q,~m #!ff2PH9NZW_.=I=8pal&|~a R#Rľ_}B%&yݒ& i4}Y7IM rM30P`;x+d_a]j#i-D6Q{\JϦ7Ezg5& v]eo")*$H†l)fZw6SI``*kG}0K#.@ y{ 4hÄ'mB +#Ɖ0k+d^Ei}d|dW]Lr*SV i lmN$FˈElƕ[E#py3nX(+Z֭,;߸M6smLn{Y-P^iLjbO6NMWdfٞtrt+7 1@־ύ""yP+o)xZ +`sPCѢee _@?0#aRa!(h(LO4 ĭe*lg[Co&let|l[U֪)tR{Z:U㺮,YY&m -ý:E\kRzn̓#h0m`Rzjk3ɺĬmb$mp[MT˶>=4 Mr& *6]EdK 4DiOט<{PrE!rץ g,0%әFx [ŻJxcFAh^xVyL_}zms0Nٽ^C)=^h(f6յ{,ӫCuـI|ڕBp^dLW6d60UI&W07k"&O|%EQtB2&A6d&lF5m]$"1(D-$d1麛n5fj5q`ƸG t!e*+ $zl<bSc=[1aÏqU Uɺ&%)vEyNPvwq +SKo(bD5zTg;ގL1V4T}jE. ;zSh`Z8b׳]D3[A c 7[.#_ȣj%Of*S1=fgIzE +@/bsᗐ`pL ;*ϡEcv~vBΰ YIfK:3ּ)p%*`3N> aM}\Bx3?jrO%’֨jz وsi]|- ?6uJ)RgL&*pYꪂ'M娂~ƅd (qJ$?[5I6h{^9S'#oТzX/ ΐ?V#L\,7JC8opMM0"ӚYQ{4i[@옉-5#""ѻlb6Egl3 HJDFbR8zAjekR+vX:TPYMd*gyMC{l^4l/ c<(˦NM=n,k5B!Vqf2o. @RD~e +"-4Փ% Qx[^ + |9ro[!NH6eiWm Ef$Tl>½Xx:A~= F1*ɦpt [ h, l0fu~%af߫n/74510XNٌPGzF"Fvjb`;ƶ"LE$=4ͺmpm9CZaz%' APE:gsh'I9 Ys:e ڤ Lߍ_Zb8Fƶ*IfFf~QB'7O' Utڤ|mwAB~`k"وRp$qq &^d&"u2yw *bb0\ Luw3bOݦ t +jr"x +T]6d`cmA6S2{w?-wAFMH!T 6vN4hUt:2Op +@,J [Pq5Dd'*U vH +a$P b7/8)]May +lBQ p2JMxOd%%ΧAv`MEPY,„e(@m6fN[n6:8.37 6[|& yt$>V7.AGXaTnb?\`"ܮ).\l f#.H.nXMi!Y&f+afkH])T$͒AGI=¾!ONJ^kt ܑWfdPg,o60;rbd4k֡j\5o&YjE@ӹnC;es +SO7Y[UzsV"g]aZpk\Ԁ5M٬{k.g;y5$BʌBGӷk,]Ķr-%d.\bOAN"qv/dAP̫F<]1UXx f zwEXcu񞤮}`E~x6Sv@]e$ ՝¡ʟm"@ikl3KV>-8*I뽵N r:H\lZ(NΠ, "%+#^t)S\s(&6RMhVA(-N"Ie6e(Ff4i(aH(F-^ S~v%(ihr.r$'ɸmVɉe!`XyyeHru0蘭&\e)+sƹڵ-#y.L ͝+&谯u2<]JQ%'j-AC=EޙVl>͓}wtռ!m sЁ +DǷ \n/]?\d?yP/(G!QY6ϏeQRΆDhIK LAӔz8 QY~SƮ2#I zUJ`"239@GwCŎ j ʒ/#}lQ JvigN  Dثn=D))XU؂&'t>#*}uF-A'e=E/3<0 f(r%.'I]h3I%,-459PF~4095LͅƇ,2QvQN^SZ0l͓}H(D0S4Yʌu#pADaff"]Qfpfkyw r\uS6J$]mik3-XIy@O7%*#E28/@ypBM[yw%NN_Svm^|ÏcH 0$/yaOWC_GYdJ!Ϣx;S?سo+2Nl ZL d q%)̋\,6{e~1;XrQ%$bg4mDܔ4wiG =U tw* D"l/޳RKC',5Z$#"_G]6hV<9tfm|v2OvdQnlrgʊ7cpu//*9PfyAۓH& +,H$oXu7嵸}+p@G`Ig3NpK$APqZ` Ug&S#9OCVğgz_'3v@R}Ŕ g5LOJ)Mc0>Ot)Mc^+qc^L IױXf>[Z/J ۃM&lx2hm$uU#RAPmů`R _qQ&K!UtXBlCԨ.$iPezՂ[g +R~K?Oͻ/npg|_sEeK2mJ-[N3ɴ!+ܯLRfV +8x I)lyvU5򬞠AfU.W} +29TSl`3&Tfr€h<(Aa*:~%*G'귉Wڃx ++9 Wƫ\_>$*d G- BqU-:vJc#:bOӄZWXI"A脁#v +2%P]:Kʾr (P`?6Mn`j*l%G@Tzz絥ɵAS/cFHD"p*v^Wtf-wZI+Y@J2Kr4˩AvJd؁J7+TWY,? +)F?J,R2lS q[Hu&]diPO ^ ҰGd7」!Z%ĄW6Gp2 W4{E(`>G7aƔb4L̢8ʋqAhY}&ПU[&7|a}sCSd04R RkɛcJ3MPe]t %؉5SF'0bXC1fcI^mo< O&K*R$"F^ ѝ4[Y9-S8 +\wOˮ M hf Nv +–(&0Slf}RBZYHԜM(bFQ:hI^YV-YoUn'%f͎F/醀Jh*D2nUra#v0ئFѵB}`{ suHYBI8(|܋r0&9@һ:WC\l* _rq@-:HL2.&}4ܳ9rw,O@te'@T!] ݨIN4 +z1$y 4|v Rp驇/3Vy\#4 Ѕ_3M&P3[EQugc TeX Mj.wec/vЉ9A#.ؓ + %^XGOd)*Ѩ +mxO LdTA֔ +p R 0΄k@-I +bYtV 3#`3=Do.4N&i;y@LhE~lsUx# VYwt!␔ V=7vlmhƙ c'ۮbm^#{4G=*Un.-^MN]p&A6 VRRuPUb7Ot 3hJ2OrՓp 4.}JXϔB Z{R*m +Yi%04vb `);f*4~#4xSmPpQwHxȑFCUݙސ@[|6P_5D&Yw?u`I5AăG6\9i&KFŢN06:+جMr`4jw Y^7cEWaDW +q!%pžLt8қ $phbG*1NVӺ@ %k=}S"UkTp9`ɬx^3EHHWC 3l*a%N`n/,=VJq{%~RudG%`Ó-t&ja)V,rj+JgӤzٖY>Ϝ(ڌ0..&m^SR)( uGEB̎L4pt"ZibM^UVo33l@>.^SeMTe{xlP+n- )(IWyyCsU45t\z5 N #yɓF!ֽ nS*^X2s:{ơC*UxG'db½=flJtR +itBW@Df'tvT@Z@e7^B2$yªrWt0UqtH:%,T<&= piqPWE[!T F: b @QWa<}H=Q(MSc֕UV.Ud#Xw +UXFlT,*5"QY3W_<oK,BiW4!COudZ+e#:$(^E`uB06f{>brI5TO6Њe7 +[W[PoD({jLQҎZa(UVJxY4ϋJ#)LM%8f 6w +@]5q\sDkY{CՑ픙U@ &2:aožn /=&vz۔*f}p}\hʵC-UI +6/]i1Ѿyz9E(XBĠqל-7 ^TgFAgHXLR8,p@ݥ!G&=T*Ϝ|.bT'eU)S p!ߣ-,Mx5(-`N]5q!){D>.SrDV*њ܅XE,gr63@Yk\ȀR~ꏘhIeu3(533F nvJ.F, ]YiL^3),S#orQq?Ui Cڨ & *ns+A~M*,MضmyԪdܬ*TXLpFu(@ +rKW9րZJA [B B) a|!y`VM9W%8,gsK*Mc{Óͺ^]lo>4%Z+^7X;aU[]܆=}KQMf/zRlz6U˛ÎG,r\PCe5H?_)sW+%"MHdJrb^Ii0)K Ut썥i4@L9DMV>0]FZN׌!Sv|+l[d)*5FS+dչEvg)jhR Ӯ*`p(ا80z2nqJ  {cBYtDS !zKއ#Nlɻdnh,c^6|E#r=Sp"JR8C*&P۫)[M$ZD5 뜆mzoN/ tx8'YB +FK1TD=᧔$u-849ERU T$A-Rrd%@XrL¦}] +&+{8h0*L8*]k߽joܣL{ PKsL 9gP,HT(2q|?W Ó5;J/hݽʼzfΖkWq8e\_ A;/3 @ +cm{:L9 -.&({p`9R(8f xA ťl/!Rgfņ' o L~E_h c2Z8~.̴ߣ Frg?{ݧ/OJ7_O_=!wnY=žLO=#a>X\lı< -߁9>aƗ0;yⱣj;8!S<*|>Gxҹ4X|﹄3w]N}IKZy8cu'ZN$4I yU&S}|QSPȹIٮj?O~lf݈!5{3#@,p:nN^E :\ij8͝Mт@aKԴxRʰrXh\1BI{׌Wp)m@v>p^އ8G1ŚʤÖwoJ +dE追Qhқugڼ>6w?&;._vF=fq}SZ`.E9'^tQzLc.c/nX^ɢ.i4ɹYh[;qr!u|x +K)5CmLm4p Gm-2PzlYH]&1צ"5"`jS H]L);6e/y;# XygVbFKK$R;/vub&kȆҐ5.l?݌)"G묲:a t-pⵦ@q-y;by.ꜝU8^ՄhE&(e;'?"s7]ӗ: +fy1 Gijhx 1I)"sL ɏ^;9ykL\< +υR[9#V a1 +=5)b)JXd@SGI5@D0[}FM2g:R[*}t{<39:**Rwm6Yf$K@L,vlnT1Yzq;3˜N sN#ȳ蕑%[ʴd+/ +e/X=9P&3d=Of(܂ghtvQ!jX Ȕq$>Dg'1`,c1qRn,wRc 3%F<)C$esa1ZbYR;QX)ˉFy#Y;qP"!TΨS<z"N/ DUVܐ/ǙW3:8#&} '+kzQoz*NE.h:h쿛]B%ډwRËw;/Qhf:2h$p-ܽs=Q8c䱵zy܌;*hՑl:9U=H,͟zm.v]QexQGulQLz\9mt)&<#xsulRs9q +^ +7|g@Ж#^PUHy1n2eu +պ8n?q*`\Hg2SEU3Zx(H4 kmHq2xVu,᪞Al6_ 0P"N+UPE i|pUDsW锦E|`\ǻVptUrUGo]ÄQ2ƞ\mw|4P)zIj aM+^Us|kU2'O̼KT9_Zns$+TJpo@EwfOc]01\!aE*y2*ӉU4Ik'?Nu\88Dj0Y=tU[ E)3. N=o4zިJ\b&)cEpz zd8 ,$N}Fu;S HnKr]h.̴KSN +Y5mptJ3({t3s:Y۸b8U$AppJֳ ~ȬtEpʛp[2T,Jҵ#ΘN1AԷ Sߧqp}Ec3Sަ)*Pc8%qY (L=c8e{cf8 +Hadm ~!f#T5)gL.nf8w`8U MN ~,/-k\ڽP 2S Qz\@;) +V[TGZTPgTj s0>xh&ةNi|SI1TQٸvS5i[d@Z$ށ%G]HT0EсT`P0A1Dl2'1Ne=rrls>ST# +mL%R@PZn/i홧U,anI:CT}mCdZX at?4Ft(llBG>V%ԈZ/йZ<?cs95 3A)R$T;3~L*m2i1.Ƶ0d, @&e윗">|TdTi$$_{$OlZcNGjΝQ&"\IImNHJ-qD 5$nfcTQ/.^V`gqnRFj@@t҉q +ZT9ԐuK^餹B;8cQC:赕D_ M}ɎDx.,"Hu\&z"vv"H)FJgD҉4fA0v"Hׇ/@vULt RZDC#El~3wCfժ?AJA&> }AB) ov"oմ^A*&y\3"H5* =`J ɞ(#|L1MѯRle=[E' 71e7br-H,J$z3HwPwH5w80ElRGȰj Wg?{(OΑQ-͌wGy37_߁_u=lΈ4IEA xeyk\v#*`Xq(QND E,"tEgtJP ݝѹAs[[|7OTS|$Yԥ94wwNX#?nG62s9#sWl[B;g)řTꨆ2X]n_d ^ꔬ j(aO;;ʻJK׭9rp:S>78H'%dnl}w%B|JFU(}gglm#)#x|wd-sR0%}ԋ rw<+#Y;b$Ϋ(qNHytԥWɢ:;CP&ebw$։e:# +Z^j3Y7#"HŁjqN'#*hOX(%EȱVN Ki;W al#`D4X~]B3VA*)o>!H5G@ +HEH":#? p/~@ H?t dM +-ef!#?OÈe.?udӈ]~QG3G EGGiJ[\}((*uwpNѷon ("g$B;PUF(pE*nazzQ<ȿFEzm{A`QQx";b2W6~FG8^v&>0L|aqPgbڪpIG1xgS*hRɚpTC$O@, wJ>9'`b睓O,voU7c;,)p4 G>Tпxo º3f8~]O^HƬtɅNbO&Dy}R4uόᙓX=KxC}ƭw 08ϐɸoNqԓ7z%Mx4D2;~ur|,,%+{d#y3*=_]6S>z:{qɩJDYSO/DTzWǏz +:l ;V'y +$p'nz7B5icXNXyԓI_gS.8QS0OgTzĠ<1.=% +ƥ,3=IrԠi,AOZG8cГOy|W&;R;q j(tRȠ'Or LJO`XcȪ}ғlAO tRkش +cgsOr֑AOU>5SΠ<#͹E?r+i”DOTzwiPw*= +|>ZUX }S =]Tz[9#Yu)ѽ@O|&LN? 6@3ş' =FfȟЌmB3Z4m' ypwx%#*;AΟGwNy *O?O 7HG *NyHm/<=_q6vFF[Qc# 4WWzUYFDa\(l'R=%p{vU`o*.v=<9`cQfQ1/8G䘵9Ь3ك[Zgz:r7(]7?*79=?}8#N*W&FG1,s^_)Emȓx ջj"ՓJ&PnHuYT|RVŪGQtMpƫKB,~ً`[%z Þ]~iK=i3y"qMZq\<ז*8d_3)bS eP153OMj:zR̉iO0ccU;5^)W,z^)́P8yesmQ>2UwEx3weX ۤ+\$á/8%M6.(0CIN/rуDv/_vEi ^ܞ]b4Wyɩ팾 H "|wb;iUGNҕqn"A8z"" v$bb +bx ޲cM"'l(Ëg 4ξ#Pp*MoT.ʢ I؂[+*C&|[aF/Agz lfvYfKwk +%goW#o*i~I0, ޿g_|޿~HJ *-_ݏ(YG(yfG0z#FږH.nͧA0[d_"0~.n)e {OEw魾DmcLDU/~DwCtz1}G]Z KyR=bj?ȿ"oݚoW xgHK]|?9! f5zFj|e+.&?&L)^7/. ,_,|G+(; L*o|z R9:̣L|6oʙͳd]/782C( +b}OdGně?ސo]W:Qbvץ ~><7Kt$;'u`{t(& @^ɮ_!EKqKMlz + aØ){G'bF<Ӄk0=h[yoa7J=4R bneQMYa +8 ~]3>e<Ο:Nzv׭}ݴ/͐nfj, '"^;ڑITyvr/hA7g+pkmOq Y93 v%:!jopŅ܁wOalSN.\&Y*q$#IjʽAaЊƈxj.!ƀ#S*cLvL0=<2b.vxϜ NoyG [[{֌KP_E}ut9y}A:!װV`09UD3@.W =UZtWoR#h'iҝ+#l2ݲq`͝CʶxNS4P"n# vp++Ÿ^: #W5Τ,p,})B{DJݡ>m3y;t+e/6 3UiRȱuΏEt# WGr"]oI:O}J`z΁܅Tɳj3ގRo8?s%A#?M_7n<@PqD^c==^NPv(F\@+f/#ƫB4%_ҌةـT,=&.`N)(88uԱo!w o1,=#''U|^14n/6]'}+AS0/fHpur Zci%+6zǧ9~6ܾf W-#x} {OkD/ }({3u8&3boxtgvA1Zx5_eSNc($ +H[n"!o-3r4~t5? )v'GDxY=NJ4=:jFLQ +n=+t Uw@+T:"{X->aǒ(̗wt! *Bg^|Z!XO2Bٍ'>L xϋ77m(y^"l^4g8tt6FFZJݘ;*__ m㨃 h)Jk|'=i߽AYx {Cɟ:.Ws8GYs3Ďi/){3$#`Yd:Su' XY$Ggu|sؼV0g 'ݟ*]@Nd3=R1zqņ"}f\ȹ;*j= \ uώ!S¥;#_(V/rVF\WӨDoKs)e&<:[sIe ljA-O&e'!~J3J$9;\b*9bR*#)gFD8 +YDpz|A(OwϪ3VzZ+x ׀ݍ=kdD3j2u v zT5ֺ"Vd3ݺ^TVy8(ui\FW.jl*ɌY$3 g+Ũeڼ:u!#Af3a2 4Q@j mwD(u"<}^EH{պ02q(9=O 2ɏcL{z⁘ H1,އuDŽJD(HB~3dqL'"Q/`ufM1`SEQs\#/|x#ҾՏS_RfkzJIQ#Fw%9ҋsQ.RjX=#`"2"9z=y +?\ 3P^ JsxSge_Qw²$6:;n+ +[ah AMw++|?o/;)ГD{sM21zCV5B7@xqQ@s*ݙ)ϪϨ9_=Dʴ`}81 ؚx7Lu0K(щBTa-\iAJ~wюL6bȂt A:f=܃܆R۩}VOGn0qtbt>{9r'ēBT896A‘^C͹P 2 $ۯhKȝj}R`i'̗ P8_74hƴ BA *~z9oGx{S8G4gQp{3u]B4#Dc44ǂuI g_cŻLx{ІhyjǪZX E^aGۓxR٢؝9gNL֎\!X8Nh=dE3|'DZΠo"@ ɠhܭ5EFP8utgH4,z͎3n)Ѽ;XwtR3TLm̧3=ͣF~[aDb.=`" 8Iku~JtP,`ԙ6%8:+0u +//&OXRXg}BE?ނޓ31QS"1:*xqv5Dzŷ~=n'zIZ)~!sJsd:N94^D4TGגd@"fUXf HBaGf8  K('*ͻYb*| u!wQQni:vtj?lk{0UzX/0Ghϫywwϓb*t0HL/{% z)xUc!Tt>A6y>e]S8%B +??:<rZKIn+U}.dUZfU~U +NN"TCǷ D9uu](y^ĴdriOk6I"(",lȪXZ8p7#$T>#IBi9E%6Ys*2@u@[ōTQ_ҐېbMdŒ(=SeLv>Y +v*OaM oFy8tfJ˳DeN7zߤ*,[u!i:Q12p'3ؑN̼U$Anuޙ;v[mw({Tμ5Y7&<*!҉')iKj ;D8`q@{Gt-N4g"N/J7!i}FAS1K*B ؂ ,5;|bqv3ЖױtB@DOM\gsGxQ]aŏ5h?Xe +?(K*1ZF|\\raԬTduOP)9"Um̉8Rpn0v݅xRxRX `ʓAXG, LQXl "I=DXb3D# q+'f_Z9&Z^:x3fޑ.LփH浟FDJ+bB<> Α:͇o~|Sm@# ȁa(ρJ}$cG ys b/ +ߺ{FDe6+^ 觔q2Χә$6; jIeE*y"STR3klb@AId|WQϿ9Vd'CljN@ʛAiRtFa<j^3P&x~8M츹OctpJ!@%Զҋ$?5[|EeSE[)+u RmfF\jt7v09Grdʈ@b$ +=voen l!/wDݔk4U{fQM!y_j`'F69=>kktŘyJumfhaW5M: H+u"Uzbϕr7D3 F[H%nb8XPi ^Mrl_ tEtᮺmycF"cƄVWthjcc՝/֔DeDlPu}aF;~~^ 꾛;kk^{o_xiuԊ?#,鬪LؑZb|6.XS0s`xOiWKw7@# +w—  (z[摃x]'~Y#w$7wZ`Z8jX5]q{9o-D4Kۅ* @?ϼ:ݽ,ȆM1ĎlŅ҇ise_R4#-{ gx6q(}n}5T9F1bG`~{R_(ˬ]J9HLm/3-g8LQuV`) „At_(Ĺ''" LP ;N9iP^B6vc/++&X/ RGԎTWQ$P +Qx9 +jz0fH{#>g*DΈIMSiz=-D쯌y'q`5bS(mbr`ǽۏØ5X +nFlD mF˓g"?T4m#h<@L?AgŗD_ HSHjP@5D+:a#ǺFQ+*CY7 +|Yc \@S +"FOFAdH4N[Һ ΒSx-$|) iWjIOц+{;pS@aeKEKw5ޙ<"Za@>#VӐ:t'PHJs^^`#x&P yVqK|9`2^Bv5/"0tԃdv^2AץUb) ` M.G{5 p35{ACP|YFzH)ZTju:BAZ!~VqGQ6N}u#Y>h8l(ey?#gcYe@fJ~v^ l§ѕ}';ܞ{-J AZ%lݏ?aEi`mp;ʢާ .e;j12B;jFGWQwGbL 񜧧_QqAa 9߰NsR?;~3Pm3D0׸z 37 (H ѫ{UY ^;s E-"dkk4:[N$y[(cxxQubOΞxfAs:Edh~΂&0- +&~ې VdbI)O&_s 3Gc#dib>*sh=&]|>Ռ87▏L)ds'XSG$#,*$VN$#ymhg pL +P_p @MYy,@L"ӂn¶VBW ň+:&&(+J- ~hɞ^La f99휄8r}zr%J+(.Qa6r)kd:M|ċA.W9b23r&̝so==}X^V#rE͹ ϦqW+E' Mj{22 +60"30zy$gVV!)[ +n \Hkh;Zy#$)AvVF6t,LlxR|z`Mp__Q5j/fVW^ʒĬ?6h3ث*!} lpT+~mV s4= W J(q25{HD޳/qA0T;;U-AX֜Q&AJ~ YrWt PiPѣ҂tVϛ-˝"R -k5%g'~9MsXs)1#J +Kd\3P /K%=T]:yM큙0;x*צΨ=Q+> >q )7w>Ne.^{DSwٽozQ)WTy5m`D42vH/?N`/9XU< +&qĀ> B4rI=#D$)Рx%1('fTJ ݏXvQXʁX1{'v`as^X0P"ˈZ4~[X{`+indTpr; 9vt!(Fx *N׫\CiR>,8doF"T[]h=2GFw@wbUv`Sݐg^zp[dݣĂ +4S,v}w?B<+9oAɑ֜^Nei9ʈIŧtk?8'os۽#p +,/hO#o픘QI|fđvp齠I&w{!fqlJF" #,AA}QJC'O|BXz]US<>G +,b̓X ԍ%CİnG3` ^rn +3M. 90dAa%vXK۳*,jĥx_Ќ^ ZFbIa\wd՗54yEFy/HQ" +[X{锡("6V J<-G:ۋZ%ѼGAɨDJUFF^ח/ `bx[₳?jTL0q+A}:Ν|;ص˴JO$.%ΖcUPG3uFrh~RN% oʕ2: + ;OTz umh(_I:;$V,(#ZB%ތ"u4g ŭ:rb"@w0x4SXºo >ݳ덨"A= >ɠz]e  '2c#"L|lʈ7r]åpD?-< j,ܝ@z<߾~F L=k  Jt@#[r 2]s +Lf q}{ϷvZD5o0?!jd}n_F=QAܡt$mt\I(XGnQAuY@vܯ>AP_^%owJ< dhp׵ + "pWD2;6 k!d{E](M~٧8k䵂"20Z2@΅g + 욑uu KG:tSJcULBA%b)oQ ϣPZ"A^}*c=豋 eK^Hxv_1vOtfڏ[9s@o5X +>=qx~O<)4T‘ͣѵ.k.C%0W])o#`*E~dޞ<>g +ەϑG"T.ܦ"uR g$َ9D`W=E> _ w{>m깽m Jƹfcid\gh.;J(xOߑ@pI׏Z+2sᄄD6mDS5(\IG7fDX诤N^( S[*84sLkWukƎ/M8!il] QCPhLk##+.ᵂmROhԈyԈϧJ_x/h0%E{밝贿lq#lT 8D?WȈp\35Qt[^Nb"%A)g+)B )y.}*m,{( J#R)]~ VUϒ T/Gp%jpo M:Uhӕ$'vgr)R#-FBP=M!CPp]RĊsݑsӔ_- d3=;_&J1cg/nXj#/&QB^MeܿC26xmtzxx>Ibt\FgMM,ڬU/ RWk" +deɀ +) e݇ީriw^߇y\K=3![F~PT/㩅'F!Av>(`DRꦮ&ˀRd|2p 6=g),UOrveFb~).G=怼7QtShZjM}`G8OEwed|14h^J7؏g_-$sFnh\9ءp]b߼h@ȪjYB ;J;KbR|3A +I|GػB$ EhqW˂C2W +6!8ӪS^֋O _ fYjj5WSu#5P WϫN(R F+K@webJD +Ẁ +,O-IIiiϐ0>IѴ+ZTPy:$AA3*<;t!@>%W@~XakfHIΰ=O9^Юlȗ~<@YTMIgsL?RM2J2kxK@KY 5Y>@pwG̉>=D1"xŮslC5 2*VQϾ -a܍L*YujH-U\`@~J;[P Ci\jM_rΨO}i~j_GX ­1ݑ>Hs=T> 𮒊ǫ A:p:p;#Z"/RVrdHzri_B'״"0<*<5FHK>Wǰ>dZt_#YYp[\JѾspF6412{ UP㸒ɿoijfDu=G:*<ä0Q$0,P 6w}l9+I,GJr9 ើH'& +ݣ7pw|`\o3Ƽa9-Rz,s蒤7> ]#⛷Q=@Quխ8o,s8͙f~¶eVp˶WC !ID}T7>_a =PK;IeC@fS|M~sGK_Qtm_#?txV;tv"wQ 7jWIRwGIכp^^eY\ zF h|/i ,9 OFT\R\ )p5YVo<{!UǷǬlsŶDQ`#$ ѯN݂"ьC_%W'XKbbcOpi8hboݩM5cV@Glqj="ipŮ~.Vu&VOa5tfڡ:J5Ĭ[4C=/edB{%Ō@ $DyT#Q@:|&WeT+#zˇtҾ|{v~= 7;@DR;?D_~Oa͖ +Q붕HCܯ#N@~XZ 9q2K%LuȢB4 C;jx# +R< _t&,g)Q)iɓ:KV=WAG(}Ffr5XqHv9P?V@ɧ3^ tt>Ѳ6ab +W̸-ׂu jpW\g+2T Y82hŗ㪺~|i9ʵLuDMk04>N!ٞG +{1#E} h +PZAnNUj-QA]uxT 앬8|*gSeY(HdNϟA}W4<(W#S.g>|}'&6 ¤ȆxW[;xwh;[=QþSKPd5zcghժQ4fHєŝmE6͈W>n@+˺hB!}ԙ'~Fq/%({p yhj _.EP6Giu4|9JQ=Iq[~Lq.ydhF̪΂=.L⥅j)vT2 @,uƒPo!#R<b&9:b3ځx9$Ui7YNS@tZgTi9JzTlr/bbhJ`͊|V~TDU9bH_)v{aj rI X}Q}q{>xH$EH q{Fey#$!H洵\Ba[7kd(A2fk%1%Vm母 PoI/R}(ϖbr{" +¹~ažEʑD1 Dqe+c~|H@GgK,-!*:ܠ)GL,gp(3+in2>CuANV#eb]C1\֊Hr2 g4ҕ;/$$ao3rH@Mr2p@E#zJ {.غ +o~Rqs+Œ蹊i!aH{W^Ġ "%Rp>=`*}l="#L38ސGcGm̯*7*7f"*5B26K~{G"bƇX2I}ТۨԔؚE&Ō|0]zwY#)~|j/]t} T*ނ0d¨,}ݟZ|L~FJ'j%8sF'G~juϷz +Ja%I,CRMz$BA"Js<ͼ723xo^p'j@Y/U^ !#>!bJ^ +zs6l^rڪ_ofD ۻ:F~WJE3~g"{yN.qK$yoڄWrFݱ\[C;:[W l'Gx*A*6[:v^N(~K ijU.WC2xySA9jt_Xx# ,n=ȥY>_.EFNU +*L}Qo +x@r(%if4#v;p U-ir&LK R Ji7M;_ >4A% q(G / Jԏu3v4 +zؠ>L'nb'p43*! vW}C̥¢Ƴ>=fz'D8d +AcZ|!NŻi,x R)`Lʢv wީ)Bu`JYd xÿ< 0atj(*JȢ] [S qKQOHz,a?~ + +`W[(2%#Xe1+ TPgxyvJ{M'gP:B8+?RH~F2dXQ<FܕxVm10n.bψg ־{mNܾG4 6jR-,  r UG(=0PF̍wl̽0h٫/ňە~Qi xҏZO/;m{;⪠ o´bS'fh?Ax#Dm.u8ϝK +DYvL/\yKVtd餘I;NKbYX YVgbx|DJB!4Ę!t諜Azj<6`;C,zڡJ(Fԙ+^'he +ьY0c;kݹ2;IХP|>4= QƊkF'};n]3d[h<;ȅ| "zţQelBf]ȫ?6~NYi+ouY4[UtZ>Dr.&95g6MXP(kJw{6v3S +ix]EOkU rL"O8ȹhlOyP~Sɞ9C.0e3?$79f8#w)-vL]^DYzSa),u)L3ц9ӷ.F^9"qdS e2J[ph /S$ylM j7 8*ID/r+JFAr![hP"\vTBYatɣtɈVj!` "BRn'xYhr$OX-A"!~RK܏.ٸ{D+X37^Gu=BQ0@PDdpFS8=@Hu=,"B!2Bqgqt1K!a`SȐuRP j{`XO!:L`HHclsg="Fuiy%7e A$S24ϪW.mN]E\[Aɻ1515u]D,SS,DV{\Ifj +լӨrkd,L#g rIZȾFH+?Ԕ .MiG7f$Zr!PX0t@3/]Iw/%.L1= +Q (3v+~ +k 7SݳUuUH)~h-@HaBЇUX87:J!bX^C7E?J eսz)8=#TN S 2gA?hx`F){G!q'>g)/+0ڽF8*OSsTQ +M? ciYA0E!qDd$}D_G"^l>݈D|CvH ;EN?".Vk `۾˺[ohh]'ؓW< +9qA['Vy 8P|LvEyWt߾maV@ k`>AZŇ) עs3B )aR'[ 9&)R$16DɻxЯW2s4#Wn B=/es-lkP0: fVnD(I!0r"pn~_\ j™92E(sE(I"zƖ,5'ݖ` a0 +~Exk0Z;suUƫtV|.n 9Rw69PE^`go-)Axb2! 'WS]fy]qMB5 H#˽49I9Glj>~.\80zl!a3>Lџjުã V溨 !u4jSqQfh)|FqF,z=^ܐ˥({EV3B\q@b)B^?o+<fN/&|3GUa{YH `F@=0Wjgj!m䯋y$miMbe3_Qx)A+ A:_a( m3~bzzN+=Kw;.%4~ 4\ܴ`ft~~};+o9!GDˡigCywJb9?Q]aķ!(d^U"S3>J2_X0)~+QgObG CKɞ)ٴ>ln=I!pUrfRL'-Ix$@F^SxI%tǾFY}eV_|*̕24?H*'yD(qsuvR(%EF -vL'J8l];BEFc13o!Wɵdy(@z,l~]efټ\l5_0Hed5z/A/ʣ:J0(=/}Uv:*W%@K(1͛8DK{9{D]w +fu") GGMA2S~hTP6[/?U;Wsǿ_?w?_O埳/nO}~V7;pIT7aՔ?=Q i} +]}&god~}Ui^uwC~>|{1*uٓ r'mE_IU2MG{wr3:K}#E50gR(|8("XEPp耳aQ!MzQV\WFލW +W^.x+\^M=~QXd>1Ehs"~DW)BaV~ 1pw(gmPgܾ];/ $"p8m4_"9<ʇ y3KI˾4.DN+i_b5\rC'(y&)Op9D=+.YܨX}ˆ+-< _Y$߶61@a 8^=J?X,I,e(HF!VaF)N}Ա:6 zB8 JXhXqrh664 ! Ӕ ^{9#שQS^bLn{_1FIgfOHa7A=d9i)':tuW=}ن/ HHj$Kp.Į@Q#>0h_Q K/ %8ƨ xЛdD%ڥ*A}[TlSxxXRdvyg_Oq ET +uwIT~SQx?`](z?rJQ.G9QbqFoXQz,HGt.U2gܳkoȨs@6>J}೷01sMt& +sSz#G9R,V3c!0Z8l"jע7 +IFm;X=U>C^u",6^/ӽ1X6^Q4\Ռ]JXXv^5^6`ɜׄ/"HyyT>Y~<=ÑI3Ʋ$?X1v\Cq#Ab# ;=v( -QnA͂S2W@lXnR!bf/( ݡv%a'A9P&}5¬r.1^1}ϢgSQB! , (LWH6RƏ.5GPxQBc3D%aTG[E)qGIU)½+l$]nNp9I ?w7}1243VICy[|>\dyhz{YD-  jvh l4 jq%.9#.CT[Þm(x>ν $t|ƈ45Yil-,JrP+fВ;kNN 6طGQwO}S@hX AhrYā)X20-aPxx3YעZ~}}Ar\ucڦ`55)2Bvh"<6hQ)Q>.$ar"1=SmqPp&W " 7|w7J|/wf^٘8n ְҎgfw!y]KqDTgAQ^ &oG*|^bh@kf]* A gZT6.-5Hm%ՐGaRbPpDU>DcJ +|mֲFP*qIMz[ƍlhi k/xWy7E\|\q=>f^!Oc^15|FܝΦ gUG/9m?ǁ H"*t!J S{n Zv KT}|~+EҘOj$K Lt29-Ĥb()6,p;FǑ*3Glj,bb?~l'MЇ P 4~P*8rzx8*U~9 +bla*驸}^n+̵bU_{j" IJ3) rW(AAifIç"2>vʯ*g}"Xfޯg(y~nQO^ȗĮjVXL:וo8P95sQH")tqFlA`4lZаU{-XVIDPZƙ0BA +a%%ت"/NvH0 &8pd={ *K +=CsQmRߞ=~hOn#&~Kþ)ATjEp|80"s/ ȑE4@iCOF/D( W^A@?]Ow1ڱEhn$Ri."I\龢Q1^%Y ۂ\bzd0~i->6PC`t6cJoG V,)>䳻ގ8%¸?$SN@{QRQxskH/sNc1U +U4aif'A9t +0/wg竌z*C'aNba$ndj3Qi k(']4RY{NzQ]WΒf"i)1-ˉ2?fK1BD>2dxO:;ŅIUH|G̢^J7MU%:h١y-~KB>Z +Bqo0nd!>·F +x(n x{JI3MC><6A Uԃ`A3=_]o ~0'k*HV6 wl…㼰iW8B5uYB -j1)c6*0Ib/a]2d5+4ﻘYuNWuGY썒W؝DEЋҕe†4{Zm8W?j a!M@i +G ~,t?By%+xC>*79_=cgf\ pԎ (lx=-T 񼝒ـ$,Gh-62R{ovv:=KRɯxg|PGZWBCkb>S!n8J#V+  id~  N:^'ed +-;3cz]xYo|!E d{P^:,گp¤ }8h Ge+Kc2089=Lnܣ P}߬ZW-sOpS lDF^F-hF "=aYON;`'Fyri>ְ9RߎQ`2Gr.bPZ*+!ԙFz%;«WY6eP__ϙ|ʬWN5KVJ[^n[fW+m| +0@=v{?xڣ|~EXh/fԿ:q/GL'_jso⍵ߙkFy%dŒ&!Wӯ?*QS44ǵw;`N9 1:xo B+(j, P\.6;JT*EZ~Ė2@ 8u~6#PS:xo/H+8\~14Y*!EvBIiP&F[is>x=J{*٫{Bj]0 >*M(#ē'b6e٩ϑ*#WmcRX ?|ҁn57F̣F|>QD̕ NSI{ +9cO"@m>(pQ32*K2x {ۡF4uD^6QwPgyW#[FTMt-n_t3:>==b?yEYORv{T.g+ڙqV=6U]&'eEkELmzy?Ҍ"ZjRR|7OvDqO5v؎o!$j/G_ 8+f#qGZZ6~P_E~8%Y8i_Ι+jc@k{q9lqfsMOK.BOhR[Gqo#m%!Ev[:RJրz_Avآ: ZCAg,gk9WRеw^`N^8?:$KK:iݾn~5r+o# 3AT88uh=:09r6kXRDȽ,{yTC!ϸ~pb"QP,[n-Ml*+K!F;y]TG(Uψ.؀@6X^HMQҟП +=JH2rJЊ=Z2ux̑CfC4c +0}p9Ctț74(ɼ\iJĶRxLwrͬJSI$Pw^QMQR{}LN;aLUJެ <ƌrr4bōFԞ845R=Vz&gn-TZZdJFQ :]b,fD0f}U+Px|Ww)9R 0OyFm\vOaV]֢YϞ~ +vg#2J`Ӣ gE R$;Trxz;VbcV7OXȽlPx-0M]M'(urG e "='1l?3jz2u ^̚>1#I nPr]=njb6S^ɲ@`-R- C[O#z S&13gb@S򈅾nE8壭(იZU,Yi4MU{"ATjeo#iļB(UsQFO{+ٌ(#n:י؋Q'e}?3f+ 4AHդ51ܓy$& X~TE(YJQ<:A)qG Ghiٍ1ÿy7D+3Ȝz i.;%,0`[\F@lBWơؓ5u& <Qip~oMIEsc{_%NwR( `wPUv+yhD¥gV48YV6atPQ D'Ϫ("~+Dg?TX\ND~e,6G`Z/e8;LE}\ϖC&R'Y7b5m%DgV;2UqрVu7j7WY Gq WdB-zzS @k^:{5=ȢZ:s' 4KH&mGE&"d ܺ:#twa=ӫ΁zW,is[Su}zJ36>=lɎ?-tdy<"b'\;H* r('ry)b.TjO0|HP(T8Md;HJ惺 +jB7W)F-W Dޡ_ +s8ө9:/v%m/lJS14Kй=Tblw8Nx;Mb"j-ЉD+'E}~j bw ]Ձ#oNa^oGbCuTeD}{Tz^gЧ}"ӻ$^.@!(&=#%HG)^d+Q E(@WA-!S3^*mMu {F]i we.Fup:"GOEٞ>"G:G7VP/9kYS^3;8o㝂 BVh 9ݮGDCC'ʚ8jnثNq>xj9R; `/[o/F!ԫuǞK&# +x*׫x +_2*w+SW,Je>D0G= jU5iߧBR 0:: T +xa*w׉1{q2ZLڳ26pv0T1Vi㳐jR#T9Uf,v1ozi<2w~bq\n +py{f:SJOm׋#@_/!Y <@G/{帎3]ޡn!u4sf+|!B!KGO"01X_oc~k@hDA%$+W灃x``#$Տ̿~$m#8^ +Xcx +8+EFB-08Ѕ1 Ѯ\ɋh!+JE܂Ze`P^ ^"< zwtJȚ 'F'Nfl# \%P$0nP^ f"^EzA1K`1wT2zi$\T2~J&,lR%g[@Q5rI>ucK9afQdb/a{31DA^.53$Ux,ŀ*[WTBR$ bk*&+Rd@-@w5#+#"(@+e+%cNzH11V[f^X)x`5)Q$qD^` @ .1TFv"N%N"V$ɄAsbNQ#N!BA1/Ӫz&/\+;.P}<{,=d= +Z] +]VuQMHC-Qp B3Hp T +["i1tn8qvtR)AT}d5;L_X4;Kb@8{pW},2^1?s,~4g BВ5xMTjgo΁xy.-.D,q/ַn3~f'l2qgdZ΃ݼN+2pqh^B$M4Wc?"/, _0gϚ_l}I3 _mʼ0g~Y}M`6\I4e/"Gsc] w#!C7.z L˙JD *A$ Up@9%JYM"g{a$daPfg~ % ,R``vbe7{!w`gzgɄܕeGpDq%^p hX]Q0|"U6~H [t ]p6E)o~xPU(B&/Gْ>@AWVA3pySlI1qrA 1  GBWzG?"NGB''iPTq"Lg ǥcߘ91ղ "PG (>Ey%9,FqBO q|pYA +/ 0JIqܔQßEhg.T(vt'L ~^oZB0L}&8y6m82=Пd΅Q(\ŝ P^b`CN/P rX|u4W$r$5N=o(FM!DG[dDpWo$&رp9a<\ADCTV7灠 pTC +(Q 1_婨k8I7LUۅ,8 iᯔQm;Wfh2뮕xZPG)\सT#~1W̆AaFI!w?KƆCJb9b. )C~̚{iPTUOx. +ٛofArX[γ]NGg_)7'a礼nWJDaXNL4y)>SЮLE"jU4#€ZlLlpp׎ˉ"(W9ev$liR +(DNοg ]1[Zn8'|@ gE|lAoApʎrCi<`y\ ", 䂇\1c8:K[ba+{"Ţ!XrC=PX0_53Z`3M٤:XXwG$AA0D)$\Kz9UE4Q05tٸAKÑVoap 8e41Hlm eK|Tzr)أg|YU)^J2`9x#ļr9sz8)2V @,%7|B]eJꓪ&bFAt_XTC^7t5gub %b57d=FV6G ENRKITU,1}HVg7pV\Af)Bqa)3*)> +_Sb jŋ\,*5]yJːS N ii;"Y6,\ tX.Q tHjiwUGiNpPF{^q`T7KSrG@/4u iN`/Eؤ.MI6IRf L9CPp6h%S<>^K}Ex.(&¥PWJ QĎ W -5xH +! +Q xs Sd#b%V욒n)Q6 eiZ#'Vt6C$)QR +~ 'TE[UᔸBٜ_y:'oA_N\]/^Wg/Ջ/8~0^}}8gWӁ1sd.??|/2vf|;?8Yǧ/_Ϯs#9;>zmr\zkxc>Փ=-l\m@j$EYYiȯԕvBjݴ/:,rӯdwD7T2^p"jmu/VmewMfS4[5x w}r +-WWf=%*}߷@t xnkpLnՆ`sMўͦ0@Bp +2 x^=\aJ 8Q=G3j8jӤ& +%Xcƍ0ůMN.cLqͰQ+f=*+́|ژF6apfIJ*#P3ˈBt+5Nx{6_E} m?ի+پC$6OXz1 ۠j0;fK3W28RSo1.Q֟YjovWPrp&9!DGm䚫#~L986isF n} 䣣 Efk=]<`9fN~P[΂}+d4֩A](Ŗ?Q&3뇊k K +fț\q%7rub#T:o9d7gVNl1*[G0a0 KctíXXA^c][CѮ'۠75CYq'dɐz0NW}VX،,6֍Ċ(b O}+cpg FbJ+Qa4Hޗ6i]þa( +@iM ih#uH5$xȰ=ps /}n<8$}z[΃h`iP c}i˭#" !efXFd)6j_N+g?^:BCn8(iKm\eJ7&={= z6Z`Ilg8u ގ.w,6؋zcrF74BGNѠo#{ 7jߔ5e.Fȹp$Vcz԰u^- gUg`4V?WBѮT&72IolR30W#5Nx=ί8oJSs] +`UyP] +D`cf4բZޜ&eV ~+ʕH;qXn8mD@Z.BvF}I|2ňP헺2R6l%fIdHpnp n,%Qdj}9U7"e6P?(hwU&3RZXs'x֊[/H/QV7 NJ3nz)RZ7/{Νʝ2m'k撝d2B} FVtZȭ0lc!G[ƹ4,\"qm 9hx +pdRNu.)wp6(<\ѥ7ΠdQL(&fMb6F\7:H8۰{P Qt-l5r㚪b!\ˍq6aN-C*Ƙк(7?Uy 9C k4xr@q]~3 'PoO?4C1LpN6Ld?3(y\֖MP4%]pJ,2Qg5bt9cWP;&gDKIc1t  O \E(ЗlPVh 3.) +ͤj D5vk8nV3OO sNY/#k3吒rt<۽]1Y3Do(m#l,&bM5oPT 1`{DL>qk64H sv>em ::godoi6MhU,0 +qͪLDK P3 +EɩUmŸANٕ9izͼ-W#aI hk풚ЌF0'vb84Pі<[9/0( .hX5ҘaYÍŘl{:ic4F_ZHch#~UW90*?FqmfDZL VN|e/g K!Z[UbXRh'ΐ:&l6sl6 0,U5+"tjJ3s<4"I6"c4VD!ɸBc]a ƛgV8ZmpSGi9*h$KX:4gvjV!OH\mZ6sWiS'7 %kCU2\ȘeR_"67)W USNQ5X.nrSk#R&܈icPЫ1Sr@c-VɈ!TܿbVDIRDUqrnM9V +tK&ccb>[;%w--RjKnq录CiEjO&=Y0``Ү<ٺZG%()Hdp3Y2o5&ht-ZU9郶 BZ_"DFYU"VSk(%NkU !MꥯaWOm5BTMDUPeሳ + Sdv +|%;38N18ڸќj.tV&_PV!)VF7be)|f;Y:Ma#]/,+?ئ]_uFu4-&.v%*7":zyfgX’0Th.&TPw D9h8 +qff5})@3p D_GY߮pHp4=URhCKghhf3rȳXlUKԙ!V3#6w&]>stream +mW 磍\L{XԵ +H6;Ŀ:k!ylV_bhQ8z`fU4KjW罡3g'娢PS;uE:UZ{$6 kP$H6F[͵s4}Q`@LE!8s4g  +G臭c=Hw-d)g[I~Q F"]=vHU!aNZ+sQۖ<P|8NǥGGo"h1Ĥ](1$FX<=GF}c3!Ee2D%+Nf]k0&/`O^A#>I`*m(%*Aw1ݷQj)=*cڄp~ݔh )Л1 b֠W+`f9ndjх-X_] +ɛY$dj +YT.#j] Y]sbw_A0;PFGDdr}Lv FTP0'[9cW1Td-u9E؍C3lT "g6|-b/q+;ۍja7@$? ;A2Ȫ}dfo"3 U`A4ؠa$ż粱6IQIƛ]~hU"m +TЫS&tķR?jvF~!]P|*5%so4z7e 0C^$D6 \i@3_@ eq` `K\MXsgHc I#bZ ˿,>QuXb$r)]iFX.y`Mp]EaQԏơ  -A[ٰ:/d6S8dk 8B_ W@ bpF'\'BIm槦}(}=M$fqAC_6D}k0|3h&Q]K=Zw(ucrWUS(Ymc`_xU.9"-p39 \&șVv]GM glkىD$6aGצmfѐlswO֠ `q9r[{y6@͐mUelVa6!ӐE4Z6R3Q˸Q5D6&Q$ q]|6ؔi$TA~4KA2{[_see" GQ$k W>@ay4-_R_AclA.jP3_hC1u0*d+׆`:IǾU<ùN=0j7fQulXz̾5GjR׽$tel,X#JRC3$Dtb` #] + P4yU;"v:Lb eAl.WMJǽ+f]`'@lصʅX2%TC]~&l{Fm4)l,6U`.~{t7I&d8N?mHv +X`6LeRZb z49D oc { + %2R B G5"kǥ)JK_T$Gj3NѼIġ;G᚞-Pbw^[FGG0u0eBl:(ڒg]zokk,.;/{#'owT% -^b\//Ī{cALej +8H귤((1E_=5}@V'GoFIe8IJq#_ɎF 17]!pĂ\fkCOe *J߮KP8-3h= LA;-*ho՛ښrTd)]FˮO7*8k8)'*9p9.;py+n7 zFc\㺿KJٶVU'!)SEqnazF?38du=t G{$vq[F; gl=/:$h }jV=z& edq@y,:E6W33)1]R{K.G uxtAϣy$$fl~Z[[1 +,L/,$I-rXL&6dDwAěqm=mIG84ZTPZDu0c$u-(j%gsi(6bͣ; rbGmɾKw'33{I֠V8HX$={s=AmHXb'n ,1I`FI_#r .b{DpVWFB,I/9xs`l|HS.Ex1v/(Jl560*V`i8\O]4 (8stDIҘ4kۏhhx,?+X't[CQhk, aHM4d4q pR^MnUƞ*tyF!helֵZxK`6N^g(ުO[ 'ؐ(ekј'ŀ$\xcQ,ZFjiTD]9fLxl`'#:4w.UJS,+P|^O)gNs|F(b6.qgS)ʨcVv=Ƥ,.n6nS%HS׉8 +z$)"'A:N2d` Z8JD2nꩋ9M$;E)HΓp+yI2qYsk쁊e=aClToBvu4rvcXkw^LiTGզd`%r}c" qQ`aN_v!)IL4INMoN&YV#()sF7SQsihVsJi͵4 (n{)J8 X|G]ROQ85L'kfQ. Y"2&@#(нM K&[jȖX^w%vZZTQ:.iF]P.l9ip<ɕؓ'bQ)0Sk _ER^.p*<&JҺoL>У&bBj,$-,f+=ON &s^icgУV'nkRj K==GT($R𰺣DO _;t:mM\o ,l-MvzU:w#Q&=8 I)HߒkO4 ШwSzKa] Y0KL0Z;sQs²ͺOkDh$};^A&k Fqchf̗ͩ.h"^IaA5J:ks +IR9 $MiG\!7þ^@5OP mE?&.f _f{; +7k  Y`{P9Vvs3 TgnGQVuh_B=hԕ5sL85r`i\ +6<9[#UY=Ks, +Wlj ,gjl77[K*;FBHUg6`itOsYOXvMbZB*TD}뾨7 +uf.%.Kplp|gpڜ7}(b~䪹}HN17L/mM-ǘ2`b̨QY(`X`EEJtbж2t&e} rYI73P6 2.yQ{Pɜ5jѠˆ0x`1xkM2Afcr}?itJGFCj08k3ɂbZ'?BU]ȶYEm[UBMw_F!%sK_spՑ%M~:*i5j` 4(f:VИ hF7d6yTlS N;W9RY 4NGg.p)[WMF u6tj%h|P2fUЉy$vf3̥{XͥIgOn8]mjK@a{R ,!Zg1vnc,T0` 0Y7aŪ4d(bl;ړ\z6f7 }/#6Mp9rМiG,yGj3E17/M2mUfjs@"FYh˶E[+@S9#&_s-lwiio6-ZpkP= {~ =!m4L ˋ_6|ØrTҝZkwFn4R3W5` +~8~֜BX{Z jng^+$L6/p)r%; 3. O_lK1.ܳU&} F go7AP޷ =>Yg0Iس@w[6& b[vgW\5)M,gAƁlgmփ`M{ff^]hV.[4novSrhemi,UUiRj,kLnoz,BNOn̗#d CɥZ0Xs{:@f~eJOCKO^=qyb^m}b'+$q3fZ𢕷 fҖir`uN,dDgKxcؽؓXY,{C:0 Ճ/њ+c ֦^vZzV>KZ{Y46/JѨr.*=Gb_7>ecr; \cwmL.ۊA}]+Z%}\jh g2 *}n'`amn2ޘs:isFr\Ǡ3 {yFtv@fh67G51*z9 &+"u9ҹQ}Uxjx,[E&H|YbObƍUC4ĩ6ƊQ { _&ys1hXύfqؚȶ-ty9PIܘE ۡ1I<-]SCQ6yrcY˨y5;#Z6-*F5/գnw_%AV5A[sLV1v!%=Iz jZeX;8 Xғ&Q\حAkwY I+3ի oJ[S_>/;V~תhR]uF˅7wÍ:u2a_Tdp9bըp7Z:TsZp,H, %yHxupo?*hźԂ܀s3 +;q.?MA+$U!F1K߹wt`@6V ;Í"SCKHhPsYfD/BxXB-t$tl7EwE=B/Lm[1ve$mǀ:!ܶ_ku9Q@2(!TI~HLu.H H`i ء ` n)/j]uZ꽳{!ˊj>W0e$uD|Zv//2",MK#;Rl.RAZ%Y@;se ԥF?̲b$'ኞZˊYvp#0/{ʊN'wΜ/'vүӤE;adniav>?e?~~|xu|~vp|:o'gM?|O.gxgj|~y3dz>^8urpjWW^g^y0=8q.Ѓ&> |߭_q_JJV+ګޏzE?Z{Iטc*/O/ݎa :|Lhe-pɛkcw|2s&:,_Ϳ<<>8{~'L3~?V;Zx}B֧]Gxzu96koOk >Č|}(~phKk/Ãj_wC,`f׆?:듃+ׇ~}Wޜa㋛$PMI_{W!>kyʎtSXoΏϮ#wzc:=V6}q9o+xy}B))<X{?''d=MVӳ"R[V|4؊wE[ k#Vm6Ǘ[p+>sʀ[p+>rg/{hsxqpt|>c Eo}u~k[&~˿em߶mG- PܜunF ena/._/m~|l~>6=lt-]>}*i{~~2l3TﳒG0=Vpfl[\Pk͵>"mn-+A|⛇65c#MOgtv4lBu^SCZԦܩ?7J;9Cg 9q罧~ ܯ0;p#Xf|kǯ^]_]c_.(S}yDh=b־?>kge~ ғ,:8mrO=mm];W Z֓<[Ah6 U?uZIZj79W/5"Vi<VƵ!5dVnw}цT>:>9X?& EO!mJ"w)n[v=9'wE@]_޳ gͦ\M}k;mZ>^/f;ezreF69D|xtR=\lHͽC 6>8;|7?G#/1]#G_998Ϯ:xy4iLM2[-n3|0N|meQc'bkͦptzM*2H{ܞǎ'\v=?D{.Owa^pM9s.3z d}?][ғ%GN:_I}5\/{|xx{Tuv~>8<>hz&|{|WHEG&SfxVm汊BͦDn3mV(% +V1?|r3菝6mg}Z Ǚ{ףw9|r3Omg3[Ǚrq[Ǚğ=Pc'b[ǙDŽ38st( (7h=ҶI06,<>L4B,{ȵ7mo73?1w㳣;0Oe7:yMOzm]?/C)'oz7ϭb~z~zzܧ~D3)L~J֮ƕ"8ۇR/em^O㴟>J[[K.=a!.$n +)R'~.onuStk-qo5@o [5@cO7A5@x40A{[oekto6|OwJ΃/] +g<]{7_{FW/{gϝܗ{xbm,}񊄴K8ާF߫>'~06MF|lp[=Vý]NjSb>ӿ̯>}z~qpv}{hb]VL+fk/M `ELW^b.a'yҺ7C"ۘGX?Ϳ轕/>櫦oo}U/n97VUl9[eVUl9@ejvXUI6Ֆ>&KF}rI8L78i^ZV_Ӏv顧`T^lvڅn~Y߁a/dr +t1?׍LͧP7P7$VloŸDĸW̥߭yBg%3tqX{%?T{?+z=cU|X o ɍm> *@ +d*@~CUl IrEӶbwu|\rOKk;[]~}xu{,m{M/Cx6Gik.о:\[)s{gMYǜ.Ogi/gi}s0݃oS0uy0Ӱv}m6m6٤>>9ދ.닗'7Vٸ!x16D1j[EV;E/~>x9?>E[mxrhXo1v<čWke޻1[)p+>+5rlV|4ʁwE[9 ȁkg<9pmT}ٶV +[AnxQєg\I4?G郝?1<ӗ';^}3>??;?_ٰ[f_{f?ٿ0;»4{ii3~.eϿ4j9U?}Vk`xtP y4xN>%/;{AjzAŽ$8C⋫EZ-˫ Fca9ū /^"ݤw4wH("te*X@'N: 7zjXJO!/)D +,<'WNB隮L0[0qW!_ ?:Vǿ< ~ n+~(UHKt}A=*/:TFyн4C;ѫGo@ry X= fAAeX9xJe_fO'ʿ𛍂o{_庌#"pow!VvWoGjou_"`?1˳.3=X`x3f ֦@22o. Vηl^joaml].TJy,My-7 &!5;8:2Wv"ϻtPoM^|6kCLDn5WǴw{2yoa@J.bKX;d/ዘjmŒfŸ[brr!} ݌ 9X2-]:D:W] *'>v~x~qDfZo~7Aj?~ũ4ROpvc i˨ z$LZNœM |`udDih8`ƐR =Al# =h$23TC ++˝|DDX4,@78υ>4y)/%id0&2ꁨ ]\k DnQOr{\Þ{Wn>RŮR,Xx<~L ] G"#Fd +W0!mO0Rk.P NP苼6 NY%`16Yly<~z<$9LZA>!da"c9ZdNQ Ϫ#8 +DrmI٭yBB?k9ɧ%Нpty +{}I1aER8=ƛgtH yn6@Ǥ-dnlusx}]#Ӻq2-:|B}h$%AxWm:a Y6k՟|g;?@#B&&ޔByՉOeepg0AQNC3FbL' i>hz<aocs69}ofD cK%Cw[Clm4j0}|hCeP4 (7(y9fl(,mqt%ڃ84W E ++4 +P !( 1ppkOXDd̄AWq|P1LOD:ͮU.Tt^ +ULG`2Mp# +P&ذ +;[5$HW IIr]7e&TB3$p\#n5Q鶔3m5 N "#.`%N>\")[cL8X eԉHb"W\ؐ c5*UUdèf_._NϏ/jnp1+`2܃߄|-3s._1`|4]JX8 + +L ēiۈ +Ԝ@Yz76P`SIS[l~ (@[K&d6'4&hXs y('P=gb"P!&D`3`x:8Is@IUENx EBcM&Z` 2]ۻ(tH 7oBDa҇ ĝ3 K! {@ĻA "cF&tDzC"xz(M~y4dڂPk&#$2:in€ PCQBV%3ܱ~#BgK 1;Cˁ9L\拰X>n&:p@)2!N*{4Wb, Ky2Xg209/bWvh'9q=D&P6X|J8lַ +=DBt4gbY]VW\U >l ɧ(Dd®$J`F\oȈ \ PКY'i!POnlaxe<($>[Ee 2~h06+0<((i$W80Ӈ0[#!;wu՛uճ^1w'<O>1ѹ.ү|طד_f> +X_qfN^8{2?#޾采e~4~M(9.xOS'g|ɜ?~IwϿJjOϮjI.Ljqn̄G{ '3t#LFA{5݆ZpZ'/$=Y\ Jt5&{0ܶ26yb>;3Y9D'{jir>{}p|6`K&څ7<\ừß;LJr]]4YAwON':=x6M^u8ُ2ۧ }ūWk'__ECv94͛}CrpqП̉T3o;OxqoY#pE߿eoY:"@ﯴ]Ͼ>Mĸo ksu_1=u|Bk䋿1~ ƵȖ(m{ J(}?8)[R%E[R%EDñ%E R? IMnb߿'_|kxc)gKjٿs0 TnJCٛ{wGMg<8nFVs +a>v{HOÐ*|~8xpZsvgg_ٳYbenQ;#wݠ~45~_KZ")_vYFsTJlLU_lo%-+b*<`6c7ԗ\6,^Ht~_Wٿ{|~D9u$ØM[LŴ+1of߽>8UQX Z JŰ_Rٽ7>;Z.~Z@o/nar7s-~o }kw|cmj ]XtrO!q5YZ}qv4zZɜ.'𛗿>o./t+g1߹پo/ykU`yw[,ѕdZ)5$@I^+D!C^)Y F^ ]`o9?y$\s٨לt{B~Qa HNo/z57DRq"Bibgjͥ]Pu!h= HUpf`j:l]h:-lN\S"P# P־*|㈁z2TtZ;#$1EGf fr ، fXR]Rk5Z]@nAA8CH6 Ds.sFܒk uyã % iCRB*r:̉w %scƴ1fr`2'Ƈx[E\qMD;dh'O ʸ H @_XyCLw <@DzPW%a0j1v~pPC#9=o X !hҹQ&KlJXϦJ&YP(BU~HRF1%2!>p]ΫMvmd@ 0X&aKon&Db7~ @xp2X/Y l l*a +@r"n[:[TvX0 gWsEr`Fʙ|BCijFCgQ#`>#M2"%j4>^m" +H_$=`FϨ*»x ++YY;`eqOM*jK˜ [pr$ #1Ո)疈D΢[㗈B[Ŵ! AbB8SΆVGCub)rWl:1H7(M `V1dyaW,gf@ͦejȥJ_N7$, aAIbF:Xnt%DO0 XفiZ̰V?9Qnao͓T05='Iw*R tn%CĈ甡e~4P CdƆomH+1C +_-9Y1mefY S|D8p謵 fN`\m zR1 +˜Mt+e +XTFڀjE"rbF"Rd]f1ēO8oFz[.2R2'lnq(c?L3-r}˫w1߆d~1ǧ;GXQnwS w(G:8aGoP.2E $]OP2a),]8=WG/?5䍢kKw!CLcF8fdهa7eJu]~]@I*ean'IF'" $(NA$Ӳnyg)7LcUMA+=(~m `[:LT auk]X8N&BWEF@#t7, Zm]Rz/X@yl ㄁uUr јGe"ˠve"К ] 2O֖jIP{΢ ,c5Nck`KؼG@\1g*oy#$B%XiDVF$RvB Q':"#m3\-:+Ϭk9QgL|(1zMuēKqV9f ,),j +3vbiyĻVb9S7 I'@8zfiKYZ-:"C1; t3wD1^v75fOw쀿n?Rkc#%(p1"$v} H;ٚ4$G=MZAs627&~ ښt?1;#iͦt +8TV97?[|,OmE8ysЛUKP#yMgYo Z7gް5kf?<H`YFt%ERxvjʤ+O3e.Lg/J DY^z{3# =gޮǣYa4MNGkŕ< cBw0(2Jkxl  IߣaU-ۖD>[Z% +tl,%V8jar>'#,Pv77HC㒖}u-3U~f؛q{y+B WZr_Q%3 ?2PjH,4iNs2aNGQA|6ں":Fa;Y"Ve/f"ΐ>6-f$紜S*1I2kNfQs= )=f:"-.p{jk~3LꆜfrҸbSl"MSIfz4SlNM0 M"G@_РQP]G$W]?On h;%ui=hf8v@5x]@xZ⍰)w0F,x 3Z|Нu=JbJɹ}Ud +a 뙶zMzY=7aVxҝv'tFt'c\ғT7t| #ֶ-e3@-uفV5'K1fDn]uAEUUR71x,dtauwPmΠ qqJJAӜUŨ*|0)%SIc F2\uTBH@Q{gZD'ivZ 1qyz͚`Jy6*e?SuSF -ht! l`%Ű/.lҠh7-r1ȳF{)x*=w(UFԈUdZ{ 1ze4)!-"xAiwcerhm1!'q(4ɴN3L>>V xe+ٺ7Q-4d(W bџ^ǘBXh6ltqUBA/\i $VԬ[1}צ/l\As/4JE1ނ0Fhbqlzn0M~b̴J7ETЈ<&#nU 1 m&c$/2iO)f % Gu?XŒ޸#wƆc R-j>ѼE[Ms0뤋vG5W. ~CV8-ՃK /%6seN/,f1ƗDX3.3#t6uD;w\1"JP0x"5s wwfݼ3pA;# O?,8v$Ø=FLMê=o3 4 AqKvh8|L\ܱm5wf4Gʁ$c'-iNFkäzSg4ҡ46jV FmGӘ]H!"Jd|ҎNctbtѬf^ڜZP]M)=£cD70QQbKD;{9mhCڶПŶW$Z'%CmP͛jUk"j67HT"6՜L]F9TU69Iʎl(^%KqNFM4)4CN|jL87쑃lg :fOIeY̊֝9t&=qn`2uOVضCɨjJ7¢h24v=s3Y3БIChمQ ZEeeL.J Jg0|%v?jnƢC0S[#^a%3Y׭nj21_ԌKn5$ƎY#>IbLIPScNJB4 s˄0st>&3vFLj$Wғ% +Ҿ9lGA!4)v\*!!|d ky%פi8}6l%Z=sfD^jc̽lcmwceDoD6qM]o[If4ݐ_$dt&'jbȰ^wTgDe:BoVٺDdj["ZIv6Gv5VI :ϕ["?@ H7DUo5K74~ǶE5A9hw ???w0}v^BbWVWɡ63y 0oOzc{k OGm:΍pacdU^q-tWr[ec5:bQd:sOޏ~P`"@tdrpOΙ)KO$/i8W倛 +ͱ6뺘6q^ Ѯ zrZܑ;r:_?b1hQMBRe$s47Fc7YPLiGfvPk5%kj*]:ee˚8]cLZ? ]A7)J*ܜ:oΛ`ۉ8b%^.J4!d~Ukny%_0"_)?Asؙ17G;8zѳ[јęT.תXBMCVQ5$T0Kj-@}6Zǻ}^ Dm*윁i8  O@ViFzB1 gW=.]!MӏϾ]IRjt+W AmV&]/se+7QMy9}u39Wnj1Lь,ŋ >B SyUGtt#__axjyq[xz&NK'9[U.^فhdzbHlgQ"Ƀ]biݡy9aΣzOH_5c]ZwٛJ;Jןɼ-~F)q;BtZjGJP{^<~Q%irnbBNTծ +_O^nOYl)LzߟZq2gމ[0?q~G|:]oAibQr8΋sxK9_ȋ~x۹]~R"I5*$K^O:ُk?F~-bۗlnOaO67i~V`'v1&KuҽB@4O$Cݗ-ܕ"k43Ea`_i,Į:E̓78H#6jmsS8S>>O"a;9v™Li HP;g/.C4Go oF np#^ͥwP-er,w>5v^vD?+$jfwrsQxؤ*Oèq]z"1`8'ΐOKJ&z. GWw=>N"Efv+ +sg"JЛG WQw{? WA‡''2 O}Pe]@}|q:6R LrKSNfܷtx|`.։dRogjx|ꃓP3<BBq_~ňo^T:v`?ʬh ˓G65] Bgy᪪|<bbCj'{TJQsuox 9%]<9L߾ ϑS& AL0r9| s'-s +S4nqG<+|= O.~xd™{T1Bbz.#ݥtG y낪ZcP\QR`x[+y8_smPp~agpV5J|P:0ot : ̙.{Ex +Btx'!J2i̒?cI>pc +))B~Bs&fp.Ϯo ˑYqhzT8.;.F| =hwWGu[oo ҫ"/hdz7U3%Y!V٧P2&-hA}s{R(2 jKlOnXnFVsS4ve3D0MYKU//!Y*/xQ+OKƟ1zA60Py"CLS@14x*6!}H <UZ4[1AίN4>T#N,IɂWps , χ 4uیH#X <=-KAK:\N^AǩOK}JޯiE{]~ٻ*]fe_ZhdO_f:*@,%2D{hj ?twl@^rb=Q=|X;;?˹|=ſ2)_ XrouoTeZͶ x} H }~-x:y.ڱ:u.g'oN2ߋ\w +9 + juUF4cMz{-%YD=b0u⫼$S5g bvd0Kem%vu萺 #<YڷƟhGzSSޏl+~Ԭ&<助۸Q*)$.|uk! N~"ͤW| {//@o QmҤ]zB`fKvh-A~ʺnڱ_ZG))+vqg_|um2MڈHKD%}frR:ot #._뒓Cd丐F@PZCHYLy>\W1s^Mba-ݝt1]5_1~FI2Tf8aRl@5Z]y'\z`WŽkgT'كj!"2g> B -;W.>Ơ{xܹO2Y*vp`#"{S+t`;=ܜ ?/Ro4x]/-c==cNvݳ4c~ )"w.!ȏ#  "\7FGr +O_I %!d/,G?k1pZ/rWA+.#d(n+fJw"qi#PrbKnJq/mWBa ׸=g2"hdzT}QvgVQw)`ɴIk@LU%~ O *߸}'y&nzwB^<9dT"qV,_H剪FԅVЂ0t%7_ arHz|_) \Eә>oA z| V=F; o"PNSك~(n}hf{^E@'UArlZqM<ݝ :\85(bSG%9LgO_ nM(]q3tncg#P6?ҍr2)H(H"RpԊ'YMP D?>YVxx8ϑKqIUJ}ih0Cj^8OG>DC?xWWr\ﵐtio|ҹѮGc|Xd 4.Ad^g{]usOHL۩~d`@yer}%ۥCiy7.K>5,Tuok&O?Yؙgosi|;ldcw`ˍKx=;?Ib\Ԟo@5eդBw@Z?S +-F>[< hek3Zt_<~PP`N~pq)QMĐTW!ldHsqJ$H y%QC~lh\*ހpk<|N "q]!1w ?  N"w[fzr<엛{_ޫ܎)Lq)Mu o!DN14 +_}8{ xȥY抢_yMt?ҭכ0?f~ޞ2 AT &;&m͝YȨ~ժ`tP-H[ ̰/Bn.͡|&}j;=pROyȦP/ÙĻ>mũx4 +J~';LʃKy5fG\x!&Am<>o݂7d[$[KM3]+܌uR|-kNH^ٵUV{[i4,}n~Qw\}A,ކ9 ~}wS>I5~rA*sQ)^-&ջӯ_ض|Z맭}bvUx;YݫLŧߔݛpOvN?nZ'C碖+˧wVk>)f*Z>]OLR}@~9j6tr?a2󵇭{޽Rյ|zmy`iJCӁ}prz$=-RYmWB997"W@uOIt,巄zIǘ.Fo$w7R*;)ћ#$|ͫ&ی"*sstXfË'?D60HD49C{?պ>|;a+i4Ǣ:u;T$ұ@`d{xD +F6cڱH)Ҏ)w 1m,AQ_Aȋ޿2@4@W-ZK@ + x*Rn/HS)zH=>z!-BEC>EC7ݔ|&"Xxi:jeߢ%Z?wlNysNҖ A\i4|")A㽝tD: )XG$q`@ Tu"<Aq5KUU>SI"# qeo F|[)3;WKE;#)]Сd~5#PɸAG C$NSdžNi6Fɣҁ-_y17k9>O!c9]o;ߎ2cf#DƆXoDS,\-AyܰLR"Uy]tQ)S'tXKz絤W=8nx%eDcN0-)E?9^Km%$g:処bdpmԉԕڞ`ڑb0*ӌ{ۓfAhi5UP=}b"c믒>y CY[QP '90-03PUТ5n^V6sCGny +k)4cT3NvBL^ yR钦_d,i2jY;/xaSF|)T0z|ɅϨ6oifiN&J:م3T]eh'c?>/G7Xl)CX3x«K2h?4g>O/+d5ݳg;z8%<s0oIzY@sCZ_Vb_GaKpr[.8k3k_u fV1ʷ"%<^k/RY7yprE8JE&U(N^vOMV3A?."Y T7P O=>g1tan[z‰!WԸ_3ji?Ѧ} f!)ajU2F\AB`k4E*+ h4535BBFC(믽86A|@Yʼn{|fހݎ"hҡE-!Yȍo.k\zf0,.d=>K50ߒXZ65Yig8\{K($pAXԆwg8Ewelj]!]ǿ0isjvw6]XZû{,[߿5Z1SZV k(pu+FfP[kho y]lq`_7 >,V6O0,}͔hIqvNڐ`J_&mچb]y&UDss%6m&sb;KA80bT]ŕ. Xnϳ޹fCwt!\v0˵%vC=cwkMdHbr.DɷBl7ߊ:#KȢ,8Fj7:|Fn_Kh}źYxr5tZ+XAgZξ Y"%tՂKWiשy}kWר\ξT nξT[v ݂*b: +:96$T]f޶xJ f]27nK 1ʛaF$96T[NW4nXN!kEpS :%Qk|va9Uι!s(/_K+.X*skJ ޞ +fKmnY>ml4&'zMr du3bղ>Jr*vO4ڲdD:!%C5R0,zqŦ:U>YRA/JZo\ĔԾ:tXfkي?:sJcC޽14ŠUxJ:0Guiu|-]ʺ4ͺޕ^?ڱT֬DzEЩ3綎/9qct +щaU 0gwO1}uM9V>NUzJ.wBw1RD^.c*C܂ֱZcʻ?}\zǺθhe:}9d4-m֋G;JNk FsXX|fc@T۫nь`*Ro.H]V6[nR~H}LE*DE*֋_P6Qpm>4uMF4z1_[ pzlӥpd?펞M™KV[)f/WQ +=-p&Y{s @{~]ӗ^U7'B!,r A%\e]$vK0 /Fpcw:/r3bq.n6=[c4rv̚tf9A_MgVKju5Y.|:t:H2kzZtfNYnEd5:b+7VMgVK7SMgVKrotfvUә.n̹L5Y-ikT-vkSZ96]Mg6&֬3rSxj:+rt)N5qM|Ct+Qlj:UәVo `p5jaԃ>zD5Ȇ&̈z㛪sL5Y-}|Q5/Mַx-^RieӯύY.pgY{4 J%NHtt,\@٪S.U.n/L%tjkdRe VL'SCDg۲.[;9W.]U]V5wV\ +\mߟ^t\UnnR1Y;ɇnk7-k8='}Emc[5^vt˟d{;X~aXE kqwӭVXk|L?Srι"pVЛ gD sRb1G8ֵ"&1亼e͊ " b8L|2&7ELWc$bnʘtȝy,cJ\UۆTnMphtW+*bAw'X{wS aq-ĬOe閺{TdmC5GS-ôK]pS^|W|*0\T=;׸B\W=ոJ `Ovz(B>~rW\{|ţupSTƟ.v5c铮JTWMwn.*=lӃE=\кo2Ozl(ãBVήp|*{D +c.Y˱RKVTR |`4HiS#R lsNW4i[;@⻚5VĽ H+;fHɽoɪ҈ڭ5R馬hwyD$aCq[c e;m}%J\@ Gj&aTklxlĊsm -f']f}h,lr̐X$-nru +nJ $tnfqI*oY2[ 'OQCS?ƺoY.-&R<>NY牗hK}ZRU9/^LY-.v6uxCCHe(V-- yVt[1W56]T\'/SP{UnkIz*@D}UD^^okt[+UZtiP^)gqT_Fu.ӌoo!\t#f.kF/L_ʷx?q)ț\ʧ*`xNI$r* oN6q6{\ ~ml({ۧ~t6ɽ~Y!H|{̙lsMg3D\yS%׆y|K%t-!w~, l*#ڝj0+eMς^^?555tb6p}ZxluMwApY*zǷyMgq gwcE[3PkgCJ̮S(^?Ǻ׍gkgZSֽ~j/ιZ^?{vڴn>=9\'>Sϲ =8.{''l^?+~Z w^^ItC{כVOOmc}|mҰo}|nNoXܫU|Ɗ%n+ֿO)Wt{ [^?[>Io\{\Խn^?[o*\\obxͭ~,k뷘Jguٲo2Xd)|ɼhn?~-["\^?UqAý~d{C1z]^?X"}~[1'YvD$+a־O; 86յi~qXAh&G;1a7;,|zU NcěWUs}}5g@c.ۍZ?=8u773ّ TxqpčiIImgq}덅_^6|T9_S U&T`2MOw^4zP*-x.Srw|2}eszQKo\rr$'9}\~bF,B lZT4c#;{ 9t$=CȘ;jCR͕ xbw]4EsjU34Pkft+>N4y:x#ŶӻB4ţp+v V>3@2>f UgݽKUİh7"ÄDNT|o3Q11PDW!D~Aah?X P/sU*\@~Jog (vBaX[8G?|5-L QԷoaGW^cmQT2NS[\gVUcYc=>Ȇ V B}wR`\y8# MawS?3O,3;;4 b?T]x`/9ߔJֵܷ[EÂy~pBa uHҖ@oE$YT bVQa)F’TbBDeu/J-(-i5:ǔ,asHa2H˚1MK,QHiAyRV T-UU<9Q\eWJiBaI͈'pҨFE;N% *V \ڙ+ lU+}܌5߁P%9N%FesKXnۆfVw"Ez%pa'U Xe%nog*T7,N&օh].TTw;7)zbGRHsPzޏ{O4hbgHo;1Z̮o3wTKS|QDr6J6>xПW]P.Ԕ=CFv)^HUW1^62"R8i/1'v nδCd(Q,wp'/S H ^O(_DZ!sƻ"Unm7c%[a-7ZgۋÈi"AφK )=>\ ^ĕѹm1 02~PL:æVеԖxUzϽ~rXp]xl%<\B<D (_s6"cKW}{܍)) F܍ěAL;qʒa >0=oSEn1F,z _n&;b^L7ZDŽ%QSkMfŋt=#()D÷HUR>j5,}MW9jT^$7,ǔjӵ{%(%Lr﨡* b,4jq{2<{@MK1%LX,a-K{.fn~ Su`,heK{}K&hKݽ7x<}* + otx4vaWG:"Mშ@hr"pJ"LQ7*S\@ O<ԖaI[bF[J&2- d"¼YLt8ztJ'fY0FZڐU^$0U9J3_7D^PHyYdq >9i-|sj 0q$d̖ψ,-y|5Drgj&\_rR@zUK+QK荾DȲ"tD.{WI gEy(DQ]9;ޫ%/fZrj%YZh,+K!I27w}@ٸ/fY~aܤ.fN*#YhP:l[u"0g*i@'1d0\Ӓ40ݮTa +qWԲMM._A cu.1[Yj]1j1 #hAyU3Qo;6t\>>I)-̺ p0%ޮyy^o6Ы'%/vèFB:Q=b0iEŕDշ h}òEMƴ5S*E{Wayܢ%҂ "nهEm>:>~g_/or$X KģT/)#}MFmx~/Λz儔DVM<=U +"wTNJ5Q{J~yv"$d0vlѴ]ꀸzHQ7g@_ů/ӮI)S {5$5Y̓<Aghͭc*E6ڞ۸QHؗa  eT vxeLl )`/%'Zʳ=JRL0A>P$ }H*ݧnw%l܀,.j/ա`n!l!Rt|gzٟ,'tٙWF>yw5xӗ~aX=D?Nnx,fRb;A- Ϳ%GWHmLrI$K|On 1\yX6&pRDzmEi& P|*bFK$S 6Fh:VTbL0ÈxNBKxh(j"E[,H +M 1`R)&tKRQZJ'$(86 aP4T_cPBY&MT2dzJ@)0Zq"$@'9MqT@%9O$Rbo0d!%l eI$I$5iyU`_T0M0 a%Guy4JS<c?[4'N +/I{d +%6lGӼ_@@B&bO3Rm`R * D[4 +h lQǧDQ\R#G = +]^tO"I& H0 \:,&(f0Q&c7 `A$(>Ha ЈG% *"p($ $X +偱aj hL2 1>!ab Ic@#QL +0bR9M,a>TtTK@p@T +t"L$`@Ա 1cp,LBN%&0 +f9IM FPQ|I)T^l:<(jm Ib}"0c3F!' á<Ҡ# `1`OSBUh:`i +Oɘ NiP#Г ۠IR`C1G -)UzTXfZ" âvDPKǃ[Ǡ]i@ Z":^ap044 G,A | +5(zA @H= &mﲔ%ΰh%`>8r Β=ea b@OÆMx¢@[HБLЊBˁ4!C M*ǀ2q:UdsN_m]&@RS<_" I  +-,=8AIs*&'M@*/.2A#!E_*_d஀h) &Nɢ +bN&8x6wzu)P)IUJt - T'"*?R4:BS C::,:v`$:Ih e :F"[J:#B;_ĉPBTfx7rpYpnyI&Dk: P93/5=󳅾 GP G,)RNb^$ ?z ,8~8MX@T74@莐DMɨ@ED%b1聳ɂL\; $Ck 60+4xS L!聎G F&zS( '8NF%$(Al}Z'N$P![ȳ(H3`_>!&x/\F +ћ&=@xDX |Zpq@jE&3WBZl>9&a } 0PFA00#.6=ţ[6AtDM \",^9 RoI-T$ 0ih_g&XC@虔 5"acGE x\00;`a~h018uI uiNC}L/ce2@s +pBB + J`A+dPI@YԐCAX3X]Z`ρ4tZ j3L9? 4%L 00[BL"!6C\("pS脁&,y[C8p"G B{K <&#d٠&!t#"B ,"11P $r` +26Y@ &P#M`N a a qId@ Lm"B +8 |]l x9} #yL!π8&P`P +* 2!-=0 +h&)P`pI) ǣ)s K? E!Fb1  p\R8qW)laV)]&# JF58& 8x84$į!8PbF 0( k` $x0IB7'`R+ ^숉Pdh `\JDCrBIf<t/Zt@*a)It3B Qsٍ? GeVVH`[K2H`#ۂn/&71rcDc|ݪMq [*%>^? raB-T=q9: + >#G6!>`RԎzg@MāAz ~i_?t}&Do,u$L;J`֮_BR{le,uP$m%Cr 2(  cW[KF,wV״>.bvC t + !N7R%eJPAn5t%{'B|aTA@mb#E.:n: >")Ó/cNe} \yE$L`K<¹AJoؕ;#ӿ((FRU؅%`Dib=.E;:E5Y(>3i vڋfyT"xx_nз'D!Nkҥ 9۷ӠJd$G;5 T A%W0 .|BH0 x!&Ne:kjta׬AI# D>zS m Ki' --;Z+ZM): jM5'&ݮ-ѤPl5{y_CimV@ DPO e(Y`;Xv(ADr &1мB +Vʉl74١82Tv"p].N\ǴkIN M~['׭`C^{,v!חT#:B*w#5#$a&H~ӭ荶fbȦs )j rFF`Lv%ǁ\C'CcĀFej* %o;Rf+ZY!\@{63=&4Xssn82#'&MmZMDߵjX%L[LLyFKUb݄wPw+!Ckxdk v3[q^qc$cxw(ج8bZCixL!R Pmgs85z hcY:N0 +͇Ó6"#pb 2xI/TR n&v0jUDgH {:Uum,6C%D7d +\x8=") w|\G˞xGQ}6zޞnn~8|_'g{x>>=_u͇; H endstream endobj 6 0 obj [5 0 R] endobj 21 0 obj <> endobj xref 0 22 0000000000 65535 f +0000000016 00000 n +0000000144 00000 n +0000042955 00000 n +0000000000 00000 f +0000046106 00000 n +0000177378 00000 n +0000043006 00000 n +0000043369 00000 n +0000046405 00000 n +0000046292 00000 n +0000045197 00000 n +0000045545 00000 n +0000045593 00000 n +0000046176 00000 n +0000046207 00000 n +0000046478 00000 n +0000046683 00000 n +0000048031 00000 n +0000066198 00000 n +0000131786 00000 n +0000177401 00000 n +trailer <]>> startxref 177595 %%EOF \ No newline at end of file diff --git a/Installer/Source/Assets.xcassets/Love.imageset/heart_light.pdf b/Installer/Source/Assets.xcassets/Love.imageset/heart_light.pdf new file mode 100644 index 0000000..1648eab --- /dev/null +++ b/Installer/Source/Assets.xcassets/Love.imageset/heart_light.pdf @@ -0,0 +1,1033 @@ +%PDF-1.5 % +1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + heart_light + + + Adobe Illustrator CC 22.0 (Macintosh) + 2018-08-31T14:26:07+02:00 + 2018-08-31T14:26:07+02:00 + 2018-08-31T14:26:07+02:00 + + + + 220 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAADcAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A455n80a15l1m51XVrmS4 uLmRpAHcssYY1CIDsqqNgB2xVKcVdiqIGpaiBQXU1P8AjI39cVb/AEnqX/LXN/yMb+uKu/Sepf8A LXN/yMb+uKu/Sepf8tc3/Ixv64q79J6l/wAtc3/Ixv64q79J6l/y1zf8jG/rirv0nqX/AC1zf8jG /rirv0nqX/LXN/yMb+uKu/Sepf8ALXN/yMb+uKu/Sepf8tc3/Ixv64q79J6l/wAtc3/Ixv64q79J 6l/y1zf8jG/rirv0nqX/AC1zf8jG/rirv0nqX/LXN/yMb+uKu/Sepf8ALXN/yMb+uKuGp6kDUXc1 f+Mj/wBcVTnSvzG8/aU4fT/MOoQU34C4kaM9942LIfpGKvVfJH/OVvmWxlitvNlqmrWXR7y3VYbp R/NxHGKSnhRfnir6T8q+b/LvmvSU1TQrxLy0b4X47PG/dJEPxIw8CPfpiqcYq7FXYq7FXYq7FX5w 3MJguJYSeRidk5dK8TSuKqeKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVkPkf z35i8l60mq6JcGN9luLdqmGeMH7Eqdx4HqO2Kvtb8uPzH0Hz3oK6npjencR0S/sHIMtvKR0Pip/Z bv8AOoCrK8VdirsVdirsVfnNqf8Ax0rv/jNJ/wASOKobFXuX5FfkRoXnTQLjX9fuLlLcXDW1pa2z LHy9NVLyO7K9QS3EBadDir1b/oV78qf+We8/6SW/piqlN/zix+VshHAX8NOoS4Br8+aNiql/0Kn+ WH+/dS/6SI/+qWKu/wChU/yw/wB+6l/0kR/9UsVd/wBCp/lh/v3Uv+kiP/qlirv+hU/yw/37qX/S RH/1SxV3/Qqf5Yf791L/AKSI/wDqlirv+hU/yw/37qX/AEkR/wDVLFXf9Cp/lh/v3Uv+kiP/AKpY q7/oVP8ALD/fupf9JEf/AFSxV3/Qqf5Yf791L/pIj/6pYq7/AKFT/LD/AH7qX/SRH/1SxV3/AEKn +WH+/dS/6SI/+qWKr4v+cVvyuQksdQkqKANcKKHxHGNcVY95s/5xK0N9Pll8rancw6gilo7a+KSQ yED7PNEjaOvieXyxV8y3tndWN5PZXcbQ3VrI8NxC32kkjYq6n3DCmKqOKuxV2Kp75K86695O16HW dGm9OeP4ZYmqYpoifiilXup/DqN8VfbX5cfmPoPnvQV1PTG9O4jol/YOQZbeUjofFT+y3f51AVZX irsVdirsVfnNqf8Ax0rv/jNJ/wASOKobFX2R/wA4vf8Akqbf/mMuf+JDFXrWKuxV2KuxV2KuxV2K uxV2KuxV2KuxV2KuxV2KviD/AJyCtYbX84PMUcI4o0lvKR/lTWsUjn6Wc4q88xVEQaffT2txdwwP JbWnA3UqglYxIeKF6dAW2r44qh8Vdiqe+SvOuveTteh1nRpvTnj+GWJqmKaIn4opV7qfw6jfFX21 +XH5j6D570FdT0xvTuI6Jf2DkGW3lI6HxU/st3+dQFWV4q7FXYq/ObU/+Old/wDGaT/iRxVDYq+y P+cXv/JU2/8AzGXP/Ehir1rFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXxL/zkZ/5OXzD/wBG f/UDBirzfFXvP/OJtnaXus+ZrO8hS4tbiwjjngkUMjo0hDKynYgjFUi/PD8j7vyXdvrOjI9x5WuH 92e0djtHIepQnZHPyO9CyryPFXYqnvkrzrr3k7XodZ0ab054/hliapimiJ+KKVe6n8Oo3xV9q/ll +ZuheftCF/YEQ30IC6jpzGskEh+7kjU+Fu/sQRirMMVdir85tT/46V3/AMZpP+JHFUNir7I/5xe/ 8lTb/wDMZc/8SGKvWsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVfE/wDzkajL+cmvlgQHFmVJ 7j6lCKj6RirzXFXv/wDziD/yknmD/mDi/wCTuKvp28s7S9tJrO8hS4tbhGjngkUMjowoysp2IIxV 8f8A54fkfd+S7t9Z0ZHuPK1w/uz2jsdo5D1KE7I5+R3oWVeR4q7FU68n+b9c8pa7b61o05huoTR0 O8csZPxRSL+0jU/iNwMVfcH5c/mFo3nry7Fq+mn05AfTvbJiDJBMBujU6g9VbuPpGKsoxV+c2p/8 dK7/AOM0n/EjiqGxV9jf84uOrflVCFIJS9uQwHY1U0P0HFXrmKuxV2KuxV2KuxV2KuxV2KuxV2Ku xV2KuxV2Kviv/nJKZZPzg1lQCDFHaI3uTaxP/wAbYq8xxV7X/wA4t+aPL2heZdYGtajb6al1aIsE t3IsMbMklSvNyq1oelcVfSf/ACsz8uP+pr0f/uIWv/VTFVG88/8A5W3tpNZ3nmXRLi1uEaOeCS+t GR0YUZWUvQgjFXyR+b3kfyr5f1b655T1yw1bQ7xj6dtbXkNxcWrHf03VHZ2j/lf6G3oWVefYq7FW d/k1+Y0/kbzjb3sjt+iLwrb6tEKkGEnaQD+aInkPao74q+5fVj9P1ea+lx5epUceNK1r0pTFX50a n/x0rv8A4zSf8SOKobFX19/zin/5LCX/ALaVx/ybixV7JirsVdirsVdirsVdirsVdirsVdirsVdi rsVdir4l/wCcjP8AycvmH/oz/wCoGDFXm+KuxV2KuxV2KuxV2KuxV9Qf8rGuP+hWvr3rH9J+j+gu fevqej168vqvxV8cVfNGp/8AHSu/+M0n/EjiqGxV9ff84p/+Swl/7aVx/wAm4sVeyYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq+Iv+chhcf8rh8wmdeLFrYp7oLSIIf+BGKvOsVdirsVdirsVdir sVdirPv0hP8A8qH+oVHpf4n9XqeX+8FKdaca79OuKsK1P/jpXf8Axmk/4kcVQ2Kvr7/nFP8A8lhL /wBtK4/5NxYq9kxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVj/mL8v/JXmS5S61zRrW/uY14J PKg9TgDULzFGIFdgcVSn/lSf5U/9SzZ/8C3/ADViqun5QfleiBB5X04gdOUCMfpJBOKrv+VR/lh/ 1K+m/wDSNH/TFVFvya/KxpRKfLFhyBBoIqLt/kg8fwxVWP5R/lgR/wAovpv/AEjx/wBMVef/AJk/ 840+UdR0e6vPKlqdL1qFGkht43doJyor6ZRy3At0UqQK9Rir5KIIJBFCNiDirsVZZ9TP/Kp/rvLb 9Pejwp/y58q1xVjmp/8AHSu/+M0n/EjiqGxV9ff84p/+Swl/7aVx/wAm4sVeyYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX56+c4Y4fOGuwxLxjj1C6RFHZVncAYqk+Ks2/ 8or/AODJ/wBiGKsS1P8A46V3/wAZpP8AiRxVDYq+vv8AnFP/AMlhL/20rj/k3Fir2TFXYq7FXYqh 73ULGxj9S7nSFO3I7n5DqfoyGTLGAuRpWN3v5iadGStpbyXBH7TERr9H2m/DNfk7TgPpFseJLJPz G1Ik+nawqO3Lm36iuY57Un0ARxLofzHvQ376zjdfBGZT+PLDHtSXUBPEneneetFuiEmLWkh/379j /gx/GmZeLtDHLn6U8TIUdHUOjBlYVVgagj2OZwNpbwq7FUv1LXtJ00f6VcKsnaJfic/7EdPpyjLq YY/qKLY7dfmPbqSLWzdx2aRgn4KG/XmDPtQfwxRxII/mNqPIUtIePcVav31yn+VJ9wRxIm3/ADI3 AubHbu0b/wDGpH8csj2p3xTxJ7pvm3Q78hEm9GU9IpvgP0HdT9+ZmLW459aPmm05zLS7FXYq7FXY q/P38xIVg/MDzNCpJWLVr5AT1IW5cYqx/FWbf+UV/wDBk/7EMVYlqf8Ax0rv/jNJ/wASOKobFX19 /wA4p/8AksJf+2lcf8m4sVeyYq7FXYqxHzH53S2ZrXTCsk42e4+0inwXsx/DNZqu0OH0w597EyYL c3VzdTGa4kaWVuruanNNOZkbJssUw0/yxrl8oeG2ZYj0kkoi09uW5+jL8ekyT5BaTCfyBr0cRdTD KQK+mjnl/wAMqj8cul2blAvYp4WOyxSwyNFKhSRDRkYUIPuDmCQQaKFuBU00TzHqOkyD0X9S3Jq9 u5+E+NP5T7jMnBqp4jty7lBejaX5h0zUbX145VjKisschCsnzr2983uHUwnGwWYLF/MnniR2a10p uCDZ7odT7J4D3zXartAn0w+aDJhzuzsXclmY1Ziakn3Oaom2LWKuxV2KuxVPtB83ahpjLFKTcWfQ xMd1H+QT0+XTM3T62WPY7xSC9GsL+1v7VLm1fnE/3g9wR2IzfY8kZxscmaIyauxV2KvgH8zP/Jj+ a/8Atsah/wBRUmKsbxVm3/lFf/Bk/wCxDFWJan/x0rv/AIzSf8SOKobFX19/zin/AOSwl/7aVx/y bixV7JirsVYh548yNbodMtGpNIK3Mg6qp/ZHu36s1faGq4fRHn1YyLBra2nup0t7dDJNIaIg6k5p 4QMjQ5sXo/l/ydZacizXIW4vevIiqIfBQf1nN9ptDHHud5MwGQ5nJdirBfzGs4UntLtQBLMGSSnf hTifxpmm7UgARLvYyYbmqYuxV2KuxV2KuxV2KuxV2KuxVN/LevzaRfByS1pJQXEXt/MPcZlaXUnF L+j1SC9TjkSSNZI2DI4DIw3BB3BGdGCCLDNdhV2Kvz8/MGYz+fvMsxHEy6revx605XLmmKpBirNv /KK/+DJ/2IYqxLU/+Old/wDGaT/iRxVDYq+vv+cU/wDyWEv/AG0rj/k3Fir2TFVC+u47OzmupPsQ oXI8aDp9OQyTEYmR6K8eurmW5uJLiZuUsrF3PuTnLTkZEk8y1vQPI+hLaWIv5l/0m6FY6/sxHcU/ 1uubvs/T8MeI8z9zKIZRmxZOxVAXuvaPZEi5u40desYPJ/8AgVqcpyajHDmQi3nfmjXzrF8rxgra wgrAp679WPzpmi1ep8WW3IMSUmzEQ7FXYq7FXYq7FXYq7FXYq7FXYq9B8gaqbiwksZDWS1NY6942 7f7E5vOzc3FHhPRlEsrzZMnYq/Pjzz/ymvmD/tpXn/J98VSTFWbf+UV/8GT/ALEMVYlqf/HSu/8A jNJ/xI4qhsVfX3/OKf8A5LCX/tpXH/JuLFXsmKsc8/XJi0Exg/70SpGfkKv/AMa5gdoyrFXeUSef 6ZafXNRtrXtNIqMR2BO5+7NJihxTEe8sA9iVVVQqiiqKADoAM6oBsbxVgfnLzVcG5k02ycxxRHjc SqaMzd1B7AdDmm12sNmEeQ5sSWH5qmLsVdirsVdirsVdirsVdirsVdirsVdiqe+SboweYYFrRZ1a JvpHIf8ADKMzdBPhyjzSHp+dCzdir8+PPP8AymvmD/tpXn/J98VSTFWbf+UV/wDBk/7EMVYlqf8A x0rv/jNJ/wASOKobFX19/wA4p/8AksJf+2lcf8m4sVeyYqxP8xh/uMtTX/d/T/YHNZ2p9A97GTGf JqhvMtkD0rIfujY5r9CP30fx0QOb1LOjZuxV4rLI0sryOas7FmPuTU5yRNm2tbgV2KuxV2KuxV2K uxV2KuxV2KuxV2KuxVH6A5XXNPI/5aIh97gfxy7TGskfeFD13OobHYq/Pjzz/wApr5g/7aV5/wAn 3xVJMVZt/wCUV/8ABk/7EMVYlqf/AB0rv/jNJ/xI4qhsVfX3/OKf/ksJf+2lcf8AJuLFXsmKsb8/ wmTQQ4/3TMjn5EFP+NswO0o3i9xRJhPlqcQa9Yu2w9UISf8AL+H+OajSSrLE+bAPWs6ZsdiryDWr NrPVru2IoEkbjX+Umqn7jnL54cMyPNrKCylXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FU18rQmXzDYq O0nP/gAW/hmTo43lj71D1fOlbHYq/Pjzz/ymvmD/ALaV5/yffFUkxVm3/lFf/Bk/7EMVYlqf/HSu /wDjNJ/xI4qhsVfX3/OKf/ksJf8AtpXH/JuLFXsmKoDXrM3ujXdsoqzxkoPFl+JfxGU6mHHjI8kF 5Gjsjq6GjKQVPgRuM5gGmD2PTr1L2wgu0+zMgangT1H0HbOpxZOOIl3tiIyxWDfmLpyq9tqCChf9 zKfEgckP3VzT9p4txP4MZMMzUsXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FWV/l5Zepqc12R8NvHxU/ 5Uhp/wARBzZdmY7mZdwTF6Dm8ZuxV+fHnn/lNfMH/bSvP+T74qkmKs2/8or/AODJ/wBiGKsS1P8A 46V3/wAZpP8AiRxVDYq+vv8AnFP/AMlhL/20rj/k3Fir2TFXYq8o80aYdO1meICkMh9WHw4Oa0+g 1Gc1q8XBkI6NZCfeQtejirpVy4UO3K1Y9OR6p9PUZm9nakD0H4MolnObhkwz8xNStmgg05GDTrIJ pAP2QFIAPz5Zqe08ooQ63bGTBs07F2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV6l5Q0s6fosQcUmuP3 0oPUch8I+haZ0eiw8GMd53ZgJ1mWl2Kvz488/wDKa+YP+2lef8n3xVJMVZt/5RX/AMGT/sQxViWp /wDHSu/+M0n/ABI4qhsVfX3/ADin/wCSwl/7aVx/ybixV7JirsVSHzhoR1PTvUhWt3bVeIDqy/tJ 9PbMLXafxIWPqCCHmO4PgRnPMEzXzNr6weiL6UJSg3+L/gvtfjmR+by1XEVtLWZmYsxLMxqWO5JO Y5KtYq7FXYq7FXYq7FXYq7FXYq7FXYq7FU+8n6EdS1ESyr/olsQ8tejN+yn9fbM3Q6fxJ2fpCQHp 2dCzdirsVfnx55/5TXzB/wBtK8/5PviqSYqzb/yiv/gyf9iGKsS1P/jpXf8Axmk/4kcVQ2Kvr7/n FP8A8lhL/wBtK4/5NxYq9kxV2KuxVgfnXywYnfVLNP3TGt1GP2Sf2x7Hvmm1+kr1x5dWJDD81TF2 KuxV2KuxV2KuxV2KuxV2KuxV2KuxVF6Vpd1qd4lrbLVm3Zj0Ve7N7DLcOGWSXCFAeraXpltptlHa W4+BPtMerMerH3OdJhxDHHhDYAi8tV2KuxV+fHnn/lNfMH/bSvP+T74qkmKs2/8AKK/+DJ/2IYqx LU/+Old/8ZpP+JHFUNir6+/5xT/8lhL/ANtK4/5NxYq9kxV2KuxVxAIIIqDsQcVefebfKRsy1/YL W0O8sQ/3X7j/ACf1Zo9bouD1R+n7mBDFM1qHYq7FXYq7FXYq7FXYq7FXYq7FUVpumXmpXS21qnNz ux6Kq92Y9hlmLFLJKorT1DQtCtNItBDCOUrUM0xG7H+AHYZ0Wn08cUaHNmAmWZCXYq7FXYq/Pjzz /wApr5g/7aV5/wAn3xVJMVZt/wCUV/8ABk/7EMVYlqf/AB0rv/jNJ/xI4qhsVfX3/OKf/ksJf+2l cf8AJuLFXsmKuxV2KuxVxAIodwcVYP5o8lFed7paVXdpbUdR4mP/AJp+7NPq9B/FD5fqYkMLIIND sRmpYuxV2KuxV2KuxV2KuxV2Kpnofl++1efjCOECn97cMPhX2HifbMjT6aWU7cu9QHpmk6PZaXbC C1Sld5JDuznxY50GHBHGKizARuXJdirsVdirsVfnx55/5TXzB/20rz/k++KpJirNv/KK/wDgyf8A YhirEtT/AOOld/8AGaT/AIkcVQ2Kvr7/AJxT/wDJYS/9tK4/5NxYq9kxV2KuxV2KuxV2Ksb8x+Tr bUeVzaUgverdkkP+VToffMDVaEZN47S+9BDz27s7mzna3uYzFMnVW/WPEZo5wMTRFFgo5BXYq7FX Yq7FXYqyjy55LuL7hdX4MNp1WPo8g/41X3zY6XQGe8topAegW1tBbQpBbxiOJBRUUUAzdxgIihyZ qmSV2KuxV2KuxV2Kvz488/8AKa+YP+2lef8AJ98VSTFWbf8AlFf/AAZP+xDFWJan/wAdK7/4zSf8 SOKobFX19/zin/5LCX/tpXH/ACbixV7JirsVdirsVdirsVdiqA1fRLDVYPSuk+If3cq7Oh9j/DKc 2njkFSQQ8413y1f6RITIPVtSaJcKNvk38pzQ6jSSxHfcd7EhKcxUOxV2Kqlta3F1OsFvG0srmioo qclCBkaG5Vn/AJd8k29lxudQCz3Q3WPrGh/42ObvS6AQ3lvJkAynNiydirsVdirsVdirsVdir8+P PP8AymvmD/tpXn/J98VSTFWbf+UV/wDBk/7EMVYlqf8Ax0rv/jNJ/wASOKobFX19/wA4p/8AksJf +2lcf8m4sVeyYq7FXYq7FXYq7FXYq7FVskUcsbRyqHjcUZGAII8CDgIBFFWD+YvIrx8rrSQXTq9r 1Yf6h7/Lrmn1XZ9bw+TExYaylSVYUYbEHqDmqYppofl2/wBXlpCvCBTSS4YfCPYeJ9sydPpZZTty 71AekaNoWn6TB6dslZGH7yZt3b6ew9s32DTxxCgzATDL0uxV2KuxV2KuxV2KuxV2Kvz488/8pr5g /wC2lef8n3xVJMVZt/5RX/wZP+xDFWJan/x0rv8A4zSf8SOKobFX2J/zi1EqflYjLWst9cu1fEcV 2+hcVevYq7FXYq7FXYq7FXYq7FXYq7FUp1Lyto2o3K3FxCRKPtsh48/9anX9eY2XSY5myN0UmcEE MESwwoI4kFFRRQAZkRiIihySvwq7FXYq7FXYq7FXYq7FXYq7FX58eef+U18wf9tK8/5PviqSYqzb /wAor/4Mn/YhirEtT/46V3/xmk/4kcVQ2Kvsj/nF7/yVNv8A8xlz/wASGKvWsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir8+fPSsvnfzCrAhhqd4CDsQRcPiqR4qzb/AMor /wCDJ/2IYqxLU/8AjpXf/GaT/iRxVDYq95/Ij89fLPk7y1N5f8wR3Kqty9xa3UCCVOMirVGXkGBD KSKA9e2KvS5v+cpfysjUFGv5STTiltQj3+N1xVR/6Gs/LD/fWpf9I8f/AFVxV3/Q1n5Yf761L/pH j/6q4q7/AKGs/LD/AH1qX/SPH/1VxV3/AENZ+WH++tS/6R4/+quKu/6Gs/LD/fWpf9I8f/VXFXf9 DWflh/vrUv8ApHj/AOquKu/6Gs/LD/fWpf8ASPH/ANVcVd/0NZ+WH++tS/6R4/8Aqrirv+hrPyw/ 31qX/SPH/wBVcVd/0NZ+WH++tS/6R4/+quKu/wChrPyw/wB9al/0jx/9VcVd/wBDWflh/vrUv+ke P/qrirv+hrPyw/31qX/SPH/1VxV3/Q1n5Yf761L/AKR4/wDqrirv+hrPyw/31qX/AEjx/wDVXFXf 9DWflh/vrUv+keP/AKq4q7/oaz8sP99al/0jx/8AVXFXf9DWflh/vrUv+keP/qrirv8Aoaz8sP8A fWpf9I8f/VXFXf8AQ1n5Yf761L/pHj/6q4q3L/zlV+WK2rzRpqEkyg8Lb0FDMe3xF+A+/FXyfr+r S6zrupavKoSXUbqa7kQdA08jSEduhbFUBirNv/KK/wDgyf8AYhirC5ZHlkeVzV3Ysx6VJNT0xVbi rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVZV9al/wCVW/Vd vR/Tnq0pvy+qcevyxVK/M/lXXPLWr3Ol6taS209tI0fJ0ZUcAkB42IoysBVSOoxVKcVcASQAKk7A DFUT+jNS/wCWSb/kW39MVd+jNS/5ZJv+Rbf0xV36M1L/AJZJv+Rbf0xV36M1L/lkm/5Ft/TFXfoz Uv8Alkm/5Ft/TFXfozUv+WSb/kW39MVd+jNS/wCWSb/kW39MVd+jNS/5ZJv+Rbf0xV36M1L/AJZJ v+Rbf0xV36M1L/lkm/5Ft/TFXfozUv8Alkm/5Ft/TFXfozUv+WSb/kW39MVd+jNS/wCWSb/kW39M Vd+jNS/5ZJv+Rbf0xV36M1L/AJZJv+Rbf0xV36M1L/lkm/5Ft/TFXfozUv8Alkm/5Ft/TFXfozUv +WSb/kW39MVd+jNS/wCWSb/kW39MVUZreeFgs0bRMRUB1Kmn04qsxV2KuxVdFDNM/CJGkfrxUFj9 wxV6L/yr7zd/yp79L/oyf6v+lvV9P039X0Pq/D1+FK+nz+Hl/DFX/9k= + + + + uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 + xmp.did:54607f9a-caea-49b8-9eeb-f106a550c605 + uuid:4339b935-e581-5840-ac72-f3c6b52f5b9a + proof:pdf + + uuid:ec456e48-5fd4-4875-93c4-e073576b0227 + xmp.did:03811155-2e06-d540-85f9-58f10d0c940a + uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 + proof:pdf + + + + + saved + xmp.iid:54607f9a-caea-49b8-9eeb-f106a550c605 + 2018-08-31T14:26:04+02:00 + Adobe Illustrator CC 22.0 (Macintosh) + / + + + + Basic RGB + 1 + False + False + + 256.000000 + 256.000000 + Pixels + + + + Cyan + Magenta + Yellow + Black + + + + + + Standard-Farbfeldgruppe + 0 + + + + Weiß + RGB + PROCESS + 255 + 255 + 255 + + + Schwarz + RGB + PROCESS + 0 + 0 + 0 + + + RGB Rot + RGB + PROCESS + 255 + 0 + 0 + + + RGB Gelb + RGB + PROCESS + 255 + 255 + 0 + + + RGB Grün + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blau + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Kalt + 1 + + + + C=56 M=0 Y=20 K=0 + RGB + PROCESS + 101 + 200 + 208 + + + C=51 M=43 Y=0 K=0 + RGB + PROCESS + 131 + 139 + 197 + + + C=26 M=41 Y=0 K=0 + RGB + PROCESS + 186 + 155 + 201 + + + + + + Graustufen + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + + Adobe PDF library 15.00 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 7 0 obj <>/Resources<>/Properties<>>>/Thumb 11 0 R/TrimBox[0.0 0.0 256.0 256.0]/Type/Page>> endobj 8 0 obj <>stream +HdWK\7 )F#>L "$^9*80 O7)Jb^?v=>b |ϣq}󿏯]첽JRK_/.}yvv^aG6/|կǭYnV_VKiݮUlޫSqRmxb+ [>dGzck(pϋ2SFq"EZ[ݵ,)vej8[:/x熗0c䞪;y7M#d#fPޞ'Mz+6lġykhptǟ 5~yhXFKie3Ǖ~+BqtN#`IkAsO[H&P_G6V=73;yΠˈ8<m*ggeJP8_8!0=Hpx1w Yd~nGP!^r](ƺ"b8x܏¾&`P,ji` F+K4AkR$f*pƒF9gCl('%sD'hYڱ!$GpF!<,C56$G~D21I\-ɽ&]G =nFx r]dOpT`wnF)9)<-y˨3vwl8m՗vi`n!ݫpôJu.q j&@6Ҏ!\ )HQC"Kޑzu.C;RH%g(3#޳ȍ+iٗ7 +  q/܊8Tf}<<_y?6ppypLema.EMi |;;胢F >z7VU ">A6Nyj(*ɴd۳g~e< #mcG.ד.8;Bcx4 koǼB /AC32C퉽j4@d8wjb )sTbN5-BTBOTxq8NlA縄T5=r"%;`H3~;0!"JƇ9؉s+ kYC^[gR' xN9 +'j*QGg'pJ Q:2c~dc#Nf>g/ >stream +8;X.+_%hVW#Xm,P7=&-pYp%g5ZHpT#A3DJ+%MCr]dAP[t+[^nicA0mP^'j!n%QE>< +Hlfgg]Wm6H#7I^th06Pf%DQc]qPU?r!`sG@&'2C@-Cp%jgm)0aU_>Do.69_Bk)[`u +Ot.l%hcBO0r^jfmXBp-=FhW>nVCN`=VE/qMjOZD9q>9XVHjok"4FEb3?iNCU6u-$0 +!'QB7Er~> endstream endobj 12 0 obj [/Indexed/DeviceRGB 255 13 0 R] endobj 13 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 14 0 obj [/View/Design] endobj 15 0 obj <>>> endobj 10 0 obj <> endobj 9 0 obj <> endobj 16 0 obj <> endobj 17 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 17.0 %%AI8_CreatorVersion: 22.0.0 %%For: (Carsten Duvenhorst) () %%Title: (heart_light.pdf) %%CreationDate: 31.08.18 14:26 %%Canvassize: 16383 %%BoundingBox: 33 -241 225 -17 %%HiResBoundingBox: 33.4615300484429 -240.495272086609 224.54759606832 -17.5783596674073 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 13.0 %AI12_BuildNumber: 244 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Passermarken]) %AI3_Cropmarks: 0 -256 256 0 %AI3_TemplateBox: 128.5 -128.5 128.5 -128.5 %AI3_TileBox: -151.5 -508 407.5 275 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI17_Begin_Content_if_version_gt:17 1 %AI9_OpenToView: -122.777777777778 12.1851851851852 2.7 1428 784 18 0 0 6 43 0 0 0 1 1 0 1 1 0 0 %AI17_Alternate_Content %AI9_OpenToView: -122.777777777778 12.1851851851852 2.7 1428 784 18 0 0 6 43 0 0 0 1 1 0 1 1 0 0 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 7 %%PageOrigin:-272 -428 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 18 0 obj <>stream +%%BoundingBox: 33 -241 225 -17 %%HiResBoundingBox: 33.4615300484429 -240.495272086609 224.54759606832 -17.5783596674073 %AI7_Thumbnail: 112 128 8 %%BeginData: 17930 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FFFD0B275227522752275227522752275227522752275227522752 %275227522752275227522752275227522752275227522752275227522752 %27522752275227522752275252527DA8A8FD1EFF2727F827F827F827F827 %F827F827F827F827F827F827F827F827F827F827F827F827F827F827F827 %F827F827F827F827F827F827F827F827F827F827F827F827F827F827F827 %F827F827F827F827F8F8F827277DA8FD1BFFFD0527F8272727F8272727F8 %272727F8272727F8272727F8272727F8272727F8272727F8272727F82727 %27F8272727F8272727F8272727F8272727F8272727F8272727F8272727F8 %272727F8272727F827F8277DFD1AFFF827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F8F852FD19FFFD5827FD18FFF827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F8A8FD16FF522727F82727 %27F8272727F8272727F8272727F8272727F8272727F8272727F8272727F8 %272727F8272727F8272727F8272727F8272727F8272727F8272727F82727 %27F8272727F8272727F8272727F8272727F8FD0627A8FD15FF2727F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F8A8FD14FF %52FD0A27F8272727F8272727F827F827F827F827F827F827F827F827F827 %F827F827F827F827F827F827F827F827F827F827F827F827F827F827F827 %F827F827F8FD1327F8A8FD13FF2727F827F827F827F82752A87DA8A8A87D %A87D7D7DA87D7D7DA87D7D7DA87D7D7DA87D7D7DA87D7D7DA87D7D7DA87D %7D7DA87D7D7DA87D7D7DA87D7D7DA87D7D7DA87DA87DA87D52F827F827F8 %27F827F827F827F827F827F827F827F8A8FD12FF52F8272727F827272752 %FD3EFFA8FD0427F8272727F8272727F8272727F8272727F8A8FD11FF2727 %F827F827F827F87DFD3FFFA8F827F827F827F827F827F827F827F827F827 %F827F87DFD10FF52F8FD07277DFD40FF52FD1427F8A8FD0FFF2727F827F8 %27F827F87DFD40FF5227F827F827F827F827F827F827F827F827F827F827 %F87DFD0EFF522727F8272727F8277DFD40FF52F8272727F8272727F82727 %27F8272727F8FD0527F87DFD0DFF2727F827F827F827F87DFD40FF52F8F8 %27F827F827F8522727F827F827F827F827F827F827F87DFD0CFF52FD0827 %7DFD40FF52F8FD072752FF52FD0D27F87DFD0BFF2727F827F827F827F87D %FD40FF52F8F827F827F827F852FFFF52F8F827F827F827F827F827F827F8 %7DFD0AFF52F8272727F82727277DFD40FF52F827F8272727F82752FFFFFF %5227F8272727F8272727F8272727F87DFD09FF2727F827F827F827F87DFD %40FF5227F827F827F827F852FD04FF52F8F827F827F827F827F827F827F8 %52FD08FF52F8FD07277DFD40FF7DF8FD072752FD05FF52FD0D27F87DFD07 %FF2727F827F827F827F87DFD40FF5227F827F827F827F852FD06FF5227F8 %27F827F827F827F827F827F852FD06FF522727F8272727F8277DFD40FF52 %F8272727F827272752FD07FF52272727F8272727F8272727F827F87DFD05 %FF2727F827F827F827F87DFD40FF52F8F827F827F827F852FD08FF52F8F8 %27F827F827F827F827F827F87DFD04FF52FD08277DFD40FF7DF8FD072752 %FD09FF52FD0D27F8A8FFFFFF2727F827F827F827F87DFD40FF5227F827F8 %27F827F852FD0AFF52F8F827F827F827F827F827F8F827FFFFFF52F82727 %27F82727277DFD40FF7DF827F8272727F82752FD0BFF7D27F8272727F827 %2727F82727277DFFFF2727F827F827F827F87DFD40FF5227F827F827F827 %F827527D527D7D7D52FD057D2727F827F827F827F827F827F827A8FF52F8 %FD07277DFD40FF7DF8FD0927F827F827F827F827F827F8FD0D27F8A8FF27 %27F827F827F827F87DFD40FF5227F827F827F827F827F827F827F827F827 %F827F827F827F827F827F827F827F827F8F827FF522727F8272727F8277D %FD40FF52F8272727F8272727F8272727F8272727F8272727F8272727F827 %2727F8272727F8272727A82727F827F827F827F87DFD40FF52F8F827F827 %F827F827F827F827F827F827F827F827F827F827F827F827F827F827F827 %F8A852FD08277DFD40FF52F8FD23277D2727F827F827F827F87DFD40FF27 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F87D52F8272727F827272752FD40FF52F827F8272727F82727 %27F8272727F8272727F8272727F8272727F8272727F8272727F827522727 %F827F827F827F87DFD40FF52FD04F827F8F8F827F8F8F827F8F8F827F8F8 %F827F8F8F827F827F827F827F827F827F85252F8FD07277DFD41FFFD0752 %7D527D527D527D527D5252527D527D5252FD0C27522727F827F827F827F8 %7DFD5AFF5227F827F827F827F827F852522727F8272727F8277DFD5BFF52 %27F8272727F8272727522727F827F827F827F87DFD5BFFA8F827F827F827 %F827F85252FD08277DFD5CFF52F8FD0727522727F827F827F827F87DFD5C %FF2727F827F827F827F87D52F8272727F827272752FD5CFF52F827F82727 %27F827522727F827F827F827F87DFD5CFF2727F827F827F827F87D52F8FD %07277DFD5CFF52F8FD0727522727F827F827F827F87DFD1AFFAECFA7CFA7 %AEA8FD19FFAECFA8CFA8CFA8FD1BFF2727F827F827F827F87D522727F827 %2727F8277DFD17FFA8A782A682827CA682827CA7A7CFCFFD11FFA7A782A6 %82827CA682A682A7A7CFFD17FF52F8272727F8272727522727F827F827F8 %27F87DFD14FFA8A77C827B827B827C827C827C827B827B8283FD0DFFA8A7 %7B827B827B827C827B827C827B827BA6A7FD15FF52F8F827F827F827F852 %52FD08277DFD13FFA7A67CA682A682A682A682A682A682A682A682A682AE %FD0AFFA7A67CA682A682A682A682A682A682A682A67CA682AEFD13FF52F8 %FD0727522727F827F827F827F87DFD11FFA87C827C827CA67C827B827B82 %7CA67C827CA67C827C827BA6A8FD06FFA87B827C827CA67C827CA67C827C %A67C827CA67C827C827BA6A8FD11FF52F8F827F827F827F87D52F8272727 %F827272752FD10FFA782827CA682827BA682A7A7CFA7A67CA682A67CA682 %A67CA68282A7FD04FFA77CA67CA682A67CA682A67CA682A67CA682A67CA6 %82A67CA68282A7FD10FF52F827F8272727F827522727F827F827F827F87D %FD0EFFA8827B827C827B827CA7A8FD05FFA8A77B827C827C827C827C827C %8282FFA8A67B827C827C827C827C827C827C827C827C827C827C827C827C %827C82A7FD0FFF5227F827F827F827F87D52F8FD07277DFD0DFFCFA67CA6 %82A67CA6A7FD0AFFA782A682A682A682A682A682A682A67CA682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A6A7FD0EFF7DF8FD %0727522727F827F827F827F87DFD0CFFAEA77B827CA67BA6A8FD0BFFA782 %7C827CA67C827CA67C827C827C827CA67C827CA67C827CA67C827CA67C82 %7CA67C827CA67C827CA67C827C82A7FD0DFF5227F827F827F827F87D5227 %27F8272727F8277DFD0CFFA77BA682A67BA7FD0EFF7CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA6AEFD0CFF52F8272727F8272727522727F827F827F827F8 %7DFD0BFFAE7B827C827BA7FD0EFFA8827B827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7BA7FD0CFF27F8F827F827F827F85252FD08277DFD0BFF82A682A682A7FD %0FFFCF7CA682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682CFFD0BFF52F8FD072752 %2727F827F827F827F87DFD0AFFA7827C827C82A8FD0FFF82827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827CA67C827CA67C827C8282FD0BFF2727F827F827F827F87D52F8272727 %F827272752FD0AFFA77CA68282A7FD0FFF82A682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA6A8FD0AFF52F827F8272727F827522727F827F827F827F8 %7DFD09FFCF7B827C827CFD0CFFCFA7A77C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827BA7FD0AFF2727F827F827F827F87D52F8FD07277DFD09FF %A78282A682AEFD0AFFCFA77CA682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682FD0AFF52F8FD0727522727F827F827F827F87DFD08FFCFA7 %7C827C82A8FD09FFA8827B827CA67C827CA67C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C82 %7CA67C827C82A7FD09FF2727F827F827F827F87D522727F8272727F8277D %FD09FF7CA6828282FD09FFCF827CA682A67CA682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA682A67BADFD09FF52F8272727F8272727522727F827F827 %F827F87DFD08FFA8827C827BA7FD09FFA77B827C827C827C827C827C827C %827C827C827C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827C8282FD09FF27F8F827F827F827F85252 %FD08277DFD08FFCF7CA68282A7FD09FF82A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A7FD09FF52F8FD0727522727F827F8 %27F827F87DFD08FFA8827CA67BAEFD08FFA7827C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827CA67C827CA67C827CA67C827CA67C827CFD09FF2727F827F827F827F8 %7D52F8272727F827272752FD08FFCF82A67C82A8FD08FFCF7BA682A67CA6 %82A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6AEFD08FF52F8 %27F8272727F827522727F827F827F827F87DFD08FFA8827C827BAEFD08FF %A7827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7CFD09FF2727F827F827F827F87D52F8FD07277DFD09FF82A682A6A7FD08 %FFCF7CA682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A7FD09FF52F8FD0727522727F827F827F827F87DFD08FFA8A67C827BA7 %FD08FFAE827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C %827C8282FD09FF2727F827F827F827F87D522727F8272727F8277DFD09FF %82A6828283FD09FFA6827CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA6 %82A67CA682A67CA7FD09FF52F8272727F8272727522727F827F827F827F8 %7DFD09FFA77B827B82A8FD08FFAE7B827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C8283FD09FF27F8F827F827F827F85252FD0827 %7DFD09FFA7A682A682FD0AFFA682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A67CCFFD09FF52F8FD0727522727F827F827F827F87D %FD09FFAE7BA67C82A7FD09FFA7827CA67C827CA67C827CA67C827CA67C82 %7CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA6 %7C827CA67C827CA67C82A8FD09FF2727F827F827F827F87D52F8272727F8 %27272752FD09FFCFA67CA682A7FD0AFF82A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA6 %82A67CA682A67CA682A67CA682FD0AFF52F827F8272727F827522727F827 %F827F827F87DFD0AFF82827C827CCFFD08FFA8A67C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827BA7FD0AFF2727F827F827F827F87D52 %F8FD07277DFD0AFFCF7CA68282A7FD09FF82A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A6CFFD0AFF52F8FD0727522727F827F827F8 %27F87DFD0AFFCF827CA67C82A8FD08FFA77C827CA67C827CA67C827CA67C %827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C8282FD0BFF2727F827F827F827F87D522727F827 %2727F8277DFD0BFFA7827CA682A7FD07FFCF7CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA6 %82A67CA682A67CA682A67CA682CFFD0BFF52F8272727F8272727522727F8 %27F827F827F87DFD0CFF7C827C827BA7FD06FF7C827C827C827C827C827C %827C827C827C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827BA7FD0CFF27F8F827F827F827F85252FD %08277DFD0CFFAD7CA682A67CA7AEFFAECF82A682A682A682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A682A682A682 %A682A682A682A682A682A682A6AEFD0CFF52F8FD0727522727F827F827F8 %27F87DFD0CFFCF827BA67C827B827C827B827C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C82 %7CA67C827CA67C827CA67C8282FD0DFF2727F827F827F827F87D52F82727 %27F827272752FD0DFFA8827CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67C %A682A67CA682A67CA682A67CA682FD0EFF52F827F8272727F827522727F8 %27F827F827F87DFD0EFF83827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827BAEFD0EFF2727F827F827F827F87D52F8FD %07277DFD0FFF82A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A67CADFD0FFF52F8FD0727522727F827F827F827F87DFD0FFF %CF7C827C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827C %A67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67B %A6CFFD0FFF2727F827F827F827F87D522727F8272727F8277DFD10FFCF7C %A682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682 %A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA6CFFD10 %FF52F8272727F8272727522727F827F827F827F87DFD11FFA87B827C827C %827C827C827C827C827C827C827C827C827C827C827C827C827C827C827C %827C827C827C827C827C827C827C827C827C827B82A8FD11FF27F8F827F8 %27F827F85252FD08277DFD12FFCF82A682A682A682A682A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A682A6A8FD12FF52F8FD0727522727F827F827F827F87DFD13 %FFAE7C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C827B82A8FD13FF2727 %F827F827F827F87D52F8272727F827272752FD14FFCF82827CA682A67CA6 %82A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A6A8FD14FF52F827F8272727F827522727F827 %F827F827F87DFD15FFAE7B827C827C827C827C827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827BA6A8FD %15FF2727F827F827F827F87D52F8FD07277DFD16FFCF82A682A682A682A6 %82A682A682A682A682A682A682A682A682A682A682A682A682A682A682A6 %82A682A682A67CA7FD17FF52F8FD0727522727F827F827F827F87DFD18FF %7C827C827CA67C827CA67C827CA67C827CA67C827CA67C827CA67C827CA6 %7C827CA67C827CA67C827C827BA7FD18FF2727F827F827F827F87D522727 %F8272727F8277DFD19FF828282A67CA682A67CA682A67CA682A67CA682A6 %7CA682A67CA682A67CA682A67CA682A67CA682827CAEFD19FF52F8272727 %F8272727522727F827F827F827F87DFD1AFF83827C827C827C827C827C82 %7C827C827C827C827C827C827C827C827C827C827C827C827C827CAEFD1A %FF27F8F827F827F827F85252FD08277DFD1BFFA8A67CA682A682A682A682 %A682A682A682A682A682A682A682A682A682A682A682A682A6A6FD1CFF52 %F8FD0727522727F827F827F827F87DFD1CFFA8A67B827C827CA67C827CA6 %7C827CA67C827CA67C827CA67C827CA67C827CA67C82A7FD1DFF2727F827 %F827F827F87D52F8272727F827272752FD1EFFA77BA682A67CA682A67CA6 %82A67CA682A67CA682A67CA682A67CA682A67CA6A8FD1EFF52F827F82727 %27F827522727F827F827F827F87DFD1FFFA87B827C827C827C827C827C82 %7C827C827C827C827C827C827C827BA7A8FD1FFF2727F827F827F827F87D %52F8FD07277DFD21FF828282A682A682A682A682A682A682A682A682A682 %A682A682AEFD21FF52F8FD0727522727F827F827F827F87DFD22FFA7827B %A67C827CA67C827CA67C827CA67C827CA67C827CCFFD22FF2727F827F827 %F827F87D522727F8272727F8277DFD23FFAEA67BA682A67CA682A67CA682 %A67CA682A67CA6A7FD24FF52F8272727F8272727522727F827F827F827F8 %7DFD24FFCFA77B827C827C827C827C827C827C827B82A7FD25FF27F8F827 %F827F827F85252FD08277DFD26FFCF82A682A682A682A682A682A682A7CF %FD26FF52F8FD0727522727F827F827F827F87DFD28FF82827CA67C827CA6 %7C827BA7FD28FF2727F827F827F827F87D52F8272727F827272752FD29FF %A7827CA682A67CA682CFFD29FF52F827F8272727F827522727F827F827F8 %27F87DFD2AFFA8827B827C8282FD2BFF2727F827F827F827F87D52F8FD07 %277DFD2CFFAD82A6A7FD2CFF52F8FD0727522727F827F827F827F87DFD2D %FFCFA8FD2DFF2727F827F827F827F87D522727F8272727F8277DFD5CFF52 %F8272727F8272727522727F827F827F827F87DFD5CFF27F8F827F827F827 %F85252FD08277DFD5CFF52F8FD0727522727F827F827F827F87DFD5CFF27 %27F827F827F827F87D52F8272727F82727277DFD5CFF52F827F8272727F8 %27522727F827F827F827F87DFD5CFF2727F827F827F827F87D52F8FD0727 %7DFD5CFF7DF8FD0727522727F827F827F827F87DFD5CFF2727F827F827F8 %27F87D522727F8272727F827277DFD04A87DA87DA87DA87DA87DA87DA87D %A87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87D %A87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87DA87D %A87DA87DA87DA87DA87DA8A8A85227F8272727F8272727522727F827F827 %F827F827F827F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8 %F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827 %F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8 %F827F827F827F827F827F827F827F85252FD6E2752F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F85227F8272727F8272727F8272727F8272727 %F8272727F8272727F8272727F8272727F8272727F8272727F8272727F827 %2727F8272727F8272727F8272727F8272727F8272727F8272727F8272727 %F8272727F8272727F8272727F8272727F8272727F8272727F8272727F827 %2727F82752F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F852FD6F27 %52F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F827F827F8 %27F827F827F827F827F827F827F827F827F827F827F85252F8FD0527F827 %2727F8272727F8272727F8272727F8272727F8272727F8272727F8272727 %F8272727F8272727F8272727F8272727F8272727F8272727F8272727F827 %2727F8272727F8272727F8272727F8272727F8272727F8272727F8272727 %F8272727F8272727F82727F852A852F827F827F827F827F8FD5127F827F8 %27F827F827F827F827F827F827F82727FF %%EndData endstream endobj 19 0 obj <>stream +%AI12_CompressedDataxdqȌԜGpekV]-qFh4]5a십>8y2@CP5C'@88~ܿ_~O?O/ŇɭoC?ŏoCOo޿mw_q^ǧw/ӻ_/?я||d'}ů?}~m7{B 'o^PSOv7>]n?9XW'Y_Jڶs+>d_|?{ѺX94|,~v~5ӿዧ߿fh_O׿}n}śww6g?~W_|Ih~zz~Ek?6Oֲo~X췭[w9f?~|^˿׃}\O^pƌs O>s='_}xi~ZSawˆ۰ iprͦG!=O_v~d~Da&zz o_-v7_)oqϛUKo~Wo~o޼{[5H'&L(<}xOO~;yٯ/G{~Xu__>=þإ_oOo>O_C;oWS_1o}|Ͽ'ٿ?xOmQ|x/;k>w-'d훯|G&$%![vO>[}xwh?c +c7?n +B}n2)nfB|4B2Fb1kl;VzđCvdZroӇ=D +Ct!WzbJw{YNlt\W~ͲMϰ!nה7E$){}cσ }_󨿏U|'?7i)otٯ<HEz󖃝s6vfێ_1*?\JQʋ<ޔW5ǎfoemOlV0hE{փs z?Wciӣ.SS>]Ar~_=ؿ?7g_貽l趺Ϙ`yFyWc+'o\isuc+c=σ}3^K/364a}Gp`{ t4}}lz"cEU]|^xxx<,! +͈Gq}/>r ͟9/ƃY}4~ŦIz`su،m6sd39zm^f{Y_nl'[m.ok~q??_ûy^4ޖSuMĄ }iޖkekl^ڢ]m' >M&64gebZy-o?T3v=d)f8?;xk!1>.QdcdG6ykѤc]BI;qn& +T]ޤ ߛJڡI9ߣm +h~_Vm`qm? &wo_-׃ͳBs3!LcHaÔ6z#-- ۰{ʹّX:;.w Gf_6uwlrM2}=G9 :մlRt̰ԣRL; ˟S%.4CMSFV3 JX].ᡇqw^oXj/>b f=s)H(0GiklReYnf\rm^>o-R\{ܭ꼑oe/i{;ףP];Sў~d{܂SeK$Ylou,G. 6=GώqS=;ۏ$U$C0ѦMa21 +7,9'P&hWL5X<ʸXn7h[-_[vMu8onfT7{l$ŦE1l~ {֭rCi}IǃLh?c/ǾQ/.+m Sˆ6 +UK3Ū"tHMv~lQng?= |gӣ?jHmL+TXXoeWпJXF^"7շ$?cYxwua%m=_NЌ6Fgp7v/>M}r֯UH)oV0}ñ^6h;nwb1uGV2;i]+`lɞmlE 8%s?jm?i~/78 +%&ҫ+A?@$, r3AL`Yzg>xv(da"DR ; ITHJ +H ԽR +L97 +OeE."LU/_c6&p=f ٣+|$?3,|f?_|M}^楹fI(f8w='̚2>i洹9kY 9/ sI)|lZ\yuYsn]ά܊sn-HR +.& {2՜zu ,39 O|Yw,f sf!1%X3f΄Ŏ0)pT,p$79iP4ص ,q2a&^zZl/O;L]hhϔk4L4azŜ{w?g{᷷?Qzx:S/0ށJ} Uvj/J^y]3QuP1>6g*7ɞA6; ?maeovepǝFj fMT`FWjZi@Ag l~o3n?iꭽWؙguNm.1CkSz3w""[ZrKaǘmڰ>dkwZʸNoWFfu5JCA_]]vZ{UmG=`rz7\Kh#FoHq1;lώGlw,i'ymLLOֻWw&6.)f64~hwѬ^rxgD7ޱgN5tNR '$B3-;z36Vȕ g(c!DyPW֨CfDCu[#/7IރmGvyxgݶۉdemUKhI>;T]yHy//Ä]t{>?˙&oJ4ܸ5O p޿Ҳ9RF};5 ++!|g[.l +pW3L(3(e9(ʺ4l. e:HRI,SMQX:dpAgس$"e?̟dc`/^Imҗڒb73ݶ1؄y5a_Έ!dڟ@ `:軽*dOa}!J8Hytn#;RޮiD -m茡W0pgb ԤOћbg!hWl![-&nM3T)#䲍N ?SɅE&5'odKRW՟]zNC0G\NY| iMf9%u3zդ"f \ԎhŇ\SyȲPa4(q,~m #!ff2PH9NZW_.=I=8pal&|~a R#Rľ_}B%&yݒ& i4}Y7IM rM30P`;x+d_a]j#i-D6Q{\JϦ7Ezg5& v]eo")*$H†l)fZw6SI``*kG}0K#.@ y{ 4hÄ'mB +#Ɖ0k+d^Ei}d|dW]Lr*SV i lmN$FˈElƕ[E#py3nX(+Z֭,;߸M6smLn{Y-P^iLjbO6NMWdfٞtrt+7 1@־ύ""yP+o)xZ +`sPCѢee _@?0#aRa!(h(LO4 ĭe*lg[Co&let|l[U֪)tR{Z:U㺮,YY&m -ý:E\kRzn̓#h0m`Rzjk3ɺĬmb$mp[MT˶>=4 Mr& *6]EdK 4DiOט<{PrE!rץ g,0%әFx [ŻJxcFAh^xVyL_}zms0Nٽ^C)=^h(f6յ{,ӫCuـI|ڕBp^dLW6d60UI&W07k"&O|%EQtB2&A6d&lF5m]$"1(D-$d1麛n5fj5q`ƸG t!e*+ $zl<bSc=[1aÏqU Uɺ&%)vEyNPvwq +SKo(bD5zTg;ގL1V4T}jE. ;zSh`Z8b׳]D3[A c 7[.#_ȣj%Of*S1=fgIzE +@/bsᗐ`pL ;*ϡEcv~vBΰ YIfK:3ּ)p%*`3N> aM}\Bx3?jrO%’֨jz وsi]|- ?6uJ)RgL&*pYꪂ'M娂~ƅd (qJ$?[5I6h{^9S'#oТzX/ ΐ?V#L\,7JC8opMM0"ӚYQ{4i[@옉-5#""ѻlb6Egl3 HJDFbR8zAjekR+vX:TPYMd*gyMC{l^4l/ c<(˦NM=n,k5B!Vqf2o. @RD~e +"-4Փ% Qx[^ + |9ro[!NH6eiWm Ef$Tl>½Xx:A~= F1*ɦpt [ h, l0fu~%af߫n/74510XNٌPGzF"Fvjb`;ƶ"LE$=4ͺmpm9CZaz%' APE:gsh'I9 Ys:e ڤ Lߍ_Zb8Fƶ*IfFf~QB'7O' Utڤ|mwAB~`k"وRp$qq &^d&"u2yw *bb0\ Luw3bOݦ t +jr"x +T]6d`cmA6S2{w?-wAFMH!T 6vN4hUt:2Op +@,J [Pq5Dd'*U vH +a$P b7/8)]May +lBQ p2JMxOd%%ΧAv`MEPY,„e(@m6fN[n6:8.37 6[|& yt$>V7.AGXaTnb?\`"ܮ).\l f#.H.nXMi!Y&f+afkH])T$͒AGI=¾!ONJ^kt ܑWfdPg,o60;rbd4k֡j\5o&YjE@ӹnC;es +SO7Y[UzsV"g]aZpk\Ԁ5M٬{k.g;y5$BʌBGӷk,]Ķr-%d.\bOAN"qv/dAP̫F<]1UXx f zwEXcu񞤮}`E~x6Sv@]e$ ՝¡ʟm"@ikl3KV>-8*I뽵N r:H\lZ(NΠ, "%+#^t)S\s(&6RMhVA(-N"Ie6e(Ff4i(aH(F-^ S~v%(ihr.r$'ɸmVɉe!`XyyeHru0蘭&\e)+sƹڵ-#y.L ͝+&谯u2<]JQ%'j-AC=EޙVl>͓}wtռ!m sЁ +DǷ \n/]?\d?yP/(G!QY6ϏeQRΆDhIK LAӔz8 QY~SƮ2#I zUJ`"239@GwCŎ j ʒ/#}lQ JvigN  Dثn=D))XU؂&'t>#*}uF-A'e=E/3<0 f(r%.'I]h3I%,-459PF~4095LͅƇ,2QvQN^SZ0l͓}H(D0S4Yʌu#pADaff"]Qfpfkyw r\uS6J$]mik3-XIy@O7%*#E28/@ypBM[yw%NN_Svm^|ÏcH 0$/yaOWC_GYdJ!Ϣx;S?سo+2Nl ZL d q%)̋\,6{e~1;XrQ%$bg4mDܔ4wiG =U tw* D"l/޳RKC',5Z$#"_G]6hV<9tfm|v2OvdQnlrgʊ7cpu//*9PfyAۓH& +,H$oXu7嵸}+p@G`Ig3NpK$APqZ` Ug&S#9OCVğgz_'3v@R}Ŕ g5LOJ)Mc0>Ot)Mc^+qc^L IױXf>[Z/J ۃM&lx2hm$uU#RAPmů`R _qQ&K!UtXBlCԨ.$iPezՂ[g +R~K?Oͻ/npg|_sEeK2mJ-[N3ɴ!+ܯLRfV +8x I)lyvU5򬞠AfU.W} +29TSl`3&Tfr€h<(Aa*:~%*G'귉Wڃx ++9 Wƫ\_>$*d G- BqU-:vJc#:bOӄZWXI"A脁#v +2%P]:Kʾr (P`?6Mn`j*l%G@Tzz絥ɵAS/cFHD"p*v^Wtf-wZI+Y@J2Kr4˩AvJd؁J7+TWY,? +)F?J,R2lS q[Hu&]diPO ^ ҰGd7」!Z%ĄW6Gp2 W4{E(`>G7aƔb4L̢8ʋqAhY}&ПU[&7|a}sCSd04R RkɛcJ3MPe]t %؉5SF'0bXC1fcI^mo< O&K*R$"F^ ѝ4[Y9-S8 +\wOˮ M hf Nv +–(&0Slf}RBZYHԜM(bFQ:hI^YV-YoUn'%f͎F/醀Jh*D2nUra#v0ئFѵB}`{ suHYBI8(|܋r0&9@һ:WC\l* _rq@-:HL2.&}4ܳ9rw,O@te'@T!] ݨIN4 +z1$y 4|v Rp驇/3Vy\#4 Ѕ_3M&P3[EQugc TeX Mj.wec/vЉ9A#.ؓ + %^XGOd)*Ѩ +mxO LdTA֔ +p R 0΄k@-I +bYtV 3#`3=Do.4N&i;y@LhE~lsUx# VYwt!␔ V=7vlmhƙ c'ۮbm^#{4G=*Un.-^MN]p&A6 VRRuPUb7Ot 3hJ2OrՓp 4.}JXϔB Z{R*m +Yi%04vb `);f*4~#4xSmPpQwHxȑFCUݙސ@[|6P_5D&Yw?u`I5AăG6\9i&KFŢN06:+جMr`4jw Y^7cEWaDW +q!%pžLt8қ $phbG*1NVӺ@ %k=}S"UkTp9`ɬx^3EHHWC 3l*a%N`n/,=VJq{%~RudG%`Ó-t&ja)V,rj+JgӤzٖY>Ϝ(ڌ0..&m^SR)( uGEB̎L4pt"ZibM^UVo33l@>.^SeMTe{xlP+n- )(IWyyCsU45t\z5 N #yɓF!ֽ nS*^X2s:{ơC*UxG'db½=flJtR +itBW@Df'tvT@Z@e7^B2$yªrWt0UqtH:%,T<&= piqPWE[!T F: b @QWa<}H=Q(MSc֕UV.Ud#Xw +UXFlT,*5"QY3W_<oK,BiW4!COudZ+e#:$(^E`uB06f{>brI5TO6Њe7 +[W[PoD({jLQҎZa(UVJxY4ϋJ#)LM%8f 6w +@]5q\sDkY{CՑ픙U@ &2:aožn /=&vz۔*f}p}\hʵC-UI +6/]i1Ѿyz9E(XBĠqל-7 ^TgFAgHXLR8,p@ݥ!G&=T*Ϝ|.bT'eU)S p!ߣ-,Mx5(-`N]5q!){D>.SrDV*њ܅XE,gr63@Yk\ȀR~ꏘhIeu3(533F nvJ.F, ]YiL^3),S#orQq?Ui Cڨ & *ns+A~M*,MضmyԪdܬ*TXLpFu(@ +rKW9րZJA [B B) a|!y`VM9W%8,gsK*Mc{Óͺ^]lo>4%Z+^7X;aU[]܆=}KQMf/zRlz6U˛ÎG,r\PCe5H?_)sW+%"MHdJrb^Ii0)K Ut썥i4@L9DMV>0]FZN׌!Sv|+l[d)*5FS+dչEvg)jhR Ӯ*`p(ا80z2nqJ  {cBYtDS !zKއ#Nlɻdnh,c^6|E#r=Sp"JR8C*&P۫)[M$ZD5 뜆mzoN/ tx8'YB +FK1TD=᧔$u-849ERU T$A-Rrd%@XrL¦}] +&+{8h0*L8*]k߽joܣL{ PKsL 9gP,HT(2q|?W Ó5;J/hݽʼzfΖkWq8e\_ A;/3 @ +cm{:L9 -.&({p`9R(8f xA ťl/!Rgfņ' o L~E_h c2Z8~.̴ߣ Frg?{ݧ/OJ7_O_=!wnY=žLO=#a>X\lı< -߁9>aƗ0;yⱣj;8!S<*|>Gxҹ4X|﹄3w]N}IKZy8cu'ZN$4I yU&S}|QSPȹIٮj?O~lf݈!5{3#@,p:nN^E :\ij8͝Mт@aKԴxRʰrXh\1BI{׌Wp)m@v>p^އ8G1ŚʤÖwoJ +dE追Qhқugڼ>6w?&;._vF=fq}SZ`.E9'^tQzLc.c/nX^ɢ.i4ɹYh[;qr!u|x +K)5CmLm4p Gm-2PzlYH]&1צ"5"`jS H]L);6e/y;# XygVbFKK$R;/vub&kȆҐ5.l?݌)"G묲:a t-pⵦ@q-y;by.ꜝU8^ՄhE&(e;'?"s7]ӗ: +fy1 Gijhx 1I)"sL ɏ^;9ykL\< +υR[9#V a1 +=5)b)JXd@SGI5@D0[}FM2g:R[*}t{<39:**Rwm6Yf$K@L,vlnT1Yzq;3˜N sN#ȳ蕑%[ʴd+/ +e/X=9P&3d=Of(܂ghtvQ!jX Ȕq$>Dg'1`,c1qRn,wRc 3%F<)C$esa1ZbYR;QX)ˉFy#Y;qP"!TΨS<z"N/ DUVܐ/ǙW3:8#&} '+kzQoz*NE.h:h쿛]B%ډwRËw;/Qhf:2h$p-ܽs=Q8c䱵zy܌;*hՑl:9U=H,͟zm.v]QexQGulQLz\9mt)&<#xsulRs9q +^ +7|g@Ж#^PUHy1n2eu +պ8n?q*`\Hg2SEU3Zx(H4 kmHq2xVu,᪞Al6_ 0P"N+UPE i|pUDsW锦E|`\ǻVptUrUGo]ÄQ2ƞ\mw|4P)zIj aM+^Us|kU2'O̼KT9_Zns$+TJpo@EwfOc]01\!aE*y2*ӉU4Ik'?Nu\88Dj0Y=tU[ E)3. N=o4zިJ\b&)cEpz zd8 ,$N}Fu;S HnKr]h.̴KSN +Y5mptJ3({t3s:Y۸b8U$AppJֳ ~ȬtEpʛp[2T,Jҵ#ΘN1AԷ Sߧqp}Ec3Sަ)*Pc8%qY (L=c8e{cf8 +Hadm ~!f#T5)gL.nf8w`8U MN ~,/-k\ڽP 2S Qz\@;) +V[TGZTPgTj s0>xh&ةNi|SI1TQٸvS5i[d@Z$ށ%G]HT0EсT`P0A1Dl2'1Ne=rrls>ST# +mL%R@PZn/i홧U,anI:CT}mCdZX at?4Ft(llBG>V%ԈZ/йZ<?cs95 3A)R$T;3~L*m2i1.Ƶ0d, @&e윗">|TdTi$$_{$OlZcNGjΝQ&"\IImNHJ-qD 5$nfcTQ/.^V`gqnRFj@@t҉q +ZT9ԐuK^餹B;8cQC:赕D_ M}ɎDx.,"Hu\&z"vv"H)FJgD҉4fA0v"Hׇ/@vULt RZDC#El~3wCfժ?AJA&> }AB) ov"oմ^A*&y\3"H5* =`J ɞ(#|L1MѯRle=[E' 71e7br-H,J$z3HwPwH5w80ElRGȰj Wg?{(OΑQ-͌wGy37_߁_u=lΈ4IEA xeyk\v#*`Xq(QND E,"tEgtJP ݝѹAs[[|7OTS|$Yԥ94wwNX#?nG62s9#sWl[B;g)řTꨆ2X]n_d ^ꔬ j(aO;;ʻJK׭9rp:S>78H'%dnl}w%B|JFU(}gglm#)#x|wd-sR0%}ԋ rw<+#Y;b$Ϋ(qNHytԥWɢ:;CP&ebw$։e:# +Z^j3Y7#"HŁjqN'#*hOX(%EȱVN Ki;W al#`D4X~]B3VA*)o>!H5G@ +HEH":#? p/~@ H?t dM +-ef!#?OÈe.?udӈ]~QG3G EGGiJ[\}((*uwpNѷon ("g$B;PUF(pE*nazzQ<ȿFEzm{A`QQx";b2W6~FG8^v&>0L|aqPgbڪpIG1xgS*hRɚpTC$O@, wJ>9'`b睓O,voU7c;,)p4 G>Tпxo º3f8~]O^HƬtɅNbO&Dy}R4uόᙓX=KxC}ƭw 08ϐɸoNqԓ7z%Mx4D2;~ur|,,%+{d#y3*=_]6S>z:{qɩJDYSO/DTzWǏz +:l ;V'y +$p'nz7B5icXNXyԓI_gS.8QS0OgTzĠ<1.=% +ƥ,3=IrԠi,AOZG8cГOy|W&;R;q j(tRȠ'Or LJO`XcȪ}ғlAO tRkش +cgsOr֑AOU>5SΠ<#͹E?r+i”DOTzwiPw*= +|>ZUX }S =]Tz[9#Yu)ѽ@O|&LN? 6@3ş' =FfȟЌmB3Z4m' ypwx%#*;AΟGwNy *O?O 7HG *NyHm/<=_q6vFF[Qc# 4WWzUYFDa\(l'R=%p{vU`o*.v=<9`cQfQ1/8G䘵9Ь3ك[Zgz:r7(]7?*79=?}8#N*W&FG1,s^_)Emȓx ջj"ՓJ&PnHuYT|RVŪGQtMpƫKB,~ً`[%z Þ]~iK=i3y"qMZq\<ז*8d_3)bS eP153OMj:zR̉iO0ccU;5^)W,z^)́P8yesmQ>2UwEx3weX ۤ+\$á/8%M6.(0CIN/rуDv/_vEi ^ܞ]b4Wyɩ팾 H "|wb;iUGNҕqn"A8z"" v$bb +bx ޲cM"'l(Ëg 4ξ#Pp*MoT.ʢ I؂[+*C&|[aF/Agz lfvYfKwk +%goW#o*i~I0, ޿g_|޿~HJ *-_ݏ(YG(yfG0z#FږH.nͧA0[d_"0~.n)e {OEw魾DmcLDU/~DwCtz1}G]Z KyR=bj?ȿ"oݚoW xgHK]|?9! f5zFj|e+.&?&L)^7/. ,_,|G+(; L*o|z R9:̣L|6oʙͳd]/782C( +b}OdGně?ސo]W:Qbvץ ~><7Kt$;'u`{t(& @^ɮ_!EKqKMlz + aØ){G'bF<Ӄk0=h[yoa7J=4R bneQMYa +8 ~]3>e<Ο:Nzv׭}ݴ/͐nfj, '"^;ڑITyvr/hA7g+pkmOq Y93 v%:!jopŅ܁wOalSN.\&Y*q$#IjʽAaЊƈxj.!ƀ#S*cLvL0=<2b.vxϜ NoyG [[{֌KP_E}ut9y}A:!װV`09UD3@.W =UZtWoR#h'iҝ+#l2ݲq`͝CʶxNS4P"n# vp++Ÿ^: #W5Τ,p,})B{DJݡ>m3y;t+e/6 3UiRȱuΏEt# WGr"]oI:O}J`z΁܅Tɳj3ގRo8?s%A#?M_7n<@PqD^c==^NPv(F\@+f/#ƫB4%_ҌةـT,=&.`N)(88uԱo!w o1,=#''U|^14n/6]'}+AS0/fHpur Zci%+6zǧ9~6ܾf W-#x} {OkD/ }({3u8&3boxtgvA1Zx5_eSNc($ +H[n"!o-3r4~t5? )v'GDxY=NJ4=:jFLQ +n=+t Uw@+T:"{X->aǒ(̗wt! *Bg^|Z!XO2Bٍ'>L xϋ77m(y^"l^4g8tt6FFZJݘ;*__ m㨃 h)Jk|'=i߽AYx {Cɟ:.Ws8GYs3Ďi/){3$#`Yd:Su' XY$Ggu|sؼV0g 'ݟ*]@Nd3=R1zqņ"}f\ȹ;*j= \ uώ!S¥;#_(V/rVF\WӨDoKs)e&<:[sIe ljA-O&e'!~J3J$9;\b*9bR*#)gFD8 +YDpz|A(OwϪ3VzZ+x ׀ݍ=kdD3j2u v zT5ֺ"Vd3ݺ^TVy8(ui\FW.jl*ɌY$3 g+Ũeڼ:u!#Af3a2 4Q@j mwD(u"<}^EH{պ02q(9=O 2ɏcL{z⁘ H1,އuDŽJD(HB~3dqL'"Q/`ufM1`SEQs\#/|x#ҾՏS_RfkzJIQ#Fw%9ҋsQ.RjX=#`"2"9z=y +?\ 3P^ JsxSge_Qw²$6:;n+ +[ah AMw++|?o/;)ГD{sM21zCV5B7@xqQ@s*ݙ)ϪϨ9_=Dʴ`}81 ؚx7Lu0K(щBTa-\iAJ~wюL6bȂt A:f=܃܆R۩}VOGn0qtbt>{9r'ēBT896A‘^C͹P 2 $ۯhKȝj}R`i'̗ P8_74hƴ BA *~z9oGx{S8G4gQp{3u]B4#Dc44ǂuI g_cŻLx{ІhyjǪZX E^aGۓxR٢؝9gNL֎\!X8Nh=dE3|'DZΠo"@ ɠhܭ5EFP8utgH4,z͎3n)Ѽ;XwtR3TLm̧3=ͣF~[aDb.=`" 8Iku~JtP,`ԙ6%8:+0u +//&OXRXg}BE?ނޓ31QS"1:*xqv5Dzŷ~=n'zIZ)~!sJsd:N94^D4TGגd@"fUXf HBaGf8  K('*ͻYb*| u!wQQni:vtj?lk{0UzX/0Ghϫywwϓb*t0HL/{% z)xUc!Tt>A6y>e]S8%B +??:<rZKIn+U}.dUZfU~U +NN"TCǷ D9uu](y^ĴdriOk6I"(",lȪXZ8p7#$T>#IBi9E%6Ys*2@u@[ōTQ_ҐېbMdŒ(=SeLv>Y +v*OaM oFy8tfJ˳DeN7zߤ*,[u!i:Q12p'3ؑN̼U$Anuޙ;v[mw({Tμ5Y7&<*!҉')iKj ;D8`q@{Gt-N4g"N/J7!i}FAS1K*B ؂ ,5;|bqv3ЖױtB@DOM\gsGxQ]aŏ5h?Xe +?(K*1ZF|\\raԬTduOP)9"Um̉8Rpn0v݅xRxRX `ʓAXG, LQXl "I=DXb3D# q+'f_Z9&Z^:x3fޑ.LփH浟FDJ+bB<> Α:͇o~|Sm@# ȁa(ρJ}$cG ys b/ +ߺ{FDe6+^ 觔q2Χә$6; jIeE*y"STR3klb@AId|WQϿ9Vd'CljN@ʛAiRtFa<j^3P&x~8M츹OctpJ!@%Զҋ$?5[|EeSE[)+u RmfF\jt7v09Grdʈ@b$ +=voen l!/wDݔk4U{fQM!y_j`'F69=>kktŘyJumfhaW5M: H+u"Uzbϕr7D3 F[H%nb8XPi ^Mrl_ tEtᮺmycF"cƄVWthjcc՝/֔DeDlPu}aF;~~^ 꾛;kk^{o_xiuԊ?#,鬪LؑZb|6.XS0s`xOiWKw7@# +w—  (z[摃x]'~Y#w$7wZ`Z8jX5]q{9o-D4Kۅ* @?ϼ:ݽ,ȆM1ĎlŅ҇ise_R4#-{ gx6q(}n}5T9F1bG`~{R_(ˬ]J9HLm/3-g8LQuV`) „At_(Ĺ''" LP ;N9iP^B6vc/++&X/ RGԎTWQ$P +Qx9 +jz0fH{#>g*DΈIMSiz=-D쯌y'q`5bS(mbr`ǽۏØ5X +nFlD mF˓g"?T4m#h<@L?AgŗD_ HSHjP@5D+:a#ǺFQ+*CY7 +|Yc \@S +"FOFAdH4N[Һ ΒSx-$|) iWjIOц+{;pS@aeKEKw5ޙ<"Za@>#VӐ:t'PHJs^^`#x&P yVqK|9`2^Bv5/"0tԃdv^2AץUb) ` M.G{5 p35{ACP|YFzH)ZTju:BAZ!~VqGQ6N}u#Y>h8l(ey?#gcYe@fJ~v^ l§ѕ}';ܞ{-J AZ%lݏ?aEi`mp;ʢާ .e;j12B;jFGWQwGbL 񜧧_QqAa 9߰NsR?;~3Pm3D0׸z 37 (H ѫ{UY ^;s E-"dkk4:[N$y[(cxxQubOΞxfAs:Edh~΂&0- +&~ې VdbI)O&_s 3Gc#dib>*sh=&]|>Ռ87▏L)ds'XSG$#,*$VN$#ymhg pL +P_p @MYy,@L"ӂn¶VBW ň+:&&(+J- ~hɞ^La f99휄8r}zr%J+(.Qa6r)kd:M|ċA.W9b23r&̝so==}X^V#rE͹ ϦqW+E' Mj{22 +60"30zy$gVV!)[ +n \Hkh;Zy#$)AvVF6t,LlxR|z`Mp__Q5j/fVW^ʒĬ?6h3ث*!} lpT+~mV s4= W J(q25{HD޳/qA0T;;U-AX֜Q&AJ~ YrWt PiPѣ҂tVϛ-˝"R -k5%g'~9MsXs)1#J +Kd\3P /K%=T]:yM큙0;x*צΨ=Q+> >q )7w>Ne.^{DSwٽozQ)WTy5m`D42vH/?N`/9XU< +&qĀ> B4rI=#D$)Рx%1('fTJ ݏXvQXʁX1{'v`as^X0P"ˈZ4~[X{`+indTpr; 9vt!(Fx *N׫\CiR>,8doF"T[]h=2GFw@wbUv`Sݐg^zp[dݣĂ +4S,v}w?B<+9oAɑ֜^Nei9ʈIŧtk?8'os۽#p +,/hO#o픘QI|fđvp齠I&w{!fqlJF" #,AA}QJC'O|BXz]US<>G +,b̓X ԍ%CİnG3` ^rn +3M. 90dAa%vXK۳*,jĥx_Ќ^ ZFbIa\wd՗54yEFy/HQ" +[X{锡("6V J<-G:ۋZ%ѼGAɨDJUFF^ח/ `bx[₳?jTL0q+A}:Ν|;ص˴JO$.%ΖcUPG3uFrh~RN% oʕ2: + ;OTz umh(_I:;$V,(#ZB%ތ"u4g ŭ:rb"@w0x4SXºo >ݳ덨"A= >ɠz]e  '2c#"L|lʈ7r]åpD?-< j,ܝ@z<߾~F L=k  Jt@#[r 2]s +Lf q}{ϷvZD5o0?!jd}n_F=QAܡt$mt\I(XGnQAuY@vܯ>AP_^%owJ< dhp׵ + "pWD2;6 k!d{E](M~٧8k䵂"20Z2@΅g + 욑uu KG:tSJcULBA%b)oQ ϣPZ"A^}*c=豋 eK^Hxv_1vOtfڏ[9s@o5X +>=qx~O<)4T‘ͣѵ.k.C%0W])o#`*E~dޞ<>g +ەϑG"T.ܦ"uR g$َ9D`W=E> _ w{>m깽m Jƹfcid\gh.;J(xOߑ@pI׏Z+2sᄄD6mDS5(\IG7fDX诤N^( S[*84sLkWukƎ/M8!il] QCPhLk##+.ᵂmROhԈyԈϧJ_x/h0%E{밝贿lq#lT 8D?WȈp\35Qt[^Nb"%A)g+)B )y.}*m,{( J#R)]~ VUϒ T/Gp%jpo M:Uhӕ$'vgr)R#-FBP=M!CPp]RĊsݑsӔ_- d3=;_&J1cg/nXj#/&QB^MeܿC26xmtzxx>Ibt\FgMM,ڬU/ RWk" +deɀ +) e݇ީriw^߇y\K=3![F~PT/㩅'F!Av>(`DRꦮ&ˀRd|2p 6=g),UOrveFb~).G=怼7QtShZjM}`G8OEwed|14h^J7؏g_-$sFnh\9ءp]b߼h@ȪjYB ;J;KbR|3A +I|GػB$ EhqW˂C2W +6!8ӪS^֋O _ fYjj5WSu#5P WϫN(R F+K@webJD +Ẁ +,O-IIiiϐ0>IѴ+ZTPy:$AA3*<;t!@>%W@~XakfHIΰ=O9^Юlȗ~<@YTMIgsL?RM2J2kxK@KY 5Y>@pwG̉>=D1"xŮslC5 2*VQϾ -a܍L*YujH-U\`@~J;[P Ci\jM_rΨO}i~j_GX ­1ݑ>Hs=T> 𮒊ǫ A:p:p;#Z"/RVrdHzri_B'״"0<*<5FHK>Wǰ>dZt_#YYp[\JѾspF6412{ UP㸒ɿoijfDu=G:*<ä0Q$0,P 6w}l9+I,GJr9 ើH'& +ݣ7pw|`\o3Ƽa9-Rz,s蒤7> ]#⛷Q=@Quխ8o,s8͙f~¶eVp˶WC !ID}T7>_a =PK;IeC@fS|M~sGK_Qtm_#?txV;tv"wQ 7jWIRwGIכp^^eY\ zF h|/i ,9 OFT\R\ )p5YVo<{!UǷǬlsŶDQ`#$ ѯN݂"ьC_%W'XKbbcOpi8hboݩM5cV@Glqj="ipŮ~.Vu&VOa5tfڡ:J5Ĭ[4C=/edB{%Ō@ $DyT#Q@:|&WeT+#zˇtҾ|{v~= 7;@DR;?D_~Oa͖ +Q붕HCܯ#N@~XZ 9q2K%LuȢB4 C;jx# +R< _t&,g)Q)iɓ:KV=WAG(}Ffr5XqHv9P?V@ɧ3^ tt>Ѳ6ab +W̸-ׂu jpW\g+2T Y82hŗ㪺~|i9ʵLuDMk04>N!ٞG +{1#E} h +PZAnNUj-QA]uxT 앬8|*gSeY(HdNϟA}W4<(W#S.g>|}'&6 ¤ȆxW[;xwh;[=QþSKPd5zcghժQ4fHєŝmE6͈W>n@+˺hB!}ԙ'~Fq/%({p yhj _.EP6Giu4|9JQ=Iq[~Lq.ydhF̪΂=.L⥅j)vT2 @,uƒPo!#R<b&9:b3ځx9$Ui7YNS@tZgTi9JzTlr/bbhJ`͊|V~TDU9bH_)v{aj rI X}Q}q{>xH$EH q{Fey#$!H洵\Ba[7kd(A2fk%1%Vm母 PoI/R}(ϖbr{" +¹~ažEʑD1 Dqe+c~|H@GgK,-!*:ܠ)GL,gp(3+in2>CuANV#eb]C1\֊Hr2 g4ҕ;/$$ao3rH@Mr2p@E#zJ {.غ +o~Rqs+Œ蹊i!aH{W^Ġ "%Rp>=`*}l="#L38ސGcGm̯*7*7f"*5B26K~{G"bƇX2I}ТۨԔؚE&Ō|0]zwY#)~|j/]t} T*ނ0d¨,}ݟZ|L~FJ'j%8sF'G~juϷz +Ja%I,CRMz$BA"Js<ͼ723xo^p'j@Y/U^ !#>!bJ^ +zs6l^rڪ_ofD ۻ:F~WJE3~g"{yN.qK$yoڄWrFݱ\[C;:[W l'Gx*A*6[:v^N(~K ijU.WC2xySA9jt_Xx# ,n=ȥY>_.EFNU +*L}Qo +x@r(%if4#v;p U-ir&LK R Ji7M;_ >4A% q(G / Jԏu3v4 +zؠ>L'nb'p43*! vW}C̥¢Ƴ>=fz'D8d +AcZ|!NŻi,x R)`Lʢv wީ)Bu`JYd xÿ< 0atj(*JȢ] [S qKQOHz,a?~ + +`W[(2%#Xe1+ TPgxyvJ{M'gP:B8+?RH~F2dXQ<FܕxVm10n.bψg ־{mNܾG4 6jR-,  r UG(=0PF̍wl̽0h٫/ňە~Qi xҏZO/;m{;⪠ o´bS'fh?Ax#Dm.u8ϝK +DYvL/\yKVtd餘I;NKbYX YVgbx|DJB!4Ę!t諜Azj<6`;C,zڡJ(Fԙ+^'he +ьY0c;kݹ2;IХP|>4= QƊkF'};n]3d[h<;ȅ| "zţQelBf]ȫ?6~NYi+ouY4[UtZ>Dr.&95g6MXP(kJw{6v3S +ix]EOkU rL"O8ȹhlOyP~Sɞ9C.0e3?$79f8#w)-vL]^DYzSa),u)L3ц9ӷ.F^9"qdS e2J[ph /S$ylM j7 8*ID/r+JFAr![hP"\vTBYatɣtɈVj!` "BRn'xYhr$OX-A"!~RK܏.ٸ{D+X37^Gu=BQ0@PDdpFS8=@Hu=,"B!2Bqgqt1K!a`SȐuRP j{`XO!:L`HHclsg="Fuiy%7e A$S24ϪW.mN]E\[Aɻ1515u]D,SS,DV{\Ifj +լӨrkd,L#g rIZȾFH+?Ԕ .MiG7f$Zr!PX0t@3/]Iw/%.L1= +Q (3v+~ +k 7SݳUuUH)~h-@HaBЇUX87:J!bX^C7E?J eսz)8=#TN S 2gA?hx`F){G!q'>g)/+0ڽF8*OSsTQ +M? ciYA0E!qDd$}D_G"^l>݈D|CvH ;EN?".Vk `۾˺[ohh]'ؓW< +9qA['Vy 8P|LvEyWt߾maV@ k`>AZŇ) עs3B )aR'[ 9&)R$16DɻxЯW2s4#Wn B=/es-lkP0: fVnD(I!0r"pn~_\ j™92E(sE(I"zƖ,5'ݖ` a0 +~Exk0Z;suUƫtV|.n 9Rw69PE^`go-)Axb2! 'WS]fy]qMB5 H#˽49I9Glj>~.\80zl!a3>Lџjުã V溨 !u4jSqQfh)|FqF,z=^ܐ˥({EV3B\q@b)B^?o+<fN/&|3GUa{YH `F@=0Wjgj!m䯋y$miMbe3_Qx)A+ A:_a( m3~bzzN+=Kw;.%4~ 4\ܴ`ft~~};+o9!GDˡigCywJb9?Q]aķ!(d^U"S3>J2_X0)~+QgObG CKɞ)ٴ>ln=I!pUrfRL'-Ix$@F^SxI%tǾFY}eV_|*̕24?H*'yD(qsuvR(%EF -vL'J8l];BEFc13o!Wɵdy(@z,l~]efټ\l5_0Hed5z/A/ʣ:J0(=/}Uv:*W%@K(1͛8DK{9{D]w +fu") GGMA2S~hTP6[/?U;Wsǿ_?w?_O埳/nO}~V7;pIT7aՔ?=Q i} +]}&god~}Ui^uwC~>|{1*uٓ r'mE_IU2MG{wr3:K}#E50gR(|8("XEPp耳aQ!MzQV\WFލW +W^.x+\^M=~QXd>1Ehs"~DW)BaV~ 1pw(gmPgܾ];/ $"p8m4_"9<ʇ y3KI˾4.DN+i_b5\rC'(y&)Op9D=+.YܨX}ˆ+-< _Y$߶61@a 8^=J?X,I,e(HF!VaF)N}Ա:6 zB8 JXhXqrh664 ! Ӕ ^{9#שQS^bLn{_1FIgfOHa7A=d9i)':tuW=}ن/ HHj$Kp.Į@Q#>0h_Q K/ %8ƨ xЛdD%ڥ*A}[TlSxxXRdvyg_Oq ET +uwIT~SQx?`](z?rJQ.G9QbqFoXQz,HGt.U2gܳkoȨs@6>J}೷01sMt& +sSz#G9R,V3c!0Z8l"jע7 +IFm;X=U>C^u",6^/ӽ1X6^Q4\Ռ]JXXv^5^6`ɜׄ/"HyyT>Y~<=ÑI3Ʋ$?X1v\Cq#Ab# ;=v( -QnA͂S2W@lXnR!bf/( ݡv%a'A9P&}5¬r.1^1}ϢgSQB! , (LWH6RƏ.5GPxQBc3D%aTG[E)qGIU)½+l$]nNp9I ?w7}1243VICy[|>\dyhz{YD-  jvh l4 jq%.9#.CT[Þm(x>ν $t|ƈ45Yil-,JrP+fВ;kNN 6طGQwO}S@hX AhrYā)X20-aPxx3YעZ~}}Ar\ucڦ`55)2Bvh"<6hQ)Q>.$ar"1=SmqPp&W " 7|w7J|/wf^٘8n ְҎgfw!y]KqDTgAQ^ &oG*|^bh@kf]* A gZT6.-5Hm%ՐGaRbPpDU>DcJ +|mֲFP*qIMz[ƍlhi k/xWy7E\|\q=>f^!Oc^15|FܝΦ gUG/9m?ǁ H"*t!J S{n Zv KT}|~+EҘOj$K Lt29-Ĥb()6,p;FǑ*3Glj,bb?~l'MЇ P 4~P*8rzx8*U~9 +bla*驸}^n+̵bU_{j" IJ3) rW(AAifIç"2>vʯ*g}"Xfޯg(y~nQO^ȗĮjVXL:וo8P95sQH")tqFlA`4lZаU{-XVIDPZƙ0BA +a%%ت"/NvH0 &8pd={ *K +=CsQmRߞ=~hOn#&~Kþ)ATjEp|80"s/ ȑE4@iCOF/D( W^A@?]Ow1ڱEhn$Ri."I\龢Q1^%Y ۂ\bzd0~i->6PC`t6cJoG V,)>䳻ގ8%¸?$SN@{QRQxskH/sNc1U +U4aif'A9t +0/wg竌z*C'aNba$ndj3Qi k(']4RY{NzQ]WΒf"i)1-ˉ2?fK1BD>2dxO:;ŅIUH|G̢^J7MU%:h١y-~KB>Z +Bqo0nd!>·F +x(n x{JI3MC><6A Uԃ`A3=_]o ~0'k*HV6 wl…㼰iW8B5uYB -j1)c6*0Ib/a]2d5+4ﻘYuNWuGY썒W؝DEЋҕe†4{Zm8W?j a!M@i +G ~,t?By%+xC>*79_=cgf\ pԎ (lx=-T 񼝒ـ$,Gh-62R{ovv:=KRɯxg|PGZWBCkb>S!n8J#V+  id~  N:^'ed +-;3cz]xYo|!E d{P^:,گp¤ }8h Ge+Kc2089=Lnܣ P}߬ZW-sOpS lDF^F-hF "=aYON;`'Fyri>ְ9RߎQ`2Gr.bPZ*+!ԙFz%;«WY6eP__ϙ|ʬWN5KVJ[^n[fW+m| +0@=v{?xڣ|~EXh/fԿ:q/GL'_jso⍵ߙkFy%dŒ&!Wӯ?*QS44ǵw;`N9 1:xo B+(j, P\.6;JT*EZ~Ė2@ 8u~6#PS:xo/H+8\~14Y*!EvBIiP&F[is>x=J{*٫{Bj]0 >*M(#ē'b6e٩ϑ*#WmcRX ?|ҁn57F̣F|>QD̕ NSI{ +9cO"@m>(pQ32*K2x {ۡF4uD^6QwPgyW#[FTMt-n_t3:>==b?yEYORv{T.g+ڙqV=6U]&'eEkELmzy?Ҍ"ZjRR|7OvDqO5v؎o!$j/G_ 8+f#qGZZ6~P_E~8%Y8i_Ι+jc@k{q9lqfsMOK.BOhR[Gqo#m%!Ev[:RJրz_Avآ: ZCAg,gk9WRеw^`N^8?:$KK:iݾn~5r+o# 3AT88uh=:09r6kXRDȽ,{yTC!ϸ~pb"QP,[n-Ml*+K!F;y]TG(Uψ.؀@6X^HMQҟП +=JH2rJЊ=Z2ux̑CfC4c +0}p9Ctț74(ɼ\iJĶRxLwrͬJSI$Pw^QMQR{}LN;aLUJެ <ƌrr4bōFԞ845R=Vz&gn-TZZdJFQ :]b,fD0f}U+Px|Ww)9R 0OyFm\vOaV]֢YϞ~ +vg#2J`Ӣ gE R$;Trxz;VbcV7OXȽlPx-0M]M'(urG e "='1l?3jz2u ^̚>1#I nPr]=njb6S^ɲ@`-R- C[O#z S&13gb@S򈅾nE8壭(იZU,Yi4MU{"ATjeo#iļB(UsQFO{+ٌ(#n:י؋Q'e}?3f+ 4AHդ51ܓy$& X~TE(YJQ<:A)qG Ghiٍ1ÿy7D+3Ȝz i.;%,0`[\F@lBWơؓ5u& <Qip~oMIEsc{_%NwR( `wPUv+yhD¥gV48YV6atPQ D'Ϫ("~+Dg?TX\ND~e,6G`Z/e8;LE}\ϖC&R'Y7b5m%DgV;2UqрVu7j7WY Gq WdB-zzS @k^:{5=ȢZ:s' 4KH&mGE&"d ܺ:#twa=ӫ΁zW,is[Su}zJ36>=lɎ?-tdy<"b'\;H* r('ry)b.TjO0|HP(T8Md;HJ惺 +jB7W)F-W Dޡ_ +s8ө9:/v%m/lJS14Kй=Tblw8Nx;Mb"j-ЉD+'E}~j bw ]Ձ#oNa^oGbCuTeD}{Tz^gЧ}"ӻ$^.@!(&=#%HG)^d+Q E(@WA-!S3^*mMu {F]i we.Fup:"GOEٞ>"G:G7VP/9kYS^3;8o㝂 BVh 9ݮGDCC'ʚ8jnثNq>xj9R; `/[o/F!ԫuǞK&# +x*׫x +_2*w+SW,Je>D0G= jU5iߧBR 0:: T +xa*w׉1{q2ZLڳ26pv0T1Vi㳐jR#T9Uf,v1ozi<2w~bq\n +py{f:SJOm׋#@_/!Y <@G/{帎3]ޡn!u4sf+|!B!KGO"01X_oc~k@hDA%$+W灃x``#$Տ̿~$m#8^ +Xcx +8+EFB-08Ѕ1 Ѯ\ɋh!+JE܂Ze`P^ ^"< zwtJȚ 'F'Nfl# \%P$0nP^ f"^EzA1K`1wT2zi$\T2~J&,lR%g[@Q5rI>ucK9afQdb/a{31DA^.53$Ux,ŀ*[WTBR$ bk*&+Rd@-@w5#+#"(@+e+%cNzH11V[f^X)x`5)Q$qD^` @ .1TFv"N%N"V$ɄAsbNQ#N!BA1/Ӫz&/\+;.P}<{,=d= +Z] +]VuQMHC-Qp B3Hp T +["i1tn8qvtR)AT}d5;L_X4;Kb@8{pW},2^1?s,~4g BВ5xMTjgo΁xy.-.D,q/ַn3~f'l2qgdZ΃ݼN+2pqh^B$M4Wc?"/, _0gϚ_l}I3 _mʼ0g~Y}M`6\I4e/"Gsc] w#!C7.z L˙JD *A$ Up@9%JYM"g{a$daPfg~ % ,R``vbe7{!w`gzgɄܕeGpDq%^p hX]Q0|"U6~H [t ]p6E)o~xPU(B&/Gْ>@AWVA3pySlI1qrA 1  GBWzG?"NGB''iPTq"Lg ǥcߘ91ղ "PG (>Ey%9,FqBO q|pYA +/ 0JIqܔQßEhg.T(vt'L ~^oZB0L}&8y6m82=Пd΅Q(\ŝ P^b`CN/P rX|u4W$r$5N=o(FM!DG[dDpWo$&رp9a<\ADCTV7灠 pTC +(Q 1_婨k8I7LUۅ,8 iᯔQm;Wfh2뮕xZPG)\सT#~1W̆AaFI!w?KƆCJb9b. )C~̚{iPTUOx. +ٛofArX[γ]NGg_)7'a礼nWJDaXNL4y)>SЮLE"jU4#€ZlLlpp׎ˉ"(W9ev$liR +(DNοg ]1[Zn8'|@ gE|lAoApʎrCi<`y\ ", 䂇\1c8:K[ba+{"Ţ!XrC=PX0_53Z`3M٤:XXwG$AA0D)$\Kz9UE4Q05tٸAKÑVoap 8e41Hlm eK|Tzr)أg|YU)^J2`9x#ļr9sz8)2V @,%7|B]eJꓪ&bFAt_XTC^7t5gub %b57d=FV6G ENRKITU,1}HVg7pV\Af)Bqa)3*)> +_Sb jŋ\,*5]yJːS N ii;"Y6,\ tX.Q tHjiwUGiNpPF{^q`T7KSrG@/4u iN`/Eؤ.MI6IRf L9CPp6h%S<>^K}Ex.(&¥PWJ QĎ W -5xH +! +Q xs Sd#b%V욒n)Q6 eiZ#'Vt6C$)QR +~ 'TE[UᔸBٜ_y:'oA_N\]/^Wg/Ջ/8~0^}}8gWӁ1sd.??|/2vf|;?8Yǧ/_Ϯs#9;>zmr\zkxc>Փ=-l\m@j$EYYiȯԕvBjݴ/:,rӯdwD7T2^p"jmu/VmewMfS4[5x w}r +-WWf=%*}߷@t xnkpLnՆ`sMўͦ0@Bp +2 x^=\aJ 8Q=G3j8jӤ& +%Xcƍ0ůMN.cLqͰQ+f=*+́|ژF6apfIJ*#P3ˈBt+5Nx{6_E} m?ի+پC$6OXz1 ۠j0;fK3W28RSo1.Q֟YjovWPrp&9!DGm䚫#~L986isF n} 䣣 Efk=]<`9fN~P[΂}+d4֩A](Ŗ?Q&3뇊k K +fț\q%7rub#T:o9d7gVNl1*[G0a0 KctíXXA^c][CѮ'۠75CYq'dɐz0NW}VX،,6֍Ċ(b O}+cpg FbJ+Qa4Hޗ6i]þa( +@iM ih#uH5$xȰ=ps /}n<8$}z[΃h`iP c}i˭#" !efXFd)6j_N+g?^:BCn8(iKm\eJ7&={= z6Z`Ilg8u ގ.w,6؋zcrF74BGNѠo#{ 7jߔ5e.Fȹp$Vcz԰u^- gUg`4V?WBѮT&72IolR30W#5Nx=ί8oJSs] +`UyP] +D`cf4բZޜ&eV ~+ʕH;qXn8mD@Z.BvF}I|2ňP헺2R6l%fIdHpnp n,%Qdj}9U7"e6P?(hwU&3RZXs'x֊[/H/QV7 NJ3nz)RZ7/{Νʝ2m'k撝d2B} FVtZȭ0lc!G[ƹ4,\"qm 9hx +pdRNu.)wp6(<\ѥ7ΠdQL(&fMb6F\7:H8۰{P Qt-l5r㚪b!\ˍq6aN-C*Ƙк(7?Uy 9C k4xr@q]~3 'PoO?4C1LpN6Ld?3(y\֖MP4%]pJ,2Qg5bt9cWP;&gDKIc1t  O \E(ЗlPVh 3.) +ͤj D5vk8nV3OO sNY/#k3吒rt<۽]1Y3Do(m#l,&bM5oPT 1`{DL>qk64H sv>em ::godoi6MhU,0 +qͪLDK P3 +EɩUmŸANٕ9izͼ-W#aI hk풚ЌF0'vb84Pі<[9/0( .hX5ҘaYÍŘl{:ic4F_ZHch#~UW90*?FqmfDZL VN|e/g K!Z[UbXRh'ΐ:&l6sl6 0,U5+"tjJ3s<4"I6"c4VD!ɸBc]a ƛgV8ZmpSGi9*h$KX:4gvjV!OH\mZ6sWiS'7 %kCU2\ȘeR_"67)W USNQ5X.nrSk#R&܈icPЫ1Sr@c-VɈ!TܿbVDIRDUqrnM9V +tK&ccb>[;%w--RjKnq录CiEjO&=Y0``Ү<ٺZG%()Hdp3Y2o5&ht-ZU9郶 BZ_"DFYU"VSk(%NkU !MꥯaWOm5BTMDUPeሳ + Sdv +|%;38N18ڸќj.tV&_PV!)VF7be)|f;Y:Ma#]/,+?ئ]_uFu4-&.v%*7":zyfgX’0Th.&TPw D9h8 +qff5})@3p D_GY߮pHp4=URhCKghhf3rȳXlUKԙ!V3#6w&]>stream +mW 磍\L{XԵ +H6;Ŀ:k!ylV_bhQ8z`fU4KjW罡3g'娢PS;uE:UZ{$6 kP$H6F[͵s4}Q`@LE!8s4g  +G臭c=Hw-d)g[I~Q F"]=vHU!aNZ+sQۖ<P|8NǥGGo"h1Ĥ](1$FX<=GF}c3!Ee2D%+Nf]k0&/`O^A#>I`*m(%*Aw1ݷQj)=*cڄp~ݔh )Л1 b֠W+`f9ndjх-X_] +ɛY$dj +YT.#j] Y]sbw_A0;PFGDdr}Lv FTP0'[9cW1Td-u9E؍C3lT "g6|-b/q+;ۍja7@$? ;A2Ȫ}dfo"3 U`A4ؠa$ż粱6IQIƛ]~hU"m +TЫS&tķR?jvF~!]P|*5%so4z7e 0C^$D6 \i@3_@ eq` `K\MXsgHc I#bZ ˿,>QuXb$r)]iFX.y`Mp]EaQԏơ  -A[ٰ:/d6S8dk 8B_ W@ bpF'\'BIm槦}(}=M$fqAC_6D}k0|3h&Q]K=Zw(ucrWUS(Ymc`_xU.9"-p39 \&șVv]GM glkىD$6aGצmfѐlswO֠ `q9r[{y6@͐mUelVa6!ӐE4Z6R3Q˸Q5D6&Q$ q]|6ؔi$TA~4KA2{[_see" GQ$k W>@ay4-_R_AclA.jP3_hC1u0*d+׆`:IǾU<ùN=0j7fQulXz̾5GjR׽$tel,X#JRC3$Dtb` #] + P4yU;"v:Lb eAl.WMJǽ+f]`'@lصʅX2%TC]~&l{Fm4)l,6U`.~{t7I&d8N?mHv +X`6LeRZb z49D oc { + %2R B G5"kǥ)JK_T$Gj3NѼIġ;G᚞-Pbw^[FGG0u0eBl:(ڒg]zokk,.;/{#'owT% -^b\//Ī{cALej +8H귤((1E_=5}@V'GoFIe8IJq#_ɎF 17]!pĂ\fkCOe *J߮KP8-3h= LA;-*ho՛ښrTd)]FˮO7*8k8)'*9p9.;py+n7 zFc\㺿KJٶVU'!)SEqnazF?38du=t G{$vq[F; gl=/:$h }jV=z& edq@y,:E6W33)1]R{K.G uxtAϣy$$fl~Z[[1 +,L/,$I-rXL&6dDwAěqm=mIG84ZTPZDu0c$u-(j%gsi(6bͣ; rbGmɾKw'33{I֠V8HX$={s=AmHXb'n ,1I`FI_#r .b{DpVWFB,I/9xs`l|HS.Ex1v/(Jl560*V`i8\O]4 (8stDIҘ4kۏhhx,?+X't[CQhk, aHM4d4q pR^MnUƞ*tyF!helֵZxK`6N^g(ުO[ 'ؐ(ekј'ŀ$\xcQ,ZFjiTD]9fLxl`'#:4w.UJS,+P|^O)gNs|F(b6.qgS)ʨcVv=Ƥ,.n6nS%HS׉8 +z$)"'A:N2d` Z8JD2nꩋ9M$;E)HΓp+yI2qYsk쁊e=aClToBvu4rvcXkw^LiTGզd`%r}c" qQ`aN_v!)IL4INMoN&YV#()sF7SQsihVsJi͵4 (n{)J8 X|G]ROQ85L'kfQ. Y"2&@#(нM K&[jȖX^w%vZZTQ:.iF]P.l9ip<ɕؓ'bQ)0Sk _ER^.p*<&JҺoL>У&bBj,$-,f+=ON &s^icgУV'nkRj K==GT($R𰺣DO _;t:mM\o ,l-MvzU:w#Q&=8 I)HߒkO4 ШwSzKa] Y0KL0Z;sQs²ͺOkDh$};^A&k Fqchf̗ͩ.h"^IaA5J:ks +IR9 $MiG\!7þ^@5OP mE?&.f _f{; +7k  Y`{P9Vvs3 TgnGQVuh_B=hԕ5sL85r`i\ +6<9[#UY=Ks, +Wlj ,gjl77[K*;FBHUg6`itOsYOXvMbZB*TD}뾨7 +uf.%.Kplp|gpڜ7}(b~䪹}HN17L/mM-ǘ2`b̨QY(`X`EEJtbж2t&e} rYI73P6 2.yQ{Pɜ5jѠˆ0x`1xkM2Afcr}?itJGFCj08k3ɂbZ'?BU]ȶYEm[UBMw_F!%sK_spՑ%M~:*i5j` 4(f:VИ hF7d6yTlS N;W9RY 4NGg.p)[WMF u6tj%h|P2fUЉy$vf3̥{XͥIgOn8]mjK@a{R ,!Zg1vnc,T0` 0Y7aŪ4d(bl;ړ\z6f7 }/#6Mp9rМiG,yGj3E17/M2mUfjs@"FYh˶E[+@S9#&_s-lwiio6-ZpkP= {~ =!m4L ˋ_6|ØrTҝZkwFn4R3W5` +~8~֜BX{Z jng^+$L6/p)r%; 3. O_lK1.ܳU&} F go7AP޷ =>Yg0Iس@w[6& b[vgW\5)M,gAƁlgmփ`M{ff^]hV.[4novSrhemi,UUiRj,kLnoz,BNOn̗#d CɥZ0Xs{:@f~eJOCKO^=qyb^m}b'+$q3fZ𢕷 fҖir`uN,dDgKxcؽؓXY,{C:0 Ճ/њ+c ֦^vZzV>KZ{Y46/JѨr.*=Gb_7>ecr; \cwmL.ۊA}]+Z%}\jh g2 *}n'`amn2ޘs:isFr\Ǡ3 {yFtv@fh67G51*z9 &+"u9ҹQ}Uxjx,[E&H|YbObƍUC4ĩ6ƊQ { _&ys1hXύfqؚȶ-ty9PIܘE ۡ1I<-]SCQ6yrcY˨y5;#Z6-*F5/գnw_%AV5A[sLV1v!%=Iz jZeX;8 Xғ&Q\حAkwY I+3ի oJ[S_>/;V~תhR]uF˅7wÍ:u2a_Tdp9bըp7Z:TsZp,H, %yHxupo?*hźԂ܀s3 +;q.?MA+$U!F1K߹wt`@6V ;Í"SCKHhPsYfD/BxXB-t$tl7EwE=B/Lm[1ve$mǀ:!ܶ_ku9Q@2(!TI~HLu.H H`i ء ` n)/j]uZ꽳{!ˊj>W0e$uD|Zv//2",MK#;Rl.RAZ%Y@;se ԥF?̲b$'ኞZˊYvp#0/{ʊN'wΜ/'vүӤE;adniav>?e?~~|xu|~vp|:o'gM?|O.gxgj|~y3dz>^8urpjWW^g^y0=8q.Ѓ&> |߭_q_JJV+ګޏzE?Z{Iטc*/O/ݎa :|Lhe-pɛkcw|2s&:,_Ϳ<<>8{~'L3~?V;Zx}B֧]Gxzu96koOk >Č|}(~phKk/Ãj_wC,`f׆?:듃+ׇ~}Wޜa㋛$PMI_{W!>kyʎtSXoΏϮ#wzc:=V6}q9o+xy}B))<X{?''d=MVӳ"R[V|4؊wE[ k#Vm6Ǘ[p+>sʀ[p+>rg/{hsxqpt|>c Eo}u~k[&~˿em߶mG- PܜunF ena/._/m~|l~>6=lt-]>}*i{~~2l3TﳒG0=Vpfl[\Pk͵>"mn-+A|⛇65c#MOgtv4lBu^SCZԦܩ?7J;9Cg 9q罧~ ܯ0;p#Xf|kǯ^]_]c_.(S}yDh=b־?>kge~ ғ,:8mrO=mm];W Z֓<[Ah6 U?uZIZj79W/5"Vi<VƵ!5dVnw}цT>:>9X?& EO!mJ"w)n[v=9'wE@]_޳ gͦ\M}k;mZ>^/f;ezreF69D|xtR=\lHͽC 6>8;|7?G#/1]#G_998Ϯ:xy4iLM2[-n3|0N|meQc'bkͦptzM*2H{ܞǎ'\v=?D{.Owa^pM9s.3z d}?][ғ%GN:_I}5\/{|xx{Tuv~>8<>hz&|{|WHEG&SfxVm汊BͦDn3mV(% +V1?|r3菝6mg}Z Ǚ{ףw9|r3Omg3[Ǚrq[Ǚğ=Pc'b[ǙDŽ38st( (7h=ҶI06,<>L4B,{ȵ7mo73?1w㳣;0Oe7:yMOzm]?/C)'oz7ϭb~z~zzܧ~D3)L~J֮ƕ"8ۇR/em^O㴟>J[[K.=a!.$n +)R'~.onuStk-qo5@o [5@cO7A5@x40A{[oekto6|OwJ΃/] +g<]{7_{FW/{gϝܗ{xbm,}񊄴K8ާF߫>'~06MF|lp[=Vý]NjSb>ӿ̯>}z~qpv}{hb]VL+fk/M `ELW^b.a'yҺ7C"ۘGX?Ϳ轕/>櫦oo}U/n97VUl9[eVUl9@ejvXUI6Ֆ>&KF}rI8L78i^ZV_Ӏv顧`T^lvڅn~Y߁a/dr +t1?׍LͧP7P7$VloŸDĸW̥߭yBg%3tqX{%?T{?+z=cU|X o ɍm> *@ +d*@~CUl IrEӶbwu|\rOKk;[]~}xu{,m{M/Cx6Gik.о:\[)s{gMYǜ.Ogi/gi}s0݃oS0uy0Ӱv}m6m6٤>>9ދ.닗'7Vٸ!x16D1j[EV;E/~>x9?>E[mxrhXo1v<čWke޻1[)p+>+5rlV|4ʁwE[9 ȁkg<9pmT}ٶV +[AnxQєg\I4?G郝?1<ӗ';^}3>??;?_ٰ[f_{f?ٿ0;»4{ii3~.eϿ4j9U?}Vk`xtP y4xN>%/;{AjzAŽ$8C⋫EZ-˫ Fca9ū /^"ݤw4wH("te*X@'N: 7zjXJO!/)D +,<'WNB隮L0[0qW!_ ?:Vǿ< ~ n+~(UHKt}A=*/:TFyн4C;ѫGo@ry X= fAAeX9xJe_fO'ʿ𛍂o{_庌#"pow!VvWoGjou_"`?1˳.3=X`x3f ֦@22o. Vηl^joaml].TJy,My-7 &!5;8:2Wv"ϻtPoM^|6kCLDn5WǴw{2yoa@J.bKX;d/ዘjmŒfŸ[brr!} ݌ 9X2-]:D:W] *'>v~x~qDfZo~7Aj?~ũ4ROpvc i˨ z$LZNœM |`udDih8`ƐR =Al# =h$23TC ++˝|DDX4,@78υ>4y)/%id0&2ꁨ ]\k DnQOr{\Þ{Wn>RŮR,Xx<~L ] G"#Fd +W0!mO0Rk.P NP苼6 NY%`16Yly<~z<$9LZA>!da"c9ZdNQ Ϫ#8 +DrmI٭yBB?k9ɧ%Нpty +{}I1aER8=ƛgtH yn6@Ǥ-dnlusx}]#Ӻq2-:|B}h$%AxWm:a Y6k՟|g;?@#B&&ޔByՉOeepg0AQNC3FbL' i>hz<aocs69}ofD cK%Cw[Clm4j0}|hCeP4 (7(y9fl(,mqt%ڃ84W E ++4 +P !( 1ppkOXDd̄AWq|P1LOD:ͮU.Tt^ +ULG`2Mp# +P&ذ +;[5$HW IIr]7e&TB3$p\#n5Q鶔3m5 N "#.`%N>\")[cL8X eԉHb"W\ؐ c5*UUdèf_._NϏ/jnp1+`2܃߄|-3s._1`|4]JX8 + +L ēiۈ +Ԝ@Yz76P`SIS[l~ (@[K&d6'4&hXs y('P=gb"P!&D`3`x:8Is@IUENx EBcM&Z` 2]ۻ(tH 7oBDa҇ ĝ3 K! {@ĻA "cF&tDzC"xz(M~y4dڂPk&#$2:in€ PCQBV%3ܱ~#BgK 1;Cˁ9L\拰X>n&:p@)2!N*{4Wb, Ky2Xg209/bWvh'9q=D&P6X|J8lַ +=DBt4gbY]VW\U >l ɧ(Dd®$J`F\oȈ \ PКY'i!POnlaxe<($>[Ee 2~h06+0<((i$W80Ӈ0[#!;wu՛uճ^1w'<O>1ѹ.ү|طד_f> +X_qfN^8{2?#޾采e~4~M(9.xOS'g|ɜ?~IwϿJjOϮjI.Ljqn̄G{ '3t#LFA{5݆ZpZ'/$=Y\ Jt5&{0ܶ26yb>;3Y9D'{jir>{}p|6`K&څ7<\ừß;LJr]]4YAwON':=x6M^u8ُ2ۧ }ūWk'__ECv94͛}CrpqП̉T3o;OxqoY#pE߿eoY:"@ﯴ]Ͼ>Mĸo ksu_1=u|Bk䋿1~ ƵȖ(m{ J(}?8)[R%E[R%EDñ%E R? IMnb߿'_|kxc)gKjٿs0 TnJCٛ{wGMg<8nFVs +a>v{HOÐ*|~8xpZsvgg_ٳYbenQ;#wݠ~45~_KZ")_vYFsTJlLU_lo%-+b*<`6c7ԗ\6,^Ht~_Wٿ{|~D9u$ØM[LŴ+1of߽>8UQX Z JŰ_Rٽ7>;Z.~Z@o/nar7s-~o }kw|cmj ]XtrO!q5YZ}qv4zZɜ.'𛗿>o./t+g1߹پo/ykU`yw[,ѕdZ)5$@I^+D!C^)Y F^ ]`o9?y$\s٨לt{B~Qa HNo/z57DRq"Bibgjͥ]Pu!h= HUpf`j:l]h:-lN\S"P# P־*|㈁z2TtZ;#$1EGf fr ، fXR]Rk5Z]@nAA8CH6 Ds.sFܒk uyã % iCRB*r:̉w %scƴ1fr`2'Ƈx[E\qMD;dh'O ʸ H @_XyCLw <@DzPW%a0j1v~pPC#9=o X !hҹQ&KlJXϦJ&YP(BU~HRF1%2!>p]ΫMvmd@ 0X&aKon&Db7~ @xp2X/Y l l*a +@r"n[:[TvX0 gWsEr`Fʙ|BCijFCgQ#`>#M2"%j4>^m" +H_$=`FϨ*»x ++YY;`eqOM*jK˜ [pr$ #1Ո)疈D΢[㗈B[Ŵ! AbB8SΆVGCub)rWl:1H7(M `V1dyaW,gf@ͦejȥJ_N7$, aAIbF:Xnt%DO0 XفiZ̰V?9Qnao͓T05='Iw*R tn%CĈ甡e~4P CdƆomH+1C +_-9Y1mefY S|D8p謵 fN`\m zR1 +˜Mt+e +XTFڀjE"rbF"Rd]f1ēO8oFz[.2R2'lnq(c?L3-r}˫w1߆d~1ǧ;GXQnwS w(G:8aGoP.2E $]OP2a),]8=WG/?5䍢kKw!CLcF8fdهa7eJu]~]@I*ean'IF'" $(NA$Ӳnyg)7LcUMA+=(~m `[:LT auk]X8N&BWEF@#t7, Zm]Rz/X@yl ㄁uUr јGe"ˠve"К ] 2O֖jIP{΢ ,c5Nck`KؼG@\1g*oy#$B%XiDVF$RvB Q':"#m3\-:+Ϭk9QgL|(1zMuēKqV9f ,),j +3vbiyĻVb9S7 I'@8zfiKYZ-:"C1; t3wD1^v75fOw쀿n?Rkc#%(p1"$v} H;ٚ4$G=MZAs627&~ ښt?1;#iͦt +8TV97?[|,OmE8ysЛUKP#yMgYo Z7gް5kf?<H`YFt%ERxvjʤ+O3e.Lg/J DY^z{3# =gޮǣYa4MNGkŕ< cBw0(2Jkxl  IߣaU-ۖD>[Z% +tl,%V8jar>'#,Pv77HC㒖}u-3U~f؛q{y+B WZr_Q%3 ?2PjH,4iNs2aNGQA|6ں":Fa;Y"Ve/f"ΐ>6-f$紜S*1I2kNfQs= )=f:"-.p{jk~3LꆜfrҸbSl"MSIfz4SlNM0 M"G@_РQP]G$W]?On h;%ui=hf8v@5x]@xZ⍰)w0F,x 3Z|Нu=JbJɹ}Ud +a 뙶zMzY=7aVxҝv'tFt'c\ғT7t| #ֶ-e3@-uفV5'K1fDn]uAEUUR71x,dtauwPmΠ qqJJAӜUŨ*|0)%SIc F2\uTBH@Q{gZD'ivZ 1qyz͚`Jy6*e?SuSF -ht! l`%Ű/.lҠh7-r1ȳF{)x*=w(UFԈUdZ{ 1ze4)!-"xAiwcerhm1!'q(4ɴN3L>>V xe+ٺ7Q-4d(W bџ^ǘBXh6ltqUBA/\i $VԬ[1}צ/l\As/4JE1ނ0Fhbqlzn0M~b̴J7ETЈ<&#nU 1 m&c$/2iO)f % Gu?XŒ޸#wƆc R-j>ѼE[Ms0뤋vG5W. ~CV8-ՃK /%6seN/,f1ƗDX3.3#t6uD;w\1"JP0x"5s wwfݼ3pA;# O?,8v$Ø=FLMê=o3 4 AqKvh8|L\ܱm5wf4Gʁ$c'-iNFkäzSg4ҡ46jV FmGӘ]H!"Jd|ҎNctbtѬf^ڜZP]M)=£cD70QQbKD;{9mhCڶПŶW$Z'%CmP͛jUk"j67HT"6՜L]F9TU69Iʎl(^%KqNFM4)4CN|jL87쑃lg :fOIeY̊֝9t&=qn`2uOVضCɨjJ7¢h24v=s3Y3БIChمQ ZEeeL.J Jg0|%v?jnƢC0S[#^a%3Y׭nj21_ԌKn5$ƎY#>IbLIPScNJB4 s˄0st>&3vFLj$Wғ% +Ҿ9lGA!4)v\*!!|d ky%פi8}6l%Z=sfD^jc̽lcmwceDoD6qM]o[If4ݐ_$dt&'jbȰ^wTgDe:BoVٺDdj["ZIv6Gv5VI :ϕ["?@ H7DUo5K74~ǶE5A9hw ???w0}v^BbWVWɡ63y 0oOzc{k OGm:΍pacdU^q-tWr[ec5:bQd:sOޏ~P`"@tdrpOΙ)KO$/i8W倛 +ͱ6뺘6q^ Ѯ zrZܑ;r:_?b1hQMBRe$s47Fc7YPLiGfvPk5%kj*]:ee˚8]cLZ? ]A7)J*ܜ:oΛ`ۉ8b%^.J4!d~Ukny%_0"_)?Asؙ17G;8zѳ[јęT.תXBMCVQ5$T0Kj-@}6Zǻ}^ Dm*윁i8  O@ViFzB1 gW=.]!MӏϾ]IRjt+W AmV&]/se+7QMy9}u39Wnj1Lь,ŋ >B SyUGtt#__axjyq[xz&NK'9[U.^فhdzbHlgQ"Ƀ]biݡy9aΣzOH_5c]ZwٛJ;Jןɼ-~F)q;BtZjGJP{^<~Q%irnbBNTծ +_O^nOYl)LzߟZq2gމ[0?q~G|:]oAibQr8΋sxK9_ȋ~x۹]~R"I5*$K^O:ُk?F~-bۗlnOaO67i~V`'v1&KuҽB@4O$Cݗ-ܕ"k43Ea`_i,Į:E̓78H#6jmsS8S>>O"a;9v™Li HP;g/.C4Go oF np#^ͥwP-er,w>5v^vD?+$jfwrsQxؤ*Oèq]z"1`8'ΐOKJ&z. GWw=>N"Efv+ +sg"JЛG WQw{? WA‡''2 O}Pe]@}|q:6R LrKSNfܷtx|`.։dRogjx|ꃓP3<BBq_~ňo^T:v`?ʬh ˓G65] Bgy᪪|<bbCj'{TJQsuox 9%]<9L߾ ϑS& AL0r9| s'-s +S4nqG<+|= O.~xd™{T1Bbz.#ݥtG y낪ZcP\QR`x[+y8_smPp~agpV5J|P:0ot : ̙.{Ex +Btx'!J2i̒?cI>pc +))B~Bs&fp.Ϯo ˑYqhzT8.;.F| =hwWGu[oo ҫ"/hdz7U3%Y!V٧P2&-hA}s{R(2 jKlOnXnFVsS4ve3D0MYKU//!Y*/xQ+OKƟ1zA60Py"CLS@14x*6!}H <UZ4[1AίN4>T#N,IɂWps , χ 4uیH#X <=-KAK:\N^AǩOK}JޯiE{]~ٻ*]fe_ZhdO_f:*@,%2D{hj ?twl@^rb=Q=|X;;?˹|=ſ2)_ XrouoTeZͶ x} H }~-x:y.ڱ:u.g'oN2ߋ\w +9 + juUF4cMz{-%YD=b0u⫼$S5g bvd0Kem%vu萺 #<YڷƟhGzSSޏl+~Ԭ&<助۸Q*)$.|uk! N~"ͤW| {//@o QmҤ]zB`fKvh-A~ʺnڱ_ZG))+vqg_|um2MڈHKD%}frR:ot #._뒓Cd丐F@PZCHYLy>\W1s^Mba-ݝt1]5_1~FI2Tf8aRl@5Z]y'\z`WŽkgT'كj!"2g> B -;W.>Ơ{xܹO2Y*vp`#"{S+t`;=ܜ ?/Ro4x]/-c==cNvݳ4c~ )"w.!ȏ#  "\7FGr +O_I %!d/,G?k1pZ/rWA+.#d(n+fJw"qi#PrbKnJq/mWBa ׸=g2"hdzT}QvgVQw)`ɴIk@LU%~ O *߸}'y&nzwB^<9dT"qV,_H剪FԅVЂ0t%7_ arHz|_) \Eә>oA z| V=F; o"PNSك~(n}hf{^E@'UArlZqM<ݝ :\85(bSG%9LgO_ nM(]q3tncg#P6?ҍr2)H(H"RpԊ'YMP D?>YVxx8ϑKqIUJ}ih0Cj^8OG>DC?xWWr\ﵐtio|ҹѮGc|Xd 4.Ad^g{]usOHL۩~d`@yer}%ۥCiy7.K>5,Tuok&O?Yؙgosi|;ldcw`ˍKx=;?Ib\Ԟo@5eդBw@Z?S +-F>[< hek3Zt_<~PP`N~pq)QMĐTW!ldHsqJ$H y%QC~lh\*ހpk<|N "q]!1w ?  N"w[fzr<엛{_ޫ܎)Lq)Mu o!DN14 +_}8{ xȥY抢_yMt?ҭכ0?f~ޞ2 AT &;&m͝YȨ~ժ`tP-H[ ̰/Bn.͡|&}j;=pROyȦP/ÙĻ>mũx4 +J~';LʃKy5fG\x!&Am<>o݂7d[$[KM3]+܌uR|-kNH^ٵUV{[i4,}n~Qw\}A,ކ9 ~}wS>I5~rA*sQ)^-&ջӯ_ض|Z맭}bvUx;YݫLŧߔݛpOvN?nZ'C碖+˧wVk>)f*Z>]OLR}@~9j6tr?a2󵇭{޽Rյ|zmy`iJCӁ}prz$=-RYmWB997"W@uOIt,巄zIǘ.Fo$w7R*;)ћ#$|ͫ&ی"*sstXfË'?D60HD49C{?պ>|;a+i4Ǣ:u;T$ұ@`d{xD +F6cڱH)Ҏ)w 1m,AQ_Aȋ޿2@4@W-ZK@ + x*Rn/HS)zH=>z!-BEC>EC7ݔ|&"Xxi:jeߢ%Z?wlNysNҖ A\i4|")A㽝tD: )XG$q`@ Tu"<Aq5KUU>SI"# qeo F|[)3;WKE;#)]Сd~5#PɸAG C$NSdžNi6Fɣҁ-_y17k9>O!c9]o;ߎ2cf#DƆXoDS,\-AyܰLR"Uy]tQ)S'tXKz絤W=8nx%eDcN0-)E?9^Km%$g:処bdpmԉԕڞ`ڑb0*ӌ{ۓfAhi5UP=}b"c믒>y CY[QP '90-03PUТ5n^V6sCGny +k)4cT3NvBL^ yR钦_d,i2jY;/xaSF|)T0z|ɅϨ6oifiN&J:م3T]eh'c?>/G7Xl)CX3x«K2h?4g>O/+d5ݳg;z8%<s0oIzY@sCZ_Vb_GaKpr[.8k3k_u fV1ʷ"%<^k/RY7yprE8JE&U(N^vOMV3A?."Y T7P O=>g1tan[z‰!WԸ_3ji?Ѧ} f!)ajU2F\AB`k4E*+ h4535BBFC(믽86A|@Yʼn{|fހݎ"hҡE-!Yȍo.k\zf0,.d=>K50ߒXZ65Yig8\{K($pAXԆwg8Ewelj]!]ǿ0isjvw6]XZû{,[߿5Z1SZV k(pu+FfP[kho y]lq`_7 >,V6O0,}͔hIqvNڐ`J_&mچb]y&UDss%6m&sb;KA80bT]ŕ. Xnϳ޹fCwt!\v0˵%vC=cwkMdHbr.DɷBl7ߊ:#KȢ,8Fj7:|Fn_Kh}źYxr5tZ+XAgZξ Y"%tՂKWiשy}kWר\ξT nξT[v ݂*b: +:96$T]f޶xJ f]27nK 1ʛaF$96T[NW4nXN!kEpS :%Qk|va9Uι!s(/_K+.X*skJ ޞ +fKmnY>ml4&'zMr du3bղ>Jr*vO4ڲdD:!%C5R0,zqŦ:U>YRA/JZo\ĔԾ:tXfkي?:sJcC޽14ŠUxJ:0Guiu|-]ʺ4ͺޕ^?ڱT֬DzEЩ3綎/9qct +щaU 0gwO1}uM9V>NUzJ.wBw1RD^.c*C܂ֱZcʻ?}\zǺθhe:}9d4-m֋G;JNk FsXX|fc@T۫nь`*Ro.H]V6[nR~H}LE*DE*֋_P6Qpm>4uMF4z1_[ pzlӥpd?펞M™KV[)f/WQ +=-p&Y{s @{~]ӗ^U7'B!,r A%\e]$vK0 /Fpcw:/r3bq.n6=[c4rv̚tf9A_MgVKju5Y.|:t:H2kzZtfNYnEd5:b+7VMgVK7SMgVKrotfvUә.n̹L5Y-ikT-vkSZ96]Mg6&֬3rSxj:+rt)N5qM|Ct+Qlj:UәVo `p5jaԃ>zD5Ȇ&̈z㛪sL5Y-}|Q5/Mַx-^RieӯύY.pgY{4 J%NHtt,\@٪S.U.n/L%tjkdRe VL'SCDg۲.[;9W.]U]V5wV\ +\mߟ^t\UnnR1Y;ɇnk7-k8='}Emc[5^vt˟d{;X~aXE kqwӭVXk|L?Srι"pVЛ gD sRb1G8ֵ"&1亼e͊ " b8L|2&7ELWc$bnʘtȝy,cJ\UۆTnMphtW+*bAw'X{wS aq-ĬOe閺{TdmC5GS-ôK]pS^|W|*0\T=;׸B\W=ոJ `Ovz(B>~rW\{|ţupSTƟ.v5c铮JTWMwn.*=lӃE=\кo2Ozl(ãBVήp|*{D +c.Y˱RKVTR |`4HiS#R lsNW4i[;@⻚5VĽ H+;fHɽoɪ҈ڭ5R馬hwyD$aCq[c e;m}%J\@ Gj&aTklxlĊsm -f']f}h,lr̐X$-nru +nJ $tnfqI*oY2[ 'OQCS?ƺoY.-&R<>NY牗hK}ZRU9/^LY-.v6uxCCHe(V-- yVt[1W56]T\'/SP{UnkIz*@D}UD^^okt[+UZtiP^)gqT_Fu.ӌoo!\t#f.kF/L_ʷx?q)ț\ʧ*`xNI$r* oN6q6{\ ~ml({ۧ~t6ɽ~Y!H|{̙lsMg3D\yS%׆y|K%t-!w~, l*#ڝj0+eMς^^?555tb6p}ZxluMwApY*zǷyMgq gwcE[3PkgCJ̮S(^?Ǻ׍gkgZSֽ~j/ιZ^?{vڴn>=9\'>Sϲ =8.{''l^?+~Z w^^ItC{כVOOmc}|mҰo}|nNoXܫU|Ɗ%n+ֿO)Wt{ [^?[>Io\{\Խn^?[o*\\obxͭ~,k뷘Jguٲo2Xd)|ɼhn?~-["\^?UqAý~d{C1z]^?X"}~[1'YvD$+a־O; 86յi~qXAh&G;1a7;,|zU NcěWUs}}5g@c.ۍZ?=8u773ّ TxqpčiIImgq}덅_^6|T9_S U&T`2MOw^4zP*-x.Srw|2}eszQKo\rr$'9}\~bF,B lZT4c#;{ 9t$=CȘ;jCR͕ xbw]4EsjU34Pkft+>N4y:x#ŶӻB4ţp+v V>3@2>f UgݽKUİh7"ÄDNT|o3Q11PDW!D~Aah?X P/sU*\@~Jog (vBaX[8G?|5-L QԷoaGW^cmQT2NS[\gVUcYc=>Ȇ V B}wR`\y8# MawS?3O,3;;4 b?T]x`/9ߔJֵܷ[EÂy~pBa uHҖ@oE$YT bVQa)F’TbBDeu/J-(-i5:ǔ,asHa2H˚1MK,QHiAyRV T-UU<9Q\eWJiBaI͈'pҨFE;N% *V \ڙ+ lU+}܌5߁P%9N%FesKXnۆfVw"Ez%pa'U Xe%nog*T7,N&օh].TTw;7)zbGRHsPzޏ{O4hbgHo;1Z̮o3wTKS|QDr6J6>xПW]P.Ԕ=CFv)^HUW1^62"R8i/1'v nδCd(Q,wp'/S H ^O(_DZ!sƻ"Unm7c%[a-7ZgۋÈi"AφK )=>\ ^ĕѹm1 02~PL:æVеԖxUzϽ~rXp]xl%<\B<D (_s6"cKW}{܍)) F܍ěAL;qʒa >0=oSEn1F,z _n&;b^L7ZDŽ%QSkMfŋt=#()D÷HUR>j5,}MW9jT^$7,ǔjӵ{%(%Lr﨡* b,4jq{2<{@MK1%LX,a-K{.fn~ Su`,heK{}K&hKݽ7x<}* + otx4vaWG:"Mშ@hr"pJ"LQ7*S\@ O<ԖaI[bF[J&2- d"¼YLt8ztJ'fY0FZڐU^$0U9J3_7D^PHyYdq >9i-|sj 0q$d̖ψ,-y|5Drgj&\_rR@zUK+QK荾DȲ"tD.{WI gEy(DQ]9;ޫ%/fZrj%YZh,+K!I27w}@ٸ/fY~aܤ.fN*#YhP:l[u"0g*i@'1d0\Ӓ40ݮTa +qWԲMM._A cu.1[Yj]1j1 #hAyU3Qo;6t\>>I)-̺ p0%ޮyy^o6Ы'%/vèFB:Q=b0iEŕDշ h}òEMƴ5S*E{Wayܢ%҂ "nهEm>:>~g_/or$X KģT/)#}MFmx~/Λz儔DVM<=U +"wTNJ5Q{J~yv"$d0vlѴ]ꀸzHQ7g@_ů/ӮI)S {5$5Y̓<Aghͭc*E6ڞ۸QHؗa  eT vxeLl )`/%'Zʳ=JRL0A>P$ }H*ݧnw%l܀,.j/ա`n!l!Rt|gzٟ,'tٙWF>yw5xӗ~aX=D?Nnx,fRb;A- Ϳ%GWHmLrI$K|On 1\yX6&pRDzmEi& P|*bFK$S 6Fh:VTbL0ÈxNBKxh(j"E[,H +M 1`R)&tKRQZJ'$(86 aP4T_cPBY&MT2dzJ@)0Zq"$@'9MqT@%9O$Rbo0d!%l eI$I$5iyU`_T0M0 a%Guy4JS<c?[4'N +/I{d +%6lGӼ_@@B&bO3Rm`R * D[4 +h lQǧDQ\R#G = +]^tO"I& H0 \:,&(f0Q&c7 `A$(>Ha ЈG% *"p($ $X +偱aj hL2 1>!ab Ic@#QL +0bR9M,a>TtTK@p@T +t"L$`@Ա 1cp,LBN%&0 +f9IM FPQ|I)T^l:<(jm Ib}"0c3F!' á<Ҡ# `1`OSBUh:`i +Oɘ NiP#Г ۠IR`C1G -)UzTXfZ" âvDPKǃ[Ǡ]i@ Z":^ap044 G,A | +5(zA @H= &mﲔ%ΰh%`>8r Β=ea b@OÆMx¢@[HБLЊBˁ4!C M*ǀ2q:UdsN_m]&@RS<_" I  +-,=8AIs*&'M@*/.2A#!E_*_d஀h) &Nɢ +bN&8x6wzu)P)IUJt - T'"*?R4:BS C::,:v`$:Ih e :F"[J:#B;_ĉPBTfx7rpYpnyI&Dk: P93/5=󳅾 GP G,)RNb^$ ?z ,8~8MX@T74@莐DMɨ@ED%b1聳ɂL\; $Ck 60+4xS L!聎G F&zS( '8NF%$(Al}Z'N$P![ȳ(H3`_>!&x/\F +ћ&=@xDX |Zpq@jE&3WBZl>9&a } 0PFA00#.6=ţ[6AtDM \",^9 RoI-T$ 0ih_g&XC@虔 5"acGE x\00;`a~h018uI uiNC}L/ce2@s +pBB + J`A+dPI@YԐCAX3X]Z`ρ4tZ j3L9? 4%L 00[BL"!6C\("pS脁&,y[C8p"G B{K <&#d٠&!t#"B ,"11P $r` +26Y@ &P#M`N a a qId@ Lm"B +8 |]l x9} #yL!π8&P`P +* 2!-=0 +h&)P`pI) ǣ)s K? E!Fb1  p\R8qW)laV)]&# JF58& 8x84$į!8PbF 0( k` $x0IB7'`R+ ^숉Pdh `\JDCrBIf<t/Zt@*a)It3B Qsٍ? GeVVH`[K2H`#ۂn/&71rcDc|ݪMq [*%>^? raB-T=q9: + >#G6!>`RԎzg@MāAz ~i_?t}&Do,u$L;J`֮_BR{le,uP$m%Cr 2(  cW[KF,wV״>.bvC t + !N7R%eJPAn5t%{'B|aTA@mb#E.:n: >")Ó/cNe} \yE$L`K<¹AJoؕ;#ӿ((FRU؅%`Dib=.E;:E5Y(>3i vڋfyT"xx_nз'D!Nkҥ 9۷ӠJd$G;5 T A%W0 .|BH0 x!&Ne:kjta׬AI# D>zS m Ki' --;Z+ZM): jM5'&ݮ-ѤPl5{y_CimV@ DPO e(Y`;Xv(ADr &1мB +Vʉl74١82Tv"p].N\ǴkIN M~['׭`C^{,v!חT#:B*w#5#$a&H~ӭ荶fbȦs )j rFF`Lv%ǁ\C'CcĀFej* %o;Rf+ZY!\@{63=&4Xssn82#'&MmZMDߵjX%L[LLyFKUb݄wPw+!Ckxdk v3[q^qc$cxw(ج8bZCixL!R Pmgs85z hcY:N0 +͇Ó6"#pb 2xI/TR n&v0jUDgH {:Uum,6C%D7d +\x8=") w|\G˞xGQ}6zޞnn~8|_'g{x>>=_u͇; H endstream endobj 6 0 obj [5 0 R] endobj 21 0 obj <> endobj xref 0 22 0000000000 65535 f +0000000016 00000 n +0000000144 00000 n +0000042955 00000 n +0000000000 00000 f +0000046106 00000 n +0000177378 00000 n +0000043006 00000 n +0000043369 00000 n +0000046405 00000 n +0000046292 00000 n +0000045197 00000 n +0000045545 00000 n +0000045593 00000 n +0000046176 00000 n +0000046207 00000 n +0000046478 00000 n +0000046683 00000 n +0000048031 00000 n +0000066198 00000 n +0000131786 00000 n +0000177401 00000 n +trailer <]>> startxref 177595 %%EOF \ No newline at end of file diff --git a/Installer/Source/Assets.xcassets/OverSight.imageset/Contents.json b/Installer/Source/Assets.xcassets/OverSight.imageset/Contents.json new file mode 100644 index 0000000..a777e22 --- /dev/null +++ b/Installer/Source/Assets.xcassets/OverSight.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "overSight.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Installer/Source/Assets.xcassets/OverSight.imageset/overSight.png b/Installer/Source/Assets.xcassets/OverSight.imageset/overSight.png new file mode 100644 index 0000000..9a3181e Binary files /dev/null and b/Installer/Source/Assets.xcassets/OverSight.imageset/overSight.png differ diff --git a/Installer/Source/Assets.xcassets/icon.png b/Installer/Source/Assets.xcassets/icon.png new file mode 100644 index 0000000..4cb5670 Binary files /dev/null and b/Installer/Source/Assets.xcassets/icon.png differ diff --git a/Installer/Source/Configure.h b/Installer/Source/Configure.h new file mode 100644 index 0000000..a7babdc --- /dev/null +++ b/Installer/Source/Configure.h @@ -0,0 +1,47 @@ +// +// file: Configure.h +// project: OverSight (config) +// description: install/uninstall logic (header) +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +#import "HelperComms.h" +#import + +@interface Configure : NSObject +{ + +} + +/* PROPERTIES */ + +//helper installed & connected +@property(nonatomic) BOOL gotHelp; + +//daemom comms object +@property(nonatomic, retain) HelperComms* xpcComms; + +/* METHODS */ + +//determine if installed +-(BOOL)isInstalled; + +//old version installed? +-(BOOL)isV1Installed; + +//invokes appropriate install || uninstall logic +-(BOOL)configure:(NSInteger)parameter; + +//install +-(BOOL)install; + +//uninstall +-(BOOL)uninstall:(BOOL)full; + +//remove helper (daemon) +-(BOOL)removeHelper; + +@end + diff --git a/Installer/Source/Configure.m b/Installer/Source/Configure.m new file mode 100644 index 0000000..c249eca --- /dev/null +++ b/Installer/Source/Configure.m @@ -0,0 +1,552 @@ +// +// file: Configure.m +// project: OverSight (config) +// description: install/uninstall logic +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import OSLog; + +#import "consts.h" +#import "Configure.h" +#import "utilities.h" + +#import +#import +#import +#import + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +@implementation Configure + +@synthesize gotHelp; +@synthesize xpcComms; + +//invokes appropriate action +// either install || uninstall logic +-(BOOL)configure:(NSInteger)parameter +{ + //return var + BOOL wasConfigured = NO; + + //uninstall flag + BOOL uninstallFlag = UNINSTALL_FULL; + + //v1 installed? + // init XPC helper + if(YES == [self isV1Installed]) + { + //dbg msg + os_log_debug(logHandle, "V1 installed, will initialize XPC helper"); + + //get help + if(YES != [self initHelper]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to init helper tool"); + + //bail + goto bail; + } + } + //other, super quick + // so nap just a sec to allow install/uninstall msg to show up + else + { + //nap + [NSThread sleepForTimeInterval:1.0f]; + } + + //install + if(ACTION_INSTALL_FLAG == parameter) + { + //dbg msg + os_log_debug(logHandle, "installing..."); + + //already installed? + // first uninstall (old version) + if(YES == [self isInstalled]) + { + //dbg msg + os_log_debug(logHandle, "already installed, so this is an upgrade..."); + + //existing install <2.0? + // upgrade rules and set flag to perform full uninstall + if(YES == [self isV1Installed]) + { + //dbg msg + os_log_debug(logHandle, "found previous version <2.0"); + + //exec upgrade v1 -> v2 logic + [self upgradeFromV1]; + + //now set full uninstall flag + uninstallFlag = UNINSTALL_FULL; + } + //otherwise, + // set flag to perform partial uninstall + else + { + //dbg msg + os_log_debug(logHandle, "previous version is not v1.*, so only partially uninstall"); + + //set flag + uninstallFlag = UNINSTALL_PARTIAL; + } + + //uninstall + if(YES != [self uninstall:uninstallFlag]) + { + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "uninstalled (type: %@)", (uninstallFlag == UNINSTALL_PARTIAL) ? @"partial" : @"full"); + } + + //install + if(YES != [self install]) + { + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "installed!"); + } + //uninstall + else if(ACTION_UNINSTALL_FLAG == parameter) + { + //dbg msg + os_log_debug(logHandle, "uninstalling..."); + + //uninstall + if(YES != [self uninstall:UNINSTALL_FULL]) + { + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "uninstalled!"); + } + + //no errors + wasConfigured = YES; + +bail: + + return wasConfigured; +} + +//determine if (already) installed +// just check if app exists +-(BOOL)isInstalled +{ + //check if extension exists + return [[NSFileManager defaultManager] fileExistsAtPath:[APPS_FOLDER stringByAppendingPathComponent:APP_NAME]]; +} + +//old version installed? +// check for 'Objective-See' in user's application support directory +-(BOOL)isV1Installed +{ + //application support directory + NSString* applicationSupport = nil; + + //get user's application support directory + applicationSupport = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES).firstObject; + + //check for installed components + return (YES == [[NSFileManager defaultManager] fileExistsAtPath:[NSString pathWithComponents:@[applicationSupport, @"Objective-See", PRODUCT_NAME]]]); +} + +//upgrade logic for v1 -> v2+ +// for now, just 'allowed item' upgrade +-(void)upgradeFromV1 +{ + //app path + NSString* application = nil; + + //application support directory + NSString* applicationSupport = nil; + + //allowed items + NSString* allowedItems = nil; + + //get app path from resources + application = [NSBundle.mainBundle pathForResource:PRODUCT_NAME ofType:@"app"]; + + //get user's application support directory + applicationSupport = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES).firstObject; + + //build path to allowed items + allowedItems = [NSString pathWithComponents:@[applicationSupport, @"Objective-See", PRODUCT_NAME, @"whitelist.plist"]]; + if(YES != [NSFileManager.defaultManager fileExistsAtPath:allowedItems]) + { + //dbg msg + os_log_debug(logHandle, "no allowed items found at: %@", allowedItems); + + //done + goto bail; + } + + //launch app to upgrade + // storing in 'NSUserDefaults' so its gotta do it + execTask(application, @[CMD_UPGRADE, allowedItems], YES, NO); + +bail: + + return; +} + +//init helper tool +// install and establish XPC connection +-(BOOL)initHelper +{ + //bail if we're already G2G + if(YES == self.gotHelp) + { + //all set + goto bail; + } + + //install + if(YES != [self blessHelper]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to install helper tool"); + + //bail + goto bail; + } + + //init XPC comms + xpcComms = [[HelperComms alloc] init]; + if(nil == xpcComms) + { + //err msg + os_log_error(logHandle, "ERROR: failed to connect to helper tool"); + + //bail + goto bail; + } + + //happy + self.gotHelp = YES; + +bail: + + return self.gotHelp; +} + +//install helper tool +// sets 'wasBlessed' iVar +-(BOOL)blessHelper +{ + //flag + BOOL wasBlessed = NO; + + //auth ref + AuthorizationRef authRef = NULL; + + //error + CFErrorRef error = NULL; + + //auth item + AuthorizationItem authItem = {}; + + //auth rights + AuthorizationRights authRights = {}; + + //auth flags + AuthorizationFlags authFlags = 0; + + //create auth + if(errAuthorizationSuccess != AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef)) + { + //err msg + os_log_error(logHandle, "ERROR: failed to create authorization"); + + //bail + goto bail; + } + + //init auth item + memset(&authItem, 0x0, sizeof(authItem)); + + //set name + authItem.name = kSMRightBlessPrivilegedHelper; + + //set auth count + authRights.count = 1; + + //set auth items + authRights.items = &authItem; + + //init flags + authFlags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; + + //get auth rights + if(errAuthorizationSuccess != AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, authFlags, NULL)) + { + //err msg + os_log_error(logHandle, "ERROR: failed to copy authorization rights"); + + //bail + goto bail; + } + + //bless + if(YES != (BOOL)SMJobBless(kSMDomainSystemLaunchd, (__bridge CFStringRef)(CONFIG_HELPER_ID), authRef, &error)) + { + //err msg + os_log_error(logHandle, "ERROR: failed to bless job (%@)", ((__bridge NSError*)error)); + + //bail + goto bail; + } + + //happy + wasBlessed = YES; + +bail: + + //free auth ref + if(NULL != authRef) + { + //free + AuthorizationFree(authRef, kAuthorizationFlagDefaults); + + //unset + authRef = NULL; + } + + //free error + if(NULL != error) + { + //release + CFRelease(error); + + //unset + error = NULL; + } + + return wasBlessed; +} + +//remove helper (daemon) +-(BOOL)removeHelper +{ + //return/status var + __block BOOL wasRemoved = NO; + + //if needed + // tell helper to remove itself + if(YES == self.gotHelp) + { + //cleanup + wasRemoved = [self.xpcComms cleanup]; + + //unset var + if(YES == wasRemoved) + { + //unset + self.gotHelp = NO; + } + } + //didn't need to remove + // just set ret var to 'ok' + else + { + //set + wasRemoved = YES; + } + + return wasRemoved; +} + +//install +// copy app/install as login item +-(BOOL)install +{ + //return/status var + BOOL wasInstalled = NO; + + //app source + NSString* applicationSrc = nil; + + //path to app + NSString* applicationDest = nil; + + //error + NSError* error = nil; + + //init app src (from resources) + applicationSrc = [NSBundle.mainBundle pathForResource:PRODUCT_NAME ofType:@"app"]; + + //init app dest + applicationDest = [@"/Applications" stringByAppendingPathComponent:APP_NAME]; + + //copy + if(YES != [NSFileManager.defaultManager copyItemAtPath:applicationSrc toPath:applicationDest error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to copy %@ -> %@ (error: %@)", applicationSrc, applicationDest, error); + + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "copied %{public}@ -> %{public}@", applicationSrc, applicationDest); + + //happy + wasInstalled = YES; + +bail: + + return wasInstalled; +} + +//uninstall +-(BOOL)uninstall:(BOOL)full +{ + //return/status var + __block BOOL wasUninstalled = NO; + + //flag + BOOL v1Uninstall = NO; + + //path to app + NSString* application = nil; + + //application support directory + NSString* applicationSupport = nil; + + //path to preferences dir + NSString* prefsDirectory = nil; + + //path to login item + NSURL* loginItem = nil; + + //error + NSError* error = nil; + + //set flag + v1Uninstall = [self isV1Installed]; + + //init path + application = [@"/Applications" stringByAppendingPathComponent:APP_NAME]; + + //dbg msg + os_log_debug(logHandle, "uninstalling %{public}@", application); + + //full? + // first uninstall app as login item + if(YES == full) + { + //v1 had different login item name + if(YES == v1Uninstall) + { + //init path + loginItem = [NSURL fileURLWithPath:[application stringByAppendingPathComponent:@"/Contents/Library/LoginItems/OverSight Helper.app"]]; + } + //otherwise, just path to app + else + { + //init path + loginItem = [NSURL fileURLWithPath:application]; + } + + //uninstall login item + if(YES != toggleLoginItem(loginItem, ACTION_UNINSTALL_FLAG)) + { + //err msg + // ...though not fatal + os_log_error(logHandle, "failed to uninstall login item"); + } + else + { + //dbg msg + os_log_debug(logHandle, "uninstalled %{public}@ as login item", loginItem.path); + } + } + + //v1.0 uninstall? + // uninstall via XPC + if(YES == [self isV1Installed]) + { + //get user's application support directory + applicationSupport = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES).firstObject; + + //init prefs directory + prefsDirectory = [NSString pathWithComponents:@[applicationSupport, @"Objective-See", PRODUCT_NAME]]; + + //dbg msg + os_log_debug(logHandle, "v1 installed, so uninstalling via XPC helper"); + + //uninstall + wasUninstalled = [xpcComms uninstall:prefsDirectory]; + + //up one level + prefsDirectory = [prefsDirectory stringByDeletingLastPathComponent]; + + //no other items + // delete obj-see directory + if(0 == [NSFileManager.defaultManager contentsOfDirectoryAtPath:prefsDirectory error:nil].count) + { + //dbg msg + os_log_debug(logHandle, "no files found in %{public}@, will remove", prefsDirectory); + + //remove + [NSFileManager.defaultManager removeItemAtPath:prefsDirectory error:nil]; + } + + //done + goto bail; + } + + //full? + // delete prefs / allowed items + if(YES == full) + { + //dbg msg + os_log_debug(logHandle, "removing preferences/allowed items"); + + //delete prefs via defaults + execTask(DEFAULTS, @[@"delete", @BUNDLE_ID], YES, NO); + } + + //always remove application + if(YES != [NSFileManager.defaultManager removeItemAtPath:application error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to remove %{public}@ (error: %@)", application, error); + + //bail + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "deleted %{public}@", application); + + //kill it + execTask(KILL_ALL, @[@"OverSight"], NO, NO); + + //happy + wasUninstalled = YES; + +bail: + + return wasUninstalled; +} + +@end diff --git a/Installer/Source/ConfigureWindowController.h b/Installer/Source/ConfigureWindowController.h new file mode 100644 index 0000000..e1419d5 --- /dev/null +++ b/Installer/Source/ConfigureWindowController.h @@ -0,0 +1,80 @@ +// +// file: ConfigureWindowController.h +// project: OverSight (config) +// description: install/uninstall window logic (header) +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + + +@import Cocoa; + +@interface ConfigureWindowController : NSWindowController +{ + +} + +/* PROPERTIES */ + +//config object +@property(nonatomic, retain) Configure* configureObj; + +//uninstall button +@property (weak, nonatomic) IBOutlet NSButton *uninstallButton; + +//install button +@property (weak, nonatomic) IBOutlet NSButton *installButton; + +//status msg +@property (weak, nonatomic) IBOutlet NSTextField *statusMsg; + +//more info button +@property (weak, nonatomic) IBOutlet NSButton *moreInfoButton; + +//spinner +@property (weak, nonatomic) IBOutlet NSProgressIndicator *activityIndicator; + +/* FULL DISK ACCESS */ + +//full disk access view +@property (strong, nonatomic) IBOutlet NSView *diskAccessView; + +//disk access view's button +@property (weak, nonatomic) IBOutlet NSButton *diskAccessButton; + +//spinner for 'waiting for FDA' message +@property (weak, nonatomic) IBOutlet NSProgressIndicator *fdaActivityIndicator; + +//'waiting for FDA' message +@property (weak, nonatomic) IBOutlet NSTextField *fdaMessage; + +//issues button +@property (weak, nonatomic) IBOutlet NSButton *issuesButton; + +/* SUPPORT US */ + +//support us view +@property (strong, nonatomic) IBOutlet NSView *supportView; + +//support us +@property (weak, nonatomic) IBOutlet NSButton *supportButton; + +//observer for app activation +@property(nonatomic, retain)id appActivationObserver; + +/* METHODS */ + +//install/uninstall button handler +-(IBAction)configureButtonHandler:(id)sender; + +//(more) info button handler +-(IBAction)info:(id)sender; + +//configure window/buttons +-(void)configure; + +//display (show) window +-(void)display; + +@end diff --git a/Installer/Source/ConfigureWindowController.m b/Installer/Source/ConfigureWindowController.m new file mode 100644 index 0000000..1d77a12 --- /dev/null +++ b/Installer/Source/ConfigureWindowController.m @@ -0,0 +1,539 @@ +// +// file: ConfigureWindowController.m +// project: OverSight (config) +// description: install/uninstall window logic +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import OSLog; + +#import "consts.h" +#import "Configure.h" +#import "utilities.h" +#import "AppDelegate.h" +#import "ConfigureWindowController.h" + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +@implementation ConfigureWindowController + +@synthesize statusMsg; +@synthesize fdaMessage; +@synthesize configureObj; +@synthesize diskAccessView; +@synthesize moreInfoButton; +@synthesize fdaActivityIndicator; +@synthesize appActivationObserver; + +//automatically called when nib is loaded +// just center window, alloc some objs, etc +-(void)awakeFromNib +{ + //center + [self.window center]; + + //when supported + // indicate title bar is transparent (too) + if(YES == [self.window respondsToSelector:@selector(titlebarAppearsTransparent)]) + { + //set transparency + self.window.titlebarAppearsTransparent = YES; + } + + //make first responder + // calling this without a timeout sometimes fails :/ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (100 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + + //and make it first responder + [self.window makeFirstResponder:self.installButton]; + + }); + + //init configure object + if(nil == self.configureObj) + { + //alloc/init Config obj + configureObj = [[Configure alloc] init]; + } + + return; +} + +//configure window/buttons +// also brings window to front +-(void)configure +{ + //flag + BOOL isInstalled = NO; + + //init flag + isInstalled = [self.configureObj isInstalled]; + + //set window title + [self window].title = [NSString stringWithFormat:@"version %@", getAppVersion()]; + + //init status msg + [self.statusMsg setStringValue:@"...protects your webcam and microphone!"]; + + //app already installed? + // enable 'uninstall' button + // change 'install' button to say 'upgrade' + if(YES == isInstalled) + { + //enable 'uninstall' + self.uninstallButton.enabled = YES; + + //set to 'upgrade' + self.installButton.title = ACTION_UPGRADE; + } + + //otherwise disable + else + { + //disable + self.uninstallButton.enabled = NO; + } + + //set delegate + [self.window setDelegate:self]; + + return; +} + +//display (show) window +// center, make front, set bg to white, etc +-(void)display +{ + //center window + [[self window] center]; + + //show (now configured) windows + [self showWindow:self]; + + //make it key window + [self.window makeKeyAndOrderFront:self]; + + //make window front + [NSApp activateIgnoringOtherApps:YES]; + + //not in dark mode? + // make window white + if(YES != isDarkMode()) + { + //make white + self.window.backgroundColor = NSColor.whiteColor; + } + + return; +} + +//button handler for configure window +// install/uninstall/close logic +-(IBAction)configureButtonHandler:(id)sender +{ + //action + NSInteger action = 0; + + //grab tag + action = ((NSButton*)sender).tag; + + //dbg msg + os_log_debug(logHandle, "handling action click: %{public}@ (tag: %ld)", ((NSButton*)sender).title, (long)action); + + //process button + switch(action) + { + //install/uninstall + case ACTION_INSTALL_FLAG: + case ACTION_UNINSTALL_FLAG: + { + //disable 'x' button + // don't want user killing app during install/upgrade + [[self.window standardWindowButton:NSWindowCloseButton] setEnabled:NO]; + + //clear status msg + self.statusMsg.stringValue = @""; + + //force redraw of status msg + // sometime doesn't refresh (e.g. slow VM) + self.statusMsg.needsDisplay = YES; + + //invoke logic to install/uninstall + // do in background so UI doesn't block + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + //install/uninstall + [self lifeCycleEvent:action]; + }); + + break; + } + + //show 'support' view + case ACTION_SHOW_SUPPORT: + { + //dbg msg + os_log_debug(logHandle, "showing 'support' view"); + + //show view + [self showView:self.supportView firstResponder:self.supportButton]; + + //unset window title + self.window.title = @""; + + break; + } + + //support, yes! + case ACTION_SUPPORT: + + //open URL + // invokes user's default browser + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:PATREON_URL]]; + + //fall thru as we want to launch app and terminate + + //close + // on non-error, launch login item + case ACTION_CLOSE_FLAG: + { + //coming from support view? + // launch helper/login item + if(YES == self.supportView.window.isVisible) + { + //dbg msg + os_log_debug(logHandle, "now launching: %@", APP_NAME); + + //launch helper app + execTask(OPEN, @[[@"/Applications" stringByAppendingPathComponent:APP_NAME], @"--args", INITIAL_LAUNCH], NO, NO); + } + + //close window + // triggers cleanup logic + [self.window close]; + + break; + } + + //default + default: + + break; + } + + return; +} + +//show view +// adds to main window, resizes, etc +-(void)showView:(NSView*)view firstResponder:(NSButton*)firstResponder +{ + //not in dark mode? + // make window white + if(YES != isDarkMode()) + { + //set white + view.layer.backgroundColor = [NSColor whiteColor].CGColor; + } + + //set content view size + self.window.contentSize = view.frame.size; + + //update config view + self.window.contentView = view; + + //make 'next' button first responder + // calling this without a timeout, sometimes fails :/ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (100 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + + //set first responder + [self.window makeFirstResponder:firstResponder]; + + }); + + return; +} + +//button handler for '?' button (on an error) +// load objective-see's documentation for error(s) in default browser +-(IBAction)info:(id)sender +{ + #pragma unused(sender) + + //open URL + // invokes user's default browser + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ERRORS_URL]]; + + return; +} + +//perform install | uninstall via Control obj +// invoked on background thread so that UI doesn't block +-(void)lifeCycleEvent:(NSInteger)event +{ + //status var + BOOL status = NO; + + //begin event + // updates ui on main thread + dispatch_sync(dispatch_get_main_queue(), + ^{ + //begin + [self beginEvent:event]; + }); + + //in background + // perform action (install | uninstall) + status = [self.configureObj configure:event]; + + //complete event + // updates ui on main thread + dispatch_async(dispatch_get_main_queue(), + ^{ + //complete + [self completeEvent:status event:event]; + }); + + return; +} + +//begin event +// basically just update UI +-(void)beginEvent:(NSInteger)event +{ + //status msg frame + CGRect statusMsgFrame; + + //grab exiting frame + statusMsgFrame = self.statusMsg.frame; + + //avoid activity indicator + // shift frame shift delta + statusMsgFrame.origin.x += FRAME_SHIFT; + + //update frame to align + self.statusMsg.frame = statusMsgFrame; + + //align text left + self.statusMsg.alignment = NSTextAlignmentLeft; + + //observe app activation + // allows workaround where process indicator stops + self.appActivationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSWorkspaceDidActivateApplicationNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) + { + #pragma unused(notification) + + //show spinner + self.activityIndicator.hidden = NO; + + //start spinner + [self.activityIndicator startAnimation:nil]; + + }]; + + //install msg + if(ACTION_INSTALL_FLAG == event) + { + //update status msg + [self.statusMsg setStringValue:@"Installing..."]; + } + //uninstall msg + else + { + //update status msg + [self.statusMsg setStringValue:@"Uninstalling..."]; + } + + //disable action button + self.uninstallButton.enabled = NO; + + //disable cancel button + self.installButton.enabled = NO; + + //show spinner + self.activityIndicator.hidden = NO; + + //start spinner + [self.activityIndicator startAnimation:nil]; + + return; +} + +//complete event +// update UI after background event has finished +-(void)completeEvent:(BOOL)success event:(NSInteger)event +{ + //status msg frame + CGRect statusMsgFrame; + + //action + NSString* action = nil; + + //result msg + NSMutableString* resultMsg = nil; + + //msg font + NSColor* resultMsgColor = nil; + + //remove app activation observer + if(nil != self.appActivationObserver) + { + //remove + [[NSNotificationCenter defaultCenter] removeObserver:self.appActivationObserver]; + + //unset + self.appActivationObserver = nil; + } + + //set action msg for install + if(ACTION_INSTALL_FLAG == event) + { + //set msg + action = @"install"; + } + //set action msg for uninstall + else + { + //set msg + action = @"uninstall"; + } + + //success + if(YES == success) + { + //set result msg + resultMsg = [NSMutableString stringWithFormat:@"☑️ %@: %@ed!\n", PRODUCT_NAME, action]; + } + //failure + else + { + //set result msg + resultMsg = [NSMutableString stringWithFormat:@"⚠️ Error: %@ failed", action]; + + //show 'get more info' button + self.moreInfoButton.hidden = NO; + } + + //stop/hide spinner + [self.activityIndicator stopAnimation:nil]; + + //hide spinner + self.activityIndicator.hidden = YES; + + //grab exiting frame + statusMsgFrame = self.statusMsg.frame; + + //shift back since activity indicator is gone + statusMsgFrame.origin.x -= FRAME_SHIFT; + + //update frame to align + self.statusMsg.frame = statusMsgFrame; + + //set font to bold + self.statusMsg.font = [NSFont fontWithName:@"Menlo-Bold" size:13]; + + //set msg color + self.statusMsg.textColor = resultMsgColor; + + //set status msg + self.statusMsg.stringValue = resultMsg; + + //install success? + // set button title & tag for 'next' + if( (YES == success) && + (ACTION_INSTALL_FLAG == event) ) + { + //next + self.installButton.title = ACTION_NEXT; + + //set tag + self.installButton.tag = ACTION_SHOW_SUPPORT; + } + //otherwise + // set button and tag for close/exit + else + { + //close + self.installButton.title = ACTION_CLOSE; + + //update it's tag + // will allow button handler method process + self.installButton.tag = ACTION_CLOSE_FLAG; + + //(re)enable 'x' button + [[self.window standardWindowButton:NSWindowCloseButton] setEnabled:YES]; + } + + //enable + self.installButton.enabled = YES; + + //...and highlighted + [self.window makeFirstResponder:self.installButton]; + + //(re)make window window key + [self.window makeKeyAndOrderFront:self]; + + //(re)make window front + [NSApp activateIgnoringOtherApps:YES]; + + return; +} + +//perform any cleanup/termination +// for now, just call into Config obj to remove helper +-(BOOL)cleanup +{ + //flag + BOOL cleanedUp = NO; + + //dbg msg + os_log_debug(logHandle, "cleaning up..."); + + //remove helper + if(YES != [self.configureObj removeHelper]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to remove config helper"); + + //bail + goto bail; + } + + //happy + cleanedUp = YES; + +bail: + + return cleanedUp; +} + +//automatically invoked when window is closing +// perform cleanup logic, then manually terminate app +-(void)windowWillClose:(NSNotification *)notification +{ + #pragma unused(notification) + + //cleanup in background + // then exit application + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + //cleanup + [self cleanup]; + + //exit on main thread + dispatch_async(dispatch_get_main_queue(), + ^{ + //exit + [NSApp terminate:self]; + }); + }); + + return; +} + +@end diff --git a/Installer/Source/ConfigureWindowController.xib b/Installer/Source/ConfigureWindowController.xib new file mode 100644 index 0000000..755850d --- /dev/null +++ b/Installer/Source/ConfigureWindowController.xib @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Installer/Source/HelperComms.h b/Installer/Source/HelperComms.h new file mode 100644 index 0000000..20dee2b --- /dev/null +++ b/Installer/Source/HelperComms.h @@ -0,0 +1,31 @@ +// +// file: HelperComms.h +// project: OverSight (config) +// description: interface to talk to blessed installer (header) +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import Foundation; + +#import "XPCProtocol.h" + +@interface HelperComms : NSObject + +//remote deamon proxy object +@property(nonatomic, retain) id daemon; + +//xpc connection +@property (atomic, strong, readwrite) NSXPCConnection* xpcServiceConnection; + +/* METHODS */ + +//uninstall +-(BOOL)uninstall:(NSString*)prefsDirectory; + +//cleanup +// remove self +-(BOOL)cleanup; + +@end diff --git a/Installer/Source/HelperComms.m b/Installer/Source/HelperComms.m new file mode 100644 index 0000000..0b60e4b --- /dev/null +++ b/Installer/Source/HelperComms.m @@ -0,0 +1,96 @@ +// +// file: HelperComms.h +// project: OverSight (config) +// description: interface to talk to blessed installer (header) +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import OSLog; +@import Foundation; + +#import "consts.h" +#import "AppDelegate.h" +#import "HelperComms.h" + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +@implementation HelperComms + +@synthesize daemon; +@synthesize xpcServiceConnection; + +//init +// create XPC connection & set remote obj interface +-(id)init +{ + //super + self = [super init]; + if(nil != self) + { + //alloc/init + xpcServiceConnection = [[NSXPCConnection alloc] initWithMachServiceName:CONFIG_HELPER_ID options:0]; + + //set remote object interface + self.xpcServiceConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; + + //resume + [self.xpcServiceConnection resume]; + } + + return self; +} + +//uninstall +-(BOOL)uninstall:(NSString*)prefsDirectory +{ + //result + __block BOOL result = NO; + + //dbg msg + os_log_debug(logHandle, "invoking 'uninstall' XPC method, with %{public}@", prefsDirectory); + + //uninstall + [[self.xpcServiceConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * proxyError) + { + //err msg + os_log_error(logHandle, "ERROR: failed to execute 'uninstall' method on helper tool (error: %@)", proxyError); + + }] uninstall:[[NSBundle mainBundle] bundlePath] prefs:prefsDirectory reply:^(NSNumber* xpcResult) + { + //capture results + result = [xpcResult boolValue]; + }]; + + return result; +} + +//cleanup +-(BOOL)cleanup +{ + //result + __block BOOL result = NO; + + //dbg msg + os_log_debug(logHandle, "invoking 'cleanup' XPC method"); + + //remove + [[(NSXPCConnection*)self.xpcServiceConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * proxyError) + { + //err msg + os_log_error(logHandle, "ERROR: failed to execute 'remove' method on helper tool (error: %@)", proxyError); + + }] cleanup:^(NSNumber* xpcResult) + { + //capture results + result = [xpcResult boolValue]; + }]; + + return result; +} + +@end diff --git a/MainApp/Info.plist b/Installer/Source/Info.plist similarity index 52% rename from MainApp/Info.plist rename to Installer/Source/Info.plist index 48b469c..f516b6d 100644 --- a/MainApp/Info.plist +++ b/Installer/Source/Info.plist @@ -3,32 +3,31 @@ CFBundleDevelopmentRegion - en + English CFBundleExecutable - $(EXECUTABLE_NAME) + ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 - CFBundleName - $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString - 1.2.0 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion - 1.2.0 - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - LSUIElement - + $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright - Copyright (c) 2017 Objective-See. All rights reserved. + Copyright (c) 2020 Objective-See. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass NSApplication + SMPrivilegedExecutables + + com.objective-see.oversight.uninstallHelper + anchor apple generic and identifier "com.objective-see.oversight.uninstallHelper" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = VBG97UB4TA) + diff --git a/Installer/Source/MainMenu.xib b/Installer/Source/MainMenu.xib new file mode 100644 index 0000000..23440e4 --- /dev/null +++ b/Installer/Source/MainMenu.xib @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Installer/Source/Script/configure.sh b/Installer/Source/Script/configure.sh new file mode 100755 index 0000000..598a882 --- /dev/null +++ b/Installer/Source/Script/configure.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# +# file: configure.sh +# project: OverSight (configure) +# description: v1.* uninstaller logic +# +# created by Patrick Wardle +# copyright (c) 2021 Objective-See. All rights reserved. +# + +#auth check +# gotta be root +if [ "${EUID}" -ne 0 ]; then + echo "" + echo "ERROR: must be run as root" + echo "" + exit -1 +fi + +#check args +if [ "$#" -ne 2 ] || ! [ "${1}" == "-uninstall" ]; then + echo "" + echo "ERROR: invalid arguments" + echo "" + exit -1 +fi + +#dbg msg +echo "uninstalling" + +#remove application +rm -rf "/Applications/OverSight.app" + +#remove preferences, etc. +rm -rf "${2}" + +killall "OverSight" 2> /dev/null +killall "com.objective-see.OverSight.helper" 2> /dev/null +killall "OverSight Helper" 2> /dev/null diff --git a/Installer/Source/main.h b/Installer/Source/main.h new file mode 100644 index 0000000..e0bda74 --- /dev/null +++ b/Installer/Source/main.h @@ -0,0 +1,19 @@ +// +// file: main.m +// project: OverSight (config app) +// description: main interface, for config (header) +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +#ifndef main_h +#define main_h + +/* FUNCTION DECLARATIONS */ + +//cmdline interface +// install or uninstall +BOOL cmdlineInterface(int action); + +#endif /* main_h */ diff --git a/Installer/Source/main.m b/Installer/Source/main.m new file mode 100644 index 0000000..9d8de08 --- /dev/null +++ b/Installer/Source/main.m @@ -0,0 +1,148 @@ +// +// file: main.m +// project: OverSight (config app) +// description: main interface, for config +// +// created by Patrick Wardle +// copyright (c) 2018 Objective-See. All rights reserved. +// + +@import Cocoa; +@import OSLog; +@import Sentry; + +#import "main.h" +#import "consts.h" +#import "utilities.h" +#import "Configure.h" + +/* GLOBALS */ + +//log handle +os_log_t logHandle = nil; + +//main interface +int main(int argc, char *argv[]) +{ + //status + int status = -1; + + //init log + logHandle = os_log_create(BUNDLE_ID, "installer"); + + //init crash reporting + [SentrySDK startWithConfigureOptions:^(SentryOptions *options) { + options.dsn = SENTRY_DSN; + options.debug = YES; + }]; + + //cmdline install? + if(YES == [NSProcessInfo.processInfo.arguments containsObject:CMD_INSTALL]) + { + //dbg msg + os_log_debug(logHandle, "performing commandline install"); + + //install + if(YES != cmdlineInterface(ACTION_INSTALL_FLAG)) + { + //err msg + printf("\n%s ERROR: install failed\n\n", PRODUCT_NAME.uppercaseString.UTF8String); + + //bail + goto bail; + } + + //dbg msg + printf("%s: install ok!\n\n", PRODUCT_NAME.uppercaseString.UTF8String); + + //happy + status = 0; + + //done + goto bail; + } + + //cmdline uninstall? + else if(YES == [[[NSProcessInfo processInfo] arguments] containsObject:CMD_UNINSTALL]) + { + //dbg msg + os_log_debug(logHandle, "performing commandline uninstall"); + + //install + if(YES != cmdlineInterface(ACTION_UNINSTALL_FLAG)) + { + //err msg + printf("\n%s ERROR: uninstall failed\n\n", PRODUCT_NAME.uppercaseString.UTF8String); + + //bail + goto bail; + } + + //dbg msg + printf("%s: uninstall ok!\n\n", PRODUCT_NAME.uppercaseString.UTF8String); + + //happy + status = 0; + + //done + goto bail; + } + + //default run mode + // just kick off main app logic + status = NSApplicationMain(argc, (const char **)argv); + +bail: + + return status; +} + +//cmdline interface +// install or uninstall +BOOL cmdlineInterface(int action) +{ + //flag + BOOL wasConfigured = NO; + + //configure obj + Configure* configure = nil; + + //ignore SIGPIPE + signal(SIGPIPE, SIG_IGN); + + //alloc/init + configure = [[Configure alloc] init]; + + //first check root + if(0 != geteuid()) + { + //err msg + printf("\n%s ERROR: cmdline interface actions require root!\n\n", PRODUCT_NAME.uppercaseString.UTF8String); + + //bail + goto bail; + } + + //configure + wasConfigured = [configure configure:action]; + if(YES != wasConfigured) + { + //bail + goto bail; + } + + + //happy + wasConfigured = YES; + +bail: + + //cleanup + if(nil != configure) + { + //cleanup + [configure removeHelper]; + } + + return wasConfigured; +} diff --git a/Installer/main.h b/Installer/main.h deleted file mode 100644 index 644a098..0000000 --- a/Installer/main.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// main.h -// Installer -// -// Created by Patrick Wardle on 9/14/16. -// Copyright © 2016 Objective-See. All rights reserved. -// - -#ifndef main_h -#define main_h - -#import "Consts.h" -#import "Logging.h" -#import "Configure.h" - -#import - -/* FUNCTION DECLARATIONS */ - -//spawn self as root -BOOL spawnAsRoot(const char* path2Self); - -//install -BOOL cmdlineInstall(void); - -//uninstall -BOOL cmdlineUninstall(void); - -#endif /* main_h */ diff --git a/Installer/main.m b/Installer/main.m deleted file mode 100644 index b78f825..0000000 --- a/Installer/main.m +++ /dev/null @@ -1,200 +0,0 @@ -// -// main.m -// OverSight -// -// Created by Patrick Wardle on 7/15/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "main.h" -#import "Consts.h" -#import "Configure.h" -#import - -//main interface -int main(int argc, const char * argv[]) -{ - //return var - int retVar = -1; - - //pool - @autoreleasepool { - - //handle '-install' / '-uninstall' - // ->this performs non-UI logic for easier automated deployment - if( (argc >= 2) && - ( (0 == strcmp(argv[1], CMD_INSTALL)) || (0 == strcmp(argv[1], CMD_UNINSTALL)) ) ) - { - //first check rooot - if(0 != geteuid()) - { - //err msg - printf("\nERROR: '%s' option, requires root\n\n", argv[1]); - - //bail - goto bail; - } - - //handle install - if(0 == strcmp(argv[1], CMD_INSTALL)) - { - //install - if(YES != cmdlineInstall()) - { - //err msg - printf("\nERROR: install failed\n\n"); - - //bail - goto bail; - } - - //dbg msg - printf("OVERSIGHT: install ok!\n"); - - //happy - retVar = 0; - } - - //handle uninstall - else if(0 == strcmp(argv[1], CMD_UNINSTALL)) - { - //uninstall - if(YES != cmdlineUninstall()) - { - //err msg - printf("\nERROR: install failed\n\n"); - } - - //dbg msg - printf("OVERSIGHT: uninstall ok!\n"); - - //happy - retVar = 0; - } - - //bail - goto bail; - - }//args - - //check for r00t - // ->then spawn self via auth exec - if(0 != geteuid()) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"non-root installer instance"); - #endif - - //spawn as root - if(YES != spawnAsRoot(argv[0])) - { - //err msg - logMsg(LOG_ERR, @"failed to spawn self as r00t"); - - //bail - goto bail; - } - - //happy - retVar = 0; - } - - //otherwise - // ->just kick off app, as we're root now - else - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"root installer instance"); - #endif - - //app away - retVar = NSApplicationMain(argc, (const char **)argv); - } - - }//pool - -//bail -bail: - - return retVar; -} - -//spawn self as root -BOOL spawnAsRoot(const char* path2Self) -{ - //return/status var - BOOL bRet = NO; - - //authorization ref - AuthorizationRef authorizatioRef = {0}; - - //args - char *args[] = {NULL}; - - //flag creation of ref - BOOL authRefCreated = NO; - - //status code - OSStatus osStatus = -1; - - //create authorization ref - // ->and check - osStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizatioRef); - if(errAuthorizationSuccess != osStatus) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"AuthorizationCreate() failed with %d", osStatus]); - - //bail - goto bail; - } - - //set flag indicating auth ref was created - authRefCreated = YES; - - //spawn self as r00t w/ install flag (will ask user for password) - // ->and check - osStatus = AuthorizationExecuteWithPrivileges(authorizatioRef, path2Self, 0, args, NULL); - - //check - if(errAuthorizationSuccess != osStatus) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"AuthorizationExecuteWithPrivileges() failed with %d", osStatus]); - - //bail - goto bail; - } - - //no errors - bRet = YES; - -//bail -bail: - - //free auth ref - if(YES == authRefCreated) - { - //free - AuthorizationFree(authorizatioRef, kAuthorizationFlagDefaults); - } - - return bRet; -} - -//install -BOOL cmdlineInstall() -{ - //do it! - return [[[Configure alloc] init] configure:ACTION_INSTALL_FLAG]; -} - -//uninstall -BOOL cmdlineUninstall() -{ - //do it! - return [[[Configure alloc] init] configure:ACTION_UNINSTALL_FLAG]; -} - diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/LoginItem/AVMonitor.h b/LoginItem/AVMonitor.h deleted file mode 100644 index 2116e46..0000000 --- a/LoginItem/AVMonitor.h +++ /dev/null @@ -1,83 +0,0 @@ -// -// ProcessMonitor.h -// OverSight -// -// Created by Patrick Wardle on 9/01/16. -// Copyright (c) 2015 Objective-See. All rights reserved. -// - - -#import -#import -#import -#import -#import - -#import "RemeberWindowController.h" - -/* DEFINES */ - -#define VIDEO_DISABLED 0x0 -#define VIDEO_ENABLED 0x1 - -#define DEVICE_INACTIVE @0x0 -#define DEVICE_ACTIVE @0x1 - -@interface AVMonitor : NSObject -{ - - -} - -/* PROPERTIES */ - -//apple mic (AVCaptureHALDevice) -@property(nonatomic, retain)AVCaptureDevice* mic; - -//apple camera (AVCaptureDALDevice) -@property(nonatomic, retain)AVCaptureDevice* camera; - -//flag indicating audio (mic) is active -@property BOOL audioActive; - -//flag indicating video (camera) is active -@property BOOL videoActive; - -//monitor thread -@property(nonatomic, retain)NSThread* videoMonitorThread; - -//popup windows -@property(nonatomic, retain)NSMutableArray* rememberPopups; - -//last event -@property(nonatomic, retain)NSDictionary* lastEvent; - -//last notification -@property(nonatomic, retain)NSString* lastNotification; - -//whitelisted procs -@property(nonatomic, retain)NSMutableArray* whiteList; - -//activation alerts that were displayed -@property(nonatomic, retain)NSMutableDictionary* activationAlerts; - - - -/* METHODS */ - -//load whitelist --(void)loadWhitelist; - -//kicks off thread to monitor --(BOOL)monitor; - -//monitor for new procs --(void)monitor4Procs; - -//determine if audio is active --(void)setAudioDevStatus:(AudioObjectID)deviceID; - -//determine if video is active --(void)setVideoDevStatus:(CMIOObjectID)deviceID; - -@end diff --git a/LoginItem/AVMonitor.m b/LoginItem/AVMonitor.m deleted file mode 100644 index ec7b131..0000000 --- a/LoginItem/AVMonitor.m +++ /dev/null @@ -1,1813 +0,0 @@ -// -// AVMonitor.m -// OverSight -// -// Created by Patrick Wardle on 9/01/16. -// Copyright (c) 2015 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" -#import "AVMonitor.h" -#import "AppDelegate.h" - -#import "../Shared/XPCProtocol.h" - -@implementation AVMonitor - -@synthesize mic; -@synthesize camera; -@synthesize lastEvent; -@synthesize whiteList; -@synthesize audioActive; -@synthesize rememberPopups; -@synthesize activationAlerts; -@synthesize lastNotification; -@synthesize videoMonitorThread; - -//init --(id)init -{ - //init super - self = [super init]; - if(nil != self) - { - //alloc - activationAlerts = [NSMutableDictionary dictionary]; - - //alloc - rememberPopups = [NSMutableArray array]; - - //load whitelist - [self loadWhitelist]; - } - - return self; -} - -//load whitelist --(void)loadWhitelist -{ - //path - NSString* path = nil; - - //init path - path = [[[@"~" stringByAppendingPathComponent:APP_SUPPORT_DIRECTORY] stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"whitelist path %@", path]); - #endif - - //check if it exists - if(YES != [[NSFileManager defaultManager] fileExistsAtPath:path]) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"nothing whitelisted yet, so won't load (file not found)"); - #endif - - //bail - goto bail; - } - - //since file is created by priv'd XPC, it shouldn't be writeable - // ...unless somebody maliciously creates it, so we check that here - if(YES == [[NSFileManager defaultManager] isWritableFileAtPath:path]) - { - //err msg - logMsg(LOG_ERR, @"whitelist is writable, so ignoring!"); - - //bail - goto bail; - } - - //load - self.whiteList = [NSMutableArray arrayWithContentsOfFile:path]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"whitelist: %@", self.whiteList]); - #endif - -//bail -bail: - - return; -} - -//grab first apple camera, or default -// ->saves into iVar 'camera' --(void)findAppleCamera -{ - //cameras - NSArray* cameras = nil; - - //grab all cameras - cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"cameras: %@", cameras]); - #endif - - //look for camera that belongs to apple - for(AVCaptureDevice* currentCamera in cameras) - { - //check if apple - if(YES == [currentCamera.manufacturer isEqualToString:@"Apple Inc."]) - { - //save - self.camera = currentCamera; - - //exit loop - break; - } - } - - //didn't find apple - // ->grab default camera - if(nil == self.camera) - { - //get default - self.camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"didn't find apple camera, grabbed default: %@", self.camera]); - #endif - } - - return; -} - -//grab first apple mic -// ->saves into iVar 'mic' --(void)findAppleMic -{ - //mics - NSArray* mics = nil; - - //grab all mics - mics = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"mics: %@", mics]); - #endif - - //look for mic that belongs to apple - for(AVCaptureDevice* currentMic in mics) - { - //check if apple - // ->also check input source - if( (YES == [currentMic.manufacturer isEqualToString:@"Apple Inc."]) && - (YES == [[[currentMic activeInputSource] inputSourceID] isEqualToString:@"imic"]) ) - { - //save - self.mic = currentMic; - - //exit loop - break; - } - } - - //didn't find apple - // ->grab default camera - if(nil == self.mic) - { - //get default - self.mic = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"didn't find apple 'imic', grabbed default: %@", self.mic]); - #endif - } - - return; -} - -//initialiaze AV notifcations/callbacks --(BOOL)monitor -{ - //return var - BOOL bRet = NO; - - //status/err var - BOOL wasErrors = NO; - - //xpc connection - __block NSXPCConnection* xpcConnection = nil; - - //device's connection id - unsigned int connectionID = 0; - - //selector for getting device id - SEL methodSelector = nil; - - //array for devices + status - NSMutableArray* devices = nil; - - //wait semaphore - dispatch_semaphore_t waitSema = nil; - - //alloc XPC connection - xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; - - //alloc device array - devices = [NSMutableArray array]; - - //set remote object interface - xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //resume - [xpcConnection resume]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"telling XPC service to begin base-lining mach messages"); - #endif - - //init wait semaphore - waitSema = dispatch_semaphore_create(0); - - //XPC service to begin baselining mach messages - // ->wait, since want this to compelete before doing other things! - [[xpcConnection remoteObjectProxy] initialize:^ - { - //signal sema - dispatch_semaphore_signal(waitSema); - - }]; - - //wait until XPC is done - // ->XPC reply block will signal semaphore - dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); - - //init selector - methodSelector = NSSelectorFromString(@"connectionID"); - - //find (first) apple camera - // ->saves camera into iVar, 'camera' - [self findAppleCamera]; - - //find (first) apple mic - // ->saves mic into iVar, 'mic' - [self findAppleMic]; - - //got camera - // ->grab connection ID and invoke helper functions - if( (nil != self.camera) && - (YES == [self.camera respondsToSelector:methodSelector]) ) - { - //ignore leak warning - // ->we know what we're doing via this 'performSelector' - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Warc-performSelector-leaks" - - //grab connection ID - connectionID = (unsigned int)[self.camera performSelector:methodSelector withObject:nil]; - - //restore - #pragma clang diagnostic pop - - //set status - // ->will set 'videoActive' iVar - [self setVideoDevStatus:connectionID]; - - //if video is already active - // ->set status & start monitoring thread - if(YES == self.videoActive) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"video already active, so will start polling for new video procs"); - #endif - - //tell XPC video is active - [[xpcConnection remoteObjectProxy] updateVideoStatus:self.videoActive reply:^{ - - //signal sema - dispatch_semaphore_signal(waitSema); - }]; - - //wait until XPC is done - // ->XPC reply block will signal semaphore - dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); - - //alloc - videoMonitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(monitor4Procs) object:nil]; - - //start - [self.videoMonitorThread start]; - } - - //save camera/status into device array - [devices addObject:@{EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:@(self.videoActive)}]; - - //register for video events - if(YES != [self watchVideo:connectionID]) - { - //err msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"failed to watch for video events"); - #endif - - //set err - wasErrors = YES; - - //don't bail - // ->can still listen for audio events - } - } - //err msg - else - { - //err msg - logMsg(LOG_ERR, @"failed to find (apple) camera :("); - - //don't bail - // ->can still listen for audio events - } - - //watch mic - // ->grab connection ID and invoke helper function - if( (nil != self.mic) && - (YES == [self.mic respondsToSelector:methodSelector]) ) - { - //ignore leak warning - // ->we know what we're doing via this 'performSelector' - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Warc-performSelector-leaks" - - //grab connection ID - connectionID = (unsigned int)[self.mic performSelector:NSSelectorFromString(@"connectionID") withObject:nil]; - - //restore - #pragma clang diagnostic pop - - //set status - // ->will set 'videoActive' iVar - [self setAudioDevStatus:connectionID]; - - //if audio is already active - // ->tell XPC that it's active - // TODO: monitor for piggybacking? - if(YES == self.audioActive) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"audio already active"); - #endif - - //tell XPC audio is active - [[xpcConnection remoteObjectProxy] updateAudioStatus:self.audioActive reply:^{ - - //signal sema - dispatch_semaphore_signal(waitSema); - - }]; - - //wait until XPC is done - // ->XPC reply block will signal semaphore - dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); - } - - //save mic/status into device array - [devices addObject:@{EVENT_DEVICE:self.mic, EVENT_DEVICE_STATUS:@(self.audioActive)}]; - - //register for audio events - if(YES != [self watchAudio:connectionID]) - { - //err msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"failed to watch for audio events"); - #endif - - //set err - wasErrors = YES; - } - } - - //err msg - else - { - //err msg - logMsg(LOG_ERR, @"failed to find (apple) mic :("); - } - - //send msg to status menu - // ->update menu to show devices & their status - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).statusBarMenuController updateStatusItemMenu:devices]; - - //make sure no errors occured - if(YES != wasErrors) - { - //happy - bRet = YES; - } - - //cleanup XPC - if(nil != xpcConnection) - { - //close connection - [xpcConnection invalidate]; - - } - - return bRet; -} - -//determine if video is active -// ->sets 'videoActive' iVar --(void)setVideoDevStatus:(CMIODeviceID)deviceID -{ - //status var - OSStatus status = -1; - - //running flag - UInt32 isRunning = -1; - - //size of query flag - UInt32 propertySize = 0; - - //property address struct - CMIOObjectPropertyAddress propertyStruct = {0}; - - //init size - propertySize = sizeof(isRunning); - - //init property struct's selector - propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere; - - //init property struct's scope - propertyStruct.mScope = kCMIOObjectPropertyScopeGlobal; - - //init property struct's element - propertyStruct.mElement = 0; - - //query to get 'kAudioDevicePropertyDeviceIsRunningSomewhere' status - status = CMIOObjectGetPropertyData(deviceID, &propertyStruct, 0, NULL, sizeof(kAudioDevicePropertyDeviceIsRunningSomewhere), &propertySize, &isRunning); - if(noErr != status) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"getting status of video device failed with %d", status]); - - //set error - isRunning = -1; - - //bail - goto bail; - } - - //set iVar - self.videoActive = isRunning; - -//bail -bail: - - return; -} - -//helper function -// determines if video went active/inactive then invokes notification generator method --(void)handleVideoNotification:(CMIOObjectID)deviceID addresses:(const CMIOObjectPropertyAddress[]) addresses -{ - //event dictionary - NSMutableDictionary* event = nil; - - //xpc connection - __block NSXPCConnection* xpcConnection = nil; - - //wait semaphore - dispatch_semaphore_t waitSema = nil; - - //devices - NSMutableArray* devices = nil; - - //init dictionary for event - event = [NSMutableDictionary dictionary]; - - //init array for devices - devices = [NSMutableArray array]; - - //sync - @synchronized (self) - { - - //set status - // ->sets 'videoActive' iVar - [self setVideoDevStatus:deviceID]; - - //add camera - if(nil != self.camera) - { - //add - [devices addObject:@{EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:@(self.videoActive)}]; - } - - //add mic - if(nil != self.mic) - { - //add - [devices addObject:@{EVENT_DEVICE:self.mic, EVENT_DEVICE_STATUS:@(self.audioActive)}]; - } - - //update status menu - // run on main thread, since its a UI update - dispatch_async(dispatch_get_main_queue(), ^{ - - //update status menu - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).statusBarMenuController updateStatusItemMenu:devices]; - - }); - - //add timestamp - event[EVENT_TIMESTAMP] = [NSDate date]; - - //add device - event[EVENT_DEVICE] = self.camera; - - //set device status - event[EVENT_DEVICE_STATUS] = [NSNumber numberWithInt:self.videoActive]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got video change notification; is running? %x", self.videoActive]); - #endif - - //alloc XPC connection - xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; - - //set remote object interface - xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //resume - [xpcConnection resume]; - - //init wait semaphore - waitSema = dispatch_semaphore_create(0); - - //tell XPC about video status - // ->for example, when video is active, will stop baselining - [[xpcConnection remoteObjectProxy] updateVideoStatus:self.videoActive reply:^{ - - //signal sema - dispatch_semaphore_signal(waitSema); - - }]; - - //wait until XPC is done - // ->XPC reply block will signal semaphore - dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); - - //if video just started - // ->ask for video procs from XPC - if(YES == self.videoActive) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"video is active, so querying XPC to get video process(s)"); - #endif - - //set allowed classes - [xpcConnection.remoteObjectInterface setClasses: [NSSet setWithObjects: [NSMutableArray class], [NSNumber class], nil] - forSelector: @selector(getVideoProcs:reply:) argumentIndex: 0 ofReply: YES]; - - //invoke XPC service - [[xpcConnection remoteObjectProxy] getVideoProcs:NO reply:^(NSMutableArray* videoProcesses) - { - //close connection - [xpcConnection invalidate]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"video procs from XPC: %@", videoProcesses]); - #endif - - //generate notification for each process - for(NSNumber* processID in videoProcesses) - { - //set pid - event[EVENT_PROCESS_ID] = processID; - - //generate notification - // do on main thread since its a UI event - dispatch_async(dispatch_get_main_queue(), ^{ - - //generate notification - [self generateNotification:event]; - - }); - - } - - //if no consumer process was found - // ->still alert user that webcam was activated, but without details/ability to block - if(0 == videoProcesses.count) - { - //set pid - event[EVENT_PROCESS_ID] = @0; - - //generate notification - // do on main thread since its a UI event - dispatch_async(dispatch_get_main_queue(), ^{ - - //generate notification - [self generateNotification:event]; - - }); - } - - //signal sema - dispatch_semaphore_signal(waitSema); - - }]; - - //wait until XPC is done - // ->XPC reply block will signal semaphore - dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); - } - - //video deactivated - // ->close XPC connection and alert user - else - { - //close connection - [xpcConnection invalidate]; - - //generate notification - // do on main thread since its a UI event - dispatch_async(dispatch_get_main_queue(), ^{ - - //generate notification - [self generateNotification:event]; - - }); - - } - - //poll for new video procs - // ->this thread will exit itself as its checks the 'videoActive' iVar - if(YES == self.videoActive) - { - //start monitor thread if needed - if(YES != videoMonitorThread.isExecuting) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"(re)Starting polling/monitor thread"); - #endif - - //alloc - videoMonitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(monitor4Procs) object:nil]; - - //start - [self.videoMonitorThread start]; - } - //no need to restart - else - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"polling/monitor thread still running"); - #endif - } - } - - }//sync - -//bail -bail: - - return; -} - -//register for video notifcations -// ->block will invoke method on event --(BOOL)watchVideo:(CMIOObjectID)deviceID -{ - //ret var - BOOL bRegistered = NO; - - //status var - OSStatus status = -1; - - //property struct - CMIOObjectPropertyAddress propertyStruct = {0}; - - //init property struct's selector - propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere; - - //init property struct's scope - propertyStruct.mScope = kAudioObjectPropertyScopeGlobal; - - //init property struct's element - propertyStruct.mElement = kAudioObjectPropertyElementMaster; - - //block - // ->invoked when video changes & just calls helper function - CMIOObjectPropertyListenerBlock listenerBlock = ^(UInt32 inNumberAddresses, const CMIOObjectPropertyAddress addresses[]) - { - //on main thread? - // handle on background thread - if(YES == [NSThread isMainThread]) - { - //invoke in background - // XPC stuff might be slow! - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - - //handle notification - [self handleVideoNotification:deviceID addresses:addresses]; - - }); - } - //on background thread - // just can invoke as it - else - { - //handle notification - [self handleVideoNotification:deviceID addresses:addresses]; - } - }; - - //register (add) property block listener - status = CMIOObjectAddPropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_main_queue(), listenerBlock); - if(noErr != status) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"CMIOObjectAddPropertyListenerBlock() failed with %d", status]); - - //bail - goto bail; - } - - //happy - bRegistered = YES; - -//bail -bail: - - return bRegistered; -} - -//determine if audio is active -// ->sets 'audioActive' iVar --(void)setAudioDevStatus:(AudioObjectID)deviceID -{ - //status var - OSStatus status = -1; - - //running flag - UInt32 isRunning = -1; - - //size of query flag - UInt32 propertySize = 0; - - //init size - propertySize = sizeof(isRunning); - - //query to get 'kAudioDevicePropertyDeviceIsRunningSomewhere' status - status = AudioDeviceGetProperty(deviceID, 0, false, kAudioDevicePropertyDeviceIsRunningSomewhere, &propertySize, &isRunning); - if(noErr != status) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"getting status of audio device failed with %d", status]); - - //set error - isRunning = -1; - - //bail - goto bail; - } - - //set iVar - self.audioActive = isRunning; - -//bail -bail: - - return; -} - -//helper function -// ->determines if audio went active/inactive then invokes notification generator method --(void)handleAudioNotification:(AudioObjectID)deviceID -{ - //event dictionary - NSMutableDictionary* event = nil; - - //devices - NSMutableArray* devices = nil; - - //xpc connection - __block NSXPCConnection* xpcConnection = nil; - - //wait semaphore - dispatch_semaphore_t waitSema = nil; - - //init dictionary - event = [NSMutableDictionary dictionary]; - - //init array for devices - devices = [NSMutableArray array]; - - //sync - @synchronized (self) - { - - //set status - // ->updates 'audioActive' iVar - [self setAudioDevStatus:deviceID]; - - //add camera - if(nil != self.camera) - { - //add - [devices addObject:@{EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:@(self.videoActive)}]; - } - - //add mic - if(nil != self.mic) - { - //add - [devices addObject:@{EVENT_DEVICE:self.mic, EVENT_DEVICE_STATUS:@(self.audioActive)}]; - } - - //update status menu - // run on main thread, since its a UI update - dispatch_async(dispatch_get_main_queue(), ^{ - - //update status menu - [((AppDelegate*)[[NSApplication sharedApplication] delegate]).statusBarMenuController updateStatusItemMenu:devices]; - - }); - - //add timestamp - event[EVENT_TIMESTAMP] = [NSDate date]; - - //add device - event[EVENT_DEVICE] = self.mic; - - //set device status - event[EVENT_DEVICE_STATUS] = [NSNumber numberWithInt:self.audioActive]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got audio change notification; is running? %x", self.audioActive]); - #endif - - //alloc XPC connection - xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; - - //set remote object interface - xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //resume - [xpcConnection resume]; - - //init wait semaphore - waitSema = dispatch_semaphore_create(0); - - //tell XPC about audio status - // ->for example, when audio is active, will stop baselining - [[xpcConnection remoteObjectProxy] updateAudioStatus:self.audioActive reply:^{ - - //signal sema - dispatch_semaphore_signal(waitSema); - - }]; - - //wait until XPC is done - // ->XPC reply block will signal semaphore - dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); - - //if audio just started - // ->ask for audio procs from XPC - if(YES == self.audioActive) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"audio is active, so querying XPC to get audio process(s)"); - #endif - - //set allowed classes - [xpcConnection.remoteObjectInterface setClasses: [NSSet setWithObjects: [NSMutableArray class], [NSNumber class], nil] - forSelector: @selector(getAudioProcs:) argumentIndex: 0 ofReply: YES]; - - //invoke XPC service - [[xpcConnection remoteObjectProxy] getAudioProcs:^(NSMutableArray* audioProcesses) - { - //close connection - [xpcConnection invalidate]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"audio procs from XPC: %@", audioProcesses]); - #endif - - //generate notification for each process - for(NSNumber* processID in audioProcesses) - { - //set pid - event[EVENT_PROCESS_ID] = processID; - - //generate notification - // do on main thread since its a UI event - dispatch_async(dispatch_get_main_queue(), ^{ - - //generate notification - [self generateNotification:event]; - - }); - } - - //if no consumer process was found - // ->still alert user that webcam was activated, but without details/ability to block - if(0 == audioProcesses.count) - { - //set pid - event[EVENT_PROCESS_ID] = @0; - - //generate notification - // do on main thread since its a UI event - dispatch_async(dispatch_get_main_queue(), ^{ - - //generate notification - [self generateNotification:event]; - - }); - } - - //signal sema - dispatch_semaphore_signal(waitSema); - - }]; - - //wait until XPC is done - // ->XPC reply block will signal semaphore - dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); - } - - //audio deactivated - // ->close XPC connection and alert user - else - { - //close connection - [xpcConnection invalidate]; - - //generate notification - // do on main thread since its a UI event - dispatch_async(dispatch_get_main_queue(), ^{ - - //generate notification - [self generateNotification:event]; - - }); - } - - }//sync - -//bail -bail: - - return; -} - -//register for audio notifcations -// ->block will invoke method on event --(BOOL)watchAudio:(AudioObjectID)deviceID -{ - //ret var - BOOL bRegistered = NO; - - //status var - OSStatus status = -1; - - //property struct - AudioObjectPropertyAddress propertyStruct = {0}; - - //init property struct's selector - propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere; - - //init property struct's scope - propertyStruct.mScope = kAudioObjectPropertyScopeGlobal; - - //init property struct's element - propertyStruct.mElement = kAudioObjectPropertyElementMaster; - - //block - // ->invoked when audio changes & just calls helper function - AudioObjectPropertyListenerBlock listenerBlock = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses) - { - //on main thread? - // handle on background thread - if(YES == [NSThread isMainThread]) - { - //invoke in background - // XPC stuff might be slow! - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - - //handle notification - [self handleAudioNotification:deviceID]; - - }); - } - //on background thread - // just can invoke as it - else - { - //handle notification - [self handleAudioNotification:deviceID]; - } - }; - - //add property listener for audio changes - status = AudioObjectAddPropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_main_queue(), listenerBlock); - if(noErr != status) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"AudioObjectAddPropertyListenerBlock() failed with %d", status]); - - //bail - goto bail; - } - - //happy - bRegistered = YES; - -//bail -bail: - - return bRegistered; -} - -//build and display notification -// ->handles extra logic like ignore whitelisted apps, disable alerts (if user has turned that off), etc --(void)generateNotification:(NSMutableDictionary*)event -{ - //pool - @autoreleasepool { - - //notification - NSUserNotification* notification = nil; - - //device - // ->audio or video - NSNumber* deviceType = nil; - - //title - NSMutableString* title = nil; - - //details - // ->just name of device for now - NSString* details = nil; - - //process name - NSString* processName = nil; - - //process path - NSString* processPath = nil; - - //log msg - NSMutableString* logMessage = nil; - - //preferences - NSDictionary* preferences = nil; - - //flag - // ->was an activation alert shown - BOOL matchingActivationAlert = NO; - - //flag - // ->is the activation alert still visible - BOOL activationAlertVisible = NO; - - //alloc notificaiton - notification = [[NSUserNotification alloc] init]; - - //alloc title - title = [NSMutableString string]; - - //alloc log msg - logMessage = [NSMutableString string]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"generating notification for %@", event]); - #endif - - //get process name - processName = getProcessName([event[EVENT_PROCESS_ID] intValue]); - - //get process path - processPath = getProcessPath([event[EVENT_PROCESS_ID] intValue]); - if(nil == processPath) - { - //set to something - processPath = PROCESS_UNKNOWN; - } - - //set device and title for audio - if(YES == [event[EVENT_DEVICE] isKindOfClass:NSClassFromString(@"AVCaptureHALDevice")]) - { - //add - [title appendString:@"Audio Device"]; - - //set device - deviceType = SOURCE_AUDIO; - } - //set device and title for video - else - { - //add - [title appendString:@"Video Device"]; - - //set device - deviceType = SOURCE_VIDEO; - } - - //ignore whitelisted processes - // ->for activation events, can check process path - if(YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) - { - //check each - // ->need match on path and device (camera || mic) - for(NSDictionary* item in self.whiteList) - { - //check path & device - if( (YES == [item[EVENT_PROCESS_PATH] isEqualToString:processPath]) && - ([item[EVENT_DEVICE] intValue] == deviceType.intValue) ) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"activation alert for process %@ is whitelisted, so ignoring", item]); - #endif - - //bail - goto bail; - } - } - } - //ignore whitelisted processes - // ->for deactivation, ignore when no activation alert was shown (cuz process will have likely died, so no pid/path, etc) - if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) - { - //any active alerts? - // ->note: we make to check if the type matches - for(NSUUID* key in self.activationAlerts.allKeys) - { - //same type? - if(event[EVENT_DEVICE] == self.activationAlerts[key][EVENT_DEVICE]) - { - //got match - matchingActivationAlert = YES; - - //no need to check more - break; - } - } - - //no match? - // ->bail, as means process was whitelisted so no activation was shown - if(YES != matchingActivationAlert) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"deactivation alert doesn't have an activation alert (whitelisted?), so ignoring"); - #endif - - //bail - goto bail; - } - } - - //check if event is essentially a duplicate (facetime, etc) - if(nil != self.lastEvent) - { - //less than 10 second ago? - if(fabs([self.lastEvent[EVENT_TIMESTAMP] timeIntervalSinceDate:event[EVENT_TIMESTAMP]]) < 10) - { - //same process/device/action - if( (YES == [self.lastEvent[EVENT_PROCESS_ID] isEqual:event[EVENT_PROCESS_ID]]) && - (YES == [self.lastEvent[EVENT_DEVICE] isEqual:event[EVENT_DEVICE]]) && - (YES == [self.lastEvent[EVENT_DEVICE_STATUS] isEqual:event[EVENT_DEVICE_STATUS]]) ) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"alert for %@ would be same as previous (%@), so ignoring", event, self.lastEvent]); - #endif - - //update - self.lastEvent = event; - - //bail to ignore - goto bail; - } - } - - }//'same' event check - - //update last event - self.lastEvent = event; - - //always (manually) load preferences - preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]]; - - //check if user wants to ingnore inactive alerts - if( (YES == [preferences[PREF_DISABLE_INACTIVE] boolValue]) && - (YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) ) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"user has decided to ingore 'inactive' events, so ingoring A/V going to disable state"); - #endif - - //bail - goto bail; - } - - //add action - // ->device went inactive - if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) - { - //add - [title appendString:@" became inactive"]; - } - //add action - // ->device went active - else - { - //add - [title appendString:@" became active"]; - } - - //set details - // ->name of device - details = ((AVCaptureDevice*)event[EVENT_DEVICE]).localizedName; - - //ALERT 1: - // ->activate alert with lots of info - if( (YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) && - (0 != [event[EVENT_PROCESS_ID] intValue]) ) - - { - //set other button title - notification.otherButtonTitle = @"allow"; - - //set action title - notification.actionButtonTitle = @"block"; - - //remove action button - notification.hasActionButton = YES; - - //set pid/name/device into user info - // ->allows code to whitelist proc and/or kill proc (later) if user clicks 'block' - notification.userInfo = @{EVENT_PROCESS_ID:event[EVENT_PROCESS_ID], EVENT_PROCESS_PATH:processPath, EVENT_PROCESS_NAME:processName, EVENT_DEVICE:deviceType, EVENT_ALERT_TYPE:ALERT_ACTIVATE}; - - //set details - // ->name of process using it / icon too? - [notification setInformativeText:[NSString stringWithFormat:@"process: %@ (%@)", processName, event[EVENT_PROCESS_ID]]]; - } - - //ALERT 2: - // ->activate alert, with no process info - else if( (YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) && - (0 == [event[EVENT_PROCESS_ID] intValue]) ) - { - //set other button title - notification.otherButtonTitle = @"ok"; - - //remove action button - notification.hasActionButton = NO; - - //set type - notification.userInfo = @{EVENT_DEVICE:deviceType, EVENT_ALERT_TYPE:ALERT_ACTIVATE}; - } - - //ALERT 3: - // ->inactive alert, with no process info - else - { - //set other button title - notification.otherButtonTitle = @"ok"; - - //remove action button - notification.hasActionButton = NO; - - //set type - notification.userInfo = @{EVENT_DEVICE:deviceType, EVENT_ALERT_TYPE:ALERT_INACTIVE}; - } - - //log event? - if(YES == [preferences[PREF_LOG_ACTIVITY] boolValue]) - { - //no process? - // ->just add title / details - if( (nil == processName) || - (YES == [processName isEqualToString:PROCESS_UNKNOWN]) ) - { - //add - [logMessage appendFormat:@"%@ (%@)", title, details]; - } - - //process - // ->add title / details / process name / process path - else - { - //add - [logMessage appendFormat:@"%@ (%@, process: %@, %@)", title, details, processName, processPath]; - } - - //log msg - logMsg(LOG_DEBUG|LOG_TO_FILE, logMessage); - } - - //set title - [notification setTitle:title]; - - //set subtitle - [notification setSubtitle:details]; - - //set id - notification.identifier = [[NSUUID UUID] UUIDString]; - - //set notification - [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; - - //deliver notification - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; - - //save displayed activation alerts - if(YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) - { - //save - self.activationAlerts[notification.identifier] = event; - } - - //for 'went inactive' notification automatically close - // ->unless there still is an active alert on the screen - if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) - { - //any active alerts still visible? - // ->note: we make to check if the type matches and still visible - for(NSUUID* key in self.activationAlerts.allKeys) - { - //same type - if( (event[EVENT_DEVICE] == self.activationAlerts[key][EVENT_DEVICE]) && - (YES != [self.activationAlerts[key][EVENT_ALERT_CLOSED] boolValue]) ) - { - //set flag - activationAlertVisible = YES; - - //no need to check more - break; - } - } - - //auto close if activation alert isn't still visible - if(YES != activationAlertVisible) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"automatically closing deactivation alert"); - #endif - - //delay, then close - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - - //close - [NSUserNotificationCenter.defaultUserNotificationCenter removeDeliveredNotification:notification]; - - }); - } - } - - }//pool - -//bail -bail: - - return; -} - -//always present notifications --(BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification -{ - return YES; -} - -//automatically invoked when user interacts w/ the notification popup -// ->handle rule creation, blocking/killing proc, etc --(void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification -{ - //pool - @autoreleasepool { - - //xpc connection - __block NSXPCConnection* xpcConnection = nil; - - //process id - NSNumber* processID = nil; - - //device type - NSNumber* deviceType = nil; - - //preferences - NSDictionary* preferences = nil; - - //log msg - NSMutableString* logMessage = nil; - - //remember popup/window controller - RememberWindowController* rememberWindowController = nil; - - //always (manually) load preferences - preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]]; - - //alloc log msg - logMessage = [NSMutableString string]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"user responded to notification: %@", notification]); - #endif - - //ignore if this notification was already seen - // ->need this logic, since have to determine if 'allow' was invoke indirectly - if(nil != self.lastNotification) - { - //same? - if(YES == [self.lastNotification isEqualToString:notification.identifier]) - { - //update - self.lastNotification = notification.identifier; - - //ignore - goto bail; - } - } - - //when it's a deactivation alert - // ->remove all activation alerts that match the same type - if(ALERT_INACTIVE.intValue == [notification.userInfo[EVENT_ALERT_TYPE] intValue]) - { - //remove any active alerts - // ->note: we make sure to check if the type matches - for(NSUUID* key in self.activationAlerts.allKeys) - { - //check stored activation alert type - // ->audio - if(YES == [self.activationAlerts[key][EVENT_DEVICE] isKindOfClass:NSClassFromString(@"AVCaptureHALDevice")]) - { - //set device - deviceType = SOURCE_AUDIO; - } - //check stored activation alert type - // ->video - else - { - //set device - deviceType = SOURCE_VIDEO; - } - - //same type? - if([notification.userInfo[EVENT_DEVICE] intValue] == deviceType.intValue) - { - //remove - [self.activationAlerts removeObjectForKey:key]; - } - } - } - - //activation alert that user has interacted with - // ->this means it's going to close, to we indicate that - else - { - //set flag - self.activationAlerts[notification.identifier][EVENT_ALERT_CLOSED] = @YES; - } - - //update - self.lastNotification = notification.identifier; - - //for alerts without an action - // ->don't need to do anything! - if(YES != notification.hasActionButton) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"popup without an action, no need to do anything"); - #endif - - //bail - goto bail; - } - - //log user's response? - if(YES == [preferences[PREF_LOG_ACTIVITY] boolValue]) - { - //user clicked 'block' - if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) - { - //add - [logMessage appendFormat:@"user clicked 'block' for %@", notification.userInfo]; - } - //user clicked 'allow' - else - { - //add - [logMessage appendFormat:@"user clicked 'allow' for %@", notification.userInfo]; - } - - //log - logMsg(LOG_DEBUG|LOG_TO_FILE, logMessage); - } - - //check if user clicked 'allow' via user info (since OS doesn't directly deliver this) - // ->show a popup w/ option to remember ('whitelist') the application - // don't do this for 'block' since that kills the app, so obv, that'd be bad to *always* do! - if(NSUserNotificationActivationTypeAdditionalActionClicked == [notification.userInfo[@"activationType"] integerValue]) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"user clicked 'allow'"); - #endif - - //can't remember process that we didn't find the path for - if(YES == [notification.userInfo[EVENT_PROCESS_PATH] isEqualToString:PROCESS_UNKNOWN]) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"don't have a process path, so not displaying whitelisting popup"); - #endif - - //bail - goto bail; - } - - //alloc/init - rememberWindowController = [[RememberWindowController alloc] initWithWindowNibName:@"RememberPopup"]; - - //center window - [[rememberWindowController window] center]; - - //show it - [rememberWindowController showWindow:self]; - - //manually configure - // ->invoke here as the outlets will be set - [rememberWindowController configure:notification avMonitor:self]; - - //make it key window - [rememberWindowController.window makeKeyAndOrderFront:self]; - - //make window front - [NSApp activateIgnoringOtherApps:YES]; - - //save reference to window - // ->otherwise memory is freed/window not shown :/ - @synchronized (self) - { - //save - [self.rememberPopups addObject:rememberWindowController]; - } - } - - //when user clicks 'block' - // ->kill the process to block it - else if(NSUserNotificationActivationTypeActionButtonClicked == notification.activationType) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"user clicked 'block'"); - #endif - - //extract process id - processID = notification.userInfo[EVENT_PROCESS_ID]; - if(nil == processID) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to extract process id from notification, %@", notification.userInfo]); - - //bail - goto bail; - } - - //alloc XPC connection - xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; - - //set remote object interface - xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //resume - [xpcConnection resume]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"invoking XPC method to kill: %@", processID]); - #endif - - //invoke XPC method 'killProcess' to terminate - [[xpcConnection remoteObjectProxy] killProcess:processID reply:^(BOOL wasKilled) - { - //check for err - if(YES != wasKilled) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to kill/block: %@", processID]); - } - - //close connection - [xpcConnection invalidate]; - - }]; - - }//user clicked 'block' - - }//pool - -//bail -bail: - - return; -} - -//manually monitor delivered notifications to see if user closes alert -// ->can't detect 'allow' otherwise :/ (see: http://stackoverflow.com/questions/21110714/mac-os-x-nsusernotificationcenter-notification-get-dismiss-event-callback) --(void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification -{ - //flag - __block BOOL notificationStillPresent; - - //user dictionary - __block NSMutableDictionary* userInfo = nil; - - //monitor in background to see if alert was dismissed - // ->invokes normal 'didActivateNotification' callback when alert is dimsissed - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - //monitor all delivered notifications until it goes away - do { - - //reset - notificationStillPresent = NO; - - //check all delivered notifications - for (NSUserNotification *nox in [[NSUserNotificationCenter defaultUserNotificationCenter] deliveredNotifications]) - { - //check - if(YES == [nox.identifier isEqualToString:notification.identifier]) - { - //found! - notificationStillPresent = YES; - - //exit loop - break; - } - } - - //nap if notification is still there - if(YES == notificationStillPresent) - { - //nap - [NSThread sleepForTimeInterval:0.25f]; - } - - //keep monitoring until its gone - } while(YES == notificationStillPresent); - - //alert was dismissed - // ->invoke 'didActivateNotification' to process if it was an 'allow/block' alert - dispatch_async(dispatch_get_main_queue(), - ^{ - //add extra info for activation alerts - // ->will allow callback to identify we delivered via this mechanism - if(YES == notification.hasActionButton) - { - //grab user info dictionary - userInfo = [notification.userInfo mutableCopy]; - - //add activation type - userInfo[@"activationType"] = [NSNumber numberWithInteger:NSUserNotificationActivationTypeAdditionalActionClicked]; - - //update - notification.userInfo = userInfo; - } - - //deliver - [self userNotificationCenter:center didActivateNotification:notification]; - }); - }); - - return; -} - -//monitor for new procs (video only at the moment) -// runs until video is no longer in use (set elsewhere) --(void)monitor4Procs -{ - - //pool - @autoreleasepool { - - //xpc connection - NSXPCConnection* xpcConnection = nil; - - //wait semaphore - dispatch_semaphore_t waitSema = nil; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"[MONITOR THREAD] video is active, so polling for new procs"); - #endif - - //poll while video is active - while(YES == self.videoActive) - { - //pool - @autoreleasepool { - - //init wait semaphore - waitSema = dispatch_semaphore_create(0); - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"[MONITOR THREAD] (re)Asking XPC for (new) video procs"); - #endif - - //alloc XPC connection - xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; - - //set remote object interface - xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //set classes - // ->arrays/numbers ok to vend - [xpcConnection.remoteObjectInterface setClasses: [NSSet setWithObjects: [NSMutableArray class], [NSNumber class], nil] - forSelector: @selector(getVideoProcs:reply:) argumentIndex: 0 ofReply: YES]; - //resume - [xpcConnection resume]; - - - //invoke XPC service to get (new) video procs - // ->will generate user notifications for any new processes - [[xpcConnection remoteObjectProxy] getVideoProcs:YES reply:^(NSMutableArray* videoProcesses) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"[MONITOR THREAD] found %lu new video procs: %@", (unsigned long)videoProcesses.count, videoProcesses]); - #endif - - //generate a notification for each process - // ->double check video is still active though... - for(NSNumber* processID in videoProcesses) - { - //check video - if(YES != self.videoActive) - { - //exit loop - break; - } - - //generate notification - [self generateNotification:[@{EVENT_TIMESTAMP:[NSDate date], EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:DEVICE_ACTIVE, EVENT_PROCESS_ID:processID} mutableCopy]]; - } - - //signal sema - dispatch_semaphore_signal(waitSema); - - //invalidate - [xpcConnection invalidate]; - - }]; - - //wait until XPC is done - // ->XPC reply block will signal semaphore - dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); - - }//pool - - //nap - [NSThread sleepForTimeInterval:5.0f]; - - }//run until video (camera) is off - - //pool - } - -bail: - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"[MONITOR THREAD] exiting polling/monitor thread since camera is off"); - #endif - - return; -} - -@end diff --git a/LoginItem/AppDelegate.h b/LoginItem/AppDelegate.h deleted file mode 100644 index ff9aba7..0000000 --- a/LoginItem/AppDelegate.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// AppDelegate.h -// Test Application Helper -// -// Created by Patrick Wardle on 9/10/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "AVMonitor.h" -#import "EventMonitor.h" -#import "StatusBarMenu.h" -#import "InfoWindowController.h" - -#import - -@interface AppDelegate : NSObject - -/* PROPERTIES */ - -//av monitor class -@property(nonatomic, retain)AVMonitor* avMonitor; - -//status bar menu -@property(nonatomic, retain)StatusBarMenu* statusBarMenuController; - -//info window -@property(nonatomic, retain)InfoWindowController* infoWindowController; - -@end - diff --git a/LoginItem/AppDelegate.m b/LoginItem/AppDelegate.m deleted file mode 100644 index 0399523..0000000 --- a/LoginItem/AppDelegate.m +++ /dev/null @@ -1,284 +0,0 @@ -// -// AppDelegate.m -// Login Item, (app helper) -// -// Created by Patrick Wardle on 9/10/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" -#import "AppDelegate.h" -#import "XPCProtocol.h" - -@interface AppDelegate () - -@property (weak) IBOutlet NSWindow *window; -@end - -@implementation AppDelegate - -@synthesize avMonitor; -@synthesize infoWindowController; -@synthesize statusBarMenuController; - -//app's main interface -// ->load status bar and kick off monitor --(void)applicationDidFinishLaunching:(NSNotification *)aNotification -{ - //preferences - NSDictionary* preferences = nil; - - //logged in user info - NSMutableDictionary* userInfo = nil; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"starting login item app logic"); - #endif - - //get user - userInfo = loggedinUser(); - if(nil == userInfo[@"user"]) - { - //err msg - logMsg(LOG_ERR, @"failed to determine logged-in user"); - - //bail - goto bail; - } - - //drop group privs - setgid([userInfo[@"gid"] intValue]); - - //drop user privs - setuid([userInfo[@"uid"] intValue]); - - //load preferences - preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]]; - - //init/load status bar - // ->but only if user didn't say: 'run in headless mode' - if(YES != [preferences[PREF_RUN_HEADLESS] boolValue]) - { - //load - [self loadStatusBar]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"initialized/loaded status bar (icon/menu)"); - #endif - } - #ifdef DEBUG - //dbg msg - else - { - //dbg msg - logMsg(LOG_DEBUG, @"running in headless mode"); - } - #endif - - //check for updates - // ->but only when user has not disabled that feature - if(YES == [preferences[PREF_CHECK_4_UPDATES] boolValue]) - { - //after a minute - //->check for updates in background - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 60 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"checking for update"); - #endif - - //check - [self isThereAnUpdate]; - - }); - } - - //when logging is enabled - // ->open/create log file - if(YES == [preferences[PREF_LOG_ACTIVITY] boolValue]) - { - //init - if(YES != initLogging()) - { - //err msg - logMsg(LOG_ERR, @"failed to init logging"); - - //bail - goto bail; - } - - //dbg msg - // ->and to file - logMsg(LOG_DEBUG|LOG_TO_FILE, @"logging intialized (login item)"); - } - - //spawn 'heartbeat' thread to XPC to keep it open - [NSThread detachNewThreadSelector:@selector(heartBeat) toTarget:self withObject:nil]; - - //create/init av event monitor - avMonitor = [[AVMonitor alloc] init]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"alloc/init'd AV monitor"); - #endif - - //start monitoring - // ->sets up audio/video callbacks - [avMonitor monitor]; - - //log msg - logMsg(LOG_DEBUG|LOG_TO_FILE, @"OverSight starting"); - -//bail -bail: - - return; -} - -//initialize status menu bar --(void)loadStatusBar -{ - //alloc/load nib - statusBarMenuController = [[StatusBarMenu alloc] init]; - - //init menu - [self.statusBarMenuController setupStatusItem]; - - return; -} - -//check for an update --(void)isThereAnUpdate -{ - //version string - NSMutableString* versionString = nil; - - //alloc string - versionString = [NSMutableString string]; - - //check if available version is newer - // ->show update window - if(YES == isNewVersion(versionString)) - { - //new version! - // ->show update popup on main thread - dispatch_sync(dispatch_get_main_queue(), ^{ - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"a new version (%@) is available", versionString]); - #endif - - //alloc/init about window - infoWindowController = [[InfoWindowController alloc] initWithWindowNibName:@"InfoWindow"]; - - //configure - [self.infoWindowController configure:[NSString stringWithFormat:@"a new version (%@) is available!", versionString] buttonTitle:@"update"]; - - //center window - [[self.infoWindowController window] center]; - - //show it - [self.infoWindowController showWindow:self]; - - //invoke function in background that will make window modal - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - //make modal - makeModal(self.infoWindowController); - - }); - - }); - } - - //no new version - // ->just (debug) log msg - #ifdef DEBUG - else - { - //dbg msg - logMsg(LOG_DEBUG, @"no updates available"); - } - #endif - - return; -} - -//ping XPC service to keep it alive --(void)heartBeat -{ - //pool - @autoreleasepool { - - //xpc connection - __block NSXPCConnection* xpcConnection = nil; - - //wait semaphore - dispatch_semaphore_t waitSema = nil; - - //alloc XPC connection - xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; - - //set remote object interface - xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //resume - [xpcConnection resume]; - - //forever - while(YES) - { - //init wait semaphore - waitSema = dispatch_semaphore_create(0); - - #ifdef DEBUG - //dbg msg - logMsg(LOG_DEBUG, @"sending XPC heart beat request"); - #endif - - //XPC service to begin baselining mach messages - // ->wait, since want this to compelete before doing other things! - [[xpcConnection remoteObjectProxy] heartBeat:^(BOOL reply) - { - //signal sema - dispatch_semaphore_signal(waitSema); - - }]; - - //wait until XPC is done - // ->XPC reply block will signal semaphore - dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); - - //nap - [NSThread sleepForTimeInterval:3.0f]; - } - - }//pool - - return; -} - -//going bye-bye -// ->close logging --(void)applicationWillTerminate:(NSNotification *)notification -{ - //log msg - logMsg(LOG_DEBUG|LOG_TO_FILE, @"OverSight ending"); - - //log msg - logMsg(LOG_DEBUG|LOG_TO_FILE, @"logging deinitialized (login item)"); - - //stop logz - deinitLogging(); - - return; -} - -@end diff --git a/LoginItem/Base.lproj/MainMenu.xib b/LoginItem/Base.lproj/MainMenu.xib deleted file mode 100644 index 621613c..0000000 --- a/LoginItem/Base.lproj/MainMenu.xib +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/LoginItem/EventMonitor.h b/LoginItem/EventMonitor.h deleted file mode 100644 index 4e99be0..0000000 --- a/LoginItem/EventMonitor.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// ProcessMonitor.h -// BlockBlock -// -// Created by Patrick Wardle on 10/19/14. -// Copyright (c) 2015 Objective-See. All rights reserved. -// -#import - -//#import "OrderedDictionary.h" - -/* -//custom struct -// format of data that's broadcast from kext -struct processStartEvent -{ - //process pid - // ->id's all chunks - pid_t pid; - - //process uid - uid_t uid; - - //process ppid - pid_t ppid; - - //process path - char path[0]; -}; -*/ - - - -@interface EventMonitor : NSObject -{ - -} - -/* PROPERTIES */ - -//process list -//@property(nonatomic, retain)OrderedDictionary* processList; - -//dictionary of partial events -//@property(nonatomic, retain)NSMutableDictionary* partialProcessEvents; - - -/* METHODS */ - -//kicks off thread to monitor --(BOOL)monitor; - -//thread function -// ->recv() process creation notification events from kext --(void)recvProcNotifications; - -@end diff --git a/LoginItem/EventMonitor.m b/LoginItem/EventMonitor.m deleted file mode 100644 index 880a172..0000000 --- a/LoginItem/EventMonitor.m +++ /dev/null @@ -1,351 +0,0 @@ -// -// ProcessMonitor.m -// BlockBlock -// -// Created by Patrick Wardle on 10/19/14. -// Copyright (c) 2015 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -//#import "Process.h" -#import "Utilities.h" -#import "EventMonitor.h" -//#import "OrderedDictionary.h" - -#import -#import -#import -#import -#import -#import - -//TODO: move into shared file -//vendor id string -#define OBJECTIVE_SEE_VENDOR "com.objectiveSee" - -//process camera event -#define PROCESS_CAMERA_EVENT 0x1 - - -@implementation EventMonitor - -//@synthesize processList; -//@synthesize partialProcessEvents; - -//init --(id)init -{ - //init super - self = [super init]; - if(nil != self) - { - //init process list - //processList = [[OrderedDictionary alloc] init]; - - //init partial process event dictionary - //partialProcessEvents = [NSMutableDictionary dictionary]; - } - - return self; -} - -//kick off threads to monitor -// ->dtrace/audit pipe/app callback --(BOOL)monitor -{ - //return var - BOOL bRet = NO; - - //start thread to get process creation notifications from kext - [NSThread detachNewThreadSelector:@selector(recvProcNotifications) toTarget:self withObject:nil]; - - //no errors - bRet = YES; - -//bail -bail: - - return bRet; -} - -//thread function -// ->recv() process creation notification events --(void)recvProcNotifications -{ - //status var - int status = -1; - - //system socket - int systemSocket = -1; - - //struct for vendor code - // ->set via call to ioctl/SIOCGKEVVENDOR - struct kev_vendor_code vendorCode = {0}; - - //struct for kernel request - // ->set filtering options - struct kev_request kevRequest = {0}; - - //struct for broadcast data from the kext - struct kern_event_msg *kernEventMsg = {0}; - - //message from kext - // ->size is cumulation of header, pid - char kextMsg[KEV_MSG_HEADER_SIZE + sizeof(pid_t)] = {0}; - - //bytes received from system socket - ssize_t bytesReceived = -1; - - //process path - // ->could be partial... - char path[MAXPATHLEN+1] = {0}; - - //length of path bytes - // ->might not be NULL terminated, so have to calc manually - int pathLength = 0; - - //cumulative path - // ->when path is long & thus chunked - NSMutableString* cumulativePath = nil; - - //custom struct - // ->process data from kext - //struct processStartEvent* procStartEvent = NULL; - - //pid of process that triggered alert - pid_t triggerProcess = -1; - - //process info - //NSMutableDictionary* procInfo = nil; - - //process object - //Process* processObj = nil; - - //create system socket - systemSocket = socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT); - if(-1 == systemSocket) - { - //set status var - status = errno; - - //err msg - //logMsg(LOG_ERR, [NSString stringWithFormat:@"socket() failed with %d", status]); - - //bail - goto bail; - } - - //set vendor name string - strncpy(vendorCode.vendor_string, OBJECTIVE_SEE_VENDOR, KEV_VENDOR_CODE_MAX_STR_LEN); - - //get vendor name -> vendor code mapping - status = ioctl(systemSocket, SIOCGKEVVENDOR, &vendorCode); - if(0 != status) - { - //err msg - //logMsg(LOG_ERR, [NSString stringWithFormat:@"ioctl(...,SIOCGKEVVENDOR,...) failed with %d", status]); - - //goto bail; - goto bail; - } - - //init filtering options - // ->only interested in objective-see's events - kevRequest.vendor_code = vendorCode.vendor_code; - - //...any class - kevRequest.kev_class = KEV_ANY_CLASS; - - //...any subclass - kevRequest.kev_subclass = KEV_ANY_SUBCLASS; - - //tell kernel what we want to filter on - status = ioctl(systemSocket, SIOCSKEVFILT, &kevRequest); - if(0 != status) - { - //err msg - //logMsg(LOG_ERR, [NSString stringWithFormat:@"ioctl(...,SIOCSKEVFILT,...) failed with %d", status]); - - //goto bail; - goto bail; - } - - //dbg msg - //logMsg(LOG_DEBUG, @"created system socket & set options, now entering recv() loop"); - - //foreverz - // ->listen/parse process creation events from kext - while(YES) - { - //ask the kext for process began events - // ->will block until event is ready - bytesReceived = recv(systemSocket, kextMsg, sizeof(kextMsg), 0); - - //type cast - // ->to access kev_event_msg header - kernEventMsg = (struct kern_event_msg*)kextMsg; - - //sanity check - // ->make sure data recv'd looks ok, sizewise - if( (bytesReceived < KEV_MSG_HEADER_SIZE) || - (bytesReceived != kernEventMsg->total_size)) - { - //dbg msg - //logMsg(LOG_DEBUG, [NSString stringWithFormat:@"recv count: %d, wanted: %d", (int)bytesReceived, kernEventMsg->total_size]); - - //ignore - continue; - } - - //only care about 'process began' events - if(PROCESS_CAMERA_EVENT != kernEventMsg->event_code) - { - //skip - continue; - } - - //dbg msg - //logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got msg from kernel, size: %d", kernEventMsg->total_size]); - - //zero out process path - bzero(path, sizeof(path)); - - //typecast custom data - // ->begins right after header - //procStartEvent = (struct processStartEvent*)&kernEventMsg->event_data[0]; - - //dbg msg(s) - //logMsg(LOG_DEBUG, [NSString stringWithFormat:@" path (in this chunk): %s \n", procStartEvent->path]); - //logMsg(LOG_DEBUG, [NSString stringWithFormat:@" pid: %d ppid: %d uid: %d\n\n", procStartEvent->pid, procStartEvent->ppid, procStartEvent->uid]); - - //init proc info dictionary - //procInfo = [NSMutableDictionary dictionary]; - - triggerProcess = (pid_t)kernEventMsg->event_data[0]; - - NSLog(@"proc %d triggered alert", triggerProcess); - - /* - //save pid - procInfo[@"pid"] = [NSNumber numberWithInt:procStartEvent->pid]; - - //save uid - procInfo[@"uid"] = [NSNumber numberWithInt:procStartEvent->uid]; - - //save ppid - procInfo[@"ppid"] = [NSNumber numberWithInt:procStartEvent->ppid]; - - //calc number of bytes in path - // ->might not be NULL terminated, so have to do it manually - pathLength = kernEventMsg->total_size - KEV_MSG_HEADER_SIZE - sizeof(pid_t) - sizeof(uid_t) - sizeof(pid_t); - - //sanity check - // ->should never happen... - if(pathLength > MAXPATHLEN) - { - //ignore - continue; - } - - //copy path into buffer - memcpy(path, (const char*)procStartEvent->path, pathLength); - - //NULL terminate it - path[pathLength] = 0x0; - - //final chunk will end in NULL - if(0x0 == *(((char*)kernEventMsg->event_data) + kernEventMsg->total_size-KEV_MSG_HEADER_SIZE-1)) - { - //check if there are path components - // ->pid is key into partial process list - cumulativePath = self.partialProcessEvents[procInfo[@"pid"]]; - - //finalize path - // ->append this last path component chunk - if(nil != cumulativePath) - { - //append - [cumulativePath appendString:[NSString stringWithUTF8String:path]]; - - //set final path - procInfo[@"path"] = cumulativePath; - - //remove entry from partial list - [self.partialProcessEvents removeObjectForKey:procInfo[@"pid"]]; - } - //path wasn't chunked - // ->can just use path as is - else - { - //set final path - procInfo[@"path"] = [NSString stringWithUTF8String:path]; - } - - //dbg msg - //logMsg(LOG_DEBUG, [NSString stringWithFormat:@"new process: %@", procInfo[@"path"]]); - - //now, create process object - processObj = [[Process alloc] initWithPid:procStartEvent->pid infoDictionary:procInfo]; - - //sync process list - // ->then insert it into list - @synchronized(self.processList) - { - //trim list if needed - if(self.processList.count >= PROCESS_LIST_MAX_SIZE) - { - //toss first (oldest) item - [self.processList removeObjectForKey:[self.processList keyAtIndex:0]]; - } - - //insert process at end - [self.processList addObject:processObj forKey:procInfo[@"pid"] atStart:NO]; - - }//sync - - }//final chunk - - //non-final chunk - // ->insert it, or append to partial process list - else - { - //try grab existing (partial) path - cumulativePath = self.partialProcessEvents[procInfo[@"pid"]]; - - //check if there are path components already... - // ->i.e. this is just another chunk, so append - if(nil != cumulativePath) - { - //append - [cumulativePath appendString:[NSString stringWithUTF8String:path]]; - } - //new process - // ->insert into partial process list - else - { - //insert - self.partialProcessEvents[procInfo[@"pid"]] = [NSMutableString stringWithUTF8String:path]; - } - - }//non-final chunk - - */ - - }//while(YES) - -//bail -bail: - - //close socket - if(-1 != systemSocket) - { - //close - close(systemSocket); - } - - return; - -} - -@end diff --git a/LoginItem/Images.xcassets/Contents.json b/LoginItem/Images.xcassets/Contents.json deleted file mode 100644 index da4a164..0000000 --- a/LoginItem/Images.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/LoginItem/Images/blackUmbrella_big.png b/LoginItem/Images/blackUmbrella_big.png deleted file mode 100644 index c1ec779..0000000 Binary files a/LoginItem/Images/blackUmbrella_big.png and /dev/null differ diff --git a/LoginItem/Images/statusIcon.png b/LoginItem/Images/statusIcon.png deleted file mode 100644 index 7d449a0..0000000 Binary files a/LoginItem/Images/statusIcon.png and /dev/null differ diff --git a/LoginItem/Images/statusIcon@2x.png b/LoginItem/Images/statusIcon@2x.png deleted file mode 100644 index 4abfa08..0000000 Binary files a/LoginItem/Images/statusIcon@2x.png and /dev/null differ diff --git a/LoginItem/Images/whiteUmbrella.png b/LoginItem/Images/whiteUmbrella.png deleted file mode 100644 index 498fb70..0000000 Binary files a/LoginItem/Images/whiteUmbrella.png and /dev/null differ diff --git a/LoginItem/RemeberWindowController.h b/LoginItem/RemeberWindowController.h deleted file mode 100644 index 64b16bb..0000000 --- a/LoginItem/RemeberWindowController.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// AboutWindowController.h -// OverSight -// -// Created by Patrick Wardle on 7/15/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -@class AVMonitor; - -@interface RememberWindowController : NSWindowController -{ - -} - -/* PROPERTIES */ - -//process path -// ->used for whitelisting -@property (nonatomic, retain)NSString* processPath; - -//device -// ->used for whitelisting -@property (nonatomic, retain)NSNumber* device; - -//instance of av monitor -@property (nonatomic, retain)AVMonitor* avMonitor; - -//version label/string -@property (weak) IBOutlet NSTextField *windowText; - -/* METHODS */ - -//save stuff into iVars -// ->configure window w/ dynamic text --(void)configure:(NSUserNotification*)notification avMonitor:(AVMonitor*)monitor; - -@end diff --git a/LoginItem/RemeberWindowController.m b/LoginItem/RemeberWindowController.m deleted file mode 100644 index 10c1f02..0000000 --- a/LoginItem/RemeberWindowController.m +++ /dev/null @@ -1,171 +0,0 @@ -// -// RememberWindowController.m -// OverSight -// -// Created by Patrick Wardle on 7/15/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "AVMonitor.h" -#import "Utilities.h" -#import "../Shared/XPCProtocol.h" -#import "RemeberWindowController.h" - - -@implementation RememberWindowController - -@synthesize device; -@synthesize avMonitor; -@synthesize processPath; - -//automatically called when nib is loaded -// ->center window --(void)awakeFromNib -{ - //center - [self.window center]; -} - -//automatically invoked when window is loaded -// ->set to window to white --(void)windowDidLoad -{ - //super - [super windowDidLoad]; - - //make white - [self.window setBackgroundColor: NSColor.whiteColor]; - - return; -} - -//save stuff into iVars -// ->also configure window w/ dynamic text --(void)configure:(NSUserNotification*)notification avMonitor:(AVMonitor*)monitor; -{ - //process name - NSString* processName = nil; - - //device type - NSString* deviceType = nil; - - //save monitor into iVar - self.avMonitor = monitor; - - //grab process name - processName = notification.userInfo[EVENT_PROCESS_NAME]; - - //grab process path - // ->saved into iVar for whitelisting - self.processPath = notification.userInfo[EVENT_PROCESS_PATH]; - - //grab device - // ->saved into iVar for whitelisting - self.device = notification.userInfo[EVENT_DEVICE]; - - //set device type for audio - if(SOURCE_AUDIO.intValue == [self.device intValue]) - { - //set - deviceType = @"mic"; - } - //set device type for mic - else if(SOURCE_VIDEO.intValue == [self.device intValue]) - { - //set - deviceType = @"camera"; - } - - //set text - [self.windowText setStringValue:[NSString stringWithFormat:@"always allow %@ to use the %@?", processName, deviceType]]; - - return; -} - -//automatically invoked when user clicks button 'Allow' --(IBAction)buttonHandler:(id)sender -{ - //xpc connection - __block NSXPCConnection* xpcConnection = nil; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"handling user response for 'allow' popup: %ld", (long)((NSButton*)sender).tag]); - #endif - - //handle 'always allow' (whitelist) button - if(BUTTON_ALWAYS_ALLOW == ((NSButton*)sender).tag) - { - //init XPC - xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; - - //set remote object interface - xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //resume - [xpcConnection resume]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"sending XPC message to whitelist"); - #endif - - //invoke XPC method 'whitelistProcess' to add process to white list - [[xpcConnection remoteObjectProxy] whitelistProcess:self.processPath device:self.device reply:^(BOOL wasWhitelisted) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got XPC response: %d", wasWhitelisted]); - #endif - - //reload whitelist on success - if(YES == wasWhitelisted) - { - //reload AVMonitor's whitelist - [self.avMonitor loadWhitelist]; - } - //err - // ->log msg - else - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to whitelist: %@", self.processPath]); - } - - //close connection - [xpcConnection invalidate]; - - }]; - } - -//bail -bail: - - //always close - [self.window close]; - - return; -} - -//automatically invoked when window is closing -// ->remove self from array --(void)windowWillClose:(NSNotification *)notification -{ - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"window is closing, will remove array reference"); - #endif - - //sync to remove - @synchronized (self.avMonitor) { - - //remove - [self.avMonitor.rememberPopups removeObject:self]; - } - - return; -} - -@end diff --git a/LoginItem/RememberPopup.xib b/LoginItem/RememberPopup.xib deleted file mode 100644 index 13b1e12..0000000 --- a/LoginItem/RememberPopup.xib +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/LoginItem/StatusBarIcon.imageset/Contents.json b/LoginItem/StatusBarIcon.imageset/Contents.json deleted file mode 100644 index f70b71f..0000000 --- a/LoginItem/StatusBarIcon.imageset/Contents.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "images" : [ - { - "idiom" : "mac", - "filename" : "statusIcon.png", - "scale" : "1x" - }, - { - "idiom" : "mac", - "filename" : "statusIcon@2x.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/LoginItem/StatusBarIcon.imageset/statusIcon.png b/LoginItem/StatusBarIcon.imageset/statusIcon.png deleted file mode 100644 index 581cbb9..0000000 Binary files a/LoginItem/StatusBarIcon.imageset/statusIcon.png and /dev/null differ diff --git a/LoginItem/StatusBarIcon.imageset/statusIcon@2x.png b/LoginItem/StatusBarIcon.imageset/statusIcon@2x.png deleted file mode 100644 index bdf0667..0000000 Binary files a/LoginItem/StatusBarIcon.imageset/statusIcon@2x.png and /dev/null differ diff --git a/LoginItem/StatusBarMenu.h b/LoginItem/StatusBarMenu.h deleted file mode 100644 index 3d73392..0000000 --- a/LoginItem/StatusBarMenu.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// StatusBarMenu.h -// OverSight -// -// Created by Patrick Wardle on 1/4/15. -// Copyright (c) 2015 Objective-See. All rights reserved. -// - -#import - -@interface StatusBarMenu : NSObject -{ - -} - -//status item -@property (nonatomic, strong, readwrite) NSStatusItem *statusItem; - -//popover -@property (retain, nonatomic)NSPopover *popover; - -//enabled flag -@property BOOL isEnabled; - -/* METHODS */ - -//setup status item -// ->init button, show popover, etc --(void)setupStatusItem; - -//create/update status item menu --(void)updateStatusItemMenu:(NSArray*)devices; - -//init a menu item --(NSMenuItem*)initializeMenuItem:(NSString*)title action:(SEL)action; - -//menu handler for 'perferences' -// ->show preferences window --(void)preferences:(id)sender; - -@end diff --git a/LoginItem/StatusBarMenu.m b/LoginItem/StatusBarMenu.m deleted file mode 100644 index 6fae470..0000000 --- a/LoginItem/StatusBarMenu.m +++ /dev/null @@ -1,283 +0,0 @@ -// -// StatusBarMenu.m -// OverSight -// -// Created by Patrick Wardle on 1/4/15. -// Copyright (c) 2015 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" -#import "AppDelegate.h" -#import "XPCProtocol.h" -#import "StatusBarMenu.h" - -#import -#import - - -@implementation StatusBarMenu - -@synthesize isEnabled; - -//init method -// ->set some intial flags, etc --(id)init -{ - //load from nib - self = [super init]; - if(self != nil) - { - //set flag - self.isEnabled = YES; - } - - return self; -} - -//setup status item -// ->init button, show popover, etc --(void)setupStatusItem -{ - //status bar image - NSImage *image = nil; - - //init status item - self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; - - //init image - image = [NSImage imageNamed:@"statusIcon"]; - - //tell OS to handle image - // ->dark mode, etc - [image setTemplate:YES]; - - //set image - self.statusItem.image = image; - - //init menu - // ->enumerator will re-invoke with devices and their status - [self updateStatusItemMenu:nil]; - - return; -} - -//create/update status item menu --(void)updateStatusItemMenu:(NSArray*)devices -{ - //pool - @autoreleasepool - { - - //menu - NSMenu* menu = nil; - - //array of active devices - NSMutableArray* activeDevices = nil; - - //array of inactive devices - NSMutableArray* inactiveDevices = nil; - - //string for device name/emoji - NSMutableString* deviceDetails = nil; - - //alloc/init window - menu = [[NSMenu alloc] init]; - - //alloc array for active devices - activeDevices = [NSMutableArray array]; - - //alloc array for inactive devices - inactiveDevices = [NSMutableArray array]; - - //add status msg - [menu addItemWithTitle:@"OVERSIGHT: monitoring 🎤 + 📸" action:NULL keyEquivalent:@""]; - - //add top separator - [menu addItem:[NSMenuItem separatorItem]]; - - //iterate over all devices - // ->classify each, and built details string for menu - for(NSDictionary* device in devices) - { - //init string for name/etc - deviceDetails = [NSMutableString string]; - - //add device emoji - // ->audio device - if(YES == [device[EVENT_DEVICE] isKindOfClass:NSClassFromString(@"AVCaptureHALDevice")]) - { - //add - [deviceDetails appendString:@" 🎤 "]; - } - //add device emoji - // ->video device - else - { - //add - [deviceDetails appendString:@" 📸 "]; - } - - //add device name - [deviceDetails appendString:((AVCaptureDevice*)device[EVENT_DEVICE]).localizedName]; - - //classify - // ->active devices - if(DEVICE_ACTIVE.intValue == [device[EVENT_DEVICE_STATUS] intValue]) - { - //add - [activeDevices addObject:deviceDetails]; - } - //classify - // ->inactive devices - else - { - //add - [inactiveDevices addObject:deviceDetails]; - } - } - - //add active devices to menu - if(0 != activeDevices.count) - { - //add title - [menu addItem:[self initializeMenuItem:@"Active Devices" action:NULL]]; - - //add each - for(NSString* deviceDetails in activeDevices) - { - [menu addItem:[self initializeMenuItem:deviceDetails action:NULL]]; - } - } - //when no active devices - // ->add title to reflect that fact - else - { - //add - [menu addItem:[self initializeMenuItem:@"No Active Devices" action:NULL]]; - } - - //add item separator - [menu addItem:[NSMenuItem separatorItem]]; - - //add inactive devices to menu - if(0 != inactiveDevices.count) - { - //add title - [menu addItem:[self initializeMenuItem:@"Inactive Devices" action:NULL]]; - - //add each - for(NSString* deviceDetails in inactiveDevices) - { - [menu addItem:[self initializeMenuItem:deviceDetails action:NULL]]; - } - } - //when no inactive devices - // ->add title to reflect that fact - else - { - //add - [menu addItem:[self initializeMenuItem:@"No Inactive Devices" action:NULL]]; - } - - //add item separator - [menu addItem:[NSMenuItem separatorItem]]; - - //create/add menu item - // ->'preferences' - [menu addItem:[self initializeMenuItem:@"Preferences" action:@selector(preferences:)]]; - - //add bottom separator - [menu addItem:[NSMenuItem separatorItem]]; - - //create/add menu item - // ->'about' - [menu addItem:[self initializeMenuItem:@"Quit" action:@selector(quit:)]]; - - //tie menu to status item - self.statusItem.menu = menu; - - }//pool - - return; -} - -//init a menu item --(NSMenuItem*)initializeMenuItem:(NSString*)title action:(SEL)action -{ - //menu item - NSMenuItem* menuItem = nil; - - //alloc menu item - // ->toggle ('enable'/'disable') - menuItem = [[NSMenuItem alloc] initWithTitle:title action:action keyEquivalent:@""]; - - //enabled - menuItem.enabled = YES; - - //self - menuItem.target = self; - - return menuItem; -} - -#pragma mark - Menu actions - -//handler for 'quit' -// ->just exit the application --(void)quit:(id)sender -{ - //xpc connection - __block NSXPCConnection* xpcConnection = nil; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"user clicked 'quit', so goodbye!"); - #endif - - //alloc XPC connection - xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; - - //set remote object interface - xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //resume - [xpcConnection resume]; - - //tell XPC to exit - [[xpcConnection remoteObjectProxy] exit]; - - //give it a sec for XPC msg to go thru - // ->don't wait on XPC since its killing itself! - [NSThread sleepForTimeInterval:0.10f]; - - //kill main (preference) app - // ->might be open, and looks odd if its still present - execTask(PKILL, @[[APP_NAME stringByDeletingPathExtension]], YES); - - //bye! - [[NSApplication sharedApplication] terminate:nil]; - - return; -} - -//handler for 'preferences' menu item -// ->launch main application which will show prefs --(void)preferences:(id)sender -{ - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"launching main app (from /Applications)"); - #endif - - //launch main app - [[NSWorkspace sharedWorkspace] launchApplication:[APPS_FOLDER stringByAppendingPathComponent:APP_NAME]]; - -//bail -bail: - - return; -} - -@end diff --git a/LoginItem/main.h b/LoginItem/main.h deleted file mode 100644 index b99f7c4..0000000 --- a/LoginItem/main.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// main.h -// OverSight -// -// Created by Patrick Wardle on 9/10/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#ifndef main_h -#define main_h - -#import "Consts.h" -#import "Logging.h" -#import - -/* FUNCTION DEFINITIONS */ - -//send XPC message to remove process from whitelist file -void unWhiteList(NSString* process, NSNumber* deviceType); - -#endif /* main_h */ diff --git a/LoginItem/main.m b/LoginItem/main.m deleted file mode 100644 index e3f1c55..0000000 --- a/LoginItem/main.m +++ /dev/null @@ -1,205 +0,0 @@ -// -// main.m -// Test Application Helper -// -// Created by Patrick Wardle on 9/10/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "main.h" -#import "Logging.h" -#import "Utilities.h" -#import "../Shared/XPCProtocol.h" - -//go go go -// ->either install/uninstall, or just launch normally -int main(int argc, const char * argv[]) -{ - //return var - int iReturn = 0; - - //logged in user info - NSMutableDictionary* userInfo = nil; - - //pool - @autoreleasepool - { - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"starting login item (args: %@/user: %@/%@)", [[NSProcessInfo processInfo] arguments], NSUserName(), loggedinUser()]); - #endif - - //check for uninstall/install flags, and process to remove from whitelist - if(2 == argc) - { - //drops privs when installing/uninstalling - // do here, only for these as they then bail - if( (0 == strcmp(argv[1], CMD_INSTALL)) || - (0 == strcmp(argv[1], CMD_UNINSTALL)) ) - { - //get user - userInfo = loggedinUser(); - if(nil == userInfo[@"user"]) - { - //err msg - logMsg(LOG_ERR, @"failed to determine logged-in user"); - - //bail - goto bail; - } - - //drop group privs - setgid([userInfo[@"gid"] intValue]); - - //drop user privs - setuid([userInfo[@"uid"] intValue]); - } - - //install - if(0 == strcmp(argv[1], CMD_INSTALL)) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"running install logic"); - #endif - - //install - if(YES != toggleLoginItem([NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]], ACTION_INSTALL_FLAG)) - { - //err msg - logMsg(LOG_ERR, @"failed to add login item"); - - //set error - iReturn = -1; - - //bail - goto bail; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"installed login item"); - #endif - - //create default prefs - [@{PREF_LOG_ACTIVITY:@YES, PREF_START_AT_LOGIN:@YES, PREF_RUN_HEADLESS:@NO, PREF_CHECK_4_UPDATES:@YES} writeToFile:[APP_PREFERENCES stringByExpandingTildeInPath] atomically:NO]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"created preferences at: %@", [APP_PREFERENCES stringByExpandingTildeInPath]]); - #endif - - //bail - goto bail; - } - //uninstall - else if(0 == strcmp(argv[1], CMD_UNINSTALL)) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"running uninstall logic"); - #endif - - //uninstall - if(YES != toggleLoginItem([NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]], ACTION_UNINSTALL_FLAG)) - { - //err msg - logMsg(LOG_ERR, @"failed to remove login item"); - - //set error - iReturn = -1; - - //don't bail - // ->keep trying to uninstall - } - - //dbg msg - #ifdef DEBUG - else - { - //dbg msg - logMsg(LOG_DEBUG, @"removed login item"); - } - #endif - - //delete prefs - [[NSFileManager defaultManager] removeItemAtPath:[APP_PREFERENCES stringByExpandingTildeInPath] error:nil]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"removed preferences from: %@", [APP_PREFERENCES stringByExpandingTildeInPath]]); - #endif - - //bail - goto bail; - } - } - - //unwhitelist path/device - else if(3 == argc) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"running 'un-whitelist me' logic"); - #endif - - //remove from whitelist file - unWhiteList([NSString stringWithUTF8String:argv[1]], [NSNumber numberWithInt:atoi(argv[2])]); - - //don't bail - // ->let it start (as it was killed) - } - - //launch app normally - iReturn = NSApplicationMain(argc, argv); - - }//pool - -bail: - - return iReturn; -} - -//send XPC message to remove process from whitelist file -void unWhiteList(NSString* process, NSNumber* device) -{ - //xpc connection - __block NSXPCConnection* xpcConnection = nil; - - //init XPC - xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; - - //set remote object interface - xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //resume - [xpcConnection resume]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"sending XPC message to remove %@/%@ from whitelist file", process, device]); - #endif - - //invoke XPC method 'whitelistProcess' to add process to white list - [[xpcConnection remoteObjectProxy] unWhitelistProcess:process device:device reply:^(BOOL wasRemoved) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got XPC response: %d", wasRemoved]); - #endif - - //err msg on failure - if(YES != wasRemoved) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to remove %@ from whitelist", process]); - } - - //close connection - [xpcConnection invalidate]; - - }]; - - return; -} diff --git a/MainApp/3rdParty/HyperlinkTextField.h b/MainApp/3rdParty/HyperlinkTextField.h deleted file mode 100755 index 2cd5e24..0000000 --- a/MainApp/3rdParty/HyperlinkTextField.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// HyperlinkTextField.h -// NSTextFieldHyperlinks -// -// Created by Toomas Vahter on 25.12.12. -// Copyright (c) 2012 Toomas Vahter. All rights reserved. -// -// This content is released under the MIT License (http://www.opensource.org/licenses/mit-license.php). -// -// 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. - -#import - -@interface HyperlinkTextField : NSTextField -@end diff --git a/MainApp/3rdParty/HyperlinkTextField.m b/MainApp/3rdParty/HyperlinkTextField.m deleted file mode 100755 index 424a7d6..0000000 --- a/MainApp/3rdParty/HyperlinkTextField.m +++ /dev/null @@ -1,153 +0,0 @@ -// -// HyperlinkTextField.m -// NSTextFieldHyperlinks -// -// Created by Toomas Vahter on 25.12.12. -// Copyright (c) 2012 Toomas Vahter. All rights reserved. -// -// This content is released under the MIT License (http://www.opensource.org/licenses/mit-license.php). -// -// 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. - -#import "HyperlinkTextField.h" - -@interface HyperlinkTextField () -@property (nonatomic, readonly) NSArray *hyperlinkInfos; -@property (nonatomic, readonly) NSTextView *textView; - -- (void)_resetHyperlinkCursorRects; -@end - -#define kHyperlinkInfoCharacterRangeKey @"range" -#define kHyperlinkInfoURLKey @"url" -#define kHyperlinkInfoRectKey @"rect" - -@implementation HyperlinkTextField - -- (void)_hyperlinkTextFieldInit -{ - [self setEditable:NO]; - [self setSelectable:NO]; -} - - -- (id)initWithFrame:(NSRect)frame -{ - if ((self = [super initWithFrame:frame])) - { - [self _hyperlinkTextFieldInit]; - } - - return self; -} - - -- (id)initWithCoder:(NSCoder *)coder -{ - if ((self = [super initWithCoder:coder])) - { - [self _hyperlinkTextFieldInit]; - } - - return self; -} - - -- (void)resetCursorRects -{ - [super resetCursorRects]; - [self _resetHyperlinkCursorRects]; -} - - -- (void)_resetHyperlinkCursorRects -{ - for (NSDictionary *info in self.hyperlinkInfos) - { - [self addCursorRect:[[info objectForKey:kHyperlinkInfoRectKey] rectValue] cursor:[NSCursor pointingHandCursor]]; - } -} - - -#pragma mark - -#pragma mark Accessors - -- (NSArray *)hyperlinkInfos -{ - NSMutableArray *hyperlinkInfos = [[NSMutableArray alloc] init]; - NSRange stringRange = NSMakeRange(0, [self.attributedStringValue length]); - __block NSTextView *textView = self.textView; - [self.attributedStringValue enumerateAttribute:NSLinkAttributeName inRange:stringRange options:0 usingBlock:^(id value, NSRange range, BOOL *stop) - { - if (value) - { - NSUInteger rectCount = 0; - NSRectArray rectArray = [textView.layoutManager rectArrayForCharacterRange:range withinSelectedCharacterRange:range inTextContainer:textView.textContainer rectCount:&rectCount]; - for (NSUInteger i = 0; i < rectCount; i++) - { - [hyperlinkInfos addObject:@{kHyperlinkInfoCharacterRangeKey : [NSValue valueWithRange:range], kHyperlinkInfoURLKey : value, kHyperlinkInfoRectKey : [NSValue valueWithRect:rectArray[i]]}]; - } - } - }]; - - return [hyperlinkInfos count] ? hyperlinkInfos : nil; -} - - -- (NSTextView *)textView -{ - // Font used for displaying and frame calculations must match - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedStringValue]; - NSFont *font = [attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL]; - - if (!font) - [attributedString addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, [attributedString length])]; - - NSRect textViewFrame = [self.cell titleRectForBounds:self.bounds]; - NSTextView *textView = [[NSTextView alloc] initWithFrame:textViewFrame]; - [textView.textStorage setAttributedString:attributedString]; - - return textView; -} - - -#pragma mark - -#pragma mark Mouse Events - -- (void)mouseUp:(NSEvent *)theEvent -{ - NSTextView *textView = self.textView; - NSPoint localPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; - NSUInteger index = [textView.layoutManager characterIndexForPoint:localPoint inTextContainer:textView.textContainer fractionOfDistanceBetweenInsertionPoints:NULL]; - - if (index != NSNotFound) - { - for (NSDictionary *info in self.hyperlinkInfos) - { - NSRange range = [[info objectForKey:kHyperlinkInfoCharacterRangeKey] rangeValue]; - if (NSLocationInRange(index, range)) - { - NSURL *url = [info objectForKey:kHyperlinkInfoURLKey]; - [[NSWorkspace sharedWorkspace] openURL:url]; - } - } - } -} - -@end diff --git a/MainApp/AppDelegate.h b/MainApp/AppDelegate.h deleted file mode 100644 index f911499..0000000 --- a/MainApp/AppDelegate.h +++ /dev/null @@ -1,96 +0,0 @@ -// -// AppDelegate.h -// Test Application -// -// Created by Patrick Wardle on 9/10/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import -#import "InfoWindowController.h" -#import "AboutWindowController.h" -#import "RulesWindowController.h" -#import "3rdParty/HyperlinkTextField.h" - - -@interface AppDelegate : NSObject - -/* PROPERTIES */ - -//about button -@property (weak) IBOutlet NSButton *about; - -//log activity button -@property (weak) IBOutlet NSButton *logActivity; - -//label to show button -@property (weak) IBOutlet HyperlinkTextField *viewLogLabel; - -//check for updates automatically button -@property (weak) IBOutlet NSButton *check4Updates; - -//start at login button -@property (weak) IBOutlet NSButton *startAtLogin; - -//run in headless mode button -@property (weak) IBOutlet NSButton *runHeadless; - -//disable 'inactive' alerts -@property (weak) IBOutlet NSButton *disableInactive; - -//check for updates now button -@property (weak) IBOutlet NSButton *check4UpdatesNow; - -//check for updates spinner -@property (weak) IBOutlet NSProgressIndicator *spinner; - -//version label -@property (weak) IBOutlet NSTextField *versionLabel; - -//info window -@property(nonatomic, retain)InfoWindowController* infoWindowController; - -//about window controller -@property(nonatomic, retain)AboutWindowController* aboutWindowController; - -//rules -@property(nonatomic, retain)RulesWindowController* rulesWindowController; - -//overlay view -@property (weak) IBOutlet NSView *overlay; - -//status message -@property (weak) IBOutlet NSTextField *statusMessage; - -//progress indicator -@property (weak) IBOutlet NSProgressIndicator *progressIndicator; - -/* METHODS */ - -//register handler for hot keys --(void)registerKeypressHandler; - -//helper function for keypresses -// ->for now, only handle cmd+q, to quit --(NSEvent*)handleKeypress:(NSEvent*)event; - -//toggle/set preferences --(IBAction)togglePreference:(NSButton *)sender; - -//'about' button handler --(IBAction)about:(id)sender; - -//'check for update' (now) button handler --(IBAction)check4Update:(id)sender; - -//check for an update --(void)isThereAnUpdate; - -//'manage rules' button handler --(IBAction)manageRules:(id)sender; - -//(re)start the login item --(void)startLoginItem:(BOOL)shouldRestart args:(NSArray*)args; - -@end - diff --git a/MainApp/AppDelegate.m b/MainApp/AppDelegate.m deleted file mode 100644 index ee18f55..0000000 --- a/MainApp/AppDelegate.m +++ /dev/null @@ -1,672 +0,0 @@ -// -// AppDelegate.m -// Test Application -// -// Created by Patrick Wardle on 9/10/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" -#import "AppDelegate.h" - -@interface AppDelegate () - -@property (weak) IBOutlet NSWindow *window; -@end - -@implementation AppDelegate - -@synthesize viewLogLabel; -@synthesize infoWindowController; -@synthesize aboutWindowController; -@synthesize rulesWindowController; - -//center window -// ->also make front, init title bar, etc --(void)awakeFromNib -{ - //center - [self.window center]; - - //set button states - [self setButtonStates]; - - //make it key window - [self.window makeKeyAndOrderFront:self]; - - //make window front - [NSApp activateIgnoringOtherApps:YES]; - - //set title - self.window.title = [NSString stringWithFormat:@"OverSight Preferences (v. %@)", getAppVersion()]; - - //make log link clickable - makeTextViewHyperlink(self.viewLogLabel, [NSURL fileURLWithPath:logFilePath()]); - - return; -} - -//app interface -// ->init user interface --(void)applicationDidFinishLaunching:(NSNotification *)aNotification -{ - //preferences - NSDictionary* preferences = nil; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"OverSight Preferences App Launched"); - #endif - - //register for hotkey presses - // ->for now, just cmd+q to quit app - [self registerKeypressHandler]; - - //create default prefs if there aren't any - // ->should only happen if new user runs the app - if(YES != [[NSFileManager defaultManager] fileExistsAtPath:[APP_PREFERENCES stringByExpandingTildeInPath]]) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"preference file not found; manually creating"); - #endif - - //write em out - // ->note; set 'start at login' to false, since no prefs here, mean installer wasn't run (user can later toggle) - [@{PREF_LOG_ACTIVITY:@YES, PREF_START_AT_LOGIN:@NO, PREF_RUN_HEADLESS:@NO, PREF_DISABLE_INACTIVE:@NO, PREF_CHECK_4_UPDATES:@YES} writeToFile:[APP_PREFERENCES stringByExpandingTildeInPath] atomically:NO]; - } - - //load preferences - preferences = [NSMutableDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]]; - - //when logging is enabled - // ->open/create log file - if(YES == [preferences[PREF_LOG_ACTIVITY] boolValue]) - { - //init - if(YES != initLogging()) - { - //err msg - logMsg(LOG_ERR, @"failed to init logging"); - } - } - - //start login item in background - // ->checks if already running though - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - //start - // -> 'NO' means don't start if already running - [self startLoginItem:NO args:nil]; - }); - - return; -} - -//automatically close when user closes window --(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication -{ - return YES; -} - -//set button states from preferences --(void)setButtonStates -{ - //preferences - NSDictionary* preferences = nil; - - //load preferences - preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]]; - - //set 'log activity' button state - self.logActivity.state = [preferences[PREF_LOG_ACTIVITY] boolValue]; - - //set 'start at login' button state - self.startAtLogin.state = [preferences[PREF_START_AT_LOGIN] boolValue]; - - //set 'run headless' button state - self.runHeadless.state = [preferences[PREF_RUN_HEADLESS] boolValue]; - - //set 'disable inactive' button state - self.disableInactive.state = [preferences[PREF_DISABLE_INACTIVE] boolValue]; - - //set 'automatically check for updates' button state - self.check4Updates.state = [preferences[PREF_CHECK_4_UPDATES] boolValue]; - - return; -} - -//register handler for hot keys -// ->for now, it just handles cmd+q to quit --(void)registerKeypressHandler -{ - //event handler - NSEvent* (^keypressHandler)(NSEvent *) = nil; - - //init handler block - // ->just call helper function - keypressHandler = ^NSEvent * (NSEvent * theEvent){ - - //invoke helper - return [self handleKeypress:theEvent]; - }; - - //register for key-down events - [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:keypressHandler]; - - return; -} - - -//helper function for keypresses -// ->for now, only handle cmd+q, to quit --(NSEvent*)handleKeypress:(NSEvent*)event -{ - //flag indicating event was handled - BOOL wasHandled = NO; - - //only care about 'cmd' + something - if(NSCommandKeyMask != (event.modifierFlags & NSCommandKeyMask)) - { - //bail - goto bail; - } - - //handle key-code - // command+q: quite - switch ([event keyCode]) - { - //'q' (quit) - case KEYCODE_Q: - - //bye! - [[NSApplication sharedApplication] terminate:nil]; - - //set flag - wasHandled = YES; - - break; - - //default - // ->do nothing - default: - - break; - } - -//bail -bail: - - //nil out event if it was handled - if(YES == wasHandled) - { - //nil - event = nil; - } - - return event; - -} - -//toggle/set preferences --(IBAction)togglePreference:(NSButton *)sender -{ - //preferences - NSMutableDictionary* preferences = nil; - - //path to login item - NSURL* loginItem = nil; - - //load preferences - preferences = [NSMutableDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]]; - - //set 'log activity' button - // ->also start/stop logging based on button state - if(sender == self.logActivity) - { - //set - preferences[PREF_LOG_ACTIVITY] = [NSNumber numberWithBool:[sender state]]; - - //when logging is enabled - // ->open/create log file - if(YES == [preferences[PREF_LOG_ACTIVITY] boolValue]) - { - //init - if(YES != initLogging()) - { - //err msg - logMsg(LOG_ERR, @"failed to init logging"); - } - //happy - // ->log msg - else - { - //log msg - logMsg(LOG_DEBUG|LOG_TO_FILE, @"logging initialized (main app)"); - } - } - //when logging is disabled - // ->close out the log file - else - { - //log msg - logMsg(LOG_DEBUG|LOG_TO_FILE, @"logging deinitialized (main app)"); - - //close - deinitLogging(); - } - } - - //set 'automatically check for updates' - else if(sender == self.check4Updates) - { - //set - preferences[PREF_CHECK_4_UPDATES] = [NSNumber numberWithBool:[sender state]]; - } - - //set 'start at login' - // ->then also toggle for current user - else if(sender == self.startAtLogin) - { - //set - preferences[PREF_START_AT_LOGIN] = [NSNumber numberWithBool:[sender state]]; - - //init path to login item - loginItem = [NSURL fileURLWithPath:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"/Contents/Library/LoginItems/OverSight Helper.app"]]; - - //toggle - if(YES != toggleLoginItem(loginItem, (int)[sender state])) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to toggle login item: %@", loginItem]); - - //bail - goto bail; - } - } - - //set 'run in headless mode' - // ->then restart login item to realize this - else if(sender == self.runHeadless) - { - //set - preferences[PREF_RUN_HEADLESS] = [NSNumber numberWithBool:[sender state]]; - - //save em now so new instance of login item can read them - [preferences writeToFile:[APP_PREFERENCES stringByExpandingTildeInPath] atomically:YES]; - - //restart login item in background - // ->will read prefs, and run in headless mode - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - //start - [self startLoginItem:YES args:nil]; - }); - } - - //set 'disable inactive alerts' - else if(sender == self.disableInactive) - { - //set - preferences[PREF_DISABLE_INACTIVE] = [NSNumber numberWithBool:[sender state]]; - } - - //save em - [preferences writeToFile:[APP_PREFERENCES stringByExpandingTildeInPath] atomically:YES]; - -//bail -bail: - - return; -} - -//'about' button/menu handler --(IBAction)about:(id)sender -{ - //alloc/init settings window - if(nil == self.aboutWindowController) - { - //alloc/init - aboutWindowController = [[AboutWindowController alloc] initWithWindowNibName:@"AboutWindow"]; - } - - //center window - [[self.aboutWindowController window] center]; - - //show it - [self.aboutWindowController showWindow:self]; - - //invoke function in background that will make window modal - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - //make modal - makeModal(self.aboutWindowController); - - }); - - return; -} - -//'check for update' (now) button handler --(IBAction)check4Update:(id)sender -{ - //disable button - self.check4UpdatesNow.enabled = NO; - - //reset - self.versionLabel.stringValue = @""; - - //re-draw - [self.versionLabel displayIfNeeded]; - - //show spinner - [self.spinner startAnimation:self]; - - //check for update - [self isThereAnUpdate]; - - return; -} - -//check for an update --(void)isThereAnUpdate -{ - //version string - NSMutableString* versionString = nil; - - //alloc string - versionString = [NSMutableString string]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"checking for new version"); - #endif - - //check if available version is newer - // ->show update popup/window - if(YES == isNewVersion(versionString)) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"a new version (%@) is available", versionString]); - #endif - - //hide version message - self.versionLabel.hidden = YES; - - //alloc/init about window - infoWindowController = [[InfoWindowController alloc] initWithWindowNibName:@"InfoWindow"]; - - //configure - [self.infoWindowController configure:[NSString stringWithFormat:@"a new version (%@) is available!", versionString] buttonTitle:@"update"]; - - //center window - [[self.infoWindowController window] center]; - - //show it - [self.infoWindowController showWindow:self]; - - //invoke function in background that will make window modal - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - //make modal - makeModal(self.infoWindowController); - - }); - - //stop/hide spinner - [self.spinner stopAnimation:self]; - - //re-enable button - self.check4UpdatesNow.enabled = YES; - } - - //no new version - // ->stop animations, etc - else - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"no updates available"); - #endif - - //stop/hide spinner - [self.spinner stopAnimation:self]; - - //re-enable button - self.check4UpdatesNow.enabled = YES; - - //show now new version message - self.versionLabel.hidden = NO; - - //set message - self.versionLabel.stringValue = @"no new versions"; - - //re-draw - [self.versionLabel displayIfNeeded]; - } - - return; -} - -//(re)start the login item --(void)startLoginItem:(BOOL)shouldRestart args:(NSArray*)args -{ - //path to login item - NSString* loginItem = nil; - - //login item's pid - pid_t loginItemPID = -1; - - //error - NSError* error = nil; - - //config (args, etc) - // ->can't be nil, so init to blank here - NSDictionary* configuration = @{}; - - //get pid of login item for user - loginItemPID = getProcessID(@"OverSight Helper", getuid()); - - //no need to start if already running - // ->well, and if 'shouldRestart' is not set - if( (-1 != loginItemPID) && - (YES != shouldRestart) ) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"login item already running and 'shouldRestart' not set, so no need to start it!"); - #endif - - //bail - goto bail; - } - - //running? - // ->kill - else if(-1 != loginItemPID) - { - //kill it - kill(loginItemPID, SIGTERM); - - //sleep - [NSThread sleepForTimeInterval:1.0f]; - - //really kill - kill(loginItemPID, SIGKILL); - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"starting login item"); - #endif - - //add overlay - [self addOverlay:shouldRestart]; - - //init path to login item - loginItem = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"/Contents/Library/LoginItems/OverSight Helper.app"]; - - //any args? - // ->init config with them args - if(nil != args) - { - //add args - configuration = @{NSWorkspaceLaunchConfigurationArguments:args}; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"starting login item with: %@/%@", configuration, args]); - #endif - - //launch it - [[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:loginItem] options:NSWorkspaceLaunchWithoutActivation configuration:configuration error:&error]; - - //remove overlay - [self removeOverlay]; - - //check if login launch was ok - // ->do down here, since always want to remove overlay - if(nil != error) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to start login item, %@/%@", loginItem, error]); - - //bail - goto bail; - } - -//bail -bail: - - return; -} - -//add overlay to main window --(void)addOverlay:(BOOL)restarting -{ - //show overlay view on main thread - dispatch_async(dispatch_get_main_queue(), ^{ - - //frame - NSRect frame = {0}; - - //pre-req - [self.overlay setWantsLayer:YES]; - - //get main window's frame - frame = self.window.contentView.frame; - - //set origin to 0/0 - frame.origin = CGPointZero; - - //tweak since window is rounded - // ->and adding this view doesn't get rounded? - frame.origin.y += 1; - frame.origin.x += 1; - frame.size.width -= 2; - - //update overlay to take up entire window - self.overlay.frame = frame; - - //set overlay's view color to white - self.overlay.layer.backgroundColor = [NSColor whiteColor].CGColor; - - //make it semi-transparent - self.overlay.alphaValue = 0.85; - - //set start message - if(YES != restarting) - { - //set - self.statusMessage.stringValue = @"starting monitor..."; - } - //set restart message - else - { - //set - self.statusMessage.stringValue = @"(re)starting monitor..."; - } - - //show message - self.statusMessage.hidden = NO; - - //show spinner - self.progressIndicator.hidden = NO; - - //animate it - [self.progressIndicator startAnimation:nil]; - - //add to main window - [self.window.contentView addSubview:self.overlay]; - - //show - self.overlay.hidden = NO; - - }); - - return; -} - -//remove overlay from main window --(void)removeOverlay -{ - //sleep to give message more viewing time - [NSThread sleepForTimeInterval:2]; - - //remove overlay view on main thread - dispatch_async(dispatch_get_main_queue(), ^{ - - //hide spinner - self.progressIndicator.hidden = YES; - - //hide view - self.overlay.hidden = YES; - - //hide message - self.statusMessage.hidden = YES; - - //remove - [self.overlay removeFromSuperview]; - - }); - - return; -} - --(IBAction)showLog:(id)sender -{ - return; -} - - -//button handle when user clicks 'Manage Rules' -// ->just shwo the rules window --(IBAction)manageRules:(id)sender -{ - //alloc - rulesWindowController = [[RulesWindowController alloc] initWithWindowNibName:@"Rules"]; - - //center window - [[self.rulesWindowController window] center]; - - //show it - [self.rulesWindowController showWindow:self]; - - return; -} - --(NSAttributedString *)stringFromHTML:(NSString *)html withFont:(NSFont *)font -{ - if (!font) font = [NSFont systemFontOfSize:0.0]; // Default font - html = [NSString stringWithFormat:@"%@", [font fontName], (int)[font pointSize], html]; - NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding]; - NSAttributedString* string = [[NSAttributedString alloc] initWithHTML:data documentAttributes:nil]; - return string; -} - - -@end diff --git a/MainApp/Base.lproj/MainMenu.xib b/MainApp/Base.lproj/MainMenu.xib deleted file mode 100644 index 0df78de..0000000 --- a/MainApp/Base.lproj/MainMenu.xib +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MainApp/RuleRow.h b/MainApp/RuleRow.h deleted file mode 100755 index 5f1985b..0000000 --- a/MainApp/RuleRow.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// RuleRow.h -// OverSight -// -// Created by Patrick Wardle on 4/4/15. -// Copyright (c) 2017 Objective-See. All rights reserved. -// - -#import - -@interface RuleRow : NSTableRowView - -@end diff --git a/MainApp/RuleRowCell.h b/MainApp/RuleRowCell.h deleted file mode 100755 index 654a42f..0000000 --- a/MainApp/RuleRowCell.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// RuleRowCell.h -// OverSight -// -// Created by Patrick Wardle on 4/6/15. -// Copyright (c) 2017 Objective-See. All rights reserved. -// - -#import - -@interface RuleRowCell : NSTableCellView - -@end diff --git a/MainApp/RuleRowCell.m b/MainApp/RuleRowCell.m deleted file mode 100755 index 769c161..0000000 --- a/MainApp/RuleRowCell.m +++ /dev/null @@ -1,24 +0,0 @@ -// -// kkRowCell.m -// OverSight -// -// Created by Patrick Wardle on 4/6/15. -// Copyright (c) 2015 Objective-See. All rights reserved. -// - -#import "RuleRowCell.h" - -@implementation RuleRowCell - --(void)drawRect:(NSRect)dirtyRect{ - [super drawRect:dirtyRect]; - - // Drawing code here. -} - --(void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle -{ - [super setBackgroundStyle: NSBackgroundStyleLight]; -} - -@end diff --git a/MainApp/RulesWindowController.h b/MainApp/RulesWindowController.h deleted file mode 100644 index 26a5019..0000000 --- a/MainApp/RulesWindowController.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// RulesWindowController.h -// OverSight -// -// Created by Patrick Wardle on 7/7/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -@interface RulesWindowController : NSWindowController - -/* PROPERTIES */ - -//table items -@property(nonatomic, retain)NSMutableArray* items; - -//top level view -@property (weak) IBOutlet NSView *view; - -//table view -@property (weak) IBOutlet NSTableView *tableView; - -//overlay -@property (strong) IBOutlet NSView *overlay; - -//activity indicator -@property (weak) IBOutlet NSProgressIndicator *spinner; - -//status message -@property (weak) IBOutlet NSTextField *message; - -/* METHODS */ - -//delete a rule --(IBAction)deleteRule:(id)sender; - -@end diff --git a/MainApp/RulesWindowController.m b/MainApp/RulesWindowController.m deleted file mode 100644 index 737afbd..0000000 --- a/MainApp/RulesWindowController.m +++ /dev/null @@ -1,297 +0,0 @@ -// -// RulesWindowController.m -// OverSight -// -// Created by Patrick Wardle on 7/7/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - - -#import "Consts.h" -#import "RuleRow.h" -#import "Utilities.h" -#import "AppDelegate.h" -#import "../Shared/Logging.h" -#import "RulesWindowController.h" -#import "../Shared/XPCProtocol.h" - -@interface RulesWindowController () - -@end - -@implementation RulesWindowController - -@synthesize items; - -//automatically called when nib is loaded -// ->just center window --(void)awakeFromNib -{ - //load whitelisted items - self.items = [NSMutableArray arrayWithContentsOfFile:[[[@"~" stringByAppendingPathComponent:APP_SUPPORT_DIRECTORY] stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST]]; - - //no white-listed items? - // ->show overlay with msg - if(0 == self.items.count) - { - //add - [self addOverlay]; - - //set message - self.message.stringValue = @"currently no applications have been 'whitelisted'"; - - //hide spinner - self.spinner.hidden = YES; - } - - return; -} - - -//table delegate -// ->return number of rows --(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView -{ - //count - return self.items.count; -} - -//table delegate method -// ->return cell for row --(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row -{ - //cell - NSTableCellView *tableCell = nil; - - //process name - NSString* processName = nil; - - //process path - NSString* processPath = nil; - - //process icon - NSImage* processIcon = nil; - - //app bundle - NSBundle* appBundle = nil; - - //device type - NSString* device = nil; - - //sanity check - if(row >= self.items.count) - { - //bail - goto bail; - } - - //grab process path - processPath = [[self.items objectAtIndex:row] objectForKey:EVENT_PROCESS_PATH]; - - //try find an app bundle - appBundle = findAppBundle(processPath); - if(nil != appBundle) - { - //grab name from app's bundle - processName = [appBundle infoDictionary][@"CFBundleName"]; - } - - //still nil? - // ->just grab from path - if(nil == processName) - { - //from path - processName = [processPath lastPathComponent]; - } - - //grab icon - processIcon = getIconForProcess(processPath); - - //set device type for audio - if(SOURCE_AUDIO.intValue == [[[self.items objectAtIndex:row] objectForKey:EVENT_DEVICE] intValue]) - { - //set - device = @"mic"; - } - //set device type for mic - else if(SOURCE_VIDEO.intValue == [[[self.items objectAtIndex:row] objectForKey:EVENT_DEVICE] intValue]) - { - //set - device = @"camera"; - } - - //init table cell - tableCell = [tableView makeViewWithIdentifier:@"itemCell" owner:self]; - if(nil == tableCell) - { - //bail - goto bail; - } - - //set icon - tableCell.imageView.image = processIcon; - - //set (main) text - // process name (device) - tableCell.textField.stringValue = [NSString stringWithFormat:@"%@ (access: %@)", processName, device]; - - //set sub text - [[tableCell viewWithTag:TABLE_ROW_SUB_TEXT_TAG] setStringValue:processPath]; - - //set detailed text color to gray - ((NSTextField*)[tableCell viewWithTag:TABLE_ROW_SUB_TEXT_TAG]).textColor = [NSColor grayColor]; - -//bail -bail: - - // Return the result - return tableCell; - -} - -//automatically invoked -// ->create custom (sub-classed) NSTableRowView --(NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row -{ - //row view - RuleRow* rowView = nil; - - //row ID - static NSString* const kRowIdentifier = @"RowView"; - - //try grab existing row view - rowView = [tableView makeViewWithIdentifier:kRowIdentifier owner:self]; - - //make new if needed - if(nil == rowView) - { - //create new - // ->size doesn't matter - rowView = [[RuleRow alloc] initWithFrame:NSZeroRect]; - - //set row ID - rowView.identifier = kRowIdentifier; - } - - return rowView; -} - -//delete a whitelist item -// ->gotta invoke the login item, as it can do that via XPC --(IBAction)deleteRule:(id)sender -{ - //index of selected row - NSInteger selectedRow = 0; - - //item - NSDictionary* item = nil; - - //rule - NSString* processPath = nil; - - //device - NSNumber* device = nil; - - //grab selected row - selectedRow = [self.tableView rowForView:sender]; - - //grab item - item = self.items[selectedRow]; - - //extract path - processPath = item[EVENT_PROCESS_PATH]; - - //extract device - device = item[EVENT_DEVICE]; - - //remove from items - [self.items removeObject:item]; - - //add/show overlay - [self addOverlay]; - - //restart login item in background - // ->pass in process path/device so it can un-whitelist via XPC - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - //restart - [((AppDelegate*)[[NSApplication sharedApplication] delegate]) startLoginItem:YES args:@[processPath, [device stringValue]]]; - - //remove overlay - // ->also refreshes window - [self removeOverlay]; - - }); - - return; -} - -//add overlay to main window --(void)addOverlay -{ - //frame - NSRect frame = {0}; - - //pre-req - [self.overlay setWantsLayer:YES]; - - //get main window's frame - frame = self.window.contentView.frame; - - //set origin to 0/0 - frame.origin = CGPointZero; - - //update overlay to take up entire window - self.overlay.frame = frame; - - //set overlay's view color to white - self.overlay.layer.backgroundColor = [NSColor whiteColor].CGColor; - - //make it semi-transparent - self.overlay.alphaValue = 0.85; - - //animate it - [self.spinner startAnimation:nil]; - - //add to main window - [self.window.contentView addSubview:self.overlay]; - - //show - self.overlay.hidden = NO; - - //disable resize button - [[self.window standardWindowButton:NSWindowZoomButton] setEnabled:NO]; - - //disable resizing - [self.window setStyleMask:self.window.styleMask^NSResizableWindowMask]; - - return; -} - -//remove overlay from main window & reload table --(void)removeOverlay -{ - //remove overlay view on main thread & reload - dispatch_async(dispatch_get_main_queue(), ^{ - - //hide view - self.overlay.hidden = YES; - - //remove - [self.overlay removeFromSuperview]; - - //reload table - [self.tableView reloadData]; - - //disable resize button - [[self.window standardWindowButton:NSWindowZoomButton] setEnabled:NO]; - - //disable resizing - [self.window setStyleMask:self.window.styleMask^NSResizableWindowMask]; - - }); - - return; -} - -@end diff --git a/MainApp/main.m b/MainApp/main.m deleted file mode 100644 index 35d0a5b..0000000 --- a/MainApp/main.m +++ /dev/null @@ -1,13 +0,0 @@ -// -// main.m -// Test Application -// -// Created by Patrick Wardle on 9/10/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -int main(int argc, const char * argv[]) { - return NSApplicationMain(argc, argv); -} diff --git a/OverSight.xcodeproj/project.pbxproj b/OverSight.xcodeproj/project.pbxproj deleted file mode 100644 index af90098..0000000 --- a/OverSight.xcodeproj/project.pbxproj +++ /dev/null @@ -1,828 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 7D0EE28D1E99A90F003D910C /* patrons.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7D0EE28C1E99A90F003D910C /* patrons.txt */; }; - 7D17C5271D6592FE0066232A /* statusIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C5141D658FEB0066232A /* statusIcon.png */; }; - 7D17C5281D6592FE0066232A /* statusIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C5151D658FEB0066232A /* statusIcon@2x.png */; }; - 7D17C5391D659E580066232A /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C52C1D659E580066232A /* AboutWindow.xib */; }; - 7D17C53B1D659E580066232A /* Exception.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C5311D659E580066232A /* Exception.m */; }; - 7D17C53C1D659E580066232A /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C5331D659E580066232A /* icon.png */; }; - 7D17C53D1D659E580066232A /* overSight.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C5341D659E580066232A /* overSight.png */; }; - 7D17C53F1D659E580066232A /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C5381D659E580066232A /* Utilities.m */; }; - 7D17CFE41D811B4E0017B475 /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C5381D659E580066232A /* Utilities.m */; }; - 7D17CFE51D8133840017B475 /* AVMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17CFE11D81121E0017B475 /* AVMonitor.m */; }; - 7D17D0101D8136C60017B475 /* OverSightXPC.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 7DC9C8121D641A350017D143 /* OverSightXPC.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 7D5735581E9DE4BC0086F474 /* HyperlinkTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D5735571E9DE4BC0086F474 /* HyperlinkTextField.m */; }; - 7D57355B1E9E46BD0086F474 /* libbsm.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D57355A1E9E46BD0086F474 /* libbsm.tbd */; }; - 7D6216731E078D65002C1774 /* RemeberWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D6216701E078C09002C1774 /* RemeberWindowController.m */; }; - 7D6216741E07BB27002C1774 /* RememberPopup.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D62166E1E078BDD002C1774 /* RememberPopup.xib */; }; - 7D62457C1D84FB8900870565 /* Enumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D62457B1D84FB8900870565 /* Enumerator.m */; }; - 7D6245801D85348E00870565 /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D62457F1D85348E00870565 /* Utilities.m */; }; - 7D6245851D87C43900870565 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D6245841D87C43900870565 /* Images.xcassets */; }; - 7D6245871D87C55F00870565 /* Logging.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C5361D659E580066232A /* Logging.m */; }; - 7D6245881D87C57600870565 /* Logging.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C5361D659E580066232A /* Logging.m */; }; - 7D6245891D87C58400870565 /* Logging.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C5361D659E580066232A /* Logging.m */; }; - 7D62458B1D87CE0900870565 /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C52E1D659E580066232A /* AboutWindowController.m */; }; - 7D6245901D87D38400870565 /* InfoWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D62458D1D87D38400870565 /* InfoWindowController.m */; }; - 7D6245911D87D39E00870565 /* InfoWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D62458D1D87D38400870565 /* InfoWindowController.m */; }; - 7D6245921D87D46800870565 /* InfoWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D62458C1D87D38400870565 /* InfoWindow.xib */; }; - 7D6245931D87E13200870565 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C5331D659E580066232A /* icon.png */; }; - 7D6245941D87E13200870565 /* overSight.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D17C5341D659E580066232A /* overSight.png */; }; - 7D6245951D87E14800870565 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8B5755CD19DA3F9300799E6B /* MainMenu.xib */; }; - 7D9A7DE81D893E4F0091C1AF /* InfoWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D62458C1D87D38400870565 /* InfoWindow.xib */; }; - 7D9A7DEC1D8BE1E00091C1AF /* Exception.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C5311D659E580066232A /* Exception.m */; }; - 7D9A7DF21D8F2C900091C1AF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D6245841D87C43900870565 /* Images.xcassets */; }; - 7DAF4B7F1D657192000DA31A /* StatusBarMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DAF4B7D1D656FD3000DA31A /* StatusBarMenu.m */; }; - 7DC038021E87025100349474 /* RulesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC038001E87025100349474 /* RulesWindowController.m */; }; - 7DC038031E87025100349474 /* Rules.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7DC038011E87025100349474 /* Rules.xib */; }; - 7DC038061E8716F700349474 /* RuleRowCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC038051E8716F700349474 /* RuleRowCell.m */; }; - 7DC038091E8717B200349474 /* RuleRow.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC038081E8717B200349474 /* RuleRow.m */; }; - 7DC9C8171D641A350017D143 /* OverSightXPC.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC9C8161D641A350017D143 /* OverSightXPC.m */; }; - 7DC9C8191D641A350017D143 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC9C8181D641A350017D143 /* main.m */; }; - 8B5755A119DA3E9500799E6B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5755A019DA3E9500799E6B /* main.m */; }; - 8B5755A419DA3E9500799E6B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5755A319DA3E9500799E6B /* AppDelegate.m */; }; - 8B5755A919DA3E9500799E6B /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8B5755A719DA3E9500799E6B /* MainMenu.xib */; }; - 8B5755C719DA3F9300799E6B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5755C619DA3F9300799E6B /* main.m */; }; - 8B5755CA19DA3F9300799E6B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5755C919DA3F9300799E6B /* AppDelegate.m */; }; - 8B5755E619DA432000799E6B /* OverSight Helper.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8B5755C219DA3F9300799E6B /* OverSight Helper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 7D17CFE61D81349D0017B475 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8B57559319DA3E9500799E6B /* Project object */; - proxyType = 1; - remoteGlobalIDString = 7DC9C8111D641A350017D143; - remoteInfo = OverSightXPC; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 7D17D00B1D81369C0017B475 /* Embed XPC Services */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; - dstSubfolderSpec = 16; - files = ( - 7D17D0101D8136C60017B475 /* OverSightXPC.xpc in Embed XPC Services */, - ); - name = "Embed XPC Services"; - runOnlyForDeploymentPostprocessing = 0; - }; - 7DC9C8211D641A350017D143 /* Embed XPC Services */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; - dstSubfolderSpec = 16; - files = ( - ); - name = "Embed XPC Services"; - runOnlyForDeploymentPostprocessing = 0; - }; - 8B5755E519DA42C100799E6B /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = Contents/Library/LoginItems; - dstSubfolderSpec = 1; - files = ( - 8B5755E619DA432000799E6B /* OverSight Helper.app in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 7D0EE28C1E99A90F003D910C /* patrons.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = patrons.txt; path = ../../patrons.txt; sourceTree = ""; }; - 7D17C5141D658FEB0066232A /* statusIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = statusIcon.png; path = Images/statusIcon.png; sourceTree = ""; }; - 7D17C5151D658FEB0066232A /* statusIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "statusIcon@2x.png"; path = "Images/statusIcon@2x.png"; sourceTree = ""; }; - 7D17C52C1D659E580066232A /* AboutWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutWindow.xib; sourceTree = ""; }; - 7D17C52D1D659E580066232A /* AboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutWindowController.h; sourceTree = ""; }; - 7D17C52E1D659E580066232A /* AboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutWindowController.m; sourceTree = ""; }; - 7D17C52F1D659E580066232A /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = ""; }; - 7D17C5301D659E580066232A /* Exception.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Exception.h; sourceTree = ""; }; - 7D17C5311D659E580066232A /* Exception.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Exception.m; sourceTree = ""; }; - 7D17C5331D659E580066232A /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; - 7D17C5341D659E580066232A /* overSight.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = overSight.png; sourceTree = ""; }; - 7D17C5351D659E580066232A /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = ""; }; - 7D17C5361D659E580066232A /* Logging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Logging.m; sourceTree = ""; }; - 7D17C5371D659E580066232A /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; - 7D17C5381D659E580066232A /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = ""; }; - 7D17CFDE1D810D6D0017B475 /* XPCProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XPCProtocol.h; path = Shared/XPCProtocol.h; sourceTree = SOURCE_ROOT; }; - 7D17CFE11D81121E0017B475 /* AVMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AVMonitor.m; sourceTree = ""; }; - 7D17CFE21D81121E0017B475 /* AVMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AVMonitor.h; sourceTree = ""; }; - 7D32457A1DA59A3700AE0711 /* main.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = main.h; sourceTree = ""; }; - 7D5735561E9DE4BC0086F474 /* HyperlinkTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HyperlinkTextField.h; path = 3rdParty/HyperlinkTextField.h; sourceTree = ""; }; - 7D5735571E9DE4BC0086F474 /* HyperlinkTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HyperlinkTextField.m; path = 3rdParty/HyperlinkTextField.m; sourceTree = ""; }; - 7D57355A1E9E46BD0086F474 /* libbsm.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbsm.tbd; path = usr/lib/libbsm.tbd; sourceTree = SDKROOT; }; - 7D62166E1E078BDD002C1774 /* RememberPopup.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RememberPopup.xib; sourceTree = ""; }; - 7D6216701E078C09002C1774 /* RemeberWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemeberWindowController.m; sourceTree = ""; }; - 7D6216721E078C13002C1774 /* RemeberWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemeberWindowController.h; sourceTree = ""; }; - 7D62457A1D84FB8900870565 /* Enumerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Enumerator.h; sourceTree = ""; }; - 7D62457B1D84FB8900870565 /* Enumerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Enumerator.m; sourceTree = ""; }; - 7D62457E1D85348E00870565 /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; - 7D62457F1D85348E00870565 /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = ""; }; - 7D6245841D87C43900870565 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Images/Images.xcassets; sourceTree = ""; }; - 7D62458C1D87D38400870565 /* InfoWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InfoWindow.xib; sourceTree = ""; }; - 7D62458D1D87D38400870565 /* InfoWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InfoWindowController.m; sourceTree = ""; }; - 7D62458E1D87D38400870565 /* InfoWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InfoWindowController.h; sourceTree = ""; }; - 7D9A7DEF1D8CADD10091C1AF /* main.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = main.h; path = OverSightXPC/main.h; sourceTree = ""; }; - 7DAF4B7C1D656FD3000DA31A /* StatusBarMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarMenu.h; sourceTree = ""; }; - 7DAF4B7D1D656FD3000DA31A /* StatusBarMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarMenu.m; sourceTree = ""; }; - 7DC037FF1E87025100349474 /* RulesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RulesWindowController.h; sourceTree = ""; }; - 7DC038001E87025100349474 /* RulesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RulesWindowController.m; sourceTree = ""; }; - 7DC038011E87025100349474 /* Rules.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Rules.xib; sourceTree = ""; }; - 7DC038041E8716F700349474 /* RuleRowCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuleRowCell.h; sourceTree = ""; }; - 7DC038051E8716F700349474 /* RuleRowCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuleRowCell.m; sourceTree = ""; }; - 7DC038071E8717B200349474 /* RuleRow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuleRow.h; sourceTree = ""; }; - 7DC038081E8717B200349474 /* RuleRow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuleRow.m; sourceTree = ""; }; - 7DC9C8121D641A350017D143 /* OverSightXPC.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = OverSightXPC.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; - 7DC9C8151D641A350017D143 /* OverSightXPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OverSightXPC.h; sourceTree = ""; }; - 7DC9C8161D641A350017D143 /* OverSightXPC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OverSightXPC.m; sourceTree = ""; }; - 7DC9C8181D641A350017D143 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 7DC9C81A1D641A350017D143 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 8B57559B19DA3E9500799E6B /* OverSight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OverSight.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 8B57559F19DA3E9500799E6B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 8B5755A019DA3E9500799E6B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 8B5755A219DA3E9500799E6B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 8B5755A319DA3E9500799E6B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 8B5755A819DA3E9500799E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 8B5755C219DA3F9300799E6B /* OverSight Helper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "OverSight Helper.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 8B5755C519DA3F9300799E6B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 8B5755C619DA3F9300799E6B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 8B5755C819DA3F9300799E6B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 8B5755C919DA3F9300799E6B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 8B5755CE19DA3F9300799E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 7DC9C80F1D641A350017D143 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D57355B1E9E46BD0086F474 /* libbsm.tbd in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8B57559819DA3E9500799E6B /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8B5755BF19DA3F9300799E6B /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 7D17C5131D658FE20066232A /* Images */ = { - isa = PBXGroup; - children = ( - 8B5755C819DA3F9300799E6B /* AppDelegate.h */, - 7D17C5141D658FEB0066232A /* statusIcon.png */, - 7D17C5151D658FEB0066232A /* statusIcon@2x.png */, - ); - name = Images; - sourceTree = ""; - }; - 7D17C52B1D659E580066232A /* Shared */ = { - isa = PBXGroup; - children = ( - 7D62458C1D87D38400870565 /* InfoWindow.xib */, - 7D62458D1D87D38400870565 /* InfoWindowController.m */, - 7D62458E1D87D38400870565 /* InfoWindowController.h */, - 7D6245841D87C43900870565 /* Images.xcassets */, - 7D17C52C1D659E580066232A /* AboutWindow.xib */, - 7D17C52D1D659E580066232A /* AboutWindowController.h */, - 7D17C52E1D659E580066232A /* AboutWindowController.m */, - 7D17C52F1D659E580066232A /* Consts.h */, - 7D17C5301D659E580066232A /* Exception.h */, - 7D17C5311D659E580066232A /* Exception.m */, - 7D62457E1D85348E00870565 /* Utilities.h */, - 7D62457F1D85348E00870565 /* Utilities.m */, - 7D17C5321D659E580066232A /* Images */, - 7D17C5351D659E580066232A /* Logging.h */, - 7D17C5361D659E580066232A /* Logging.m */, - 7D17C5371D659E580066232A /* Utilities.h */, - 7D17C5381D659E580066232A /* Utilities.m */, - ); - path = Shared; - sourceTree = ""; - }; - 7D17C5321D659E580066232A /* Images */ = { - isa = PBXGroup; - children = ( - 7D17C5331D659E580066232A /* icon.png */, - 7D17C5341D659E580066232A /* overSight.png */, - ); - path = Images; - sourceTree = ""; - }; - 7D5735551E9DE4B00086F474 /* 3rdParty */ = { - isa = PBXGroup; - children = ( - 7D5735561E9DE4BC0086F474 /* HyperlinkTextField.h */, - 7D5735571E9DE4BC0086F474 /* HyperlinkTextField.m */, - ); - name = 3rdParty; - sourceTree = ""; - }; - 7D5735591E9E46BD0086F474 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 7D57355A1E9E46BD0086F474 /* libbsm.tbd */, - ); - name = Frameworks; - sourceTree = ""; - }; - 7D6245861D87C53500870565 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 7DC9C8181D641A350017D143 /* main.m */, - 7DC9C81A1D641A350017D143 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 7DC9C8131D641A350017D143 /* XPCService */ = { - isa = PBXGroup; - children = ( - 7D62457A1D84FB8900870565 /* Enumerator.h */, - 7D62457B1D84FB8900870565 /* Enumerator.m */, - 7D17CFDE1D810D6D0017B475 /* XPCProtocol.h */, - 7DC9C8151D641A350017D143 /* OverSightXPC.h */, - 7DC9C8161D641A350017D143 /* OverSightXPC.m */, - 7D6245861D87C53500870565 /* Supporting Files */, - ); - name = XPCService; - path = OverSightXPC; - sourceTree = ""; - }; - 8B57559219DA3E9500799E6B = { - isa = PBXGroup; - children = ( - 7D9A7DEF1D8CADD10091C1AF /* main.h */, - 7D17C52B1D659E580066232A /* Shared */, - 8B57559D19DA3E9500799E6B /* MainApp */, - 8B5755C319DA3F9300799E6B /* LoginItem */, - 7DC9C8131D641A350017D143 /* XPCService */, - 8B57559C19DA3E9500799E6B /* Products */, - 7D5735591E9E46BD0086F474 /* Frameworks */, - ); - sourceTree = ""; - }; - 8B57559C19DA3E9500799E6B /* Products */ = { - isa = PBXGroup; - children = ( - 8B57559B19DA3E9500799E6B /* OverSight.app */, - 8B5755C219DA3F9300799E6B /* OverSight Helper.app */, - 7DC9C8121D641A350017D143 /* OverSightXPC.xpc */, - ); - name = Products; - sourceTree = ""; - }; - 8B57559D19DA3E9500799E6B /* MainApp */ = { - isa = PBXGroup; - children = ( - 7D5735551E9DE4B00086F474 /* 3rdParty */, - 7DC038071E8717B200349474 /* RuleRow.h */, - 7DC038081E8717B200349474 /* RuleRow.m */, - 7DC038041E8716F700349474 /* RuleRowCell.h */, - 7DC038051E8716F700349474 /* RuleRowCell.m */, - 8B5755A219DA3E9500799E6B /* AppDelegate.h */, - 8B5755A319DA3E9500799E6B /* AppDelegate.m */, - 8B5755A719DA3E9500799E6B /* MainMenu.xib */, - 7DC037FF1E87025100349474 /* RulesWindowController.h */, - 7DC038001E87025100349474 /* RulesWindowController.m */, - 7DC038011E87025100349474 /* Rules.xib */, - 8B57559E19DA3E9500799E6B /* Supporting Files */, - ); - path = MainApp; - sourceTree = ""; - }; - 8B57559E19DA3E9500799E6B /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 7D0EE28C1E99A90F003D910C /* patrons.txt */, - 8B57559F19DA3E9500799E6B /* Info.plist */, - 8B5755A019DA3E9500799E6B /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 8B5755C319DA3F9300799E6B /* LoginItem */ = { - isa = PBXGroup; - children = ( - 7D6216721E078C13002C1774 /* RemeberWindowController.h */, - 7D6216701E078C09002C1774 /* RemeberWindowController.m */, - 7D62166E1E078BDD002C1774 /* RememberPopup.xib */, - 7D17C5131D658FE20066232A /* Images */, - 8B5755C919DA3F9300799E6B /* AppDelegate.m */, - 7D17CFE21D81121E0017B475 /* AVMonitor.h */, - 7D17CFE11D81121E0017B475 /* AVMonitor.m */, - 7DAF4B7C1D656FD3000DA31A /* StatusBarMenu.h */, - 7DAF4B7D1D656FD3000DA31A /* StatusBarMenu.m */, - 8B5755CD19DA3F9300799E6B /* MainMenu.xib */, - 8B5755C419DA3F9300799E6B /* Supporting Files */, - ); - path = LoginItem; - sourceTree = ""; - }; - 8B5755C419DA3F9300799E6B /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 8B5755C519DA3F9300799E6B /* Info.plist */, - 8B5755C619DA3F9300799E6B /* main.m */, - 7D32457A1DA59A3700AE0711 /* main.h */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 7DC9C8111D641A350017D143 /* OverSightXPC */ = { - isa = PBXNativeTarget; - buildConfigurationList = 7DC9C81E1D641A350017D143 /* Build configuration list for PBXNativeTarget "OverSightXPC" */; - buildPhases = ( - 7DC9C80E1D641A350017D143 /* Sources */, - 7DC9C80F1D641A350017D143 /* Frameworks */, - 7DC9C8101D641A350017D143 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = OverSightXPC; - productName = OverSightXPC; - productReference = 7DC9C8121D641A350017D143 /* OverSightXPC.xpc */; - productType = "com.apple.product-type.xpc-service"; - }; - 8B57559A19DA3E9500799E6B /* OverSight */ = { - isa = PBXNativeTarget; - buildConfigurationList = 8B5755B819DA3E9500799E6B /* Build configuration list for PBXNativeTarget "OverSight" */; - buildPhases = ( - 8B57559719DA3E9500799E6B /* Sources */, - 8B57559819DA3E9500799E6B /* Frameworks */, - 8B57559919DA3E9500799E6B /* Resources */, - 8B5755E519DA42C100799E6B /* CopyFiles */, - 7DC9C8211D641A350017D143 /* Embed XPC Services */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = OverSight; - productName = "Test Application"; - productReference = 8B57559B19DA3E9500799E6B /* OverSight.app */; - productType = "com.apple.product-type.application"; - }; - 8B5755C119DA3F9300799E6B /* OverSight Helper */ = { - isa = PBXNativeTarget; - buildConfigurationList = 8B5755DC19DA3F9300799E6B /* Build configuration list for PBXNativeTarget "OverSight Helper" */; - buildPhases = ( - 8B5755BE19DA3F9300799E6B /* Sources */, - 8B5755BF19DA3F9300799E6B /* Frameworks */, - 8B5755C019DA3F9300799E6B /* Resources */, - 7D17D00B1D81369C0017B475 /* Embed XPC Services */, - ); - buildRules = ( - ); - dependencies = ( - 7D17CFE71D81349D0017B475 /* PBXTargetDependency */, - ); - name = "OverSight Helper"; - productName = "Test Application Helper"; - productReference = 8B5755C219DA3F9300799E6B /* OverSight Helper.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 8B57559319DA3E9500799E6B /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0900; - ORGANIZATIONNAME = "Cory Bohon"; - TargetAttributes = { - 7DC9C8111D641A350017D143 = { - CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = VBG97UB4TA; - }; - 8B57559A19DA3E9500799E6B = { - CreatedOnToolsVersion = 6.0.1; - DevelopmentTeam = VBG97UB4TA; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 0; - }; - }; - }; - 8B5755C119DA3F9300799E6B = { - CreatedOnToolsVersion = 6.0.1; - DevelopmentTeam = VBG97UB4TA; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 0; - }; - }; - }; - }; - }; - buildConfigurationList = 8B57559619DA3E9500799E6B /* Build configuration list for PBXProject "OverSight" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 8B57559219DA3E9500799E6B; - productRefGroup = 8B57559C19DA3E9500799E6B /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 8B57559A19DA3E9500799E6B /* OverSight */, - 8B5755C119DA3F9300799E6B /* OverSight Helper */, - 7DC9C8111D641A350017D143 /* OverSightXPC */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 7DC9C8101D641A350017D143 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8B57559919DA3E9500799E6B /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D0EE28D1E99A90F003D910C /* patrons.txt in Resources */, - 7D9A7DF21D8F2C900091C1AF /* Images.xcassets in Resources */, - 7DC038031E87025100349474 /* Rules.xib in Resources */, - 7D6245921D87D46800870565 /* InfoWindow.xib in Resources */, - 7D17C53C1D659E580066232A /* icon.png in Resources */, - 8B5755A919DA3E9500799E6B /* MainMenu.xib in Resources */, - 7D17C53D1D659E580066232A /* overSight.png in Resources */, - 7D17C5391D659E580066232A /* AboutWindow.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8B5755C019DA3F9300799E6B /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D6216741E07BB27002C1774 /* RememberPopup.xib in Resources */, - 7D9A7DE81D893E4F0091C1AF /* InfoWindow.xib in Resources */, - 7D6245951D87E14800870565 /* MainMenu.xib in Resources */, - 7D6245931D87E13200870565 /* icon.png in Resources */, - 7D6245941D87E13200870565 /* overSight.png in Resources */, - 7D17C5271D6592FE0066232A /* statusIcon.png in Resources */, - 7D6245851D87C43900870565 /* Images.xcassets in Resources */, - 7D17C5281D6592FE0066232A /* statusIcon@2x.png in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 7DC9C80E1D641A350017D143 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D9A7DEC1D8BE1E00091C1AF /* Exception.m in Sources */, - 7D6245871D87C55F00870565 /* Logging.m in Sources */, - 7D17CFE41D811B4E0017B475 /* Utilities.m in Sources */, - 7D62457C1D84FB8900870565 /* Enumerator.m in Sources */, - 7DC9C8191D641A350017D143 /* main.m in Sources */, - 7DC9C8171D641A350017D143 /* OverSightXPC.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8B57559719DA3E9500799E6B /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7DC038061E8716F700349474 /* RuleRowCell.m in Sources */, - 7DC038021E87025100349474 /* RulesWindowController.m in Sources */, - 7D6245911D87D39E00870565 /* InfoWindowController.m in Sources */, - 7D5735581E9DE4BC0086F474 /* HyperlinkTextField.m in Sources */, - 7D62458B1D87CE0900870565 /* AboutWindowController.m in Sources */, - 7D6245891D87C58400870565 /* Logging.m in Sources */, - 8B5755A419DA3E9500799E6B /* AppDelegate.m in Sources */, - 8B5755A119DA3E9500799E6B /* main.m in Sources */, - 7D17C53B1D659E580066232A /* Exception.m in Sources */, - 7DC038091E8717B200349474 /* RuleRow.m in Sources */, - 7D17C53F1D659E580066232A /* Utilities.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8B5755BE19DA3F9300799E6B /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D6216731E078D65002C1774 /* RemeberWindowController.m in Sources */, - 7D6245881D87C57600870565 /* Logging.m in Sources */, - 7D6245801D85348E00870565 /* Utilities.m in Sources */, - 7D17CFE51D8133840017B475 /* AVMonitor.m in Sources */, - 7D6245901D87D38400870565 /* InfoWindowController.m in Sources */, - 7DAF4B7F1D657192000DA31A /* StatusBarMenu.m in Sources */, - 8B5755CA19DA3F9300799E6B /* AppDelegate.m in Sources */, - 8B5755C719DA3F9300799E6B /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 7D17CFE71D81349D0017B475 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 7DC9C8111D641A350017D143 /* OverSightXPC */; - targetProxy = 7D17CFE61D81349D0017B475 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 8B5755A719DA3E9500799E6B /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 8B5755A819DA3E9500799E6B /* Base */, - ); - name = MainMenu.xib; - sourceTree = ""; - }; - 8B5755CD19DA3F9300799E6B /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 8B5755CE19DA3F9300799E6B /* Base */, - ); - name = MainMenu.xib; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 7DC9C81F1D641A350017D143 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_TESTABILITY = YES; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = OverSightXPC/Info.plist; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.OverSightXPC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 7DC9C8201D641A350017D143 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = OverSightXPC/Info.plist; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.OverSightXPC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Release; - }; - 8B5755B619DA3E9500799E6B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - USER_HEADER_SEARCH_PATHS = ../Shared; - }; - name = Debug; - }; - 8B5755B719DA3E9500799E6B /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COPY_PHASE_STRIP = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - USER_HEADER_SEARCH_PATHS = ../Shared; - }; - name = Release; - }; - 8B5755B919DA3E9500799E6B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - INFOPLIST_FILE = MainApp/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.OverSight"; - PRODUCT_NAME = OverSight; - PROVISIONING_PROFILE = ""; - }; - name = Debug; - }; - 8B5755BA19DA3E9500799E6B /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - INFOPLIST_FILE = MainApp/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.OverSight"; - PRODUCT_NAME = OverSight; - PROVISIONING_PROFILE = ""; - }; - name = Release; - }; - 8B5755DD19DA3F9300799E6B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COMBINE_HIDPI_IMAGES = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = LoginItem/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.OverSightHelper"; - PRODUCT_NAME = "OverSight Helper"; - PROVISIONING_PROFILE = ""; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 8B5755DE19DA3F9300799E6B /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = LoginItem/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.OverSightHelper"; - PRODUCT_NAME = "OverSight Helper"; - PROVISIONING_PROFILE = ""; - SKIP_INSTALL = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 7DC9C81E1D641A350017D143 /* Build configuration list for PBXNativeTarget "OverSightXPC" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7DC9C81F1D641A350017D143 /* Debug */, - 7DC9C8201D641A350017D143 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 8B57559619DA3E9500799E6B /* Build configuration list for PBXProject "OverSight" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 8B5755B619DA3E9500799E6B /* Debug */, - 8B5755B719DA3E9500799E6B /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 8B5755B819DA3E9500799E6B /* Build configuration list for PBXNativeTarget "OverSight" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 8B5755B919DA3E9500799E6B /* Debug */, - 8B5755BA19DA3E9500799E6B /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 8B5755DC19DA3F9300799E6B /* Build configuration list for PBXNativeTarget "OverSight Helper" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 8B5755DD19DA3F9300799E6B /* Debug */, - 8B5755DE19DA3F9300799E6B /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 8B57559319DA3E9500799E6B /* Project object */; -} diff --git a/OverSight.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/OverSight.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 0472f66..0000000 --- a/OverSight.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/OverSight.xcworkspace/contents.xcworkspacedata b/OverSight.xcworkspace/contents.xcworkspacedata index 6108574..78ffc6a 100644 --- a/OverSight.xcworkspace/contents.xcworkspacedata +++ b/OverSight.xcworkspace/contents.xcworkspacedata @@ -2,9 +2,9 @@ + location = "group:Installer/Installer.xcodeproj"> + location = "group:Application/Application.xcodeproj"> diff --git a/OverSight.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/OverSight.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/OverSight.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/OverSightXPC/Enumerator.h b/OverSightXPC/Enumerator.h deleted file mode 100644 index b255d59..0000000 --- a/OverSightXPC/Enumerator.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// Enumerator.h -// cameraUsers -// -// Created by Patrick Wardle on 9/9/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -@interface Enumerator : NSObject - -/* DEFINES */ - -//camera assistant daemon on older macs -#define VDC_ASSISTANT @"/System/Library/Frameworks/CoreMediaIO.framework/Versions/A/Resources/VDC.plugin/Contents/Resources/VDCAssistant" - -//camera assistant daemon -#define APPLE_CAMERA_ASSISTANT @"/Library/CoreMediaIO/Plug-Ins/DAL/AppleCamera.plugin/Contents/Resources/AppleCameraAssistant" - -//core audio -#define CORE_AUDIO @"/usr/sbin/coreaudiod" - -//lsmp binary -#define LSMP @"/usr/bin/lsmp" - -//sample binary -#define SAMPLE @"/usr/bin/sample" - -//path to siri -#define SIRI @"/System/Library/PrivateFrameworks/AssistantServices.framework/assistantd" - - -/* PROPERTIES */ - -//camera assistant pid -@property pid_t cameraAssistantProcess; - -//core audio pid -@property pid_t coreAudioProcess; - -//flag indicating video is active -@property BOOL videoActive; - -//flag indicating mic is active -@property BOOL audioActive; - -//list of procs that have send Mach msg to *Assistant -@property(nonatomic, retain)NSMutableDictionary* machSendersVideo; - -//list of procs that have send Mach msg to coreaudio -@property(nonatomic, retain)NSMutableDictionary* machSendersAudio; - -//list of procs that have i/o reg entries -// ->IOService:/AppleACPIPlatformExpert/IOPMrootDomain/RootDomainUserClient -@property(nonatomic, retain)NSMutableDictionary* userClients; - -/* METHODS */ - -//singleton interface -+(id)sharedManager; - -//forever, baseline by getting all current procs that have sent a mach msg to *Assistant -// ->ensures its only invoke while camera is not in use, so these are all just baselined procs --(void)start; - -//enumerate all (recent) process that appear to be using the mic --(NSMutableArray*)enumAudioProcs; - -//enumerate all (recent) process that appear to be using video --(NSMutableArray*)enumVideoProcs:(BOOL)polling; - -//set status of audio --(void)updateAudioStatus:(BOOL)isEnabled; - -//set status of video --(void)updateVideoStatus:(BOOL)isEnabled; - - -@end diff --git a/OverSightXPC/Enumerator.m b/OverSightXPC/Enumerator.m deleted file mode 100644 index 185e81b..0000000 --- a/OverSightXPC/Enumerator.m +++ /dev/null @@ -1,1070 +0,0 @@ -// -// Enumerator.m -// cameraUsers -// -// Created by Patrick Wardle on 9/9/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "main.h" -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" -#import "Enumerator.h" - -#import -#import - -//ignored mach sender procs -static NSArray* ignoredProcs = nil; - -@implementation Enumerator - -@synthesize audioActive; -@synthesize userClients; -@synthesize videoActive; -@synthesize coreAudioProcess; -@synthesize machSendersAudio; -@synthesize machSendersVideo; -@synthesize cameraAssistantProcess; - - -//init --(instancetype)init -{ - //init - if(self = [super init]) - { - //init ignored procs - ignoredProcs = @[ - @"/sbin/launchd", - @"/usr/libexec/lsd", - @"/usr/sbin/notifyd", - @"/usr/sbin/syslogd", - @"/usr/sbin/cfprefsd", - @"/usr/libexec/avconferenced", - @"/usr/libexec/opendirectoryd", - @"/usr/libexec/UserEventAgent", - @"/System/Library/CoreServices/launchservicesd", - @"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/CVMServer", - @"/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Resources/WindowServer", - @"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/Support/coreservicesd", - @"/System/Library/Frameworks/VideoToolbox.framework/Versions/A/XPCServices/VTDecoderXPCService.xpc/Contents/MacOS/VTDecoderXPCService" - ]; - } - - return self; -} - -//singleton interface -+(id)sharedManager -{ - //instance - static Enumerator* sharedEnumerator = nil; - - //once token - static dispatch_once_t onceToken; - - //init - // ->only exec'd once though :) - dispatch_once(&onceToken, ^{ - - //init - sharedEnumerator = [[self alloc] init]; - - }); - - return sharedEnumerator; -} - -//forever, baseline by getting all current procs that have sent a mach msg to *Assistant / coreaudio -// ->logic only exec'd while camera/mic is not in use, so these are all just baselined procs --(void)start -{ - //flag - BOOL nap = NO; - - //baseline forever - // ->though logic will skip if video or mic is active (respectively) - while(YES) - { - //le sleep? - if(nap == YES) - { - //nap - [NSThread sleepForTimeInterval:30]; - } - - //set flag - // from now on, want to wait a bit - nap = YES; - - //pool - @autoreleasepool - { - - //sync baselining - @synchronized(self) - { - //only baseline if video isn't active - if(YES != self.videoActive) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"baselining mach senders for video..."); - #endif - - //find camera assistant - // only do this once, or again, if it died - if( (0 == self.cameraAssistantProcess) || - (YES != isProcessAlive(self.cameraAssistantProcess)) ) - { - //find camera assistant - // ->first look for 'VDCAssistant' - self.cameraAssistantProcess = findProcess(VDC_ASSISTANT); - if(0 == self.cameraAssistantProcess) - { - //look for 'AppleCameraAssistant' - self.cameraAssistantProcess = findProcess(APPLE_CAMERA_ASSISTANT); - } - } - - //baseline - if(0 != self.cameraAssistantProcess) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"camera assistent process: %d", self.coreAudioProcess]); - #endif - - //enumerate procs that have send mach messages - self.machSendersVideo = [self enumMachSenders:self.cameraAssistantProcess]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu baselined mach senders: %@", (unsigned long)self.machSendersVideo.count, self.machSendersVideo]); - #endif - } - } - - //only baseline if audio isn't active - if(YES != self.audioActive) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"baselining mach senders for audio..."); - #endif - - //find core audio - // only do this once, or again, if it died - if( (0 == self.coreAudioProcess) || - (YES != isProcessAlive(self.coreAudioProcess)) ) - { - //find core audio - self.coreAudioProcess = findProcess(CORE_AUDIO); - } - - //baseline - if(0 != self.coreAudioProcess) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"camera core audio process: %d", self.coreAudioProcess]); - #endif - - //enumerate procs that have send mach messages - self.machSendersAudio = [self enumMachSenders:self.coreAudioProcess]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu baselined mach senders: %@", (unsigned long)self.machSendersAudio.count, self.machSendersVideo]); - - //dbg msg - logMsg(LOG_DEBUG, @"baselining i/o registry entries for audio..."); - #endif - - //enumerate procs that have i/o registry entries - self.userClients = [self enumDomainUserClients]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu baselined i/or registry senders: %@", (unsigned long)self.userClients.count, self.userClients]); - #endif - } - } - - }//sync - - }//pool - } - - return; -} - -//enumerate all (recent) process that appear to be using video --(NSMutableArray*)enumVideoProcs:(BOOL)polling -{ - //current procs - NSMutableArray* videoProcs = nil; - - //pool - @autoreleasepool - { - - //mach senders - NSMutableDictionary* currentSenders = nil; - - //candidate video procs - // ->those that have new mach message - NSMutableArray* candidateVideoProcs = nil; - - //foreground app - pid_t activeApp = 0; - - //alloc - candidateVideoProcs = [NSMutableArray array]; - - //sync this logic - // ->prevent baselining thread from doing anything - @synchronized(self) - { - - //find camera assistant - // only do this once, or again, if it died - if( (0 == self.cameraAssistantProcess) || - (YES != isProcessAlive(self.cameraAssistantProcess)) ) - { - //find camera assistant - // ->first look for 'VDCAssistant' - self.cameraAssistantProcess = findProcess(VDC_ASSISTANT); - if(0 == self.cameraAssistantProcess) - { - //look for 'AppleCameraAssistant' - self.cameraAssistantProcess = findProcess(APPLE_CAMERA_ASSISTANT); - } - } - - //sanity check - if(0 == self.cameraAssistantProcess) - { - //err msg - logMsg(LOG_ERR, @"failed to find VDCAssistant/AppleCameraAssistant process"); - - //bail - goto bail; - } - - //get procs that currrently have sent Mach msg to *Assistant - // ->returns dictionary of process id, and number of mach messages - currentSenders = [self enumMachSenders:self.cameraAssistantProcess]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu current mach senders: %@", (unsigned long)currentSenders.count, currentSenders]); - #endif - - //remove any known/existing senders - for(NSNumber* processID in currentSenders.allKeys) - { - //add any candidate procs - // ->those that have new mach message - if([currentSenders[processID] intValue] > [self.machSendersVideo[processID] intValue]) - { - //ignore client/requestor - if(clientPID == processID.intValue) - { - //ignore - continue; - } - - //add - [candidateVideoProcs addObject:processID]; - } - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu candidate video procs: %@", (unsigned long)candidateVideoProcs.count, candidateVideoProcs]); - #endif - - //update - self.machSendersVideo = currentSenders; - - //didn't find any? - // ->when not polling, add foreground process (and sample it below) - if( (0 == candidateVideoProcs.count) && - (YES != polling)) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"didn't find any candidate video apps, and not polling, so will grab (and sample) active application"); - #endif - - //get active app - activeApp = frontmostApplication(); - if(-1 != activeApp) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found active application: %d", activeApp]); - #endif - - //add it - [candidateVideoProcs addObject:[NSNumber numberWithInt:activeApp]]; - } - } - - //invoke 'sample' to confirm that candidates are using CMIO/video inputs - // ->note, will skip FaceTime.app on macOS Sierra, as it doesn't do CMIO stuff directly - videoProcs = [self sampleCandidates:candidateVideoProcs]; - - }//sync - - }//pool - -bail: - - return videoProcs; -} - -//enumerate all (recent) process that appear to be using the mic --(NSMutableArray*)enumAudioProcs -{ - //current procs - NSMutableArray* audioProcs = nil; - - //pool - @autoreleasepool - { - - //current mach senders - NSMutableDictionary* currentSenders = nil; - - //new senders - NSMutableArray* newSenders = nil; - - //current domain user clients (from i/o registry) - NSMutableDictionary* currentUserClients = nil; - - //new user clients - NSMutableArray* newUserClients = nil; - - //candidate audio procs - // ->those that have new mach message - NSMutableArray* candidateAudioProcs = nil; - - //itersection set - NSMutableSet* intersection = nil; - - //'frontmost' application - pid_t activeApp = -1; - - //alloc array - newSenders = [NSMutableArray array]; - - //alloc array - newUserClients = [NSMutableArray array]; - - //sync this logic - // ->prevent baselining thread from doing anything - @synchronized(self) - { - //find coreaudio - //find core audio - // only do this once, or again, if it died - if( (0 == self.coreAudioProcess) || - (YES != isProcessAlive(self.coreAudioProcess)) ) - { - //find core audio - self.coreAudioProcess = findProcess(CORE_AUDIO); - } - - //sanity check - if(0 == self.coreAudioProcess) - { - //err msg - logMsg(LOG_ERR, @"failed to find coreaudio process"); - - //bail - goto bail; - } - - //get procs that currrently have sent Mach msg to core audio - // ->returns dictionary of process id, and number of mach messages - currentSenders = [self enumMachSenders:self.coreAudioProcess]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu current mach senders: %@", (unsigned long)currentSenders.count, currentSenders]); - #endif - - //add new senders or those w/ new mach msgs - for(NSNumber* processID in currentSenders.allKeys) - { - //ignore client/requestor (self) - if(clientPID == processID.intValue) - { - //skip - continue; - } - - //skip any that don't have new mach message - if( (nil != self.machSendersAudio[processID]) && - ([self.machSendersAudio[processID] intValue] >= [currentSenders[processID] intValue]) ) - { - //skip - continue; - } - - //ok new, so add - [newSenders addObject:processID]; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"new mach senders: %@", newSenders]); - #endif - - //update iVar - self.machSendersAudio = currentSenders; - - //grab current 'IOPMrootDomain/RootDomainUserClient/IOUserClientCreator's - currentUserClients = [self enumDomainUserClients]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu current i/o registry user clients: %@", (unsigned long)currentUserClients.count, currentUserClients]); - #endif - - //add new user clients - for(NSNumber* processID in currentUserClients.allKeys) - { - //ignore client/requestor (self) - if(clientPID == processID.intValue) - { - //skip - continue; - } - - //skip any that don't have new mach message - if( (nil != self.userClients[processID]) && - ([self.userClients[processID] intValue] >= [currentUserClients[processID] intValue]) ) - { - //skip - continue; - } - - //ok new, so add - [newUserClients addObject:processID]; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"new user clients: %@", newUserClients]); - #endif - - //update iVar - self.userClients = currentUserClients; - - //init set for intersection - intersection = [NSMutableSet setWithArray:newSenders]; - - //get procs that have sent mach messages *and* have an entry in the i/o registry - [intersection intersectSet:[NSMutableSet setWithArray:newUserClients]]; - - //assign - candidateAudioProcs = [[intersection allObjects] mutableCopy]; - - //if there aren't any new i/o registy clients might just be siri - if(0 == candidateAudioProcs.count) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"no new user clients"); - #endif - - //1 new mach msg sender - // just use that as candidate - if(1 == newSenders.count) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"but only found one new mach sender, so using that!"); - #endif - - //assign as candidate - [candidateAudioProcs addObject:newSenders.firstObject]; - } - - //more than - // ->check if any are siri ('assisantd')? - else - { - //check each new ones - for(NSNumber* newSender in newSenders) - { - //check each - if(YES == [SIRI isEqualToString:getProcessPath([newSender intValue])]) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"found a mach sender that's 'siri' so using that!"); - #endif - - //assign as candidate - [candidateAudioProcs addObject:newSenders.firstObject]; - - //all set - break; - } - } - } - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu candidate audio procs: %@", (unsigned long)candidateAudioProcs.count, candidateAudioProcs]); - #endif - - //only one candidate? - // ->all set, so assign, then bail here - if(1 == candidateAudioProcs.count) - { - //assign - audioProcs = candidateAudioProcs; - - //bail - goto bail; - } - - //still none - // ->add active app as candiate (and sample it, below) - if(0 == candidateAudioProcs.count) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"didn't find any candidate audio apps, will grab (and sample) active application"); - #endif - - //get active app - activeApp = frontmostApplication(); - if(-1 != activeApp) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found active application: %d", activeApp]); - #endif - - //add it - [candidateAudioProcs addObject:[NSNumber numberWithInt:activeApp]]; - } - } - - //got one or more candidate application - // ->invoke 'sample' to determine which candidate is using CMIO/video inputs - // note: will skip FaceTime.app on macOS Sierra, as it doesn't do CMIO stuff directly - audioProcs = [self sampleCandidates:candidateAudioProcs]; - - }//sync - - }//pool - -bail: - - return audioProcs; -} - -//get procs that currrently have sent Mach msg to a target process -// ->returns dictionary of process id, and number of mach messages --(NSMutableDictionary*)enumMachSenders:(pid_t)targetProcess -{ - //senders - NSMutableDictionary* senders = nil; - - //pool - @autoreleasepool - { - - //results from 'lsmp' cmd - NSString* results = nil; - - //substrings - NSArray* subStrings = nil; - - //process id - NSNumber* processID = nil; - - //process path - NSString* processPath = nil; - - //alloc - senders = [NSMutableDictionary dictionary]; - - //exec 'lsmp' w/ pid of camera asssistant to get mach ports - results = [[NSString alloc] initWithData:execTask(LSMP, @[@"-p", @(targetProcess).stringValue], YES) encoding:NSUTF8StringEncoding]; - if( (nil == results) || - (0 == results.length) ) - { - //bail - goto bail; - } - - //parse results - // ->looking for () process name - for(NSString* line in [results componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\n"]]) - { - //skip blank lines - if(0 == line.length) - { - //skip - continue; - } - - //parse on '()' - subStrings = [line componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"()"]]; - if(subStrings.count < 3) - { - //skip - continue; - } - - //skip 'unknown' processes - // output looks like "(-) Unknown Process" - if(YES == [[subStrings objectAtIndex:0x1] isEqualToString:@"-"]) - { - //skip - continue; - } - - //extract process id - // ->insides '()', so will be second substring - processID = @([[subStrings objectAtIndex:0x1] integerValue]); - if(nil == processID) - { - //skip - continue; - } - - //ignore target process - if(targetProcess == processID.intValue) - { - //skip - continue; - } - - //get process path - // ->skip blank/unknown procs - processPath = getProcessPath(processID.intValue); - if( (nil == processPath) || - (0 == processPath.length) ) - { - //skip - continue; - } - - //ignore apple daemons (that send mach messages, etc) - if(YES == [ignoredProcs containsObject:processPath]) - { - //skip - continue; - } - - //add/inc to dictionary - senders[processID] = @([senders[processID] unsignedIntegerValue] + 1); - } - - }//pool - -bail: - - return senders; -} - -//iterate thru i/o registry to get all RootDomainUserClient under IOPMrootDomain -// ->returns dictionary of process id, and number of user client entries --(NSMutableDictionary*)enumDomainUserClients -{ - //array of RootDomainUserClients - NSMutableDictionary* clients = nil; - - //pool - @autoreleasepool - { - - //matching service - io_service_t matchingService = 0; - - //iterator - io_iterator_t iterator = 0; - - //kids - io_registry_entry_t child = 0; - - //client creator - CFTypeRef creator = 0; - - //for parsing - NSArray* components = nil; - - //process id - NSNumber* processID = nil; - - //alloc - clients = [NSMutableDictionary dictionary]; - - //get IOPMrootDomain obj - matchingService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMrootDomain")); - if(0 == matchingService) - { - //bail - goto bail; - } - - //get iterator - if(noErr != IORegistryEntryGetChildIterator(matchingService, kIOServicePlane, &iterator)) - { - //bail - goto bail; - } - - //iterator over all children - // ->store all that have 'IOUserClientCreator' - while((child = IOIteratorNext(iterator))) - { - //try get creator - creator = IORegistryEntryCreateCFProperty(child, CFSTR("IOUserClientCreator"), kCFAllocatorDefault, 0); - - //always release child - IOObjectRelease(child); - - //unset - child = 0; - - //if couldn't get a creator - // ->might just not be of RootDomainUserClient, so skip - if(0 == creator) - { - //skip - continue; - } - - //parse - components = [(__bridge NSString*)creator componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" ,"]]; - - //extact pid and save - if(components.count >= 4) - { - //grab pid - // format is: "pid 4781, process" - processID = [NSNumber numberWithShort:[components[0x1] intValue]]; - if(0 != processID.intValue) - { - //add/inc to dictionary - clients[processID] = @([clients[processID] unsignedIntegerValue] + 1); - } - } - - //release - CFRelease(creator); - - //unset - creator = 0; - } - -bail: - - //release iterator - if(0 != iterator) - { - //release - IOObjectRelease(iterator); - - //unset - iterator = 0; - } - - //release obj - if(0 != matchingService) - { - //release - IOObjectRelease(matchingService); - - //unset - matchingService = 0; - } - - }//pool - - return clients; -} - -//invoke 'sample' to confirm candidates are using CMIO/video/av inputs -// note: path audio/vide invoke 'CMIOGraph::DoWork' --(NSMutableArray*)sampleCandidates:(NSArray*)currentSenders -{ - //av procs - NSMutableArray* avProcs = nil; - - //pool - @autoreleasepool - { - - //results from 'sample' cmd - NSString* results = nil; - - //process path - NSString* processPath = nil; - - //alloc - avProcs = [NSMutableArray array]; - - //invoke 'sample' on each - // ->skips FaceTime.app though on macOS Sierra - for(NSNumber* processID in currentSenders) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"processing %d for sampling", processID.intValue]); - #endif - - //get process path - // ->skip ones that fail - processPath = getProcessPath(processID.intValue); - if( (nil == processPath) || - (0 == processPath.length) ) - { - //next - continue; - } - - //if we're running on macOS Sierra and there is only 1 candidate proc and its FaceTime - // ->don't sample, as it does thing wierdly.... - if( (YES == [processPath isEqualToString:FACE_TIME]) && - ([getOSVersion() [@"minorVersion"] intValue] >= 12) ) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"not sampling as candidate app is FaceTime on macOS Sierra"); - #endif - - //add - [avProcs addObject:processID]; - - //next - continue; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"sampling %d", processID.intValue]); - #endif - - //exec 'sample' to get threads/dylibs - // ->uses 1.0 seconds for sampling time - results = [[NSString alloc] initWithData:execTask(SAMPLE, @[processID.stringValue, @"1"], YES) encoding:NSUTF8StringEncoding]; - if( (nil == results) || - (0 == results.length) ) - { - //skip - continue; - } - - //sampling a process creates a temp file - //->make sure we delete it this file to clean up ;) - [self deleteSampleFile:processPath]; - - //for now, just check for 'CMIOGraph::DoWork' - // ->note: both audio/video invoke this, so this method works for both! - if(YES != [results containsString:@"CMIOGraph::DoWork"]) - { - //skip - continue; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"processing %d for has 'CMIOGraph::DoWork', as adding to list of candidates", processID.intValue]); - #endif - - //looks like a av proc! - [avProcs addObject:processID]; - } - - }//pool - - return avProcs; -} - -//'sample' binary creates a file -// ->this looks for that file and deletes it --(void)deleteSampleFile:(NSString*)processPath -{ - //pool - @autoreleasepool - { - - //error - NSError* error = nil; - - //files - NSArray* files = nil; - - //grab all files in /tmp - files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/tmp/" error:&error]; - if(nil != error) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to enumerate files in /tmp, %@", error]); - - //bail - goto bail; - } - - //find/delete file - for(NSString* file in files) - { - //skip non-sample files - if(YES != [file hasSuffix:@".sample.txt"]) - { - //skip - continue; - } - - //ignore files that don't contain process name - if(YES != [file containsString:[processPath lastPathComponent]]) - { - //skip - continue; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"deleting sample file: %@", file]); - #endif - - //delete - if(YES != [[NSFileManager defaultManager] removeItemAtPath:[@"/tmp" stringByAppendingPathComponent:file] error:&error]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete %@ (%@)", file, error]); - - //bail - goto bail; - } - - }//all files - - }//pool - -//bail -bail: - - - return; -} - -//set status of video -// ->extra logic is executed to 'refresh' iVars when video is disabled --(void)updateVideoStatus:(BOOL)isEnabled -{ - //pool - @autoreleasepool - { - - //sync - @synchronized(self) - { - //set - self.videoActive = isEnabled; - - //when video disabled - // ->re-enumerate mach senders - if(YES != isEnabled) - { - //find camera assistant - // only do this once, or again, if it died - if( (0 == self.cameraAssistantProcess) || - (YES != isProcessAlive(self.cameraAssistantProcess)) ) - { - //find camera assistant - // ->first look for 'VDCAssistant' - self.cameraAssistantProcess = findProcess(VDC_ASSISTANT); - if(0 == self.cameraAssistantProcess) - { - //look for 'AppleCameraAssistant' - self.cameraAssistantProcess = findProcess(APPLE_CAMERA_ASSISTANT); - } - } - - //sanity check - if(0 == self.cameraAssistantProcess) - { - //err msg - logMsg(LOG_ERR, @"failed to find VDCAssistant/AppleCameraAssistant process"); - - //bail - goto bail; - } - - //enumerate mach senders - self.machSendersVideo = [self enumMachSenders:self.cameraAssistantProcess]; - } - - }//sync - - }//pool - -//bail -bail: - - return; -} - -//set status of audio -// ->extra logic is executed to 'refresh' iVars when audio is disabled --(void)updateAudioStatus:(BOOL)isEnabled -{ - //pool - @autoreleasepool - { - - //sync - @synchronized(self) - { - //set - self.audioActive = isEnabled; - - //when audio disabled - // ->re-enumerate mach senders & i/o registry user clients - if(YES != isEnabled) - { - //find coreaudio - //find core audio - // only do this once, or again, if it died - if( (0 == self.coreAudioProcess) || - (YES != isProcessAlive(self.coreAudioProcess)) ) - { - //find core audio - self.coreAudioProcess = findProcess(CORE_AUDIO); - } - - //enumerate - if(0 != self.coreAudioProcess) - { - //enumerate mach senders - self.machSendersAudio = [self enumMachSenders:self.coreAudioProcess]; - - //enumerate i/o registry user clients - self.userClients = [self enumDomainUserClients]; - } - } - } - - }//pool - - return; -} - -@end - - diff --git a/OverSightXPC/Info.plist b/OverSightXPC/Info.plist deleted file mode 100644 index 2330d25..0000000 --- a/OverSightXPC/Info.plist +++ /dev/null @@ -1,33 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - OverSightXPC - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - XPC! - CFBundleShortVersionString - 1.2.0 - CFBundleSignature - ???? - CFBundleVersion - 1.2.0 - NSHumanReadableCopyright - Copyright (c) 2017 Objective-See. All rights reserved. - XPCService - - ServiceType - Application - - - diff --git a/OverSightXPC/OverSightXPC.h b/OverSightXPC/OverSightXPC.h deleted file mode 100644 index fa9ab83..0000000 --- a/OverSightXPC/OverSightXPC.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// OverSightXPC.h -// OverSightXPC -// -// Created by Patrick Wardle on 8/16/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "XPCProtocol.h" -#import - -/* DEFINES */ - - -// This object implements the protocol which we have defined. It provides the actual behavior for the service. It is 'exported' by the service to make it available to the process hosting the service over an NSXPCConnection. -@interface OverSightXPC : NSObject - -/* PROPERTIES */ - -//flag indicating video is action -@property BOOL videoActive; - -//list of procs that have send Mach msg to *Assistant -@property(nonatomic, retain)NSMutableDictionary* machSenders; - -@end diff --git a/OverSightXPC/OverSightXPC.m b/OverSightXPC/OverSightXPC.m deleted file mode 100644 index 0724746..0000000 --- a/OverSightXPC/OverSightXPC.m +++ /dev/null @@ -1,252 +0,0 @@ -// -// OverSightXPC.m -// OverSightXPC -// -// Created by Patrick Wardle on 8/16/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" -#import "Enumerator.h" -#import "OverSightXPC.h" - -@implementation OverSightXPC - -@synthesize machSenders; -@synthesize videoActive; - -//do any initializations -// ->for now, just kick off enumerator --(void)initialize:(void (^)(void))reply -{ - //start enumerating - // will forever baseline current mach msg procs - [NSThread detachNewThreadSelector:@selector(start) toTarget:[Enumerator sharedManager] withObject:nil]; - - //reply - reply(); - - return; -} - -//heartbeat -// need as otherwise kernel might kill XPC --(void)heartBeat:(void (^)(BOOL))reply -{ - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"heartbeat request"); - #endif - - //nap - [NSThread sleepForTimeInterval:3.0f]; - - reply(YES); - - return; -} - -//call into emumerate to get (new) video proc --(void)getVideoProcs:(BOOL)polling reply:(void (^)(NSMutableArray *))reply -{ - //reply w/ video procs - reply([[Enumerator sharedManager] enumVideoProcs:polling]); - - return; -} - -//call into emumerate to get (new) audio proc --(void)getAudioProcs:(void (^)(NSMutableArray *))reply -{ - //reply w/ video procs - reply([[Enumerator sharedManager] enumAudioProcs]); - - return; -} - -//update status video -// ->allows enumerator to stop baselining (when active), etc --(void)updateVideoStatus:(unsigned int)status reply:(void (^)(void))reply -{ - //set status - [[Enumerator sharedManager] updateVideoStatus:status]; - - //reply - reply(); - - return; -} - -//update status audio -// ->allows enumerator to stop baselining (when active), etc --(void)updateAudioStatus:(unsigned int)status reply:(void (^)(void))reply -{ - //set status - [[Enumerator sharedManager] updateAudioStatus:status]; - - //reply - reply(); - - return; -} - -//whitelist a process --(void)whitelistProcess:(NSString*)processPath device:(NSNumber*)device reply:(void (^)(BOOL))reply -{ - //flag - BOOL wasAdded = NO; - - //path to whitelist - NSString* path = nil; - - //whitelist - NSMutableArray* whiteList = nil; - - //init path to whitelist - path = [[[@"~" stringByAppendingPathComponent:APP_SUPPORT_DIRECTORY] stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST]; - - //load whitelist - whiteList = [NSMutableArray arrayWithContentsOfFile:path]; - - //failed to load - // ->might not exist yet, so alloc - if(nil == whiteList) - { - //alloc - whiteList = [NSMutableArray array]; - } - - //add - [whiteList addObject:@{EVENT_PROCESS_PATH:processPath, EVENT_DEVICE:device}]; - - //save to disk - if(YES != [whiteList writeToFile:path atomically:YES]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"XPC: failed to save %@ -> %@", processPath, path]); - - //bail - goto bail; - } - - //happy - wasAdded = YES; - -bail: - - //reply - reply(wasAdded); - - return; -} - -//remove a process from the whitelist file --(void)unWhitelistProcess:(NSString*)processPath device:(NSNumber*)device reply:(void (^)(BOOL))reply -{ - //flag - BOOL wasRemoved = NO; - - //path to whitelist - NSString* path = nil; - - //whitelist - NSMutableArray* whiteList = nil; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got request to unwhitelist %@/%@", processPath, device]); - #endif - - //init path to whitelist - path = [[[@"~" stringByAppendingPathComponent:APP_SUPPORT_DIRECTORY] stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST]; - - //load whitelist - whiteList = [NSMutableArray arrayWithContentsOfFile:path]; - if(nil == whiteList) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"XPC: failed to load whitelist from %@", path]); - - //bail - goto bail; - } - - //find/remove item from whitelist - for(NSDictionary* item in whiteList) - { - //match path and device? - if( (YES == [item[EVENT_PROCESS_PATH] isEqualToString:processPath]) && - ([item[EVENT_DEVICE] intValue] == device.intValue) ) - { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"found match in whitelist, will remove!"); - #endif - - //remove - // ->ok, since we aren't going to iterate any more - [whiteList removeObject:item]; - - //save to disk - if(YES != [whiteList writeToFile:path atomically:YES]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"XPC: failed to save updated whitelist to %@", path]); - - //bail - goto bail; - } - - //happy - wasRemoved = YES; - - //done - goto bail; - } - } - -bail: - - //reply - reply(wasRemoved); - - return; -} - -//kill a process --(void)killProcess:(NSNumber*)processID reply:(void (^)(BOOL))reply -{ - //flag - BOOL wasKilled = NO; - - //terminate - if(-1 == kill(processID.intValue, SIGKILL)) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"XPC: failed to kill %@, with %d", processID, errno]); - - //bail - goto bail; - } - - //happy - wasKilled = YES; - -bail: - - //reply - reply(wasKilled); - - return; -} - -//exit --(void)exit -{ - //bye - exit(0); -} - -@end diff --git a/OverSightXPC/main.h b/OverSightXPC/main.h deleted file mode 100644 index 513fba0..0000000 --- a/OverSightXPC/main.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// main.h -// OverSight -// -// Created by Patrick Wardle on 9/16/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#ifndef main_h -#define main_h - -#import -#import - -#import "Logging.h" -#import "Exception.h" -#import "XPCProtocol.h" -#import "OverSightXPC.h" - - -/* GLOBALS */ - -//client/requestor pid -extern pid_t clientPID; - -/* FUNCTION DEFINITIONS */ - -OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement); - -/* #DEFINES */ - -//signing auth -#define SIGNING_AUTH @"Developer ID Application: Objective-See, LLC (VBG97UB4TA)" - -/* INTERFACES */ - -//interface for 'extension' to NSXPCConnection -// ->allows us to access the 'private' auditToken iVar -@interface ExtendedNSXPCConnection : NSXPCConnection -{ - //private iVar - audit_token_t auditToken; -} -//private iVar -@property audit_token_t auditToken; - -@end - -//skeleton interface -@interface ServiceDelegate : NSObject -@end - - -#endif /* main_h */ diff --git a/OverSightXPC/main.m b/OverSightXPC/main.m deleted file mode 100644 index 485c5a0..0000000 --- a/OverSightXPC/main.m +++ /dev/null @@ -1,142 +0,0 @@ -// -// main.m -// OverSightXPC -// -// Created by Patrick Wardle on 8/16/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "main.h" -#import "Logging.h" - -/* GLOBALS */ - -//client/requestor pid -pid_t clientPID = 0; - -//implementation for 'extension' to NSXPCConnection -// ->allows us to access the 'private' auditToken iVar -@implementation ExtendedNSXPCConnection - -//private iVar -@synthesize auditToken; - -@end - -@implementation ServiceDelegate - -//automatically invoked -// allows NSXPCListener to configure/accept/resume a new incoming NSXPCConnection -// note: we only allow binaries signed by Objective-See to connect & talk to this! --(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection -{ - //flag - BOOL shouldAccept = NO; - - //task ref - SecTaskRef taskRef = 0; - - //signing req string - NSString *requirementString = nil; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"new client connection"); - #endif - - //init signing req string - requirementString = [NSString stringWithFormat:@"anchor trusted and certificate leaf [subject.CN] = \"%@\"", SIGNING_AUTH]; - - //step 1: create task ref - // ->uses NSXPCConnection's (private) 'auditToken' iVar - taskRef = SecTaskCreateWithAuditToken(NULL, ((ExtendedNSXPCConnection*)newConnection).auditToken); - if(NULL == taskRef) - { - //bail - goto bail; - } - - //step 2: validate - // ->check that client is signed with Objective-See's dev cert - if(0 != SecTaskValidateForRequirement(taskRef, (__bridge CFStringRef)(requirementString))) - { - //bail - goto bail; - } - - //set the interface that the exported object implements - newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; - - //set object exported by connection - newConnection.exportedObject = [[OverSightXPC alloc] init]; - - //resume - [newConnection resume]; - - //grab client/requestor's pid - clientPID = audit_token_to_pid(((ExtendedNSXPCConnection*)newConnection).auditToken); - - //happy - shouldAccept = YES; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"accepted new client connection (pid: %d)", clientPID]); - #endif - -//bail -bail: - - //release task ref object - if(NULL != taskRef) - { - //release - CFRelease(taskRef); - - //unset - taskRef = NULL; - } - - return shouldAccept; -} - -@end - -//main entrypoint -// ->install exception handlers & setup/kickoff listener -int main(int argc, const char *argv[]) -{ - //ret var - int status = -1; - - //service delegate - ServiceDelegate* delegate = nil; - - //listener - NSXPCListener* listener = nil; - - //first thing... - // ->install exception handlers! - installExceptionHandlers(); - - //create the delegate for the service. - delegate = [[ServiceDelegate alloc] init]; - - //set up the one NSXPCListener for this service - // ->handles incoming connections - listener = [NSXPCListener serviceListener]; - - //set delegate - listener.delegate = delegate; - - //resuming the listener starts this service - // ->method does not return - [listener resume]; - - //happy - status = 0; - -bail: - - return status; -} diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fcd8eb --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# BlockBlock +BlockBlock provides continual protection by monitoring persistence locations: +

+ +For more details, see its [product page](https://objective-see.com/products/blockblock.html). diff --git a/Shared/AboutWindowController.h b/Shared/AboutWindowController.h deleted file mode 100644 index 47d5869..0000000 --- a/Shared/AboutWindowController.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// AboutWindowController.h -// OverSight -// -// Created by Patrick Wardle on 7/15/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -@interface AboutWindowController : NSWindowController -{ - -} - -/* PROPERTIES */ - -//version label/string -@property (weak) IBOutlet NSTextField *versionLabel; - -//patrons -@property (unsafe_unretained) IBOutlet NSTextView *patrons; - - -/* METHODS */ - - -@end diff --git a/Shared/Consts.h b/Shared/Consts.h index 7ff2be7..6108f41 100644 --- a/Shared/Consts.h +++ b/Shared/Consts.h @@ -1,76 +1,89 @@ // -// Consts.h -// OverSight +// file: consts.h +// project: OverSight (shared) +// description: #defines and what not // -// Created by Patrick Wardle on 7/7/16. -// Copyright (c) 2016 Objective-See. All rights reserved. +// created by Patrick Wardle +// copyright (c) 2020 Objective-See. All rights reserved. // -#ifndef OS_Consts_h -#define OS_Consts_h +#ifndef consts_h +#define consts_h -//success -#define STATUS_SUCCESS 0 +//start at login +#define PREF_AUTOSTART_MODE @"startAtLogin" -//apps folder -#define APPS_FOLDER @"/Applications" +//disable 'inactive' alerts +#define PREF_DISABLE_INACTIVE @"disableInactive" + +//pref +// execute action +#define PREF_EXECUTE_ACTION @"executeAction" + +//pref +// execution path +#define PREF_EXECUTE_PATH @"executePath" + +//pref +// execute action +#define PREF_EXECUTE_ACTION_ARGS @"executeActionArgs" + +//cs consts +// from: cs_blobs.h +#define CS_VALID 0x00000001 +#define CS_ADHOC 0x0000002 +#define CS_RUNTIME 0x00010000 + +//patreon url +#define PATREON_URL @"https://www.patreon.com/join/objective_see" + +//sentry crash reporting URL +#define SENTRY_DSN @"https://729ac84fd0014ea1aa48ca46386546b6@o130950.ingest.sentry.io/5745525" //app name -#define APP_NAME @"OverSight.app" +#define PRODUCT_NAME @"OverSight" -//app helper name -#define APP_HELPER @"OverSight Helper" +//bundle ID +#define BUNDLE_ID "com.objective-see.oversight" -//app helper XPC -#define APP_HELPER_XPC @"OverSight XPC" +//main app bundle id +#define MAIN_APP_ID @"com.objective-see.oversight" -//product url -#define PRODUCT_URL @"https://objective-see.com/products/oversight.html" +//helper (login item) ID +#define HELPER_ID @"com.objective-see.oversight.helper" + +//installer (app) ID +#define INSTALLER_ID @"com.objective-see.oversight.installer" + +//installer (helper) ID +#define CONFIG_HELPER_ID @"com.objective-see.oversight.uninstallHelper" + +//signing auth +#define SIGNING_AUTH @"Developer ID Application: Objective-See, LLC (VBG97UB4TA)" //product version url #define PRODUCT_VERSIONS_URL @"https://objective-see.com/products.json" -//patreon url -#define PATREON_URL @"https://www.patreon.com/objective_see" +//product url +#define PRODUCT_URL @"https://objective-see.com/products/oversight.html" -//OS version x -#define OS_MAJOR_VERSION_X 10 +//error(s) url +#define ERRORS_URL @"https://objective-see.com/errors.html" -//OS minor version yosemite -#define OS_MINOR_VERSION_YOSEMITE 10 +//support us button tag +#define BUTTON_SUPPORT_US 100 -//OS minor version el capitan -#define OS_MINOR_VERSION_EL_CAPITAN 11 +//more info button tag +#define BUTTON_MORE_INFO 101 -//install flag -#define CMD_INSTALL "-install" +//install cmd +#define CMD_INSTALL @"-install" -//uninstall flag -#define CMD_UNINSTALL "-uninstall" +//uninstall cmd +#define CMD_UNINSTALL @"-uninstall" -//action to install -// ->also button title -#define ACTION_INSTALL @"Install" - -//action to uninstall -// ->also button title -#define ACTION_UNINSTALL @"Uninstall" - -//button title -// ->close -#define ACTION_CLOSE @"Close" - -//button title -// ->next -#define ACTION_NEXT @"Next »" - -//button title -// ->no -#define ACTION_NO @"No" - -//button title -// ->yes -#define ACTION_YES @"Yes!" +//install cmd +#define CMD_UPGRADE @"-upgrade" //flag to uninstall #define ACTION_UNINSTALL_FLAG 0 @@ -78,65 +91,131 @@ //flag to install #define ACTION_INSTALL_FLAG 1 -//flag for partial uninstall (leave whitelist) -#define UNINSTALL_PARIAL 0 +//flag for partial uninstall +// leave preferences file, etc. +#define UNINSTALL_PARTIAL 0 //flag for full uninstall #define UNINSTALL_FULL 1 -//error msg -#define KEY_ERROR_MSG @"errorMsg" +//add rule, block +#define BUTTON_BLOCK 0 -//sub msg -#define KEY_ERROR_SUB_MSG @"errorSubMsg" +//add rule, allow +#define BUTTON_ALLOW 1 -//error URL -#define KEY_ERROR_URL @"errorURL" +//prefs +// disabled status +#define PREF_IS_DISABLED @"disabled" -//flag for error popup -#define KEY_ERROR_SHOULD_EXIT @"shouldExit" +//prefs +// passive mode +#define PREF_PASSIVE_MODE @"passiveMode" -//errors url -#define ERRORS_URL @"https://objective-see.com/errors.html" +//prefs +// icon mode +#define PREF_NO_ICON_MODE @"noIconMode" + +//prefs +// update mode +#define PREF_NO_UPDATE_MODE @"noupdateMode" + +//allowed items (key) +#define PREFS_ALLOWED_ITEMS @"allowedItems" //general error URL #define FATAL_ERROR_URL @"https://objective-see.com/errors.html" -//path to preferences -#define APP_PREFERENCES @"~/Library/Preferences/com.objective-see.OverSight.plist" +//key for exit code +#define EXIT_CODE @"exitCode" -//log activity button -#define PREF_LOG_ACTIVITY @"logActivity" +//rules changed +#define RULES_CHANGED @"com.objective-see.oversight.rulesChanged" -//automatically check for updates button -#define PREF_CHECK_4_UPDATES @"check4Updates" +//first time flag +#define INITIAL_LAUNCH @"-initialLaunch" -//run in headless mode -#define PREF_RUN_HEADLESS @"runHeadless" +/* INSTALLER */ -//start at login -#define PREF_START_AT_LOGIN @"startAtLogin" +//menu: 'about' +#define MENU_ITEM_ABOUT 0 -//disable 'inactive' alerts -#define PREF_DISABLE_INACTIVE @"disableInactive" +//menu: 'quit' +#define MENU_ITEM_QUIT 1 -//keycode for 'q' -#define KEYCODE_Q 0x0C -//path to pkill -#define PKILL @"/usr/bin/pkill" +//app name +#define APP_NAME @"OverSight.app" -//path to xattr -#define XATTR @"/usr/bin/xattr" +//apps folder +#define APPS_FOLDER @"/Applications" + +//frame shift +// for status msg to avoid activity indicator +#define FRAME_SHIFT 45 + +//flag to close +#define ACTION_CLOSE_FLAG -1 + +//cmdline flag to uninstall +#define ACTION_UNINSTALL @"-uninstall" + +//flag to uninstall +#define ACTION_UNINSTALL_FLAG 0 + +//cmdline flag to uninstall +#define ACTION_INSTALL @"-install" + +//flag to install +#define ACTION_INSTALL_FLAG 1 + +//button title: upgrade +#define ACTION_UPGRADE @"Upgrade" + +//button title: close +#define ACTION_CLOSE @"Close" + +//button title: next +#define ACTION_NEXT @"Next »" + +//show friends +#define ACTION_SHOW_SUPPORT 4 + +//support us +#define ACTION_SUPPORT 5 + +//register +#define LSREGISTER @"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister" + +//path to open +#define OPEN @"/usr/bin/open" + +//path to launchctl +#define LAUNCHCTL @"/bin/launchctl" + +//path to defaults +#define DEFAULTS @"/usr/bin/defaults" + +//path to kill all +#define KILL_ALL @"/usr/bin/killall" //path to facetime -#define FACE_TIME @"/Applications/FaceTime.app/Contents/MacOS/FaceTime" +#define FACE_TIME @"/System/Applications/FaceTime.app/Contents/MacOS/FaceTime" -//app support directory -#define APP_SUPPORT_DIRECTORY @"Library/Application Support/Objective-See/OverSight" +//rules window +#define WINDOW_RULES 0 -//whitelist -#define FILE_WHITELIST @"whitelist.plist" +//preferences window +#define WINDOW_PREFERENCES 1 + +//key for stdout output +#define STDOUT @"stdOutput" + +//key for stderr output +#define STDERR @"stdError" + +//key for exit code +#define EXIT_CODE @"exitCode" //event keys #define EVENT_DEVICE @"device" @@ -145,43 +224,9 @@ #define EVENT_PROCESS_ID @"processID" #define EVENT_ALERT_TYPE @"alertType" #define EVENT_ALERT_CLOSED @"eventClose" -#define EVENT_PROCESS_NAME @"processName" #define EVENT_PROCESS_PATH @"processPath" -//unknown process -#define PROCESS_UNKNOWN @"" - -//went inactive -#define ALERT_INACTIVE @0x0 - -//went active -#define ALERT_ACTIVATE @0x1 - -//source audio -#define SOURCE_AUDIO @0x1 - -//source video -#define SOURCE_VIDEO @0x2 - -//always allow button -#define BUTTON_ALWAYS_ALLOW 100 - -//no/close button -#define BUTTON_NO 101 - -//id (tag) for detailed text in rules table -#define TABLE_ROW_SUB_TEXT_TAG 101 - -//support us button tag -#define BUTTON_SUPPORT_US 100 - -//more info button tag -#define BUTTON_MORE_INFO 101 - -//log file name -#define LOG_FILE_NAME @"OverSight.log" - -//log to file flag -#define LOG_TO_FILE 0x10 +//av devices +typedef enum {Device_Camera, Device_Microphone} AVDevice; #endif diff --git a/Shared/Exception.h b/Shared/Exception.h deleted file mode 100755 index 53969b6..0000000 --- a/Shared/Exception.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Exception.h -// OverSight -// -// Created by Patrick Wardle on 7/7/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -//install exception/signal handlers -void installExceptionHandlers(void); - -//exception handler for Obj-C exceptions -void exceptionHandler(NSException *exception); - -//signal handler for *nix style exceptions -void signalHandler(int signal, siginfo_t *info, void *context); - -//display error window -void displayErrorWindow(NSDictionary* errorInfo); diff --git a/Shared/Exception.m b/Shared/Exception.m deleted file mode 100755 index b263a2b..0000000 --- a/Shared/Exception.m +++ /dev/null @@ -1,186 +0,0 @@ -// -// Exception.m -// OverSight -// -// Created by Patrick Wardle on 7/7/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "Exception.h" -#import "Utilities.h" - -#ifdef IS_INSTALLER_APP -#import "AppDelegate.h" -#endif - - -//global -// ->only report an fatal exception once -BOOL wasReported = NO; - -//install exception/signal handlers -void installExceptionHandlers() -{ - //sigaction struct - struct sigaction sa = {0}; - - //init signal struct - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - sa.sa_sigaction = signalHandler; - - //objective-C exception handler - NSSetUncaughtExceptionHandler(&exceptionHandler); - - //install signal handlers - sigaction(SIGILL, &sa, NULL); - sigaction(SIGSEGV, &sa, NULL); - sigaction(SIGBUS, &sa, NULL); - sigaction(SIGABRT, &sa, NULL); - sigaction(SIGTRAP, &sa, NULL); - sigaction(SIGFPE, &sa, NULL); - - return; -} - -//exception handler -// will be invoked for Obj-C exceptions -void exceptionHandler(NSException *exception) -{ - //error msg - NSString* errorMessage = nil; - - //ignore if exception was already reported - if(YES == wasReported) - { - //bail - return; - } - - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"OBJECTIVE-SEE ERROR: OS version: %@ /App version: %@", [[NSProcessInfo processInfo] operatingSystemVersionString], getAppVersion()]); - - //create error msg - errorMessage = [NSString stringWithFormat:@"unhandled obj-c exception caught [name: %@ / reason: %@]", [exception name], [exception reason]]; - - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"OBJECTIVE-SEE ERROR: %@", errorMessage]); - - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"OBJECTIVE-SEE ERROR: %@", [[NSThread callStackSymbols] description]]); - - //set flag - wasReported = YES; - - //start installer-specific code - #ifdef IS_INSTALLER_APP - - //error info dictionary - NSMutableDictionary* errorInfo = nil; - - //alloc - errorInfo = [NSMutableDictionary dictionary]; - - //add main error msg - errorInfo[KEY_ERROR_MSG] = @"ERROR: unrecoverable fault"; - - //add sub msg - errorInfo[KEY_ERROR_SUB_MSG] = [exception name]; - - //set error URL - errorInfo[KEY_ERROR_URL] = FATAL_ERROR_URL; - - //fatal error - // ->agent should exit - errorInfo[KEY_ERROR_SHOULD_EXIT] = [NSNumber numberWithBool:YES]; - - //display error msg - [((AppDelegate*)[[NSApplication sharedApplication] delegate]) displayErrorWindow:errorInfo]; - - //need to sleep, otherwise returning from this function will cause OS to kill agent - // ->instead, we want error popup to be displayed (which will exit agent when closed) - if(YES != [NSThread isMainThread]) - { - //nap - while(YES) - { - //nap - [NSThread sleepForTimeInterval:1.0f]; - } - } - - //end app-specific code - #endif - - return; -} - -//handler for signals -// will be invoked for BSD/*nix signals -void signalHandler(int signal, siginfo_t *info, void *context) -{ - //error msg - NSString* errorMessage = nil; - - //context - ucontext_t *uContext = NULL; - - //ignore if exception was already reported - if(YES == wasReported) - { - //bail - return; - } - - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"OBJECTIVE-SEE ERROR: OS version: %@ /App version: %@", [[NSProcessInfo processInfo] operatingSystemVersionString], getAppVersion()]); - - //typecast context - uContext = (ucontext_t *)context; - - //create error msg - errorMessage = [NSString stringWithFormat:@"unhandled exception caught, si_signo: %d /si_code: %s /si_addr: %p /rip: %p", - info->si_signo, (info->si_code == SEGV_MAPERR) ? "SEGV_MAPERR" : "SEGV_ACCERR", info->si_addr, (unsigned long*)uContext->uc_mcontext->__ss.__rip]; - - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"OBJECTIVE-SEE ERROR: %@", errorMessage]); - - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"OBJECTIVE-SEE ERROR: %@", [[NSThread callStackSymbols] description]]); - - //set flag - wasReported = YES; - - //start installer-specific code - #ifdef IS_INSTALLER_APP - - //error info dictionary - NSMutableDictionary* errorInfo = nil; - - //alloc - errorInfo = [NSMutableDictionary dictionary]; - - //add main error msg - errorInfo[KEY_ERROR_MSG] = @"ERROR: unrecoverable fault"; - - //add sub msg - errorInfo[KEY_ERROR_SUB_MSG] = [NSString stringWithFormat:@"si_signo: %d / rip: %p", info->si_signo, (unsigned long*)uContext->uc_mcontext->__ss.__rip]; - - //set error URL - errorInfo[KEY_ERROR_URL] = FATAL_ERROR_URL; - - //fatal error - // ->agent should exit - errorInfo[KEY_ERROR_SHOULD_EXIT] = [NSNumber numberWithBool:YES]; - - //display error msg - [((AppDelegate*)[[NSApplication sharedApplication] delegate]) displayErrorWindow:errorInfo]; - - //end app-specific code - #endif - - return; -} - diff --git a/Shared/Images/Images.xcassets/Contents.json b/Shared/Images/Images.xcassets/Contents.json deleted file mode 100644 index da4a164..0000000 --- a/Shared/Images/Images.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Shared/InfoWindowController.m b/Shared/InfoWindowController.m deleted file mode 100644 index 20f8e6e..0000000 --- a/Shared/InfoWindowController.m +++ /dev/null @@ -1,224 +0,0 @@ -// -// PrefsWindowController.m -// OverSight -// -// Created by Patrick Wardle on 2/6/15. -// Copyright (c) 2015 Objective-See, LLC. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" -#import "AppDelegate.h" -#import "InfoWindowController.h" - - -@implementation InfoWindowController - -@synthesize infoLabel; -@synthesize overlayView; -@synthesize firstButton; -@synthesize actionButton; -@synthesize infoLabelString; -@synthesize actionButtonTitle; -@synthesize progressIndicator; - - -//automatically called when nib is loaded -// ->center window --(void)awakeFromNib -{ - //center - [self.window center]; - - return; -} - -//automatically invoked when window is loaded -// ->set to white --(void)windowDidLoad -{ - //super - [super windowDidLoad]; - - //make white - [self.window setBackgroundColor: NSColor.whiteColor]; - - //set main label - [self.infoLabel setStringValue:self.infoLabelString]; - - //set button text - self.actionButton.title = self.actionButtonTitle; - - //hide first button when action is 'update' - // ->don't need update check button ;) - if(YES == [self.actionButton.title isEqualToString:@"update"]) - { - //hide - self.firstButton.hidden = YES; - } - - //make it key window - [self.window makeKeyAndOrderFront:self]; - - //make window front - [NSApp activateIgnoringOtherApps:YES]; - - return; -} - -//automatically invoked when window is closing -// ->make ourselves unmodal --(void)windowWillClose:(NSNotification *)notification -{ - //make un-modal - [[NSApplication sharedApplication] stopModal]; - - return; -} - -//save the main label's & button title's text -// ->invoked before window is loaded (and thus buttons, etc are nil) --(void)configure:(NSString*)label buttonTitle:(NSString*)buttonTitle -{ - //save label's string - self.infoLabelString = label; - - //save button's title - self.actionButtonTitle = buttonTitle; - - return; -} - -//invoked when user clicks 'check for updates' button -// ->invoke method on app delegate that checks/displays result --(IBAction)updateBtnHandler:(id)sender -{ - //version string - NSMutableString* versionString = nil; - - //version flag - __block NSInteger versionFlag = -1; - - //alloc string - versionString = [NSMutableString string]; - - //pre-req - [self.overlayView setWantsLayer:YES]; - - //set overlay's view color to black - self.overlayView.layer.backgroundColor = [NSColor whiteColor].CGColor; - - //make it semi-transparent - self.overlayView.alphaValue = 0.75; - - //show it - self.overlayView.hidden = NO; - - //disable 'update' check button - ((NSButton*)sender).enabled = NO; - - //disable close button - self.actionButton.enabled = NO; - - //remove detailed textz - self.infoLabel.stringValue = @""; - - //show spinner - self.progressIndicator.hidden = NO; - - //animate it - [self.progressIndicator startAnimation:nil]; - - //delay so UI shows spinner, etc - // ->then process version logic - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ - { - //get version flag - versionFlag = isNewVersion(versionString); - - //run on main thread for UI updates, etc - dispatch_sync(dispatch_get_main_queue(), ^{ - - //hide spinner - self.progressIndicator.hidden = YES; - - //hide overlay - self.overlayView.hidden = YES; - - //take action based on version - // ->show error, new version msg, etc - switch(versionFlag) - { - //error - // ->show err msg - case -1: - - //red for error - self.infoLabel.textColor = [NSColor redColor]; - - //set label - self.infoLabel.stringValue = @"error, update check failed :("; - - //set button title - self.actionButton.title = @"close"; - - break; - - //new version - case YES: - - //set label - self.infoLabel.stringValue = [NSString stringWithFormat:@"a new version (%@) is available!", versionString]; - - //set button title - self.actionButton.title = @"update"; - - break; - - //no new version - case NO: - - //set label - // ->versions should be the same, but we'll use getAppVersion() (since beta's might be newer than what's live) - self.infoLabel.stringValue = [NSString stringWithFormat:@"your version, (%@), is current", getAppVersion()]; - - //set button title - self.actionButton.title = @"close"; - - break; - - default: - - break; - } - - //always enable action button - self.actionButton.enabled = YES; - - }); //dispatch on main thread - - }); //dispatch for delay - - return; -} - -//invoked when user clicks button -// ->trigger action such as opening product website, updating, etc --(IBAction)buttonHandler:(id)sender -{ - //handle 'update' / 'more info', etc - // ->open BB's webpage - if(YES != [((NSButton*)sender).title isEqualToString:@"close"]) - { - //open URL - // ->invokes user's default browser - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:PRODUCT_URL]]; - } - - //always close window - [[self window] close]; - - return; -} -@end diff --git a/Shared/Libraries/libFileMonitor.a b/Shared/Libraries/libFileMonitor.a new file mode 100644 index 0000000..ab17536 Binary files /dev/null and b/Shared/Libraries/libFileMonitor.a differ diff --git a/Shared/Logging.h b/Shared/Logging.h deleted file mode 100644 index e6f2dc9..0000000 --- a/Shared/Logging.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Logging.h -// OverSight -// -// Created by Patrick Wardle on 7/7/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// -#import -#import - -//log a msg to syslog -// ->also disk, if error -void logMsg(int level, NSString* msg); - -//prep/open log file -BOOL initLogging(void); - -//get path to log file -NSString* logFilePath(void); - -//de-init logging -void deinitLogging(void); - -//log to file -void log2File(NSString* msg); diff --git a/Shared/Logging.m b/Shared/Logging.m deleted file mode 100644 index bfa3d97..0000000 --- a/Shared/Logging.m +++ /dev/null @@ -1,166 +0,0 @@ -// -// Logging.m -// OverSight -// -// Created by Patrick Wardle on 7/7/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" - -//global log file handle -NSFileHandle* logFileHandle = nil; - -//log a msg -// ->default to syslog, and if an err msg, to disk -void logMsg(int level, NSString* msg) -{ - //flag for logging - BOOL shouldLog = NO; - - //log prefix - NSMutableString* logPrefix = nil; - - //first grab logging flag - shouldLog = (LOG_TO_FILE == (level & LOG_TO_FILE)); - - //then remove it - // ->make sure syslog is happy - level &= ~LOG_TO_FILE; - - //alloc/init - // ->always start w/ 'LULU' + pid - logPrefix = [NSMutableString stringWithFormat:@"OVERSIGHT(%d)", getpid()]; - - //if its error, add error to prefix - if(LOG_ERR == level) - { - //add - [logPrefix appendString:@" ERROR"]; - } - - //debug mode logic - #ifdef DEBUG - - //in debug mode promote debug msgs to LOG_NOTICE - // OSX/macOS only shows LOG_NOTICE and above - if(LOG_DEBUG == level) - { - //promote - level = LOG_NOTICE; - } - - #endif - - //log to syslog if a level was specified - // as code doesn't use LOG_EMERG (0), this check is ok - if(0 != level) - { - //log to syslog - syslog(level, "%s: %s", [logPrefix UTF8String], [msg UTF8String]); - } - - //when a message is to be logged to file - // ->log it, when logging is enabled - if(YES == shouldLog) - { - //but only when logging is enable - if(nil != logFileHandle) - { - //log - log2File(msg); - } - } - - return; -} - -//get path to log file -NSString* logFilePath() -{ - //path to log directory - NSString* logDirectory = nil; - - //path to log file - NSString* logFile = nil; - - //get log file directory - logDirectory = [[@"~" stringByAppendingPathComponent:APP_SUPPORT_DIRECTORY] stringByExpandingTildeInPath]; - - //build path - logFile = [logDirectory stringByAppendingPathComponent:LOG_FILE_NAME]; - - return logFile; -} - -//log to file -void log2File(NSString* msg) -{ - //append timestamp - // ->write msg out to disk - [logFileHandle writeData:[[NSString stringWithFormat:@"%@: %@\n", [NSDate date], msg] dataUsingEncoding:NSUTF8StringEncoding]]; - - return; -} - -//de-init logging -void deinitLogging() -{ - //close file handle - [logFileHandle closeFile]; - - //nil out - logFileHandle = nil; - - return; -} - -//prep/open log file -BOOL initLogging() -{ - //ret var - BOOL bRet = NO; - - //log file path - NSString* logPath = nil; - - //get path to log file - logPath = logFilePath(); - - //first time - // ->create log file - if(YES != [[NSFileManager defaultManager] fileExistsAtPath:logPath]) - { - //create - [[NSFileManager defaultManager] createFileAtPath:logPath contents:nil attributes:nil]; - } - - //get file handle - logFileHandle = [NSFileHandle fileHandleForWritingAtPath:logPath]; - if(nil == logFileHandle) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to get log file handle to %@", logPath]); - - //bail - goto bail; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"opened log file; %@", logPath]); - #endif - - //seek to end - [logFileHandle seekToEndOfFile]; - - //happy - bRet = YES; - -//bail -bail: - - return bRet; -} diff --git a/Shared/UserClientShared.h b/Shared/UserClientShared.h new file mode 100755 index 0000000..0e73978 --- /dev/null +++ b/Shared/UserClientShared.h @@ -0,0 +1,105 @@ +// +// file: UserClientShared.h +// project: lulu (shared) +// description: dispatch selectors and data structs shared between user and kernel mode +// +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. +// + +#ifndef userClientShared_h +#define userClientShared_h + +#include + +#if defined (KERNEL) +extern "C" { +#endif + +#include +#include +#include +#include + +#if defined (KERNEL) +} +#endif + +//user client method dispatch selectors. +enum dispatchSelectors { + + kTestUserClientEnable, + kTestUserClientDisable, + kTestUserClientAddRule, + kTestUserClientRemoveRule, + kTestUserClientMethodCount +}; + +//type +struct genericEvent_s +{ + //type + UInt32 type; +}; + +//network out event struct +struct networkOutEvent_s { + + //type + UInt32 type; + + //process pid + UInt32 pid; + + //socket type + int socketType; + + //local socket address + struct sockaddr_in6 localAddress; + + //remote socket address + struct sockaddr_in6 remoteAddress; +}; + +//dns response out event struct +struct dnsResponseEvent_s { + + //type + UInt32 type; + + //response + unsigned char response[512]; +}; + +//firewall event union +// holds various structs, but max size will be 'padding' +typedef union +{ + //generic event + struct genericEvent_s genericEvent; + + //network out event + struct networkOutEvent_s networkOutEvent; + + //dns response event + struct dnsResponseEvent_s dnsResponseEvent; + + //padding + unsigned char padding[sizeof(UInt32) + 512]; + +} firewallEvent; + +//dns header struct +// from: http://www.nersc.gov/~scottc/software/snort/dns_head.html +#pragma pack(push,1) +struct dnsHeader { + unsigned short id; + unsigned short flags; + unsigned short qdcount; + unsigned short ancount; + unsigned short nscount; + unsigned short arcount; +}; +#pragma pack(pop) + +#endif diff --git a/Shared/Utilities.h b/Shared/Utilities.h index 290c81e..b2a780f 100644 --- a/Shared/Utilities.h +++ b/Shared/Utilities.h @@ -1,90 +1,134 @@ // -// Utilities.h -// OverSight +// file: utilities.h +// project: OverSight (shared) +// description: various helper/utility functions (header) // -// Created by Patrick Wardle on 7/7/16. -// Copyright (c) 2016 Objective-See. All rights reserved. +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. // -#ifndef WYS_Utilities_h -#define WYS_Utilities_h +#ifndef Utilities_h +#define Utilities_h #import #import /* FUNCTIONS */ -//get OS version -NSDictionary* getOSVersion(void); +//give path to app +// get full path to its binary +NSString* getAppBinary(NSString* appPath); + +//given an app binary +// try get app's bundle +NSBundle* getAppBundle(NSString* binaryPath); + +//get path to (main) app +// login item is in app bundle, so parse up to get main app +NSString* getMainAppPath(void); //get app's version // ->extracted from Info.plist NSString* getAppVersion(void); +//get (true) parent +NSDictionary* getRealParent(pid_t pid); + + +//extract value from plist +// takes optional wait time... +id getValueFromPlist(NSString* plistFile, NSString* key, BOOL insensitive, float maxWait); + +//find 'top-level' app of binary +// useful to determine if binary (or other app) is embedded in a 'parent' app bundle +NSString* topLevelApp(NSString* binaryPath); + +//verify that an app bundle is +// a) signed +// b) signed with signing auth +OSStatus verifyApp(NSString* path, NSString* signingAuth); + +//get name of logged in user +NSString* getConsoleUser(void); + +//check if process is alive +BOOL isProcessAlive(pid_t processID); + //set dir's|file's group/owner BOOL setFileOwner(NSString* path, NSNumber* groupID, NSNumber* ownerID, BOOL recursive); //set permissions for file BOOL setFilePermissions(NSString* file, int permissions, BOOL recursive); -//exec a process and grab it's output -NSData* execTask(NSString* binaryPath, NSArray* arguments, BOOL shouldWait); - -//get OS's major or minor version -SInt32 getVersion(OSType selector); - //given a path to binary // parse it back up to find app's bundle NSBundle* findAppBundle(NSString* binaryPath); -//get app's version -// ->extracted from Info.plist -NSString* getAppVersion(void); - -//query interwebz to get latest version -NSString* getLatestVersion(void); - -//determine if there is a new version -// -1, YES or NO -NSInteger isNewVersion(NSMutableString* versionString); - //get process's path NSString* getProcessPath(pid_t pid); -//given a pid -// ->get the name of the process -NSString* getProcessName(pid_t pid); +//get process name +// either via app bundle, or path +NSString* getProcessName(NSString* path); -//given a process name -// ->get the (first) instance of that process -pid_t getProcessID(NSString* processName, uid_t userID); +//given a process path and user +// return array of all matching pids +NSMutableArray* getProcessIDs(NSString* processPath, int userID); + +//given a pid, get its parent (ppid) +pid_t getParentID(int pid); + +//figure out binary's name +// either via app bundle, or from path +NSString* getBinaryName(NSString* path); + +//enable/disable a menu +void toggleMenu(NSMenu* menu, BOOL shouldEnable); + +//toggle login item +// either add (install) or remove (uninstall) +BOOL toggleLoginItem(NSURL* loginItem, int toggleFlag); //get an icon for a process -// ->for apps, this will be app's icon, otherwise just a standard system one +// for apps, this will be app's icon, otherwise just a standard system one NSImage* getIconForProcess(NSString* path); //wait until a window is non nil -// ->then make it modal +// then make it modal void makeModal(NSWindowController* windowController); -//toggle login item -// ->either add (install) or remove (uninstall) -BOOL toggleLoginItem(NSURL* loginItem, int toggleFlag); - -//get logged in user -// name, uid, and gid -NSMutableDictionary* loggedinUser(void); - //find a process by name pid_t findProcess(NSString* processName); -//convert a textview to a clickable hyperlink -void makeTextViewHyperlink(NSTextField* textField, NSURL* url); +//hash a file (sha256) +NSMutableString* hashFile(NSString* filePath); -//get active application -pid_t frontmostApplication(void); +//exec a process with args +// if 'shouldWait' is set, wait and return stdout/in and termination status +NSMutableDictionary* execTask(NSString* binaryPath, NSArray* arguments, BOOL shouldWait, BOOL grabOutput); -//check if process is alive -BOOL isProcessAlive(pid_t processID); +//loads a framework +// note: assumes is in 'Framework' dir +NSBundle* loadFramework(NSString* name); + +//in dark mode? +BOOL isDarkMode(void); + +//check if a file is restricted (SIP) +BOOL isFileRestricted(NSString* file); + +//check if something is nil +// if so, return a default ('unknown') value +NSString* valueForStringItem(NSString* item); + +//determine if path is translocated +// thanks: http://lapcatsoftware.com/articles/detect-app-translocation.html +BOOL isTranslocated(NSString* path); + +//running on M1? +BOOL AppleSilicon(void); + +//show an alert +NSModalResponse showAlert(NSString* messageText, NSString* informativeText); #endif diff --git a/Shared/Utilities.m b/Shared/Utilities.m index 22e0ec1..66a0218 100644 --- a/Shared/Utilities.m +++ b/Shared/Utilities.m @@ -1,83 +1,445 @@ // -// Utilities.m -// OverSight +// file: utilities.m +// project: OverSight (shared) +// description: various helper/utility functions // -// Created by Patrick Wardle on 7/7/16. -// Copyright (c) 2016 Objective-See. All rights reserved. +// created by Patrick Wardle +// copyright (c) 2017 Objective-See. All rights reserved. // -#import "Consts.h" -#import "Logging.h" -#import "Utilities.h" +@import OSLog; +#import "consts.h" +#import "utilities.h" + +#import #import #import #import -#import #import +#import #import #import #import #import +/* GLOBALS */ -//get OS version -NSDictionary* getOSVersion() -{ - //os version info - NSMutableDictionary* osVersionInfo = nil; - - //major v - SInt32 majorVersion = 0; - - //minor v - SInt32 minorVersion = 0; - - //alloc dictionary - osVersionInfo = [NSMutableDictionary dictionary]; - - //get major version - if(STATUS_SUCCESS != Gestalt(gestaltSystemVersionMajor, &majorVersion)) - { - //reset - osVersionInfo = nil; - - //bail - goto bail; - } - - //get minor version - if(STATUS_SUCCESS != Gestalt(gestaltSystemVersionMinor, &minorVersion)) - { - //reset - osVersionInfo = nil; - - //bail - goto bail; - } - - //set major version - osVersionInfo[@"majorVersion"] = [NSNumber numberWithInteger:majorVersion]; - - //set minor version - osVersionInfo[@"minorVersion"] = [NSNumber numberWithInteger:minorVersion]; - -//bail -bail: - - return osVersionInfo; - -} - +//log handle +extern os_log_t logHandle; //get app's version -// ->extracted from Info.plist +// extracted from Info.plist NSString* getAppVersion() { //read and return 'CFBundleVersion' from bundle return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; } +//given an app binary +// try get app's bundle +NSBundle* getAppBundle(NSString* binaryPath) +{ + //bundle + NSBundle* appBundle = nil; + + //app path + NSString* appPath = nil; + + //build app path + // assuming path is /Contents/MacOS/ + appPath = [[[binaryPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent]; + if(YES != [appPath hasSuffix:@".app"]) + { + //bail + goto bail; + } + + //try to load app's bundle + appBundle = [NSBundle bundleWithPath:appPath]; + if(nil == appBundle) + { + //bail + goto bail; + } + + //sanity check + // binary paths match? + if(YES != [appBundle.executablePath isEqualToString:binaryPath]) + { + //unset + appBundle = nil; + + //bail + goto bail; + } + +bail: + + return appBundle; +} + +//figure out binary's name +// either via app bundle, or from path +NSString* getBinaryName(NSString* path) +{ + //name + NSString* name = nil; + + //bundle + NSBundle* bundle = nil; + + //try get bundle + // then extract name from 'CFBundleName' + bundle = getAppBundle(path); + if(nil != bundle) + { + //extract name + name = [bundle infoDictionary][@"CFBundleName"]; + } + + //no app bundle || no 'CFBundleName'? + // just use last component from the path + if(nil == name) + { + //set name + name = [path lastPathComponent]; + } + + return name; +} + +//get path to (main) app of a login item +// login item is in app bundle, so parse up to get main app +NSString* getMainAppPath() +{ + //path components + NSArray *pathComponents = nil; + + //path to config (main) app + NSString* mainApp = nil; + + //get path components + // then build full path to main app + pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents]; + if(pathComponents.count > 4) + { + //init path to full (main) app + mainApp = [NSString pathWithComponents:[pathComponents subarrayWithRange:NSMakeRange(0, pathComponents.count - 4)]]; + } + + //when (still) nil + // use default path + if(nil == mainApp) + { + //default + mainApp = [@"/Applications" stringByAppendingPathComponent:APP_NAME]; + } + + return mainApp; +} + +//give path to app +// get full path to its binary +NSString* getAppBinary(NSString* appPath) +{ + //binary path + NSString* binaryPath = nil; + + //app bundle + NSBundle* appBundle = nil; + + //load app bundle + appBundle = [NSBundle bundleWithPath:appPath]; + if(nil == appBundle) + { + //err msg + os_log_error(logHandle, "ERROR: failed to load app bundle for %@", appPath); + + //bail + goto bail; + } + + //extract executable + binaryPath = appBundle.executablePath; + +bail: + + return binaryPath; +} + + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +//get (true) parent +NSDictionary* getRealParent(pid_t pid) +{ + //process info + NSDictionary* processInfo = nil; + + //process serial number + ProcessSerialNumber psn = {0, kNoProcess}; + + //(parent) process serial number + ProcessSerialNumber ppsn = {0, kNoProcess}; + + //get process serial number from pid + if(noErr != GetProcessForPID(pid, &psn)) + { + //err + goto bail; + } + + //get process (carbon) info + processInfo = CFBridgingRelease(ProcessInformationCopyDictionary(&psn, (UInt32)kProcessDictionaryIncludeAllInformationMask)); + if(nil == processInfo) + { + //err + goto bail; + } + + //extract/convert parent ppsn + ppsn.lowLongOfPSN = [processInfo[@"ParentPSN"] longLongValue] & 0x00000000FFFFFFFFLL; + ppsn.highLongOfPSN = ([processInfo[@"ParentPSN"] longLongValue] >> 32) & 0x00000000FFFFFFFFLL; + + //get parent process (carbon) info + processInfo = CFBridgingRelease(ProcessInformationCopyDictionary(&ppsn, (UInt32)kProcessDictionaryIncludeAllInformationMask)); + if(nil == processInfo) + { + //err + goto bail; + } + +bail: + + return processInfo; +} + +#pragma GCC diagnostic pop + +/* + +//build an array of processes ancestry +// uses `GetProcessForPID` to try get (real) parent +NSMutableArray* generateProcessHierarchy(pid_t pid, NSString* name) +{ + //process hierarchy + NSMutableArray* processHierarchy = nil; + + //current process id + pid_t currentPID = -1; + + //parent pid + pid_t parentPID = -1; + + //process name + NSString* parentName = nil; + + //alloc + processHierarchy = [NSMutableArray array]; + + //parent + NSDictionary* parent = nil; + + //add current process (leaf) + // parent(s) will then be added at front... + [processHierarchy addObject:[@{@"pid":[NSNumber numberWithInt:pid], @"name":valueForStringItem(name)} mutableCopy]]; + + //init current to self + currentPID = pid; + + //scan back + while(YES) + { + //get (real) parent + parent = getRealParent(currentPID); + if(nil == parent) + { + break; + } + + //get parent pid + parentPID = [parent[@"pid"] intValue]; + + //end of heirarchy? + if( (0 == parentPID) || + (-1 == parentPID) || + (currentPID == parentPID) ) + { + //bail + break; + } + + //get name + // first from bundle, then from executable + name = parent[@"CFBundleName"]; + if(0 == name.length) + { + //via executable + name = [parent[@"CFBundleExecutable"] lastPathComponent]; + } + + //add parent + // always at front + [processHierarchy insertObject:[@{@"pid":[NSNumber numberWithInt:parentPID], @"name":valueForStringItem(parentName)} mutableCopy] atIndex:0]; + + //update + currentPID = parentPID; + } + + return processHierarchy; +} + +*/ + +//check if something is nil +// if so, return a default ('unknown') value +NSString* valueForStringItem(NSString* item) +{ + return (nil != item) ? item : @"unknown"; +} + +//find 'top-level' app of binary +// useful to determine if binary (or other app) is embedded in a 'parent' app bundle +NSString* topLevelApp(NSString* binaryPath) +{ + //app path + NSString* appPath = nil; + + //offset of (first) '.app' + NSRange offset; + + //find first instance of '.app' in path + offset = [binaryPath rangeOfString:@".app/" options:NSCaseInsensitiveSearch]; + if(NSNotFound == offset.location) + { + //bail + goto bail; + } + + //extact app path + // from start, to & including '.app' + appPath = [binaryPath substringWithRange:NSMakeRange(0, offset.location+4)]; + +bail: + + return appPath; +} + +//verify that an app bundle is valid +// signed & with (our) signing auth / identifier +OSStatus verifyApp(NSString* path, NSString* signingAuth) +{ + //status + OSStatus status = !noErr; + + //signing req string + NSString *requirement = nil; + + //code + SecStaticCodeRef staticCode = NULL; + + //signing reqs + SecRequirementRef requirementRef = NULL; + + //init signing req string + requirement = [NSString stringWithFormat:@"anchor apple generic and identifier \"%@\" and certificate leaf [subject.CN] = \"%@\" and info [CFBundleShortVersionString] >= \"1.0.0\"", INSTALLER_ID, signingAuth]; + + //create static code + status = SecStaticCodeCreateWithPath((__bridge CFURLRef)([NSURL fileURLWithPath:path]), kSecCSDefaultFlags, &staticCode); + if(noErr != status) + { + //err msg + os_log_error(logHandle, "ERROR: SecStaticCodeCreateWithPath failed w/ %d", status); + + //bail + goto bail; + } + + //create req string + status = SecRequirementCreateWithString((__bridge CFStringRef _Nonnull)(requirement), kSecCSDefaultFlags, &requirementRef); + if( (noErr != status) || + (requirementRef == NULL) ) + { + //err msg + os_log_error(logHandle, "ERROR: SecRequirementCreateWithString failed w/ %d", status); + + //bail + goto bail; + } + + //check if file is signed w/ apple dev id by checking if it conforms to req string + status = SecStaticCodeCheckValidity(staticCode, kSecCSDefaultFlags, requirementRef); + if(noErr != status) + { + //err msg + os_log_error(logHandle, "ERROR: SecStaticCodeCheckValidity failed w/ %d", status); + + //bail + goto bail; + } + + //happy + status = noErr; + +bail: + + //free req reference + if(NULL != requirementRef) + { + //free + CFRelease(requirementRef); + requirementRef = NULL; + } + + //free static code + if(NULL != staticCode) + { + //free + CFRelease(staticCode); + staticCode = NULL; + } + + return status; +} + +//get name of logged in user +NSString* getConsoleUser() +{ + //copy/return user + return CFBridgingRelease(SCDynamicStoreCopyConsoleUser(NULL, NULL, NULL)); +} + +//get process name +// either via app bundle, or path +NSString* getProcessName(NSString* path) +{ + //process name + NSString* processName = nil; + + //app bundle + NSBundle* appBundle = nil; + + //try find an app bundle + appBundle = findAppBundle(path); + if(nil != appBundle) + { + //grab name from app's bundle + processName = [appBundle infoDictionary][@"CFBundleName"]; + } + + //still nil? + // just grab from path + if(nil == processName) + { + //from path + processName = [path lastPathComponent]; + } + + return processName; +} + //given a path to binary // parse it back up to find app's bundle NSBundle* findAppBundle(NSString* binaryPath) @@ -89,7 +451,7 @@ NSBundle* findAppBundle(NSString* binaryPath) NSString* appPath = nil; //first just try full path - appPath = binaryPath; + appPath = [[binaryPath stringByStandardizingPath] stringByResolvingSymlinksInPath]; //try to find the app's bundle/info dictionary do @@ -146,16 +508,14 @@ BOOL setFileOwner(NSString* path, NSNumber* groupID, NSNumber* ownerID, BOOL rec if(YES != [[NSFileManager defaultManager] setAttributes:fileOwner ofItemAtPath:path error:NULL]) { //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set ownership for %@ (%@)", path, fileOwner]); + os_log_error(logHandle, "ERROR: failed to set ownership for %@ (%@)", path, fileOwner); //bail goto bail; } //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"set ownership for %@ (%@)", path, fileOwner]); - #endif + os_log_debug(logHandle, "set ownership for %@ (%@)", path, fileOwner); //do it recursively if(YES == recursive) @@ -179,7 +539,7 @@ BOOL setFileOwner(NSString* path, NSNumber* groupID, NSNumber* ownerID, BOOL rec if(YES != [[NSFileManager defaultManager] setAttributes:fileOwner ofItemAtPath:fullPath error:NULL]) { //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set ownership for %@ (%@)", fullPath, fileOwner]); + os_log_error(logHandle, "ERROR: failed to set ownership for %@ (%@)", fullPath, fileOwner); //bail goto bail; @@ -224,8 +584,7 @@ BOOL setFilePermissions(NSString* file, int permissions, BOOL recursive) root = [NSURL fileURLWithPath:file]; //init enumerator - enumerator = [[NSFileManager defaultManager] enumeratorAtURL:root includingPropertiesForKeys:[NSArray arrayWithObject:NSURLIsDirectoryKey] options:0 - errorHandler:^(NSURL *url, NSError *error) { return YES; }]; + enumerator = [[NSFileManager defaultManager] enumeratorAtURL:root includingPropertiesForKeys:[NSArray arrayWithObject:NSURLIsDirectoryKey] options:0 errorHandler:nil]; //set file permissions on each for(NSURL* currentFile in enumerator) @@ -234,7 +593,7 @@ BOOL setFilePermissions(NSString* file, int permissions, BOOL recursive) if(YES != [[NSFileManager defaultManager] setAttributes:filePermissions ofItemAtPath:currentFile.path error:&error]) { //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set permissions for %@ (%@), %@", currentFile.path, filePermissions, error]); + os_log_error(logHandle, "ERROR: failed to set permissions for %@ (%@), %@", currentFile.path, filePermissions, error); //bail goto bail; @@ -243,11 +602,11 @@ BOOL setFilePermissions(NSString* file, int permissions, BOOL recursive) } //always set permissions on passed in file (or top-level directory) - // ->note: recursive enumerator skips root directory, so execute this always + // note: recursive enumerator skips root directory, so execute this always if(YES != [[NSFileManager defaultManager] setAttributes:filePermissions ofItemAtPath:file error:NULL]) { //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set permissions for %@ (%@)", file, filePermissions]); + os_log_error(logHandle, "ERROR: failed to set permissions for %@ (%@)", file, filePermissions); //bail goto bail; @@ -256,147 +615,16 @@ BOOL setFilePermissions(NSString* file, int permissions, BOOL recursive) //happy bSetPermissions = YES; -//bail bail: return bSetPermissions; } -//exec a process and grab it's output -NSData* execTask(NSString* binaryPath, NSArray* arguments, BOOL shouldWait) -{ - //task - NSTask *task = nil; - - //output pipe - NSPipe *outPipe = nil; - - //output - NSData *output = nil; - - //dispatch group - dispatch_group_t dispatchGroup = 0; - - //init task - task = [[NSTask alloc] init]; - - //init pipe - outPipe = [NSPipe pipe]; - - //create dispatch group - dispatchGroup = dispatch_group_create(); - - //set task's path - task.launchPath = binaryPath; - - //set task's args - if(nil != arguments) - { - //add - task.arguments = arguments; - } - - //set task's output to pipe - // ->but only if we're waiting for exit - if(YES == shouldWait) - { - //redirect - task.standardOutput = outPipe; - } - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"@exec'ing %@ (args: %@)", binaryPath, arguments]); - #endif - - //enter dispatch - dispatch_group_enter(dispatchGroup); - - //set task's termination to leave dispatch group - task.terminationHandler = ^(NSTask *task){ - - //leave - dispatch_group_leave(dispatchGroup); - }; - - //wrap task launch - @try - { - //launch - [task launch]; - } - @catch(NSException* exception) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"task failed with %@", exception]); - - //bail - goto bail; - } - - //when waiting - // ->grab data - if(YES == shouldWait) - { - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"invoking 'readDataToEndOfFile' to get all data"); - #endif - - //read until file is closed - output = [outPipe.fileHandleForReading readDataToEndOfFile]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"now waiting for task to exit"); - #endif - - //wait till exit - dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER); - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, @"task exited"); - #endif - - }//wait - -//bail -bail: - - - return output; -} - -//get OS's major or minor version -SInt32 getVersion(OSType selector) -{ - //version - // ->major or minor - SInt32 version = -1; - - //get version info - if(noErr != Gestalt(selector, &version)) - { - //reset version - version = -1; - - //err - goto bail; - } - -//bail -bail: - - return version; -} - //get process's path NSString* getProcessPath(pid_t pid) { //task path - NSString* taskPath = nil; + NSString* processPath = nil; //buffer for process path char pathBuffer[PROC_PIDPATHINFO_MAXSIZE] = {0}; @@ -408,7 +636,7 @@ NSString* getProcessPath(pid_t pid) int mib[3] = {0}; //system's size for max args - int systemMaxArgs = 0; + unsigned long systemMaxArgs = 0; //process's args char* taskArgs = NULL; @@ -420,17 +648,17 @@ NSString* getProcessPath(pid_t pid) size_t size = 0; //reset buffer - bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE); + memset(pathBuffer, 0x0, PROC_PIDPATHINFO_MAXSIZE); //first attempt to get path via 'proc_pidpath()' status = proc_pidpath(pid, pathBuffer, sizeof(pathBuffer)); if(0 != status) { //init task's name - taskPath = [NSString stringWithUTF8String:pathBuffer]; + processPath = [NSString stringWithUTF8String:pathBuffer]; } //otherwise - // ->try via task's args ('KERN_PROCARGS2') + // try via task's args ('KERN_PROCARGS2') else { //init mib @@ -473,7 +701,7 @@ NSString* getProcessPath(pid_t pid) } //sanity check - // ->ensure buffer is somewhat sane + // ensure buffer is somewhat sane if(size <= sizeof(int)) { //bail @@ -481,15 +709,13 @@ NSString* getProcessPath(pid_t pid) } //extract number of args - // ->at start of buffer memcpy(&numberOfArgs, taskArgs, sizeof(numberOfArgs)); //extract task's name - // ->follows # of args (int) and is NULL-terminated - taskPath = [NSString stringWithUTF8String:taskArgs + sizeof(int)]; + // follows # of args (int) and is NULL-terminated + processPath = [NSString stringWithUTF8String:taskArgs + sizeof(int)]; } -//bail bail: //free process args @@ -502,66 +728,19 @@ bail: taskArgs = NULL; } - return taskPath; + return processPath; } -//given a pid -// ->get the name of the process -NSString* getProcessName(pid_t pid) -{ - //task path - NSString* processName = nil; - - //process path - NSString* processPath = nil; - - //app's bundle - NSBundle* appBundle = nil; - - //get process path - processPath = getProcessPath(pid); - if( (nil == processPath) || - (0 == processPath.length) ) - { - //default to 'unknown' - processName = @""; - - //bail - goto bail; - } - - //try find an app bundle - appBundle = findAppBundle(processPath); - if(nil != appBundle) - { - //grab name from app's bundle - processName = [appBundle infoDictionary][@"CFBundleName"]; - } - - //still nil? - // ->just grab from path - if(nil == processName) - { - //from path - processName = [processPath lastPathComponent]; - } - -//bail -bail: - - return processName; -} - -//given a process name -// ->get the (first) instance of that process -pid_t getProcessID(NSString* processName, uid_t userID) +//given a process path and user +// return array of all matching pids +NSMutableArray* getProcessIDs(NSString* processPath, int userID) { //status int status = -1; - //process id - pid_t processID = -1; - + //process IDs + NSMutableArray* processIDs = nil; + //# of procs int numberOfProcesses = 0; @@ -569,7 +748,7 @@ pid_t getProcessID(NSString* processName, uid_t userID) pid_t* pids = NULL; //process info struct - struct kinfo_proc procInfo = {0}; + struct kinfo_proc procInfo; //size of struct size_t procInfoSize = sizeof(procInfo); @@ -577,14 +756,25 @@ pid_t getProcessID(NSString* processName, uid_t userID) //mib int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, -1}; + //clear buffer + memset(&procInfo, 0x0, procInfoSize); + //get # of procs - numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); + numberOfProcesses = proc_listallpids(NULL, 0); + if(-1 == numberOfProcesses) + { + //bail + goto bail; + } //alloc buffer for pids - pids = calloc(numberOfProcesses, sizeof(pid_t)); + pids = calloc((unsigned long)numberOfProcesses, sizeof(pid_t)); + + //alloc + processIDs = [NSMutableArray array]; //get list of pids - status = proc_listpids(PROC_ALL_PIDS, 0, pids, numberOfProcesses * sizeof(pid_t)); + status = proc_listallpids(pids, numberOfProcesses * (int)sizeof(pid_t)); if(status < 0) { //bail @@ -592,8 +782,8 @@ pid_t getProcessID(NSString* processName, uid_t userID) } //iterate over all pids - // ->get name for each - for(int i = 0; i < numberOfProcesses; ++i) + // ->get name for each process + for(int i = 0; i < (int)numberOfProcesses; i++) { //skip blank pids if(0 == pids[i]) @@ -602,39 +792,41 @@ pid_t getProcessID(NSString* processName, uid_t userID) continue; } - //skip if name doesn't match - if(YES != [processName isEqualToString:getProcessName(pids[i])]) + //skip if path doesn't match + if(YES != [processPath isEqualToString:getProcessPath(pids[i])]) { //next continue; } - //init mib - mib[0x3] = pids[i]; - - //make syscall to get proc info - if( (0 != sysctl(mib, 0x4, &procInfo, &procInfoSize, NULL, 0)) || - (0 == procInfoSize) ) + //need to also match on user? + // caller can pass in -1 to skip this check + if(-1 != userID) { - //skip - continue; - } + //init mib + mib[0x3] = pids[i]; + + //make syscall to get proc info for user + if( (0 != sysctl(mib, 0x4, &procInfo, &procInfoSize, NULL, 0)) || + (0 == procInfoSize) ) + { + //skip + continue; + } - //skip if user id doesn't match - if(userID != procInfo.kp_eproc.e_ucred.cr_uid) - { - //skip - continue; + //skip if user id doesn't match + if(userID != (int)procInfo.kp_eproc.e_ucred.cr_uid) + { + //skip + continue; + } } //got match - processID = pids[i]; - - //exit loop - break; + // add to list + [processIDs addObject:[NSNumber numberWithInt:pids[i]]]; } - -//bail + bail: //free buffer @@ -647,12 +839,28 @@ bail: pids = NULL; } - return processID; + return processIDs; } +//enable/disable a menu +void toggleMenu(NSMenu* menu, BOOL shouldEnable) +{ + //disable autoenable + menu.autoenablesItems = NO; + + //iterate over + // set state of each item + for(NSMenuItem* item in menu.itemArray) + { + //set state + item.enabled = shouldEnable; + } + + return; +} //get an icon for a process -// ->for apps, this will be app's icon, otherwise just a standard system one +// for apps, this will be app's icon, otherwise just a standard system one NSImage* getIconForProcess(NSString* path) { //icon's file name @@ -668,39 +876,64 @@ NSImage* getIconForProcess(NSString* path) NSImage* icon = nil; //system's document icon - static NSData* documentIcon = nil; + static NSImage* documentIcon = nil; //bundle NSBundle* appBundle = nil; + //invalid path? + // grab a default icon and bail + if(YES != [[NSFileManager defaultManager] fileExistsAtPath:path]) + { + //set icon to system 'application' icon + icon = [[NSWorkspace sharedWorkspace] + iconForFileType: NSFileTypeForHFSTypeCode(kGenericApplicationIcon)]; + + //set size to 64 @2x + [icon setSize:NSMakeSize(128, 128)]; + + //bail + goto bail; + } + //first try grab bundle // ->then extact icon from this appBundle = findAppBundle(path); if(nil != appBundle) { - //get file - iconFile = appBundle.infoDictionary[@"CFBundleIconFile"]; - - //get path extension - iconExtension = [iconFile pathExtension]; - - //if its blank (i.e. not specified) - // ->go with 'icns' - if(YES == [iconExtension isEqualTo:@""]) + //extract icon + icon = [[NSWorkspace sharedWorkspace] iconForFile:appBundle.bundlePath]; + if(nil != icon) { - //set type - iconExtension = @"icns"; + //done! + goto bail; } - //set full path - iconPath = [appBundle pathForResource:[iconFile stringByDeletingPathExtension] ofType:iconExtension]; - - //load it - icon = [[NSImage alloc] initWithContentsOfFile:iconPath]; + //get file + iconFile = appBundle.infoDictionary[@"CFBundleIconFile"]; + if(nil != iconFile) + { + //get path extension + iconExtension = [iconFile pathExtension]; + + //if its blank (i.e. not specified) + // go with 'icns' + if(YES == [iconExtension isEqualTo:@""]) + { + //set type + iconExtension = @"icns"; + } + + //set full path + iconPath = [appBundle pathForResource:[iconFile stringByDeletingPathExtension] ofType:iconExtension]; + + //load it + icon = [[NSImage alloc] initWithContentsOfFile:iconPath]; + } } //process is not an app or couldn't get icon - // ->try to get it via shared workspace + // try to get it via shared workspace if( (nil == appBundle) || (nil == icon) ) { @@ -708,129 +941,39 @@ NSImage* getIconForProcess(NSString* path) icon = [[NSWorkspace sharedWorkspace] iconForFile:path]; //load system document icon - // ->static var, so only load once + // static var, so only load once if(nil == documentIcon) { //load - documentIcon = [[[NSWorkspace sharedWorkspace] iconForFileType: - NSFileTypeForHFSTypeCode(kGenericDocumentIcon)] TIFFRepresentation]; + documentIcon = [[NSWorkspace sharedWorkspace] iconForFileType: + NSFileTypeForHFSTypeCode(kGenericDocumentIcon)]; } //if 'iconForFile' method doesn't find and icon, it returns the system 'document' icon - // ->the system 'applicatoon' icon seems more applicable, so use that here... - if(YES == [[icon TIFFRepresentation] isEqual:documentIcon]) + // the system 'application' icon seems more applicable, so use that here... + if(YES == [icon isEqual:documentIcon]) { - //set icon to system 'applicaiton' icon + //set icon to system 'application' icon icon = [[NSWorkspace sharedWorkspace] - iconForFileType: NSFileTypeForHFSTypeCode(kGenericApplicationIcon)]; + iconForFileType: NSFileTypeForHFSTypeCode(kGenericApplicationIcon)]; } //'iconForFileType' returns small icons - // ->so set size to 128 + // so set size to 64 @2x [icon setSize:NSMakeSize(128, 128)]; } +bail: + return icon; } -//determine if there is a new version -// -1, YES or NO -NSInteger isNewVersion(NSMutableString* versionString) -{ - //flag - NSInteger newVersionExists = -1; - - //installed version - NSString* installedVersion = nil; - - //latest version - NSString* latestVersion = nil; - - //get installed version - installedVersion = getAppVersion(); - - //get latest version - // ->will query internet (obj-see website) - latestVersion = getLatestVersion(); - if(nil == latestVersion) - { - //set error msg - [versionString setString:@"failed to get latest version"]; - - //bail - goto bail; - } - - //save version - [versionString setString:latestVersion]; - - //set version flag - // ->YES/NO - newVersionExists = (NSOrderedAscending == [installedVersion compare:latestVersion options:NSNumericSearch]); - -//bail -bail: - - return newVersionExists; -} - -//query interwebz to get latest version -NSString* getLatestVersion() -{ - //product version(s) data - NSData* productsVersionData = nil; - - //version dictionary - NSDictionary* productsVersionDictionary = nil; - - //latest version - NSString* latestVersion = nil; - - //get version from remote URL - productsVersionData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:PRODUCT_VERSIONS_URL]]; - if(nil == productsVersionData) - { - //bail - goto bail; - } - - //convert JSON to dictionary - // ->wrap as may throw exception - @try - { - //convert - productsVersionDictionary = [NSJSONSerialization JSONObjectWithData:productsVersionData options:0 error:nil]; - if(nil == productsVersionDictionary) - { - //bail - goto bail; - } - } - @catch(NSException* exception) - { - //bail - goto bail; - } - - //extract latest version - latestVersion = [[productsVersionDictionary objectForKey:@"OverSight"] objectForKey:@"version"]; - - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"latest version: %@", latestVersion]); - #endif - -bail: - - return latestVersion; -} - //wait until a window is non nil -// ->then make it modal +// then make it modal void makeModal(NSWindowController* windowController) { //wait up to 1 second window to be non-nil - // ->then make modal + // then make modal for(int i=0; i<20; i++) { //can make it modal once we have a window @@ -856,13 +999,102 @@ void makeModal(NSWindowController* windowController) return; } +//find a process by name +pid_t findProcess(NSString* processName) +{ + //pid + pid_t processID = 0; + + //status + int status = -1; + + //# of procs + int numberOfProcesses = 0; + + //array of pids + pid_t* pids = NULL; + + //process path + NSString* processPath = nil; + + //get # of procs + numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); + if(-1 == numberOfProcesses) + { + //bail + goto bail; + } + + //alloc buffer for pids + pids = calloc((unsigned long)numberOfProcesses, sizeof(pid_t)); + + //get list of pids + status = proc_listpids(PROC_ALL_PIDS, 0, pids, numberOfProcesses * (int)sizeof(pid_t)); + if(status < 0) + { + //bail + goto bail; + } + + //iterate over all pids + // get name for each via helper function + for(int i = 0; i < numberOfProcesses; ++i) + { + //skip blank pids + if(0 == pids[i]) + { + //skip + continue; + } + + //get name + processPath = getProcessPath(pids[i]); + if( (nil == processPath) || + (0 == processPath.length) ) + { + //skip + continue; + } + + //match? + if(YES == [processPath isEqualToString:processName]) + { + //save + processID = pids[i]; + + //pau + break; + } + + }//all procs + +bail: + + //free buffer + if(NULL != pids) + { + //free + free(pids); + } + + return processID; +} + +//for login item enable/disable +// we use the launch services APIs, since replacements don't always work :( +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + //toggle login item -// ->either add (install) or remove (uninstall) +// either add (install) or remove (uninstall) BOOL toggleLoginItem(NSURL* loginItem, int toggleFlag) { //flag BOOL wasToggled = NO; - + + //flag + BOOL alreadyAdded = NO; + //login item ref LSSharedFileListRef loginItemsRef = NULL; @@ -879,9 +1111,46 @@ BOOL toggleLoginItem(NSURL* loginItem, int toggleFlag) if(ACTION_INSTALL_FLAG == toggleFlag) { //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"adding login item %@", loginItem]); - #endif + os_log_debug(logHandle, "adding login item %@", loginItem); + + //grab existing login items + loginItems = LSSharedFileListCopySnapshot(loginItemsRef, nil); + + //already added? + for(id item in (__bridge NSArray *)loginItems) + { + //grab current item + currentLoginItem = LSSharedFileListItemCopyResolvedURL((__bridge LSSharedFileListItemRef)item, 0, NULL); + if(NULL == currentLoginItem) continue; + + //is match? + if(YES == [(__bridge NSURL *)currentLoginItem isEqual:loginItem]) + { + //set flag + alreadyAdded = YES; + } + + //release + CFRelease(currentLoginItem); + currentLoginItem = NULL; + + //done? + if(YES == alreadyAdded) break; + } + + //sanity check + if(YES == alreadyAdded) + { + //dbg msg + os_log_debug(logHandle, "%@ already exists as a login item", loginItem); + + //happy + wasToggled = YES; + + //done + goto bail; + + } //add LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, (__bridge CFURLRef)(loginItem), NULL, NULL); @@ -890,9 +1159,7 @@ BOOL toggleLoginItem(NSURL* loginItem, int toggleFlag) if(NULL != itemRef) { //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"added %@/%@", loginItem, itemRef]); - #endif + os_log_debug(logHandle, "added %@/%@", loginItem, itemRef); //release CFRelease(itemRef); @@ -904,7 +1171,7 @@ BOOL toggleLoginItem(NSURL* loginItem, int toggleFlag) else { //err msg - logMsg(LOG_ERR, @"failed to add login item"); + os_log_error(logHandle, "ERROR: failed to add login item"); //bail goto bail; @@ -917,24 +1184,19 @@ BOOL toggleLoginItem(NSURL* loginItem, int toggleFlag) else { //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"removing login item %@", loginItem]); - #endif + os_log_debug(logHandle, "removing login item %@", loginItem); //grab existing login items loginItems = LSSharedFileListCopySnapshot(loginItemsRef, nil); //iterate over all login items - // ->look for self, then remove it/them - for (id item in (__bridge NSArray *)loginItems) + // look for self, then remove it + for(id item in (__bridge NSArray *)loginItems) { //get current login item - if( (noErr != LSSharedFileListItemResolve((__bridge LSSharedFileListItemRef)item, 0, (CFURLRef*)¤tLoginItem, NULL)) || - (NULL == currentLoginItem) ) - { - //skip - continue; - } + currentLoginItem = LSSharedFileListItemCopyResolvedURL((__bridge LSSharedFileListItemRef)item, 0, NULL); + if(NULL == currentLoginItem) continue; + //current login item match self? if(YES == [(__bridge NSURL *)currentLoginItem isEqual:loginItem]) @@ -943,34 +1205,32 @@ BOOL toggleLoginItem(NSURL* loginItem, int toggleFlag) if(noErr != LSSharedFileListItemRemove(loginItemsRef, (__bridge LSSharedFileListItemRef)item)) { //err msg - logMsg(LOG_ERR, @"failed to remove login item"); + os_log_error(logHandle, "ERROR: failed to remove login item"); //bail goto bail; } //dbg msg - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"removed loginItem: %@", loginItem]); + os_log_debug(logHandle, "removed login item: %@", loginItem); //happy wasToggled = YES; + + //all done + goto bail; } //release - if(NULL != currentLoginItem) - { - //release - CFRelease(currentLoginItem); - - //reset - currentLoginItem = NULL; - } + CFRelease(currentLoginItem); + + //reset + currentLoginItem = NULL; }//all login items }//remove/uninstall -//bail bail: //release login items @@ -993,184 +1253,18 @@ bail: loginItemsRef = NULL; } + //release url + if(NULL != currentLoginItem) + { + //release + CFRelease(currentLoginItem); + currentLoginItem = NULL; + } + return wasToggled; } -//get logged in user -// name, uid, and gid -NSMutableDictionary* loggedinUser() -{ - //user info - NSMutableDictionary* userInfo = nil; - - //store - SCDynamicStoreRef store = nil; - - //user - NSString* user = nil; - - //uid - uid_t uid = 0; - - //gid - gid_t gid = 0; - - //allco dictionary - userInfo = [NSMutableDictionary dictionary]; - - //create store - store = SCDynamicStoreCreate(NULL, CFSTR("GetConsoleUser"), NULL, NULL); - if(NULL == store) - { - //bail - goto bail; - } - - //get user and uid/gid - user = CFBridgingRelease(SCDynamicStoreCopyConsoleUser(store, &uid, &gid)); - - //add user - userInfo[@"user"] = user; - - //add uid - userInfo[@"uid"] = [NSNumber numberWithUnsignedInt:uid]; - - //add uid - userInfo[@"gid"] = [NSNumber numberWithUnsignedInt:gid]; - -//bail -bail: - - //release store - if(NULL != store) - { - //release - CFRelease(store); - } - - return userInfo; -} - -//find a process by name -pid_t findProcess(NSString* processName) -{ - //pid - pid_t processID = 0; - - //status - int status = -1; - - //# of procs - int numberOfProcesses = 0; - - //array of pids - pid_t* pids = NULL; - - //process path - NSString* processPath = nil; - - //get # of procs - numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); - - //alloc buffer for pids - pids = calloc(numberOfProcesses, sizeof(pid_t)); - - //get list of pids - status = proc_listpids(PROC_ALL_PIDS, 0, pids, numberOfProcesses * sizeof(pid_t)); - if(status < 0) - { - //bail - goto bail; - } - - //iterate over all pids - // ->get name for each via helper function - for(int i = 0; i < numberOfProcesses; ++i) - { - //skip blank pids - if(0 == pids[i]) - { - //skip - continue; - } - - //get name - processPath = getProcessPath(pids[i]); - if( (nil == processPath) || - (0 == processPath.length) ) - { - //skip - continue; - } - - //match? - if(YES == [processPath isEqualToString:processName]) - { - //save - processID = pids[i]; - - //pau - break; - } - - }//all procs - -//bail -bail: - - //free buffer - if(NULL != pids) - { - //free - free(pids); - } - - return processID; -} - -//convert a textview to a clickable hyperlink -void makeTextViewHyperlink(NSTextField* textField, NSURL* url) -{ - //hyperlink - NSMutableAttributedString *hyperlinkString = nil; - - //range - NSRange range = {0}; - - //init hyper link - hyperlinkString = [[NSMutableAttributedString alloc] initWithString:textField.stringValue]; - - //init range - range = NSMakeRange(0, [hyperlinkString length]); - - //start editing - [hyperlinkString beginEditing]; - - //add url - [hyperlinkString addAttribute:NSLinkAttributeName value:url range:range]; - - //make it blue - [hyperlinkString addAttribute:NSForegroundColorAttributeName value:[NSColor blueColor] range:NSMakeRange(0, [hyperlinkString length])]; - - //underline - [hyperlinkString addAttribute: - NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:NSSingleUnderlineStyle] range:NSMakeRange(0, [hyperlinkString length])]; - - //done editing - [hyperlinkString endEditing]; - - //set text - [textField setAttributedStringValue:hyperlinkString]; - - return; -} - -//get frontmost (active) app -pid_t frontmostApplication() -{ - //get/ret - return NSWorkspace.sharedWorkspace.frontmostApplication.processIdentifier; -} +#pragma clang diagnostic pop //check if process is alive BOOL isProcessAlive(pid_t processID) @@ -1182,21 +1276,375 @@ BOOL isProcessAlive(pid_t processID) int signalStatus = -1; //send kill with 0 to determine if alive - // -> see: http://stackoverflow.com/questions/9152979/check-if-process-exists-given-its-pid signalStatus = kill(processID, 0); //is alive? if( (0 == signalStatus) || - ( (0 != signalStatus) && (errno != ESRCH) ) ) + ((0 != signalStatus) && (errno != ESRCH)) ) { - //dbg msg - #ifdef DEBUG - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"agent (%d) is ALIVE", processID]); - #endif - //alive! bIsAlive = YES; } return bIsAlive; } + + +//hash a file +NSMutableString* hashFile(NSString* filePath) +{ + //file's contents + NSData* fileContents = nil; + + //hash digest + uint8_t digestSHA256[CC_SHA256_DIGEST_LENGTH] = {0}; + + //hash as string + NSMutableString* sha256 = nil; + + //index var + NSUInteger index = 0; + + //init + sha256 = [NSMutableString string]; + + //load file + if(nil == (fileContents = [NSData dataWithContentsOfFile:filePath])) + { + //bail + goto bail; + } + + //sha256 it + CC_SHA256(fileContents.bytes, (unsigned int)fileContents.length, digestSHA256); + + //convert to NSString + // iterate over each bytes in computed digest and format + for(index=0; index < CC_SHA256_DIGEST_LENGTH; index++) + { + //format/append + [sha256 appendFormat:@"%02lX", (unsigned long)digestSHA256[index]]; + } + +bail: + + return sha256; +} + +//exec a process with args +// if 'shouldWait' is set, wait and return stdout/in and termination status +NSMutableDictionary* execTask(NSString* binaryPath, NSArray* arguments, BOOL shouldWait, BOOL grabOutput) +{ + //task + NSTask* task = nil; + + //output pipe for stdout + NSPipe* stdOutPipe = nil; + + //output pipe for stderr + NSPipe* stdErrPipe = nil; + + //read handle for stdout + NSFileHandle* stdOutReadHandle = nil; + + //read handle for stderr + NSFileHandle* stdErrReadHandle = nil; + + //results dictionary + NSMutableDictionary* results = nil; + + //output for stdout + NSMutableData *stdOutData = nil; + + //output for stderr + NSMutableData *stdErrData = nil; + + //init dictionary for results + results = [NSMutableDictionary dictionary]; + + //init task + task = [[NSTask alloc] init]; + + //sanity check + // NSTask throws if path isn't found... + if(YES != [NSFileManager.defaultManager fileExistsAtPath:binaryPath]) + { + //bail + goto bail; + } + + //only setup pipes if wait flag is set + if(YES == grabOutput) + { + //init stdout pipe + stdOutPipe = [NSPipe pipe]; + + //init stderr pipe + stdErrPipe = [NSPipe pipe]; + + //init stdout read handle + stdOutReadHandle = [stdOutPipe fileHandleForReading]; + + //init stderr read handle + stdErrReadHandle = [stdErrPipe fileHandleForReading]; + + //init stdout output buffer + stdOutData = [NSMutableData data]; + + //init stderr output buffer + stdErrData = [NSMutableData data]; + + //set task's stdout + task.standardOutput = stdOutPipe; + + //set task's stderr + task.standardError = stdErrPipe; + } + + //set task's path + task.launchPath = binaryPath; + + //set task's args + if(nil != arguments) + { + //set + task.arguments = arguments; + } + + //dbg msg + os_log_debug(logHandle, "execing task %{public}@ (arguments: %{public}@)", task.launchPath, task.arguments); + + //wrap task launch + @try + { + //launch + [task launch]; + } + @catch(NSException *exception) + { + //err msg + os_log_debug(logHandle, "failed to launch task (%{public}@)", exception); + + //bail + goto bail; + } + + //no need to wait + // can just bail w/ no output + if( (YES != shouldWait) && + (YES != grabOutput) ) + { + //bail + goto bail; + } + + //wait + // ...but no output + else if( (YES == shouldWait) && + (YES != grabOutput) ) + { + //wait + [task waitUntilExit]; + + //add exit code + results[EXIT_CODE] = [NSNumber numberWithInteger:task.terminationStatus]; + + //bail + goto bail; + } + + //grab output? + // even if wait not set, still will wait! + else + { + //read in stdout/stderr + while(YES == [task isRunning]) + { + //accumulate stdout + [stdOutData appendData:[stdOutReadHandle readDataToEndOfFile]]; + + //accumulate stderr + [stdErrData appendData:[stdErrReadHandle readDataToEndOfFile]]; + } + + //grab any leftover stdout + [stdOutData appendData:[stdOutReadHandle readDataToEndOfFile]]; + + //grab any leftover stderr + [stdErrData appendData:[stdErrReadHandle readDataToEndOfFile]]; + + //add stdout + if(0 != stdOutData.length) + { + //add + results[STDOUT] = stdOutData; + } + + //add stderr + if(0 != stdErrData.length) + { + //add + results[STDERR] = stdErrData; + } + + //add exit code + results[EXIT_CODE] = [NSNumber numberWithInteger:task.terminationStatus]; + } + +bail: + + //dbg msg + os_log_debug(logHandle, "task completed with %@", results); + + return results; +} + + +//loads a framework +// note: assumes it is in 'Framework' dir +NSBundle* loadFramework(NSString* name) +{ + //handle + NSBundle* framework = nil; + + //framework path + NSString* path = nil; + + //init path + path = [NSString stringWithFormat:@"%@/../Frameworks/%@", [NSProcessInfo.processInfo.arguments[0] stringByDeletingLastPathComponent], name]; + + //standardize path + path = [path stringByStandardizingPath]; + + //init framework (bundle) + framework = [NSBundle bundleWithPath:path]; + if(NULL == framework) + { + //bail + goto bail; + } + + //load framework + if(YES != [framework loadAndReturnError:nil]) + { + //bail + goto bail; + } + +bail: + + return framework; +} + +//check if a file is restricted (SIP) +BOOL isFileRestricted(NSString* file) +{ + //flag + BOOL restricted = NO; + + //info + struct stat info = {0}; + + //clear + memset(&info, 0x0, sizeof(struct stat)); + + //get file info + if(0 == lstat(file.UTF8String, &info)) + { + //check flags + restricted = (BOOL)(info.st_flags & SF_RESTRICTED); + } + + return restricted; +} + +//in dark mode? +BOOL isDarkMode() +{ + return [[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"] isEqualToString:@"Dark"]; +} + +//running on M1? +BOOL AppleSilicon(void) +{ + //flag + BOOL isAppleSilicon = NO; + + //type + cpu_type_t type = -1; + + //size + size_t size = 0; + + //mib + int mib[CTL_MAXNAME] = {0}; + + //length + size_t length = CTL_MAXNAME; + + //get mib for 'proc_cputype' + if(noErr != sysctlnametomib("sysctl.proc_cputype", mib, &length)) + { + //bail + goto bail; + } + + //add pid + mib[length] = getpid(); + + //inc length + length++; + + //init size + size = sizeof(cpu_type_t); + + //get CPU type + if(noErr != sysctl(mib, (u_int)length, &type, &size, 0, 0)) + { + //bail + goto bail; + } + + isAppleSilicon = (CPU_TYPE_ARM64 == type); + +bail: + + return isAppleSilicon; +} + +//show an alert +NSModalResponse showAlert(NSString* messageText, NSString* informativeText) +{ + //alert + NSAlert* alert = nil; + + //response + NSModalResponse response = 0; + + //init alert + alert = [[NSAlert alloc] init]; + + //set style + alert.alertStyle = NSAlertStyleWarning; + + //main text + alert.messageText = messageText; + + //add details + if(nil != informativeText) + { + //details + alert.informativeText = informativeText; + } + + //add button + [alert addButtonWithTitle:@"OK"]; + + //make app active + [NSApp activateIgnoringOtherApps:YES]; + + //show + response = [alert runModal]; + + return response; +} diff --git a/Shared/XPCProtocol.h b/Shared/XPCProtocol.h deleted file mode 100644 index 701df1d..0000000 --- a/Shared/XPCProtocol.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// OverSightXPCProtocol.h -// OverSightXPC -// -// Created by Patrick Wardle on 8/16/16. -// Copyright (c) 2016 Objective-See. All rights reserved. -// - -#import - -// The protocol that this service will vend as its API. This header file will also need to be visible to the process hosting the service. -@protocol XPCProtocol - -//start enumerator --(void)initialize:(void (^)(void))reply; - -//heartbeat -// need as otherwise kernel might kill XPC --(void)heartBeat:(void (^)(BOOL))reply; - -//get (new) audio procs --(void)getAudioProcs:(void (^)(NSMutableArray *))reply; - -//get (new) video procs --(void)getVideoProcs:(BOOL)polling reply:(void (^)(NSMutableArray *))reply; - -//update status video -// ->allows enumerator to stop baselining (when active), etc --(void)updateVideoStatus:(unsigned int)status reply:(void (^)(void))reply; - -//update status video -// ->allows enumerator to stop baselining (when active), etc --(void)updateAudioStatus:(unsigned int)status reply:(void (^)(void))reply; - -//whitelist a process --(void)whitelistProcess:(NSString*)processPath device:(NSNumber*)device reply:(void (^)(BOOL))reply; - -//remove a process from the whitelist file --(void)unWhitelistProcess:(NSString*)processPath device:(NSNumber*)device reply:(void (^)(BOOL))reply; - -//kill a process --(void)killProcess:(NSNumber*)processID reply:(void (^)(BOOL))reply; - -//exit --(void)exit; - -@end diff --git a/Shared/images/objectiveSeeLogo.png b/Shared/images/objectiveSeeLogo.png new file mode 100644 index 0000000..d4ca67a Binary files /dev/null and b/Shared/images/objectiveSeeLogo.png differ diff --git a/Shared/Images/objectiveSee.png b/Shared/images/objectiveSeeText.png similarity index 100% rename from Shared/Images/objectiveSee.png rename to Shared/images/objectiveSeeText.png diff --git a/Shared/patrons.txt b/Shared/patrons.txt new file mode 100644 index 0000000..9d8db07 --- /dev/null +++ b/Shared/patrons.txt @@ -0,0 +1,5 @@ +Patrons (2^6+): +Jan Koum, Christian Blümlein, MikeyH + +Friends of Objective-See: +1Password, Jamf, Mosyle, SmugMug, Guardian Mobile Firewall, SecureMac, iVerify, Halo Privacy, uberAgent